commit c5fab8aa9488d16cc498fa454199715fdbe6c1e5 Author: AirDog46 Date: Tue May 13 19:45:22 2025 +0300 go my file uploader diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c5d7f07 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=crlf + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5fa2415 --- /dev/null +++ b/.gitignore @@ -0,0 +1,149 @@ +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.db + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +#Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +# Mac crap +.DS_Store + +########################## +## Project specific rules +########################## + +build/Installer/*.exe +ProcessHacker/include/phapprev.h +ProcessHacker/sdk/phapppub.h +plugins-extra/ +sdk/ + +KProcessHacker/*.log +KProcessHacker/*.err +KProcessHacker/*.wrn +KProcessHacker/*/objchk_* +KProcessHacker/*/objfre_* + +!plugins/SamplePlugin/bin/ +plugins/SamplePlugin/bin/Debug32/ +plugins/SamplePlugin/bin/Debug64/ +plugins/SamplePlugin/bin/Release32/* +!plugins/SamplePlugin/bin/Release32/SamplePlugin.dll +plugins/SamplePlugin/bin/Release64/* +!plugins/SamplePlugin/bin/Release64/SamplePlugin.dll + +!tools/CustomSignTool/bin/ +tools/CustomSignTool/bin/Debug*/ +tools/CustomSignTool/bin/Release*/* +!tools/CustomSignTool/bin/Release32/CustomSignTool.exe +!tools/CustomSignTool/bin/Release64/CustomSignTool.exe + +!tools/fixlib/bin/ +tools/fixlib/bin/Debug/ +tools/fixlib/bin/Release/* +!tools/fixlib/bin/Release/fixlib.exe + +!tools/GenerateHeader/GenerateHeader/bin/ +tools/GenerateHeader/GenerateHeader/bin/Debug/ +tools/GenerateHeader/GenerateHeader/bin/Release/* +!tools/GenerateHeader/GenerateHeader/bin/Release/GenerateHeader.exe + +!tools/GenerateZw/GenerateZw/bin/ +tools/GenerateZw/GenerateZw/bin/Debug/ +tools/GenerateZw/GenerateZw/bin/Release/* +!tools/GenerateZw/GenerateZw/bin/Release/GenerateZw.exe + +!tools/UpdateGitRevision/UpdateGitRevision/bin/ +tools/UpdateGitRevision/UpdateGitRevision/bin/Debug/ +tools/UpdateGitRevision/UpdateGitRevision/bin/Release/* +!tools/UpdateGitRevision/UpdateGitRevision/bin/Release/UpdateGitRevision.exe \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1660edc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "plugins-extra"] + path = plugins-extra + url = https://github.com/processhacker2/plugins-extra diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..28538c7 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,654 @@ +Process Hacker + +2.39 + * HIGHLIGHTS: + * Improved compatibility with security and anti-cheat software + * Added ability to edit process environment variables + * Fixed .NET process detection + * OTHER CHANGES: + * Improved tooltip information for dllhost.exe + * Removed Terminator + * Updated DotNetTools plugin: + * Fixed .NET assembly tab performance issues + * Added extra .NET memory counters to the .NET performance tab + * Added "Show sizes in bytes" checkbox to the .NET performance tab + * Added right-click menu to the .NET assembly tab + * Updated ExtendedTools plugin: + * Fixed "No process" disk event bug + * Updated HardwareDevices plugin: + * Fixed incorrect drive letters + * Fixed drive letter and panel clipping issue + +2.38 + * HIGHLIGHTS: + * Added labels to indicate the maximum data point in each I/O graph + * Graph grids now scale correctly when resized + * Improved high DPI scaling + * Added exploit mitigation policy information to process properties (Windows 8 and above) + * Added File modified time and File size columns for processes and modules + * Added Key modified time column for services + * Clicking a tray icon now shows the pop-up UI (useful for touch-enabled devices) + * The NetAdapters plugin has been renamed to HardwareDevices + * This plugin shows network adapter and disk drive graphs + * If you are manually upgrading, please delete NetAdapters.dll from the plugins folder + * Updated UserNotes plugin: + * Added "Collapse by default" option for processes + * OTHER CHANGES: + * Added "Start when I log on" option + * Added "Not responding" text to tray icon rich pop-up for programs that are hung + * Added right-click menu and double-click action for environment variables + * Added dialog box to show long command line strings + * Added Time stamp column for processes + * Added -sysinfo command line parameter for opening System Information at startup + * Added 32x32 icons for high DPI displays + * Digital signature verification is now performed with very low I/O priority + * Improved performance when handling a large number of threads, modules or handles + * The pop-up UI no longer displays when double-clicking the tray icon + * Fixed ASLR state being shown as N/A in process properties + * Fixed multi monitor window placement bug + * Fixed handle enumeration bug affecting processes with PID >= 65536 + * Fixed Interrupts being missing from the max CPU usage history + * Updated ToolStatus plugin: + * Added 32x32 icons for high DPI displays + * Fixed status bar crash + * NOTE: + * This release has significant internal code changes. Please make sure all plugins are up-to-date. + +2.37 + * HIGHLIGHTS: + * Updated for Windows 10 + * The "Include CPU (and other) usage of children in collapsed processes" option now aggregates memory and I/O statistics + * Added regex search to "Find Handles or DLLs" + * Added process exit codes to log + * Fixed crash that occurred under some conditions when processes terminated + * OTHER CHANGES: + * Added warning when trying to search for handles when the system has too many handles open + * Upgraded to PCRE2 + * Updated DotNetTools plugin: + * Rewrite of .NET Performance statistics and AppDomain enumeration + * Updated OnlineChecks plugin: + * Fixed virusscan.jotti.org uploader + * Updated NetAdapters plugin: + * Added adapter details window + * Updated ToolStatus plugin: + * Added CPU, Memory and I/O graphs to the toolbar (not enabled by default) + * Added toolbar and status bar customization, as well as a new theme + * Added option to auto-hide the main menu + * Updated UserNotes plugin: + * Added individual process highlighting support + +2.36 + * HIGHLIGHTS: + * New rich pop-up UI when hovering the cursor over a tray icon, showing the most active processes + * Completely new Memory tab for processes, with heap, stack and working set usage + * Process Hacker now takes 32-bit dumps of 32-bit processes on 64-bit Windows + * NOTE: When using the portable (.zip) release, the entire archive must be extracted + * Updated DotNetTools plugin: + * Process Hacker now displays managed stack traces for 32-bit .NET processes on 64-bit Windows + * Fixed inaccurate stack traces when clicking Refresh + * Added AppDomain column for threads in .NET programs + * OTHER CHANGES: + * Added customizable bytes per row setting for memory editor + * Dramatically faster handle listing and search when running without administrative privileges + * Improved accuracy and speed of symbol resolution, especially when new modules are loaded + * Added trigger and delayed start information to service list + * Added file information to service list tooltips + * Balloon tips for process/service notifications are now clickable + * Added handle names for unnamed File objects + * Added I/O Priority to tray icon process menu + * Added warning for users who attempt to start the 32-bit version on 64-bit Windows + * Updated ExtendedServices plugin: + * Added service protection and SID information + * Added auto-elevation when saving recovery information, triggers and other service settings + * Updated ExtendedTools plugin: + * Added tray icon mini info window support + * Improved automatic GPU node selection + * Updated UserNotes plugin: + * Added tray icon mini info window support + * Fixed a bug in phsvc that caused hangs when automatically elevating actions + * Fixed hang when viewing handle security for certain File objects + * Fixed lack of information on startup when using slower refresh intervals + * Fixed Read/Write Address crash + * Fixed service non-polling mode on Windows 8 and above + * Fixed file dialog crash in Windows PE environments + * Fixed string scanning false positive case + * Fixed process window detection for Modern UI apps + * Fixed handle list selection bug when disabling "Hide unnamed handles" + * NOTE: + * This release has significant internal code changes. Please make sure all plugins are up-to-date. + +2.35 + * NEW/IMPROVED: + * Added Load Time and Load Reason columns for modules (Windows 8 and above) + * Added handle names for Job and Section objects + * Added Read/Write Memory for Section objects (in process Handles tab) + * Added CF Guard (Control Flow Guard) column for processes and modules + * Added highlighting for AppContainer DLLs + * Added AppContainer and CF Guard image characteristics to peview + * Added Open Key and Open File Location menu items for services + * Set priority and I/O priority for multiple processes at once + * Support for up to 64 processors when setting process/thread affinity + * Updated ExtendedTools plugin: + * Added Disk and Network graphs for all processes + * Updated UserNotes plugin: + * Added ability to save I/O priority + * FIXED: + * Fixed memory editor copy bug + +2.34 + * NEW/IMPROVED: + * Proper Unicode support + * CPU and GPU graphs are displayed in a grid now (thanks pavel_kv!) + * Start Task Manager now elevates when necessary + * Better names for memory regions in Memory tab (for PEBs, TEBs, thread stacks) + * Added tooltip information for user-mode driver framework (UMDF) host processes + * Added option to reduce row height (set ThinRows to 1 in settings.xml) + * Added NetAdapters plugin: adds graphs for selected network adapters to the System Information window + * Updated ExtendedTools plugin: + * Added GPU graphs for all processes + * Can now use the search box in the Disk tab + * Improved kernel logger handling + * FIXED: + * Fixed touch scrolling + * Fixed EtwRegistration object names for 64-bit Windows 8.1 + * Fixed tray icons being clipped in high DPI environments + * Fixed crash in memory editor + * Fixed multi monitor window placement bug + +2.33 + * NEW/IMPROVED: + * View digital signature information from process properties and peview + * Signatures for Windows 8 apps are now detected + * Improved file, key, process and thread handle properties + * Added DPI Awareness column + * Added new Windows 8.1 process protection information + * KProcessHacker is no longer needed for highlighting of GUI threads + * Added suspend count for threads on Windows 8.1 + * Updated DotNetTools plugin: + * Improved .NET assembly enumeration timeout handling + * FIXED: + * Service start type and error control are never updated if modified outside of Process Hacker + +2.32 + * NOTE: + * All executable files are now signed. + * NEW/IMPROVED: + * Updated for Windows 8.1 + * Added progress display for thread stacks + * Updated ExtendedServices plugin: + * Added new trigger data types + * Updated NetworkTools plugin: + * Updated UI + * Updated OnlineChecks plugin: + * Added file analyzed prompt + * FIXED: + * Fixed handling of long symbol names + * Fixed Run As preventing Windows 8 apps from starting + * Fixed console host information for Windows 8.1 + * Fixed reflected processes not terminating on Windows 8.1 + * Fixed CPU frequency on Windows 8.1 + +2.31 + * NEW/IMPROVED: + * Updated ExtendedServices plugin: + * Fixed some bugs relating to Windows 8 + * Updated OnlineChecks plugin: + * Added upload progress + * Updated UserNotes plugin: + * Fixed bug where process priorities were not actually saved + * FIXED: + * Fixed module list not updating properly + * DLL enumeration crash + +2.30 + * NEW/IMPROVED: + * Added "Icon click toggles visibility" option + * Re-enabled powerful process termination on 32-bit Windows 8 + * Updated UserNotes plugin: + * Added ability to save process priority + * Added "Only for processes with the same command line" option for process comments + * FIXED: + * Fixed crash on CPUs without SSE2 + +2.29 + * NEW/IMPROVED: + * Added App ID column for processes + * Added new ASLR information for Windows 8 + * Added Restart to Boot Options and Hybrid Shutdown menu items for Windows 8 + * Added ability to specify processes by their names and inject and unload DLLs in command line + * Removed 512 character limit when copying text + * Moved Terminator to Miscellaneous menu + * Updated default dbghelp.dll path for Windows SDK v8 + * Updated ExtendedServices plugin: + * Added new triggers for Windows 8 + * Fixed bug when restarting services + * Updated ExtendedTools plugin: + * Improved support for multiple GPUs (again) + * GPU column now respects "Include CPU usage of children" option + * Updated ToolStatus plugin: + * Fixed search box fonts + * Fixed controls not being properly hidden/removed from the window when disabled + * Updated WindowExplorer plugin: + * Fixed window list not displaying Modern UI windows + * FIXED: + * Fixed Load Count column sorting bug + * Fixed signature verification on Windows 8 + * Fixed task scheduler information on Windows 8 + * Fixed drag bug in tree list + * Fixed KProcessHacker bug affecting TmTx objects + * Fixed Run As feature on Windows 8 + * Fixed bug where -settings parameter is not propagated + * Fixed tab key behavior on main window + * Fixed recognition of Modern UI windows + +2.28 + * NEW/IMPROVED: + * peview now resolves .lnk targets + * Fixed Ctrl+A for processes, services and network connections and added Ctrl+A for other windows + * Changed confirmation prompts to select the destructive action by default + * Updated DotNetTools plugin: + * Fixed inaccurate stack traces for certain .NET programs + * Updated ExtendedTools plugin: + * Fixed network graph scaling + * Updated ToolStatus plugin: + * Added search box + * Updated Updater plugin + * FIXED: + * Fixed Verification Status column sorting bug in module list + * Fixed rare System Information crash + * Fixed bug in opening process handles + * Fixed freezing when viewing stack traces of certain system threads + +2.27 + * NEW/IMPROVED: + * Updated OnlineChecks plugin: + * 2012-01-16: Updated VirusTotal uploader and added hash checking + * FIXED: + * Fixed Description column sorting bug + * Fixed notification icon bug + +2.26 + * NEW/IMPROVED: + * Added option to show Commit Charge in system information summary view + * Added -priority and -selectpid command line options + * Updated ExtendedTools plugin: + * Improved support for multiple GPUs + * FIXED: + * Fixed 100% CPU when starting on some machines + +2.25 + * NEW/IMPROVED: + * Improved CPU frequency calculation + * Updated ExtendedTools plugin: + * Added GPU node selection + * Fixed incorrect GPU usage calculation + * FIXED: + * Graph tooltip position with large cursors + * Fixed .NET process detection + * Fixed incorrect values in Bits column + +2.24 + * NOTE: + * This release has significant internal code changes. Please make sure all plugins are up-to-date. + * NEW/IMPROVED: + * Completely new system information window + * Added option to scroll to new processes + * Added option to hide driver services + * Added menu item to copy individual cells + * Improved module scanning + * Added Start Task Manager menu item + * Added Image base to peview + * Updated ExtendedTools plugin: + * Added support for new system information window + * Added Disk, Network and GPU tray icons + * Added support for custom fonts in the Disk tab + * Updated Updater plugin: + * Added download speed + * Added remaining time + * FIXED: + * Fixed retrieval of version information for certain files + * Fixed driver file names on Windows XP + * Fixed Run As Administrator when used with complex commands + +2.23 + * NEW/IMPROVED: + * Added display of token capabilities, user/device claims and security attributes + * Added ability to change token integrity levels + * Added Description column to service list + * Added option to reset all settings + * Made grid color darker + * Enabled multi-selection in the hidden processes window + * Added UserNotes plugin + * Updated ExtendedNotifications plugin: + * Added Growl support + * Updated ExtendedTools plugin: + * Added GPU monitoring + * Added rate columns for disk and network I/O + * FIXED: + * Fixed copying lists when plugin columns are enabled + * Freezing when viewing the tooltip for a process with a very long command line + * Disabled Hidden Processes feature on 64-bit systems + +2.22 + * NEW/IMPROVED: + * Added highlighting for metro style apps + * Added Package Name column + * Added package name to process tooltip + * Improved .NET process detection + * Updated OS Context column for Windows 8 + * Updated ExtendedTools plugin: + * Updated disk monitoring for Windows 8 + * Updated memory list information for Windows 8 + * Updated WindowExplorer plugin: + * Fixed hook support for low integrity processes + * FIXED: + * Fixed memory leaks + * Fixed bug preventing Interrupts/DPCs from being shown as the max. CPU process on 64-bit systems + * Fixed DEP Status column on 64-bit systems + +2.21 + * NEW/IMPROVED: + * Added Private Bytes Delta, ASLR and Subsystem columns + * Added ASLR and Time Stamp columns to modules list + * Added check for debugger in Terminator + * FIXED: + * Fixed Show CPU Below 0.01 not respecting locale + * Fixed copying from network list + +2.20 + * NEW/IMPROVED: + * Added support for managed thread stacks on x64 + * Added column selection for handle list + * Added CPU column to threads list + * Improved module detection + * Added Ideal Processor to Threads tab + * Added pool usage and minimum/maximum working set columns + * Implemented Properties button for Thread handles + * Set descending sort as the default for most numeric columns + * Extended header context menu + * Removed tooltip text truncation + * Improved cycle-based CPU usage calculation + * Set default KProcessHacker security level to only allow connections when Process Hacker is running as administrator. + See README.txt for instructions on how to restore the old behavior. + * Added Updater plugin + * Updated DotNetTools plugin: + * Added managed symbol resolution for thread stacks + * Updated ExtendedTools plugin: + * Added Disk tab + * Added Hard Faults, Hard Faults Delta and Peak Threads columns to process tree list + * Added Firewall Status column + * FIXED: + * Fixed file name resolution bug + * Save settings on shutdown/logoff + * Fixed state highlighting bug + * Fixed command line propagation for -elevate + * Fixed tree list mouse wheel handling + * Fixed saving network list + +2.19 + * NEW/IMPROVED: + * Added cycle-based CPU usage for Windows 7 + * Added Show CPU Below 0.01 + * Added OS Context column + * Rewrote graph drawing code for improved performance + * Optimized retrieval of cycle time and private working set information for Windows 7 + * Added Open File Location to process context menu and reorganized some items + * Added checkboxes to Terminator + * FIXED: + * Crash when sorting by Time Stamp + * GDI handle leak in drag selection + +2.18 + * NEW/IMPROVED: + * Completely rewritten tree list control: + * Process Name column is now fixed to the left + * Tooltips for column headers + * Improved performance + * Bug fixes + * Added more process tree list columns + * Added Time stamp column to network list + * Date/time display is now swapped (so time is shown before date) + * Added W3 terminator test + * Added DotNetTools plugin + * Updated ExtendedServices plugin: + * Disabled editing of required privileges for drivers + * Updated ExtendedTools plugin: + * Added ETW columns for processes and network connections + * Updated OnlineChecks plugin: + * Added Comodo Instant Malware Analysis + * Updated WindowExplorer plugin: + * Fixed hook bugs + * FIXED: + * Fixed Run As This User + * Verification Status sorting + +2.17 + * NEW/IMPROVED: + * Added support for setting page priority + * Added elevation support for setting priority + * Added support for automatically using a settings file in the program directory (e.g. ProcessHacker.exe.settings.xml) + * Improved Run As mechanism + * Updated ExtendedServices plugin: + * Added support for editing triggers + * Added support for editing preshutdown time-out + * Added support for editing required privileges + * Added elevation support for restarting services + * Updated WindowExplorer plugin: + * Added more window properties + * FIXED: + * Handle leak + +2.16 + * NEW/IMPROVED: + * Updated WindowExplorer plugin + * PE viewer: Added version string to CLR tab + * PE viewer: Added display of delay imports + * PE viewer: Added Load Config tab + * Improved wait analysis + * Added arrows to the service list to indicate whether a service is running + * FIXED: + * Fixed the IPv6-related workaround causing crashes + * Incorrect handling of window positions + +2.15 + * NEW/IMPROVED: + * Updated ExtendedServices plugin + * Updated ToolStatus plugin + * Added DEP Status column + * Improved User Name column + * FIXED: + * Image file versions + * Workaround for an IPv6-related bug in Windows XP + * DPCs and Interrupts in System Information tooltips + * File dialog crash on Windows XP + * ExtendedTools plugin: WS Watch refresh bug + +2.14 + * NEW/IMPROVED: + * ExtendedServices plugin: Option to add a Services menu for processes + * Command line support for setting process priority and I/O priority + * Improved termination of explorer.exe + * FIXED: + * Icon should restore the main window if it is minimized + * System Information window crashes + * Hide Processes From Other Users and Hide Signed Processes settings are now saved + * Font selection on Windows XP + * ToolStatus plugin: Always on Top status being reset by Find Window + * Service-related crashes + * WindowExplorer plugin: sorting in tree list + * Process minidump creation with old versions of dbghelp.dll + +2.13 + * NEW/IMPROVED: + * Added copy support to PE viewer + * Added Connect Time, Disconnect Time and Last Input Time to session properties + * Added more working set counters to the Statistics tab + * FIXED: + * Column sort arrows + * CPU usage calculations + +2.12 + * NEW/IMPROVED: + * Updated KProcessHacker for Windows 7 SP1 + * Added elevation support for more actions + * Added ability to disable plugins + * Updated ToolStatus plugin + * Added Remote Control for sessions + * More command line options + * FIXED: + * Memory leaks + * Run As issues with different sessions + +2.11 + * NEW/IMPROVED: + * Added WS Watch and other features to ExtendedTools plugin + * Added WindowExplorer plugin + * Properties for hidden processes + * Improved menus + * Debug console can now be closed without affecting the entire program + * FIXED: + * Always on Top issues + * Hang when setting DEP status of a terminating process + * Encoding bug in NetworkTools plugin + * LSA interfacing issues + * Creating dumps of self + +2.10 + * NEW/IMPROVED: + * KProcessHacker is now signed, so it works on 64-bit systems. Thank you to the ReactOS Foundation. + * Added Run As Limited User + * Added CPU, private bytes and I/O history columns + * Added font selection + * Slightly improved highlighting configuration + * FIXED: + * High DPI support + * Multi-monitor support in graph tooltips + * DEP status retrieval + * ExtendedTools plugin crash + * Notification icon menu crash + * Memory leaks + * Other small bug fixes + +2.9 + * NEW/IMPROVED: + * Added column selection for modules list + * Added wait analysis for 64-bit systems + * Added signature verification for modules + * Added ExtendedTools plugin (Vista and above only) with Disk and Network information + * Updated ExtendedNotifications plugin: added ability to log events to a file + * Updated ExtendedServices plugin: new tab on Vista and above + * Updated ToolStatus plugin: resolves ghost windows to hung windows + * Environment variables and current directory are now correctly shown for WOW64 processes + * I/O priority names are now used instead of numbers + * FIXED: + * Network list bug + * Memory leaks + +2.8 + * NEW/IMPROVED: + * Better service list (including column selection) + * Added Peak Handles + * Process tree sorting is now preserved + * Save works for services and network connections + * Pausing now works correctly with the Network tab + * Added option to display inclusive CPU usages for collapsed processes + * Added CLR tab to peview + * Added ability to destroy heaps + * Improved process tree list appearance + * Certain command line parameters are now propagated + * FIXED: + * Icon handling bugs + * Memory leaks + * Extended tooltips for WOW64 processes + +2.7 + * NEW/IMPROVED: + * Vastly improved startup time and lower memory usage + * Added Cycles and Cycles Delta columns + * Added option to disable address resolution for network connections + * Added Logon Time to session properties + * Added time stamp display to peview + * FIXED: + * ToolStatus layout problems + * .NET highlighting crashes + * Run As on Windows XP + +2.6 + * NEW/IMPROVED: + * Sorting for most lists is now much faster + * Hide Signed Processes option + * Added plugin for uploading files to online virus scanners + * Added Network tools plugin + * Updated ExtendedServices plugin + * PE viewer now verifies checksums + * Performance improvements + * FIXED: + * Fixed service handle leak + +2.5 + * NEW/IMPROVED: + * Unmap section views in Memory tab + * Plugin for extended service information (including recovery information, dependencies and dependents) + * FIXED: + * Critical bug for file dialogs on Windows XP + * Esc couldn't close Service Properties on open + * Small bug fixes + +2.4 + * NEW/IMPROVED: + * Better Run As behaviour + * Show Processes From All Users option + * Can now unmap section views + * Control over thread affinity + * Window Title and Window Status columns + * Plugin for filtering notifications + * Plugin for toolbar and status bar + * Performance improvements + * FIXED: + * Memory leak + * SbieSupport plugin on 64-bit + * Crash when running under certain conditions + * Memory case-insensitive filter + * Process parent association bug + * REMOVED: + * Process database + +2.3 + * NEW/IMPROVED: + * Can add processes to jobs + * Double-clicking in the system information graphs now opens information for the relevant process + * Setting I/O priority doesn't need KProcessHacker anymore + * Elevation for certain actions + * FIXED: + * HKCU key name resolution + * Network connection host resolution + * Information window resizing + * Log clearing + +2.2 + * NEW/IMPROVED: + * Plugins support + * Can now unload 32-bit modules on 64-bit systems + * Tasks are shown in tooltips for taskeng.exe/taskhost.exe processes + * Run As can now start processes elevated + * Handle count by type + * Process priorities in notification icon menu + * CSV export + * Relative start times + * FIXED: + * Run and Run As shortcuts + * Command line handling + * Process tree selection + +2.1 + * NEW/IMPROVED: + * Add Pause key shortcut to pause/resume updates + * Added Ctrl+Tab and Ctrl+Shift+Tab shortcuts + * Grid is a bit darker + * Checks for digital signatures and packing is now off by default and optional + * FIXED: + * MD5 calculation code for files was wrong + * Process record bugs + +2.0 + * First release in the Process Hacker 2.x branch. diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt new file mode 100644 index 0000000..5469a3b --- /dev/null +++ b/COPYRIGHT.txt @@ -0,0 +1,132 @@ +== Process Hacker == +Process Hacker is licensed under the GNU GPL v3, with exceptions. A full +copy of the license is provided in LICENSE.txt. + + Copyright (C) 2009-2016 wj32 and various authors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +== Mini-XML == +Process Hacker uses Mini-XML licensed under the following terms: + + The Mini-XML library and included programs are provided under the + terms of the GNU Library General Public License (LGPL) with the + following exceptions: + + 1. Static linking of applications to the Mini-XML library + does not constitute a derivative work and does not require + the author to provide source code for the application, use + the shared Mini-XML libraries, or link their applications + against a user-supplied version of Mini-XML. + + If you link the application to a modified version of + Mini-XML, then the changes to Mini-XML must be provided + under the terms of the LGPL in sections 1, 2, and 4. + + 2. You do not have to provide a copy of the Mini-XML license + with programs that are linked to the Mini-XML library, nor + do you have to identify the Mini-XML license in your + program or documentation as required by section 6 of the + LGPL. + +== PCRE == +Process Hacker uses Perl-Compatible Regular Expressions licensed under the +following terms: + + PCRE is a library of functions to support regular expressions whose syntax + and semantics are as close as possible to those of the Perl 5 language. + + Release 8 of PCRE is distributed under the terms of the "BSD" licence, as + specified below. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the name of Google + Inc. nor the names of their contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +== MD5 == +Process Hacker uses a MD5 implementation licensed under the following terms: + + MD5 hash implementation and interface functions + Copyright (c) 2003-2005, Jouni Malinen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + +== SHA == +Process Hacker uses a SHA implementation licensed under the following terms: + + Copyright 2004 Filip Navara + Based on public domain SHA code by Steve Reid + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +== Natural order string comparison == +Process Hacker uses "strnatcmp.c" licensed under the following terms: + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + This code has been modified for Process Hacker. diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..c7b8011 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1753 @@ +# Doxyfile 1.7.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "Process Hacker" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./KProcessHacker \ + ./ProcessHacker \ + ./phlib \ + ./plugins \ + ./tools \ + ./tests + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = ./plugins-extra/GfxPlugin/include + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is adviced to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..f9326d4 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,205 @@ +## Building +Process Hacker must be built using a Microsoft C compiler. Do not attempt to use any other compiler or be prepared to spend a long time trying to fix things. The only tested IDE is Visual Studio 2015. + +The Windows SDK 10 must be installed. To create a XP-compatible driver, KProcessHacker must be built using WDK v7, not the latest WDK. + +## Conventions + +### Names +* Functions, function parameters, and global variables use CamelCase. +* Local variables use lowerCamelCase. +* Structs, enums and unions use CAPS_WITH_UNDERSCORES. + +All names must have an appropriate prefix (with some exceptions): + +* "Ph" or "PH_" (structures) for public names. +* "Php" or "PHP_" for private names. +* Some variants such as "Pha". +* Private prefixes are created by appending "p" to the prefix. E.g. "Ph" -> "Php", "Pha" -> "Phap". +* Functions and global variables without a prefix must be declared "static". + * "static" names must not have a public prefix. + * Names with a private prefix do not have to be "static". +* Structures without a prefix must be declared in a ".c" file. Structures declared in a ".c" file may or may not have a prefix. + +### Types +Unless used for the Win32 API, the standard types are: + +* `BOOLEAN` for a 1 byte boolean, or `LOGICAL` for a 4 byte boolean. +* `UCHAR` for 1 byte. +* `SHORT`/`USHORT` for 2 bytes. +* `LONG`/`ULONG` for 4 bytes. +* `LONG64`/`ULONG64` for 8 bytes. +* `CHAR` for a 1 byte character. +* `WCHAR` for a 2 byte character. +* `PSTR` for a string of 1 byte characters. +* `PWSTR` for a string of 2 byte characters. + +#### Booleans +Always use: + +``` + if (booleanVariable) // not "if (booleanVariable == TRUE)" + { + ... + } +``` + +to test a boolean value. + +### Annotations, qualifiers +* All functions use SAL annotations, such as `_In_`, `_Inout_`, `_Out_`, etc. +* Do not use `const`, unless obvious optimizations can be made by the compiler (e.g. inlining). +* Do not use `volatile` in definitions. Instead, cast to a volatile pointer when necessary. + +### Function success indicators +There are three main types of indicators used: + +* A `NTSTATUS` value is returned. The `NT_SUCCESS` macro checks if a status value indicates success. +* A `BOOLEAN` value is returned. `TRUE` indicates success. +* The result of the function is returned (e.g. a pointer). A special value (e.g. `NULL`) indicates failure. + +Unless indicated, a function which fails is guaranteed not to modify any of its output parameters (`_Out_`, `_Out_opt_`, etc.). + +For functions which are passed a callback function, it is not guaranteed that a failed function has not executed the callback function. + +### Threads +Every thread start routine must have the following signature: + +``` + NTSTATUS NameOfRoutine( + _In_ PVOID Parameter + ); +``` + +Thread creation is done through the `PhCreateThread` function. + +### Collections +The collections available are summarized below: + +Name | Use | Type +--------------------- | ----------------------- | --------------- +`PH_ARRAY` | Array | Non-intrusive +`PH_LIST` | Array | Non-intrusive +`LIST_ENTRY` | Doubly linked list | Intrusive +`SINGLE_LIST_ENTRY` | Singly linked list | Intrusive +`PH_POINTER_LIST` | Array | Non-intrusive +`LIST_ENTRY` | Stack | Intrusive +`SINGLE_LIST_ENTRY` | Stack | Intrusive +`LIST_ENTRY` | Queue | Intrusive +`RTL_AVL_TABLE` | Binary tree (AVL) | Non-intrusive +`PH_AVL_LINKS` | Binary tree (AVL) | Intrusive +`RTL_GENERIC_TABLE` | Binary tree (splay) | Non-intrusive +`PH_HASHTABLE` | Hashtable | Non-intrusive +`PH_HASH_ENTRY` | Hashtable | Intrusive +`PH_CIRCULAR_BUFFER` | Circular buffer | Non-intrusive + +### Synchronization +The queued lock should be used for all synchronization, due to its small size and good performance. Although the queued lock is a reader-writer lock, it can be used as a mutex simply by using the exclusive acquire/release functions. + +Events can be used through `PH_EVENT`. This object does not create a kernel event object until needed, and testing its state is very fast. + +Rundown protection is available through `PH_RUNDOWN_PROTECT`. + +Condition variables are available using the queued lock. Simply declare and initialize a queued lock variable, and use the `PhPulse(All)Condition` and `PhWaitForCondition` functions. + +Custom locking with low overhead can be built using the wake event, built on the queued lock. Test one or more conditions in a loop and use `PhQueueWakeEvent`/`PhWaitForWakeEvent` to block. When a condition is modified use `PhSetWakeEvent` to wake waiters. If after calling `PhQueueWakeEvent` it is determined that no blocking should occur, use `PhSetWakeEvent`. + +### Exceptions (SEH) +The only method of error handling used in Process Hacker is the return value (`NTSTATUS`, `BOOLEAN`, etc.). Exceptions are used for exceptional situations which cannot easily be recovered from (e.g. a lock acquire function fails to block, or an object has a negative reference count. + +Exceptions to this rule include: + +* `PhAllocate`, which raises an exception if it fails to allocate. Checking the return value of each allocation to increase reliability is not worth the extra effort involved, as failed allocations are very rare. +* `PhProbeAddress`, which raises an exception if an address lies outside of a specified range. Raising an exception makes it possible to conduct multiple checks in one SEH block. +* `STATUS_NOT_IMPLEMENTED` exceptions triggered by code paths which should not be reached, purely due to programmer error. `assert(FALSE)` could also be used in this case. + +### Memory management +Use `PhAllocate`/`PhFree` to allocate/free memory. For complex objects, use the reference counting system. + +There is semi-automatic reference counting available in the form of auto-dereference pools (similar to Apple's `NSAutoreleasePool`s). Use the `PhAutoDereferenceObject` to add an object to the thread's pool, and the object will be dereferenced at an unspecified time in the future. However, the object is guaranteed to not be dereferenced while the current function is executing. + +Referencing an object is necessary whenever a pointer to the object is stored in a globally visible location or passed to another thread. In most other cases, referencing is not necessary. + +All objects passed to functions must have a guaranteed reference for the duration of that call. One mistake is to keep a reference which could be destroyed in a window procedure, and to use that reference implicitly inside the window procedure. Messages can still be pumped (e.g. dialog boxes) while the window procedure is executing, so the window procedure must reference the object as soon as possible. + +### Names (2) + +#### Object creation/deletion +* Allocate means allocate memory without initialization. +* Create means allocate memory for an object and initialize the object. +* Free can be used for objects created with Allocate or Create functions. +* Destroy can also be used for objects created with Create functions. +* Initialize means initialize an object with caller-supplied storage. +* Delete is paired with Initialize and does not free the object as it was allocated by the caller. +* Create is used when objects are being created through the reference counting system. + +Examples: +* `PhAllocateFromFreeList`/`PhFreeToFreeList` +* `PhCreateFileDialog`/`PhFreeFileDialog` +* `PhInitializeWorkQueue`/`PhDeleteWorkQueue` +* `PhCreateString`/`PhDereferenceObject` + +#### Element counts +* Length specifies the length in bytes. E.g. `PhCreateString` requires the length to be specified in bytes. +* Count specifies the length in elements. E.g. `PhSubstring` requires the length to be specified in characters. +* Index specifies the index in elements. E.g. `PhSubstring` requires the index to be specified in characters. + +When null terminated strings are being written to output, the return count, if any, must be specified as the number of characters written including the null terminator. + +### Strings +Strings use the `PH_STRING` type, managed by reference counting. To create a string object from a null-terminated string: + +``` + PPH_STRING myString = PhCreateString(L"My string"); + + wprintf( + L"My string is \"%s\", and uses %Iu bytes.\n", + myString->Buffer, + myString->Length + ); +``` + +All string objects have an embedded length (always in bytes), and the string is additionally null-terminated for compatibility reasons. + +String objects must be treated as immutable unless a string object is created and modified before the pointer is shared with any other functions or stored in any global variables. This exception applies only when creating a string using `PhCreateString` or `PhCreateStringEx`. + +Strings can be concatenated with `PhConcatStrings`: + +``` + PPH_STRING newString; + + newString = PhConcatStrings( + 4, + L"My first string, ", + L"My second string, ", + aStringFromSomewhere, + L"My fourth string." + ); +``` + +Another version concatenates two strings: + +``` + PPH_STRING newString; + + newString = PhConcatStrings2( + L"My first string, ", + L"My second string." + ); +``` + +Strings can be formatted: + +``` + PPH_STRING newString; + + newString = PhFormatString( + L"%d: %s, %#x", + 100, + L"test", + 0xff + ); +``` + +### Tips + * Use !! to "cast" to a boolean. diff --git a/KProcessHacker/KProcessHacker.vcxproj b/KProcessHacker/KProcessHacker.vcxproj new file mode 100644 index 0000000..87fb632 --- /dev/null +++ b/KProcessHacker/KProcessHacker.vcxproj @@ -0,0 +1,139 @@ + + + + + Win8 Debug + Win32 + + + Win8 Release + Win32 + + + Win8 Debug + x64 + + + Win8 Release + x64 + + + + {F4853009-C5D2-4A25-BE4D-BB0D9F84E2FF} + {dd38f7fc-d7bd-488b-9242-7d8754cde80d} + v4.5 + 11.0 + Win8 Debug + Win32 + + + KProcessHacker + $(VCTargetsPath11) + + + WindowsKernelModeDriver8.0 + Driver + WDM + + + + Windows8 + true + + + Windows8 + false + + + Windows8 + true + + + Windows8 + false + + + + + + + + + + DbgengKernelDebugger + + + $(ProjectDir)bin\$(Configuration) $(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration) $(PlatformArchitecture)\ + kprocesshacker + + + $(ProjectDir)bin\$(Configuration) $(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration) $(PlatformArchitecture)\ + kprocesshacker + + + $(ProjectDir)bin\$(Configuration) $(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration) $(PlatformArchitecture)\ + kprocesshacker + + + $(ProjectDir)bin\$(Configuration) $(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration) $(PlatformArchitecture)\ + kprocesshacker + + + + ../phnt/include;../phlib/include;include;$(IntDir);%(AdditionalIncludeDirectories) + KPH_CONFIG_CLEAN;_X86_=1;i386=1;STD_CALL;%(PreprocessorDefinitions) + Level3 + + + + + ../phnt/include;../phlib/include;include;$(IntDir);%(AdditionalIncludeDirectories) + KPH_CONFIG_CLEAN;_X86_=1;i386=1;STD_CALL;%(PreprocessorDefinitions) + Level3 + + + + + ../phnt/include;../phlib/include;include;$(IntDir);%(AdditionalIncludeDirectories) + KPH_CONFIG_CLEAN;_WIN64;_AMD64_;AMD64;%(PreprocessorDefinitions) + Level3 + + + + + ../phnt/include;../phlib/include;include;$(IntDir);%(AdditionalIncludeDirectories) + KPH_CONFIG_CLEAN;_WIN64;_AMD64_;AMD64;%(PreprocessorDefinitions) + Level3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KProcessHacker/KProcessHacker.vcxproj.filters b/KProcessHacker/KProcessHacker.vcxproj.filters new file mode 100644 index 0000000..9c11950 --- /dev/null +++ b/KProcessHacker/KProcessHacker.vcxproj.filters @@ -0,0 +1,66 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/KProcessHacker/bin-signed/amd64/kprocesshacker.sys b/KProcessHacker/bin-signed/amd64/kprocesshacker.sys new file mode 100644 index 0000000..dd0e59f Binary files /dev/null and b/KProcessHacker/bin-signed/amd64/kprocesshacker.sys differ diff --git a/KProcessHacker/bin-signed/i386/kprocesshacker.sys b/KProcessHacker/bin-signed/i386/kprocesshacker.sys new file mode 100644 index 0000000..e0fc898 Binary files /dev/null and b/KProcessHacker/bin-signed/i386/kprocesshacker.sys differ diff --git a/KProcessHacker/clean/makefile b/KProcessHacker/clean/makefile new file mode 100644 index 0000000..05a507b --- /dev/null +++ b/KProcessHacker/clean/makefile @@ -0,0 +1 @@ +!INCLUDE $(NTMAKEENV)\makefile.def \ No newline at end of file diff --git a/KProcessHacker/clean/sources b/KProcessHacker/clean/sources new file mode 100644 index 0000000..53a0d69 --- /dev/null +++ b/KProcessHacker/clean/sources @@ -0,0 +1,7 @@ +!IF 0 + +This builds a clean version of KProcessHacker suitable for driver signing. + +!ENDIF + +!include ..\sources.inc diff --git a/KProcessHacker/devctrl.c b/KProcessHacker/devctrl.c new file mode 100644 index 0000000..5e60dfb --- /dev/null +++ b/KProcessHacker/devctrl.c @@ -0,0 +1,566 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +NTSTATUS KphDispatchDeviceControl( + __in PDEVICE_OBJECT DeviceObject, + __in PIRP Irp + ) +{ + NTSTATUS status; + PIO_STACK_LOCATION stackLocation; + PFILE_OBJECT fileObject; + PKPH_CLIENT client; + PVOID originalInput; + ULONG inputLength; + ULONG ioControlCode; + KPROCESSOR_MODE accessMode; + UCHAR capturedInput[16 * sizeof(ULONG_PTR)]; + PVOID capturedInputPointer; + +#define VERIFY_INPUT_LENGTH \ + do { \ + /* Ensure at compile time that our local buffer fits this particular call. */ \ + C_ASSERT(sizeof(*input) <= sizeof(capturedInput)); \ + \ + if (inputLength != sizeof(*input)) \ + { \ + status = STATUS_INFO_LENGTH_MISMATCH; \ + goto ControlEnd; \ + } \ + } while (0) + + stackLocation = IoGetCurrentIrpStackLocation(Irp); + fileObject = stackLocation->FileObject; + client = fileObject->FsContext; + + originalInput = stackLocation->Parameters.DeviceIoControl.Type3InputBuffer; + inputLength = stackLocation->Parameters.DeviceIoControl.InputBufferLength; + ioControlCode = stackLocation->Parameters.DeviceIoControl.IoControlCode; + accessMode = Irp->RequestorMode; + + // Make sure we have a client object. + if (!client) + { + status = STATUS_INTERNAL_ERROR; + goto ControlEnd; + } + + // Enforce signature requirement if necessary. + if ((ioControlCode != KPH_GETFEATURES && ioControlCode != KPH_VERIFYCLIENT) && + (KphParameters.SecurityLevel == KphSecuritySignatureCheck || + KphParameters.SecurityLevel == KphSecuritySignatureAndPrivilegeCheck) && + !client->VerificationSucceeded) + { + status = STATUS_ACCESS_DENIED; + goto ControlEnd; + } + + // Make sure we actually have input if the input length is non-zero. + if (inputLength != 0 && !originalInput) + { + status = STATUS_INVALID_BUFFER_SIZE; + goto ControlEnd; + } + + // Make sure the caller isn't giving us a huge buffer. If they are, it can't be correct because + // we have a compile-time check that makes sure our buffer can store the arguments for all the + // calls. + if (inputLength > sizeof(capturedInput)) + { + status = STATUS_INVALID_BUFFER_SIZE; + goto ControlEnd; + } + + // Probe and capture the input buffer. + if (accessMode != KernelMode) + { + __try + { + ProbeForRead(originalInput, inputLength, sizeof(UCHAR)); + memcpy(capturedInput, originalInput, inputLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + goto ControlEnd; + } + } + else + { + memcpy(capturedInput, originalInput, inputLength); + } + + capturedInputPointer = capturedInput; // avoid casting below + + switch (ioControlCode) + { + case KPH_GETFEATURES: + { + struct + { + PULONG Features; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiGetFeatures( + input->Features, + accessMode + ); + } + break; + case KPH_VERIFYCLIENT: + { + struct + { + PVOID CodeAddress; + PVOID Signature; + ULONG SignatureSize; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + if (accessMode == UserMode) + { + status = KpiVerifyClient( + input->CodeAddress, + input->Signature, + input->SignatureSize, + client + ); + } + else + { + status = STATUS_UNSUCCESSFUL; + } + } + break; + case KPH_RETRIEVEKEY: + { + struct + { + KPH_KEY_LEVEL KeyLevel; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + if (accessMode == UserMode) + { + status = KphRetrieveKeyViaApc( + client, + input->KeyLevel, + Irp + ); + } + else + { + status = STATUS_UNSUCCESSFUL; + } + } + break; + case KPH_OPENPROCESS: + { + struct + { + PHANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PCLIENT_ID ClientId; + KPH_KEY Key; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenProcess( + input->ProcessHandle, + input->DesiredAccess, + input->ClientId, + input->Key, + client, + accessMode + ); + } + break; + case KPH_OPENPROCESSTOKEN: + { + struct + { + HANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PHANDLE TokenHandle; + KPH_KEY Key; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenProcessToken( + input->ProcessHandle, + input->DesiredAccess, + input->TokenHandle, + input->Key, + client, + accessMode + ); + } + break; + case KPH_OPENPROCESSJOB: + { + struct + { + HANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PHANDLE JobHandle; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenProcessJob( + input->ProcessHandle, + input->DesiredAccess, + input->JobHandle, + accessMode + ); + } + break; + case KPH_TERMINATEPROCESS: + { + struct + { + HANDLE ProcessHandle; + NTSTATUS ExitStatus; + KPH_KEY Key; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiTerminateProcess( + input->ProcessHandle, + input->ExitStatus, + input->Key, + client, + accessMode + ); + } + break; + case KPH_READVIRTUALMEMORYUNSAFE: + { + struct + { + HANDLE ProcessHandle; + PVOID BaseAddress; + PVOID Buffer; + SIZE_T BufferSize; + PSIZE_T NumberOfBytesRead; + KPH_KEY Key; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiReadVirtualMemoryUnsafe( + input->ProcessHandle, + input->BaseAddress, + input->Buffer, + input->BufferSize, + input->NumberOfBytesRead, + input->Key, + client, + accessMode + ); + } + break; + case KPH_QUERYINFORMATIONPROCESS: + { + struct + { + HANDLE ProcessHandle; + KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass; + PVOID ProcessInformation; + ULONG ProcessInformationLength; + PULONG ReturnLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiQueryInformationProcess( + input->ProcessHandle, + input->ProcessInformationClass, + input->ProcessInformation, + input->ProcessInformationLength, + input->ReturnLength, + accessMode + ); + } + break; + case KPH_SETINFORMATIONPROCESS: + { + struct + { + HANDLE ProcessHandle; + KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass; + PVOID ProcessInformation; + ULONG ProcessInformationLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiSetInformationProcess( + input->ProcessHandle, + input->ProcessInformationClass, + input->ProcessInformation, + input->ProcessInformationLength, + accessMode + ); + } + break; + case KPH_OPENTHREAD: + { + struct + { + PHANDLE ThreadHandle; + ACCESS_MASK DesiredAccess; + PCLIENT_ID ClientId; + KPH_KEY Key; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenThread( + input->ThreadHandle, + input->DesiredAccess, + input->ClientId, + input->Key, + client, + accessMode + ); + } + break; + case KPH_OPENTHREADPROCESS: + { + struct + { + HANDLE ThreadHandle; + ACCESS_MASK DesiredAccess; + PHANDLE ProcessHandle; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenThreadProcess( + input->ThreadHandle, + input->DesiredAccess, + input->ProcessHandle, + accessMode + ); + } + break; + case KPH_CAPTURESTACKBACKTRACETHREAD: + { + struct + { + HANDLE ThreadHandle; + ULONG FramesToSkip; + ULONG FramesToCapture; + PVOID *BackTrace; + PULONG CapturedFrames; + PULONG BackTraceHash; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiCaptureStackBackTraceThread( + input->ThreadHandle, + input->FramesToSkip, + input->FramesToCapture, + input->BackTrace, + input->CapturedFrames, + input->BackTraceHash, + accessMode + ); + } + break; + case KPH_QUERYINFORMATIONTHREAD: + { + struct + { + HANDLE ThreadHandle; + KPH_THREAD_INFORMATION_CLASS ThreadInformationClass; + PVOID ThreadInformation; + ULONG ThreadInformationLength; + PULONG ReturnLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiQueryInformationThread( + input->ThreadHandle, + input->ThreadInformationClass, + input->ThreadInformation, + input->ThreadInformationLength, + input->ReturnLength, + accessMode + ); + } + break; + case KPH_SETINFORMATIONTHREAD: + { + struct + { + HANDLE ThreadHandle; + KPH_THREAD_INFORMATION_CLASS ThreadInformationClass; + PVOID ThreadInformation; + ULONG ThreadInformationLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiSetInformationThread( + input->ThreadHandle, + input->ThreadInformationClass, + input->ThreadInformation, + input->ThreadInformationLength, + accessMode + ); + } + break; + case KPH_ENUMERATEPROCESSHANDLES: + { + struct + { + HANDLE ProcessHandle; + PVOID Buffer; + ULONG BufferLength; + PULONG ReturnLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiEnumerateProcessHandles( + input->ProcessHandle, + input->Buffer, + input->BufferLength, + input->ReturnLength, + accessMode + ); + } + break; + case KPH_QUERYINFORMATIONOBJECT: + { + struct + { + HANDLE ProcessHandle; + HANDLE Handle; + KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass; + PVOID ObjectInformation; + ULONG ObjectInformationLength; + PULONG ReturnLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiQueryInformationObject( + input->ProcessHandle, + input->Handle, + input->ObjectInformationClass, + input->ObjectInformation, + input->ObjectInformationLength, + input->ReturnLength, + accessMode + ); + } + break; + case KPH_SETINFORMATIONOBJECT: + { + struct + { + HANDLE ProcessHandle; + HANDLE Handle; + KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass; + PVOID ObjectInformation; + ULONG ObjectInformationLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiSetInformationObject( + input->ProcessHandle, + input->Handle, + input->ObjectInformationClass, + input->ObjectInformation, + input->ObjectInformationLength, + accessMode + ); + } + break; + case KPH_OPENDRIVER: + { + struct + { + PHANDLE DriverHandle; + ACCESS_MASK DesiredAccess; + POBJECT_ATTRIBUTES ObjectAttributes; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiOpenDriver( + input->DriverHandle, + input->DesiredAccess, + input->ObjectAttributes, + accessMode + ); + } + break; + case KPH_QUERYINFORMATIONDRIVER: + { + struct + { + HANDLE DriverHandle; + DRIVER_INFORMATION_CLASS DriverInformationClass; + PVOID DriverInformation; + ULONG DriverInformationLength; + PULONG ReturnLength; + } *input = capturedInputPointer; + + VERIFY_INPUT_LENGTH; + + status = KpiQueryInformationDriver( + input->DriverHandle, + input->DriverInformationClass, + input->DriverInformation, + input->DriverInformationLength, + input->ReturnLength, + accessMode + ); + } + break; + default: + status = STATUS_INVALID_DEVICE_REQUEST; + break; + } + +ControlEnd: + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return status; +} diff --git a/KProcessHacker/dirs b/KProcessHacker/dirs new file mode 100644 index 0000000..b8263d8 --- /dev/null +++ b/KProcessHacker/dirs @@ -0,0 +1 @@ +DIRS=clean diff --git a/KProcessHacker/dyndata.c b/KProcessHacker/dyndata.c new file mode 100644 index 0000000..40d4cfe --- /dev/null +++ b/KProcessHacker/dyndata.c @@ -0,0 +1,167 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#define _DYNDATA_PRIVATE +#include + +#define C_2sTo4(x) ((unsigned int)(signed short)(x)) + +NTSTATUS KphpLoadDynamicConfiguration( + __in PVOID Buffer, + __in ULONG Length + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphDynamicDataInitialization) +#pragma alloc_text(PAGE, KphReadDynamicDataParameters) +#pragma alloc_text(PAGE, KphpLoadDynamicConfiguration) +#endif + +NTSTATUS KphDynamicDataInitialization( + VOID + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + PAGED_CODE(); + + // Get Windows version information. + + KphDynOsVersionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + status = RtlGetVersion((PRTL_OSVERSIONINFOW)&KphDynOsVersionInfo); + + return status; +} + +NTSTATUS KphReadDynamicDataParameters( + __in_opt HANDLE KeyHandle + ) +{ + NTSTATUS status; + UNICODE_STRING valueName; + PKEY_VALUE_PARTIAL_INFORMATION info; + ULONG resultLength; + + PAGED_CODE(); + + if (!KeyHandle) + return STATUS_UNSUCCESSFUL; + + RtlInitUnicodeString(&valueName, L"DynamicConfiguration"); + + status = ZwQueryValueKey( + KeyHandle, + &valueName, + KeyValuePartialInformation, + NULL, + 0, + &resultLength + ); + + if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL) + { + // Unexpected status; fail now. + return STATUS_UNSUCCESSFUL; + } + + info = ExAllocatePoolWithTag(PagedPool, resultLength, 'ThpK'); + + if (!info) + return STATUS_INSUFFICIENT_RESOURCES; + + status = ZwQueryValueKey( + KeyHandle, + &valueName, + KeyValuePartialInformation, + info, + resultLength, + &resultLength + ); + + if (NT_SUCCESS(status)) + { + if (info->Type == REG_BINARY) + status = KphpLoadDynamicConfiguration(info->Data, info->DataLength); + else + status = STATUS_OBJECT_TYPE_MISMATCH; + + if (!NT_SUCCESS(status)) + dprintf("Unable to load dynamic configuration: 0x%x\n", status); + } + + ExFreePoolWithTag(info, 'ThpK'); + + return status; +} + +NTSTATUS KphpLoadDynamicConfiguration( + __in PVOID Buffer, + __in ULONG Length + ) +{ + PKPH_DYN_CONFIGURATION config; + ULONG i; + PKPH_DYN_PACKAGE package; + + PAGED_CODE(); + + config = Buffer; + + if (Length < FIELD_OFFSET(KPH_DYN_CONFIGURATION, Packages)) + return STATUS_INVALID_PARAMETER; + if (config->Version != KPH_DYN_CONFIGURATION_VERSION) + return STATUS_INVALID_PARAMETER; + if (config->NumberOfPackages > KPH_DYN_MAXIMUM_PACKAGES) + return STATUS_INVALID_PARAMETER; + if (Length < FIELD_OFFSET(KPH_DYN_CONFIGURATION, Packages) + config->NumberOfPackages * sizeof(KPH_DYN_PACKAGE)) + return STATUS_INVALID_PARAMETER; + + dprintf("Loading dynamic configuration with %u package(s)\n", config->NumberOfPackages); + + for (i = 0; i < config->NumberOfPackages; i++) + { + package = &config->Packages[i]; + + if (package->MajorVersion == KphDynOsVersionInfo.dwMajorVersion && + package->MinorVersion == KphDynOsVersionInfo.dwMinorVersion && + (package->ServicePackMajor == (USHORT)-1 || package->ServicePackMajor == KphDynOsVersionInfo.wServicePackMajor) && + (package->BuildNumber == (USHORT)-1 || package->BuildNumber == KphDynOsVersionInfo.dwBuildNumber)) + { + dprintf("Found matching package at index %u for Windows %u.%u\n", i, package->MajorVersion, package->MinorVersion); + + KphDynNtVersion = package->ResultingNtVersion; + + KphDynEgeGuid = C_2sTo4(package->StructData.EgeGuid); + KphDynEpObjectTable = C_2sTo4(package->StructData.EpObjectTable); + KphDynEreGuidEntry = C_2sTo4(package->StructData.EreGuidEntry); + KphDynHtHandleContentionEvent = C_2sTo4(package->StructData.HtHandleContentionEvent); + KphDynOtName = C_2sTo4(package->StructData.OtName); + KphDynOtIndex = C_2sTo4(package->StructData.OtIndex); + KphDynObDecodeShift = C_2sTo4(package->StructData.ObDecodeShift); + KphDynObAttributesShift = C_2sTo4(package->StructData.ObAttributesShift); + + return STATUS_SUCCESS; + } + } + + return STATUS_NOT_FOUND; +} diff --git a/KProcessHacker/dynimp.c b/KProcessHacker/dynimp.c new file mode 100644 index 0000000..d432611 --- /dev/null +++ b/KProcessHacker/dynimp.c @@ -0,0 +1,60 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphGetSystemRoutineAddress) +#pragma alloc_text(PAGE, KphDynamicImport) +#endif + +/** + * Dynamically imports routines. + */ +VOID KphDynamicImport( + VOID + ) +{ + PAGED_CODE(); + NOTHING; +} + +/** + * Retrieves the address of a function exported by NTOS or HAL. + * + * \param SystemRoutineName The name of the function. + * + * \return The address of the function, or NULL if the function could + * not be found. + */ +PVOID KphGetSystemRoutineAddress( + __in PWSTR SystemRoutineName + ) +{ + UNICODE_STRING systemRoutineName; + + PAGED_CODE(); + + RtlInitUnicodeString(&systemRoutineName, SystemRoutineName); + + return MmGetSystemRoutineAddress(&systemRoutineName); +} diff --git a/KProcessHacker/include/dyndata.h b/KProcessHacker/include/dyndata.h new file mode 100644 index 0000000..d31f0f0 --- /dev/null +++ b/KProcessHacker/include/dyndata.h @@ -0,0 +1,47 @@ +#ifndef DYNDATA_H +#define DYNDATA_H + +#ifdef EXT +#undef EXT +#endif + +#ifdef _DYNDATA_PRIVATE +#define EXT +#define OFFDEFAULT = -1 +#else +#define EXT extern +#define OFFDEFAULT +#endif + +EXT ULONG KphDynNtVersion; +EXT RTL_OSVERSIONINFOEXW KphDynOsVersionInfo; + +// Structures +// Ege: ETW_GUID_ENTRY +// Ep: EPROCESS +// Ere: ETW_REG_ENTRY +// Et: ETHREAD +// Ht: HANDLE_TABLE +// Oh: OBJECT_HEADER +// Ot: OBJECT_TYPE +// Oti: OBJECT_TYPE_INITIALIZER, offset measured from an OBJECT_TYPE +// ObDecodeShift: shift value in ObpDecodeObject +// ObAttributesShift: shift value in ObpGetHandleAttributes +EXT ULONG KphDynEgeGuid OFFDEFAULT; +EXT ULONG KphDynEpObjectTable OFFDEFAULT; +EXT ULONG KphDynEreGuidEntry OFFDEFAULT; +EXT ULONG KphDynHtHandleContentionEvent OFFDEFAULT; +EXT ULONG KphDynOtName OFFDEFAULT; +EXT ULONG KphDynOtIndex OFFDEFAULT; +EXT ULONG KphDynObDecodeShift OFFDEFAULT; +EXT ULONG KphDynObAttributesShift OFFDEFAULT; + +NTSTATUS KphDynamicDataInitialization( + VOID + ); + +NTSTATUS KphReadDynamicDataParameters( + __in_opt HANDLE KeyHandle + ); + +#endif diff --git a/KProcessHacker/include/kph.h b/KProcessHacker/include/kph.h new file mode 100644 index 0000000..8ee115f --- /dev/null +++ b/KProcessHacker/include/kph.h @@ -0,0 +1,365 @@ +#ifndef KPH_H +#define KPH_H + +#include +#define PHNT_MODE PHNT_MODE_KERNEL +#include +#include +#include +#include + +// Debugging + +#ifdef DBG +#define dprintf(Format, ...) DbgPrint("KProcessHacker: " Format, __VA_ARGS__) +#else +#define dprintf +#endif + +typedef struct _KPH_CLIENT +{ + struct + { + ULONG VerificationPerformed : 1; + ULONG VerificationSucceeded : 1; + ULONG KeysGenerated : 1; + ULONG SpareBits : 29; + }; + FAST_MUTEX StateMutex; + NTSTATUS VerificationStatus; + PVOID VerifiedProcess; // EPROCESS (for equality checking only - do not access contents) + HANDLE VerifiedProcessId; + PVOID VerifiedRangeBase; + SIZE_T VerifiedRangeSize; + // Level 1 and 2 secret keys + FAST_MUTEX KeyBackoffMutex; + KPH_KEY L1Key; + KPH_KEY L2Key; +} KPH_CLIENT, *PKPH_CLIENT; + +typedef struct _KPH_PARAMETERS +{ + KPH_SECURITY_LEVEL SecurityLevel; +} KPH_PARAMETERS, *PKPH_PARAMETERS; + +// main + +extern ULONG KphFeatures; +extern KPH_PARAMETERS KphParameters; + +NTSTATUS KpiGetFeatures( + __out PULONG Features, + __in KPROCESSOR_MODE AccessMode + ); + +// devctrl + +__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH KphDispatchDeviceControl; + +NTSTATUS KphDispatchDeviceControl( + __in PDEVICE_OBJECT DeviceObject, + __in PIRP Irp + ); + +// dynimp + +VOID KphDynamicImport( + VOID + ); + +PVOID KphGetSystemRoutineAddress( + __in PWSTR SystemRoutineName + ); + +// object + +PHANDLE_TABLE KphReferenceProcessHandleTable( + __in PEPROCESS Process + ); + +VOID KphDereferenceProcessHandleTable( + __in PEPROCESS Process + ); + +VOID KphUnlockHandleTableEntry( + __in PHANDLE_TABLE HandleTable, + __in PHANDLE_TABLE_ENTRY HandleTableEntry + ); + +NTSTATUS KpiEnumerateProcessHandles( + __in HANDLE ProcessHandle, + __out_bcount(BufferLength) PVOID Buffer, + __in_opt ULONG BufferLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KphQueryNameObject( + __in PVOID Object, + __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer, + __in ULONG BufferLength, + __out PULONG ReturnLength + ); + +NTSTATUS KphQueryNameFileObject( + __in PFILE_OBJECT FileObject, + __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer, + __in ULONG BufferLength, + __out PULONG ReturnLength + ); + +NTSTATUS KpiQueryInformationObject( + __in HANDLE ProcessHandle, + __in HANDLE Handle, + __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + __out_bcount(ObjectInformationLength) PVOID ObjectInformation, + __in ULONG ObjectInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiSetInformationObject( + __in HANDLE ProcessHandle, + __in HANDLE Handle, + __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + __in_bcount(ObjectInformationLength) PVOID ObjectInformation, + __in ULONG ObjectInformationLength, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KphOpenNamedObject( + __out PHANDLE ObjectHandle, + __in ACCESS_MASK DesiredAccess, + __in POBJECT_ATTRIBUTES ObjectAttributes, + __in POBJECT_TYPE ObjectType, + __in KPROCESSOR_MODE AccessMode + ); + +// process + +NTSTATUS KpiOpenProcess( + __out PHANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __in PCLIENT_ID ClientId, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiOpenProcessToken( + __in HANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE TokenHandle, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiOpenProcessJob( + __in HANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE JobHandle, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiTerminateProcess( + __in HANDLE ProcessHandle, + __in NTSTATUS ExitStatus, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiQueryInformationProcess( + __in HANDLE ProcessHandle, + __in KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + __out_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiSetInformationProcess( + __in HANDLE ProcessHandle, + __in KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + __in_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, + __in KPROCESSOR_MODE AccessMode + ); + +// qrydrv + +NTSTATUS KpiOpenDriver( + __out PHANDLE DriverHandle, + __in ACCESS_MASK DesiredAccess, + __in POBJECT_ATTRIBUTES ObjectAttributes, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiQueryInformationDriver( + __in HANDLE DriverHandle, + __in DRIVER_INFORMATION_CLASS DriverInformationClass, + __out_bcount(DriverInformationLength) PVOID DriverInformation, + __in ULONG DriverInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ); + +// thread + +NTSTATUS KpiOpenThread( + __out PHANDLE ThreadHandle, + __in ACCESS_MASK DesiredAccess, + __in PCLIENT_ID ClientId, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiOpenThreadProcess( + __in HANDLE ThreadHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE ProcessHandle, + __in KPROCESSOR_MODE AccessMode + ); + +ULONG KphCaptureStackBackTrace( + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __in_opt ULONG Flags, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG BackTraceHash + ); + +NTSTATUS KphCaptureStackBackTraceThread( + __in PETHREAD Thread, + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG CapturedFrames, + __out_opt PULONG BackTraceHash, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiCaptureStackBackTraceThread( + __in HANDLE ThreadHandle, + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG CapturedFrames, + __out_opt PULONG BackTraceHash, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiQueryInformationThread( + __in HANDLE ThreadHandle, + __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + __out_bcount(ProcessInformationLength) PVOID ThreadInformation, + __in ULONG ThreadInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ); + +NTSTATUS KpiSetInformationThread( + __in HANDLE ThreadHandle, + __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + __in_bcount(ThreadInformationLength) PVOID ThreadInformation, + __in ULONG ThreadInformationLength, + __in KPROCESSOR_MODE AccessMode + ); + +// util + +VOID KphFreeCapturedUnicodeString( + __in PUNICODE_STRING CapturedUnicodeString + ); + +NTSTATUS KphCaptureUnicodeString( + __in PUNICODE_STRING UnicodeString, + __out PUNICODE_STRING CapturedUnicodeString + ); + +NTSTATUS KphEnumerateSystemModules( + __out PRTL_PROCESS_MODULES *Modules + ); + +NTSTATUS KphValidateAddressForSystemModules( + __in PVOID Address, + __in SIZE_T Length + ); + +NTSTATUS KphGetProcessMappedFileName( + __in HANDLE ProcessHandle, + __in PVOID BaseAddress, + __out PUNICODE_STRING *FileName + ); + +// verify + +NTSTATUS KphHashFile( + __in PUNICODE_STRING FileName, + __out PVOID *Hash, + __out PULONG HashSize + ); + +NTSTATUS KphVerifyFile( + __in PUNICODE_STRING FileName, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize + ); + +VOID KphVerifyClient( + __inout PKPH_CLIENT Client, + __in PVOID CodeAddress, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize + ); + +NTSTATUS KpiVerifyClient( + __in PVOID CodeAddress, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize, + __in PKPH_CLIENT Client + ); + +VOID KphGenerateKeysClient( + __inout PKPH_CLIENT Client + ); + +NTSTATUS KphRetrieveKeyViaApc( + __inout PKPH_CLIENT Client, + __in KPH_KEY_LEVEL KeyLevel, + __inout PIRP Irp + ); + +NTSTATUS KphValidateKey( + __in KPH_KEY_LEVEL RequiredKeyLevel, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +// vm + +NTSTATUS KphCopyVirtualMemory( + __in PEPROCESS FromProcess, + __in PVOID FromAddress, + __in PEPROCESS ToProcess, + __in PVOID ToAddress, + __in SIZE_T BufferLength, + __in KPROCESSOR_MODE AccessMode, + __out PSIZE_T ReturnLength + ); + +NTSTATUS KpiReadVirtualMemoryUnsafe( + __in_opt HANDLE ProcessHandle, + __in PVOID BaseAddress, + __out_bcount(BufferSize) PVOID Buffer, + __in SIZE_T BufferSize, + __out_opt PSIZE_T NumberOfBytesRead, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ); + +#endif diff --git a/KProcessHacker/include/ntfill.h b/KProcessHacker/include/ntfill.h new file mode 100644 index 0000000..865bbe8 --- /dev/null +++ b/KProcessHacker/include/ntfill.h @@ -0,0 +1,351 @@ +#ifndef NTFILL_H +#define NTFILL_H + +extern ULONG KphDynNtVersion; +extern ULONG KphDynObDecodeShift; +extern ULONG KphDynObAttributesShift; + +// EX + +typedef struct _EX_PUSH_LOCK_WAIT_BLOCK *PEX_PUSH_LOCK_WAIT_BLOCK; + +NTKERNELAPI +VOID +FASTCALL +ExfUnblockPushLock( + __inout PEX_PUSH_LOCK PushLock, + __inout_opt PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock + ); + +typedef struct _HANDLE_TABLE_ENTRY +{ + union + { + PVOID Object; + ULONG ObAttributes; + ULONG_PTR Value; + }; + union + { + ACCESS_MASK GrantedAccess; + LONG NextFreeTableEntry; + }; +} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY; + +typedef struct _HANDLE_TABLE HANDLE_TABLE, *PHANDLE_TABLE; + +typedef BOOLEAN (NTAPI *PEX_ENUM_HANDLE_CALLBACK_61)( + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ); + +// since WIN8 +typedef BOOLEAN (NTAPI *PEX_ENUM_HANDLE_CALLBACK)( + __in PHANDLE_TABLE HandleTable, + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ); + +NTKERNELAPI +BOOLEAN +NTAPI +ExEnumHandleTable( + __in PHANDLE_TABLE HandleTable, + __in PEX_ENUM_HANDLE_CALLBACK EnumHandleProcedure, + __inout PVOID Context, + __out_opt PHANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemInformation( + __in SYSTEM_INFORMATION_CLASS SystemInformationClass, + __out_bcount_opt(SystemInformationLength) PVOID SystemInformation, + __in ULONG SystemInformationLength, + __out_opt PULONG ReturnLength + ); + +// IO + +extern POBJECT_TYPE *IoDriverObjectType; + +// KE + +typedef enum _KAPC_ENVIRONMENT +{ + OriginalApcEnvironment, + AttachedApcEnvironment, + CurrentApcEnvironment, + InsertApcEnvironment +} KAPC_ENVIRONMENT, *PKAPC_ENVIRONMENT; + +typedef VOID (NTAPI *PKNORMAL_ROUTINE)( + __in PVOID NormalContext, + __in PVOID SystemArgument1, + __in PVOID SystemArgument2 + ); + +typedef VOID KKERNEL_ROUTINE( + __in PRKAPC Apc, + __inout PKNORMAL_ROUTINE *NormalRoutine, + __inout PVOID *NormalContext, + __inout PVOID *SystemArgument1, + __inout PVOID *SystemArgument2 + ); + +typedef KKERNEL_ROUTINE (NTAPI *PKKERNEL_ROUTINE); + +typedef VOID (NTAPI *PKRUNDOWN_ROUTINE)( + __in PRKAPC Apc + ); + +NTKERNELAPI +VOID +NTAPI +KeInitializeApc( + __out PRKAPC Apc, + __in PRKTHREAD Thread, + __in KAPC_ENVIRONMENT Environment, + __in PKKERNEL_ROUTINE KernelRoutine, + __in_opt PKRUNDOWN_ROUTINE RundownRoutine, + __in_opt PKNORMAL_ROUTINE NormalRoutine, + __in_opt KPROCESSOR_MODE ProcessorMode, + __in_opt PVOID NormalContext + ); + +NTKERNELAPI +BOOLEAN +NTAPI +KeInsertQueueApc( + __inout PRKAPC Apc, + __in_opt PVOID SystemArgument1, + __in_opt PVOID SystemArgument2, + __in KPRIORITY Increment + ); + +// MM + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryVirtualMemory( + __in HANDLE ProcessHandle, + __in PVOID BaseAddress, + __in MEMORY_INFORMATION_CLASS MemoryInformationClass, + __out_bcount(MemoryInformationLength) PVOID MemoryInformation, + __in SIZE_T MemoryInformationLength, + __out_opt PSIZE_T ReturnLength + ); + +// OB + +// These definitions are no longer correct, but they produce correct results. + +#define OBJ_PROTECT_CLOSE 0x00000001 +#define OBJ_HANDLE_ATTRIBUTES (OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE) + +// This attribute is now stored in the GrantedAccess field. +#define ObpAccessProtectCloseBit 0x2000000 + +#define ObpDecodeGrantedAccess(Access) \ + ((Access) & ~ObpAccessProtectCloseBit) + +FORCEINLINE PVOID ObpDecodeObject(PVOID Object) +{ +#ifdef _M_X64 + if (KphDynNtVersion >= PHNT_WIN8) + { + if (KphDynObDecodeShift != -1) + return (PVOID)(((LONG_PTR)Object >> KphDynObDecodeShift) & ~(ULONG_PTR)0xf); + else + return NULL; + } + else + { + return (PVOID)((ULONG_PTR)Object & ~OBJ_HANDLE_ATTRIBUTES); + } +#else + return (PVOID)((ULONG_PTR)Object & ~OBJ_HANDLE_ATTRIBUTES); +#endif +} + +FORCEINLINE ULONG ObpGetHandleAttributes(PHANDLE_TABLE_ENTRY HandleTableEntry) +{ +#ifdef _M_X64 + if (KphDynNtVersion >= PHNT_WIN8) + { + if (KphDynObAttributesShift != -1) + return (ULONG)(HandleTableEntry->Value >> KphDynObAttributesShift) & 0x3; + else + return 0; + } + else + { + return (HandleTableEntry->ObAttributes & (OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE)) | + ((HandleTableEntry->GrantedAccess & ObpAccessProtectCloseBit) ? OBJ_PROTECT_CLOSE : 0); + } +#else + return (HandleTableEntry->ObAttributes & (OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE)) | + ((HandleTableEntry->GrantedAccess & ObpAccessProtectCloseBit) ? OBJ_PROTECT_CLOSE : 0); +#endif +} + +typedef struct _OBJECT_CREATE_INFORMATION OBJECT_CREATE_INFORMATION, *POBJECT_CREATE_INFORMATION; + +// This is incorrect as of Windows 8.1, but the size of the structure is still correct. +typedef struct _OBJECT_HEADER +{ + LONG PointerCount; + union + { + LONG HandleCount; + PVOID NextToFree; + }; + POBJECT_TYPE Type; + UCHAR NameInfoOffset; + UCHAR HandleInfoOffset; + UCHAR QuotaInfoOffset; + UCHAR Flags; + union + { + POBJECT_CREATE_INFORMATION ObjectCreateInfo; + PVOID QuotaBlockCharged; + }; + PVOID SecurityDescriptor; + QUAD Body; +} OBJECT_HEADER, *POBJECT_HEADER; + +#define OBJECT_TO_OBJECT_HEADER(Object) CONTAINING_RECORD((Object), OBJECT_HEADER, Body) + +NTKERNELAPI +POBJECT_TYPE +NTAPI +ObGetObjectType( + __in PVOID Object + ); + +NTKERNELAPI +NTSTATUS +NTAPI +ObOpenObjectByName( + __in POBJECT_ATTRIBUTES ObjectAttributes, + __in POBJECT_TYPE ObjectType, + __in KPROCESSOR_MODE PreviousMode, + __in_opt PACCESS_STATE AccessState, + __in_opt ACCESS_MASK DesiredAccess, + __in PVOID ParseContext, + __out PHANDLE Handle + ); + +NTKERNELAPI +NTSTATUS +NTAPI +ObSetHandleAttributes( + __in HANDLE Handle, + __in POBJECT_HANDLE_FLAG_INFORMATION HandleFlags, + __in KPROCESSOR_MODE PreviousMode + ); + +NTKERNELAPI +NTSTATUS +ObCloseHandle( + __in HANDLE Handle, + __in KPROCESSOR_MODE PreviousMode + ); + +// PS + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationProcess( + __in HANDLE ProcessHandle, + __in PROCESSINFOCLASS ProcessInformationClass, + __out_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, + __out_opt PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationProcess( + __in HANDLE ProcessHandle, + __in PROCESSINFOCLASS ProcessInformationClass, + __in_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationThread( + __in HANDLE ThreadHandle, + __in THREADINFOCLASS ThreadInformationClass, + __out_bcount(ThreadInformationLength) PVOID ThreadInformation, + __in ULONG ThreadInformationLength, + __out_opt PULONG ReturnLength + ); + +NTKERNELAPI +NTSTATUS +NTAPI +PsLookupProcessThreadByCid( + __in PCLIENT_ID ClientId, + __out_opt PEPROCESS *Process, + __out PETHREAD *Thread + ); + +NTKERNELAPI +PVOID +NTAPI +PsGetThreadWin32Thread( + __in PETHREAD Thread + ); + +typedef struct _EJOB *PEJOB; + +extern POBJECT_TYPE *PsJobType; + +NTKERNELAPI +PEJOB +NTAPI +PsGetProcessJob( + __in PEPROCESS Process + ); + +NTKERNELAPI +NTSTATUS +NTAPI +PsAcquireProcessExitSynchronization( + __in PEPROCESS Process + ); + +NTKERNELAPI +VOID +NTAPI +PsReleaseProcessExitSynchronization( + __in PEPROCESS Process + ); + +// RTL + +// Sensible limit that may or may not correspond to the actual Windows value. +#define MAX_STACK_DEPTH 256 + +#define RTL_WALK_USER_MODE_STACK 0x00000001 +#define RTL_WALK_VALID_FLAGS 0x00000001 + +NTSYSAPI +ULONG +NTAPI +RtlWalkFrameChain( + __out PVOID *Callers, + __in ULONG Count, + __in ULONG Flags + ); + +#endif diff --git a/KProcessHacker/main.c b/KProcessHacker/main.c new file mode 100644 index 0000000..3162323 --- /dev/null +++ b/KProcessHacker/main.c @@ -0,0 +1,356 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +DRIVER_INITIALIZE DriverEntry; +DRIVER_UNLOAD DriverUnload; +__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH KphDispatchCreate; +__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH KphDispatchClose; + +ULONG KphpReadIntegerParameter( + __in_opt HANDLE KeyHandle, + __in PUNICODE_STRING ValueName, + __in ULONG DefaultValue + ); + +NTSTATUS KphpReadDriverParameters( + __in PUNICODE_STRING RegistryPath + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, DriverEntry) +#pragma alloc_text(PAGE, DriverUnload) +#pragma alloc_text(PAGE, KphpReadIntegerParameter) +#pragma alloc_text(PAGE, KphpReadDriverParameters) +#pragma alloc_text(PAGE, KpiGetFeatures) +#endif + +PDRIVER_OBJECT KphDriverObject; +PDEVICE_OBJECT KphDeviceObject; +ULONG KphFeatures; +KPH_PARAMETERS KphParameters; + +NTSTATUS DriverEntry( + __in PDRIVER_OBJECT DriverObject, + __in PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS status; + UNICODE_STRING deviceName; + PDEVICE_OBJECT deviceObject; + + PAGED_CODE(); + + KphDriverObject = DriverObject; + + if (!NT_SUCCESS(status = KphDynamicDataInitialization())) + return status; + + KphDynamicImport(); + + if (!NT_SUCCESS(status = KphpReadDriverParameters(RegistryPath))) + return status; + + // Create the device. + + RtlInitUnicodeString(&deviceName, KPH_DEVICE_NAME); + + status = IoCreateDevice( + DriverObject, + 0, + &deviceName, + FILE_DEVICE_UNKNOWN, + FILE_DEVICE_SECURE_OPEN, + FALSE, + &deviceObject + ); + + if (!NT_SUCCESS(status)) + return status; + + KphDeviceObject = deviceObject; + + // Set up I/O. + + DriverObject->MajorFunction[IRP_MJ_CREATE] = KphDispatchCreate; + DriverObject->MajorFunction[IRP_MJ_CLOSE] = KphDispatchClose; + DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KphDispatchDeviceControl; + DriverObject->DriverUnload = DriverUnload; + + deviceObject->Flags &= ~DO_DEVICE_INITIALIZING; + + dprintf("Driver loaded\n"); + + return status; +} + +VOID DriverUnload( + __in PDRIVER_OBJECT DriverObject + ) +{ + PAGED_CODE(); + + IoDeleteDevice(KphDeviceObject); + + dprintf("Driver unloaded\n"); +} + +NTSTATUS KphDispatchCreate( + __in PDEVICE_OBJECT DeviceObject, + __in PIRP Irp + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PIO_STACK_LOCATION stackLocation; + PFILE_OBJECT fileObject; + PIO_SECURITY_CONTEXT securityContext; + PKPH_CLIENT client; + + stackLocation = IoGetCurrentIrpStackLocation(Irp); + fileObject = stackLocation->FileObject; + securityContext = stackLocation->Parameters.Create.SecurityContext; + + dprintf("Client (PID %Iu) is connecting\n", PsGetCurrentProcessId()); + + if (KphParameters.SecurityLevel == KphSecurityPrivilegeCheck || + KphParameters.SecurityLevel == KphSecuritySignatureAndPrivilegeCheck) + { + UCHAR requiredPrivilegesBuffer[FIELD_OFFSET(PRIVILEGE_SET, Privilege) + sizeof(LUID_AND_ATTRIBUTES)]; + PPRIVILEGE_SET requiredPrivileges; + + // Check for SeDebugPrivilege. + + requiredPrivileges = (PPRIVILEGE_SET)requiredPrivilegesBuffer; + requiredPrivileges->PrivilegeCount = 1; + requiredPrivileges->Control = PRIVILEGE_SET_ALL_NECESSARY; + requiredPrivileges->Privilege[0].Luid.LowPart = SE_DEBUG_PRIVILEGE; + requiredPrivileges->Privilege[0].Luid.HighPart = 0; + requiredPrivileges->Privilege[0].Attributes = 0; + + if (!SePrivilegeCheck( + requiredPrivileges, + &securityContext->AccessState->SubjectSecurityContext, + Irp->RequestorMode + )) + { + status = STATUS_PRIVILEGE_NOT_HELD; + dprintf("Client (PID %Iu) was rejected\n", PsGetCurrentProcessId()); + } + } + + if (NT_SUCCESS(status)) + { + client = ExAllocatePoolWithTag(PagedPool, sizeof(KPH_CLIENT), 'ChpK'); + + if (client) + { + memset(client, 0, sizeof(KPH_CLIENT)); + + ExInitializeFastMutex(&client->StateMutex); + ExInitializeFastMutex(&client->KeyBackoffMutex); + + fileObject->FsContext = client; + } + else + { + dprintf("Unable to allocate memory for client (PID %Iu)\n", PsGetCurrentProcessId()); + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return status; +} + +NTSTATUS KphDispatchClose( + __in PDEVICE_OBJECT DeviceObject, + __in PIRP Irp + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PIO_STACK_LOCATION stackLocation; + PFILE_OBJECT fileObject; + PKPH_CLIENT client; + + stackLocation = IoGetCurrentIrpStackLocation(Irp); + fileObject = stackLocation->FileObject; + client = fileObject->FsContext; + + if (client) + { + ExFreePoolWithTag(client, 'ChpK'); + } + + Irp->IoStatus.Status = status; + Irp->IoStatus.Information = 0; + IoCompleteRequest(Irp, IO_NO_INCREMENT); + + return status; +} + +/** + * Reads an integer (REG_DWORD) parameter from the registry. + * + * \param KeyHandle A handle to the Parameters key. If NULL, the function + * fails immediately and returns \a DefaultValue. + * \param ValueName The name of the parameter. + * \param DefaultValue The value that is returned if the function fails + * to retrieve the parameter from the registry. + * + * \return The parameter value, or \a DefaultValue if the function failed. + */ +ULONG KphpReadIntegerParameter( + __in_opt HANDLE KeyHandle, + __in PUNICODE_STRING ValueName, + __in ULONG DefaultValue + ) +{ + NTSTATUS status; + UCHAR buffer[FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + sizeof(ULONG)]; + PKEY_VALUE_PARTIAL_INFORMATION info; + ULONG resultLength; + + PAGED_CODE(); + + if (!KeyHandle) + return DefaultValue; + + info = (PKEY_VALUE_PARTIAL_INFORMATION)buffer; + + status = ZwQueryValueKey( + KeyHandle, + ValueName, + KeyValuePartialInformation, + info, + sizeof(buffer), + &resultLength + ); + + if (info->Type != REG_DWORD) + status = STATUS_OBJECT_TYPE_MISMATCH; + + if (!NT_SUCCESS(status)) + { + dprintf("Unable to query parameter %.*S: 0x%x\n", ValueName->Length / sizeof(WCHAR), ValueName->Buffer, status); + return DefaultValue; + } + + return *(PULONG)info->Data; +} + +/** + * Reads the driver parameters. + * + * \param RegistryPath The registry path of the driver. + */ +NTSTATUS KphpReadDriverParameters( + __in PUNICODE_STRING RegistryPath + ) +{ + NTSTATUS status; + HANDLE parametersKeyHandle; + UNICODE_STRING parametersString; + UNICODE_STRING parametersKeyName; + OBJECT_ATTRIBUTES objectAttributes; + UNICODE_STRING valueName; + + PAGED_CODE(); + + // Open the Parameters key. + + RtlInitUnicodeString(¶metersString, L"\\Parameters"); + + parametersKeyName.Length = RegistryPath->Length + parametersString.Length; + parametersKeyName.MaximumLength = parametersKeyName.Length; + parametersKeyName.Buffer = ExAllocatePoolWithTag(PagedPool, parametersKeyName.MaximumLength, 'ThpK'); + + if (!parametersKeyName.Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + memcpy(parametersKeyName.Buffer, RegistryPath->Buffer, RegistryPath->Length); + memcpy(¶metersKeyName.Buffer[RegistryPath->Length / sizeof(WCHAR)], parametersString.Buffer, parametersString.Length); + + InitializeObjectAttributes( + &objectAttributes, + ¶metersKeyName, + OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, + NULL, + NULL + ); + status = ZwOpenKey( + ¶metersKeyHandle, + KEY_READ, + &objectAttributes + ); + ExFreePoolWithTag(parametersKeyName.Buffer, 'ThpK'); + + if (!NT_SUCCESS(status)) + { + dprintf("Unable to open Parameters key: 0x%x\n", status); + status = STATUS_SUCCESS; + parametersKeyHandle = NULL; + // Continue so we can set up defaults. + } + + // Read in the parameters. + + RtlInitUnicodeString(&valueName, L"SecurityLevel"); + KphParameters.SecurityLevel = KphpReadIntegerParameter(parametersKeyHandle, &valueName, KphSecurityPrivilegeCheck); + + KphReadDynamicDataParameters(parametersKeyHandle); + + if (parametersKeyHandle) + ZwClose(parametersKeyHandle); + + return status; +} + +NTSTATUS KpiGetFeatures( + __out PULONG Features, + __in KPROCESSOR_MODE AccessMode + ) +{ + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(Features, sizeof(ULONG), sizeof(ULONG)); + *Features = KphFeatures; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + *Features = KphFeatures; + } + + return STATUS_SUCCESS; +} diff --git a/KProcessHacker/object.c b/KProcessHacker/object.c new file mode 100644 index 0000000..8e9f1eb --- /dev/null +++ b/KProcessHacker/object.c @@ -0,0 +1,1295 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#ifdef _X86_ +#define KERNEL_HANDLE_BIT (0x80000000) +#else +#define KERNEL_HANDLE_BIT (0xffffffff80000000) +#endif + +#define IsKernelHandle(Handle) ((LONG_PTR)(Handle) < 0) +#define MakeKernelHandle(Handle) ((HANDLE)((ULONG_PTR)(Handle) | KERNEL_HANDLE_BIT)) + +typedef struct _KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT +{ + PVOID Buffer; + PVOID BufferLimit; + PVOID CurrentEntry; + ULONG Count; + NTSTATUS Status; +} KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT, *PKPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT; + +BOOLEAN KphpEnumerateProcessHandlesEnumCallback61( + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ); + +BOOLEAN KphpEnumerateProcessHandlesEnumCallback( + __in PHANDLE_TABLE HandleTable, + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphReferenceProcessHandleTable) +#pragma alloc_text(PAGE, KphDereferenceProcessHandleTable) +#pragma alloc_text(PAGE, KphUnlockHandleTableEntry) +#pragma alloc_text(PAGE, KphpEnumerateProcessHandlesEnumCallback61) +#pragma alloc_text(PAGE, KphpEnumerateProcessHandlesEnumCallback) +#pragma alloc_text(PAGE, KpiEnumerateProcessHandles) +#pragma alloc_text(PAGE, KphQueryNameObject) +#pragma alloc_text(PAGE, KphQueryNameFileObject) +#pragma alloc_text(PAGE, KpiQueryInformationObject) +#pragma alloc_text(PAGE, KpiSetInformationObject) +#pragma alloc_text(PAGE, KphOpenNamedObject) +#endif + +/** + * Gets a pointer to the handle table of a process. + * + * \param Process A process object. + * + * \return A pointer to the handle table, or NULL if the process is terminating or the request is + * not supported. You must call KphDereferenceProcessHandleTable() when the handle table is no + * longer needed. + */ +PHANDLE_TABLE KphReferenceProcessHandleTable( + __in PEPROCESS Process + ) +{ + PHANDLE_TABLE handleTable = NULL; + + PAGED_CODE(); + + // Fail if we don't have an offset. + if (KphDynEpObjectTable == -1) + return NULL; + + // Prevent the process from terminating and get its handle table. + if (NT_SUCCESS(PsAcquireProcessExitSynchronization(Process))) + { + handleTable = *(PHANDLE_TABLE *)((ULONG_PTR)Process + KphDynEpObjectTable); + + if (!handleTable) + PsReleaseProcessExitSynchronization(Process); + } + + return handleTable; +} + +/** + * Dereferences the handle table of a process. + * + * \param Process A process object. + */ +VOID KphDereferenceProcessHandleTable( + __in PEPROCESS Process + ) +{ + PAGED_CODE(); + + PsReleaseProcessExitSynchronization(Process); +} + +VOID KphUnlockHandleTableEntry( + __in PHANDLE_TABLE HandleTable, + __in PHANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + PEX_PUSH_LOCK handleContentionEvent; + + PAGED_CODE(); + + // Set the unlocked bit. + +#ifdef _M_X64 + InterlockedExchangeAdd64(&HandleTableEntry->Value, 1); +#else + InterlockedExchangeAdd(&HandleTableEntry->Value, 1); +#endif + + // Allow waiters to wake up. + + handleContentionEvent = (PEX_PUSH_LOCK)((ULONG_PTR)HandleTable + KphDynHtHandleContentionEvent); + + if (*(PULONG_PTR)handleContentionEvent != 0) + ExfUnblockPushLock(handleContentionEvent, NULL); +} + +BOOLEAN KphpEnumerateProcessHandlesEnumCallback61( + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ) +{ + PKPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT context = Context; + KPH_PROCESS_HANDLE handleInfo; + POBJECT_HEADER objectHeader; + POBJECT_TYPE objectType; + PKPH_PROCESS_HANDLE entryInBuffer; + + PAGED_CODE(); + + objectHeader = ObpDecodeObject(HandleTableEntry->Object); + handleInfo.Handle = Handle; + handleInfo.Object = objectHeader ? &objectHeader->Body : NULL; + handleInfo.GrantedAccess = ObpDecodeGrantedAccess(HandleTableEntry->GrantedAccess); + handleInfo.ObjectTypeIndex = -1; + handleInfo.Reserved1 = 0; + handleInfo.HandleAttributes = ObpGetHandleAttributes(HandleTableEntry); + handleInfo.Reserved2 = 0; + + if (handleInfo.Object) + { + objectType = ObGetObjectType(handleInfo.Object); + + if (objectType && KphDynOtIndex != -1) + handleInfo.ObjectTypeIndex = (USHORT)*(PUCHAR)((ULONG_PTR)objectType + KphDynOtIndex); + } + + // Advance the current entry pointer regardless of whether the information will be written; this + // will allow the parent function to report the correct return length. + entryInBuffer = context->CurrentEntry; + context->CurrentEntry = (PVOID)((ULONG_PTR)context->CurrentEntry + sizeof(KPH_PROCESS_HANDLE)); + context->Count++; + + // Only write if we have not exceeded the buffer length. Also check for a potential overflow (if + // the process has an extremely large number of handles, the buffer pointer may wrap). + if ( + (ULONG_PTR)entryInBuffer >= (ULONG_PTR)context->Buffer && + (ULONG_PTR)entryInBuffer + sizeof(KPH_PROCESS_HANDLE) <= (ULONG_PTR)context->BufferLimit + ) + { + __try + { + *entryInBuffer = handleInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + // Report an error. + if (context->Status == STATUS_SUCCESS) + context->Status = GetExceptionCode(); + } + } + else + { + // Report that the buffer is too small. + if (context->Status == STATUS_SUCCESS) + context->Status = STATUS_BUFFER_TOO_SMALL; + } + + return FALSE; +} + +BOOLEAN KphpEnumerateProcessHandlesEnumCallback( + __in PHANDLE_TABLE HandleTable, + __inout PHANDLE_TABLE_ENTRY HandleTableEntry, + __in HANDLE Handle, + __in PVOID Context + ) +{ + BOOLEAN result; + + PAGED_CODE(); + + result = KphpEnumerateProcessHandlesEnumCallback61(HandleTableEntry, Handle, Context); + KphUnlockHandleTableEntry(HandleTable, HandleTableEntry); + + return result; +} + +/** + * Enumerates the handles of a process. + * + * \param ProcessHandle A handle to a process. + * \param Buffer The buffer in which the handle information will be stored. + * \param BufferLength The number of bytes available in \a Buffer. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a Buffer. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiEnumerateProcessHandles( + __in HANDLE ProcessHandle, + __out_bcount(BufferLength) PVOID Buffer, + __in_opt ULONG BufferLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + BOOLEAN result; + PEPROCESS process; + PHANDLE_TABLE handleTable; + KPHP_ENUMERATE_PROCESS_HANDLES_CONTEXT context; + + PAGED_CODE(); + + if (KphDynNtVersion >= PHNT_WIN8 && KphDynHtHandleContentionEvent == -1) + { + return STATUS_NOT_SUPPORTED; + } + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(Buffer, BufferLength, sizeof(ULONG)); + + if (ReturnLength) + ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + // Reference the process object. + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Get its handle table. + handleTable = KphReferenceProcessHandleTable(process); + + if (!handleTable) + { + ObDereferenceObject(process); + return STATUS_UNSUCCESSFUL; + } + + // Initialize the enumeration context. + context.Buffer = Buffer; + context.BufferLimit = (PVOID)((ULONG_PTR)Buffer + BufferLength); + context.CurrentEntry = ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->Handles; + context.Count = 0; + context.Status = STATUS_SUCCESS; + + // Enumerate the handles. + + if (KphDynNtVersion >= PHNT_WIN8) + { + result = ExEnumHandleTable( + handleTable, + KphpEnumerateProcessHandlesEnumCallback, + &context, + NULL + ); + } + else + { + result = ExEnumHandleTable( + handleTable, + (PEX_ENUM_HANDLE_CALLBACK)KphpEnumerateProcessHandlesEnumCallback61, + &context, + NULL + ); + } + + KphDereferenceProcessHandleTable(process); + ObDereferenceObject(process); + + // Write the number of handles if we can. + if (BufferLength >= FIELD_OFFSET(KPH_PROCESS_HANDLE_INFORMATION, Handles)) + { + if (AccessMode != KernelMode) + { + __try + { + ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->HandleCount = context.Count; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + ((PKPH_PROCESS_HANDLE_INFORMATION)Buffer)->HandleCount = context.Count; + } + } + + // Supply the return length if the caller wanted it. + if (ReturnLength) + { + ULONG returnLength; + + // Note: if the CurrentEntry pointer wrapped, this will give the wrong return length. + returnLength = (ULONG)((ULONG_PTR)context.CurrentEntry - (ULONG_PTR)Buffer); + + if (AccessMode != KernelMode) + { + __try + { + *ReturnLength = returnLength; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + *ReturnLength = returnLength; + } + } + + return context.Status; +} + +/** + * Queries the name of an object. + * + * \param Object A pointer to an object. + * \param Buffer The buffer in which the object name will be stored. + * \param BufferLength The number of bytes available in \a Buffer. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a Buffer. + */ +NTSTATUS KphQueryNameObject( + __in PVOID Object, + __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer, + __in ULONG BufferLength, + __out PULONG ReturnLength + ) +{ + NTSTATUS status; + POBJECT_TYPE objectType; + + PAGED_CODE(); + + objectType = ObGetObjectType(Object); + + // Check if we are going to hang when querying the object, and use + // the special file object query function if needed. + if (objectType == *IoFileObjectType && + (((PFILE_OBJECT)Object)->Busy || ((PFILE_OBJECT)Object)->Waiters)) + { + status = KphQueryNameFileObject(Object, Buffer, BufferLength, ReturnLength); + dprintf("KphQueryNameFileObject: status 0x%x\n", status); + } + else + { + status = ObQueryNameString(Object, Buffer, BufferLength, ReturnLength); + dprintf("ObQueryNameString: status 0x%x\n", status); + } + + // Make the error returns consistent. + if (status == STATUS_BUFFER_OVERFLOW) // returned by I/O subsystem + status = STATUS_BUFFER_TOO_SMALL; + if (status == STATUS_INFO_LENGTH_MISMATCH) // returned by ObQueryNameString + status = STATUS_BUFFER_TOO_SMALL; + + if (NT_SUCCESS(status)) + dprintf("KphQueryNameObject: %.*S\n", Buffer->Name.Length / sizeof(WCHAR), Buffer->Name.Buffer); + else + dprintf("KphQueryNameObject: status 0x%x\n", status); + + return status; +} + +/** + * Queries the name of a file object. + * + * \param FileObject A pointer to a file object. + * \param Buffer The buffer in which the object name will be stored. + * \param BufferLength The number of bytes available in \a Buffer. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a Buffer. + */ +NTSTATUS KphQueryNameFileObject( + __in PFILE_OBJECT FileObject, + __out_bcount(BufferLength) POBJECT_NAME_INFORMATION Buffer, + __in ULONG BufferLength, + __out PULONG ReturnLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG returnLength; + PCHAR objectName; + ULONG usedLength; + ULONG subNameLength; + PFILE_OBJECT relatedFileObject; + + PAGED_CODE(); + + // We need at least the size of OBJECT_NAME_INFORMATION to continue. + if (BufferLength < sizeof(OBJECT_NAME_INFORMATION)) + { + *ReturnLength = sizeof(OBJECT_NAME_INFORMATION); + + return STATUS_BUFFER_TOO_SMALL; + } + + // Assume failure. + Buffer->Name.Length = 0; + // We will place the object name directly after the UNICODE_STRING structure in the buffer. + Buffer->Name.Buffer = (PWSTR)((ULONG_PTR)Buffer + sizeof(OBJECT_NAME_INFORMATION)); + // Retain a local pointer to the object name so we can manipulate the pointer. + objectName = (PCHAR)Buffer->Name.Buffer; + // A variable that keeps track of how much space we have used. + usedLength = sizeof(OBJECT_NAME_INFORMATION); + + // Check if the file object has an associated device (e.g. "\Device\NamedPipe", "\Device\Mup"). + // We can use the user-supplied buffer for this since if the buffer isn't big enough, we can't + // proceed anyway (we are going to use the name). + if (FileObject->DeviceObject) + { + status = ObQueryNameString( + FileObject->DeviceObject, + Buffer, + BufferLength, + &returnLength + ); + + if (!NT_SUCCESS(status)) + { + if (status == STATUS_INFO_LENGTH_MISMATCH) + status = STATUS_BUFFER_TOO_SMALL; + + *ReturnLength = returnLength; + + return status; + } + + // The UNICODE_STRING in the buffer is now filled in. We will append to the object name + // later, so we need to fix the object name pointer by adding the length, in bytes, of the + // device name string we just got. + objectName += Buffer->Name.Length; + usedLength += Buffer->Name.Length; + } + + // Check if the file object has a file name component. If not, we can't do anything else, so we + // just return the name we have already. + if (!FileObject->FileName.Buffer) + { + *ReturnLength = usedLength; + + return STATUS_SUCCESS; + } + + // The file object has a name. We need to walk up the file object chain and append the names of + // the related file objects in reverse order. This means we need to calculate the total length + // first. + + relatedFileObject = FileObject; + subNameLength = 0; + + do + { + subNameLength += relatedFileObject->FileName.Length; + + // Avoid infinite loops. + if (relatedFileObject == relatedFileObject->RelatedFileObject) + break; + + relatedFileObject = relatedFileObject->RelatedFileObject; + } + while (relatedFileObject); + + usedLength += subNameLength; + + // Check if we have enough space to write the whole thing. + if (usedLength > BufferLength) + { + *ReturnLength = usedLength; + + return STATUS_BUFFER_TOO_SMALL; + } + + // We're ready to begin copying the names. + + // Add the name length because we're copying in reverse order. + objectName += subNameLength; + + relatedFileObject = FileObject; + + do + { + objectName -= relatedFileObject->FileName.Length; + memcpy(objectName, relatedFileObject->FileName.Buffer, relatedFileObject->FileName.Length); + + // Avoid infinite loops. + if (relatedFileObject == relatedFileObject->RelatedFileObject) + break; + + relatedFileObject = relatedFileObject->RelatedFileObject; + } + while (relatedFileObject); + + // Update the length. + Buffer->Name.Length += (USHORT)subNameLength; + + // Pass the return length back. + *ReturnLength = usedLength; + + return STATUS_SUCCESS; +} + +/** + * Queries object information. + * + * \param ProcessHandle A handle to a process. + * \param Handle A handle which is present in the process referenced by \a ProcessHandle. + * \param ObjectInformationClass The type of information to retrieve. + * \param ObjectInformation The buffer in which the information will be stored. + * \param ObjectInformationLength The number of bytes available in \a ObjectInformation. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a ObjectInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiQueryInformationObject( + __in HANDLE ProcessHandle, + __in HANDLE Handle, + __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + __out_bcount(ObjectInformationLength) PVOID ObjectInformation, + __in ULONG ObjectInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + KPROCESSOR_MODE referenceMode; + KAPC_STATE apcState; + ULONG returnLength; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(ObjectInformation, ObjectInformationLength, sizeof(ULONG)); + + if (ReturnLength) + ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (process == PsInitialSystemProcess) + { + // A check was added in Windows 7 - if we're attached to the System process, the handle must + // be a kernel handle. + Handle = MakeKernelHandle(Handle); + referenceMode = KernelMode; + } + else + { + // Make sure the handle isn't a kernel handle if we're not attached to the System process. + // This means we can avoid referencing then opening the objects later when calling + // ZwQueryObject, etc. + if (IsKernelHandle(Handle)) + { + ObDereferenceObject(process); + return STATUS_INVALID_HANDLE; + } + + referenceMode = AccessMode; + } + + switch (ObjectInformationClass) + { + case KphObjectBasicInformation: + { + OBJECT_BASIC_INFORMATION basicInfo; + + KeStackAttachProcess(process, &apcState); + status = ZwQueryObject( + Handle, + ObjectBasicInformation, + &basicInfo, + sizeof(OBJECT_BASIC_INFORMATION), + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + if (ObjectInformationLength == sizeof(OBJECT_BASIC_INFORMATION)) + { + __try + { + *(POBJECT_BASIC_INFORMATION)ObjectInformation = basicInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + + returnLength = sizeof(OBJECT_BASIC_INFORMATION); + } + break; + case KphObjectNameInformation: + { + PVOID object; + ULONG allocateSize; + POBJECT_NAME_INFORMATION nameInfo; + + returnLength = sizeof(OBJECT_NAME_INFORMATION); + + // Attach to the process a get a pointer to the object. + KeStackAttachProcess(process, &apcState); + status = ObReferenceObjectByHandle( + Handle, + 0, + NULL, + referenceMode, + &object, + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + allocateSize = ObjectInformationLength; + + if (allocateSize < sizeof(OBJECT_NAME_INFORMATION)) // make sure we never try to allocate 0 bytes + allocateSize = sizeof(OBJECT_NAME_INFORMATION); + + nameInfo = ExAllocatePoolWithQuotaTag(PagedPool, allocateSize, 'QhpK'); + + if (nameInfo) + { + // Make sure we don't leak any data. + memset(nameInfo, 0, ObjectInformationLength); + + status = KphQueryNameObject( + object, + nameInfo, + ObjectInformationLength, + &returnLength + ); + dprintf("KpiQueryInformationObject: called KphQueryNameObject: Handle: 0x%Ix, ObjectInformationLength: %u, returnLength: %u\n", + Handle, ObjectInformationLength, returnLength); + + if (NT_SUCCESS(status)) + { + // Fix up the buffer pointer. + if (nameInfo->Name.Buffer) + nameInfo->Name.Buffer = (PVOID)((ULONG_PTR)nameInfo->Name.Buffer - (ULONG_PTR)nameInfo + (ULONG_PTR)ObjectInformation); + + __try + { + memcpy(ObjectInformation, nameInfo, ObjectInformationLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + + ExFreePoolWithTag(nameInfo, 'QhpK'); + } + else + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + + ObDereferenceObject(object); + } + } + break; + case KphObjectTypeInformation: + { + ULONG allocateSize; + POBJECT_TYPE_INFORMATION typeInfo; + + returnLength = sizeof(OBJECT_TYPE_INFORMATION); + allocateSize = ObjectInformationLength; + + if (allocateSize < sizeof(OBJECT_TYPE_INFORMATION)) + allocateSize = sizeof(OBJECT_TYPE_INFORMATION); + + // ObQueryTypeInfo uses ObjectType->Name.MaximumLength instead of + // ObjectType->Name.Length + sizeof(WCHAR) to calculate the required buffer size. In + // Windows 8, certain object types (e.g. TmTx) do NOT include the null terminator in + // MaximumLength, which causes ObQueryTypeInfo to overrun the given buffer. To work + // around this bug, we add some (generous) padding to our allocation. + allocateSize += sizeof(ULONGLONG); + + typeInfo = ExAllocatePoolWithQuotaTag(PagedPool, allocateSize, 'QhpK'); + + if (typeInfo) + { + memset(typeInfo, 0, ObjectInformationLength); + + KeStackAttachProcess(process, &apcState); + status = ZwQueryObject( + Handle, + ObjectTypeInformation, + typeInfo, + ObjectInformationLength, + &returnLength + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + // Fix up the buffer pointer. + if (typeInfo->TypeName.Buffer) + typeInfo->TypeName.Buffer = (PVOID)((ULONG_PTR)typeInfo->TypeName.Buffer - (ULONG_PTR)typeInfo + (ULONG_PTR)ObjectInformation); + + __try + { + memcpy(ObjectInformation, typeInfo, ObjectInformationLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + + ExFreePoolWithTag(typeInfo, 'QhpK'); + } + else + { + status = STATUS_INSUFFICIENT_RESOURCES; + } + } + break; + case KphObjectHandleFlagInformation: + { + OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo; + + KeStackAttachProcess(process, &apcState); + status = ZwQueryObject( + Handle, + ObjectHandleFlagInformation, + &handleFlagInfo, + sizeof(OBJECT_HANDLE_FLAG_INFORMATION), + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + if (ObjectInformationLength == sizeof(OBJECT_HANDLE_FLAG_INFORMATION)) + { + __try + { + *(POBJECT_HANDLE_FLAG_INFORMATION)ObjectInformation = handleFlagInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + + returnLength = sizeof(OBJECT_HANDLE_FLAG_INFORMATION); + } + break; + case KphObjectProcessBasicInformation: + { + PROCESS_BASIC_INFORMATION basicInfo; + + KeStackAttachProcess(process, &apcState); + status = ZwQueryInformationProcess( + Handle, + ProcessBasicInformation, + &basicInfo, + sizeof(PROCESS_BASIC_INFORMATION), + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + if (ObjectInformationLength == sizeof(PROCESS_BASIC_INFORMATION)) + { + __try + { + *(PPROCESS_BASIC_INFORMATION)ObjectInformation = basicInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + + returnLength = sizeof(PROCESS_BASIC_INFORMATION); + } + break; + case KphObjectThreadBasicInformation: + { + THREAD_BASIC_INFORMATION basicInfo; + + KeStackAttachProcess(process, &apcState); + status = ZwQueryInformationThread( + Handle, + ThreadBasicInformation, + &basicInfo, + sizeof(THREAD_BASIC_INFORMATION), + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + if (ObjectInformationLength == sizeof(THREAD_BASIC_INFORMATION)) + { + __try + { + *(PTHREAD_BASIC_INFORMATION)ObjectInformation = basicInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + + returnLength = sizeof(THREAD_BASIC_INFORMATION); + } + break; + case KphObjectEtwRegBasicInformation: + { + PVOID etwReg; + PVOID objectType; + PUNICODE_STRING objectTypeName; + UNICODE_STRING etwRegistrationName; + PVOID guidEntry; + ETWREG_BASIC_INFORMATION basicInfo; + + // Check dynamic data requirements. + if (KphDynEgeGuid != -1 && + KphDynEreGuidEntry != -1 && + KphDynOtName != -1) + { + // Attach to the process and get a pointer to the object. We don't have a pointer to + // the EtwRegistration object type, so we'll just have to check the type name. + + KeStackAttachProcess(process, &apcState); + status = ObReferenceObjectByHandle( + Handle, + 0, + NULL, + referenceMode, + &etwReg, + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + // Check the type name. + + objectType = ObGetObjectType(etwReg); + + if (objectType) + { + objectTypeName = (PUNICODE_STRING)((ULONG_PTR)objectType + KphDynOtName); + RtlInitUnicodeString(&etwRegistrationName, L"EtwRegistration"); + + if (!RtlEqualUnicodeString(objectTypeName, &etwRegistrationName, FALSE)) + { + status = STATUS_OBJECT_TYPE_MISMATCH; + } + } + else + { + status = STATUS_NOT_SUPPORTED; + } + + if (NT_SUCCESS(status)) + { + guidEntry = *(PVOID *)((ULONG_PTR)etwReg + KphDynEreGuidEntry); + + if (guidEntry) + basicInfo.Guid = *(GUID *)((ULONG_PTR)guidEntry + KphDynEgeGuid); + else + memset(&basicInfo.Guid, 0, sizeof(GUID)); + + basicInfo.SessionId = 0; // not implemented + + if (ObjectInformationLength == sizeof(ETWREG_BASIC_INFORMATION)) + { + __try + { + *(PETWREG_BASIC_INFORMATION)ObjectInformation = basicInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + + ObDereferenceObject(etwReg); + } + } + else + { + status = STATUS_NOT_SUPPORTED; + } + + returnLength = sizeof(ETWREG_BASIC_INFORMATION); + } + break; + case KphObjectFileObjectInformation: + { + PFILE_OBJECT fileObject; + KPH_FILE_OBJECT_INFORMATION objectInfo; + + KeStackAttachProcess(process, &apcState); + status = ObReferenceObjectByHandle( + Handle, + 0, + *IoFileObjectType, + referenceMode, + &fileObject, + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + objectInfo.LockOperation = fileObject->LockOperation; + objectInfo.DeletePending = fileObject->DeletePending; + objectInfo.ReadAccess = fileObject->ReadAccess; + objectInfo.WriteAccess = fileObject->WriteAccess; + objectInfo.DeleteAccess = fileObject->DeleteAccess; + objectInfo.SharedRead = fileObject->SharedRead; + objectInfo.SharedWrite = fileObject->SharedWrite; + objectInfo.SharedDelete = fileObject->SharedDelete; + objectInfo.CurrentByteOffset = fileObject->CurrentByteOffset; + objectInfo.Flags = fileObject->Flags; + + if (ObjectInformationLength == sizeof(KPH_FILE_OBJECT_INFORMATION)) + { + __try + { + *(PKPH_FILE_OBJECT_INFORMATION)ObjectInformation = objectInfo; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + + ObDereferenceObject(fileObject); + } + + returnLength = sizeof(KPH_FILE_OBJECT_INFORMATION); + } + break; + case KphObjectFileObjectDriver: + { + PFILE_OBJECT fileObject; + HANDLE driverHandle; + + if (ObjectInformationLength == sizeof(KPH_FILE_OBJECT_DRIVER)) + { + KeStackAttachProcess(process, &apcState); + status = ObReferenceObjectByHandle( + Handle, + 0, + *IoFileObjectType, + referenceMode, + &fileObject, + NULL + ); + KeUnstackDetachProcess(&apcState); + + if (NT_SUCCESS(status)) + { + if (fileObject->DeviceObject && fileObject->DeviceObject->DriverObject) + { + status = ObOpenObjectByPointer( + fileObject->DeviceObject->DriverObject, + 0, + NULL, + SYNCHRONIZE, + *IoDriverObjectType, + AccessMode, + &driverHandle + ); + } + else + { + driverHandle = NULL; + } + + if (NT_SUCCESS(status)) + { + __try + { + ((PKPH_FILE_OBJECT_DRIVER)ObjectInformation)->DriverHandle = driverHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + + ObDereferenceObject(fileObject); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + break; + default: + status = STATUS_INVALID_INFO_CLASS; + returnLength = 0; + break; + } + + ObDereferenceObject(process); + + if (ReturnLength) + { + if (AccessMode != KernelMode) + { + __try + { + *ReturnLength = returnLength; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + NOTHING; + } + } + else + { + *ReturnLength = returnLength; + } + } + + return status; +} + +/** + * Sets object information. + * + * \param ProcessHandle A handle to a process. + * \param Handle A handle which is present in the process referenced by \a ProcessHandle. + * \param ObjectInformationClass The type of information to set. + * \param ObjectInformation A buffer which contains the information to set. + * \param ObjectInformationLength The number of bytes present in \a ObjectInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiSetInformationObject( + __in HANDLE ProcessHandle, + __in HANDLE Handle, + __in KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + __in_bcount(ObjectInformationLength) PVOID ObjectInformation, + __in ULONG ObjectInformationLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + KAPC_STATE apcState; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + ULONG alignment; + + switch (ObjectInformationClass) + { + case KphObjectHandleFlagInformation: + alignment = sizeof(BOOLEAN); + break; + default: + alignment = sizeof(ULONG); + break; + } + + __try + { + ProbeForRead(ObjectInformation, ObjectInformationLength, alignment); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (process == PsInitialSystemProcess) + { + Handle = MakeKernelHandle(Handle); + } + else + { + if (IsKernelHandle(Handle)) + { + ObDereferenceObject(process); + return STATUS_INVALID_HANDLE; + } + } + + switch (ObjectInformationClass) + { + case KphObjectHandleFlagInformation: + { + OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo; + + if (ObjectInformationLength == sizeof(OBJECT_HANDLE_FLAG_INFORMATION)) + { + __try + { + handleFlagInfo = *(POBJECT_HANDLE_FLAG_INFORMATION)ObjectInformation; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + + if (NT_SUCCESS(status)) + { + KeStackAttachProcess(process, &apcState); + status = ObSetHandleAttributes(Handle, &handleFlagInfo, KernelMode); + KeUnstackDetachProcess(&apcState); + } + } + break; + default: + status = STATUS_INVALID_INFO_CLASS; + break; + } + + ObDereferenceObject(process); + + return status; +} + +NTSTATUS KphOpenNamedObject( + __out PHANDLE ObjectHandle, + __in ACCESS_MASK DesiredAccess, + __in POBJECT_ATTRIBUTES ObjectAttributes, + __in POBJECT_TYPE ObjectType, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + HANDLE objectHandle; + + PAGED_CODE(); + + // Open the object. + status = ObOpenObjectByName( + ObjectAttributes, + ObjectType, + AccessMode, + NULL, + DesiredAccess, + NULL, + &objectHandle + ); + + // Pass the handle back. + if (AccessMode != KernelMode) + { + __try + { + *ObjectHandle = objectHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *ObjectHandle = objectHandle; + } + + return status; +} diff --git a/KProcessHacker/process.c b/KProcessHacker/process.c new file mode 100644 index 0000000..c0aa6d1 --- /dev/null +++ b/KProcessHacker/process.c @@ -0,0 +1,570 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KpiOpenProcess) +#pragma alloc_text(PAGE, KpiOpenProcessToken) +#pragma alloc_text(PAGE, KpiOpenProcessJob) +#pragma alloc_text(PAGE, KpiTerminateProcess) +#pragma alloc_text(PAGE, KpiQueryInformationProcess) +#pragma alloc_text(PAGE, KpiSetInformationProcess) +#endif + +/** + * Opens a process. + * + * \param ProcessHandle A variable which receives the process handle. + * \param DesiredAccess The desired access to the process. + * \param ClientId The identifier of a process or thread. If \a UniqueThread is present, the process + * of the identified thread will be opened. If \a UniqueProcess is present, the identified process + * will be opened. + * \param Key An access key. + * \li If a L2 key is provided, no access checks are performed. + * \li If a L1 key is provided, only read access is permitted but no additional access checks are + * performed. + * \li If no valid key is provided, the function fails. + * \param Client The client that initiated the request. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiOpenProcess( + __out PHANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __in PCLIENT_ID ClientId, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + CLIENT_ID clientId; + PEPROCESS process; + PETHREAD thread; + KPH_KEY_LEVEL requiredKeyLevel; + HANDLE processHandle; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(ProcessHandle, sizeof(HANDLE), sizeof(HANDLE)); + ProbeForRead(ClientId, sizeof(CLIENT_ID), sizeof(ULONG)); + clientId = *ClientId; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + clientId = *ClientId; + } + + // Use the thread ID if it was specified. + if (clientId.UniqueThread) + { + status = PsLookupProcessThreadByCid(&clientId, &process, &thread); + + if (NT_SUCCESS(status)) + { + // We don't actually need the thread. + ObDereferenceObject(thread); + } + } + else + { + status = PsLookupProcessByProcessId(clientId.UniqueProcess, &process); + } + + if (!NT_SUCCESS(status)) + return status; + + requiredKeyLevel = KphKeyLevel1; + + if ((DesiredAccess & KPH_PROCESS_READ_ACCESS) != DesiredAccess) + requiredKeyLevel = KphKeyLevel2; + + if (NT_SUCCESS(status = KphValidateKey(requiredKeyLevel, Key, Client, AccessMode))) + { + // Always open in KernelMode to skip ordinary access checks. + status = ObOpenObjectByPointer( + process, + 0, + NULL, + DesiredAccess, + *PsProcessType, + KernelMode, + &processHandle + ); + } + + ObDereferenceObject(process); + + if (NT_SUCCESS(status)) + { + if (AccessMode != KernelMode) + { + __try + { + *ProcessHandle = processHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *ProcessHandle = processHandle; + } + } + + return status; +} + +/** + * Opens the token of a process. + * + * \param ProcessHandle A handle to a process. + * \param DesiredAccess The desired access to the token. + * \param TokenHandle A variable which receives the token handle. + * \param Key An access key. + * \li If a L2 key is provided, no access checks are performed. + * \li If a L1 key is provided, only read access is permitted but no additional access checks are + * performed. + * \li If no valid key is provided, the function fails. + * \param Client The client that initiated the request. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiOpenProcessToken( + __in HANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE TokenHandle, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + PACCESS_TOKEN primaryToken; + KPH_KEY_LEVEL requiredKeyLevel; + HANDLE tokenHandle; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(TokenHandle, sizeof(HANDLE), sizeof(HANDLE)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (primaryToken = PsReferencePrimaryToken(process)) + { + requiredKeyLevel = KphKeyLevel1; + + if ((DesiredAccess & KPH_TOKEN_READ_ACCESS) != DesiredAccess) + requiredKeyLevel = KphKeyLevel2; + + if (NT_SUCCESS(status = KphValidateKey(requiredKeyLevel, Key, Client, AccessMode))) + { + status = ObOpenObjectByPointer( + primaryToken, + 0, + NULL, + DesiredAccess, + *SeTokenObjectType, + KernelMode, + &tokenHandle + ); + } + + PsDereferencePrimaryToken(primaryToken); + } + else + { + status = STATUS_NO_TOKEN; + } + + ObDereferenceObject(process); + + if (NT_SUCCESS(status)) + { + if (AccessMode != KernelMode) + { + __try + { + *TokenHandle = tokenHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *TokenHandle = tokenHandle; + } + } + + return status; +} + +/** + * Opens the job object of a process. + * + * \param ProcessHandle A handle to a process. + * \param DesiredAccess The desired access to the job. + * \param JobHandle A variable which receives the job object handle. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiOpenProcessJob( + __in HANDLE ProcessHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE JobHandle, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + PEJOB job; + HANDLE jobHandle = NULL; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(JobHandle, sizeof(HANDLE), sizeof(HANDLE)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + job = PsGetProcessJob(process); + + if (job) + { + status = ObOpenObjectByPointer( + job, + 0, + NULL, + DesiredAccess, + *PsJobType, + AccessMode, + &jobHandle + ); + } + else + { + status = STATUS_NOT_FOUND; + } + + ObDereferenceObject(process); + + if (NT_SUCCESS(status)) + { + if (AccessMode != KernelMode) + { + __try + { + *JobHandle = jobHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *JobHandle = jobHandle; + } + } + + return status; +} + +/** + * Terminates a process. + * + * \param ProcessHandle A handle to a process. + * \param ExitStatus A status value which indicates why the process is being terminated. + * \param Key An access key. + * \li If a L2 key is provided, no access checks are performed. + * \li If no valid L2 key is provided, the function fails. + * \param Client The client that initiated the request. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiTerminateProcess( + __in HANDLE ProcessHandle, + __in NTSTATUS ExitStatus, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + + PAGED_CODE(); + + if (!NT_SUCCESS(status = KphValidateKey(KphKeyLevel2, Key, Client, AccessMode))) + return status; + + status = ObReferenceObjectByHandle( + ProcessHandle, + 0, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (process != PsGetCurrentProcess()) + { + HANDLE newProcessHandle; + + // Re-open the process to get a kernel handle. + if (NT_SUCCESS(status = ObOpenObjectByPointer( + process, + OBJ_KERNEL_HANDLE, + NULL, + PROCESS_TERMINATE, + *PsProcessType, + KernelMode, + &newProcessHandle + ))) + { + status = ZwTerminateProcess(newProcessHandle, ExitStatus); + ZwClose(newProcessHandle); + } + } + else + { + status = STATUS_CANT_TERMINATE_SELF; + } + + ObDereferenceObject(process); + + return status; +} + +/** + * Queries process information. + * + * \param ProcessHandle A handle to a process. + * \param ProcessInformationClass The type of information to query. + * \param ProcessInformation The buffer in which the information will be stored. + * \param ProcessInformationLength The number of bytes available in \a ProcessInformation. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a ProcessInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiQueryInformationProcess( + __in HANDLE ProcessHandle, + __in KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + __out_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + ULONG returnLength; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + ULONG alignment; + + switch (ProcessInformationClass) + { + default: + alignment = sizeof(ULONG); + break; + } + + __try + { + ProbeForWrite(ProcessInformation, ProcessInformationLength, alignment); + + if (ReturnLength) + ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + PROCESS_QUERY_INFORMATION, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + switch (ProcessInformationClass) + { + default: + status = STATUS_INVALID_INFO_CLASS; + returnLength = 0; + break; + } + + ObDereferenceObject(process); + + if (ReturnLength) + { + if (AccessMode != KernelMode) + { + __try + { + *ReturnLength = returnLength; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + NOTHING; + } + } + else + { + *ReturnLength = returnLength; + } + } + + return status; +} + +/** + * Sets process information. + * + * \param ProcessHandle A handle to a process. + * \param ProcessInformationClass The type of information to set. + * \param ProcessInformation A buffer which contains the information to set. + * \param ProcessInformationLength The number of bytes present in \a ProcessInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiSetInformationProcess( + __in HANDLE ProcessHandle, + __in KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + __in_bcount(ProcessInformationLength) PVOID ProcessInformation, + __in ULONG ProcessInformationLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + ULONG alignment; + + switch (ProcessInformationClass) + { + default: + alignment = sizeof(ULONG); + break; + } + + __try + { + ProbeForRead(ProcessInformation, ProcessInformationLength, alignment); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ProcessHandle, + PROCESS_SET_INFORMATION, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + switch (ProcessInformationClass) + { + default: + status = STATUS_INVALID_INFO_CLASS; + break; + } + + ObDereferenceObject(process); + + return status; +} diff --git a/KProcessHacker/qrydrv.c b/KProcessHacker/qrydrv.c new file mode 100644 index 0000000..d2cb31b --- /dev/null +++ b/KProcessHacker/qrydrv.c @@ -0,0 +1,238 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +VOID KphpCopyInfoUnicodeString( + __out PVOID Information, + __in_opt PUNICODE_STRING UnicodeString + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KpiOpenDriver) +#pragma alloc_text(PAGE, KpiQueryInformationDriver) +#pragma alloc_text(PAGE, KphpCopyInfoUnicodeString) +#endif + +NTSTATUS KpiOpenDriver( + __out PHANDLE DriverHandle, + __in ACCESS_MASK DesiredAccess, + __in POBJECT_ATTRIBUTES ObjectAttributes, + __in KPROCESSOR_MODE AccessMode + ) +{ + PAGED_CODE(); + + return KphOpenNamedObject( + DriverHandle, + DesiredAccess, + ObjectAttributes, + *IoDriverObjectType, + AccessMode + ); +} + +NTSTATUS KpiQueryInformationDriver( + __in HANDLE DriverHandle, + __in DRIVER_INFORMATION_CLASS DriverInformationClass, + __out_bcount(DriverInformationLength) PVOID DriverInformation, + __in ULONG DriverInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PDRIVER_OBJECT driverObject; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(DriverInformation, DriverInformationLength, 1); + + if (ReturnLength) + ProbeForWrite(ReturnLength, sizeof(ULONG), 1); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + DriverHandle, + 0, + *IoDriverObjectType, + AccessMode, + &driverObject, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + __try + { + switch (DriverInformationClass) + { + // Basic information such as flags, driver base and driver size. + case DriverBasicInformation: + { + if (DriverInformationLength == sizeof(DRIVER_BASIC_INFORMATION)) + { + PDRIVER_BASIC_INFORMATION basicInfo; + + basicInfo = (PDRIVER_BASIC_INFORMATION)DriverInformation; + basicInfo->Flags = driverObject->Flags; + basicInfo->DriverStart = driverObject->DriverStart; + basicInfo->DriverSize = driverObject->DriverSize; + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + + if (ReturnLength) + *ReturnLength = sizeof(DRIVER_BASIC_INFORMATION); + } + break; + + // The name of the driver - e.g. \Driver\Null. + case DriverNameInformation: + { + if (DriverInformation) + { + /* Check buffer length. */ + if (sizeof(UNICODE_STRING) + driverObject->DriverName.Length <= + DriverInformationLength) + { + KphpCopyInfoUnicodeString( + DriverInformation, + &driverObject->DriverName + ); + } + else + { + status = STATUS_BUFFER_TOO_SMALL; + } + } + + if (ReturnLength) + *ReturnLength = sizeof(UNICODE_STRING) + driverObject->DriverName.Length; + } + break; + + // The name of the driver's service key - e.g. \REGISTRY\... + case DriverServiceKeyNameInformation: + { + if (driverObject->DriverExtension) + { + if (DriverInformation) + { + if (sizeof(UNICODE_STRING) + + driverObject->DriverExtension->ServiceKeyName.Length <= + DriverInformationLength) + { + KphpCopyInfoUnicodeString( + DriverInformation, + &driverObject->DriverExtension->ServiceKeyName + ); + } + else + { + status = STATUS_BUFFER_TOO_SMALL; + } + } + + if (ReturnLength) + { + *ReturnLength = sizeof(UNICODE_STRING) + + driverObject->DriverExtension->ServiceKeyName.Length; + } + } + else + { + if (DriverInformation) + { + if (sizeof(UNICODE_STRING) <= DriverInformationLength) + { + // Zero the information buffer. + KphpCopyInfoUnicodeString( + DriverInformation, + NULL + ); + } + else + { + status = STATUS_BUFFER_TOO_SMALL; + } + } + + if (ReturnLength) + *ReturnLength = sizeof(UNICODE_STRING); + } + } + break; + + default: + { + status = STATUS_INVALID_INFO_CLASS; + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + + ObDereferenceObject(driverObject); + + return status; +} + +VOID KphpCopyInfoUnicodeString( + __out PVOID Information, + __in_opt PUNICODE_STRING UnicodeString + ) +{ + PUNICODE_STRING targetUnicodeString = Information; + PWCHAR targetBuffer; + + PAGED_CODE(); + + if (UnicodeString) + { + targetBuffer = (PWCHAR)((PCHAR)Information + sizeof(UNICODE_STRING)); + + targetUnicodeString->Length = UnicodeString->Length; + targetUnicodeString->MaximumLength = UnicodeString->Length; + targetUnicodeString->Buffer = targetBuffer; + memcpy(targetBuffer, UnicodeString->Buffer, UnicodeString->Length); + } + else + { + targetUnicodeString->Length = 0; + targetUnicodeString->MaximumLength = 0; + targetUnicodeString->Buffer = NULL; + } +} diff --git a/KProcessHacker/resource.rc b/KProcessHacker/resource.rc new file mode 100644 index 0000000..c7c1fbf --- /dev/null +++ b/KProcessHacker/resource.rc @@ -0,0 +1,53 @@ +#include + +#define VER_COMMA 3,0,0,0 +#define VER_STR "3.0\0" + +#define VER_FILEVERSION VER_COMMA +#define VER_FILEVERSION_STR VER_STR +#define VER_PRODUCTVERSION VER_COMMA +#define VER_PRODUCTVERSION_STR VER_STR + +#ifndef DEBUG +#define VER_DEBUG 0 +#else +#define VER_DEBUG VS_FF_DEBUG +#endif + +#define VER_PRIVATEBUILD 0 +#define VER_PRERELEASE 0 + +#define VER_COMPANYNAME_STR "wj32\0" +#define VER_FILEDESCRIPTION_STR "KProcessHacker\0" +#define VER_LEGALCOPYRIGHT_STR "Licensed under the GNU GPL, v3.\0" +#define VER_ORIGINALFILENAME_STR "kprocesshacker.sys\0" +#define VER_PRODUCTNAME_STR "KProcessHacker\0" + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_PRODUCTVERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS (VER_PRIVATEBUILD | VER_PRERELEASE | VER_DEBUG) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DRV +FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", VER_COMPANYNAME_STR + VALUE "FileDescription", VER_FILEDESCRIPTION_STR + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "LegalCopyright", VER_LEGALCOPYRIGHT_STR + VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/KProcessHacker/sign.cmd b/KProcessHacker/sign.cmd new file mode 100644 index 0000000..90e2927 --- /dev/null +++ b/KProcessHacker/sign.cmd @@ -0,0 +1,7 @@ +@echo off +set PHBASE=.. +set SIGN_TIMESTAMP=1 +copy bin\i386\kprocesshacker.sys bin-signed\i386\kprocesshacker.sys +copy bin\amd64\kprocesshacker.sys bin-signed\amd64\kprocesshacker.sys +call ..\build\internal\sign.cmd bin-signed\i386\kprocesshacker.sys kmcs +call ..\build\internal\sign.cmd bin-signed\amd64\kprocesshacker.sys kmcs diff --git a/KProcessHacker/sources.inc b/KProcessHacker/sources.inc new file mode 100644 index 0000000..8d3795d --- /dev/null +++ b/KProcessHacker/sources.inc @@ -0,0 +1,28 @@ +TARGETTYPE=DRIVER + +!IF !DEFINED(TARGETNAME) +TARGETNAME=kprocesshacker +!ENDIF + +!IF !DEFINED(TARGETPATH) +TARGETPATH=..\bin +!ENDIF + +TARGETLIBS=$(TARGETLIBS) $(DDK_LIB_PATH)\ksecdd.lib + +INCLUDES=$(DDK_INC_PATH);..\include;..\..\phnt\include;..\..\phlib\include +LIBS=%BUILD%\lib + +SOURCES= \ + ..\main.c \ + ..\devctrl.c \ + ..\dyndata.c \ + ..\dynimp.c \ + ..\object.c \ + ..\process.c \ + ..\qrydrv.c \ + ..\thread.c \ + ..\util.c \ + ..\verify.c \ + ..\vm.c \ + ..\resource.rc diff --git a/KProcessHacker/thread.c b/KProcessHacker/thread.c new file mode 100644 index 0000000..da8154c --- /dev/null +++ b/KProcessHacker/thread.c @@ -0,0 +1,714 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +typedef struct _CAPTURE_BACKTRACE_THREAD_CONTEXT +{ + BOOLEAN Local; + KAPC Apc; + KEVENT CompletedEvent; + ULONG FramesToSkip; + ULONG FramesToCapture; + PVOID *BackTrace; + ULONG CapturedFrames; + ULONG BackTraceHash; +} CAPTURE_BACKTRACE_THREAD_CONTEXT, *PCAPTURE_BACKTRACE_THREAD_CONTEXT; + +KKERNEL_ROUTINE KphpCaptureStackBackTraceThreadSpecialApc; + +VOID KphpCaptureStackBackTraceThreadSpecialApc( + __in PRKAPC Apc, + __inout PKNORMAL_ROUTINE *NormalRoutine, + __inout PVOID *NormalContext, + __inout PVOID *SystemArgument1, + __inout PVOID *SystemArgument2 + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KpiOpenThread) +#pragma alloc_text(PAGE, KpiOpenThreadProcess) +#pragma alloc_text(PAGE, KphCaptureStackBackTraceThread) +#pragma alloc_text(PAGE, KphpCaptureStackBackTraceThreadSpecialApc) +#pragma alloc_text(PAGE, KpiCaptureStackBackTraceThread) +#pragma alloc_text(PAGE, KpiQueryInformationThread) +#pragma alloc_text(PAGE, KpiSetInformationThread) +#endif + +/** + * Opens a thread. + * + * \param ThreadHandle A variable which receives the thread handle. + * \param DesiredAccess The desired access to the thread. + * \param ClientId The identifier of a thread. \a UniqueThread must be present. If \a UniqueProcess + * is present, the process of the referenced thread will be checked against this identifier. + * \param Key An access key. + * \li If a L2 key is provided, no access checks are performed. + * \li If a L1 key is provided, only read access is permitted but no additional access checks are + * performed. + * \li If no valid key is provided, the function fails. + * \param Client The client that initiated the request. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiOpenThread( + __out PHANDLE ThreadHandle, + __in ACCESS_MASK DesiredAccess, + __in PCLIENT_ID ClientId, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + CLIENT_ID clientId; + PETHREAD thread; + KPH_KEY_LEVEL requiredKeyLevel; + HANDLE threadHandle; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(ThreadHandle, sizeof(HANDLE), sizeof(HANDLE)); + ProbeForRead(ClientId, sizeof(CLIENT_ID), sizeof(ULONG)); + clientId = *ClientId; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + clientId = *ClientId; + } + + // Use the process ID if it was specified. + if (clientId.UniqueProcess) + { + status = PsLookupProcessThreadByCid(&clientId, NULL, &thread); + } + else + { + status = PsLookupThreadByThreadId(clientId.UniqueThread, &thread); + } + + if (!NT_SUCCESS(status)) + return status; + + + requiredKeyLevel = KphKeyLevel1; + + if ((DesiredAccess & KPH_THREAD_READ_ACCESS) != DesiredAccess) + requiredKeyLevel = KphKeyLevel2; + + if (NT_SUCCESS(status = KphValidateKey(requiredKeyLevel, Key, Client, AccessMode))) + { + // Always open in KernelMode to skip access checks. + status = ObOpenObjectByPointer( + thread, + 0, + NULL, + DesiredAccess, + *PsThreadType, + KernelMode, + &threadHandle + ); + } + + ObDereferenceObject(thread); + + if (NT_SUCCESS(status)) + { + if (AccessMode != KernelMode) + { + __try + { + *ThreadHandle = threadHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *ThreadHandle = threadHandle; + } + } + + return status; +} + +/** + * Opens the process of a thread. + * + * \param ThreadHandle A handle to a thread. + * \param DesiredAccess The desired access to the process. + * \param ProcessHandle A variable which receives the process handle. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiOpenThreadProcess( + __in HANDLE ThreadHandle, + __in ACCESS_MASK DesiredAccess, + __out PHANDLE ProcessHandle, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PETHREAD thread; + PEPROCESS process; + HANDLE processHandle; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(ProcessHandle, sizeof(HANDLE), sizeof(HANDLE)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ThreadHandle, + 0, + *PsThreadType, + AccessMode, + &thread, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + process = IoThreadToProcess(thread); + + status = ObOpenObjectByPointer( + process, + 0, + NULL, + DesiredAccess, + *PsProcessType, + AccessMode, + &processHandle + ); + + ObDereferenceObject(thread); + + if (NT_SUCCESS(status)) + { + if (AccessMode != KernelMode) + { + __try + { + *ProcessHandle = processHandle; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + *ProcessHandle = processHandle; + } + } + + return status; +} + +/** + * Captures a stack trace of the current thread. + * + * \param FramesToSkip The number of frames to skip from the bottom of the stack. + * \param FramesToCapture The number of frames to capture. + * \param Flags A combination of the following: + * \li \c RTL_WALK_USER_MODE_STACK The user-mode stack will be retrieved instead of the kernel-mode + * stack. + * \param BackTrace An array in which the stack trace will be stored. + * \param BackTraceHash A variable which receives a hash of the stack trace. + * + * \return The number of frames captured. + */ +ULONG KphCaptureStackBackTrace( + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __in_opt ULONG Flags, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG BackTraceHash + ) +{ + PVOID backTrace[MAX_STACK_DEPTH]; + ULONG framesFound; + ULONG hash; + ULONG i; + + // Skip the current frame (for this function). + FramesToSkip++; + + // Ensure that we won't overrun the buffer. + if (FramesToCapture + FramesToSkip > MAX_STACK_DEPTH) + return 0; + // Validate the flags. + if ((Flags & RTL_WALK_VALID_FLAGS) != Flags) + return 0; + + // Walk the stack. + framesFound = RtlWalkFrameChain( + backTrace, + FramesToCapture + FramesToSkip, + Flags + ); + // Return nothing if we found fewer frames than we wanted to skip. + if (framesFound <= FramesToSkip) + return 0; + + // Copy over the stack trace. At the same time we calculate the stack trace hash by summing the + // addresses. + for (i = 0, hash = 0; i < FramesToCapture; i++) + { + if (FramesToSkip + i >= framesFound) + break; + + BackTrace[i] = backTrace[FramesToSkip + i]; + hash += PtrToUlong(BackTrace[i]); + } + + if (BackTraceHash) + *BackTraceHash = hash; + + return i; +} + +/** + * Captures the stack trace of a thread. + * + * \param Thread The thread to capture the stack trace of. + * \param FramesToSkip The number of frames to skip from the bottom of the stack. + * \param FramesToCapture The number of frames to capture. + * \param BackTrace An array in which the stack trace will be stored. + * \param CapturedFrames A variable which receives the number of frames captured. + * \param BackTraceHash A variable which receives a hash of the stack trace. + * \param AccessMode The mode in which to perform access checks. + * + * \return The number of frames captured. + */ +NTSTATUS KphCaptureStackBackTraceThread( + __in PETHREAD Thread, + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG CapturedFrames, + __out_opt PULONG BackTraceHash, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status = STATUS_SUCCESS; + CAPTURE_BACKTRACE_THREAD_CONTEXT context; + ULONG backTraceSize; + PVOID *backTrace; + + PAGED_CODE(); + + // Make sure the caller didn't request too many frames. This also restricts the amount of memory + // we will try to allocate later. + if (FramesToCapture > MAX_STACK_DEPTH) + return STATUS_INVALID_PARAMETER_3; + + backTraceSize = FramesToCapture * sizeof(PVOID); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(BackTrace, backTraceSize, sizeof(PVOID)); + + if (CapturedFrames) + ProbeForWrite(CapturedFrames, sizeof(ULONG), sizeof(ULONG)); + if (BackTraceHash) + ProbeForWrite(BackTraceHash, sizeof(ULONG), sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + // If the caller doesn't want to capture anything, return immediately. + if (backTraceSize == 0) + { + if (AccessMode != KernelMode) + { + __try + { + if (CapturedFrames) + *CapturedFrames = 0; + if (BackTraceHash) + *BackTraceHash = 0; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + if (CapturedFrames) + *CapturedFrames = 0; + if (BackTraceHash) + *BackTraceHash = 0; + } + + return status; + } + + // Allocate storage for the stack trace. + backTrace = ExAllocatePoolWithTag(NonPagedPool, backTraceSize, 'bhpK'); + + if (!backTrace) + return STATUS_INSUFFICIENT_RESOURCES; + + // Initialize the context structure. + context.FramesToSkip = FramesToSkip; + context.FramesToCapture = FramesToCapture; + context.BackTrace = backTrace; + + // Check if we're trying to get a stack trace of the current thread. + // If so, we don't need to insert an APC. + if (Thread == PsGetCurrentThread()) + { + PCAPTURE_BACKTRACE_THREAD_CONTEXT contextPtr = &context; + PVOID dummy = NULL; + KIRQL oldIrql; + + // Raise the IRQL to APC_LEVEL to simulate an APC environment, + // and call the APC routine directly. + + context.Local = TRUE; + KeRaiseIrql(APC_LEVEL, &oldIrql); + KphpCaptureStackBackTraceThreadSpecialApc( + &context.Apc, + NULL, + NULL, + &contextPtr, + &dummy + ); + KeLowerIrql(oldIrql); + } + else + { + context.Local = FALSE; + KeInitializeEvent(&context.CompletedEvent, NotificationEvent, FALSE); + KeInitializeApc( + &context.Apc, + (PKTHREAD)Thread, + OriginalApcEnvironment, + KphpCaptureStackBackTraceThreadSpecialApc, + NULL, + NULL, + KernelMode, + NULL + ); + + if (KeInsertQueueApc(&context.Apc, &context, NULL, 2)) + { + // Wait for the APC to complete. + status = KeWaitForSingleObject( + &context.CompletedEvent, + Executive, + KernelMode, + FALSE, + NULL + ); + } + else + { + status = STATUS_UNSUCCESSFUL; + } + } + + if (NT_SUCCESS(status)) + { + ASSERT(context.CapturedFrames <= FramesToCapture); + + if (AccessMode != KernelMode) + { + __try + { + memcpy(BackTrace, backTrace, context.CapturedFrames * sizeof(PVOID)); + + if (CapturedFrames) + *CapturedFrames = context.CapturedFrames; + if (BackTraceHash) + *BackTraceHash = context.BackTraceHash; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + } + else + { + memcpy(BackTrace, backTrace, context.CapturedFrames * sizeof(PVOID)); + + if (CapturedFrames) + *CapturedFrames = context.CapturedFrames; + if (BackTraceHash) + *BackTraceHash = context.BackTraceHash; + } + } + + ExFreePoolWithTag(backTrace, 'bhpK'); + + return status; +} + +VOID KphpCaptureStackBackTraceThreadSpecialApc( + __in PRKAPC Apc, + __inout PKNORMAL_ROUTINE *NormalRoutine, + __inout PVOID *NormalContext, + __inout PVOID *SystemArgument1, + __inout PVOID *SystemArgument2 + ) +{ + PCAPTURE_BACKTRACE_THREAD_CONTEXT context = *SystemArgument1; + + PAGED_CODE(); + + context->CapturedFrames = KphCaptureStackBackTrace( + context->FramesToSkip, + context->FramesToCapture, + 0, + context->BackTrace, + &context->BackTraceHash + ); + + if (!context->Local) + { + // Notify the originating thread that we have completed. + KeSetEvent(&context->CompletedEvent, 0, FALSE); + } +} + +/** + * Captures the stack trace of a thread. + * + * \param ThreadHandle A handle to the thread to capture the stack trace of. + * \param FramesToSkip The number of frames to skip from the bottom of the stack. + * \param FramesToCapture The number of frames to capture. + * \param BackTrace An array in which the stack trace will be stored. + * \param CapturedFrames A variable which receives the number of frames captured. + * \param BackTraceHash A variable which receives a hash of the stack trace. + * \param AccessMode The mode in which to perform access checks. + * + * \return The number of frames captured. + */ +NTSTATUS KpiCaptureStackBackTraceThread( + __in HANDLE ThreadHandle, + __in ULONG FramesToSkip, + __in ULONG FramesToCapture, + __out_ecount(FramesToCapture) PVOID *BackTrace, + __out_opt PULONG CapturedFrames, + __out_opt PULONG BackTraceHash, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PETHREAD thread; + + PAGED_CODE(); + + status = ObReferenceObjectByHandle( + ThreadHandle, + 0, + *PsThreadType, + AccessMode, + &thread, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + status = KphCaptureStackBackTraceThread( + thread, + FramesToSkip, + FramesToCapture, + BackTrace, + CapturedFrames, + BackTraceHash, + AccessMode + ); + ObDereferenceObject(thread); + + return status; +} + +/** + * Queries thread information. + * + * \param ThreadHandle A handle to a thread. + * \param ThreadInformationClass The type of information to query. + * \param ThreadInformation The buffer in which the information will be stored. + * \param ThreadInformationLength The number of bytes available in \a ThreadInformation. + * \param ReturnLength A variable which receives the number of bytes required to be available in + * \a ThreadInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiQueryInformationThread( + __in HANDLE ThreadHandle, + __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + __out_bcount(ProcessInformationLength) PVOID ThreadInformation, + __in ULONG ThreadInformationLength, + __out_opt PULONG ReturnLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PETHREAD thread; + ULONG returnLength; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForWrite(ThreadInformation, ThreadInformationLength, sizeof(ULONG)); + + if (ReturnLength) + ProbeForWrite(ReturnLength, sizeof(ULONG), sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ThreadHandle, + THREAD_QUERY_INFORMATION, + *PsThreadType, + AccessMode, + &thread, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + switch (ThreadInformationClass) + { + default: + status = STATUS_INVALID_INFO_CLASS; + returnLength = 0; + break; + } + + ObDereferenceObject(thread); + + if (ReturnLength) + { + if (AccessMode != KernelMode) + { + __try + { + *ReturnLength = returnLength; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + NOTHING; + } + } + else + { + *ReturnLength = returnLength; + } + } + + return status; +} + +/** + * Sets thread information. + * + * \param ThreadHandle A handle to a thread. + * \param ThreadInformationClass The type of information to set. + * \param ThreadInformation A buffer which contains the information to set. + * \param ThreadInformationLength The number of bytes present in \a ThreadInformation. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiSetInformationThread( + __in HANDLE ThreadHandle, + __in KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + __in_bcount(ThreadInformationLength) PVOID ThreadInformation, + __in ULONG ThreadInformationLength, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PETHREAD thread; + + PAGED_CODE(); + + if (AccessMode != KernelMode) + { + __try + { + ProbeForRead(ThreadInformation, ThreadInformationLength, sizeof(ULONG)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + + status = ObReferenceObjectByHandle( + ThreadHandle, + THREAD_SET_INFORMATION, + *PsThreadType, + AccessMode, + &thread, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + switch (ThreadInformationClass) + { + default: + status = STATUS_INVALID_INFO_CLASS; + break; + } + + ObDereferenceObject(thread); + + return status; +} diff --git a/KProcessHacker/util.c b/KProcessHacker/util.c new file mode 100644 index 0000000..b30ae7e --- /dev/null +++ b/KProcessHacker/util.c @@ -0,0 +1,272 @@ +/* + * KProcessHacker + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphFreeCapturedUnicodeString) +#pragma alloc_text(PAGE, KphCaptureUnicodeString) +#pragma alloc_text(PAGE, KphEnumerateSystemModules) +#pragma alloc_text(PAGE, KphValidateAddressForSystemModules) +#pragma alloc_text(PAGE, KphGetProcessMappedFileName) +#endif + +VOID KphFreeCapturedUnicodeString( + __in PUNICODE_STRING CapturedUnicodeString + ) +{ + PAGED_CODE(); + + if (CapturedUnicodeString->Buffer) + ExFreePoolWithTag(CapturedUnicodeString->Buffer, 'UhpK'); +} + +NTSTATUS KphCaptureUnicodeString( + __in PUNICODE_STRING UnicodeString, + __out PUNICODE_STRING CapturedUnicodeString + ) +{ + UNICODE_STRING unicodeString; + PWCHAR userBuffer; + + PAGED_CODE(); + + __try + { + ProbeForRead(UnicodeString, sizeof(UNICODE_STRING), sizeof(ULONG)); + unicodeString.Length = UnicodeString->Length; + unicodeString.MaximumLength = unicodeString.Length; + unicodeString.Buffer = NULL; + + userBuffer = UnicodeString->Buffer; + ProbeForRead(userBuffer, unicodeString.Length, sizeof(WCHAR)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + if (unicodeString.Length & 1) + { + return STATUS_INVALID_PARAMETER; + } + + if (unicodeString.Length != 0) + { + unicodeString.Buffer = ExAllocatePoolWithTag( + PagedPool, + unicodeString.Length, + 'UhpK' + ); + + if (!unicodeString.Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + __try + { + memcpy( + unicodeString.Buffer, + userBuffer, + unicodeString.Length + ); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + KphFreeCapturedUnicodeString(&unicodeString); + return GetExceptionCode(); + } + } + + *CapturedUnicodeString = unicodeString; + + return STATUS_SUCCESS; +} + +/** + * Enumerates the modules loaded by the kernel. + * + * \param Modules A variable which receives a pointer to a structure containing information about + * the kernel modules. The structure must be freed with the tag 'ThpK'. + */ +NTSTATUS KphEnumerateSystemModules( + __out PRTL_PROCESS_MODULES *Modules + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + ULONG attempts; + + PAGED_CODE(); + + bufferSize = 2048; + attempts = 8; + + do + { + buffer = ExAllocatePoolWithTag(PagedPool, bufferSize, 'ThpK'); + + if (!buffer) + { + status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + status = ZwQuerySystemInformation( + SystemModuleInformation, + buffer, + bufferSize, + &bufferSize + ); + + if (NT_SUCCESS(status)) + { + *Modules = buffer; + + return status; + } + + ExFreePoolWithTag(buffer, 'ThpK'); + + if (status != STATUS_INFO_LENGTH_MISMATCH) + { + break; + } + } while (--attempts); + + return status; +} + +/** + * Checks if an address range lies within a kernel module. + * + * \param Address The beginning of the address range. + * \param Length The number of bytes in the address range. + */ +NTSTATUS KphValidateAddressForSystemModules( + __in PVOID Address, + __in SIZE_T Length + ) +{ + NTSTATUS status; + PRTL_PROCESS_MODULES modules; + ULONG i; + BOOLEAN valid; + + PAGED_CODE(); + + status = KphEnumerateSystemModules(&modules); + + if (!NT_SUCCESS(status)) + return status; + + valid = FALSE; + + for (i = 0; i < modules->NumberOfModules; i++) + { + if ( + (ULONG_PTR)Address + Length >= (ULONG_PTR)Address && + (ULONG_PTR)Address >= (ULONG_PTR)modules->Modules[i].ImageBase && + (ULONG_PTR)Address + Length <= (ULONG_PTR)modules->Modules[i].ImageBase + modules->Modules[i].ImageSize + ) + { + dprintf("Validated address 0x%Ix in %s\n", Address, modules->Modules[i].FullPathName); + valid = TRUE; + break; + } + } + + ExFreePoolWithTag(modules, 'ThpK'); + + if (valid) + status = STATUS_SUCCESS; + else + status = STATUS_ACCESS_VIOLATION; + + return status; +} + +/** + * Gets the file name of a mapped section. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param BaseAddress The base address of the section view. + * \param Modules A variable which receives a pointer to a string containing the file name of the + * section. The structure must be freed with the tag 'ThpK'. + */ +NTSTATUS KphGetProcessMappedFileName( + __in HANDLE ProcessHandle, + __in PVOID BaseAddress, + __out PUNICODE_STRING *FileName + ) +{ + NTSTATUS status; + PVOID buffer; + SIZE_T bufferSize; + SIZE_T returnLength; + + PAGED_CODE(); + + bufferSize = 0x100; + buffer = ExAllocatePoolWithTag(PagedPool, bufferSize, 'ThpK'); + + if (!buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + status = ZwQueryVirtualMemory( + ProcessHandle, + BaseAddress, + MemoryMappedFilenameInformation, + buffer, + bufferSize, + &returnLength + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + ExFreePoolWithTag(buffer, 'ThpK'); + bufferSize = returnLength; + buffer = ExAllocatePoolWithTag(PagedPool, bufferSize, 'ThpK'); + + if (!buffer) + return STATUS_INSUFFICIENT_RESOURCES; + + status = ZwQueryVirtualMemory( + ProcessHandle, + BaseAddress, + MemoryMappedFilenameInformation, + buffer, + bufferSize, + &returnLength + ); + } + + if (!NT_SUCCESS(status)) + { + ExFreePoolWithTag(buffer, 'ThpK'); + return status; + } + + *FileName = buffer; + + return status; +} diff --git a/KProcessHacker/verify.c b/KProcessHacker/verify.c new file mode 100644 index 0000000..08ccbf1 --- /dev/null +++ b/KProcessHacker/verify.c @@ -0,0 +1,496 @@ +/* + * KProcessHacker + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +#define FILE_BUFFER_SIZE (2 * PAGE_SIZE) +#define FILE_MAX_SIZE (32 * 1024 * 1024) // 32 MB + +VOID KphpBackoffKey( + __in PKPH_CLIENT Client + ); + +static UCHAR KphpTrustedPublicKey[] = +{ + 0x45, 0x43, 0x53, 0x31, 0x20, 0x00, 0x00, 0x00, 0x5f, 0xe8, 0xab, 0xac, 0x01, 0xad, 0x6b, 0x48, + 0xfd, 0x84, 0x7f, 0x43, 0x70, 0xb6, 0x57, 0xb0, 0x76, 0xe3, 0x10, 0x07, 0x19, 0xbd, 0x0e, 0xd4, + 0x10, 0x5c, 0x1f, 0xfc, 0x40, 0x91, 0xb6, 0xed, 0x94, 0x37, 0x76, 0xb7, 0x86, 0x88, 0xf7, 0x34, + 0x12, 0x91, 0xf6, 0x65, 0x23, 0x58, 0xc9, 0xeb, 0x2f, 0xcb, 0x96, 0x13, 0x8f, 0xca, 0x57, 0x7a, + 0xd0, 0x7a, 0xbf, 0x22, 0xde, 0xd2, 0x15, 0xfc +}; + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphHashFile) +#pragma alloc_text(PAGE, KphVerifyFile) +#pragma alloc_text(PAGE, KphVerifyClient) +#pragma alloc_text(PAGE, KpiVerifyClient) +#pragma alloc_text(PAGE, KphGenerateKeysClient) +#pragma alloc_text(PAGE, KphRetrieveKeyViaApc) +#pragma alloc_text(PAGE, KphValidateKey) +#pragma alloc_text(PAGE, KphpBackoffKey) +#endif + +NTSTATUS KphHashFile( + __in PUNICODE_STRING FileName, + __out PVOID *Hash, + __out PULONG HashSize + ) +{ + NTSTATUS status; + BCRYPT_ALG_HANDLE hashAlgHandle = NULL; + ULONG querySize; + ULONG hashObjectSize; + ULONG hashSize; + PVOID hashObject = NULL; + PVOID hash = NULL; + BCRYPT_HASH_HANDLE hashHandle = NULL; + OBJECT_ATTRIBUTES objectAttributes; + IO_STATUS_BLOCK iosb; + HANDLE fileHandle = NULL; + FILE_STANDARD_INFORMATION standardInfo; + ULONG remainingBytes; + ULONG bytesToRead; + PVOID buffer = NULL; + + PAGED_CODE(); + + // Open the hash algorithm and allocate memory for the hash object. + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hashAlgHandle, KPH_HASH_ALGORITHM, NULL, 0))) + goto CleanupExit; + if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_OBJECT_LENGTH, + (PUCHAR)&hashObjectSize, sizeof(ULONG), &querySize, 0))) + { + goto CleanupExit; + } + if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_HASH_LENGTH, (PUCHAR)&hashSize, + sizeof(ULONG), &querySize, 0))) + { + goto CleanupExit; + } + + if (!(hashObject = ExAllocatePoolWithTag(PagedPool, hashObjectSize, 'vhpK'))) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto CleanupExit; + } + if (!(hash = ExAllocatePoolWithTag(PagedPool, hashSize, 'vhpK'))) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto CleanupExit; + } + + if (!NT_SUCCESS(status = BCryptCreateHash(hashAlgHandle, &hashHandle, hashObject, hashObjectSize, + NULL, 0, 0))) + { + goto CleanupExit; + } + + // Open the file and compute the hash. + + InitializeObjectAttributes(&objectAttributes, FileName, OBJ_KERNEL_HANDLE, NULL, NULL); + + if (!NT_SUCCESS(status = ZwCreateFile(&fileHandle, FILE_GENERIC_READ, &objectAttributes, + &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0))) + { + goto CleanupExit; + } + + if (!NT_SUCCESS(status = ZwQueryInformationFile(fileHandle, &iosb, &standardInfo, + sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation))) + { + goto CleanupExit; + } + + if (standardInfo.EndOfFile.QuadPart <= 0) + { + status = STATUS_UNSUCCESSFUL; + goto CleanupExit; + } + if (standardInfo.EndOfFile.QuadPart > FILE_MAX_SIZE) + { + status = STATUS_FILE_TOO_LARGE; + goto CleanupExit; + } + + if (!(buffer = ExAllocatePoolWithTag(PagedPool, FILE_BUFFER_SIZE, 'vhpK'))) + { + status = STATUS_INSUFFICIENT_RESOURCES; + goto CleanupExit; + } + + remainingBytes = (ULONG)standardInfo.EndOfFile.QuadPart; + + while (remainingBytes != 0) + { + bytesToRead = FILE_BUFFER_SIZE; + if (bytesToRead > remainingBytes) + bytesToRead = remainingBytes; + + if (!NT_SUCCESS(status = ZwReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, bytesToRead, + NULL, NULL))) + { + goto CleanupExit; + } + if ((ULONG)iosb.Information != bytesToRead) + { + status = STATUS_INTERNAL_ERROR; + goto CleanupExit; + } + + if (!NT_SUCCESS(status = BCryptHashData(hashHandle, buffer, bytesToRead, 0))) + goto CleanupExit; + + remainingBytes -= bytesToRead; + } + + if (!NT_SUCCESS(status = BCryptFinishHash(hashHandle, hash, hashSize, 0))) + goto CleanupExit; + + if (NT_SUCCESS(status)) + { + *Hash = hash; + *HashSize = hashSize; + + hash = NULL; // Don't free this in the cleanup section + } + +CleanupExit: + if (buffer) + ExFreePoolWithTag(buffer, 'vhpK'); + if (fileHandle) + ZwClose(fileHandle); + if (hashHandle) + BCryptDestroyHash(hashHandle); + if (hash) + ExFreePoolWithTag(hash, 'vhpK'); + if (hashObject) + ExFreePoolWithTag(hashObject, 'vhpK'); + if (hashAlgHandle) + BCryptCloseAlgorithmProvider(hashAlgHandle, 0); + + return status; +} + +NTSTATUS KphVerifyFile( + __in PUNICODE_STRING FileName, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize + ) +{ + NTSTATUS status; + BCRYPT_ALG_HANDLE signAlgHandle = NULL; + BCRYPT_KEY_HANDLE keyHandle = NULL; + PVOID hash = NULL; + ULONG hashSize; + + PAGED_CODE(); + + // Import the trusted public key. + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, KPH_SIGN_ALGORITHM, NULL, 0))) + goto CleanupExit; + if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, KPH_BLOB_PUBLIC, &keyHandle, + KphpTrustedPublicKey, sizeof(KphpTrustedPublicKey), 0))) + { + goto CleanupExit; + } + + // Hash the file. + + if (!NT_SUCCESS(status = KphHashFile(FileName, &hash, &hashSize))) + goto CleanupExit; + + // Verify the hash. + + if (!NT_SUCCESS(status = BCryptVerifySignature(keyHandle, NULL, hash, hashSize, Signature, + SignatureSize, 0))) + { + goto CleanupExit; + } + +CleanupExit: + if (hash) + ExFreePoolWithTag(hash, 'vhpK'); + if (keyHandle) + BCryptDestroyKey(keyHandle); + if (signAlgHandle) + BCryptCloseAlgorithmProvider(signAlgHandle, 0); + + return status; +} + +VOID KphVerifyClient( + __inout PKPH_CLIENT Client, + __in PVOID CodeAddress, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize + ) +{ + NTSTATUS status; + PUNICODE_STRING processFileName = NULL; + MEMORY_BASIC_INFORMATION memoryBasicInfo; + PUNICODE_STRING mappedFileName = NULL; + + PAGED_CODE(); + + if (Client->VerificationPerformed) + return; + + if ((ULONG_PTR)CodeAddress > (ULONG_PTR)MmHighestUserAddress) + { + status = STATUS_ACCESS_VIOLATION; + goto CleanupExit; + } + if (!NT_SUCCESS(status = SeLocateProcessImageName(PsGetCurrentProcess(), &processFileName))) + goto CleanupExit; + if (!NT_SUCCESS(status = ZwQueryVirtualMemory(NtCurrentProcess(), CodeAddress, + MemoryBasicInformation, &memoryBasicInfo, sizeof(MEMORY_BASIC_INFORMATION), NULL))) + { + goto CleanupExit; + } + if (memoryBasicInfo.Type != MEM_IMAGE || memoryBasicInfo.State != MEM_COMMIT) + { + status = STATUS_INVALID_PARAMETER; + goto CleanupExit; + } + if (!NT_SUCCESS(status = KphGetProcessMappedFileName(NtCurrentProcess(), CodeAddress, + &mappedFileName))) + { + goto CleanupExit; + } + if (!RtlEqualUnicodeString(processFileName, mappedFileName, TRUE)) + { + status = STATUS_INVALID_PARAMETER; + goto CleanupExit; + } + + status = KphVerifyFile(processFileName, Signature, SignatureSize); + +CleanupExit: + if (mappedFileName) + ExFreePoolWithTag(mappedFileName, 'ThpK'); + if (processFileName) + ExFreePool(processFileName); + + ExAcquireFastMutex(&Client->StateMutex); + + if (NT_SUCCESS(status)) + { + Client->VerifiedProcess = PsGetCurrentProcess(); + Client->VerifiedProcessId = PsGetCurrentProcessId(); + Client->VerifiedRangeBase = memoryBasicInfo.BaseAddress; + Client->VerifiedRangeSize = memoryBasicInfo.RegionSize; + } + + Client->VerificationStatus = status; + MemoryBarrier(); + Client->VerificationSucceeded = NT_SUCCESS(status); + Client->VerificationPerformed = TRUE; + + ExReleaseFastMutex(&Client->StateMutex); +} + +NTSTATUS KpiVerifyClient( + __in PVOID CodeAddress, + __in_bcount(SignatureSize) PUCHAR Signature, + __in ULONG SignatureSize, + __in PKPH_CLIENT Client + ) +{ + PUCHAR signature; + + PAGED_CODE(); + + __try + { + ProbeForRead(Signature, SignatureSize, 1); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + if (SignatureSize > KPH_SIGNATURE_MAX_SIZE) + return STATUS_INVALID_PARAMETER_3; + + signature = ExAllocatePoolWithTag(PagedPool, SignatureSize, 'ThpK'); + if (!signature) + return STATUS_INSUFFICIENT_RESOURCES; + + __try + { + memcpy(signature, Signature, SignatureSize); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + ExFreePoolWithTag(signature, 'ThpK'); + return GetExceptionCode(); + } + + KphVerifyClient(Client, CodeAddress, Signature, SignatureSize); + ExFreePoolWithTag(signature, 'ThpK'); + + return Client->VerificationStatus; +} + +VOID KphGenerateKeysClient( + __inout PKPH_CLIENT Client + ) +{ + ULONGLONG interruptTime; + ULONG seed; + KPH_KEY l1Key; + KPH_KEY l2Key; + + PAGED_CODE(); + + if (Client->KeysGenerated) + return; + + interruptTime = KeQueryInterruptTime(); + seed = (ULONG)(interruptTime >> 32) | (ULONG)interruptTime | PtrToUlong(Client); + l1Key = RtlRandomEx(&seed) | 0x80000000; // Make sure the key is nonzero + do + { + l2Key = RtlRandomEx(&seed) | 0x80000000; + } while (l2Key == l1Key); + + ExAcquireFastMutex(&Client->StateMutex); + + if (!Client->KeysGenerated) + { + Client->L1Key = l1Key; + Client->L2Key = l2Key; + MemoryBarrier(); + Client->KeysGenerated = TRUE; + } + + ExReleaseFastMutex(&Client->StateMutex); +} + +NTSTATUS KphRetrieveKeyViaApc( + __inout PKPH_CLIENT Client, + __in KPH_KEY_LEVEL KeyLevel, + __inout PIRP Irp + ) +{ + PIO_APC_ROUTINE userApcRoutine; + KPH_KEY key; + + PAGED_CODE(); + + if (!Client->VerificationSucceeded) + return STATUS_ACCESS_DENIED; + + MemoryBarrier(); + + if (PsGetCurrentProcess() != Client->VerifiedProcess || + PsGetCurrentProcessId() != Client->VerifiedProcessId) + { + return STATUS_ACCESS_DENIED; + } + + userApcRoutine = Irp->Overlay.AsynchronousParameters.UserApcRoutine; + + if (!userApcRoutine) + return STATUS_INVALID_PARAMETER; + if ((ULONG_PTR)userApcRoutine < (ULONG_PTR)Client->VerifiedRangeBase || + (ULONG_PTR)userApcRoutine >= (ULONG_PTR)Client->VerifiedRangeBase + Client->VerifiedRangeSize) + { + return STATUS_ACCESS_DENIED; + } + + KphGenerateKeysClient(Client); + + switch (KeyLevel) + { + case KphKeyLevel1: + key = Client->L1Key; + break; + case KphKeyLevel2: + key = Client->L2Key; + break; + default: + return STATUS_INVALID_PARAMETER; + } + + Irp->Overlay.AsynchronousParameters.UserApcContext = UlongToPtr(key); + + return STATUS_SUCCESS; +} + +NTSTATUS KphValidateKey( + __in KPH_KEY_LEVEL RequiredKeyLevel, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + PAGED_CODE(); + + if (AccessMode == KernelMode) + return STATUS_SUCCESS; + + if (Key && Client->VerificationSucceeded && Client->KeysGenerated) + { + MemoryBarrier(); + + switch (RequiredKeyLevel) + { + case KphKeyLevel1: + if (Key == Client->L1Key || Key == Client->L2Key) + return STATUS_SUCCESS; + else + KphpBackoffKey(Client); + break; + case KphKeyLevel2: + if (Key == Client->L2Key) + return STATUS_SUCCESS; + else + KphpBackoffKey(Client); + break; + default: + return STATUS_INVALID_PARAMETER; + } + } + + return STATUS_ACCESS_DENIED; +} + +VOID KphpBackoffKey( + __in PKPH_CLIENT Client + ) +{ + LARGE_INTEGER backoffTime; + + PAGED_CODE(); + + // Serialize to make it impossible for a single client to bypass the backoff by creating + // multiple threads. + ExAcquireFastMutex(&Client->KeyBackoffMutex); + + backoffTime.QuadPart = -KPH_KEY_BACKOFF_TIME; + KeDelayExecutionThread(KernelMode, FALSE, &backoffTime); + + ExReleaseFastMutex(&Client->KeyBackoffMutex); +} diff --git a/KProcessHacker/vm.c b/KProcessHacker/vm.c new file mode 100644 index 0000000..41dc7ef --- /dev/null +++ b/KProcessHacker/vm.c @@ -0,0 +1,449 @@ +/* + * KProcessHacker + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +ULONG KphpGetCopyExceptionInfo( + __in PEXCEPTION_POINTERS ExceptionInfo, + __out PBOOLEAN HaveBadAddress, + __out PULONG_PTR BadAddress + ); + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, KphCopyVirtualMemory) +#pragma alloc_text(PAGE, KpiReadVirtualMemoryUnsafe) +#endif + +#define KPH_STACK_COPY_BYTES 0x200 +#define KPH_POOL_COPY_BYTES 0x10000 +#define KPH_MAPPED_COPY_PAGES 14 +#define KPH_POOL_COPY_THRESHOLD 0x3ff + +ULONG KphpGetCopyExceptionInfo( + __in PEXCEPTION_POINTERS ExceptionInfo, + __out PBOOLEAN HaveBadAddress, + __out PULONG_PTR BadAddress + ) +{ + PEXCEPTION_RECORD exceptionRecord; + + *HaveBadAddress = FALSE; + exceptionRecord = ExceptionInfo->ExceptionRecord; + + if ((exceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) || + (exceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) || + (exceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR)) + { + if (exceptionRecord->NumberParameters > 1) + { + /* We have the address. */ + *HaveBadAddress = TRUE; + *BadAddress = exceptionRecord->ExceptionInformation[1]; + } + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +/** + * Copies memory from one process to another. + * + * \param FromProcess The source process. + * \param FromAddress The source address. + * \param ToProcess The target process. + * \param ToAddress The target address. + * \param BufferLength The number of bytes to copy. + * \param AccessMode The mode in which to perform access checks. + * \param ReturnLength A variable which receives the number of bytes copied. + */ +NTSTATUS KphCopyVirtualMemory( + __in PEPROCESS FromProcess, + __in PVOID FromAddress, + __in PEPROCESS ToProcess, + __in PVOID ToAddress, + __in SIZE_T BufferLength, + __in KPROCESSOR_MODE AccessMode, + __out PSIZE_T ReturnLength + ) +{ + UCHAR stackBuffer[KPH_STACK_COPY_BYTES]; + PVOID buffer; + PFN_NUMBER mdlBuffer[(sizeof(MDL) / sizeof(PFN_NUMBER)) + KPH_MAPPED_COPY_PAGES + 1]; + PMDL mdl = (PMDL)mdlBuffer; + PVOID mappedAddress; + SIZE_T mappedTotalSize; + SIZE_T blockSize; + SIZE_T stillToCopy; + KAPC_STATE apcState; + PVOID sourceAddress; + PVOID targetAddress; + BOOLEAN doMappedCopy; + BOOLEAN pagesLocked; + BOOLEAN copyingToTarget = FALSE; + BOOLEAN probing = FALSE; + BOOLEAN mapping = FALSE; + BOOLEAN haveBadAddress; + ULONG_PTR badAddress; + + PAGED_CODE(); + + sourceAddress = FromAddress; + targetAddress = ToAddress; + + // We don't check if buffer == NULL when freeing. If buffer doesn't need to be freed, set to + // stackBuffer, not NULL. + buffer = stackBuffer; + + mappedTotalSize = (KPH_MAPPED_COPY_PAGES - 2) * PAGE_SIZE; + + if (mappedTotalSize > BufferLength) + mappedTotalSize = BufferLength; + + stillToCopy = BufferLength; + blockSize = mappedTotalSize; + + while (stillToCopy) + { + // If we're at the last copy block, copy the remaining bytes instead of the whole block + // size. + if (blockSize > stillToCopy) + blockSize = stillToCopy; + + // Choose the best method based on the number of bytes left to copy. + if (blockSize > KPH_POOL_COPY_THRESHOLD) + { + doMappedCopy = TRUE; + } + else + { + doMappedCopy = FALSE; + + if (blockSize <= KPH_STACK_COPY_BYTES) + { + if (buffer != stackBuffer) + ExFreePoolWithTag(buffer, 'ChpK'); + + buffer = stackBuffer; + } + else + { + // Don't allocate the buffer if we've done so already. Note that the block size + // never increases, so this allocation will always be OK. + if (buffer == stackBuffer) + { + // Keep trying to allocate a buffer. + + while (TRUE) + { + buffer = ExAllocatePoolWithTag(NonPagedPool, blockSize, 'ChpK'); + + // Stop trying if we got a buffer. + if (buffer) + break; + + blockSize /= 2; + + // Use the stack buffer if we can. + if (blockSize <= KPH_STACK_COPY_BYTES) + { + buffer = stackBuffer; + break; + } + } + } + } + } + + // Reset state. + mappedAddress = NULL; + pagesLocked = FALSE; + copyingToTarget = FALSE; + + KeStackAttachProcess(FromProcess, &apcState); + + __try + { + // Probe only if this is the first time. + if (sourceAddress == FromAddress && AccessMode != KernelMode) + { + probing = TRUE; + ProbeForRead(sourceAddress, BufferLength, sizeof(UCHAR)); + probing = FALSE; + } + + if (doMappedCopy) + { + // Initialize the MDL. + MmInitializeMdl(mdl, sourceAddress, blockSize); + MmProbeAndLockPages(mdl, AccessMode, IoReadAccess); + pagesLocked = TRUE; + + // Map the pages. + mappedAddress = MmMapLockedPagesSpecifyCache( + mdl, + KernelMode, + MmCached, + NULL, + FALSE, + HighPagePriority + ); + + if (!mappedAddress) + { + // Insufficient resources; exit. + mapping = TRUE; + ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES); + } + } + else + { + memcpy(buffer, sourceAddress, blockSize); + } + + KeUnstackDetachProcess(&apcState); + + // Attach to the target process and copy the contents out. + KeStackAttachProcess(ToProcess, &apcState); + + // Probe only if this is the first time. + if (targetAddress == ToAddress && AccessMode != KernelMode) + { + probing = TRUE; + ProbeForWrite(targetAddress, BufferLength, sizeof(UCHAR)); + probing = FALSE; + } + + // Copy the data. + copyingToTarget = TRUE; + + if (doMappedCopy) + memcpy(targetAddress, mappedAddress, blockSize); + else + memcpy(targetAddress, buffer, blockSize); + } + __except (KphpGetCopyExceptionInfo( + GetExceptionInformation(), + &haveBadAddress, + &badAddress + )) + { + KeUnstackDetachProcess(&apcState); + + // If we mapped the pages, unmap them. + if (mappedAddress) + MmUnmapLockedPages(mappedAddress, mdl); + + // If we locked the pages, unlock them. + if (pagesLocked) + MmUnlockPages(mdl); + + // If we allocated pool storage, free it. + if (buffer != stackBuffer) + ExFreePoolWithTag(buffer, 'ChpK'); + + // If we failed when probing or mapping, return the error status. + if (probing || mapping) + return GetExceptionCode(); + + // Determine which copy failed. + if (copyingToTarget && haveBadAddress) + { + *ReturnLength = (ULONG)(badAddress - (ULONG_PTR)sourceAddress); + } + else + { + *ReturnLength = BufferLength - stillToCopy; + } + + return STATUS_PARTIAL_COPY; + } + + KeUnstackDetachProcess(&apcState); + + if (doMappedCopy) + { + MmUnmapLockedPages(mappedAddress, mdl); + MmUnlockPages(mdl); + } + + stillToCopy -= blockSize; + sourceAddress = (PVOID)((ULONG_PTR)sourceAddress + blockSize); + targetAddress = (PVOID)((ULONG_PTR)targetAddress + blockSize); + } + + if (buffer != stackBuffer) + ExFreePoolWithTag(buffer, 'ChpK'); + + *ReturnLength = BufferLength; + + return STATUS_SUCCESS; +} + +/** + * Copies process or kernel memory into the current process. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_VM_READ access. This + * parameter may be NULL if \a BaseAddress lies above the user-mode range. + * \param BaseAddress The address from which memory is to be copied. + * \param Buffer A buffer which receives the copied memory. + * \param BufferSize The number of bytes to copy. + * \param NumberOfBytesRead A variable which receives the number of bytes copied to the buffer. + * \param Key An access key. If no valid L2 key is provided, the function fails. + * \param Client The client that initiated the request. + * \param AccessMode The mode in which to perform access checks. + */ +NTSTATUS KpiReadVirtualMemoryUnsafe( + __in_opt HANDLE ProcessHandle, + __in PVOID BaseAddress, + __out_bcount(BufferSize) PVOID Buffer, + __in SIZE_T BufferSize, + __out_opt PSIZE_T NumberOfBytesRead, + __in_opt KPH_KEY Key, + __in PKPH_CLIENT Client, + __in KPROCESSOR_MODE AccessMode + ) +{ + NTSTATUS status; + PEPROCESS process; + SIZE_T numberOfBytesRead; + + PAGED_CODE(); + + if (!NT_SUCCESS(status = KphValidateKey(KphKeyLevel2, Key, Client, AccessMode))) + return status; + + if (AccessMode != KernelMode) + { + if ( + (ULONG_PTR)BaseAddress + BufferSize < (ULONG_PTR)BaseAddress || + (ULONG_PTR)Buffer + BufferSize < (ULONG_PTR)Buffer || + (ULONG_PTR)Buffer + BufferSize > (ULONG_PTR)MmHighestUserAddress + ) + { + return STATUS_ACCESS_VIOLATION; + } + + if (NumberOfBytesRead) + { + __try + { + ProbeForWrite(NumberOfBytesRead, sizeof(SIZE_T), sizeof(SIZE_T)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + } + + if (BufferSize != 0) + { + // Select the appropriate copy method. + if ((ULONG_PTR)BaseAddress + BufferSize > (ULONG_PTR)MmHighestUserAddress) + { + ULONG_PTR page; + ULONG_PTR pageEnd; + + status = KphValidateAddressForSystemModules(BaseAddress, BufferSize); + + if (!NT_SUCCESS(status)) + return status; + + // Kernel memory copy (unsafe) + + page = (ULONG_PTR)BaseAddress & ~(PAGE_SIZE - 1); + pageEnd = ((ULONG_PTR)BaseAddress + BufferSize - 1) & ~(PAGE_SIZE - 1); + + __try + { + // This will obviously fail if any of the pages aren't resident. + for (; page <= pageEnd; page += PAGE_SIZE) + { + if (!MmIsAddressValid((PVOID)page)) + ExRaiseStatus(STATUS_ACCESS_VIOLATION); + } + + memcpy(Buffer, BaseAddress, BufferSize); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + numberOfBytesRead = BufferSize; + status = STATUS_SUCCESS; + } + else + { + // User memory copy (safe) + + status = ObReferenceObjectByHandle( + ProcessHandle, + PROCESS_VM_READ, + *PsProcessType, + AccessMode, + &process, + NULL + ); + + if (NT_SUCCESS(status)) + { + status = KphCopyVirtualMemory( + process, + BaseAddress, + PsGetCurrentProcess(), + Buffer, + BufferSize, + AccessMode, + &numberOfBytesRead + ); + ObDereferenceObject(process); + } + } + } + else + { + numberOfBytesRead = 0; + status = STATUS_SUCCESS; + } + + if (NumberOfBytesRead) + { + if (AccessMode != KernelMode) + { + __try + { + *NumberOfBytesRead = numberOfBytesRead; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + // Don't mess with the status. + NOTHING; + } + } + else + { + *NumberOfBytesRead = numberOfBytesRead; + } + } + + return status; +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..201de86 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,685 @@ +Process Hacker is distributed under the GNU GPL version 3, with the +following exception: + + Permission is granted to dynamically (but not statically) link this + program with independent modules, regardless of the license terms of + these independent modules, provided that this program is not modified + in any way. An independent module is a module which is not derived + from or based on this program. If you modify this program, this + additional permission no longer applies unless authorized by the + copyright holders. + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ProcessHacker.sln b/ProcessHacker.sln new file mode 100644 index 0000000..61ff399 --- /dev/null +++ b/ProcessHacker.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{2758DC86-368B-430C-9D29-F1EF20032A71}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProcessHacker", "ProcessHacker\ProcessHacker.vcxproj", "{0271DD27-6707-4290-8DFE-285702B7115D}" + ProjectSection(ProjectDependencies) = postProject + {477D0215-F252-41A1-874B-F27E3EA1ED17} = {477D0215-F252-41A1-874B-F27E3EA1ED17} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "phlib", "phlib\phlib.vcxproj", "{477D0215-F252-41A1-874B-F27E3EA1ED17}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixlib", "tools\fixlib\fixlib.vcxproj", "{31F4AA06-7ED5-4A6D-B901-19AD4BD16175}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "peview", "tools\peview\peview.vcxproj", "{72C124A2-3C80-41C6-ABA1-C4948B713204}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FD3C278D-BD40-4551-AE67-4DE196F8D7F6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "phlib-test", "tests\phlib-test\phlib-test.vcxproj", "{0C21014E-BC90-4AE5-AA32-398445C13B28}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomSignTool", "tools\CustomSignTool\CustomSignTool.vcxproj", "{E8CD0A41-1537-4EA6-98AC-E80CD59C478E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0271DD27-6707-4290-8DFE-285702B7115D}.Debug|Win32.ActiveCfg = Debug|Win32 + {0271DD27-6707-4290-8DFE-285702B7115D}.Debug|Win32.Build.0 = Debug|Win32 + {0271DD27-6707-4290-8DFE-285702B7115D}.Debug|x64.ActiveCfg = Debug|x64 + {0271DD27-6707-4290-8DFE-285702B7115D}.Debug|x64.Build.0 = Debug|x64 + {0271DD27-6707-4290-8DFE-285702B7115D}.Release|Win32.ActiveCfg = Release|Win32 + {0271DD27-6707-4290-8DFE-285702B7115D}.Release|Win32.Build.0 = Release|Win32 + {0271DD27-6707-4290-8DFE-285702B7115D}.Release|x64.ActiveCfg = Release|x64 + {0271DD27-6707-4290-8DFE-285702B7115D}.Release|x64.Build.0 = Release|x64 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Debug|Win32.ActiveCfg = Debug|Win32 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Debug|Win32.Build.0 = Debug|Win32 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Debug|x64.ActiveCfg = Debug|x64 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Debug|x64.Build.0 = Debug|x64 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Release|Win32.ActiveCfg = Release|Win32 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Release|Win32.Build.0 = Release|Win32 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Release|x64.ActiveCfg = Release|x64 + {477D0215-F252-41A1-874B-F27E3EA1ED17}.Release|x64.Build.0 = Release|x64 + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175}.Debug|Win32.ActiveCfg = Debug|Win32 + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175}.Debug|x64.ActiveCfg = Debug|Win32 + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175}.Release|Win32.ActiveCfg = Release|Win32 + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175}.Release|x64.ActiveCfg = Release|Win32 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Debug|Win32.ActiveCfg = Debug|Win32 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Debug|Win32.Build.0 = Debug|Win32 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Debug|x64.ActiveCfg = Debug|x64 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Debug|x64.Build.0 = Debug|x64 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Release|Win32.ActiveCfg = Release|Win32 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Release|Win32.Build.0 = Release|Win32 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Release|x64.ActiveCfg = Release|x64 + {72C124A2-3C80-41C6-ABA1-C4948B713204}.Release|x64.Build.0 = Release|x64 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Debug|Win32.Build.0 = Debug|Win32 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Debug|x64.ActiveCfg = Debug|Win32 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Release|Win32.ActiveCfg = Release|Win32 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Release|Win32.Build.0 = Release|Win32 + {0C21014E-BC90-4AE5-AA32-398445C13B28}.Release|x64.ActiveCfg = Release|Win32 + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E}.Debug|Win32.ActiveCfg = Debug|Win32 + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E}.Debug|x64.ActiveCfg = Debug|x64 + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E}.Release|Win32.ActiveCfg = Release|Win32 + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E}.Release|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175} = {2758DC86-368B-430C-9D29-F1EF20032A71} + {72C124A2-3C80-41C6-ABA1-C4948B713204} = {2758DC86-368B-430C-9D29-F1EF20032A71} + {0C21014E-BC90-4AE5-AA32-398445C13B28} = {FD3C278D-BD40-4551-AE67-4DE196F8D7F6} + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E} = {2758DC86-368B-430C-9D29-F1EF20032A71} + EndGlobalSection +EndGlobal diff --git a/ProcessHacker/ProcessHacker.def b/ProcessHacker/ProcessHacker.def new file mode 100644 index 0000000..4df30e9 --- /dev/null +++ b/ProcessHacker/ProcessHacker.def @@ -0,0 +1,569 @@ +EXPORTS + +; +; phlib exports +; + +; ref + PhAutoDereferenceObject + PhCreateAlloc + PhCreateObject + PhCreateObjectType + PhCreateObjectTypeEx + PhDeleteAutoPool + PhDereferenceObject + PhDereferenceObjectDeferDelete + PhDereferenceObjectEx + PhDrainAutoPool + PhGetObjectType + PhGetObjectTypeInformation + PhInitializeAutoPool + PhReferenceObject + PhReferenceObjectEx + PhReferenceObjectSafe + +; queuedlock + PhfAcquireQueuedLockExclusive + PhfAcquireQueuedLockShared + PhfPulseAllCondition + PhfPulseCondition + PhfQueueWakeEvent + PhfReleaseQueuedLockExclusive + PhfReleaseQueuedLockShared + PhfSetWakeEvent + PhfWaitForCondition + PhfWaitForConditionEx + PhfWaitForWakeEvent + PhfWakeForReleaseQueuedLock + +; phconfig + PhGlobalDpi DATA + PhHeapHandle DATA + PhIsExecutingInWow64 + PhLibImageBase DATA + PhOsVersion DATA + PhSystemBasicInformation DATA + ProcessAllAccess DATA + ProcessQueryAccess DATA + ThreadAllAccess DATA + ThreadQueryAccess DATA + ThreadSetAccess DATA + WindowsVersion DATA + +; phbasesup + PhAddElementAvlTree + PhAddEntryHashtable + PhAddEntryHashtableEx + PhAddItemArray + PhAddItemList + PhAddItemPointerList + PhAddItemsArray + PhAddItemSimpleHashtable + PhAddItemsList + PhAllocate + PhAllocateExSafe + PhAllocateFromFreeList + PhAllocatePage + PhAllocateSafe + PhAppendBytesBuilder + PhAppendBytesBuilder2 + PhAppendBytesBuilderEx + PhAppendCharStringBuilder + PhAppendCharStringBuilder2 + PhAppendFormatStringBuilder + PhAppendFormatStringBuilder_V + PhAppendStringBuilder + PhAppendStringBuilder2 + PhAppendStringBuilderEx + PhBufferToHexString + PhBufferToHexStringEx + PhClearArray + PhClearHashtable + PhClearList + PhCompareStringRef + PhCompareStringZNatural + PhConcatStringRef2 + PhConcatStringRef3 + PhConcatStrings + PhConcatStrings_V + PhConcatStrings2 + PhConvertMultiByteToUtf16 + PhConvertMultiByteToUtf16Ex + PhConvertUtf16ToAsciiEx + PhConvertUtf16ToMultiByte + PhConvertUtf16ToMultiByteEx + PhConvertUtf16ToUtf8 + PhConvertUtf16ToUtf8Buffer + PhConvertUtf16ToUtf8Ex + PhConvertUtf16ToUtf8Size + PhConvertUtf8ToUtf16 + PhConvertUtf8ToUtf16Buffer + PhConvertUtf8ToUtf16Ex + PhConvertUtf8ToUtf16Size + PhCopyBytesZ + PhCopyStringZ + PhCopyStringZFromBytes + PhCopyStringZFromMultiByte + PhCountStringZ + PhCreateBytes + PhCreateBytesEx + PhCreateHashtable + PhCreateList + PhCreatePointerList + PhCreateSimpleHashtable + PhCreateString + PhCreateStringEx + PhCreateThread + PhDecodeUnicodeDecoder + PhDeleteArray + PhDeleteBytesBuilder + PhDeleteCallback + PhDeleteFreeList + PhDeleteStringBuilder + PhDivideSinglesBySingle + PhDosErrorToNtStatus + PhDuplicateBytesZ + PhDuplicateBytesZSafe + PhDuplicateStringZ + PhEncodeUnicode + PhEnumAvlTree + PhEnumHashtable + PhEnumPointerListEx + PhEqualStringRef + PhExponentiate + PhExponentiate64 + PhfAcquireRundownProtection + PhfBeginInitOnce + PhfEndInitOnce + PhFillMemoryUlong + PhFinalArrayItems + PhFinalBytesBuilderBytes + PhFinalStringBuilderString + PhFindCharInStringRef + PhFindElementAvlTree + PhFindEntryHashtable + PhFindItemList + PhFindItemPointerList + PhFindItemSimpleHashtable + PhFindLastCharInStringRef + PhFindStringInStringRef + PhfInitializeBarrier + PhfInitializeEvent + PhfInitializeInitOnce + PhfInitializeRundownProtection + PhFormat + PhFormatString + PhFormatString_V + PhFormatToBuffer + PhFree + PhFreePage + PhFreeToFreeList + PhfReleaseRundownProtection + PhfResetEvent + PhfSetEvent + PhfWaitForBarrier + PhfWaitForEvent + PhfWaitForRundownProtection + PhGetPrimeNumber + PhHashBytes + PhHashStringRef + PhHexStringToBuffer + PhInitializeArray + PhInitializeAvlTree + PhInitializeBytesBuilder + PhInitializeCallback + PhInitializeFreeList + PhInitializeStringBuilder + PhInsertItemList + PhInsertItemsList + PhInsertStringBuilder + PhInsertStringBuilder2 + PhInsertStringBuilderEx + PhIntegerToString64 + PhInvokeCallback + PhLocalTimeToSystemTime + PhLowerBoundElementAvlTree + PhLowerDualBoundElementAvlTree + PhMaximumElementAvlTree + PhMinimumElementAvlTree + PhNtStatusFileNotFound + PhNtStatusToDosError + PhPredecessorElementAvlTree + PhPrintTimeSpan + PhQuerySystemTime + PhQueryTimeZoneBias + PhReAllocate + PhReAllocateSafe + PhReferenceEmptyString + PhRegisterCallback + PhRegisterCallbackEx + PhRemoveElementAvlTree + PhRemoveEntryHashtable + PhRemoveItemArray + PhRemoveItemList + PhRemoveItemPointerList + PhRemoveItemsArray + PhRemoveItemSimpleHashtable + PhRemoveItemsList + PhRemoveStringBuilder + PhResizeArray + PhResizeList + PhRoundUpToPowerOfTwo + PhSplitStringRefAtChar + PhSplitStringRefAtLastChar + PhSplitStringRefAtString + PhSplitStringRefEx + PhStringToDouble + PhStringToInteger64 + PhSuccessorElementAvlTree + PhSystemTimeToLocalTime + PhTrimStringRef + PhUnregisterCallback + PhUpperBoundElementAvlTree + PhUpperDualBoundElementAvlTree + PhWriteUnicodeDecoder + PhZeroExtendToUtf16Buffer + PhZeroExtendToUtf16Ex + +; phnative + PhCreateFileWin32 + PhCreateFileWin32Ex + PhCreateKey + PhDeleteFileWin32 + PhDisconnectNamedPipe + PhEnumDirectoryFile + PhEnumDirectoryObjects + PhEnumFileStreams + PhEnumGenericModules + PhEnumHandles + PhEnumHandlesEx + PhEnumKernelModules + PhEnumPagefiles + PhEnumProcessEnvironmentVariables + PhEnumProcesses + PhEnumProcessesEx + PhEnumProcessesForSession + PhEnumProcessModules + PhEnumProcessModules32 + PhEnumProcessModules32Ex + PhEnumProcessModulesEx + PhFindProcessInformation + PhFindProcessInformationByImageName + PhGetFileName + PhGetFileSize + PhGetJobProcessIdList + PhGetKernelFileName + PhGetObjectSecurity + PhGetOwnTokenAttributes + PhGetProcedureAddressRemote + PhGetProcessCommandLine + PhGetProcessDepStatus + PhGetProcessEnvironment + PhGetProcessImageFileName + PhGetProcessImageFileNameByProcessId + PhGetProcessImageFileNameWin32 + PhGetProcessIsDotNet + PhGetProcessIsDotNetEx + PhGetProcessMappedFileName + PhGetProcessPebString + PhGetProcessWindowTitle + PhGetProcessWorkingSetInformation + PhGetProcessWsCounters + PhGetTokenGroups + PhGetTokenIntegrityLevel + PhGetTokenOwner + PhGetTokenPrimaryGroup + PhGetTokenPrivileges + PhGetTokenUser + PhImpersonateClientOfNamedPipe + PhInjectDllProcess + PhListenNamedPipe + PhOpenKey + PhOpenProcess = PhOpenProcessPublic + PhOpenThread = PhOpenThreadPublic + PhOpenThreadProcess + PhPeekNamedPipe + PhQueryFullAttributesFileWin32 + PhQueryKey + PhQueryValueKey + PhResolveDevicePrefix + PhSetFileSize + PhSetObjectSecurity + PhSetTokenIsVirtualizationEnabled + PhSetTokenPrivilege + PhSetTokenPrivilege2 + PhSetTokenSessionId + PhTerminateProcess = PhTerminateProcessPublic + PhTransceiveNamedPipe + PhUnloadDllProcess + PhUnloadDriver + PhUpdateDosDevicePrefixes + PhUpdateMupDevicePrefixes + PhWaitForNamedPipe + +; phutil + PhAdjustRectangleToBounds + PhAdjustRectangleToWorkingArea + PhCenterRectangle + PhCenterWindow + PhCompareUnicodeStringZIgnoreMenuPrefix + PhCreateOpenFileDialog + PhCreateProcess + PhCreateProcessAsUser + PhCreateProcessWin32 + PhCreateProcessWin32Ex + PhCreateSaveFileDialog + PhDeleteImageVersionInfo + PhDereferenceObjects + PhEllipsisString + PhEllipsisStringPath + PhEscapeCommandLinePart + PhEscapeStringForMenuPrefix + PhExpandEnvironmentStrings + PhFinalHash + PhFindIntegerSiKeyValuePairs + PhFindLoaderEntry + PhFindStringSiKeyValuePairs + PhFormatDate + PhFormatDateTime + PhFormatDecimal + PhFormatGuid + PhFormatImageVersionInfo + PhFormatSize + PhFormatTime + PhFormatTimeSpan + PhFormatTimeSpanRelative + PhFormatUInt64 + PhFreeFileDialog + PhGenerateGuid + PhGenerateGuidFromName + PhGenerateRandomAlphaString + PhGetApplicationDirectory + PhGetApplicationFileName + PhGetBaseName + PhGetDllFileName + PhGetFileDialogFileName + PhGetFileDialogFilterIndex + PhGetFileDialogOptions + PhGetFileVersionInfo + PhGetFileVersionInfoLangCodePage + PhGetFileVersionInfoString + PhGetFileVersionInfoString2 + PhGetFullPath + PhGetKnownLocation + PhGetMessage + PhGetNtMessage + PhGetSystemDirectory + PhGetSystemRoot + PhGetWin32Message + PhInitializeHash + PhInitializeImageVersionInfo + PhIsExecutablePacked + PhMapFlags1 + PhMapFlags2 + PhMatchWildcards + PhParseCommandLine + PhParseCommandLineFuzzy + PhParseCommandLinePart + PhQueryRegistryString + PhReferenceObjects + PhSetFileDialogFileName + PhSetFileDialogFilter + PhSetFileDialogOptions + PhShellExecute + PhShellExecuteEx + PhShellExploreFile + PhShellOpenKey + PhShellProperties + PhShowConfirmMessage + PhShowContinueStatus + PhShowFileDialog + PhShowMessage + PhShowMessage_V + PhShowStatus + PhUpdateHash + +; circbuf + PhClearCircularBuffer_FLOAT + PhClearCircularBuffer_PVOID + PhClearCircularBuffer_ULONG + PhClearCircularBuffer_ULONG64 + PhCopyCircularBuffer_FLOAT + PhCopyCircularBuffer_PVOID + PhCopyCircularBuffer_ULONG + PhCopyCircularBuffer_ULONG64 + PhDeleteCircularBuffer_FLOAT + PhDeleteCircularBuffer_PVOID + PhDeleteCircularBuffer_ULONG + PhDeleteCircularBuffer_ULONG64 + PhInitializeCircularBuffer_FLOAT + PhInitializeCircularBuffer_PVOID + PhInitializeCircularBuffer_ULONG + PhInitializeCircularBuffer_ULONG64 + PhResizeCircularBuffer_FLOAT + PhResizeCircularBuffer_PVOID + PhResizeCircularBuffer_ULONG + PhResizeCircularBuffer_ULONG64 + +; cpysave + PhGetGenericTreeNewLines + PhGetTreeNewText + +; emenu + PhCreateEMenu + PhCreateEMenuItem + PhDestroyEMenu + PhDestroyEMenuItem + PhFindEMenuItem + PhIndexOfEMenuItem + PhInsertEMenuItem + PhLoadResourceEMenuItem + PhModifyEMenuItem + PhRemoveAllEMenuItems + PhRemoveEMenuItem + PhSetFlagsAllEMenuItems + PhSetFlagsEMenuItem + PhShowEMenu + +; fastlock + PhDeleteFastLock + PhfAcquireFastLockExclusive + PhfAcquireFastLockShared + PhfReleaseFastLockExclusive + PhfReleaseFastLockShared + PhfTryAcquireFastLockExclusive + PhfTryAcquireFastLockShared + PhInitializeFastLock + +; filestream + PhCreateFileStream + PhCreateFileStream2 + PhFlushFileStream + PhGetPositionFileStream + PhLockFileStream + PhReadFileStream + PhSeekFileStream + PhUnlockFileStream + PhVerifyFileStream + PhWriteFileStream + PhWriteStringAsUtf8FileStream + PhWriteStringAsUtf8FileStream2 + PhWriteStringAsUtf8FileStreamEx + PhWriteStringFormatAsUtf8FileStream + PhWriteStringFormatAsUtf8FileStream_V + +; graph + PhDeleteGraphState + PhDrawGraphDirect + PhGetDrawInfoGraphBuffers + PhGraphStateGetDrawInfo + PhInitializeGraphState + PhSetGraphText + +; guisup + PhAddComboBoxStrings + PhAddLayoutItem + PhAddLayoutItemEx + PhAddListViewColumn + PhAddListViewItem + PhAddTabControlTab + PhDeleteLayoutManager + PhFindListViewItemByFlags + PhFindListViewItemByParam + PhGetComboBoxString + PhGetFileShellIcon + PhGetListBoxString + PhGetListViewItemImageIndex + PhGetListViewItemParam + PhGetSelectedListViewItemParam + PhGetSelectedListViewItemParams + PhGetStockApplicationIcon + PhGetWindowText + PhGetWindowTextEx + PhIconToBitmap + PhInitializeLayoutManager + PhLayoutManagerLayout + PhLoadIcon + PhLoadListViewColumnSettings + PhModalPropertySheet + PhRemoveListViewItem + PhSaveListViewColumnSettings + PhSelectComboBoxString + PhSetClipboardString + PhSetControlTheme + PhSetExtendedListView + PhSetHeaderSortIcon + PhSetImageListBitmap + PhSetListViewItemImageIndex + PhSetListViewSubItem + PhSetStateAllListViewItems + +; hndlinfo + PhEnumObjectTypes + PhFormatNativeKeyName + PhGetHandleInformation + PhGetHandleInformationEx + PhStdGetClientIdName + +; lsasup + PhGetSidFullName + PhLookupName + PhLookupPrivilegeDisplayName + PhLookupPrivilegeName + PhLookupPrivilegeValue + PhLookupSid + PhOpenLsaPolicy + PhSidToStringSid + +; secedit + PhCreateSecurityPage + PhEditSecurity + PhGetAccessEntries + PhGetAccessString + PhGetSeObjectSecurity + PhSetSeObjectSecurity + PhStdGetObjectSecurity + PhStdSetObjectSecurity + +; svcsup + PhEnumServices + PhGetServiceConfig + PhGetServiceDelayedAutoStart + PhGetServiceDescription + PhGetServiceErrorControlInteger + PhGetServiceErrorControlString + PhGetServiceNameFromTag + PhGetServiceStartTypeInteger + PhGetServiceStartTypeString + PhGetServiceStateString + PhGetServiceTypeInteger + PhGetServiceTypeString + PhGetThreadServiceTag + PhOpenService + PhQueryServiceVariableSize + PhSetServiceDelayedAutoStart + +; symprv + PhCreateSymbolProvider + PhGetLineFromAddress + PhGetModuleFromAddress + PhGetSymbolFromAddress + PhGetSymbolFromName + PhLoadModuleSymbolProvider + PhSetOptionsSymbolProvider + PhSetSearchPathSymbolProvider + PhStackWalk + PhWalkThreadStack + PhWriteMiniDumpProcess + +; verify + PhVerifyFile + +; workqueue + PhDeleteWorkQueue + PhGetGlobalWorkQueue + PhInitializeWorkQueue + PhInitializeWorkQueueEnvironment + PhQueueItemWorkQueue + PhQueueItemWorkQueueEx + PhWaitForWorkQueue diff --git a/ProcessHacker/ProcessHacker.ico b/ProcessHacker/ProcessHacker.ico new file mode 100644 index 0000000..5225637 Binary files /dev/null and b/ProcessHacker/ProcessHacker.ico differ diff --git a/ProcessHacker/ProcessHacker.manifest b/ProcessHacker/ProcessHacker.manifest new file mode 100644 index 0000000..0372058 --- /dev/null +++ b/ProcessHacker/ProcessHacker.manifest @@ -0,0 +1,46 @@ + + + + Process Hacker + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + \ No newline at end of file diff --git a/ProcessHacker/ProcessHacker.rc b/ProcessHacker/ProcessHacker.rc new file mode 100644 index 0000000..801ab1e --- /dev/null +++ b/ProcessHacker/ProcessHacker.rc @@ -0,0 +1,2606 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +#include "include/phappres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "#include ""include/phappres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_PROCESSHACKER ICON "ProcessHacker.ico" + +IDI_PHAPPLICATION ICON "resources\\application.ico" + +IDI_COG ICON "resources\\cog.ico" + +IDI_PHAPPLICATIONGO ICON "resources\\application_go.ico" + +IDI_COGGO ICON "resources\\cog_go.ico" + +IDI_PIN ICON "resources\\pin.ico" + +IDI_FOLDER ICON "resources\\folder.ico" + +IDI_PENCIL ICON "resources\\pencil.ico" + +IDI_MAGNIFIER ICON "resources\\magnifier.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MAINWND MENU +BEGIN + POPUP "&Hacker" + BEGIN + MENUITEM "&Run...\aCtrl+R", ID_HACKER_RUN + MENUITEM "Run as administrator...", ID_HACKER_RUNASADMINISTRATOR + MENUITEM "Run as &limited user...", ID_HACKER_RUNASLIMITEDUSER + MENUITEM "Run &as...\aCtrl+Shift+R", ID_HACKER_RUNAS + MENUITEM "Show &details for all processes", ID_HACKER_SHOWDETAILSFORALLPROCESSES + MENUITEM SEPARATOR + MENUITEM "&Save...\aCtrl+S", ID_HACKER_SAVE + MENUITEM "&Find handles or DLLs...\aCtrl+F", ID_HACKER_FINDHANDLESORDLLS + MENUITEM "&Options...", ID_HACKER_OPTIONS + MENUITEM "&Plugins...", ID_HACKER_PLUGINS + MENUITEM SEPARATOR + POPUP "&Computer" + BEGIN + MENUITEM "&Lock", ID_COMPUTER_LOCK + MENUITEM "Log o&ff", ID_COMPUTER_LOGOFF + MENUITEM SEPARATOR + MENUITEM "&Sleep", ID_COMPUTER_SLEEP + MENUITEM "&Hibernate", ID_COMPUTER_HIBERNATE + MENUITEM SEPARATOR + MENUITEM "R&estart", ID_COMPUTER_RESTART + MENUITEM "Restart to boot options", ID_COMPUTER_RESTARTBOOTOPTIONS + MENUITEM "Shu&t down", ID_COMPUTER_SHUTDOWN + MENUITEM "H&ybrid shut down", ID_COMPUTER_SHUTDOWNHYBRID + END + MENUITEM "E&xit", ID_HACKER_EXIT + END + POPUP "&View" + BEGIN + MENUITEM "System &information\aCtrl+I", ID_VIEW_SYSTEMINFORMATION + POPUP "&Tray icons" + BEGIN + MENUITEM "CPU history", ID_TRAYICONS_CPUHISTORY + MENUITEM "CPU usage", ID_TRAYICONS_CPUUSAGE + MENUITEM "I/O history", ID_TRAYICONS_IOHISTORY + MENUITEM "Commit charge history", ID_TRAYICONS_COMMITHISTORY + MENUITEM "Physical memory history", ID_TRAYICONS_PHYSICALMEMORYHISTORY + END + MENUITEM SEPARATOR + MENUITEM "
", ID_VIEW_SECTIONPLACEHOLDER + MENUITEM SEPARATOR + MENUITEM "&Always on top", ID_VIEW_ALWAYSONTOP + POPUP "&Opacity" + BEGIN + MENUITEM "10%", ID_OPACITY_10 + MENUITEM "20%", ID_OPACITY_20 + MENUITEM "30%", ID_OPACITY_30 + MENUITEM "40%", ID_OPACITY_40 + MENUITEM "50%", ID_OPACITY_50 + MENUITEM "60%", ID_OPACITY_60 + MENUITEM "70%", ID_OPACITY_70 + MENUITEM "80%", ID_OPACITY_80 + MENUITEM "90%", ID_OPACITY_90 + MENUITEM "Opaque", ID_OPACITY_OPAQUE + END + MENUITEM SEPARATOR + MENUITEM "&Refresh\aF5", ID_VIEW_REFRESH + POPUP "Refresh i&nterval" + BEGIN + MENUITEM "Fast (0.5s)", ID_UPDATEINTERVAL_FAST + MENUITEM "Normal (1s)", ID_UPDATEINTERVAL_NORMAL + MENUITEM "Below normal (2s)", ID_UPDATEINTERVAL_BELOWNORMAL + MENUITEM "Slow (5s)", ID_UPDATEINTERVAL_SLOW + MENUITEM "Very slow (10s)", ID_UPDATEINTERVAL_VERYSLOW + END + MENUITEM "Refresh a&utomatically\aF6", ID_VIEW_UPDATEAUTOMATICALLY + END + POPUP "&Tools" + BEGIN + MENUITEM "Create service...", ID_TOOLS_CREATESERVICE + MENUITEM "Hidden processes", ID_TOOLS_HIDDENPROCESSES + MENUITEM "Inspect executable file...", ID_TOOLS_INSPECTEXECUTABLEFILE + MENUITEM "Pagefiles", ID_TOOLS_PAGEFILES + MENUITEM "Start Task Manager", ID_TOOLS_STARTTASKMANAGER + END + POPUP "&Users" + BEGIN + MENUITEM "Dummy", ID_USERS_DUMMY + END + POPUP "H&elp" + BEGIN + MENUITEM "&Log\aCtrl+L", ID_HELP_LOG + MENUITEM "&Donate", ID_HELP_DONATE + MENUITEM "Debu&g console", ID_HELP_DEBUGCONSOLE + MENUITEM "&About", ID_HELP_ABOUT + END +END + +IDR_THREAD MENU +BEGIN + POPUP "Thread" + BEGIN + MENUITEM "&Inspect\aEnter", ID_THREAD_INSPECT + MENUITEM "T&erminate\aDel", ID_THREAD_TERMINATE + MENUITEM "&Suspend", ID_THREAD_SUSPEND + MENUITEM "Res&ume", ID_THREAD_RESUME + MENUITEM SEPARATOR + MENUITEM "&Affinity", ID_THREAD_AFFINITY + MENUITEM "Per&missions", ID_THREAD_PERMISSIONS + MENUITEM "&Token", ID_THREAD_TOKEN + POPUP "Analy&ze" + BEGIN + MENUITEM "Wait", ID_ANALYZE_WAIT + END + POPUP "&Priority" + BEGIN + MENUITEM "Time critical", ID_PRIORITY_TIMECRITICAL + MENUITEM "Highest", ID_PRIORITY_HIGHEST + MENUITEM "Above normal", ID_PRIORITY_ABOVENORMAL + MENUITEM "Normal", ID_PRIORITY_NORMAL + MENUITEM "Below normal", ID_PRIORITY_BELOWNORMAL + MENUITEM "Lowest", ID_PRIORITY_LOWEST + MENUITEM "Idle", ID_PRIORITY_IDLE + END + POPUP "I/O priority" + BEGIN + MENUITEM "High", ID_IOPRIORITY_HIGH + MENUITEM "Normal", ID_IOPRIORITY_NORMAL + MENUITEM "Low", ID_IOPRIORITY_LOW + MENUITEM "Very low", ID_IOPRIORITY_VERYLOW + END + POPUP "Page priority" + BEGIN + MENUITEM "Normal", ID_PAGEPRIORITY_NORMAL + MENUITEM "Below normal", ID_PAGEPRIORITY_BELOWNORMAL + MENUITEM "Medium", ID_PAGEPRIORITY_MEDIUM + MENUITEM "Low", ID_PAGEPRIORITY_LOW + MENUITEM "Very low", ID_PAGEPRIORITY_VERYLOW + END + MENUITEM SEPARATOR + MENUITEM "&Copy\aCtrl+C", ID_THREAD_COPY + END +END + +IDR_HANDLE MENU +BEGIN + POPUP "Handle" + BEGIN + MENUITEM "C&lose\aDel", ID_HANDLE_CLOSE + MENUITEM "&Protected", ID_HANDLE_PROTECTED + MENUITEM "&Inherit", ID_HANDLE_INHERIT + MENUITEM SEPARATOR + MENUITEM "&Copy\aCtrl+C", ID_HANDLE_COPY + MENUITEM "Prope&rties\aEnter", ID_HANDLE_PROPERTIES + END +END + +IDR_MODULE MENU +BEGIN + POPUP "Module" + BEGIN + MENUITEM "&Unload\aDel", ID_MODULE_UNLOAD + MENUITEM SEPARATOR + MENUITEM "&Inspect\aEnter", ID_MODULE_INSPECT + MENUITEM "&Search online\aCtrl+M", ID_MODULE_SEARCHONLINE + MENUITEM "Open &file location\aCtrl+Enter", ID_MODULE_OPENFILELOCATION + MENUITEM "P&roperties", ID_MODULE_PROPERTIES + MENUITEM "&Copy\aCtrl+C", ID_MODULE_COPY + END +END + +IDR_COMPUTER MENU +BEGIN + POPUP "Computer" + BEGIN + MENUITEM "&Lock", ID_COMPUTER_LOCK + MENUITEM "Log o&ff", ID_COMPUTER_LOGOFF + MENUITEM SEPARATOR + MENUITEM "&Sleep", ID_COMPUTER_SLEEP + MENUITEM "&Hibernate", ID_COMPUTER_HIBERNATE + MENUITEM SEPARATOR + MENUITEM "R&estart", ID_COMPUTER_RESTART + MENUITEM "Restart to boot &options", ID_COMPUTER_RESTARTBOOTOPTIONS + MENUITEM "Shu&t down", ID_COMPUTER_SHUTDOWN + MENUITEM "H&ybrid shut down", ID_COMPUTER_SHUTDOWNHYBRID + END +END + +IDR_PROCESS MENU +BEGIN + POPUP "Process" + BEGIN + MENUITEM "T&erminate\aDel", ID_PROCESS_TERMINATE + MENUITEM "Terminate tree\aShift+Del", ID_PROCESS_TERMINATETREE + MENUITEM "&Suspend", ID_PROCESS_SUSPEND + MENUITEM "Res&ume", ID_PROCESS_RESUME + MENUITEM "Res&tart", ID_PROCESS_RESTART + MENUITEM SEPARATOR + MENUITEM "Create dump file...", ID_PROCESS_CREATEDUMPFILE + MENUITEM "Debug", ID_PROCESS_DEBUG + MENUITEM "Virtualization", ID_PROCESS_VIRTUALIZATION + MENUITEM SEPARATOR + MENUITEM "&Affinity", ID_PROCESS_AFFINITY + POPUP "&Priority" + BEGIN + MENUITEM "Real time", ID_PRIORITY_REALTIME + MENUITEM "High", ID_PRIORITY_HIGH + MENUITEM "Above normal", ID_PRIORITY_ABOVENORMAL + MENUITEM "Normal", ID_PRIORITY_NORMAL + MENUITEM "Below normal", ID_PRIORITY_BELOWNORMAL + MENUITEM "Idle", ID_PRIORITY_IDLE + END + POPUP "&I/O priority" + BEGIN + MENUITEM "High", ID_IOPRIORITY_HIGH + MENUITEM "Normal", ID_IOPRIORITY_NORMAL + MENUITEM "Low", ID_IOPRIORITY_LOW + MENUITEM "Very low", ID_IOPRIORITY_VERYLOW + END + POPUP "&Miscellaneous" + BEGIN + MENUITEM "Detach from debugger", ID_MISCELLANEOUS_DETACHFROMDEBUGGER + MENUITEM "GDI handles", ID_MISCELLANEOUS_GDIHANDLES + MENUITEM "Inject DLL...", ID_MISCELLANEOUS_INJECTDLL + POPUP "Page priority" + BEGIN + MENUITEM "Normal", ID_PAGEPRIORITY_NORMAL + MENUITEM "Below normal", ID_PAGEPRIORITY_BELOWNORMAL + MENUITEM "Medium", ID_PAGEPRIORITY_MEDIUM + MENUITEM "Low", ID_PAGEPRIORITY_LOW + MENUITEM "Very low", ID_PAGEPRIORITY_VERYLOW + END + MENUITEM "Reduce working set", ID_MISCELLANEOUS_REDUCEWORKINGSET + MENUITEM "Run as...", ID_MISCELLANEOUS_RUNAS + MENUITEM "Run as this user...", ID_MISCELLANEOUS_RUNASTHISUSER + END + POPUP "&Window" + BEGIN + MENUITEM "Bring to front", ID_WINDOW_BRINGTOFRONT + MENUITEM "Restore", ID_WINDOW_RESTORE + MENUITEM "Minimize", ID_WINDOW_MINIMIZE + MENUITEM "Maximize", ID_WINDOW_MAXIMIZE + MENUITEM SEPARATOR + MENUITEM "Close", ID_WINDOW_CLOSE + END + MENUITEM SEPARATOR + MENUITEM "Search online\aCtrl+M", ID_PROCESS_SEARCHONLINE + MENUITEM "Open &file location\aCtrl+Enter", ID_PROCESS_OPENFILELOCATION + MENUITEM "P&roperties\aEnter", ID_PROCESS_PROPERTIES + MENUITEM "&Copy\aCtrl+C", ID_PROCESS_COPY + END +END + +IDR_SERVICE MENU +BEGIN + POPUP "Service" + BEGIN + MENUITEM "&Go to process", ID_SERVICE_GOTOPROCESS + MENUITEM "&Start", ID_SERVICE_START + MENUITEM "C&ontinue", ID_SERVICE_CONTINUE + MENUITEM "&Pause", ID_SERVICE_PAUSE + MENUITEM "S&top", ID_SERVICE_STOP + MENUITEM "&Delete\aDel", ID_SERVICE_DELETE + MENUITEM SEPARATOR + MENUITEM "Open &key", ID_SERVICE_OPENKEY + MENUITEM "Open &file location\aCtrl+Enter", ID_SERVICE_OPENFILELOCATION + MENUITEM "P&roperties\aEnter", ID_SERVICE_PROPERTIES + MENUITEM "&Copy\aCtrl+C", ID_SERVICE_COPY + END +END + +IDR_PRIVILEGE MENU +BEGIN + POPUP "Privilege" + BEGIN + MENUITEM "&Enable", ID_PRIVILEGE_ENABLE + MENUITEM "&Disable", ID_PRIVILEGE_DISABLE + MENUITEM "&Remove", ID_PRIVILEGE_REMOVE + MENUITEM SEPARATOR + MENUITEM "&Copy\aCtrl+C", ID_PRIVILEGE_COPY + END +END + +IDR_FINDOBJ MENU +BEGIN + POPUP "Object" + BEGIN + MENUITEM "C&lose\aDel", ID_OBJECT_CLOSE + MENUITEM SEPARATOR + MENUITEM "Go to owning &process", ID_OBJECT_GOTOOWNINGPROCESS + MENUITEM "Prope&rties", ID_OBJECT_PROPERTIES + MENUITEM "&Copy\aCtrl+C", ID_OBJECT_COPY + END +END + +IDR_USER MENU +BEGIN + POPUP "User" + BEGIN + MENUITEM "&Connect", ID_USER_CONNECT + MENUITEM "&Disconnect", ID_USER_DISCONNECT + MENUITEM "&Logoff", ID_USER_LOGOFF + MENUITEM "Rem&ote control", ID_USER_REMOTECONTROL + MENUITEM "Send &message...", ID_USER_SENDMESSAGE + MENUITEM "P&roperties", ID_USER_PROPERTIES + END +END + +IDR_NETWORK MENU +BEGIN + POPUP "Network" + BEGIN + MENUITEM "&Go to process\aEnter", ID_NETWORK_GOTOPROCESS + MENUITEM "Go to service", ID_NETWORK_GOTOSERVICE + MENUITEM "View &stack", ID_NETWORK_VIEWSTACK + MENUITEM "C&lose", ID_NETWORK_CLOSE + MENUITEM SEPARATOR + MENUITEM "&Copy\aCtrl+C", ID_NETWORK_COPY + END +END + +IDR_ICON MENU +BEGIN + POPUP "Icon" + BEGIN + MENUITEM "&Show/Hide Process Hacker", ID_ICON_SHOWHIDEPROCESSHACKER + MENUITEM "System &information", ID_ICON_SYSTEMINFORMATION + POPUP "N&otifications" + BEGIN + MENUITEM "Enable all", ID_NOTIFICATIONS_ENABLEALL + MENUITEM "Disable all", ID_NOTIFICATIONS_DISABLEALL + MENUITEM SEPARATOR + MENUITEM "New processes", ID_NOTIFICATIONS_NEWPROCESSES + MENUITEM "Terminated processes", ID_NOTIFICATIONS_TERMINATEDPROCESSES + MENUITEM "New services", ID_NOTIFICATIONS_NEWSERVICES + MENUITEM "Started services", ID_NOTIFICATIONS_STARTEDSERVICES + MENUITEM "Stopped services", ID_NOTIFICATIONS_STOPPEDSERVICES + MENUITEM "Deleted services", ID_NOTIFICATIONS_DELETEDSERVICES + END + POPUP "&Processes" + BEGIN + MENUITEM "Dummy", ID_PROCESSES_DUMMY + END + MENUITEM SEPARATOR + POPUP "&Computer" + BEGIN + MENUITEM "&Lock", ID_COMPUTER_LOCK + MENUITEM "Log o&ff", ID_COMPUTER_LOGOFF + MENUITEM SEPARATOR + MENUITEM "&Sleep", ID_COMPUTER_SLEEP + MENUITEM "&Hibernate", ID_COMPUTER_HIBERNATE + MENUITEM SEPARATOR + MENUITEM "R&estart", ID_COMPUTER_RESTART + MENUITEM "Restart to boot options", ID_COMPUTER_RESTARTBOOTOPTIONS + MENUITEM "Shu&t down", ID_COMPUTER_SHUTDOWN + MENUITEM "H&ybrid shut down", ID_COMPUTER_SHUTDOWNHYBRID + END + MENUITEM "E&xit", ID_ICON_EXIT + END +END + +IDR_MEMORY MENU +BEGIN + POPUP "Memory" + BEGIN + MENUITEM "&Read/Write memory", ID_MEMORY_READWRITEMEMORY + MENUITEM "&Save...", ID_MEMORY_SAVE + MENUITEM "Change &protection...", ID_MEMORY_CHANGEPROTECTION + MENUITEM "&Free", ID_MEMORY_FREE + MENUITEM "&Decommit", ID_MEMORY_DECOMMIT + MENUITEM SEPARATOR + MENUITEM "Read/Write &address...", ID_MEMORY_READWRITEADDRESS + MENUITEM "&Copy\aCtrl+C", ID_MEMORY_COPY + END +END + +IDR_MEMFILTER MENU +BEGIN + POPUP "Filter" + BEGIN + MENUITEM "Contains...", ID_FILTER_CONTAINS + MENUITEM "Contains (case-insensitive)...", ID_FILTER_CONTAINS_CASEINSENSITIVE + MENUITEM "Regex...", ID_FILTER_REGEX + MENUITEM "Regex (case-insensitive)...", ID_FILTER_REGEX_CASEINSENSITIVE + END +END + +IDR_EMPTYMEMLISTS MENU +BEGIN + POPUP "Empty" + BEGIN + MENUITEM "Empty working sets", ID_EMPTY_EMPTYWORKINGSETS + MENUITEM "Empty modified page list", ID_EMPTY_EMPTYMODIFIEDPAGELIST + MENUITEM "Empty standby list", ID_EMPTY_EMPTYSTANDBYLIST + MENUITEM "Empty priority 0 standby list", ID_EMPTY_EMPTYPRIORITY0STANDBYLIST + END +END + +IDR_MINIINFO MENU +BEGIN + POPUP "Mini Info" + BEGIN + POPUP "&Opacity" + BEGIN + MENUITEM "10%", ID_OPACITY_10 + MENUITEM "20%", ID_OPACITY_20 + MENUITEM "30%", ID_OPACITY_30 + MENUITEM "40%", ID_OPACITY_40 + MENUITEM "50%", ID_OPACITY_50 + MENUITEM "60%", ID_OPACITY_60 + MENUITEM "70%", ID_OPACITY_70 + MENUITEM "80%", ID_OPACITY_80 + MENUITEM "90%", ID_OPACITY_90 + MENUITEM "Opaque", ID_OPACITY_OPAQUE + END + MENUITEM SEPARATOR + MENUITEM "&Refresh\aF5", ID_MINIINFO_REFRESH + MENUITEM "Refresh a&utomatically\aF6", ID_MINIINFO_REFRESHAUTOMATICALLY + END +END + +IDR_MINIINFO_PROCESS MENU +BEGIN + POPUP "Process" + BEGIN + MENUITEM SEPARATOR + MENUITEM "&Go to process", ID_PROCESS_GOTOPROCESS + END +END + +IDR_ENVIRONMENT MENU +BEGIN + POPUP "Environment" + BEGIN + MENUITEM "&Edit", ID_ENVIRONMENT_EDIT + MENUITEM "&Delete\aDel", ID_ENVIRONMENT_DELETE + MENUITEM "&Copy\aCtrl+C", ID_ENVIRONMENT_COPY + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROCGENERAL DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Permissions",IDC_PERMISSIONS,143,197,50,14 + PUSHBUTTON "Terminate",IDC_TERMINATE,197,197,50,14 + GROUPBOX "File",IDC_FILE,7,7,246,79 + ICON "",IDC_FILEICON,14,18,20,20 + EDITTEXT IDC_NAME,44,17,182,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_COMPANYNAME,44,29,183,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + CONTROL "Company name link",IDC_COMPANYNAME_LINK,"SysLink",LWS_NOPREFIX | NOT WS_VISIBLE | WS_TABSTOP,46,29,183,9 + LTEXT "Version:",IDC_STATIC,15,41,27,8 + EDITTEXT IDC_VERSION,44,41,113,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Image file name:",IDC_STATIC,15,55,56,8 + EDITTEXT IDC_FILENAME,15,65,191,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Inspect",IDC_INSPECT,210,64,17,15,BS_ICON + PUSHBUTTON "Open",IDC_OPENFILENAME,230,64,17,15,BS_ICON + GROUPBOX "Process",IDC_PROCESS,7,92,246,161 + LTEXT "Command line:",IDC_STATIC,13,103,50,8 + EDITTEXT IDC_CMDLINE,79,101,147,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Current directory:",IDC_STATIC,13,119,60,8 + EDITTEXT IDC_CURDIR,79,117,168,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Started:",IDC_STATIC,13,135,28,8 + EDITTEXT IDC_STARTED,79,133,168,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "PEB address:",IDC_STATIC,13,151,44,8 + EDITTEXT IDC_PEBADDRESS,79,149,101,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Image type:",IDC_PROCESSTYPELABEL,185,151,41,8,NOT WS_VISIBLE + LTEXT "32-bit",IDC_PROCESSTYPETEXT,228,151,20,8,NOT WS_VISIBLE + LTEXT "Parent:",IDC_STATIC,13,167,25,8 + EDITTEXT IDC_PARENTPROCESS,79,165,147,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "View",IDC_VIEWPARENTPROCESS,230,163,17,15,BS_ICON + LTEXT "Mitigation policies:",IDC_STATIC,13,183,59,8 + EDITTEXT IDC_MITIGATION,79,181,126,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Details",IDC_VIEWMITIGATION,209,180,38,14 + LTEXT "Protection:",IDC_STATIC,13,200,36,8 + EDITTEXT IDC_PROTECTION,51,200,80,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + PUSHBUTTON "View",IDC_VIEWCOMMANDLINE,230,100,17,15,BS_ICON +END + +IDD_PROCMODULES DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Modules" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0xa,7,7,246,246,WS_EX_CLIENTEDGE +END + +IDD_PROCTHREADS DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Threads" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Start module:",IDC_STATICBL1,7,170,44,8 + EDITTEXT IDC_STARTMODULE,55,168,177,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Open",IDC_OPENSTARTMODULE,236,166,17,15,BS_ICON | WS_DISABLED + LTEXT "Started:",IDC_STATICBL2,7,184,28,8 + LTEXT "N/A",IDC_STARTED,75,184,178,8 + LTEXT "Kernel time:",IDC_STATICBL3,7,207,40,8 + LTEXT "N/A",IDC_KERNELTIME,75,207,51,8 + LTEXT "User time:",IDC_STATICBL4,7,217,35,8 + LTEXT "N/A",IDC_USERTIME,75,217,51,8 + LTEXT "Context switches:",IDC_STATICBL5,7,228,60,8 + LTEXT "N/A",IDC_CONTEXTSWITCHES,75,228,51,8,SS_ENDELLIPSIS + LTEXT "Cycles:",IDC_STATICBL6,7,239,24,8 + LTEXT "N/A",IDC_CYCLES,41,239,85,8,SS_ENDELLIPSIS + LTEXT "Base priority:",IDC_STATICBL9,136,205,44,8 + LTEXT "Priority:",IDC_STATICBL8,136,195,26,8 + LTEXT "I/O priority:",IDC_STATICBL10,136,216,39,8 + LTEXT "Page priority:",IDC_STATICBL11,136,227,44,8 + LTEXT "State:",IDC_STATICBL7,7,195,21,8 + LTEXT "N/A",IDC_STATE,41,195,85,8,SS_ENDELLIPSIS + LTEXT "N/A",IDC_PRIORITY,195,195,58,8 + LTEXT "N/A",IDC_BASEPRIORITY,195,205,58,8 + LTEXT "N/A",IDC_IOPRIORITY,195,216,58,8 + LTEXT "N/A",IDC_PAGEPRIORITY,195,227,58,8 + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0x22,7,7,246,156,WS_EX_CLIENTEDGE + LTEXT "Ideal processor:",IDC_STATICBL12,136,239,53,8 + LTEXT "N/A",IDC_IDEALPROCESSOR,195,239,58,8 +END + +IDD_PROCHANDLES DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Handles" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Hide unnamed handles",IDC_HIDEUNNAMEDHANDLES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,88,10 + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0x2,7,21,246,232,WS_EX_CLIENTEDGE +END + +IDD_PROCENVIRONMENT DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Environment" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,246,227 + PUSHBUTTON "New...",IDC_NEW,93,239,50,14 + PUSHBUTTON "Edit...",IDC_EDIT,148,239,50,14 + PUSHBUTTON "Delete",IDC_DELETE,203,239,50,14 +END + +IDD_THRDSTACK DIALOGEX 0, 0, 261, 228 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Thread Stack" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,7,247,196 + PUSHBUTTON "Copy",IDC_COPY,96,207,50,14 + PUSHBUTTON "Refresh",IDC_REFRESH,150,207,50,14 + PUSHBUTTON "Close",IDOK,204,207,50,14 +END + +IDD_ABOUT DIALOGEX 0, 0, 270, 195 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + ICON IDI_PROCESSHACKER,IDC_STATIC,16,15,20,20 + LTEXT "Process Hacker",IDC_ABOUT_NAME,45,14,192,8 + LTEXT "Licensed under the GNU GPL, v3.",IDC_STATIC,45,27,193,8 + LTEXT "Copyright (c) 2008-2016 Wen Jia Liu (wj32)",IDC_STATIC,15,40,140,8 + CONTROL "Credits.",IDC_CREDITS,"SysLink",WS_TABSTOP,15,55,248,115 + CONTROL "Process Hacker on SourceForge.net",IDC_LINK_SF, + "SysLink",WS_TABSTOP,7,177,130,11 + PUSHBUTTON "Diagnostics",IDC_DIAGNOSTICS,160,174,50,14 + DEFPUSHBUTTON "OK",IDOK,213,174,50,14 +END + +IDD_SRVLIST DIALOGEX 0, 0, 237, 201 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,0,0,237,141 + EDITTEXT IDC_DESCRIPTION,0,144,237,40,ES_MULTILINE | ES_READONLY | NOT WS_BORDER + PUSHBUTTON "&Start",IDC_START,134,187,50,14 + PUSHBUTTON "&Pause",IDC_PAUSE,187,187,50,14 +END + +IDD_SRVGENERAL DIALOGEX 0, 0, 282, 183 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_DESCRIPTION,7,7,268,45,ES_MULTILINE | ES_READONLY | WS_VSCROLL + LTEXT "Type:",IDC_STATIC,7,57,20,8 + COMBOBOX IDC_TYPE,30,56,110,65,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Start type:",IDC_STATIC,147,57,38,8 + COMBOBOX IDC_STARTTYPE,188,56,87,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Error control:",IDC_STATIC,7,75,47,8 + COMBOBOX IDC_ERRORCONTROL,58,73,82,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Group:",IDC_STATIC,147,75,23,8 + EDITTEXT IDC_GROUP,173,74,102,12,ES_AUTOHSCROLL + LTEXT "Binary path:",IDC_STATIC,7,92,40,8 + EDITTEXT IDC_BINARYPATH,58,91,163,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,225,90,50,14 + LTEXT "User account:",IDC_STATIC,7,110,46,8 + EDITTEXT IDC_USERACCOUNT,58,108,217,12,ES_AUTOHSCROLL + LTEXT "Password:",IDC_STATIC,7,127,34,8 + EDITTEXT IDC_PASSWORD,58,125,204,12,ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "",IDC_PASSWORDCHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,266,126,9,10 + LTEXT "Service DLL:",IDC_STATIC,7,144,48,8 + EDITTEXT IDC_SERVICEDLL,58,142,217,12,ES_AUTOHSCROLL | ES_READONLY + CONTROL "Delayed start",IDC_DELAYEDSTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,158,59,10 +END + +IDD_HNDLGENERAL DIALOGEX 0, 0, 260, 181 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Basic information",IDC_BASICINFORMATION,7,7,246,67 + LTEXT "Name:",IDC_STATIC,14,19,22,8 + EDITTEXT IDC_NAME,40,19,206,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Type:",IDC_STATIC,14,32,20,8 + EDITTEXT IDC_TYPE,40,32,206,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Object address:",IDC_STATIC,14,44,53,8 + EDITTEXT IDC_ADDRESS,71,44,175,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Granted access:",IDC_STATIC,14,56,54,8 + EDITTEXT IDC_GRANTED_ACCESS,71,56,175,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + GROUPBOX "References",IDC_STATIC,7,78,119,39 + LTEXT "References:",IDC_STATIC,14,90,40,8 + LTEXT "Static",IDC_REFERENCES,58,90,41,8 + LTEXT "Handles:",IDC_STATIC,14,101,29,8 + LTEXT "Static",IDC_HANDLES,58,101,40,8 + GROUPBOX "Quota charges",IDC_STATIC,131,78,122,39 + LTEXT "Paged:",IDC_STATIC,138,89,24,8 + LTEXT "Static",IDC_PAGED,182,89,39,8 + LTEXT "Non-paged:",IDC_STATIC,138,100,39,8 + LTEXT "Static",IDC_NONPAGED,182,100,39,8 +END + +IDD_INFORMATION DIALOGEX 0, 0, 317, 184 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Information" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_TEXT,7,7,303,152,ES_MULTILINE | ES_READONLY | WS_VSCROLL + PUSHBUTTON "Save...",IDC_SAVE,154,163,50,14 + PUSHBUTTON "Copy",IDC_COPY,207,163,50,14 + DEFPUSHBUTTON "Close",IDOK,260,163,50,14 +END + +IDD_FINDOBJECTS DIALOGEX 0, 0, 357, 233 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Find Handles or DLLs" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Filter:",IDC_STATIC,7,9,20,8 + EDITTEXT IDC_FILTER,32,8,223,12,ES_AUTOHSCROLL + PUSHBUTTON "Find",IDOK,300,7,50,14 + CONTROL "",IDC_RESULTS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,26,343,200 + CONTROL "&Regex",IDC_REGEX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,261,9,36,10 +END + +IDD_OBJTOKEN DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Token" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "User:",IDC_STATIC,7,7,18,8 + EDITTEXT IDC_USER,48,7,205,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "User SID:",IDC_STATIC,7,18,32,8 + EDITTEXT IDC_USERSID,48,18,205,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Session:",IDC_STATIC,7,29,28,8 + LTEXT "Unknown",IDC_SESSIONID,38,29,30,8 + LTEXT "Elevated:",IDC_STATIC,85,29,32,8 + LTEXT "Unknown",IDC_ELEVATED,120,29,30,8 + LTEXT "Virtualized:",IDC_STATIC,169,29,36,8 + LTEXT "Unknown",IDC_VIRTUALIZED,209,29,44,8 + LTEXT "App container SID:",IDC_STATIC,7,40,62,8 + EDITTEXT IDC_APPCONTAINERSID,75,40,178,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + CONTROL "",IDC_GROUPS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,53,246,84 + CONTROL "",IDC_PRIVILEGES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,142,246,83 + LTEXT "To view capabilities, claims and other attributes, click Advanced.",IDC_INSTRUCTION,7,228,206,8 + PUSHBUTTON "Integrity",IDC_INTEGRITY,149,239,50,14 + PUSHBUTTON "Advanced",IDC_ADVANCED,203,239,50,14 +END + +IDD_HIDDENPROCESSES DIALOGEX 0, 0, 337, 221 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Hidden Processes" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Processes highlighted red are hidden while those highlighted grey have terminated.",IDC_INTRO,7,7,323,12 + CONTROL "",IDC_PROCESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,23,323,162 + RTEXT "",IDC_DESCRIPTION,7,187,323,11 + COMBOBOX IDC_METHOD,7,201,73,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Terminate",IDC_TERMINATE,115,200,50,14 + PUSHBUTTON "Save...",IDC_SAVE,170,200,50,14 + PUSHBUTTON "&Scan",IDC_SCAN,225,200,50,14 + DEFPUSHBUTTON "Close",IDOK,280,200,50,14 +END + +IDD_RUNAS DIALOGEX 0, 0, 278, 127 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Run As" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Enter the command to start as the specified user.",IDC_STATIC,7,7,160,8 + LTEXT "Program:",IDC_STATIC,7,23,30,8 + EDITTEXT IDC_PROGRAM,53,21,163,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,221,20,50,14 + LTEXT "User name:",IDC_STATIC,7,40,38,8 + COMBOBOX IDC_USERNAME,53,38,123,12,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Type:",IDC_STATIC,183,40,20,8 + COMBOBOX IDC_TYPE,207,38,64,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Password:",IDC_STATIC,7,56,34,8 + EDITTEXT IDC_PASSWORD,53,55,123,12,ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "Toggle elevation",IDC_TOGGLEELEVATION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,183,56,69,10 + LTEXT "Session ID:",IDC_STATIC,7,73,37,8 + EDITTEXT IDC_SESSIONID,53,72,104,12,ES_AUTOHSCROLL | ES_NUMBER + PUSHBUTTON "...",IDC_SESSIONS,161,71,16,14 + LTEXT "Desktop:",IDC_STATIC,7,90,30,8 + EDITTEXT IDC_DESKTOP,53,88,104,12,ES_AUTOHSCROLL + PUSHBUTTON "...",IDC_DESKTOPS,161,87,16,14 + DEFPUSHBUTTON "OK",IDOK,168,106,50,14 + PUSHBUTTON "Cancel",IDCANCEL,221,106,50,14 +END + +IDD_PROGRESS DIALOGEX 0, 0, 216, 62 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Progress" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,159,41,50,14 + CONTROL "",IDC_PROGRESS,"msctls_progress32",0x0,7,23,202,14 + LTEXT "Please wait...",IDC_PROGRESSTEXT,7,7,202,12 +END + +IDD_PAGEFILES DIALOGEX 0, 0, 322, 162 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Pagefiles" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,7,308,128 + PUSHBUTTON "Refresh",IDC_REFRESH,211,141,50,14 + DEFPUSHBUTTON "Close",IDOK,265,141,50,14 +END + +IDD_TOKGENERAL DIALOGEX 0, 0, 270, 228 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Token",IDC_STATIC,7,7,256,132 + LTEXT "User:",IDC_STATIC,15,19,18,8 + EDITTEXT IDC_USER,71,17,185,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "User SID:",IDC_STATIC,15,36,32,8 + EDITTEXT IDC_USERSID,71,34,185,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Owner:",IDC_STATIC,15,53,25,8 + EDITTEXT IDC_OWNER,71,51,185,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Primary group:",IDC_STATIC,15,70,49,8 + EDITTEXT IDC_PRIMARYGROUP,71,68,185,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Session ID:",IDC_STATIC,15,87,37,8 + EDITTEXT IDC_SESSIONID,71,85,88,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Elevated:",IDC_STATIC,15,104,32,8 + EDITTEXT IDC_ELEVATED,71,102,117,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Virtualization:",IDC_STATIC,15,121,44,8 + EDITTEXT IDC_VIRTUALIZATION,71,119,185,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Linked token",IDC_LINKEDTOKEN,193,101,63,14 + GROUPBOX "Source",IDC_STATIC,7,143,256,47 + LTEXT "Name:",IDC_STATIC,15,155,22,8 + EDITTEXT IDC_SOURCENAME,71,153,185,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "LUID:",IDC_STATIC,15,173,19,8 + EDITTEXT IDC_SOURCELUID,71,171,185,12,ES_AUTOHSCROLL | ES_READONLY +END + +IDD_TOKADVANCED DIALOGEX 0, 0, 236, 186 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Type:",IDC_STATIC,7,8,20,8 + EDITTEXT IDC_TYPE,81,7,116,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Impersonation level:",IDC_STATIC,7,26,68,8 + EDITTEXT IDC_IMPERSONATIONLEVEL,81,24,116,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Token LUID:",IDC_STATIC,7,43,55,8 + EDITTEXT IDC_TOKENLUID,81,41,116,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Authentication LUID:",IDC_STATIC,7,61,68,8 + EDITTEXT IDC_AUTHENTICATIONLUID,81,59,116,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Memory used:",IDC_STATIC,7,78,47,8 + EDITTEXT IDC_MEMORYUSED,81,76,116,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Memory available:",IDC_STATIC,7,95,60,8 + EDITTEXT IDC_MEMORYAVAILABLE,81,93,116,12,ES_AUTOHSCROLL | ES_READONLY +END + +IDD_OBJJOB DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Job" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Name:",IDC_STATIC,7,9,22,8 + EDITTEXT IDC_NAME,35,8,164,12,ES_AUTOHSCROLL + PUSHBUTTON "Terminate",IDC_TERMINATE,203,7,50,14 + LTEXT "Processes in job:",IDC_STATIC,7,25,55,8 + CONTROL "",IDC_PROCESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,38,246,62 + PUSHBUTTON "Add...",IDC_ADD,203,103,50,14 + LTEXT "Limits:",IDC_STATIC,7,117,21,8 + CONTROL "",IDC_LIMITS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,129,246,107 + PUSHBUTTON "Advanced",IDC_ADVANCED,203,239,50,14 +END + +IDD_OBJEVENT DIALOGEX 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Event" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Type:",IDC_STATIC,7,7,20,8 + LTEXT "Unknown",IDC_TYPE,42,7,137,8 + LTEXT "Signaled:",IDC_STATIC,7,18,30,8 + LTEXT "Unknown",IDC_SIGNALED,42,18,137,8 + PUSHBUTTON "Set",IDC_SET,7,34,50,14 + PUSHBUTTON "Reset",IDC_RESET,61,34,50,14 + PUSHBUTTON "Pulse",IDC_PULSE,115,34,50,14 +END + +IDD_OBJMUTANT DIALOGEX 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Mutant" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Count:",IDC_STATIC,7,7,23,8 + LTEXT "Unknown",IDC_COUNT,55,7,124,8 + LTEXT "Abandoned:",IDC_STATIC,7,18,40,8 + LTEXT "Unknown",IDC_ABANDONED,55,18,124,8 + LTEXT "Owner:",IDC_OWNERLABEL,7,29,25,8 + LTEXT "Unknown",IDC_OWNER,55,29,124,8 +END + +IDD_OBJSEMAPHORE DIALOGEX 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Semaphore" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Current count:",IDC_STATIC,7,7,50,8 + LTEXT "Unknown",IDC_CURRENTCOUNT,66,7,113,8 + LTEXT "Maximum count:",IDC_STATIC,7,18,54,8 + LTEXT "Unknown",IDC_MAXIMUMCOUNT,66,18,113,8 + PUSHBUTTON "Acquire",IDC_ACQUIRE,7,34,50,14 + PUSHBUTTON "Release",IDC_RELEASE,61,34,50,14 +END + +IDD_OBJTIMER DIALOGEX 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Timer" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Signaled:",-1,7,7,30,8 + LTEXT "Unknown",IDC_SIGNALED,42,7,137,8 + PUSHBUTTON "Cancel",IDC_CANCEL,7,19,50,14 +END + +IDD_JOBSTATISTICS DIALOGEX 0, 0, 259, 186 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Statistics" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "General",IDC_STATIC,7,7,133,47 + LTEXT "Active processes",IDC_STATIC,15,18,55,8 + RTEXT "Static",IDC_ZACTIVEPROCESSES_V,99,18,37,8,SS_ENDELLIPSIS + LTEXT "Total processes",IDC_STATIC,15,28,51,8 + RTEXT "Static",IDC_ZTOTALPROCESSES_V,95,29,41,8,SS_ENDELLIPSIS + LTEXT "Terminated processes",IDC_STATIC,15,39,71,8 + RTEXT "Static",IDC_ZTERMINATEDPROCESSES_V,99,39,37,8,SS_ENDELLIPSIS + GROUPBOX "Time",IDC_STATIC,7,57,133,57 + LTEXT "User time",IDC_STATIC,15,68,32,8 + LTEXT "Kernel time",IDC_STATIC,15,79,38,8 + LTEXT "User time (period)",IDC_STATIC,15,89,60,8 + LTEXT "Kernel time (period)",IDC_STATIC,15,99,65,8 + RTEXT "Static",IDC_ZUSERTIME_V,83,68,53,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZKERNELTIME_V,85,79,51,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZUSERTIMEPERIOD_V,87,89,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZKERNELTIMEPERIOD_V,87,99,49,8,SS_ENDELLIPSIS + GROUPBOX "Memory",IDC_STATIC,7,118,133,48 + LTEXT "Page faults",IDC_STATIC,15,129,38,8 + LTEXT "Peak process usage",IDC_STATIC,15,140,65,8 + LTEXT "Peak job usage",IDC_STATIC,15,151,52,8 + RTEXT "Static",IDC_ZPAGEFAULTS_V,86,129,50,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPEAKPROCESSUSAGE_V,85,140,51,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPEAKJOBUSAGE_V,86,151,50,8,SS_ENDELLIPSIS + GROUPBOX "I/O",IDC_STATIC,146,7,106,75 + LTEXT "Reads",IDC_STATIC,152,17,21,8 + LTEXT "Read bytes",IDC_STATIC,152,27,38,8 + LTEXT "Writes",IDC_STATIC,152,37,22,8 + LTEXT "Write bytes",IDC_STATIC,152,47,38,8 + LTEXT "Other",IDC_STATIC,152,57,20,8 + LTEXT "Other bytes",IDC_STATIC,152,67,40,8 + RTEXT "Static",IDC_ZIOREADS_V,200,17,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOREADBYTES_V,200,27,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOWRITES_V,200,37,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOWRITEBYTES_V,200,47,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOOTHER_V,200,57,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOOTHERBYTES_V,200,67,47,8,SS_ENDELLIPSIS +END + +IDD_OBJEVENTPAIR DIALOGEX 0, 0, 186, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Event Pair" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Set low",IDC_SETLOW,7,7,50,14 + PUSHBUTTON "Set high",IDC_SETHIGH,61,7,50,14 +END + +IDD_OBJSECTION DIALOGEX 0, 0, 255, 76 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Section" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Type:",IDC_STATIC,7,7,20,8 + LTEXT "Unknown",IDC_TYPE,43,7,205,8 + LTEXT "Size:",IDC_STATIC,7,18,16,8 + LTEXT "Unknown",IDC_SIZE_,43,18,205,8 + LTEXT "File:",IDC_STATIC,7,29,14,8 + EDITTEXT IDC_FILE,43,29,205,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER +END + +IDD_AFFINITY DIALOGEX 0, 0, 279, 227 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Affinity" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,169,206,50,14 + PUSHBUTTON "Cancel",IDCANCEL,222,206,50,14 + LTEXT "Affinity controls which CPUs threads are allowed to execute on.",IDC_STATIC,7,7,265,11 + CONTROL "CPU 0",IDC_CPU0,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,21,35,10 + CONTROL "CPU 1",IDC_CPU1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,33,35,10 + CONTROL "CPU 2",IDC_CPU2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,44,35,10 + CONTROL "CPU 3",IDC_CPU3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,56,35,10 + CONTROL "CPU 4",IDC_CPU4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,67,35,10 + CONTROL "CPU 5",IDC_CPU5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,78,35,10 + CONTROL "CPU 6",IDC_CPU6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,89,35,10 + CONTROL "CPU 7",IDC_CPU7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,100,35,10 + CONTROL "CPU 8",IDC_CPU8,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,111,35,10 + CONTROL "CPU 9",IDC_CPU9,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,122,35,10 + CONTROL "CPU 10",IDC_CPU10,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,134,39,10 + CONTROL "CPU 11",IDC_CPU11,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,145,39,10 + CONTROL "CPU 12",IDC_CPU12,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,156,39,10 + CONTROL "CPU 13",IDC_CPU13,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,167,39,10 + CONTROL "CPU 14",IDC_CPU14,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,178,39,10 + CONTROL "CPU 15",IDC_CPU15,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,190,39,10 + CONTROL "CPU 16",IDC_CPU16,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,21,39,10 + CONTROL "CPU 17",IDC_CPU17,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,33,39,10 + CONTROL "CPU 18",IDC_CPU18,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,44,39,10 + CONTROL "CPU 19",IDC_CPU19,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,56,39,10 + CONTROL "CPU 20",IDC_CPU20,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,67,39,10 + CONTROL "CPU 21",IDC_CPU21,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,78,39,10 + CONTROL "CPU 22",IDC_CPU22,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,89,39,10 + CONTROL "CPU 23",IDC_CPU23,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,100,39,10 + CONTROL "CPU 24",IDC_CPU24,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,111,39,10 + CONTROL "CPU 25",IDC_CPU25,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,122,39,10 + CONTROL "CPU 26",IDC_CPU26,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,134,39,10 + CONTROL "CPU 27",IDC_CPU27,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,145,39,10 + CONTROL "CPU 28",IDC_CPU28,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,156,39,10 + CONTROL "CPU 29",IDC_CPU29,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,167,39,10 + CONTROL "CPU 30",IDC_CPU30,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,178,39,10 + CONTROL "CPU 31",IDC_CPU31,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,79,190,39,10 + CONTROL "CPU 32",IDC_CPU32,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,21,39,10 + CONTROL "CPU 33",IDC_CPU33,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,33,39,10 + CONTROL "CPU 34",IDC_CPU34,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,44,39,10 + CONTROL "CPU 35",IDC_CPU35,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,56,39,10 + CONTROL "CPU 36",IDC_CPU36,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,67,39,10 + CONTROL "CPU 37",IDC_CPU37,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,78,39,10 + CONTROL "CPU 38",IDC_CPU38,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,89,39,10 + CONTROL "CPU 39",IDC_CPU39,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,100,39,10 + CONTROL "CPU 40",IDC_CPU40,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,111,39,10 + CONTROL "CPU 41",IDC_CPU41,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,122,39,10 + CONTROL "CPU 42",IDC_CPU42,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,134,39,10 + CONTROL "CPU 43",IDC_CPU43,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,145,39,10 + CONTROL "CPU 44",IDC_CPU44,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,156,39,10 + CONTROL "CPU 45",IDC_CPU45,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,167,39,10 + CONTROL "CPU 46",IDC_CPU46,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,178,39,10 + CONTROL "CPU 47",IDC_CPU47,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,190,39,10 + CONTROL "CPU 48",IDC_CPU48,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,21,39,10 + CONTROL "CPU 49",IDC_CPU49,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,33,39,10 + CONTROL "CPU 50",IDC_CPU50,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,44,39,10 + CONTROL "CPU 51",IDC_CPU51,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,56,39,10 + CONTROL "CPU 52",IDC_CPU52,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,67,39,10 + CONTROL "CPU 53",IDC_CPU53,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,78,39,10 + CONTROL "CPU 54",IDC_CPU54,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,89,39,10 + CONTROL "CPU 55",IDC_CPU55,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,100,39,10 + CONTROL "CPU 56",IDC_CPU56,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,111,39,10 + CONTROL "CPU 57",IDC_CPU57,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,122,39,10 + CONTROL "CPU 58",IDC_CPU58,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,134,39,10 + CONTROL "CPU 59",IDC_CPU59,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,145,39,10 + CONTROL "CPU 60",IDC_CPU60,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,156,39,10 + CONTROL "CPU 61",IDC_CPU61,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,167,39,10 + CONTROL "CPU 62",IDC_CPU62,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,178,39,10 + CONTROL "CPU 63",IDC_CPU63,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,224,190,39,10 + PUSHBUTTON "Select all",IDC_SELECTALL,7,206,50,14 + PUSHBUTTON "Deselect all",IDC_DESELECTALL,60,206,50,14 +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 +CAPTION "System Information" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Click a graph to view details for that section.",IDC_INSTRUCTION,7,228,144,8 + CONTROL "Always on &top",IDC_ALWAYSONTOP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,297,228,63,10 + DEFPUSHBUTTON "Close",IDOK,366,226,50,14 +END + +IDD_EDITMESSAGE DIALOGEX 0, 0, 282, 163 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Message" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Title:",IDC_STATIC,7,8,17,8 + EDITTEXT IDC_TITLE,52,7,223,12,ES_AUTOHSCROLL + LTEXT "Text:",IDC_STATIC,7,24,18,8 + EDITTEXT IDC_TEXT,52,23,223,79,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + LTEXT "Icon:",IDC_STATIC,7,107,18,8 + COMBOBOX IDC_TYPE,52,105,80,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Timeout (s):",IDC_STATIC,7,123,40,8 + EDITTEXT IDC_TIMEOUT,52,122,43,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,172,142,50,14 + PUSHBUTTON "Cancel",IDCANCEL,225,142,50,14 +END + +IDD_SESSION DIALOGEX 0, 0, 201, 149 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Session Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDOK,144,128,50,14 + LTEXT "User name:",IDC_STATIC,7,7,38,8 + LTEXT "Session ID:",IDC_STATIC,7,18,37,8 + LTEXT "State:",IDC_STATIC,7,29,21,8 + LTEXT "Client name:",IDC_STATIC,7,84,41,8 + LTEXT "Client address:",IDC_STATIC,7,95,49,8 + LTEXT "Client display:",IDC_STATIC,7,106,46,8 + LTEXT "N/A",IDC_USERNAME,66,7,128,8 + LTEXT "N/A",IDC_SESSIONID,66,18,128,8 + LTEXT "N/A",IDC_STATE,66,29,128,8 + LTEXT "N/A",IDC_CLIENTNAME,66,84,128,8 + LTEXT "N/A",IDC_CLIENTADDRESS,66,95,128,8 + LTEXT "N/A",IDC_CLIENTDISPLAY,66,106,128,8 + LTEXT "Logon time:",IDC_STATIC,7,40,38,8 + LTEXT "N/A",IDC_LOGONTIME,66,40,128,8 + LTEXT "Connect time:",IDC_STATIC,7,51,46,8 + LTEXT "Disconnect time:",IDC_STATIC,7,62,54,8 + LTEXT "Last input time:",IDC_STATIC,7,73,50,8 + LTEXT "N/A",IDC_CONNECTTIME,66,51,128,8 + LTEXT "N/A",IDC_DISCONNECTTIME,66,62,128,8 + LTEXT "N/A",IDC_LASTINPUTTIME,66,74,128,8 +END + +IDD_PROCMEMORY DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Memory" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Strings...",IDC_STRINGS,150,7,50,14 + PUSHBUTTON "Refresh",IDC_REFRESH,203,7,50,14 + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0xa,7,26,246,227,WS_EX_CLIENTEDGE + CONTROL "Hide free regions",IDC_HIDEFREEREGIONS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,9,71,10 +END + +IDD_CHOOSE DIALOGEX 0, 0, 199, 73 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Text:",IDC_MESSAGE,7,7,185,8,SS_ENDELLIPSIS + COMBOBOX IDC_CHOICE,7,20,185,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP + CONTROL "Option",IDC_OPTION,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,39,185,10 + DEFPUSHBUTTON "OK",IDOK,89,52,50,14 + PUSHBUTTON "Cancel",IDCANCEL,142,52,50,14 + COMBOBOX IDC_CHOICEUSER,7,20,185,30,CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_CHOICESIMPLE,7,20,185,13,ES_PASSWORD | ES_AUTOHSCROLL | NOT WS_VISIBLE +END + +IDD_OPTGENERAL DIALOGEX 0, 0, 250, 154 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Search engine:",IDC_STATIC,7,8,49,8 + EDITTEXT IDC_SEARCHENGINE,61,7,182,12,ES_AUTOHSCROLL + LTEXT "PE viewer:",IDC_STATIC,7,23,35,8 + EDITTEXT IDC_PEVIEWER,61,22,182,12,ES_AUTOHSCROLL + LTEXT "Max. size unit:",IDC_STATIC,7,38,49,8 + COMBOBOX IDC_MAXSIZEUNIT,61,37,39,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Icon processes:",IDC_STATIC,7,55,52,8 + EDITTEXT IDC_ICONPROCESSES,61,53,40,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Allow only one instance",IDC_ALLOWONLYONEINSTANCE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,71,91,10 + CONTROL "Hide when closed",IDC_HIDEONCLOSE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,83,71,10 + CONTROL "Hide when minimized",IDC_HIDEONMINIMIZE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,95,81,10 + CONTROL "Start hidden",IDC_STARTHIDDEN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,119,55,10 + CONTROL "Collapse services on start",IDC_COLLAPSESERVICES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,71,98,10 + CONTROL "Single-click tray icons",IDC_ICONSINGLECLICK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,83,83,10 + CONTROL "Icon click toggles visibility",IDC_ICONTOGGLESVISIBILITY, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,95,97,10 + CONTROL "Enable plugins",IDC_ENABLEPLUGINS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,122,107,61,10 + PUSHBUTTON "Font...",IDC_FONT,7,133,50,14 + CONTROL "Start when I log on",IDC_STARTATLOGON,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,107,77,10 +END + +IDD_OPTHIGHLIGHTING DIALOGEX 0, 0, 250, 174 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Highlighting" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Highlighting duration:",IDC_STATIC,7,8,70,8 + EDITTEXT IDC_HIGHLIGHTINGDURATION,79,7,40,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "New objects:",IDC_STATIC,7,25,44,8 + CONTROL "New objects",IDC_NEWOBJECTS,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,57,23,33,13 + LTEXT "Removed objects:",IDC_STATIC,127,25,60,8 + CONTROL "Removed objects",IDC_REMOVEDOBJECTS,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,193,23,33,13 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,41,236,107 + LTEXT "Double-click an item to change it.",IDC_STATIC,7,156,106,8 + PUSHBUTTON "Enable all",IDC_ENABLEALL,140,153,50,14 + PUSHBUTTON "Disable all",IDC_DISABLEALL,193,153,50,14 +END + +IDD_CHOOSECOLUMNS DIALOGEX 0, 0, 381, 207 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Choose Columns" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Select the columns that will appear in the list.",IDC_MESSAGE,7,7,367,10 + LTEXT "Inactive columns:",IDC_STATIC,7,21,57,8 + LISTBOX IDC_INACTIVE,7,31,129,150,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Show >",IDC_SHOW,139,86,50,14 + PUSHBUTTON "< Hide",IDC_HIDE,139,103,50,14 + LISTBOX IDC_ACTIVE,192,31,129,150,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Move up",IDC_MOVEUP,324,31,50,14 + PUSHBUTTON "Move down",IDC_MOVEDOWN,324,48,50,14 + DEFPUSHBUTTON "OK",IDOK,272,186,50,14 + PUSHBUTTON "Cancel",IDCANCEL,324,186,50,14 + LTEXT "Active columns:",IDC_STATIC,192,21,51,8 +END + +IDD_NETSTACK DIALOGEX 0, 0, 261, 228 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Network Stack" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,7,247,196 + PUSHBUTTON "Close",IDOK,204,207,50,14 +END + +IDD_CREATESERVICE DIALOGEX 0, 0, 287, 133 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Create Service" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Name:",IDC_STATIC,7,8,22,8 + EDITTEXT IDC_NAME,61,7,219,12,ES_AUTOHSCROLL + LTEXT "Display name:",IDC_STATIC,7,24,46,8 + EDITTEXT IDC_DISPLAYNAME,61,23,219,12,ES_AUTOHSCROLL + LTEXT "Type:",IDC_STATIC,7,41,20,8 + COMBOBOX IDC_TYPE,61,39,97,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Start type:",IDC_STATIC,7,58,38,8 + COMBOBOX IDC_STARTTYPE,61,56,97,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Error control:",IDC_STATIC,7,75,45,8 + COMBOBOX IDC_ERRORCONTROL,61,73,97,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Binary path:",IDC_STATIC,7,91,40,8 + EDITTEXT IDC_BINARYPATH,61,90,165,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,230,89,50,14 + DEFPUSHBUTTON "OK",IDOK,176,112,50,14 + PUSHBUTTON "Cancel",IDCANCEL,230,112,50,14 +END + +IDD_PROCPERFORMANCE DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Performance" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "CPU",IDC_GROUPCPU,7,7,246,74,0,WS_EX_TRANSPARENT + GROUPBOX "Private bytes",IDC_GROUPPRIVATEBYTES,7,86,246,77,0,WS_EX_TRANSPARENT + GROUPBOX "I/O",IDC_GROUPIO,7,164,246,89,0,WS_EX_TRANSPARENT + CONTROL "",IDC_CPU,"PhGraph",WS_CLIPSIBLINGS,105,42,50,14 + CONTROL "",IDC_PRIVATEBYTES,"PhGraph",WS_CLIPSIBLINGS,105,122,50,14 + CONTROL "",IDC_IO,"PhGraph",WS_CLIPSIBLINGS,105,204,50,14 +END + +IDD_PROCSTATISTICS DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Statistics" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "CPU",IDC_STATIC,7,7,119,64 + GROUPBOX "I/O",IDC_STATIC,131,7,122,83 + LTEXT "Priority",IDC_STATIC,14,17,24,8 + LTEXT "Cycles",IDC_STATIC,14,27,22,8 + LTEXT "Kernel time",IDC_STATIC,14,37,38,8 + LTEXT "User time",IDC_STATIC,14,47,32,8 + LTEXT "Total time",IDC_STATIC,14,57,34,8 + RTEXT "Static",IDC_ZPRIORITY_V,59,17,61,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCYCLES_V,43,27,77,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZKERNELTIME_V,59,37,61,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZUSERTIME_V,59,47,61,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZTOTALTIME_V,59,57,61,8,SS_ENDELLIPSIS + GROUPBOX "Memory",IDC_STATIC,7,74,120,128 + LTEXT "Private bytes",IDC_STATIC,14,84,44,8 + LTEXT "Working set",IDC_STATIC,14,136,40,8 + LTEXT "Peak working set",IDC_STATIC,14,178,57,8 + LTEXT "Virtual size",IDC_STATIC,14,105,36,8 + LTEXT "Peak virtual size",IDC_STATIC,14,115,53,8 + LTEXT "Peak private bytes",IDC_STATIC,14,95,61,8 + LTEXT "Page faults",IDC_STATIC,14,125,38,8 + LTEXT "Page priority",IDC_STATIC,14,188,42,8 + RTEXT "Static",IDC_ZPRIVATEBYTES_V,72,84,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWORKINGSET_V,72,136,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPEAKWORKINGSET_V,78,178,42,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZVIRTUALSIZE_V,72,105,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPEAKVIRTUALSIZE_V,72,115,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPEAKPRIVATEBYTES_V,80,95,40,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEFAULTS_V,72,125,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEPRIORITY_V,72,188,48,8,SS_ENDELLIPSIS + LTEXT "Reads",IDC_STATIC,138,17,21,8 + LTEXT "Read bytes",IDC_STATIC,138,27,38,8 + LTEXT "Writes",IDC_STATIC,138,37,22,8 + LTEXT "Write bytes",IDC_STATIC,138,47,38,8 + LTEXT "Other",IDC_STATIC,138,57,20,8 + LTEXT "Other bytes",IDC_STATIC,138,67,40,8 + LTEXT "I/O priority",IDC_STATIC,138,77,36,8 + RTEXT "Static",IDC_ZIOREADS_V,184,17,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOREADBYTES_V,184,27,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOWRITES_V,184,37,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOWRITEBYTES_V,184,47,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOOTHER_V,184,57,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOOTHERBYTES_V,184,67,63,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZIOPRIORITY_V,184,77,63,8,SS_ENDELLIPSIS + GROUPBOX "Other",IDC_STATIC,131,92,122,70 + LTEXT "Handles",IDC_STATIC,138,102,26,8 + LTEXT "GDI handles",IDC_STATIC,138,122,40,8 + LTEXT "USER handles",IDC_STATIC,138,132,46,8 + RTEXT "Static",IDC_ZHANDLES_V,193,102,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZGDIHANDLES_V,193,122,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZUSERHANDLES_V,193,132,54,8,SS_ENDELLIPSIS + PUSHBUTTON "Details",IDC_DETAILS,137,143,50,14 + LTEXT "Peak handles",IDC_STATIC,138,112,44,8 + RTEXT "Static",IDC_ZPEAKHANDLES_V,192,112,55,8,SS_ENDELLIPSIS + LTEXT "Private WS",IDC_STATIC,22,147,36,8 + LTEXT "Shareable WS",IDC_STATIC,22,157,46,8 + LTEXT "Shared WS",IDC_STATIC,22,167,36,8 + RTEXT "Static",IDC_ZPRIVATEWS_V,78,147,42,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSHAREABLEWS_V,78,157,42,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSHAREDWS_V,78,167,42,8,SS_ENDELLIPSIS +END + +IDD_OPTADVANCED DIALOGEX 0, 0, 250, 150 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Advanced" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Enable warnings",IDC_ENABLEWARNINGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,68,10 + CONTROL "Enable kernel-mode driver",IDC_ENABLEKERNELMODEDRIVER, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,19,99,10 + CONTROL "Hide unnamed handles",IDC_HIDEUNNAMEDHANDLES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,31,88,10 + CONTROL "Check images for digital signatures and packing",IDC_ENABLESTAGE2, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,43,167,10 + CONTROL "Resolve addresses for network connections",IDC_ENABLENETWORKRESOLVE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,55,155,10 + CONTROL "Include CPU (and other) usage of children in collapsed processes",IDC_PROPAGATECPUUSAGE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,67,223,10 + CONTROL "Show tooltips instantly",IDC_ENABLEINSTANTTOOLTIPS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,79,88,10 + CONTROL "Enable cycle-based CPU usage",IDC_ENABLECYCLECPUUSAGE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,91,114,10 + CONTROL "Replace Task Manager with Process Hacker",IDC_REPLACETASKMANAGER, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,105,154,10 + PUSHBUTTON "Change...",IDC_CHANGE,161,103,62,14 + LTEXT "History sample count:",IDC_SAMPLECOUNTLABEL,7,121,70,8 + EDITTEXT IDC_SAMPLECOUNT,82,120,39,12,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Automatic",IDC_SAMPLECOUNTAUTOMATIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,126,121,48,10 +END + +IDD_GDIHANDLES DIALOGEX 0, 0, 351, 307 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GDI Handles" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,337,275 + PUSHBUTTON "Refresh",IDC_REFRESH,240,286,50,14 + DEFPUSHBUTTON "Close",IDOK,294,286,50,14 +END + +IDD_LOG DIALOGEX 0, 0, 313, 303 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Log" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,7,299,272 + PUSHBUTTON "Clear",IDC_CLEAR,7,282,50,14 + CONTROL "Auto-scroll",IDC_AUTOSCROLL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,61,284,50,10 + PUSHBUTTON "Save...",IDC_SAVE,148,282,50,14 + PUSHBUTTON "Copy",IDC_COPY,202,282,50,14 + DEFPUSHBUTTON "Close",IDOK,256,282,50,14 +END + +IDD_OPTSYMBOLS DIALOGEX 0, 0, 250, 75 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Symbols" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Dbghelp.dll path:",IDC_STATIC,7,9,56,8 + EDITTEXT IDC_DBGHELPPATH,66,8,123,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,193,7,50,14 + LTEXT "Search path:",IDC_STATIC,7,24,42,8 + EDITTEXT IDC_DBGHELPSEARCHPATH,66,23,177,12,ES_AUTOHSCROLL + CONTROL "Undecorate symbols",IDC_UNDECORATESYMBOLS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,38,81,10 +END + +IDD_MEMEDIT DIALOGEX 0, 0, 441, 269 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Memory" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_MEMORY,"PhHexEdit",WS_CLIPSIBLINGS | WS_VSCROLL | WS_TABSTOP,7,7,427,239 + PUSHBUTTON "Re-read",IDC_REREAD,7,248,50,14 + PUSHBUTTON "Write",IDC_WRITE,60,248,50,14 + PUSHBUTTON "Go to...",IDC_GOTO,112,248,50,14 + PUSHBUTTON "Save...",IDC_SAVE,331,248,50,14 + PUSHBUTTON "Close",IDOK,384,248,50,14 + COMBOBOX IDC_BYTESPERROW,164,249,86,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + +IDD_MEMPROTECT DIALOGEX 0, 0, 270, 171 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Memory Protection" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_INTRO,7,7,256,122,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "New value:",IDC_STATIC,120,136,37,8 + EDITTEXT IDC_VALUE,161,135,102,12,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,161,150,50,14 + PUSHBUTTON "Cancel",IDCANCEL,213,150,50,14 +END + +IDD_MEMRESULTS DIALOGEX 0, 0, 313, 266 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Results" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Results.",IDC_INTRO,7,9,299,8 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,7,21,299,221 + PUSHBUTTON "Filter",IDC_FILTER,7,245,50,14 + PUSHBUTTON "Save...",IDC_SAVE,149,245,50,14 + PUSHBUTTON "Copy",IDC_COPY,203,245,50,14 + DEFPUSHBUTTON "Close",IDOK,256,245,50,14 +END + +IDD_MEMSTRING DIALOGEX 0, 0, 241, 86 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "String Search" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Minimum length:",IDC_STATIC,7,8,54,8 + EDITTEXT IDC_MINIMUMLENGTH,67,7,51,12,ES_AUTOHSCROLL + CONTROL "Detect Unicode",IDC_DETECTUNICODE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,22,65,10 + LTEXT "Search in the following types of memory regions:",IDC_STATIC,7,36,157,8 + CONTROL "Private",IDC_PRIVATE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,49,39,10 + CONTROL "Image",IDC_IMAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,55,49,36,10 + CONTROL "Mapped",IDC_MAPPED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,101,49,41,10 + DEFPUSHBUTTON "OK",IDOK,131,65,50,14 + PUSHBUTTON "Cancel",IDCANCEL,184,65,50,14 +END + +IDD_OPTGRAPHS DIALOGEX 0, 0, 250, 156 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Graphs" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Show text",IDC_SHOWTEXT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,49,10 + CONTROL "Use old colors (black background)",IDC_USEOLDCOLORS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,19,123,10 + CONTROL "Show commit charge instead of physical memory in summary view",IDC_SHOWCOMMITINSUMMARY, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,31,227,10 + LTEXT "CPU kernel:",IDC_STATIC,7,48,39,8 + CONTROL "CPU kernel",IDC_CPUKERNEL,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,46,33,13 + LTEXT "CPU user:",IDC_STATIC,7,65,34,8 + CONTROL "CPU user",IDC_CPUUSER,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,63,33,13 + LTEXT "I/O R+O:",IDC_STATIC,7,82,32,8 + CONTROL "I/O R+O",IDC_IORO,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,80,33,13 + LTEXT "I/O W:",IDC_STATIC,7,98,23,8 + CONTROL "I/O W",IDC_IOW,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,97,33,13 + LTEXT "Private bytes:",IDC_STATIC,7,115,46,8 + CONTROL "Private bytes",IDC_PRIVATE,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,113,33,13 + LTEXT "Physical memory:",IDC_STATIC,7,131,56,8 + CONTROL "Physical memory",IDC_PHYSICAL,"PhColorBox",WS_CLIPSIBLINGS | WS_TABSTOP,73,129,33,13 +END + +IDD_PLUGINS DIALOGEX 0, 0, 291, 272 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Plugins" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,277,119 + GROUPBOX "Plugin",IDC_STATIC,7,129,277,118 + LTEXT "Name:",IDC_STATIC,15,141,22,8 + EDITTEXT IDC_NAME,71,141,104,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Internal name:",IDC_STATIC,15,152,49,8 + EDITTEXT IDC_INTERNALNAME,71,152,203,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Author:",IDC_STATIC,15,163,26,8 + EDITTEXT IDC_AUTHOR,71,163,203,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "URL:",IDC_STATIC,15,174,16,8 + EDITTEXT IDC_URL,71,174,174,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + CONTROL "Open",IDC_OPENURL,"SysLink",WS_TABSTOP,250,174,26,10 + LTEXT "File name:",IDC_STATIC,15,185,34,8 + EDITTEXT IDC_FILENAME,71,185,203,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Version:",IDC_STATIC,183,141,27,8 + EDITTEXT IDC_VERSION,215,141,59,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Description:",IDC_STATIC,15,196,39,8 + EDITTEXT IDC_DESCRIPTION,14,208,211,34,ES_MULTILINE | ES_READONLY + PUSHBUTTON "Disable",IDC_DISABLE,230,211,50,14 + PUSHBUTTON "Options...",IDC_OPTIONS,230,228,50,14 + PUSHBUTTON "Clean up",IDC_CLEANUP,7,251,50,14 + LTEXT "Changes may require a restart to take effect.",IDC_INSTRUCTION,82,254,148,8,NOT WS_VISIBLE + DEFPUSHBUTTON "Close",IDOK,234,251,50,14 +END + +IDD_HANDLESTATS DIALOGEX 0, 0, 219, 175 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Handle Statistics" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,205,143 + DEFPUSHBUTTON "Close",IDOK,162,154,50,14 +END + +IDD_PROCRECORD DIALOGEX 0, 0, 260, 202 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Process Record" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Name:",IDC_STATIC,7,9,22,8 + EDITTEXT IDC_PROCESSNAME,39,9,214,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Parent:",IDC_STATIC,7,21,25,8 + EDITTEXT IDC_PARENT,39,21,214,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + GROUPBOX "File",IDC_FILE,7,38,246,79 + ICON "",IDC_FILEICON,14,49,20,20 + EDITTEXT IDC_NAME,44,48,182,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_COMPANYNAME,44,60,183,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Version:",IDC_STATIC,15,72,27,8 + EDITTEXT IDC_VERSION,44,72,183,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Image file name:",IDC_STATIC,15,86,56,8 + EDITTEXT IDC_FILENAME,15,96,211,12,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "Open",IDC_OPENFILENAME,230,95,17,15,BS_ICON + LTEXT "Command line:",IDC_STATIC,7,123,50,8 + EDITTEXT IDC_CMDLINE,65,121,188,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Started:",IDC_STATIC,7,139,28,8 + EDITTEXT IDC_STARTED,65,137,188,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Terminated:",IDC_STATIC,7,155,40,8 + EDITTEXT IDC_TERMINATED,65,153,188,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Session ID:",IDC_STATIC,7,170,37,8 + LTEXT "Static",IDC_SESSIONID,50,170,19,8 + PUSHBUTTON "Properties",IDC_PROPERTIES,150,181,50,14 + DEFPUSHBUTTON "Close",IDOK,203,181,50,14 +END + +IDD_CHOOSEPROCESS DIALOGEX 0, 0, 317, 239 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Select a Process" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Select a process from the list below.",IDC_MESSAGE,7,7,303,8,SS_ENDELLIPSIS + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,21,303,193 + PUSHBUTTON "Refresh",IDC_REFRESH,7,218,50,14 + DEFPUSHBUTTON "OK",IDOK,205,218,50,14 + PUSHBUTTON "Cancel",IDCANCEL,260,218,50,14 +END + +IDD_PROCSERVICES DIALOGEX 0, 0, 260, 261 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Services" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Services",IDC_SERVICES_LAYOUT,7,7,246,247,NOT WS_VISIBLE | WS_BORDER +END + +IDD_SHADOWSESSION DIALOGEX 0, 0, 228, 84 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Remote Control" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "To end the remote control session, press this key and the modifiers selected below:",IDC_STATIC,7,7,214,19 + COMBOBOX IDC_VIRTUALKEY,7,27,74,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Shift",IDC_SHIFT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,47,31,10 + CONTROL "Ctrl",IDC_CTRL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,49,47,27,10 + CONTROL "Alt",IDC_ALT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,89,47,25,10 + DEFPUSHBUTTON "OK",IDOK,117,63,50,14 + PUSHBUTTON "Cancel",IDCANCEL,171,63,50,14 +END + +IDD_TOKCAPABILITIES DIALOGEX 0, 0, 270, 228 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Capabilities" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,256,214 +END + +IDD_TOKATTRIBUTES DIALOGEX 0, 0, 270, 228 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Attributes" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0x32,7,7,256,214,WS_EX_CLIENTEDGE +END + +IDD_SYSINFO_CPU DIALOGEX 0, 0, 316, 195 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "CPU" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + RTEXT "CPU name",IDC_CPUNAME,41,4,274,16,SS_WORDELLIPSIS + LTEXT "CPU",IDC_TITLE,0,0,37,21 + LTEXT "Panel layout",IDC_LAYOUT,0,105,315,88,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,82,NOT WS_VISIBLE +END + +IDD_SYSINFO_CPUPANEL DIALOGEX 0, 0, 237, 86 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Utilization:",IDC_STATIC,0,3,34,8 + LTEXT "Speed:",IDC_STATIC,108,3,24,8 + LTEXT "Static",IDC_UTILIZATION,40,0,62,15 + LTEXT "Static",IDC_SPEED,137,0,100,15 + GROUPBOX "System",IDC_STATIC,0,17,97,55 + LTEXT "Processes",IDC_STATIC,7,28,33,8 + LTEXT "Threads",IDC_STATIC,7,38,27,8 + LTEXT "Handles",IDC_STATIC,7,48,26,8 + LTEXT "Uptime",IDC_STATIC,7,58,23,8 + RTEXT "Static",IDC_ZPROCESSES_V,45,28,46,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZTHREADS_V,45,38,46,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZHANDLES_V,45,48,46,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZUPTIME_V,46,58,45,8,SS_ENDELLIPSIS + CONTROL "&Show one graph per CPU",IDC_ONEGRAPHPERCPU,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,0,76,96,10 + GROUPBOX "CPU",IDC_STATIC,101,17,136,55 + LTEXT "Context switches delta",IDC_STATIC,109,28,76,8 + LTEXT "Interrupts delta",IDC_STATIC,109,38,52,8 + LTEXT "DPCs delta",IDC_STATIC,109,48,36,8 + LTEXT "System calls delta",IDC_STATIC,109,58,60,8 + RTEXT "Static",IDC_ZCONTEXTSWITCHESDELTA_V,191,28,40,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZINTERRUPTSDELTA_V,185,39,46,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZDPCSDELTA_V,181,48,50,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSYSTEMCALLSDELTA_V,179,59,52,8,SS_ENDELLIPSIS +END + +IDD_SYSINFO_MEMPANEL DIALOGEX 0, 0, 358, 171 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Commit charge",IDC_STATIC,0,0,108,44 + LTEXT "Current",IDC_STATIC,7,11,26,8 + LTEXT "Peak",IDC_STATIC,7,21,16,8 + LTEXT "Limit",IDC_STATIC,7,31,15,8 + RTEXT "Static",IDC_ZCOMMITCURRENT_V,45,11,56,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCOMMITPEAK_V,41,21,60,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCOMMITLIMIT_V,40,31,61,8,SS_ENDELLIPSIS + GROUPBOX "Physical memory",IDC_STATIC,112,0,118,73 + LTEXT "Current",IDC_STATIC,120,11,26,8 + LTEXT "Cache WS",IDC_STATIC,120,41,34,8 + RTEXT "Static",IDC_ZPHYSICALCURRENT_V,165,11,57,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPHYSICALTOTAL_V,167,21,55,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPHYSICALCACHEWS_V,159,41,63,8,SS_ENDELLIPSIS + LTEXT "Total",IDC_STATIC,120,21,17,8 + LTEXT "Kernel WS",IDC_STATIC,120,51,34,8 + RTEXT "Static",IDC_ZPHYSICALKERNELWS_V,167,51,55,8,SS_ENDELLIPSIS + LTEXT "Driver WS",IDC_STATIC,120,61,33,8 + RTEXT "Static",IDC_ZPHYSICALDRIVERWS_V,163,61,59,8,SS_ENDELLIPSIS + GROUPBOX "Paged pool",IDC_STATIC,0,47,108,64 + LTEXT "Working set",IDC_STATIC,7,58,40,8 + LTEXT "Limit",IDC_STATIC,7,78,15,8 + RTEXT "Static",IDC_ZPAGEDWORKINGSET_V,54,58,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEDVIRTUALSIZE_V,52,68,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEDLIMIT_V,48,78,53,8,SS_ENDELLIPSIS + LTEXT "Virtual size",IDC_STATIC,7,68,36,8 + LTEXT "Allocs delta",IDC_STATIC,7,88,38,8 + RTEXT "Static",IDC_ZPAGEDALLOCSDELTA_V,53,88,48,8,SS_ENDELLIPSIS + LTEXT "Frees delta",IDC_STATIC,7,98,38,8 + RTEXT "Static",IDC_ZPAGEDFREESDELTA_V,51,98,50,8,SS_ENDELLIPSIS + GROUPBOX "Non-paged pool",IDC_STATIC,0,114,108,55 + LTEXT "Usage",IDC_STATIC,7,125,21,8 + LTEXT "Allocs delta",IDC_STATIC,7,145,38,8 + RTEXT "Static",IDC_ZNONPAGEDUSAGE_V,54,125,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZNONPAGEDLIMIT_V,52,135,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZNONPAGEDALLOCSDELTA_V,56,145,45,8,SS_ENDELLIPSIS + LTEXT "Limit",IDC_STATIC,7,135,15,8 + LTEXT "Frees delta",IDC_STATIC,7,155,38,8 + RTEXT "Static",IDC_ZNONPAGEDFREESDELTA_V,56,155,45,8,SS_ENDELLIPSIS + GROUPBOX "Memory lists",IDC_STATIC,234,0,123,169 + LTEXT "Zeroed",IDC_STATIC,242,11,24,8 + LTEXT "Modified",IDC_STATIC,242,31,28,8 + RTEXT "Static",IDC_ZLISTZEROED_V,304,11,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTFREE_V,302,21,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTMODIFIED_V,298,31,53,8,SS_ENDELLIPSIS + LTEXT "Free",IDC_STATIC,242,21,16,8 + LTEXT "Modified no-write",IDC_STATIC,242,41,56,8 + RTEXT "Static",IDC_ZLISTMODIFIEDNOWRITE_V,303,41,48,8,SS_ENDELLIPSIS + LTEXT "Standby",IDC_STATIC,242,61,28,8 + LTEXT "Priority 0",IDC_STATIC,251,71,30,8 + LTEXT "Priority 1",IDC_STATIC,251,80,30,8 + LTEXT "Priority 2",IDC_STATIC,251,90,30,8 + LTEXT "Priority 3",IDC_STATIC,251,100,30,8 + LTEXT "Priority 4",IDC_STATIC,251,110,30,8 + LTEXT "Priority 5",IDC_STATIC,251,120,30,8 + LTEXT "Priority 6",IDC_STATIC,251,130,30,8 + LTEXT "Priority 7",IDC_STATIC,251,140,30,8 + RTEXT "Static",IDC_ZLISTSTANDBY_V,303,61,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY0_V,303,71,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY1_V,303,80,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY2_V,303,90,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY3_V,303,100,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY4_V,303,110,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY5_V,303,120,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY6_V,303,130,48,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY7_V,303,140,48,8,SS_ENDELLIPSIS + GROUPBOX "Paging",IDC_STATIC,112,75,118,55 + LTEXT "Page faults delta",IDC_STATIC,120,86,57,8 + LTEXT "Pagefile writes delta",IDC_STATIC,120,106,68,8 + RTEXT "Static",IDC_ZPAGINGPAGEFAULTSDELTA_V,183,86,39,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGINGPAGEREADSDELTA_V,179,96,43,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGINGPAGEFILEWRITESDELTA_V,189,106,33,8,SS_ENDELLIPSIS + LTEXT "Page reads delta",IDC_STATIC,120,96,58,8 + LTEXT "Mapped writes delta",IDC_STATIC,120,116,68,8 + RTEXT "Static",IDC_ZPAGINGMAPPEDWRITESDELTA_V,191,116,31,8,SS_ENDELLIPSIS + LTEXT "Modified pagefile",IDC_STATIC,242,51,55,8 + RTEXT "Static",IDC_ZLISTMODIFIEDPAGEFILE_V,303,51,48,8,SS_ENDELLIPSIS + PUSHBUTTON "&More",IDC_MORE,242,152,50,14 + RTEXT "Static",IDC_ZPHYSICALRESERVED_V,167,31,55,8,SS_ENDELLIPSIS + LTEXT "Reserved",IDC_STATIC,120,31,32,8 +END + +IDD_SYSINFO_MEM DIALOGEX 0, 0, 316, 250 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Memory" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Memory",IDC_TITLE,0,0,110,21 + LTEXT "Panel layout",IDC_LAYOUT,0,74,315,175,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,51,NOT WS_VISIBLE + RTEXT "Total physical",IDC_TOTALPHYSICAL,127,4,188,16,SS_WORDELLIPSIS + LTEXT "Commit charge:",IDC_COMMIT_L,111,1,52,8 + LTEXT "Physical memory:",IDC_PHYSICAL_L,111,7,56,8 +END + +IDD_SYSINFO_IO DIALOGEX 0, 0, 316, 187 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CAPTION | WS_SYSMENU +CAPTION "I/O" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "I/O",IDC_TITLE,0,0,110,21 + LTEXT "Panel layout",IDC_LAYOUT,0,109,315,78,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,84,NOT WS_VISIBLE +END + +IDD_SYSINFO_IOPANEL DIALOGEX 0, 0, 276, 75 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "I/O deltas",IDC_STATIC,0,0,133,74 + LTEXT "Reads delta",IDC_STATIC,7,11,40,8 + LTEXT "Read bytes delta",IDC_STATIC,7,21,56,8 + LTEXT "Writes delta",IDC_STATIC,7,31,40,8 + LTEXT "Write bytes delta",IDC_STATIC,7,41,57,8 + RTEXT "Static",IDC_ZREADSDELTA_V,59,11,67,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZREADBYTESDELTA_V,75,21,51,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITESDELTA_V,57,31,69,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITEBYTESDELTA_V,73,41,53,8,SS_ENDELLIPSIS + LTEXT "Other delta",IDC_STATIC,7,51,38,8 + LTEXT "Other bytes delta",IDC_STATIC,7,61,58,8 + RTEXT "Static",IDC_ZOTHERDELTA_V,67,51,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZOTHERBYTESDELTA_V,81,61,45,8,SS_ENDELLIPSIS + GROUPBOX "I/O totals",IDC_STATIC,137,0,133,74 + LTEXT "Reads",IDC_STATIC,145,11,21,8 + LTEXT "Read bytes",IDC_STATIC,145,21,38,8 + LTEXT "Writes",IDC_STATIC,145,31,22,8 + LTEXT "Write bytes",IDC_STATIC,145,41,38,8 + RTEXT "Static",IDC_ZREADS_V,197,11,67,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZREADBYTES_V,192,21,72,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITES_V,195,31,69,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITEBYTES_V,190,41,74,8,SS_ENDELLIPSIS + LTEXT "Other",IDC_STATIC,145,51,20,8 + LTEXT "Other bytes",IDC_STATIC,145,61,40,8 + RTEXT "Static",IDC_ZOTHER_V,191,51,73,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZOTHERBYTES_V,192,61,72,8,SS_ENDELLIPSIS +END + +IDD_MEMLISTS DIALOGEX 0, 0, 228, 195 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST +CAPTION "Memory Lists" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDOK,171,174,50,14 + LTEXT "Zeroed",IDC_STATIC,7,7,24,8 + LTEXT "Free",IDC_STATIC,7,17,16,8 + LTEXT "Modified",IDC_STATIC,7,28,28,8 + LTEXT "Modified no-write",IDC_STATIC,7,39,56,8 + LTEXT "Bad",IDC_STATIC,7,60,13,8 + LTEXT "Standby",IDC_STATIC,7,72,28,8 + LTEXT "Priority 0",IDC_STATIC,23,83,30,8 + LTEXT "Priority 1",IDC_STATIC,23,94,30,8 + LTEXT "Priority 2",IDC_STATIC,23,105,30,8 + LTEXT "Priority 3",IDC_STATIC,23,116,30,8 + LTEXT "Priority 4",IDC_STATIC,23,128,30,8 + LTEXT "Priority 5",IDC_STATIC,23,139,30,8 + LTEXT "Priority 6",IDC_STATIC,23,150,30,8 + LTEXT "Priority 7",IDC_STATIC,23,161,30,8 + RTEXT "Static",IDC_ZLISTZEROED_V,73,7,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTFREE_V,73,17,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTMODIFIED_V,73,28,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTMODIFIEDNOWRITE_V,73,39,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTBAD_V,73,60,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY_V,73,72,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY0_V,73,83,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY1_V,73,94,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY2_V,73,105,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY3_V,73,116,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY4_V,73,128,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY5_V,73,139,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY6_V,73,150,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTSTANDBY7_V,73,161,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED_V,162,72,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED0_V,162,84,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED1_V,162,95,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED2_V,162,106,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED3_V,162,117,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED4_V,162,128,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED5_V,162,139,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED6_V,162,150,59,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZLISTREPURPOSED7_V,162,161,59,8,SS_ENDELLIPSIS + RTEXT "(Repurposed)",IDC_STATIC,162,60,59,8 + PUSHBUTTON "&Empty",IDC_EMPTY,117,174,50,14 + LTEXT "Modified pagefile",IDC_STATIC,7,49,55,8 + RTEXT "Static",IDC_ZLISTMODIFIEDPAGEFILE_V,73,49,59,8,SS_ENDELLIPSIS +END + +IDD_CONTAINER DIALOGEX 0, 0, 316, 182 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN +END + +IDD_SYSINFO_MEMPANELXP DIALOGEX 0, 0, 230, 171 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Commit charge",-1,0,0,108,44 + LTEXT "Current",-1,7,11,26,8 + LTEXT "Peak",-1,7,21,16,8 + LTEXT "Limit",-1,7,31,15,8 + RTEXT "Static",IDC_ZCOMMITCURRENT_V,45,11,56,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCOMMITPEAK_V,41,21,60,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCOMMITLIMIT_V,40,31,61,8,SS_ENDELLIPSIS + GROUPBOX "Physical memory",-1,112,0,118,64 + LTEXT "Current",-1,120,11,26,8 + LTEXT "Cache WS",-1,120,31,34,8 + RTEXT "Static",IDC_ZPHYSICALCURRENT_V,165,11,57,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPHYSICALTOTAL_V,167,21,55,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPHYSICALCACHEWS_V,159,31,63,8,SS_ENDELLIPSIS + LTEXT "Total",-1,120,21,17,8 + LTEXT "Kernel WS",-1,120,41,34,8 + RTEXT "Static",IDC_ZPHYSICALKERNELWS_V,167,41,55,8,SS_ENDELLIPSIS + LTEXT "Driver WS",-1,120,51,33,8 + RTEXT "Static",IDC_ZPHYSICALDRIVERWS_V,163,51,59,8,SS_ENDELLIPSIS + GROUPBOX "Paged pool",-1,0,47,108,64 + LTEXT "Working set",-1,7,58,40,8 + LTEXT "Limit",-1,7,78,15,8 + RTEXT "Static",IDC_ZPAGEDWORKINGSET_V,54,58,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEDVIRTUALSIZE_V,52,68,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGEDLIMIT_V,48,78,53,8,SS_ENDELLIPSIS + LTEXT "Virtual size",-1,7,68,36,8 + LTEXT "Allocs delta",-1,7,88,38,8 + RTEXT "Static",IDC_ZPAGEDALLOCSDELTA_V,53,88,48,8,SS_ENDELLIPSIS + LTEXT "Frees delta",-1,7,98,38,8 + RTEXT "Static",IDC_ZPAGEDFREESDELTA_V,51,98,50,8,SS_ENDELLIPSIS + GROUPBOX "Non-paged pool",-1,0,114,108,55 + LTEXT "Usage",-1,7,125,21,8 + LTEXT "Allocs delta",-1,7,145,38,8 + RTEXT "Static",IDC_ZNONPAGEDUSAGE_V,54,125,47,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZNONPAGEDLIMIT_V,52,135,49,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZNONPAGEDALLOCSDELTA_V,56,145,45,8,SS_ENDELLIPSIS + LTEXT "Limit",-1,7,135,15,8 + LTEXT "Frees delta",-1,7,155,38,8 + RTEXT "Static",IDC_ZNONPAGEDFREESDELTA_V,56,155,45,8,SS_ENDELLIPSIS + GROUPBOX "Paging",-1,112,67,118,55 + LTEXT "Page faults delta",-1,120,78,57,8 + LTEXT "Pagefile writes delta",-1,120,98,68,8 + RTEXT "Static",IDC_ZPAGINGPAGEFAULTSDELTA_V,183,78,39,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGINGPAGEREADSDELTA_V,179,88,43,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZPAGINGPAGEFILEWRITESDELTA_V,189,98,33,8,SS_ENDELLIPSIS + LTEXT "Page reads delta",-1,120,88,58,8 + LTEXT "Mapped writes delta",-1,120,108,68,8 + RTEXT "Static",IDC_ZPAGINGMAPPEDWRITESDELTA_V,191,108,31,8,SS_ENDELLIPSIS +END + +IDD_MINIINFO DIALOGEX 0, 0, 217, 150 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Content layout",IDC_LAYOUT,4,3,210,128,NOT WS_VISIBLE + CONTROL "Section",IDC_SECTION,"Static",SS_LEFTNOWORDWRAP | SS_NOTIFY | SS_ENDELLIPSIS | WS_GROUP,4,134,177,13 + PUSHBUTTON "Options",IDC_OPTIONS,183,133,15,14,BS_ICON + CONTROL "Pin",IDC_PIN,"Button",BS_AUTOCHECKBOX | BS_ICON | BS_PUSHLIKE | WS_TABSTOP,199,133,15,14 +END + +IDD_MINIINFO_LIST DIALOGEX 0, 0, 217, 141 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "CPU" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP | 0x82,0,0,217,140,WS_EX_CLIENTEDGE +END + +IDD_MITIGATION DIALOGEX 0, 0, 277, 217 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Mitigation Policies" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,263,108 + LTEXT "Description:",IDC_DESCRIPTIONLABEL,7,118,38,8 + EDITTEXT IDC_DESCRIPTION,7,129,263,62,ES_MULTILINE | ES_READONLY | WS_VSCROLL + DEFPUSHBUTTON "OK",IDOK,220,196,50,14 +END + +IDD_EDITENV DIALOGEX 0, 0, 311, 177 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Edit Environment Variable" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Name:",IDC_STATIC,7,8,21,8 + EDITTEXT IDC_NAME,38,7,266,12,ES_AUTOHSCROLL + LTEXT "Value:",IDC_STATIC,7,25,21,8 + EDITTEXT IDC_VALUE,38,24,266,128,ES_MULTILINE | WS_VSCROLL + DEFPUSHBUTTON "OK",IDOK,200,156,50,14 + PUSHBUTTON "Cancel",IDCANCEL,254,156,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROCGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCMODULES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCTHREADS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCHANDLES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCENVIRONMENT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_THRDSTACK, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 254 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + IDD_ABOUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 263 + TOPMARGIN, 7 + BOTTOMMARGIN, 188 + END + + IDD_SRVLIST, DIALOG + BEGIN + END + + IDD_SRVGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_HNDLGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 174 + END + + IDD_INFORMATION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 310 + TOPMARGIN, 7 + BOTTOMMARGIN, 177 + END + + IDD_FINDOBJECTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 350 + TOPMARGIN, 7 + BOTTOMMARGIN, 226 + END + + IDD_OBJTOKEN, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_HIDDENPROCESSES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 330 + TOPMARGIN, 7 + BOTTOMMARGIN, 214 + END + + IDD_RUNAS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 271 + TOPMARGIN, 7 + BOTTOMMARGIN, 120 + END + + IDD_PROGRESS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 209 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END + + IDD_PAGEFILES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 315 + TOPMARGIN, 7 + BOTTOMMARGIN, 155 + END + + IDD_TOKGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 263 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + IDD_TOKADVANCED, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 229 + TOPMARGIN, 7 + BOTTOMMARGIN, 179 + END + + IDD_OBJJOB, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_OBJEVENT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_OBJMUTANT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_OBJSEMAPHORE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_OBJTIMER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_JOBSTATISTICS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 252 + TOPMARGIN, 7 + BOTTOMMARGIN, 179 + END + + IDD_OBJEVENTPAIR, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_OBJSECTION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_AFFINITY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 272 + TOPMARGIN, 7 + BOTTOMMARGIN, 220 + END + + IDD_SYSINFO, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 416 + TOPMARGIN, 7 + BOTTOMMARGIN, 240 + END + + IDD_EDITMESSAGE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 156 + END + + IDD_SESSION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 194 + TOPMARGIN, 7 + BOTTOMMARGIN, 142 + END + + IDD_PROCMEMORY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_CHOOSE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 192 + TOPMARGIN, 7 + BOTTOMMARGIN, 66 + END + + IDD_OPTGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 147 + END + + IDD_OPTHIGHLIGHTING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 167 + END + + IDD_CHOOSECOLUMNS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 374 + TOPMARGIN, 7 + BOTTOMMARGIN, 200 + END + + IDD_NETSTACK, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 254 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + IDD_CREATESERVICE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 280 + TOPMARGIN, 7 + BOTTOMMARGIN, 126 + END + + IDD_PROCPERFORMANCE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCSTATISTICS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_OPTADVANCED, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 143 + END + + IDD_GDIHANDLES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 344 + TOPMARGIN, 7 + BOTTOMMARGIN, 300 + END + + IDD_LOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 306 + TOPMARGIN, 7 + BOTTOMMARGIN, 296 + END + + IDD_OPTSYMBOLS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 68 + END + + IDD_MEMEDIT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 434 + TOPMARGIN, 7 + BOTTOMMARGIN, 262 + END + + IDD_MEMPROTECT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 263 + TOPMARGIN, 7 + BOTTOMMARGIN, 164 + END + + IDD_MEMRESULTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 306 + TOPMARGIN, 7 + BOTTOMMARGIN, 259 + END + + IDD_MEMSTRING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 234 + TOPMARGIN, 7 + BOTTOMMARGIN, 79 + END + + IDD_OPTGRAPHS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 243 + TOPMARGIN, 7 + BOTTOMMARGIN, 149 + END + + IDD_PLUGINS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 284 + TOPMARGIN, 7 + BOTTOMMARGIN, 265 + END + + IDD_HANDLESTATS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 212 + TOPMARGIN, 7 + BOTTOMMARGIN, 168 + END + + IDD_PROCRECORD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 195 + END + + IDD_CHOOSEPROCESS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 310 + TOPMARGIN, 7 + BOTTOMMARGIN, 232 + END + + IDD_PROCSERVICES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 254 + END + + IDD_SHADOWSESSION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 221 + TOPMARGIN, 7 + BOTTOMMARGIN, 77 + END + + IDD_TOKCAPABILITIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 263 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + IDD_TOKATTRIBUTES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 263 + TOPMARGIN, 7 + BOTTOMMARGIN, 221 + END + + IDD_SYSINFO_CPU, DIALOG + BEGIN + BOTTOMMARGIN, 193 + END + + IDD_SYSINFO_CPUPANEL, DIALOG + BEGIN + BOTTOMMARGIN, 85 + END + + IDD_SYSINFO_MEMPANEL, DIALOG + BEGIN + END + + IDD_SYSINFO_MEM, DIALOG + BEGIN + BOTTOMMARGIN, 247 + END + + IDD_SYSINFO_IO, DIALOG + BEGIN + END + + IDD_SYSINFO_IOPANEL, DIALOG + BEGIN + BOTTOMMARGIN, 74 + END + + IDD_MEMLISTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 221 + TOPMARGIN, 7 + BOTTOMMARGIN, 188 + END + + IDD_CONTAINER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 175 + END + + IDD_SYSINFO_MEMPANELXP, DIALOG + BEGIN + END + + IDD_MINIINFO, DIALOG + BEGIN + LEFTMARGIN, 4 + RIGHTMARGIN, 214 + TOPMARGIN, 3 + BOTTOMMARGIN, 147 + END + + IDD_MINIINFO_LIST, DIALOG + BEGIN + END + + IDD_MITIGATION, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 270 + TOPMARGIN, 7 + BOTTOMMARGIN, 210 + END + + IDD_EDITENV, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 304 + TOPMARGIN, 7 + BOTTOMMARGIN, 170 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_MAINWND_ACCEL ACCELERATORS +BEGIN + VK_ESCAPE, ID_ESC_EXIT, VIRTKEY, NOINVERT + "F", ID_HACKER_FINDHANDLESORDLLS, VIRTKEY, CONTROL, NOINVERT + "R", ID_HACKER_RUN, VIRTKEY, CONTROL, NOINVERT + "R", ID_HACKER_RUNAS, VIRTKEY, SHIFT, CONTROL, NOINVERT + "S", ID_HACKER_SAVE, VIRTKEY, CONTROL, NOINVERT + "L", ID_HELP_LOG, VIRTKEY, CONTROL, NOINVERT + "M", ID_PROCESS_SEARCHONLINE, VIRTKEY, CONTROL, NOINVERT + VK_TAB, ID_TAB_NEXT, VIRTKEY, CONTROL, NOINVERT + VK_TAB, ID_TAB_PREV, VIRTKEY, SHIFT, CONTROL, NOINVERT + VK_F5, ID_VIEW_REFRESH, VIRTKEY, NOINVERT + "I", ID_VIEW_SYSTEMINFORMATION, VIRTKEY, CONTROL, NOINVERT + VK_PAUSE, ID_VIEW_UPDATEAUTOMATICALLY, VIRTKEY, NOINVERT + VK_F6, ID_VIEW_UPDATEAUTOMATICALLY, VIRTKEY, NOINVERT +END + +IDR_SYSINFO_ACCEL ACCELERATORS +BEGIN + "1", ID_DIGIT1, VIRTKEY, NOINVERT + "2", ID_DIGIT2, VIRTKEY, NOINVERT + "3", ID_DIGIT3, VIRTKEY, NOINVERT + "4", ID_DIGIT4, VIRTKEY, NOINVERT + "5", ID_DIGIT5, VIRTKEY, NOINVERT + "6", ID_DIGIT6, VIRTKEY, NOINVERT + "7", ID_DIGIT7, VIRTKEY, NOINVERT + "8", ID_DIGIT8, VIRTKEY, NOINVERT + "9", ID_DIGIT9, VIRTKEY, NOINVERT + "B", IDC_BACK, VIRTKEY, ALT, NOINVERT + VK_BACK, IDC_BACK, VIRTKEY, NOINVERT + VK_PAUSE, IDC_PAUSE, VIRTKEY, NOINVERT + VK_F5, IDC_REFRESH, VIRTKEY, NOINVERT + VK_F6, IDC_PAUSE, VIRTKEY, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_CROSS BITMAP "resources\\cross.bmp" + +IDB_TICK BITMAP "resources\\tick.bmp" + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +IDR_RT_MANIFEST RT_MANIFEST "ProcessHacker.manifest" + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_FINDOBJECTS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OPTADVANCED AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_SYSINFO_MEMPANEL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_ABOUT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCGENERAL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_MITIGATION AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_INFORMATION AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OPTGENERAL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_MINIINFO AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCTHREADS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCRECORD AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCENVIRONMENT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_EDITENV AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/ProcessHacker/ProcessHacker.vcxproj b/ProcessHacker/ProcessHacker.vcxproj new file mode 100644 index 0000000..2a9de4c --- /dev/null +++ b/ProcessHacker/ProcessHacker.vcxproj @@ -0,0 +1,421 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {0271DD27-6707-4290-8DFE-285702B7115D} + ProcessHacker + Win32Proj + 10.0.10586.0 + + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + false + false + false + false + + + + Disabled + ..\phnt\include;..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_PHAPP_;_WINDOWS;WIN32;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;winsta.lib;comctl32.lib;version.lib;uxtheme.lib;%(AdditionalDependencies) + true + Windows + MachineX86 + 5.01 + ..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + ProcessHacker.def + + + update_rev.bat + + + Generating revision number... + + + + + Disabled + ..\phnt\include;..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_PHAPP_;_WINDOWS;WIN64;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;winsta.lib;comctl32.lib;version.lib;uxtheme.lib;%(AdditionalDependencies) + true + Windows + MachineX64 + 5.02 + ..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + ProcessHacker.def + + + update_rev.bat + + + Generating revision number... + + + + + MaxSpeed + true + ..\phnt\include;..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_PHAPP_;_WINDOWS;WIN32;NDEBUG;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + Level3 + ProgramDatabase + StdCall + true + true + StreamingSIMDExtensions + + + phlib.lib;ntdll.lib;winsta.lib;comctl32.lib;version.lib;uxtheme.lib;%(AdditionalDependencies) + true + Windows + true + true + MachineX86 + true + 5.01 + ..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + ProcessHacker.def + + + update_rev.bat + + + Generating revision number... + + + + + MaxSpeed + true + ..\phnt\include;..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_PHAPP_;_WINDOWS;WIN64;NDEBUG;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;winsta.lib;comctl32.lib;version.lib;uxtheme.lib;%(AdditionalDependencies) + true + Windows + true + true + MachineX64 + true + 5.02 + ..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + ProcessHacker.def + + + update_rev.bat + + + Generating revision number... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + \ No newline at end of file diff --git a/ProcessHacker/ProcessHacker.vcxproj.filters b/ProcessHacker/ProcessHacker.vcxproj.filters new file mode 100644 index 0000000..6b42c5f --- /dev/null +++ b/ProcessHacker/ProcessHacker.vcxproj.filters @@ -0,0 +1,620 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {3fa0b151-12c1-4832-94fb-e16c52584b65} + + + {eaabb14f-9091-4b18-8764-45c8350f4887} + + + {84a3be0b-42cf-42ae-bcd3-16f165caa0db} + + + {e72b6c69-b510-4578-9ed5-34a16d8dffc1} + + + {c4b014d0-6bd0-4848-a23f-e639563d998e} + + + {67c40321-0b28-4c16-8c48-dc0b64c148d7} + + + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Mini-XML + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + phsvc + + + phsvc + + + phsvc + + + phsvc + + + phsvc + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + PCRE + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + Process Hacker + + + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + PCRE\Headers + + + PCRE\Headers + + + PCRE\Headers + + + PCRE\Headers + + + PCRE\Headers + + + PCRE\Headers + + + Mini-XML\Headers + + + Mini-XML\Headers + + + Mini-XML\Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + Headers + + + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Module + + + + + Resources + + + Resources + + + + + Resources + + + + + Resources + + + Resources + + + Resources + + + Resources + + + \ No newline at end of file diff --git a/ProcessHacker/about.c b/ProcessHacker/about.c new file mode 100644 index 0000000..ff1095f --- /dev/null +++ b/ProcessHacker/about.c @@ -0,0 +1,184 @@ +/* + * Process Hacker - + * about dialog + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static INT_PTR CALLBACK PhpAboutDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_STRING appName; + +#if (PHAPP_VERSION_REVISION != 0) + appName = PhFormatString( + L"Process Hacker %u.%u.%u", + PHAPP_VERSION_MAJOR, + PHAPP_VERSION_MINOR, + PHAPP_VERSION_REVISION + ); +#else + appName = PhFormatString( + L"Process Hacker %u.%u", + PHAPP_VERSION_MAJOR, + PHAPP_VERSION_MINOR + ); +#endif + + SetDlgItemText(hwndDlg, IDC_ABOUT_NAME, appName->Buffer); + PhDereferenceObject(appName); + + SetDlgItemText(hwndDlg, IDC_CREDITS, + L" Installer by XhmikosR\n" + L"Thanks to:\n" + L" dmex\n" + L" Donors - thank you for your support!\n" + L" Sysinternals Forums\n" + L" ReactOS\n" + L"Process Hacker uses the following components:\n" + L" Mini-XML by Michael Sweet\n" + L" PCRE\n" + L" MD5 code by Jouni Malinen\n" + L" SHA1 code by Filip Navara, based on code by Steve Reid\n" + L" Silk icons\n" + L" Farm-fresh web icons\n" + ); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_DIAGNOSTICS: + { + PhShowInformationDialog(hwndDlg, PH_AUTO_T(PH_STRING, PhGetDiagnosticsString())->Buffer, 0); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case NM_CLICK: + { + switch (header->idFrom) + { + case IDC_CREDITS: + case IDC_LINK_SF: + PhShellExecute(hwndDlg, ((PNMLINK)header)->item.szUrl, NULL); + break; + } + } + break; + } + } + break; + } + + return FALSE; +} + +VOID PhShowAboutDialog( + _In_ HWND ParentWindowHandle + ) +{ + DialogBox( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_ABOUT), + ParentWindowHandle, + PhpAboutDlgProc + ); +} + +FORCEINLINE ULONG PhpGetObjectTypeObjectCount( + _In_ PPH_OBJECT_TYPE ObjectType + ) +{ + PH_OBJECT_TYPE_INFORMATION info; + + PhGetObjectTypeInformation(ObjectType, &info); + + return info.NumberOfObjects; +} + +PPH_STRING PhGetDiagnosticsString( + VOID + ) +{ + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 50); + + PhAppendFormatStringBuilder(&stringBuilder, L"OBJECT INFORMATION\r\n"); + +#define OBJECT_TYPE_COUNT(Type) PhAppendFormatStringBuilder(&stringBuilder, \ + L#Type L": %u objects\r\n", PhpGetObjectTypeObjectCount(Type)) + + // ref + OBJECT_TYPE_COUNT(PhObjectTypeObject); + + // basesup + OBJECT_TYPE_COUNT(PhStringType); + OBJECT_TYPE_COUNT(PhBytesType); + OBJECT_TYPE_COUNT(PhListType); + OBJECT_TYPE_COUNT(PhPointerListType); + OBJECT_TYPE_COUNT(PhHashtableType); + OBJECT_TYPE_COUNT(PhFileStreamType); + + // ph + OBJECT_TYPE_COUNT(PhSymbolProviderType); + OBJECT_TYPE_COUNT(PhProcessItemType); + OBJECT_TYPE_COUNT(PhServiceItemType); + OBJECT_TYPE_COUNT(PhNetworkItemType); + OBJECT_TYPE_COUNT(PhModuleProviderType); + OBJECT_TYPE_COUNT(PhModuleItemType); + OBJECT_TYPE_COUNT(PhThreadProviderType); + OBJECT_TYPE_COUNT(PhThreadItemType); + OBJECT_TYPE_COUNT(PhHandleProviderType); + OBJECT_TYPE_COUNT(PhHandleItemType); + OBJECT_TYPE_COUNT(PhMemoryItemType); + + return PhFinalStringBuilderString(&stringBuilder); +} diff --git a/ProcessHacker/actions.c b/ProcessHacker/actions.c new file mode 100644 index 0000000..ed29897 --- /dev/null +++ b/ProcessHacker/actions.c @@ -0,0 +1,3126 @@ +/* + * Process Hacker - + * UI actions + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * These are a set of consistent functions which will perform actions on objects such as processes, + * threads and services, while displaying any necessary prompts and error messages. Automatic + * elevation can also easily be added if necessary. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef DWORD (WINAPI *_SetTcpEntry)( + _In_ PMIB_TCPROW pTcpRow + ); + +static PWSTR DangerousProcesses[] = +{ + L"csrss.exe", L"dwm.exe", L"logonui.exe", L"lsass.exe", L"lsm.exe", + L"services.exe", L"smss.exe", L"wininit.exe", L"winlogon.exe" +}; + +static PPH_STRING DebuggerCommand = NULL; +static PH_INITONCE DebuggerCommandInitOnce = PH_INITONCE_INIT; +static ULONG PhSvcReferenceCount = 0; +static PH_PHSVC_MODE PhSvcCurrentMode; +static PH_QUEUED_LOCK PhSvcStartLock = PH_QUEUED_LOCK_INIT; + +HRESULT CALLBACK PhpElevateActionCallbackProc( + _In_ HWND hwnd, + _In_ UINT uNotification, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ LONG_PTR dwRefData + ) +{ + switch (uNotification) + { + case TDN_CREATED: + SendMessage(hwnd, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, IDYES, TRUE); + break; + } + + return S_OK; +} + +BOOLEAN PhpShowElevatePrompt( + _In_ HWND hWnd, + _In_ PWSTR Message, + _In_ NTSTATUS Status, + _Out_ PINT Button + ) +{ + TASKDIALOGCONFIG config = { sizeof(config) }; + TASKDIALOG_BUTTON buttons[1]; + INT button; + + // Currently the error dialog box is similar to the one displayed + // when you try to label a drive in Windows Explorer. It's much better + // than the clunky dialog in PH 1.x. + + config.hwndParent = hWnd; + config.hInstance = PhInstanceHandle; + config.dwFlags = IsWindowVisible(hWnd) ? TDF_POSITION_RELATIVE_TO_WINDOW : 0; + config.pszWindowTitle = L"Process Hacker"; + config.pszMainIcon = TD_ERROR_ICON; + config.pszMainInstruction = PhaConcatStrings2(Message, L".")->Buffer; + config.pszContent = L"You will need to provide administrator permission. " + L"Click Continue to complete this operation."; + config.dwCommonButtons = TDCBF_CANCEL_BUTTON; + + buttons[0].nButtonID = IDYES; + buttons[0].pszButtonText = L"Continue"; + + config.cButtons = 1; + config.pButtons = buttons; + config.nDefaultButton = IDYES; + + config.pfCallback = PhpElevateActionCallbackProc; + + if (TaskDialogIndirect_Import()( + &config, + &button, + NULL, + NULL + ) == S_OK) + { + *Button = button; + return TRUE; + } + else + { + return FALSE; + } +} + +/** + * Shows an error, prompts for elevation, and executes a command. + * + * \param hWnd The window to display user interface components on. + * \param Message A message describing the operation that failed. + * \param Status A NTSTATUS value. + * \param Command The arguments to pass to the new instance of + * the application, if required. + * \param Success A variable which receives TRUE if the elevated + * action succeeded or FALSE if the action failed. + * + * \return TRUE if the user was prompted for elevation, otherwise + * FALSE, in which case you need to show your own error message. + */ +BOOLEAN PhpShowErrorAndElevateAction( + _In_ HWND hWnd, + _In_ PWSTR Message, + _In_ NTSTATUS Status, + _In_ PWSTR Command, + _Out_ PBOOLEAN Success + ) +{ + PH_ACTION_ELEVATION_LEVEL elevationLevel; + INT button = IDNO; + + if (!( + Status == STATUS_ACCESS_DENIED || + Status == STATUS_PRIVILEGE_NOT_HELD || + (NT_NTWIN32(Status) && WIN32_FROM_NTSTATUS(Status) == ERROR_ACCESS_DENIED) + )) + return FALSE; + + if (!WINDOWS_HAS_UAC || PhGetOwnTokenAttributes().Elevated) + return FALSE; + + elevationLevel = PhGetIntegerSetting(L"ElevationLevel"); + + if (elevationLevel == NeverElevateAction) + return FALSE; + + if (elevationLevel == PromptElevateAction && TaskDialogIndirect_Import()) + { + if (!PhpShowElevatePrompt(hWnd, Message, Status, &button)) + return FALSE; + } + + if (elevationLevel == AlwaysElevateAction || button == IDYES) + { + NTSTATUS status; + HANDLE processHandle; + LARGE_INTEGER timeout; + PROCESS_BASIC_INFORMATION basicInfo; + + if (PhShellProcessHacker( + hWnd, + Command, + SW_SHOW, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS, + 0, + &processHandle + )) + { + timeout.QuadPart = -10 * PH_TIMEOUT_SEC; + status = NtWaitForSingleObject(processHandle, FALSE, &timeout); + + if ( + status == STATUS_WAIT_0 && + NT_SUCCESS(status = PhGetProcessBasicInformation(processHandle, &basicInfo)) + ) + { + status = basicInfo.ExitStatus; + } + + NtClose(processHandle); + + if (NT_SUCCESS(status)) + { + *Success = TRUE; + } + else + { + *Success = FALSE; + PhShowStatus(hWnd, Message, status, 0); + } + } + } + + return TRUE; +} + +/** + * Shows an error, prompts for elevation, and connects to phsvc. + * + * \param hWnd The window to display user interface components on. + * \param Message A message describing the operation that failed. + * \param Status A NTSTATUS value. + * \param Connected A variable which receives TRUE if the user + * elevated the action and phsvc was started, or FALSE if the user + * cancelled elevation. If the value is TRUE, you need to + * perform any necessary phsvc calls and use PhUiDisconnectFromPhSvc() + * to disconnect from phsvc. + * + * \return TRUE if the user was prompted for elevation, otherwise + * FALSE, in which case you need to show your own error message. + */ +BOOLEAN PhpShowErrorAndConnectToPhSvc( + _In_ HWND hWnd, + _In_ PWSTR Message, + _In_ NTSTATUS Status, + _Out_ PBOOLEAN Connected + ) +{ + PH_ACTION_ELEVATION_LEVEL elevationLevel; + INT button = IDNO; + + *Connected = FALSE; + + if (!( + Status == STATUS_ACCESS_DENIED || + Status == STATUS_PRIVILEGE_NOT_HELD || + (NT_NTWIN32(Status) && WIN32_FROM_NTSTATUS(Status) == ERROR_ACCESS_DENIED) + )) + return FALSE; + + if (!WINDOWS_HAS_UAC || PhGetOwnTokenAttributes().Elevated) + return FALSE; + + elevationLevel = PhGetIntegerSetting(L"ElevationLevel"); + + if (elevationLevel == NeverElevateAction) + return FALSE; + + // Try to connect now so we can avoid prompting the user. + if (PhUiConnectToPhSvc(hWnd, TRUE)) + { + *Connected = TRUE; + return TRUE; + } + + if (elevationLevel == PromptElevateAction && TaskDialogIndirect_Import()) + { + if (!PhpShowElevatePrompt(hWnd, Message, Status, &button)) + return FALSE; + } + + if (elevationLevel == AlwaysElevateAction || button == IDYES) + { + *Connected = PhUiConnectToPhSvc(hWnd, FALSE); + } + + return TRUE; +} + +/** + * Connects to phsvc. + * + * \param hWnd The window to display user interface components on. + * \param ConnectOnly TRUE to only try to connect to phsvc, otherwise + * FALSE to try to elevate and start phsvc if the initial connection + * attempt failed. + */ +BOOLEAN PhUiConnectToPhSvc( + _In_opt_ HWND hWnd, + _In_ BOOLEAN ConnectOnly + ) +{ + return PhUiConnectToPhSvcEx(hWnd, ElevatedPhSvcMode, ConnectOnly); +} + +VOID PhpGetPhSvcPortName( + _In_ PH_PHSVC_MODE Mode, + _Out_ PUNICODE_STRING PortName + ) +{ + switch (Mode) + { + case ElevatedPhSvcMode: + if (!PhIsExecutingInWow64()) + RtlInitUnicodeString(PortName, PHSVC_PORT_NAME); + else + RtlInitUnicodeString(PortName, PHSVC_WOW64_PORT_NAME); + break; + case Wow64PhSvcMode: + RtlInitUnicodeString(PortName, PHSVC_WOW64_PORT_NAME); + break; + default: + PhRaiseStatus(STATUS_INVALID_PARAMETER); + break; + } +} + +BOOLEAN PhpStartPhSvcProcess( + _In_opt_ HWND hWnd, + _In_ PH_PHSVC_MODE Mode + ) +{ + switch (Mode) + { + case ElevatedPhSvcMode: + if (PhShellProcessHacker( + hWnd, + L"-phsvc", + SW_HIDE, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS, + 0, + NULL + )) + { + return TRUE; + } + + break; + case Wow64PhSvcMode: + { + static PWSTR relativeFileNames[] = + { + L"\\x86\\ProcessHacker.exe", + L"\\..\\x86\\ProcessHacker.exe", +#ifdef DEBUG + L"\\..\\Debug32\\ProcessHacker.exe", +#endif + L"\\..\\Release32\\ProcessHacker.exe" + }; + + ULONG i; + + for (i = 0; i < sizeof(relativeFileNames) / sizeof(PWSTR); i++) + { + PPH_STRING fileName; + + fileName = PhConcatStrings2(PhApplicationDirectory->Buffer, relativeFileNames[i]); + PhMoveReference(&fileName, PhGetFullPath(fileName->Buffer, NULL)); + + if (fileName && RtlDoesFileExists_U(fileName->Buffer)) + { + if (PhShellProcessHackerEx( + hWnd, + fileName->Buffer, + L"-phsvc", + SW_HIDE, + 0, + PH_SHELL_APP_PROPAGATE_PARAMETERS, + 0, + NULL + )) + { + PhDereferenceObject(fileName); + return TRUE; + } + } + + PhClearReference(&fileName); + } + } + break; + } + + return FALSE; +} + +/** + * Connects to phsvc. + * + * \param hWnd The window to display user interface components on. + * \param Mode The type of phsvc instance to connect to. + * \param ConnectOnly TRUE to only try to connect to phsvc, otherwise + * FALSE to try to elevate and start phsvc if the initial connection + * attempt failed. + */ +BOOLEAN PhUiConnectToPhSvcEx( + _In_opt_ HWND hWnd, + _In_ PH_PHSVC_MODE Mode, + _In_ BOOLEAN ConnectOnly + ) +{ + NTSTATUS status; + BOOLEAN started; + UNICODE_STRING portName; + + if (_InterlockedIncrementNoZero(&PhSvcReferenceCount)) + { + if (PhSvcCurrentMode == Mode) + { + started = TRUE; + } + else + { + _InterlockedDecrement(&PhSvcReferenceCount); + started = FALSE; + } + } + else + { + PhAcquireQueuedLockExclusive(&PhSvcStartLock); + + if (PhSvcReferenceCount == 0) + { + started = FALSE; + PhpGetPhSvcPortName(Mode, &portName); + + // Try to connect first, then start the server if we failed. + status = PhSvcConnectToServer(&portName, 0); + + if (NT_SUCCESS(status)) + { + started = TRUE; + PhSvcCurrentMode = Mode; + _InterlockedIncrement(&PhSvcReferenceCount); + } + else if (!ConnectOnly) + { + // Prompt for elevation, and then try to connect to the server. + + if (PhpStartPhSvcProcess(hWnd, Mode)) + started = TRUE; + + if (started) + { + ULONG attempts = 10; + LARGE_INTEGER interval; + + // Try to connect several times because the server may take + // a while to initialize. + do + { + status = PhSvcConnectToServer(&portName, 0); + + if (NT_SUCCESS(status)) + break; + + interval.QuadPart = -50 * PH_TIMEOUT_MS; + NtDelayExecution(FALSE, &interval); + } while (--attempts != 0); + + // Increment the reference count even if we failed. + // We don't want to prompt the user again. + + PhSvcCurrentMode = Mode; + _InterlockedIncrement(&PhSvcReferenceCount); + } + } + } + else + { + if (PhSvcCurrentMode == Mode) + { + started = TRUE; + _InterlockedIncrement(&PhSvcReferenceCount); + } + else + { + started = FALSE; + } + } + + PhReleaseQueuedLockExclusive(&PhSvcStartLock); + } + + return started; +} + +/** + * Disconnects from phsvc. + */ +VOID PhUiDisconnectFromPhSvc( + VOID + ) +{ + PhAcquireQueuedLockExclusive(&PhSvcStartLock); + + if (_InterlockedDecrement(&PhSvcReferenceCount) == 0) + { + PhSvcDisconnectFromServer(); + } + + PhReleaseQueuedLockExclusive(&PhSvcStartLock); +} + +BOOLEAN PhUiLockComputer( + _In_ HWND hWnd + ) +{ + if (LockWorkStation()) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to lock the computer", 0, GetLastError()); + + return FALSE; +} + +BOOLEAN PhUiLogoffComputer( + _In_ HWND hWnd + ) +{ + if (ExitWindowsEx(EWX_LOGOFF, 0)) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to log off the computer", 0, GetLastError()); + + return FALSE; +} + +BOOLEAN PhUiSleepComputer( + _In_ HWND hWnd + ) +{ + NTSTATUS status; + + if (NT_SUCCESS(status = NtInitiatePowerAction( + PowerActionSleep, + PowerSystemSleeping1, + 0, + FALSE + ))) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to sleep the computer", status, 0); + + return FALSE; +} + +BOOLEAN PhUiHibernateComputer( + _In_ HWND hWnd + ) +{ + NTSTATUS status; + + if (NT_SUCCESS(status = NtInitiatePowerAction( + PowerActionHibernate, + PowerSystemSleeping1, + 0, + FALSE + ))) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to hibernate the computer", status, 0); + + return FALSE; +} + +BOOLEAN PhUiRestartComputer( + _In_ HWND hWnd, + _In_ ULONG Flags + ) +{ + if (!PhGetIntegerSetting(L"EnableWarnings") || PhShowConfirmMessage( + hWnd, + L"restart", + L"the computer", + NULL, + FALSE + )) + { + if (ExitWindowsEx(EWX_REBOOT | Flags, 0)) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to restart the computer", 0, GetLastError()); + } + + return FALSE; +} + +BOOLEAN PhUiShutdownComputer( + _In_ HWND hWnd, + _In_ ULONG Flags + ) +{ + if (!PhGetIntegerSetting(L"EnableWarnings") || PhShowConfirmMessage( + hWnd, + L"shut down", + L"the computer", + NULL, + FALSE + )) + { + if (ExitWindowsEx(EWX_POWEROFF | Flags, 0)) + { + return TRUE; + } + else if (ExitWindowsEx(EWX_SHUTDOWN | Flags, 0)) + { + return TRUE; + } + else + { + PhShowStatus(hWnd, L"Unable to shut down the computer", 0, GetLastError()); + } + } + + return FALSE; +} + +BOOLEAN PhUiConnectSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ) +{ + BOOLEAN success = FALSE; + PPH_STRING selectedChoice = NULL; + PPH_STRING oldSelectedChoice = NULL; + + // Try once with no password. + if (WinStationConnectW(NULL, SessionId, -1, L"", TRUE)) + return TRUE; + + while (PhaChoiceDialog( + hWnd, + L"Connect to session", + L"Password:", + NULL, + 0, + NULL, + PH_CHOICE_DIALOG_PASSWORD, + &selectedChoice, + NULL, + NULL + )) + { + if (oldSelectedChoice) + { + RtlSecureZeroMemory(oldSelectedChoice->Buffer, oldSelectedChoice->Length); + PhDereferenceObject(oldSelectedChoice); + } + + oldSelectedChoice = selectedChoice; + + if (WinStationConnectW(NULL, SessionId, -1, selectedChoice->Buffer, TRUE)) + { + success = TRUE; + break; + } + else + { + if (!PhShowContinueStatus(hWnd, L"Unable to connect to the session", 0, GetLastError())) + break; + } + } + + if (oldSelectedChoice) + { + RtlSecureZeroMemory(oldSelectedChoice->Buffer, oldSelectedChoice->Length); + PhDereferenceObject(oldSelectedChoice); + } + + return success; +} + +BOOLEAN PhUiDisconnectSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ) +{ + if (WinStationDisconnect(NULL, SessionId, FALSE)) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to disconnect the session", 0, GetLastError()); + + return FALSE; +} + +BOOLEAN PhUiLogoffSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ) +{ + if (!PhGetIntegerSetting(L"EnableWarnings") || PhShowConfirmMessage( + hWnd, + L"logoff", + L"the user", + NULL, + FALSE + )) + { + if (WinStationReset(NULL, SessionId, FALSE)) + return TRUE; + else + PhShowStatus(hWnd, L"Unable to logoff the session", 0, GetLastError()); + } + + return FALSE; +} + +/** + * Determines if a process is a system process. + * + * \param ProcessId The PID of the process to check. + */ +static BOOLEAN PhpIsDangerousProcess( + _In_ HANDLE ProcessId + ) +{ + NTSTATUS status; + HANDLE processHandle; + PPH_STRING fileName; + PPH_STRING systemDirectory; + ULONG i; + + if (ProcessId == SYSTEM_PROCESS_ID) + return TRUE; + + if (WINDOWS_HAS_IMAGE_FILE_NAME_BY_PROCESS_ID) + { + status = PhGetProcessImageFileNameByProcessId(ProcessId, &fileName); + } + else + { + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + ProcessId + ))) + return FALSE; + + status = PhGetProcessImageFileName(processHandle, &fileName); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + return FALSE; + + PhMoveReference(&fileName, PhGetFileName(fileName)); + PH_AUTO(fileName); + + systemDirectory = PH_AUTO(PhGetSystemDirectory()); + + for (i = 0; i < sizeof(DangerousProcesses) / sizeof(PWSTR); i++) + { + PPH_STRING fullName; + + fullName = PhaConcatStrings(3, systemDirectory->Buffer, L"\\", DangerousProcesses[i]); + + if (PhEqualString(fileName, fullName, TRUE)) + return TRUE; + } + + return FALSE; +} + +/** + * Checks if the user wants to proceed with an operation. + * + * \param hWnd A handle to the parent window. + * \param Verb A verb describing the action. + * \param Message A message containing additional information + * about the action. + * \param WarnOnlyIfDangerous TRUE to skip the confirmation + * dialog if none of the processes are system processes, + * FALSE to always show the confirmation dialog. + * \param Processes An array of pointers to process items. + * \param NumberOfProcesses The number of process items. + * + * \return TRUE if the user wants to proceed with the operation, + * otherwise FALSE. + */ +static BOOLEAN PhpShowContinueMessageProcesses( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_opt_ PWSTR Message, + _In_ BOOLEAN WarnOnlyIfDangerous, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + PWSTR object; + ULONG i; + BOOLEAN critical = FALSE; + BOOLEAN dangerous = FALSE; + BOOLEAN cont = FALSE; + + if (NumberOfProcesses == 0) + return FALSE; + + for (i = 0; i < NumberOfProcesses; i++) + { + HANDLE processHandle; + ULONG breakOnTermination; + + breakOnTermination = 0; + + if (NT_SUCCESS(PhOpenProcess(&processHandle, PROCESS_QUERY_INFORMATION, Processes[i]->ProcessId))) + { + NtQueryInformationProcess(processHandle, ProcessBreakOnTermination, &breakOnTermination, sizeof(ULONG), NULL); + NtClose(processHandle); + } + + if (breakOnTermination != 0) + { + critical = TRUE; + dangerous = TRUE; + break; + } + + if (PhpIsDangerousProcess(Processes[i]->ProcessId)) + { + dangerous = TRUE; + break; + } + } + + if (WarnOnlyIfDangerous && !dangerous) + return TRUE; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + if (NumberOfProcesses == 1) + { + object = Processes[0]->ProcessName->Buffer; + } + else if (NumberOfProcesses == 2) + { + object = PhaConcatStrings( + 3, + Processes[0]->ProcessName->Buffer, + L" and ", + Processes[1]->ProcessName->Buffer + )->Buffer; + } + else + { + object = L"the selected processes"; + } + + if (!dangerous) + { + cont = PhShowConfirmMessage( + hWnd, + Verb, + object, + Message, + FALSE + ); + } + else if (!critical) + { + cont = PhShowConfirmMessage( + hWnd, + Verb, + object, + PhaConcatStrings( + 3, + L"You are about to ", + Verb, + L" one or more system processes." + )->Buffer, + TRUE + ); + } + else + { + PPH_STRING message; + + if (PhEqualStringZ(Verb, L"terminate", FALSE)) + { + message = PhaConcatStrings( + 3, + L"You are about to ", + Verb, + L" one or more critical processes. This will shut down the operating system immediately." + ); + } + else + { + message = PhaConcatStrings( + 3, + L"You are about to ", + Verb, + L" one or more critical processes." + ); + } + + cont = PhShowConfirmMessage( + hWnd, + Verb, + object, + message->Buffer, + TRUE + ); + } + } + else + { + cont = TRUE; + } + + return cont; +} + +/** + * Shows an error message to the user and checks + * if the user wants to continue. + * + * \param hWnd A handle to the parent window. + * \param Verb A verb describing the action which + * resulted in an error. + * \param Process The process item which the action + * was performed on. + * \param Status A NT status value representing the + * error. + * \param Win32Result A Win32 error code representing + * the error. + * + * \return TRUE if the user wants to continue, otherwise + * FALSE. The result is typically only useful when + * executing an action on multiple processes. + */ +static BOOLEAN PhpShowErrorProcess( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PPH_PROCESS_ITEM Process, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + if (!PH_IS_FAKE_PROCESS_ID(Process->ProcessId)) + { + return PhShowContinueStatus( + hWnd, + PhaFormatString( + L"Unable to %s %s (PID %u)", + Verb, + Process->ProcessName->Buffer, + HandleToUlong(Process->ProcessId) + )->Buffer, + Status, + Win32Result + ); + } + else + { + return PhShowContinueStatus( + hWnd, + PhaFormatString( + L"Unable to %s %s", + Verb, + Process->ProcessName->Buffer + )->Buffer, + Status, + Win32Result + ); + } +} + +BOOLEAN PhUiTerminateProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + if (!PhpShowContinueMessageProcesses( + hWnd, + L"terminate", + L"Terminating a process will cause unsaved data to be lost.", + FALSE, + Processes, + NumberOfProcesses + )) + return FALSE; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_TERMINATE, + Processes[i]->ProcessId + ))) + { + // An exit status of 1 is used here for compatibility reasons: + // 1. Both Task Manager and Process Explorer use 1. + // 2. winlogon tries to restart explorer.exe if the exit status is not 1. + + status = PhTerminateProcess(processHandle, 1); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to terminate ", Processes[i]->ProcessName->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlProcess(Processes[i]->ProcessId, PhSvcControlProcessTerminate, 0))) + success = TRUE; + else + PhpShowErrorProcess(hWnd, L"terminate", Processes[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorProcess(hWnd, L"terminate", Processes[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhpUiTerminateTreeProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process, + _In_ PVOID Processes, + _Inout_ PBOOLEAN Success + ) +{ + NTSTATUS status; + PSYSTEM_PROCESS_INFORMATION process; + HANDLE processHandle; + PPH_PROCESS_ITEM processItem; + + // Note: + // FALSE should be written to Success if any part of the operation failed. + // The return value of this function indicates whether to continue with + // the operation (FALSE if user cancelled). + + // Terminate the process. + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_TERMINATE, + Process->ProcessId + ))) + { + status = PhTerminateProcess(processHandle, 1); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + *Success = FALSE; + + if (!PhpShowErrorProcess(hWnd, L"terminate", Process, status, 0)) + return FALSE; + } + + // Terminate the process' children. + + process = PH_FIRST_PROCESS(Processes); + + do + { + if (process->UniqueProcessId != Process->ProcessId && + process->InheritedFromUniqueProcessId == Process->ProcessId) + { + if (processItem = PhReferenceProcessItem(process->UniqueProcessId)) + { + // Check the creation time to make sure it is a descendant. + if (processItem->CreateTime.QuadPart >= Process->CreateTime.QuadPart) + { + if (!PhpUiTerminateTreeProcess(hWnd, processItem, Processes, Success)) + { + PhDereferenceObject(processItem); + return FALSE; + } + } + + PhDereferenceObject(processItem); + } + } + } while (process = PH_NEXT_PROCESS(process)); + + return TRUE; +} + +BOOLEAN PhUiTerminateTreeProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + NTSTATUS status; + BOOLEAN success = TRUE; + BOOLEAN cont = FALSE; + PVOID processes; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + cont = PhShowConfirmMessage( + hWnd, + L"terminate", + PhaConcatStrings2(Process->ProcessName->Buffer, L" and its descendants")->Buffer, + L"Terminating a process tree will cause the process and its descendants to be terminated.", + FALSE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + { + PhShowStatus(hWnd, L"Unable to enumerate processes", status, 0); + return FALSE; + } + + PhpUiTerminateTreeProcess(hWnd, Process, processes, &success); + PhFree(processes); + + return success; +} + +BOOLEAN PhUiSuspendProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + if (!PhpShowContinueMessageProcesses( + hWnd, + L"suspend", + NULL, + TRUE, + Processes, + NumberOfProcesses + )) + return FALSE; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SUSPEND_RESUME, + Processes[i]->ProcessId + ))) + { + status = NtSuspendProcess(processHandle); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to suspend ", Processes[i]->ProcessName->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlProcess(Processes[i]->ProcessId, PhSvcControlProcessSuspend, 0))) + success = TRUE; + else + PhpShowErrorProcess(hWnd, L"suspend", Processes[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorProcess(hWnd, L"suspend", Processes[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiResumeProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + if (!PhpShowContinueMessageProcesses( + hWnd, + L"resume", + NULL, + TRUE, + Processes, + NumberOfProcesses + )) + return FALSE; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SUSPEND_RESUME, + Processes[i]->ProcessId + ))) + { + status = NtResumeProcess(processHandle); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to resume ", Processes[i]->ProcessName->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlProcess(Processes[i]->ProcessId, PhSvcControlProcessResume, 0))) + success = TRUE; + else + PhpShowErrorProcess(hWnd, L"resume", Processes[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorProcess(hWnd, L"resume", Processes[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiRestartProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + HANDLE processHandle = NULL; + PPH_STRING commandLine; + PPH_STRING currentDirectory; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + cont = PhShowConfirmMessage( + hWnd, + L"restart", + Process->ProcessName->Buffer, + L"The process will be restarted with the same command line and " + L"working directory, but if it is running under a different user it " + L"will be restarted under the current user.", + FALSE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + // Open the process and get the command line and current directory. + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_VM_READ, + Process->ProcessId + ))) + goto ErrorExit; + + if (!NT_SUCCESS(status = PhGetProcessCommandLine( + processHandle, + &commandLine + ))) + goto ErrorExit; + + PH_AUTO(commandLine); + + if (!NT_SUCCESS(status = PhGetProcessPebString( + processHandle, + PhpoCurrentDirectory, + ¤tDirectory + ))) + goto ErrorExit; + + PH_AUTO(currentDirectory); + + NtClose(processHandle); + processHandle = NULL; + + // Open the process and terminate it. + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_TERMINATE, + Process->ProcessId + ))) + goto ErrorExit; + + if (!NT_SUCCESS(status = PhTerminateProcess( + processHandle, + 1 + ))) + goto ErrorExit; + + NtClose(processHandle); + processHandle = NULL; + + // Start the process. + + status = PhCreateProcessWin32( + PhGetString(Process->FileName), // we didn't wait for S1 processing + commandLine->Buffer, + NULL, + currentDirectory->Buffer, + 0, + NULL, + NULL, + NULL + ); + +ErrorExit: + if (processHandle) + NtClose(processHandle); + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"restart", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +// Contributed by evilpie (#2981421) +BOOLEAN PhUiDebugProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + PH_STRING_BUILDER commandLineBuilder; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + cont = PhShowConfirmMessage( + hWnd, + L"debug", + Process->ProcessName->Buffer, + L"Debugging a process may result in loss of data.", + FALSE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + if (PhBeginInitOnce(&DebuggerCommandInitOnce)) + { + static PH_STRINGREF aeDebugKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug"); + + HANDLE keyHandle; + PPH_STRING debugger; + PH_STRINGREF commandPart; + PH_STRINGREF dummy; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &aeDebugKeyName, + 0 + ))) + { + if (debugger = PH_AUTO(PhQueryRegistryString(keyHandle, L"Debugger"))) + { + if (PhSplitStringRefAtChar(&debugger->sr, '"', &dummy, &commandPart) && + PhSplitStringRefAtChar(&commandPart, '"', &commandPart, &dummy)) + { + DebuggerCommand = PhCreateString2(&commandPart); + } + } + + NtClose(keyHandle); + } + + PhEndInitOnce(&DebuggerCommandInitOnce); + } + + if (!DebuggerCommand) + { + PhShowError(hWnd, L"Unable to locate the debugger."); + return FALSE; + } + + PhInitializeStringBuilder(&commandLineBuilder, DebuggerCommand->Length + 30); + + PhAppendCharStringBuilder(&commandLineBuilder, '"'); + PhAppendStringBuilder(&commandLineBuilder, &DebuggerCommand->sr); + PhAppendCharStringBuilder(&commandLineBuilder, '"'); + PhAppendFormatStringBuilder(&commandLineBuilder, L" -p %u", HandleToUlong(Process->ProcessId)); + + status = PhCreateProcessWin32( + NULL, + commandLineBuilder.String->Buffer, + NULL, + NULL, + 0, + NULL, + NULL, + NULL + ); + + PhDeleteStringBuilder(&commandLineBuilder); + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"debug", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiReduceWorkingSetProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + BOOLEAN success = TRUE; + ULONG i; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_QUOTA, + Processes[i]->ProcessId + ))) + { + QUOTA_LIMITS quotaLimits; + + memset("aLimits, 0, sizeof(QUOTA_LIMITS)); + quotaLimits.MinimumWorkingSetSize = -1; + quotaLimits.MaximumWorkingSetSize = -1; + + status = NtSetInformationProcess( + processHandle, + ProcessQuotaLimits, + "aLimits, + sizeof(QUOTA_LIMITS) + ); + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + success = FALSE; + + if (!PhpShowErrorProcess(hWnd, L"reduce the working set of", Processes[i], status, 0)) + break; + } + } + + return success; +} + +BOOLEAN PhUiSetVirtualizationProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process, + _In_ BOOLEAN Enable + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + HANDLE processHandle; + HANDLE tokenHandle; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + cont = PhShowConfirmMessage( + hWnd, + L"set", + L"virtualization for the process", + L"Enabling or disabling virtualization for a process may " + L"alter its functionality and produce undesirable effects.", + FALSE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Process->ProcessId + ))) + { + if (NT_SUCCESS(status = PhOpenProcessToken( + processHandle, + TOKEN_WRITE, + &tokenHandle + ))) + { + status = PhSetTokenIsVirtualizationEnabled(tokenHandle, Enable); + NtClose(tokenHandle); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"set virtualization for", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiDetachFromDebuggerProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + NTSTATUS status; + HANDLE processHandle; + HANDLE debugObjectHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME, + Process->ProcessId + ))) + { + if (NT_SUCCESS(status = PhGetProcessDebugObject( + processHandle, + &debugObjectHandle + ))) + { + ULONG flags; + + // Disable kill-on-close. + flags = 0; + NtSetInformationDebugObject( + debugObjectHandle, + DebugObjectFlags, + &flags, + sizeof(ULONG), + NULL + ); + + status = NtRemoveProcessDebug(processHandle, debugObjectHandle); + + NtClose(debugObjectHandle); + } + + NtClose(processHandle); + } + + if (status == STATUS_PORT_NOT_SET) + { + PhShowInformation(hWnd, L"The process is not being debugged."); + return FALSE; + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"detach debugger from", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiInjectDllProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + static PH_FILETYPE_FILTER filters[] = + { + { L"DLL files (*.dll)", L"*.dll" }, + { L"All files (*.*)", L"*.*" } + }; + + NTSTATUS status; + PVOID fileDialog; + PPH_STRING fileName; + HANDLE processHandle; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + if (!PhShowFileDialog(hWnd, fileDialog)) + { + PhFreeFileDialog(fileDialog); + return FALSE; + } + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + PhFreeFileDialog(fileDialog); + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | + PROCESS_VM_READ | PROCESS_VM_WRITE, + Process->ProcessId + ))) + { + LARGE_INTEGER timeout; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + status = PhInjectDllProcess( + processHandle, + fileName->Buffer, + &timeout + ); + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"inject the DLL into", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiSetIoPriorityProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses, + _In_ IO_PRIORITY_HINT IoPriority + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + Processes[i]->ProcessId + ))) + { + if (Processes[i]->ProcessId != SYSTEM_PROCESS_ID) + { + status = PhSetProcessIoPriority(processHandle, IoPriority); + } + else + { + // See comment in PhUiSetPriorityProcesses. + status = STATUS_UNSUCCESSFUL; + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + // The operation may have failed due to the lack of SeIncreaseBasePriorityPrivilege. + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to set the I/O priority of ", Processes[i]->ProcessName->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlProcess(Processes[i]->ProcessId, PhSvcControlProcessIoPriority, IoPriority))) + success = TRUE; + else + PhpShowErrorProcess(hWnd, L"set the I/O priority of", Processes[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorProcess(hWnd, L"set the I/O priority of", Processes[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiSetPagePriorityProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process, + _In_ ULONG PagePriority + ) +{ + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + Process->ProcessId + ))) + { + if (Process->ProcessId != SYSTEM_PROCESS_ID) + { + status = NtSetInformationProcess( + processHandle, + ProcessPagePriority, + &PagePriority, + sizeof(ULONG) + ); + } + else + { + // See comment in PhUiSetPriorityProcesses. + status = STATUS_UNSUCCESSFUL; + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorProcess(hWnd, L"set the page priority of", Process, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiSetPriorityProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses, + _In_ ULONG PriorityClass + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + for (i = 0; i < NumberOfProcesses; i++) + { + NTSTATUS status; + HANDLE processHandle; + PROCESS_PRIORITY_CLASS priorityClass; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + Processes[i]->ProcessId + ))) + { + if (Processes[i]->ProcessId != SYSTEM_PROCESS_ID) + { + priorityClass.Foreground = FALSE; + priorityClass.PriorityClass = (UCHAR)PriorityClass; + status = NtSetInformationProcess(processHandle, ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS)); + } + else + { + // Changing the priority of System can lead to a BSOD on some versions of Windows, + // so disallow this. + status = STATUS_UNSUCCESSFUL; + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + // The operation may have failed due to the lack of SeIncreaseBasePriorityPrivilege. + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to set the priority of ", Processes[i]->ProcessName->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlProcess(Processes[i]->ProcessId, PhSvcControlProcessPriority, PriorityClass))) + success = TRUE; + else + PhpShowErrorProcess(hWnd, L"set the priority of", Processes[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorProcess(hWnd, L"set the priority of", Processes[i], status, 0)) + break; + } + } + } + + return success; +} + +static VOID PhpShowErrorService( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PPH_SERVICE_ITEM Service, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + PhShowStatus( + hWnd, + PhaFormatString( + L"Unable to %s %s", + Verb, + Service->Name->Buffer + )->Buffer, + Status, + Win32Result + ); +} + +BOOLEAN PhUiStartService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ) +{ + SC_HANDLE serviceHandle; + BOOLEAN success = FALSE; + + serviceHandle = PhOpenService(Service->Name->Buffer, SERVICE_START); + + if (serviceHandle) + { + if (StartService(serviceHandle, 0, NULL)) + success = TRUE; + + CloseServiceHandle(serviceHandle); + } + + if (!success) + { + NTSTATUS status; + BOOLEAN connected; + + status = PhGetLastWin32ErrorAsNtStatus(); + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to start ", Service->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlService(Service->Name->Buffer, PhSvcControlServiceStart))) + success = TRUE; + else + PhpShowErrorService(hWnd, L"start", Service, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorService(hWnd, L"start", Service, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiContinueService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ) +{ + SC_HANDLE serviceHandle; + BOOLEAN success = FALSE; + + serviceHandle = PhOpenService(Service->Name->Buffer, SERVICE_PAUSE_CONTINUE); + + if (serviceHandle) + { + SERVICE_STATUS serviceStatus; + + if (ControlService(serviceHandle, SERVICE_CONTROL_CONTINUE, &serviceStatus)) + success = TRUE; + + CloseServiceHandle(serviceHandle); + } + + if (!success) + { + NTSTATUS status; + BOOLEAN connected; + + status = PhGetLastWin32ErrorAsNtStatus(); + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to continue ", Service->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlService(Service->Name->Buffer, PhSvcControlServiceContinue))) + success = TRUE; + else + PhpShowErrorService(hWnd, L"continue", Service, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorService(hWnd, L"continue", Service, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiPauseService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ) +{ + SC_HANDLE serviceHandle; + BOOLEAN success = FALSE; + + serviceHandle = PhOpenService(Service->Name->Buffer, SERVICE_PAUSE_CONTINUE); + + if (serviceHandle) + { + SERVICE_STATUS serviceStatus; + + if (ControlService(serviceHandle, SERVICE_CONTROL_PAUSE, &serviceStatus)) + success = TRUE; + + CloseServiceHandle(serviceHandle); + } + + if (!success) + { + NTSTATUS status; + BOOLEAN connected; + + status = PhGetLastWin32ErrorAsNtStatus(); + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to pause ", Service->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlService(Service->Name->Buffer, PhSvcControlServicePause))) + success = TRUE; + else + PhpShowErrorService(hWnd, L"pause", Service, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorService(hWnd, L"pause", Service, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiStopService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ) +{ + SC_HANDLE serviceHandle; + BOOLEAN success = FALSE; + + serviceHandle = PhOpenService(Service->Name->Buffer, SERVICE_STOP); + + if (serviceHandle) + { + SERVICE_STATUS serviceStatus; + + if (ControlService(serviceHandle, SERVICE_CONTROL_STOP, &serviceStatus)) + success = TRUE; + + CloseServiceHandle(serviceHandle); + } + + if (!success) + { + NTSTATUS status; + BOOLEAN connected; + + status = PhGetLastWin32ErrorAsNtStatus(); + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to stop ", Service->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlService(Service->Name->Buffer, PhSvcControlServiceStop))) + success = TRUE; + else + PhpShowErrorService(hWnd, L"stop", Service, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorService(hWnd, L"stop", Service, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiDeleteService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ) +{ + SC_HANDLE serviceHandle; + BOOLEAN success = FALSE; + + // Warnings cannot be disabled for service deletion. + if (!PhShowConfirmMessage( + hWnd, + L"delete", + Service->Name->Buffer, + L"Deleting a service can prevent the system from starting " + L"or functioning properly.", + TRUE + )) + return FALSE; + + serviceHandle = PhOpenService(Service->Name->Buffer, DELETE); + + if (serviceHandle) + { + if (DeleteService(serviceHandle)) + success = TRUE; + + CloseServiceHandle(serviceHandle); + } + + if (!success) + { + NTSTATUS status; + BOOLEAN connected; + + status = PhGetLastWin32ErrorAsNtStatus(); + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to delete ", Service->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlService(Service->Name->Buffer, PhSvcControlServiceDelete))) + success = TRUE; + else + PhpShowErrorService(hWnd, L"delete", Service, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorService(hWnd, L"delete", Service, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiCloseConnections( + _In_ HWND hWnd, + _In_ PPH_NETWORK_ITEM *Connections, + _In_ ULONG NumberOfConnections + ) +{ + + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG result; + ULONG i; + _SetTcpEntry SetTcpEntry_I; + MIB_TCPROW tcpRow; + + SetTcpEntry_I = PhGetModuleProcAddress(L"iphlpapi.dll", "SetTcpEntry"); + + if (!SetTcpEntry_I) + { + PhShowError( + hWnd, + L"This feature is not supported by your operating system." + ); + return FALSE; + } + + for (i = 0; i < NumberOfConnections; i++) + { + if ( + Connections[i]->ProtocolType != PH_TCP4_NETWORK_PROTOCOL || + Connections[i]->State != MIB_TCP_STATE_ESTAB + ) + continue; + + tcpRow.dwState = MIB_TCP_STATE_DELETE_TCB; + tcpRow.dwLocalAddr = Connections[i]->LocalEndpoint.Address.Ipv4; + tcpRow.dwLocalPort = _byteswap_ushort((USHORT)Connections[i]->LocalEndpoint.Port); + tcpRow.dwRemoteAddr = Connections[i]->RemoteEndpoint.Address.Ipv4; + tcpRow.dwRemotePort = _byteswap_ushort((USHORT)Connections[i]->RemoteEndpoint.Port); + + if ((result = SetTcpEntry_I(&tcpRow)) != 0) + { + NTSTATUS status; + BOOLEAN connected; + + success = FALSE; + + // SetTcpEntry returns ERROR_MR_MID_NOT_FOUND for access denied errors for some reason. + if (result == ERROR_MR_MID_NOT_FOUND) + result = ERROR_ACCESS_DENIED; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + L"Unable to close the TCP connection", + NTSTATUS_FROM_WIN32(result), + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallSetTcpEntry(&tcpRow))) + success = TRUE; + else + PhShowStatus(hWnd, L"Unable to close the TCP connection", status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (PhShowMessage( + hWnd, + MB_ICONERROR | MB_OKCANCEL, + L"Unable to close the TCP connection (from %s:%u). " + L"Make sure Process Hacker is running with administrative privileges.", + Connections[i]->LocalAddressString, + Connections[i]->LocalEndpoint.Port + ) != IDOK) + break; + } + } + } + + return success; +} + +static BOOLEAN PhpShowContinueMessageThreads( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PWSTR Message, + _In_ BOOLEAN Warning, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ) +{ + PWSTR object; + BOOLEAN cont = FALSE; + + if (NumberOfThreads == 0) + return FALSE; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + if (NumberOfThreads == 1) + { + object = L"the selected thread"; + } + else + { + object = L"the selected threads"; + } + + cont = PhShowConfirmMessage( + hWnd, + Verb, + object, + Message, + Warning + ); + } + else + { + cont = TRUE; + } + + return cont; +} + +static BOOLEAN PhpShowErrorThread( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PPH_THREAD_ITEM Thread, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + return PhShowContinueStatus( + hWnd, + PhaFormatString( + L"Unable to %s thread %u", + Verb, + HandleToUlong(Thread->ThreadId) + )->Buffer, + Status, + Win32Result + ); +} + +BOOLEAN PhUiTerminateThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + if (!PhpShowContinueMessageThreads( + hWnd, + L"terminate", + L"Terminating a thread may cause the process to stop working.", + FALSE, + Threads, + NumberOfThreads + )) + return FALSE; + + for (i = 0; i < NumberOfThreads; i++) + { + NTSTATUS status; + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_TERMINATE, + Threads[i]->ThreadId + ))) + { + status = NtTerminateThread(threadHandle, STATUS_SUCCESS); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaFormatString(L"Unable to terminate thread %u", HandleToUlong(Threads[i]->ThreadId))->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlThread(Threads[i]->ThreadId, PhSvcControlThreadTerminate, 0))) + success = TRUE; + else + PhpShowErrorThread(hWnd, L"terminate", Threads[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorThread(hWnd, L"terminate", Threads[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiSuspendThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + for (i = 0; i < NumberOfThreads; i++) + { + NTSTATUS status; + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_SUSPEND_RESUME, + Threads[i]->ThreadId + ))) + { + status = NtSuspendThread(threadHandle, NULL); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaFormatString(L"Unable to suspend thread %u", HandleToUlong(Threads[i]->ThreadId))->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlThread(Threads[i]->ThreadId, PhSvcControlThreadSuspend, 0))) + success = TRUE; + else + PhpShowErrorThread(hWnd, L"suspend", Threads[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorThread(hWnd, L"suspend", Threads[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiResumeThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ) +{ + BOOLEAN success = TRUE; + BOOLEAN cancelled = FALSE; + ULONG i; + + for (i = 0; i < NumberOfThreads; i++) + { + NTSTATUS status; + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_SUSPEND_RESUME, + Threads[i]->ThreadId + ))) + { + status = NtResumeThread(threadHandle, NULL); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + if (!cancelled && PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaFormatString(L"Unable to resume thread %u", HandleToUlong(Threads[i]->ThreadId))->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlThread(Threads[i]->ThreadId, PhSvcControlThreadResume, 0))) + success = TRUE; + else + PhpShowErrorThread(hWnd, L"resume", Threads[i], status, 0); + + PhUiDisconnectFromPhSvc(); + } + else + { + cancelled = TRUE; + } + } + else + { + if (!PhpShowErrorThread(hWnd, L"resume", Threads[i], status, 0)) + break; + } + } + } + + return success; +} + +BOOLEAN PhUiSetPriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ LONG Increment + ) +{ + NTSTATUS status; + HANDLE threadHandle; + + // Special saturation values + if (Increment == THREAD_PRIORITY_TIME_CRITICAL) + Increment = THREAD_BASE_PRIORITY_LOWRT + 1; + else if (Increment == THREAD_PRIORITY_IDLE) + Increment = THREAD_BASE_PRIORITY_IDLE - 1; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + ThreadSetAccess, + Thread->ThreadId + ))) + { + status = NtSetInformationThread(threadHandle, ThreadBasePriority, &Increment, sizeof(LONG)); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorThread(hWnd, L"set the priority of", Thread, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiSetIoPriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ IO_PRIORITY_HINT IoPriority + ) +{ + NTSTATUS status; + BOOLEAN success = TRUE; + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_SET_INFORMATION, + Thread->ThreadId + ))) + { + status = PhSetThreadIoPriority(threadHandle, IoPriority); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + BOOLEAN connected; + + success = FALSE; + + // The operation may have failed due to the lack of SeIncreaseBasePriorityPrivilege. + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaFormatString(L"Unable to set the I/O priority of thread %u", HandleToUlong(Thread->ThreadId))->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallControlThread(Thread->ThreadId, PhSvcControlThreadIoPriority, IoPriority))) + success = TRUE; + else + PhpShowErrorThread(hWnd, L"set the I/O priority of", Thread, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhpShowErrorThread(hWnd, L"set the I/O priority of", Thread, status, 0); + } + } + + return success; +} + +BOOLEAN PhUiSetPagePriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ ULONG PagePriority + ) +{ + NTSTATUS status; + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_SET_INFORMATION, + Thread->ThreadId + ))) + { + status = NtSetInformationThread( + threadHandle, + ThreadPagePriority, + &PagePriority, + sizeof(ULONG) + ); + NtClose(threadHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorThread(hWnd, L"set the page priority of", Thread, status, 0); + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiUnloadModule( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_MODULE_ITEM Module + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + HANDLE processHandle; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + PWSTR verb; + PWSTR message; + + switch (Module->Type) + { + case PH_MODULE_TYPE_MODULE: + case PH_MODULE_TYPE_WOW64_MODULE: + verb = L"unload"; + message = L"Unloading a module may cause the process to crash."; + + if (WindowsVersion >= WINDOWS_8) + message = L"Unloading a module may cause the process to crash. NOTE: This feature may not work correctly on your version of Windows."; + + break; + case PH_MODULE_TYPE_KERNEL_MODULE: + verb = L"unload"; + message = L"Unloading a driver may cause system instability."; + break; + case PH_MODULE_TYPE_MAPPED_FILE: + case PH_MODULE_TYPE_MAPPED_IMAGE: + verb = L"unmap"; + message = L"Unmapping a section view may cause the process to crash."; + break; + default: + return FALSE; + } + + cont = PhShowConfirmMessage( + hWnd, + verb, + Module->Name->Buffer, + message, + TRUE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + switch (Module->Type) + { + case PH_MODULE_TYPE_MODULE: + case PH_MODULE_TYPE_WOW64_MODULE: + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | + PROCESS_VM_READ | PROCESS_VM_WRITE, + ProcessId + ))) + { + LARGE_INTEGER timeout; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + status = PhUnloadDllProcess( + processHandle, + Module->BaseAddress, + &timeout + ); + + NtClose(processHandle); + } + + if (status == STATUS_DLL_NOT_FOUND) + { + PhShowError(hWnd, L"Unable to find the module to unload."); + return FALSE; + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus( + hWnd, + PhaConcatStrings2(L"Unable to unload ", Module->Name->Buffer)->Buffer, + status, + 0 + ); + return FALSE; + } + + break; + + case PH_MODULE_TYPE_KERNEL_MODULE: + status = PhUnloadDriver(Module->BaseAddress, Module->Name->Buffer); + + if (!NT_SUCCESS(status)) + { + BOOLEAN success = FALSE; + BOOLEAN connected; + + if (PhpShowErrorAndConnectToPhSvc( + hWnd, + PhaConcatStrings2(L"Unable to unload ", Module->Name->Buffer)->Buffer, + status, + &connected + )) + { + if (connected) + { + if (NT_SUCCESS(status = PhSvcCallUnloadDriver(Module->BaseAddress, Module->Name->Buffer))) + success = TRUE; + else + PhShowStatus(hWnd, PhaConcatStrings2(L"Unable to unload ", Module->Name->Buffer)->Buffer, status, 0); + + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhShowStatus( + hWnd, + PhaConcatStrings( + 3, + L"Unable to unload ", + Module->Name->Buffer, + L". Make sure Process Hacker is running with " + L"administrative privileges. Error" + )->Buffer, + status, + 0 + ); + return FALSE; + } + + return success; + } + + break; + + case PH_MODULE_TYPE_MAPPED_FILE: + case PH_MODULE_TYPE_MAPPED_IMAGE: + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_VM_OPERATION, + ProcessId + ))) + { + status = NtUnmapViewOfSection(processHandle, Module->BaseAddress); + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus( + hWnd, + PhaFormatString(L"Unable to unmap the section view at 0x%Ix", Module->BaseAddress)->Buffer, + status, + 0 + ); + return FALSE; + } + + break; + + default: + return FALSE; + } + + return TRUE; +} + +BOOLEAN PhUiFreeMemory( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_MEMORY_ITEM MemoryItem, + _In_ BOOLEAN Free + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + HANDLE processHandle; + + if (PhGetIntegerSetting(L"EnableWarnings")) + { + PWSTR verb; + PWSTR message; + + if (!(MemoryItem->Type & (MEM_MAPPED | MEM_IMAGE))) + { + if (Free) + { + verb = L"free"; + message = L"Freeing memory regions may cause the process to crash."; + } + else + { + verb = L"decommit"; + message = L"Decommitting memory regions may cause the process to crash."; + } + } + else + { + verb = L"unmap"; + message = L"Unmapping a section view may cause the process to crash."; + } + + cont = PhShowConfirmMessage( + hWnd, + verb, + L"the memory region", + message, + TRUE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_VM_OPERATION, + ProcessId + ))) + { + PVOID baseAddress; + SIZE_T regionSize; + + baseAddress = MemoryItem->BaseAddress; + + if (!(MemoryItem->Type & (MEM_MAPPED | MEM_IMAGE))) + { + // The size needs to be 0 if we're freeing. + if (Free) + regionSize = 0; + else + regionSize = MemoryItem->RegionSize; + + status = NtFreeVirtualMemory( + processHandle, + &baseAddress, + ®ionSize, + Free ? MEM_RELEASE : MEM_DECOMMIT + ); + } + else + { + status = NtUnmapViewOfSection(processHandle, baseAddress); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PWSTR message; + + if (!(MemoryItem->Type & (MEM_MAPPED | MEM_IMAGE))) + { + if (Free) + message = L"Unable to free the memory region"; + else + message = L"Unable to decommit the memory region"; + } + else + { + message = L"Unable to unmap the section view"; + } + + PhShowStatus( + hWnd, + message, + status, + 0 + ); + return FALSE; + } + + return TRUE; +} + +static BOOLEAN PhpShowErrorHandle( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PPH_HANDLE_ITEM Handle, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + if (!PhIsNullOrEmptyString(Handle->BestObjectName)) + { + return PhShowContinueStatus( + hWnd, + PhaFormatString( + L"Unable to %s handle \"%s\" (0x%Ix)", + Verb, + Handle->BestObjectName->Buffer, + HandleToUlong(Handle->Handle) + )->Buffer, + Status, + Win32Result + ); + } + else + { + return PhShowContinueStatus( + hWnd, + PhaFormatString( + L"Unable to %s handle 0x%Ix", + Verb, + HandleToUlong(Handle->Handle) + )->Buffer, + Status, + Win32Result + ); + } +} + +BOOLEAN PhUiCloseHandles( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM *Handles, + _In_ ULONG NumberOfHandles, + _In_ BOOLEAN Warn + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + BOOLEAN success = TRUE; + HANDLE processHandle; + + if (NumberOfHandles == 0) + return FALSE; + + if (Warn && PhGetIntegerSetting(L"EnableWarnings")) + { + cont = PhShowConfirmMessage( + hWnd, + L"close", + NumberOfHandles == 1 ? L"the selected handle" : L"the selected handles", + L"Closing handles may cause system instability and data corruption.", + FALSE + ); + } + else + { + cont = TRUE; + } + + if (!cont) + return FALSE; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + ProcessId + ))) + { + ULONG i; + + for (i = 0; i < NumberOfHandles; i++) + { + status = NtDuplicateObject( + processHandle, + Handles[i]->Handle, + NULL, + NULL, + 0, + 0, + DUPLICATE_CLOSE_SOURCE + ); + + if (!NT_SUCCESS(status)) + { + success = FALSE; + + if (!PhpShowErrorHandle( + hWnd, + L"close", + Handles[i], + status, + 0 + )) + break; + } + } + + NtClose(processHandle); + } + else + { + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return FALSE; + } + + return success; +} + +BOOLEAN PhUiSetAttributesHandle( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM Handle, + _In_ ULONG Attributes + ) +{ + NTSTATUS status; + HANDLE processHandle; + + if (!KphIsConnected()) + { + PhShowError(hWnd, KPH_ERROR_MESSAGE); + return FALSE; + } + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + ProcessId + ))) + { + OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo; + + handleFlagInfo.Inherit = !!(Attributes & OBJ_INHERIT); + handleFlagInfo.ProtectFromClose = !!(Attributes & OBJ_PROTECT_CLOSE); + + status = KphSetInformationObject( + processHandle, + Handle->Handle, + KphObjectHandleFlagInformation, + &handleFlagInfo, + sizeof(OBJECT_HANDLE_FLAG_INFORMATION) + ); + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhpShowErrorHandle(hWnd, L"set attributes of", Handle, status, 0); + return FALSE; + } + + return TRUE; +} diff --git a/ProcessHacker/affinity.c b/ProcessHacker/affinity.c new file mode 100644 index 0000000..fc02319 --- /dev/null +++ b/ProcessHacker/affinity.c @@ -0,0 +1,315 @@ +/* + * Process Hacker - + * process affinity editor + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The affinity dialog was originally created to support the modification + * of process affinity masks, but now supports modifying thread affinity + * and generic masks. + */ + +#include +#include +#include +#include + +typedef struct _AFFINITY_DIALOG_CONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + PPH_THREAD_ITEM ThreadItem; + ULONG_PTR AffinityMask; + ULONG_PTR NewAffinityMask; +} AFFINITY_DIALOG_CONTEXT, *PAFFINITY_DIALOG_CONTEXT; + +INT_PTR CALLBACK PhpProcessAffinityDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowProcessAffinityDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_opt_ PPH_THREAD_ITEM ThreadItem + ) +{ + AFFINITY_DIALOG_CONTEXT context; + + assert(!!ProcessItem != !!ThreadItem); // make sure we have one and not the other + + context.ProcessItem = ProcessItem; + context.ThreadItem = ThreadItem; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_AFFINITY), + ParentWindowHandle, + PhpProcessAffinityDlgProc, + (LPARAM)&context + ); +} + +BOOLEAN PhShowProcessAffinityDialog2( + _In_ HWND ParentWindowHandle, + _In_ ULONG_PTR AffinityMask, + _Out_ PULONG_PTR NewAffinityMask + ) +{ + AFFINITY_DIALOG_CONTEXT context; + + context.ProcessItem = NULL; + context.ThreadItem = NULL; + context.AffinityMask = AffinityMask; + + if (DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_AFFINITY), + ParentWindowHandle, + PhpProcessAffinityDlgProc, + (LPARAM)&context + ) == IDOK) + { + *NewAffinityMask = context.NewAffinityMask; + + return TRUE; + } + else + { + return FALSE; + } +} + +static INT_PTR CALLBACK PhpProcessAffinityDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + PAFFINITY_DIALOG_CONTEXT context = (PAFFINITY_DIALOG_CONTEXT)lParam; + SYSTEM_BASIC_INFORMATION systemBasicInfo; + ULONG_PTR systemAffinityMask; + ULONG_PTR affinityMask; + ULONG i; + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + systemAffinityMask = 0; + + if (context->ProcessItem) + { + HANDLE processHandle; + PROCESS_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + context->ProcessItem->ProcessId + ))) + { + status = PhGetProcessBasicInformation(processHandle, &basicInfo); + + if (NT_SUCCESS(status)) + affinityMask = basicInfo.AffinityMask; + + NtClose(processHandle); + } + } + else if (context->ThreadItem) + { + HANDLE threadHandle; + THREAD_BASIC_INFORMATION basicInfo; + HANDLE processHandle; + PROCESS_BASIC_INFORMATION processBasicInfo; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + ThreadQueryAccess, + context->ThreadItem->ThreadId + ))) + { + status = PhGetThreadBasicInformation(threadHandle, &basicInfo); + + if (NT_SUCCESS(status)) + { + affinityMask = basicInfo.AffinityMask; + + // A thread's affinity mask is restricted by the process affinity mask, + // so use that as the system affinity mask. + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + basicInfo.ClientId.UniqueProcess + ))) + { + if (NT_SUCCESS(PhGetProcessBasicInformation(processHandle, &processBasicInfo))) + systemAffinityMask = processBasicInfo.AffinityMask; + + NtClose(processHandle); + } + } + + NtClose(threadHandle); + } + } + else + { + affinityMask = context->AffinityMask; + status = STATUS_SUCCESS; + } + + if (NT_SUCCESS(status) && systemAffinityMask == 0) + { + status = NtQuerySystemInformation( + SystemBasicInformation, + &systemBasicInfo, + sizeof(SYSTEM_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + systemAffinityMask = systemBasicInfo.ActiveProcessorsAffinityMask; + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus(hwndDlg, L"Unable to retrieve the affinity", status, 0); + EndDialog(hwndDlg, IDCANCEL); + break; + } + + // Disable the CPU checkboxes which aren't part of the system affinity mask, + // and check the CPU checkboxes which are part of the affinity mask. + + for (i = 0; i < 8 * 8; i++) + { + if ((i < sizeof(ULONG_PTR) * 8) && ((systemAffinityMask >> i) & 0x1)) + { + if ((affinityMask >> i) & 0x1) + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_CPU0 + i), BST_CHECKED); + } + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CPU0 + i), FALSE); + } + } + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + NTSTATUS status; + PAFFINITY_DIALOG_CONTEXT context = (PAFFINITY_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + ULONG i; + ULONG_PTR affinityMask; + + // Work out the affinity mask. + + affinityMask = 0; + + for (i = 0; i < sizeof(ULONG_PTR) * 8; i++) + { + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_CPU0 + i)) == BST_CHECKED) + affinityMask |= (ULONG_PTR)1 << i; + } + + if (context->ProcessItem) + { + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + context->ProcessItem->ProcessId + ))) + { + status = PhSetProcessAffinityMask(processHandle, affinityMask); + NtClose(processHandle); + } + } + else if (context->ThreadItem) + { + HANDLE threadHandle; + + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + ThreadSetAccess, + context->ThreadItem->ThreadId + ))) + { + status = PhSetThreadAffinityMask(threadHandle, affinityMask); + NtClose(threadHandle); + } + } + else + { + context->NewAffinityMask = affinityMask; + status = STATUS_SUCCESS; + } + + if (NT_SUCCESS(status)) + EndDialog(hwndDlg, IDOK); + else + PhShowStatus(hwndDlg, L"Unable to set the affinity", status, 0); + } + break; + case IDC_SELECTALL: + case IDC_DESELECTALL: + { + ULONG i; + + for (i = 0; i < sizeof(ULONG_PTR) * 8; i++) + { + HWND checkBox = GetDlgItem(hwndDlg, IDC_CPU0 + i); + + if (IsWindowEnabled(checkBox)) + Button_SetCheck(checkBox, LOWORD(wParam) == IDC_SELECTALL ? BST_CHECKED : BST_UNCHECKED); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/anawait.c b/ProcessHacker/anawait.c new file mode 100644 index 0000000..91fdb5b --- /dev/null +++ b/ProcessHacker/anawait.c @@ -0,0 +1,1074 @@ +/* + * Process Hacker - + * thread wait analysis + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * There are two ways of seeing what a thread is waiting on. The first method + * is to walk the stack of a thread and read the arguments to whatever system + * call it is blocking on; this only works on x86 because on x64 the arguments + * are passed in registers (at least the first four are). The second method + * involves using the ThreadLastSystemCall info class for NtQueryInformationThread + * to retrieve the first argument to the system call the thread is blocking on. + * This is obviously only useful for NtWaitForSingleObject. + * + * There are other methods for specific scenarios, like USER messages and ALPC + * calls. + */ + +#include +#include +#include +#include + +typedef HWND (WINAPI *_GetSendMessageReceiver)( + _In_ HANDLE ThreadId + ); + +typedef NTSTATUS (NTAPI *_NtAlpcQueryInformation)( + _In_ HANDLE PortHandle, + _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass, + _Out_writes_bytes_(Length) PVOID PortInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +typedef struct _ANALYZE_WAIT_CONTEXT +{ + BOOLEAN Found; + BOOLEAN IsWow64; + HANDLE ProcessId; + HANDLE ThreadId; + HANDLE ProcessHandle; + + PPH_SYMBOL_PROVIDER SymbolProvider; + PH_STRING_BUILDER StringBuilder; + + PVOID PrevParams[4]; +} ANALYZE_WAIT_CONTEXT, *PANALYZE_WAIT_CONTEXT; + +VOID PhpAnalyzeWaitPassive( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId + ); + +BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( + _In_ PPH_THREAD_STACK_FRAME StackFrame, + _In_opt_ PVOID Context + ); + +VOID PhpAnalyzeWaitFallbacks( + _In_ PANALYZE_WAIT_CONTEXT Context + ); + +VOID PhpInitializeServiceNumbers( + VOID + ); + +PPH_STRING PhpaGetHandleString( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle + ); + +VOID PhpGetWfmoInformation( + _In_ HANDLE ProcessHandle, + _In_ BOOLEAN IsWow64, + _In_ ULONG NumberOfHandles, + _In_ PHANDLE AddressOfHandles, + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _Inout_ PPH_STRING_BUILDER StringBuilder + ); + +PPH_STRING PhpaGetSendMessageReceiver( + _In_ HANDLE ThreadId + ); + +PPH_STRING PhpaGetAlpcInformation( + _In_ HANDLE ThreadId + ); + +static PH_INITONCE ServiceNumbersInitOnce = PH_INITONCE_INIT; +static USHORT NumberForWfso = -1; +static USHORT NumberForWfmo = -1; +static USHORT NumberForRf = -1; + +VOID PhUiAnalyzeWaitThread( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PPH_SYMBOL_PROVIDER SymbolProvider + ) +{ + NTSTATUS status; + HANDLE threadHandle; +#ifdef _WIN64 + HANDLE processHandle; + BOOLEAN isWow64; +#endif + CLIENT_ID clientId; + ANALYZE_WAIT_CONTEXT context; + +#ifdef _WIN64 + // Determine if the process is WOW64. If not, we use the passive method. + + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessId))) + { + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return; + } + + if (!NT_SUCCESS(status = PhGetProcessIsWow64(processHandle, &isWow64)) || !isWow64) + { + PhpAnalyzeWaitPassive(hWnd, ProcessId, ThreadId); + return; + } + + NtClose(processHandle); +#endif + + if (!NT_SUCCESS(status = PhOpenThread( + &threadHandle, + ThreadQueryAccess | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, + ThreadId + ))) + { + PhShowStatus(hWnd, L"Unable to open the thread", status, 0); + return; + } + + context.ProcessId = ProcessId; + context.ThreadId = ThreadId; + + context.ProcessHandle = SymbolProvider->ProcessHandle; + context.SymbolProvider = SymbolProvider; + PhInitializeStringBuilder(&context.StringBuilder, 100); + + clientId.UniqueProcess = ProcessId; + clientId.UniqueThread = ThreadId; + + PhWalkThreadStack( + threadHandle, + SymbolProvider->ProcessHandle, + &clientId, + SymbolProvider, + PH_WALK_I386_STACK, + PhpWalkThreadStackAnalyzeCallback, + &context + ); + NtClose(threadHandle); + + PhpAnalyzeWaitFallbacks(&context); + + if (context.Found) + { + PhShowInformationDialog(hWnd, context.StringBuilder.String->Buffer, 0); + } + else + { + PhShowInformation(hWnd, L"The thread does not appear to be waiting."); + } + + PhDeleteStringBuilder(&context.StringBuilder); +} + +VOID PhpAnalyzeWaitPassive( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId + ) +{ + NTSTATUS status; + HANDLE processHandle; + HANDLE threadHandle; + THREAD_LAST_SYSCALL_INFORMATION lastSystemCall; + PH_STRING_BUILDER stringBuilder; + PPH_STRING string; + + PhpInitializeServiceNumbers(); + + if (!NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_GET_CONTEXT, ThreadId))) + { + PhShowStatus(hWnd, L"Unable to open the thread", status, 0); + return; + } + + if (!NT_SUCCESS(status = NtQueryInformationThread( + threadHandle, + ThreadLastSystemCall, + &lastSystemCall, + sizeof(THREAD_LAST_SYSCALL_INFORMATION), + NULL + ))) + { + NtClose(threadHandle); + PhShowInformation(hWnd, L"Unable to determine whether the thread is waiting."); + return; + } + + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE, ProcessId))) + { + NtClose(threadHandle); + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return; + } + + PhInitializeStringBuilder(&stringBuilder, 100); + + if (lastSystemCall.SystemCallNumber == NumberForWfso) + { + string = PhpaGetHandleString(processHandle, lastSystemCall.FirstArgument); + + PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for:\r\n"); + PhAppendStringBuilder(&stringBuilder, &string->sr); + } + else if (lastSystemCall.SystemCallNumber == NumberForWfmo) + { + PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for multiple (%u) objects.", PtrToUlong(lastSystemCall.FirstArgument)); + } + else if (lastSystemCall.SystemCallNumber == NumberForRf) + { + string = PhpaGetHandleString(processHandle, lastSystemCall.FirstArgument); + + PhAppendFormatStringBuilder(&stringBuilder, L"Thread is waiting for file I/O:\r\n"); + PhAppendStringBuilder(&stringBuilder, &string->sr); + } + else + { + string = PhpaGetSendMessageReceiver(ThreadId); + + if (string) + { + PhAppendStringBuilder2(&stringBuilder, L"Thread is sending a USER message:\r\n"); + PhAppendStringBuilder(&stringBuilder, &string->sr); + } + else + { + string = PhpaGetAlpcInformation(ThreadId); + + if (string) + { + PhAppendStringBuilder2(&stringBuilder, L"Thread is waiting for an ALPC port:\r\n"); + PhAppendStringBuilder(&stringBuilder, &string->sr); + } + } + } + + if (stringBuilder.String->Length == 0) + PhAppendStringBuilder2(&stringBuilder, L"Unable to determine why the thread is waiting."); + + PhShowInformationDialog(hWnd, stringBuilder.String->Buffer, 0); + + PhDeleteStringBuilder(&stringBuilder); + NtClose(processHandle); + NtClose(threadHandle); +} + +static BOOLEAN NTAPI PhpWalkThreadStackAnalyzeCallback( + _In_ PPH_THREAD_STACK_FRAME StackFrame, + _In_opt_ PVOID Context + ) +{ + PANALYZE_WAIT_CONTEXT context = (PANALYZE_WAIT_CONTEXT)Context; + PPH_STRING name; + + name = PhGetSymbolFromAddress( + context->SymbolProvider, + (ULONG64)StackFrame->PcAddress, + NULL, + NULL, + NULL, + NULL + ); + + if (!name) + return TRUE; + + context->Found = TRUE; + +#define FUNC_MATCH(Name) PhStartsWithString2(name, L##Name, TRUE) +#define NT_FUNC_MATCH(Name) ( \ + PhStartsWithString2(name, L"ntdll.dll!Nt" L##Name, TRUE) || \ + PhStartsWithString2(name, L"ntdll.dll!Zw" L##Name, TRUE) \ + ) + + if (!name) + { + // Dummy + } + else if (FUNC_MATCH("kernel32.dll!Sleep")) + { + PhAppendFormatStringBuilder( + &context->StringBuilder, + L"Thread is sleeping. Timeout: %u milliseconds.", + PtrToUlong(StackFrame->Params[0]) + ); + } + else if (NT_FUNC_MATCH("DelayExecution")) + { + BOOLEAN alertable = !!StackFrame->Params[0]; + PVOID timeoutAddress = StackFrame->Params[1]; + LARGE_INTEGER timeout; + + if (NT_SUCCESS(NtReadVirtualMemory( + context->ProcessHandle, + timeoutAddress, + &timeout, + sizeof(LARGE_INTEGER), + NULL + ))) + { + if (timeout.QuadPart < 0) + { + PhAppendFormatStringBuilder( + &context->StringBuilder, + L"Thread is sleeping. Timeout: %I64u milliseconds.", + -timeout.QuadPart / PH_TIMEOUT_MS + ); + } + else + { + // TODO + } + } + else + { + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is sleeping." + ); + } + } + else if (NT_FUNC_MATCH("DeviceIoControlFile")) + { + HANDLE handle = (HANDLE)StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for an I/O control request:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if (NT_FUNC_MATCH("FsControlFile")) + { + HANDLE handle = StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for a FS control request:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if (NT_FUNC_MATCH("QueryObject")) + { + HANDLE handle = StackFrame->Params[0]; + + // Use the KiFastSystemCall args if the handle we have is wrong. + if ((ULONG_PTR)handle % 4 != 0 || !handle) + handle = context->PrevParams[1]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is querying an object:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if (NT_FUNC_MATCH("ReadFile") || NT_FUNC_MATCH("WriteFile")) + { + HANDLE handle = StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for file I/O:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if (NT_FUNC_MATCH("RemoveIoCompletion")) + { + HANDLE handle = StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for an I/O completion port:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if ( + NT_FUNC_MATCH("ReplyWaitReceivePort") || + NT_FUNC_MATCH("RequestWaitReplyPort") || + NT_FUNC_MATCH("AlpcSendWaitReceivePort") + ) + { + HANDLE handle = StackFrame->Params[0]; + PPH_STRING alpcInfo; + + PhAppendStringBuilder2( + &context->StringBuilder, + WindowsVersion >= WINDOWS_VISTA ? L"Thread is waiting for an ALPC port:\r\n" : L"Thread is waiting for a LPC port:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + + if (alpcInfo = PhpaGetAlpcInformation(context->ThreadId)) + { + PhAppendStringBuilder2( + &context->StringBuilder, + L"\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &alpcInfo->sr + ); + } + } + else if ( + NT_FUNC_MATCH("SetHighWaitLowEventPair") || + NT_FUNC_MATCH("SetLowWaitHighEventPair") || + NT_FUNC_MATCH("WaitHighEventPair") || + NT_FUNC_MATCH("WaitLowEventPair") + ) + { + HANDLE handle = StackFrame->Params[0]; + + if ((ULONG_PTR)handle % 4 != 0 || !handle) + handle = context->PrevParams[1]; + + PhAppendFormatStringBuilder( + &context->StringBuilder, + L"Thread is waiting (%s) for an event pair:\r\n", + name->Buffer + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if ( + FUNC_MATCH("user32.dll!NtUserGetMessage") || + FUNC_MATCH("user32.dll!NtUserWaitMessage") + ) + { + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for a USER message.\r\n" + ); + } + else if (FUNC_MATCH("user32.dll!NtUserMessageCall")) + { + PPH_STRING receiverString; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is sending a USER message:\r\n" + ); + + receiverString = PhpaGetSendMessageReceiver(context->ThreadId); + + if (receiverString) + { + PhAppendStringBuilder(&context->StringBuilder, &receiverString->sr); + PhAppendStringBuilder2(&context->StringBuilder, L"\r\n"); + } + else + { + PhAppendStringBuilder2(&context->StringBuilder, L"Unknown.\r\n"); + } + } + else if (NT_FUNC_MATCH("WaitForDebugEvent")) + { + HANDLE handle = StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for a debug event:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if ( + NT_FUNC_MATCH("WaitForKeyedEvent") || + NT_FUNC_MATCH("ReleaseKeyedEvent") + ) + { + HANDLE handle = StackFrame->Params[0]; + PVOID key = StackFrame->Params[1]; + + PhAppendFormatStringBuilder( + &context->StringBuilder, + L"Thread is waiting (%s) for a keyed event (key 0x%Ix):\r\n", + name->Buffer, + key + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if ( + NT_FUNC_MATCH("WaitForMultipleObjects") || + FUNC_MATCH("kernel32.dll!WaitForMultipleObjects") + ) + { + ULONG numberOfHandles = PtrToUlong(StackFrame->Params[0]); + PVOID addressOfHandles = StackFrame->Params[1]; + WAIT_TYPE waitType = (WAIT_TYPE)StackFrame->Params[2]; + BOOLEAN alertable = !!StackFrame->Params[3]; + + if (numberOfHandles > MAXIMUM_WAIT_OBJECTS) + { + numberOfHandles = PtrToUlong(context->PrevParams[1]); + addressOfHandles = context->PrevParams[2]; + waitType = (WAIT_TYPE)context->PrevParams[3]; + alertable = FALSE; + } + + PhpGetWfmoInformation( + context->ProcessHandle, + TRUE, // on x64 this function is only called for WOW64 processes + numberOfHandles, + addressOfHandles, + waitType, + alertable, + &context->StringBuilder + ); + } + else if ( + NT_FUNC_MATCH("WaitForSingleObject") || + FUNC_MATCH("kernel32.dll!WaitForSingleObject") + ) + { + HANDLE handle = StackFrame->Params[0]; + BOOLEAN alertable = !!StackFrame->Params[1]; + + if ((ULONG_PTR)handle % 4 != 0 || !handle) + { + handle = context->PrevParams[1]; + alertable = !!context->PrevParams[2]; + } + + PhAppendFormatStringBuilder( + &context->StringBuilder, + L"Thread is waiting (%s) for:\r\n", + alertable ? L"alertable" : L"non-alertable" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else if (NT_FUNC_MATCH("WaitForWorkViaWorkerFactory")) + { + HANDLE handle = StackFrame->Params[0]; + + PhAppendStringBuilder2( + &context->StringBuilder, + L"Thread is waiting for work from a worker factory:\r\n" + ); + PhAppendStringBuilder( + &context->StringBuilder, + &PhpaGetHandleString(context->ProcessHandle, handle)->sr + ); + } + else + { + context->Found = FALSE; + } + + PhDereferenceObject(name); + memcpy(&context->PrevParams, StackFrame->Params, sizeof(StackFrame->Params)); + + return !context->Found; +} + +static VOID PhpAnalyzeWaitFallbacks( + _In_ PANALYZE_WAIT_CONTEXT Context + ) +{ + PPH_STRING info; + + // We didn't detect NtUserMessageCall, but this may still apply due to another + // win32k system call (e.g. from EnableWindow). + if (!Context->Found && (info = PhpaGetSendMessageReceiver(Context->ThreadId))) + { + PhAppendStringBuilder2( + &Context->StringBuilder, + L"Thread is sending a USER message:\r\n" + ); + PhAppendStringBuilder(&Context->StringBuilder, &info->sr); + PhAppendStringBuilder2(&Context->StringBuilder, L"\r\n"); + + Context->Found = TRUE; + } + + // Nt(Alpc)ConnectPort doesn't get detected anywhere else. + if (!Context->Found && (info = PhpaGetAlpcInformation(Context->ThreadId))) + { + PhAppendStringBuilder2( + &Context->StringBuilder, + L"Thread is waiting for an ALPC port:\r\n" + ); + PhAppendStringBuilder(&Context->StringBuilder, &info->sr); + PhAppendStringBuilder2(&Context->StringBuilder, L"\r\n"); + + Context->Found = TRUE; + } +} + +static BOOLEAN PhpWaitUntilThreadIsWaiting( + _In_ HANDLE ThreadHandle + ) +{ + ULONG attempts; + BOOLEAN isWaiting = FALSE; + THREAD_BASIC_INFORMATION basicInfo; + + if (!NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo))) + return FALSE; + + for (attempts = 0; attempts < 5; attempts++) + { + PVOID processes; + PSYSTEM_PROCESS_INFORMATION processInfo; + ULONG i; + LARGE_INTEGER interval; + + interval.QuadPart = -100 * PH_TIMEOUT_MS; + NtDelayExecution(FALSE, &interval); + + if (!NT_SUCCESS(PhEnumProcesses(&processes))) + break; + + processInfo = PhFindProcessInformation(processes, basicInfo.ClientId.UniqueProcess); + + if (processInfo) + { + for (i = 0; i < processInfo->NumberOfThreads; i++) + { + if ( + processInfo->Threads[i].ClientId.UniqueThread == basicInfo.ClientId.UniqueThread && + processInfo->Threads[i].ThreadState == Waiting && + (processInfo->Threads[i].WaitReason == UserRequest || + processInfo->Threads[i].WaitReason == Executive) + ) + { + isWaiting = TRUE; + break; + } + } + } + + PhFree(processes); + + if (isWaiting) + break; + + interval.QuadPart = -500 * PH_TIMEOUT_MS; + NtDelayExecution(FALSE, &interval); + } + + return isWaiting; +} + +static VOID PhpGetThreadLastSystemCallNumber( + _In_ HANDLE ThreadHandle, + _Out_ PUSHORT LastSystemCallNumber + ) +{ + THREAD_LAST_SYSCALL_INFORMATION lastSystemCall; + + if (NT_SUCCESS(NtQueryInformationThread( + ThreadHandle, + ThreadLastSystemCall, + &lastSystemCall, + sizeof(THREAD_LAST_SYSCALL_INFORMATION), + NULL + ))) + { + *LastSystemCallNumber = lastSystemCall.SystemCallNumber; + } +} + +static NTSTATUS PhpWfsoThreadStart( + _In_ PVOID Parameter + ) +{ + HANDLE eventHandle; + LARGE_INTEGER timeout; + + eventHandle = Parameter; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + NtWaitForSingleObject(eventHandle, FALSE, &timeout); + + return STATUS_SUCCESS; +} + +static NTSTATUS PhpWfmoThreadStart( + _In_ PVOID Parameter + ) +{ + HANDLE eventHandle; + LARGE_INTEGER timeout; + + eventHandle = Parameter; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + NtWaitForMultipleObjects(1, &eventHandle, WaitAll, FALSE, &timeout); + + return STATUS_SUCCESS; +} + +static NTSTATUS PhpRfThreadStart( + _In_ PVOID Parameter + ) +{ + HANDLE fileHandle; + IO_STATUS_BLOCK isb; + ULONG data; + + fileHandle = Parameter; + + NtReadFile(fileHandle, NULL, NULL, NULL, &isb, &data, sizeof(ULONG), NULL, NULL); + + return STATUS_SUCCESS; +} + +static VOID PhpInitializeServiceNumbers( + VOID + ) +{ + if (PhBeginInitOnce(&ServiceNumbersInitOnce)) + { + NTSTATUS status; + HANDLE eventHandle; + HANDLE threadHandle; + HANDLE pipeReadHandle; + HANDLE pipeWriteHandle; + + // The ThreadLastSystemCall info class only works when the thread is in the Waiting + // state. We'll create a thread which blocks on an event object we create, then wait + // until it is in the Waiting state. Only then can we query the thread using + // ThreadLastSystemCall. + + // NtWaitForSingleObject + + status = NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + + if (NT_SUCCESS(status)) + { + if (threadHandle = PhCreateThread(0, PhpWfsoThreadStart, eventHandle)) + { + if (PhpWaitUntilThreadIsWaiting(threadHandle)) + { + PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForWfso); + } + + // Allow the thread to exit. + NtSetEvent(eventHandle, NULL); + NtClose(threadHandle); + } + + NtClose(eventHandle); + } + + // NtWaitForMultipleObjects + + status = NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + + if (NT_SUCCESS(status)) + { + if (threadHandle = PhCreateThread(0, PhpWfmoThreadStart, eventHandle)) + { + if (PhpWaitUntilThreadIsWaiting(threadHandle)) + { + PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForWfmo); + } + + NtSetEvent(eventHandle, NULL); + NtClose(threadHandle); + } + + NtClose(eventHandle); + } + + // NtReadFile + + if (CreatePipe(&pipeReadHandle, &pipeWriteHandle, NULL, 0)) + { + if (threadHandle = PhCreateThread(0, PhpRfThreadStart, pipeReadHandle)) + { + ULONG data = 0; + IO_STATUS_BLOCK isb; + + if (PhpWaitUntilThreadIsWaiting(threadHandle)) + { + PhpGetThreadLastSystemCallNumber(threadHandle, &NumberForRf); + } + + NtWriteFile(pipeWriteHandle, NULL, NULL, NULL, &isb, &data, sizeof(data), NULL, NULL); + NtClose(threadHandle); + } + + NtClose(pipeReadHandle); + NtClose(pipeWriteHandle); + } + + PhEndInitOnce(&ServiceNumbersInitOnce); + } +} + +static PPH_STRING PhpaGetHandleString( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle + ) +{ + PPH_STRING typeName = NULL; + PPH_STRING name = NULL; + PPH_STRING result; + + PhGetHandleInformation( + ProcessHandle, + Handle, + -1, + NULL, + &typeName, + NULL, + &name + ); + PH_AUTO(typeName); + PH_AUTO(name); + + if (typeName && name) + { + result = PhaFormatString( + L"Handle 0x%Ix (%s): %s", + Handle, + typeName->Buffer, + !PhIsNullOrEmptyString(name) ? name->Buffer : L"(unnamed object)" + ); + } + else + { + result = PhaFormatString( + L"Handle 0x%Ix: (error querying handle)", + Handle + ); + } + + return result; +} + +static VOID PhpGetWfmoInformation( + _In_ HANDLE ProcessHandle, + _In_ BOOLEAN IsWow64, + _In_ ULONG NumberOfHandles, + _In_ PHANDLE AddressOfHandles, + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _Inout_ PPH_STRING_BUILDER StringBuilder + ) +{ + NTSTATUS status; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + ULONG i; + + status = STATUS_SUCCESS; + + if (NumberOfHandles <= MAXIMUM_WAIT_OBJECTS) + { +#ifdef _WIN64 + if (IsWow64) + { + ULONG handles32[MAXIMUM_WAIT_OBJECTS]; + + if (NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + AddressOfHandles, + handles32, + NumberOfHandles * sizeof(ULONG), + NULL + ))) + { + for (i = 0; i < NumberOfHandles; i++) + handles[i] = UlongToHandle(handles32[i]); + } + } + else + { +#endif + status = NtReadVirtualMemory( + ProcessHandle, + AddressOfHandles, + handles, + NumberOfHandles * sizeof(HANDLE), + NULL + ); +#ifdef _WIN64 + } +#endif + + if (NT_SUCCESS(status)) + { + PhAppendFormatStringBuilder( + StringBuilder, + L"Thread is waiting (%s, %s) for:\r\n", + Alertable ? L"alertable" : L"non-alertable", + WaitType == WaitAll ? L"wait all" : L"wait any" + ); + + for (i = 0; i < NumberOfHandles; i++) + { + PhAppendStringBuilder( + StringBuilder, + &PhpaGetHandleString(ProcessHandle, handles[i])->sr + ); + PhAppendStringBuilder2( + StringBuilder, + L"\r\n" + ); + } + } + } + + if (!NT_SUCCESS(status) || NumberOfHandles > MAXIMUM_WAIT_OBJECTS) + { + PhAppendStringBuilder2( + StringBuilder, + L"Thread is waiting for multiple objects." + ); + } +} + +static PPH_STRING PhpaGetSendMessageReceiver( + _In_ HANDLE ThreadId + ) +{ + static _GetSendMessageReceiver GetSendMessageReceiver_I; + + HWND windowHandle; + ULONG threadId; + ULONG processId; + CLIENT_ID clientId; + PPH_STRING clientIdName; + WCHAR windowClass[64]; + PPH_STRING windowText; + + // GetSendMessageReceiver is an undocumented function exported by + // user32.dll. It retrieves the handle of the window which a thread + // is sending a message to. + + if (!GetSendMessageReceiver_I) + GetSendMessageReceiver_I = PhGetModuleProcAddress(L"user32.dll", "GetSendMessageReceiver"); + + if (!GetSendMessageReceiver_I) + return NULL; + + windowHandle = GetSendMessageReceiver_I(ThreadId); + + if (!windowHandle) + return NULL; + + threadId = GetWindowThreadProcessId(windowHandle, &processId); + + clientId.UniqueProcess = UlongToHandle(processId); + clientId.UniqueThread = UlongToHandle(threadId); + clientIdName = PH_AUTO(PhGetClientIdName(&clientId)); + + if (!GetClassName(windowHandle, windowClass, sizeof(windowClass) / sizeof(WCHAR))) + windowClass[0] = 0; + + windowText = PH_AUTO(PhGetWindowText(windowHandle)); + + return PhaFormatString(L"Window 0x%Ix (%s): %s \"%s\"", windowHandle, clientIdName->Buffer, windowClass, PhGetStringOrEmpty(windowText)); +} + +static PPH_STRING PhpaGetAlpcInformation( + _In_ HANDLE ThreadId + ) +{ + static _NtAlpcQueryInformation NtAlpcQueryInformation_I; + + NTSTATUS status; + PPH_STRING string = NULL; + HANDLE threadHandle; + PALPC_SERVER_INFORMATION serverInfo; + ULONG bufferLength; + + if (!NtAlpcQueryInformation_I) + NtAlpcQueryInformation_I = PhGetModuleProcAddress(L"ntdll.dll", "NtAlpcQueryInformation"); + + if (!NtAlpcQueryInformation_I) + return NULL; + + if (!NT_SUCCESS(PhOpenThread(&threadHandle, THREAD_QUERY_INFORMATION, ThreadId))) + return NULL; + + bufferLength = 0x110; + serverInfo = PhAllocate(bufferLength); + serverInfo->In.ThreadHandle = threadHandle; + + status = NtAlpcQueryInformation_I(NULL, AlpcServerInformation, serverInfo, bufferLength, &bufferLength); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(serverInfo); + serverInfo = PhAllocate(bufferLength); + serverInfo->In.ThreadHandle = threadHandle; + + status = NtAlpcQueryInformation_I(NULL, AlpcServerInformation, serverInfo, bufferLength, &bufferLength); + } + + if (NT_SUCCESS(status) && serverInfo->Out.ThreadBlocked) + { + CLIENT_ID clientId; + PPH_STRING clientIdName; + + clientId.UniqueProcess = serverInfo->Out.ConnectedProcessId; + clientId.UniqueThread = NULL; + clientIdName = PH_AUTO(PhGetClientIdName(&clientId)); + + string = PhaFormatString(L"ALPC Port: %.*s (%s)", serverInfo->Out.ConnectionPortName.Length / 2, serverInfo->Out.ConnectionPortName.Buffer, clientIdName->Buffer); + } + + PhFree(serverInfo); + NtClose(threadHandle); + + return string; +} diff --git a/ProcessHacker/appsup.c b/ProcessHacker/appsup.c new file mode 100644 index 0000000..926ddb1 --- /dev/null +++ b/ProcessHacker/appsup.c @@ -0,0 +1,2208 @@ +/* + * Process Hacker - + * application support functions + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mxml/mxml.h" +#include "pcre/pcre2.h" +#include + +#pragma warning(push) +#pragma warning(disable: 4091) // Ignore 'no variable declared on typedef' +#include +#pragma warning(pop) + +#include + +typedef LONG (WINAPI *_GetPackageFullName)( + _In_ HANDLE hProcess, + _Inout_ UINT32 *packageFullNameLength, + _Out_opt_ PWSTR packageFullName + ); + +typedef LONG (WINAPI *_GetPackagePath)( + _In_ PACKAGE_ID *packageId, + _Reserved_ UINT32 reserved, + _Inout_ UINT32 *pathLength, + _Out_opt_ PWSTR path + ); + +typedef LONG (WINAPI *_PackageIdFromFullName)( + _In_ PCWSTR packageFullName, + _In_ UINT32 flags, + _Inout_ UINT32 *bufferLength, + _Out_opt_ BYTE *buffer + ); + +GUID XP_CONTEXT_GUID = { 0xbeb1b341, 0x6837, 0x4c83, { 0x83, 0x66, 0x2b, 0x45, 0x1e, 0x7c, 0xe6, 0x9b } }; +GUID VISTA_CONTEXT_GUID = { 0xe2011457, 0x1546, 0x43c5, { 0xa5, 0xfe, 0x00, 0x8d, 0xee, 0xe3, 0xd3, 0xf0 } }; +GUID WIN7_CONTEXT_GUID = { 0x35138b9a, 0x5d96, 0x4fbd, { 0x8e, 0x2d, 0xa2, 0x44, 0x02, 0x25, 0xf9, 0x3a } }; +GUID WIN8_CONTEXT_GUID = { 0x4a2f28e3, 0x53b9, 0x4441, { 0xba, 0x9c, 0xd6, 0x9d, 0x4a, 0x4a, 0x6e, 0x38 } }; +GUID WINBLUE_CONTEXT_GUID = { 0x1f676c76, 0x80e1, 0x4239, { 0x95, 0xbb, 0x83, 0xd0, 0xf6, 0xd0, 0xda, 0x78 } }; +GUID WINTHRESHOLD_CONTEXT_GUID = { 0x8e0f7a12, 0xbfb3, 0x4fe8, { 0xb9, 0xa5, 0x48, 0xfd, 0x50, 0xa1, 0x5a, 0x9a } }; + +/** + * Determines whether a process is suspended. + * + * \param Process The SYSTEM_PROCESS_INFORMATION structure + * of the process. + */ +BOOLEAN PhGetProcessIsSuspended( + _In_ PSYSTEM_PROCESS_INFORMATION Process + ) +{ + ULONG i; + + for (i = 0; i < Process->NumberOfThreads; i++) + { + if ( + Process->Threads[i].ThreadState != Waiting || + Process->Threads[i].WaitReason != Suspended + ) + return FALSE; + } + + return Process->NumberOfThreads != 0; +} + +/** + * Determines the OS compatibility context of a process. + * + * \param ProcessHandle A handle to a process. + * \param Guid A variable which receives a GUID identifying an + * operating system version. + */ +NTSTATUS PhGetProcessSwitchContext( + _In_ HANDLE ProcessHandle, + _Out_ PGUID Guid + ) +{ + NTSTATUS status; + PROCESS_BASIC_INFORMATION basicInfo; +#ifdef _WIN64 + PVOID peb32; + ULONG data32; +#endif + PVOID data; + + // Reverse-engineered from WdcGetProcessSwitchContext (wdc.dll). + // On Windows 8, the function is now SdbGetAppCompatData (apphelp.dll). + // On Windows 10, the function is again WdcGetProcessSwitchContext. + +#ifdef _WIN64 + if (NT_SUCCESS(PhGetProcessPeb32(ProcessHandle, &peb32)) && peb32) + { + if (WindowsVersion >= WINDOWS_8) + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, pShimData)), + &data32, + sizeof(ULONG), + NULL + ))) + return status; + } + else + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, pContextData)), + &data32, + sizeof(ULONG), + NULL + ))) + return status; + } + + data = UlongToPtr(data32); + } + else + { +#endif + if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo))) + return status; + + if (WindowsVersion >= WINDOWS_8) + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, pShimData)), + &data, + sizeof(PVOID), + NULL + ))) + return status; + } + else + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, pContextData)), + &data, + sizeof(PVOID), + NULL + ))) + return status; + } +#ifdef _WIN64 + } +#endif + + if (!data) + return STATUS_UNSUCCESSFUL; // no compatibility context data + + if (WindowsVersion >= WINDOWS_10) + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(data, 2040 + 24), // Magic value from SbReadProcContextByHandle + Guid, + sizeof(GUID), + NULL + ))) + return status; + } + else if (WindowsVersion >= WINDOWS_8_1) + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(data, 2040 + 16), // Magic value from SbReadProcContextByHandle + Guid, + sizeof(GUID), + NULL + ))) + return status; + } + else if (WindowsVersion >= WINDOWS_8) + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(data, 2040), // Magic value from SbReadProcContextByHandle + Guid, + sizeof(GUID), + NULL + ))) + return status; + } + else + { + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(data, 32), // Magic value from WdcGetProcessSwitchContext + Guid, + sizeof(GUID), + NULL + ))) + return status; + } + + return STATUS_SUCCESS; +} + +PPH_STRING PhGetProcessPackageFullName( + _In_ HANDLE ProcessHandle + ) +{ + static _GetPackageFullName getPackageFullName = NULL; + + LONG result; + PPH_STRING name; + ULONG nameLength; + + if (!getPackageFullName) + getPackageFullName = PhGetModuleProcAddress(L"kernel32.dll", "GetPackageFullName"); + if (!getPackageFullName) + return NULL; + + nameLength = 101; + name = PhCreateStringEx(NULL, (nameLength - 1) * 2); + + result = getPackageFullName(ProcessHandle, &nameLength, name->Buffer); + + if (result == ERROR_INSUFFICIENT_BUFFER) + { + PhDereferenceObject(name); + name = PhCreateStringEx(NULL, (nameLength - 1) * 2); + + result = getPackageFullName(ProcessHandle, &nameLength, name->Buffer); + } + + if (result == ERROR_SUCCESS) + { + PhTrimToNullTerminatorString(name); + return name; + } + else + { + PhDereferenceObject(name); + return NULL; + } +} + +PACKAGE_ID *PhPackageIdFromFullName( + _In_ PWSTR PackageFullName + ) +{ + static _PackageIdFromFullName packageIdFromFullName = NULL; + + LONG result; + PVOID packageIdBuffer; + ULONG packageIdBufferSize; + + if (!packageIdFromFullName) + packageIdFromFullName = PhGetModuleProcAddress(L"kernel32.dll", "PackageIdFromFullName"); + if (!packageIdFromFullName) + return NULL; + + packageIdBufferSize = 100; + packageIdBuffer = PhAllocate(packageIdBufferSize); + + result = packageIdFromFullName(PackageFullName, PACKAGE_INFORMATION_BASIC, &packageIdBufferSize, (PBYTE)packageIdBuffer); + + if (result == ERROR_INSUFFICIENT_BUFFER) + { + PhFree(packageIdBuffer); + packageIdBuffer = PhAllocate(packageIdBufferSize); + + result = packageIdFromFullName(PackageFullName, PACKAGE_INFORMATION_BASIC, &packageIdBufferSize, (PBYTE)packageIdBuffer); + } + + if (result == ERROR_SUCCESS) + { + return packageIdBuffer; + } + else + { + PhFree(packageIdBuffer); + return NULL; + } +} + +PPH_STRING PhGetPackagePath( + _In_ PACKAGE_ID *PackageId + ) +{ + static _GetPackagePath getPackagePath = NULL; + + LONG result; + PPH_STRING path; + ULONG pathLength; + + if (!getPackagePath) + getPackagePath = PhGetModuleProcAddress(L"kernel32.dll", "GetPackagePath"); + if (!getPackagePath) + return NULL; + + pathLength = 101; + path = PhCreateStringEx(NULL, (pathLength - 1) * 2); + + result = getPackagePath(PackageId, 0, &pathLength, path->Buffer); + + if (result == ERROR_INSUFFICIENT_BUFFER) + { + PhDereferenceObject(path); + path = PhCreateStringEx(NULL, (pathLength - 1) * 2); + + result = getPackagePath(PackageId, 0, &pathLength, path->Buffer); + } + + if (result == ERROR_SUCCESS) + { + PhTrimToNullTerminatorString(path); + return path; + } + else + { + PhDereferenceObject(path); + return NULL; + } +} + +/** + * Determines the type of a process based on its image file name. + * + * \param ProcessHandle A handle to a process. + * \param KnownProcessType A variable which receives the process + * type. + */ +NTSTATUS PhGetProcessKnownType( + _In_ HANDLE ProcessHandle, + _Out_ PH_KNOWN_PROCESS_TYPE *KnownProcessType + ) +{ + NTSTATUS status; + PH_KNOWN_PROCESS_TYPE knownProcessType; + PROCESS_BASIC_INFORMATION basicInfo; + PH_STRINGREF systemRootPrefix; + PPH_STRING fileName; + PPH_STRING newFileName; + PH_STRINGREF name; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; +#endif + + if (!NT_SUCCESS(status = PhGetProcessBasicInformation( + ProcessHandle, + &basicInfo + ))) + return status; + + if (basicInfo.UniqueProcessId == SYSTEM_PROCESS_ID) + { + *KnownProcessType = SystemProcessType; + return STATUS_SUCCESS; + } + + PhGetSystemRoot(&systemRootPrefix); + + if (!NT_SUCCESS(status = PhGetProcessImageFileName( + ProcessHandle, + &fileName + ))) + { + return status; + } + + newFileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + name = newFileName->sr; + + knownProcessType = UnknownProcessType; + + if (PhStartsWithStringRef(&name, &systemRootPrefix, TRUE)) + { + // Skip the system root, and we now have three cases: + // 1. \\xyz.exe - Windows executable. + // 2. \\System32\\xyz.exe - system32 executable. + // 3. \\SysWow64\\xyz.exe - system32 executable + WOW64. + PhSkipStringRef(&name, systemRootPrefix.Length); + + if (PhEqualStringRef2(&name, L"\\explorer.exe", TRUE)) + { + knownProcessType = ExplorerProcessType; + } + else if ( + PhStartsWithStringRef2(&name, L"\\System32", TRUE) +#ifdef _WIN64 + || (PhStartsWithStringRef2(&name, L"\\SysWow64", TRUE) && (isWow64 = TRUE, TRUE)) // ugly but necessary +#endif + ) + { + // SysTem32 and SysWow64 are both 8 characters long. + PhSkipStringRef(&name, 9 * sizeof(WCHAR)); + + if (FALSE) + ; // Dummy + else if (PhEqualStringRef2(&name, L"\\smss.exe", TRUE)) + knownProcessType = SessionManagerProcessType; + else if (PhEqualStringRef2(&name, L"\\csrss.exe", TRUE)) + knownProcessType = WindowsSubsystemProcessType; + else if (PhEqualStringRef2(&name, L"\\wininit.exe", TRUE)) + knownProcessType = WindowsStartupProcessType; + else if (PhEqualStringRef2(&name, L"\\services.exe", TRUE)) + knownProcessType = ServiceControlManagerProcessType; + else if (PhEqualStringRef2(&name, L"\\lsass.exe", TRUE)) + knownProcessType = LocalSecurityAuthorityProcessType; + else if (PhEqualStringRef2(&name, L"\\lsm.exe", TRUE)) + knownProcessType = LocalSessionManagerProcessType; + else if (PhEqualStringRef2(&name, L"\\winlogon.exe", TRUE)) + knownProcessType = WindowsLogonProcessType; + else if (PhEqualStringRef2(&name, L"\\svchost.exe", TRUE)) + knownProcessType = ServiceHostProcessType; + else if (PhEqualStringRef2(&name, L"\\rundll32.exe", TRUE)) + knownProcessType = RunDllAsAppProcessType; + else if (PhEqualStringRef2(&name, L"\\dllhost.exe", TRUE)) + knownProcessType = ComSurrogateProcessType; + else if (PhEqualStringRef2(&name, L"\\taskeng.exe", TRUE)) + knownProcessType = TaskHostProcessType; + else if (PhEqualStringRef2(&name, L"\\taskhost.exe", TRUE)) + knownProcessType = TaskHostProcessType; + else if (PhEqualStringRef2(&name, L"\\taskhostex.exe", TRUE)) + knownProcessType = TaskHostProcessType; + else if (PhEqualStringRef2(&name, L"\\taskhostw.exe", TRUE)) + knownProcessType = TaskHostProcessType; + else if (PhEqualStringRef2(&name, L"\\wudfhost.exe", TRUE)) + knownProcessType = UmdfHostProcessType; + } + } + + PhDereferenceObject(newFileName); + +#ifdef _WIN64 + if (isWow64) + knownProcessType |= KnownProcessWow64; +#endif + + *KnownProcessType = knownProcessType; + + return status; +} + +static BOOLEAN NTAPI PhpSvchostCommandLineCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + PPH_KNOWN_PROCESS_COMMAND_LINE knownCommandLine = Context; + + if (Option && Option->Id == 1) + { + PhSwapReference(&knownCommandLine->ServiceHost.GroupName, Value); + } + + return TRUE; +} + +BOOLEAN PhaGetProcessKnownCommandLine( + _In_ PPH_STRING CommandLine, + _In_ PH_KNOWN_PROCESS_TYPE KnownProcessType, + _Out_ PPH_KNOWN_PROCESS_COMMAND_LINE KnownCommandLine + ) +{ + switch (KnownProcessType & KnownProcessTypeMask) + { + case ServiceHostProcessType: + { + // svchost.exe -k + + static PH_COMMAND_LINE_OPTION options[] = + { + { 1, L"k", MandatoryArgumentType } + }; + + KnownCommandLine->ServiceHost.GroupName = NULL; + + PhParseCommandLine( + &CommandLine->sr, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS, + PhpSvchostCommandLineCallback, + KnownCommandLine + ); + + if (KnownCommandLine->ServiceHost.GroupName) + { + PH_AUTO(KnownCommandLine->ServiceHost.GroupName); + return TRUE; + } + else + { + return FALSE; + } + } + break; + case RunDllAsAppProcessType: + { + // rundll32.exe , ... + + SIZE_T i; + PH_STRINGREF dllNamePart; + PH_STRINGREF procedureNamePart; + PPH_STRING dllName; + PPH_STRING procedureName; + + i = 0; + + // Get the rundll32.exe part. + + dllName = PhParseCommandLinePart(&CommandLine->sr, &i); + + if (!dllName) + return FALSE; + + PhDereferenceObject(dllName); + + // Get the DLL name part. + + while (i < CommandLine->Length / 2 && CommandLine->Buffer[i] == ' ') + i++; + + dllName = PhParseCommandLinePart(&CommandLine->sr, &i); + + if (!dllName) + return FALSE; + + PH_AUTO(dllName); + + // The procedure name begins after the last comma. + + if (!PhSplitStringRefAtLastChar(&dllName->sr, ',', &dllNamePart, &procedureNamePart)) + return FALSE; + + dllName = PH_AUTO(PhCreateString2(&dllNamePart)); + procedureName = PH_AUTO(PhCreateString2(&procedureNamePart)); + + // If the DLL name isn't an absolute path, assume it's in system32. + // TODO: Use a proper search function. + + if (RtlDetermineDosPathNameType_U(dllName->Buffer) == RtlPathTypeRelative) + { + dllName = PhaConcatStrings( + 3, + PH_AUTO_T(PH_STRING, PhGetSystemDirectory())->Buffer, + L"\\", + dllName->Buffer + ); + } + + KnownCommandLine->RunDllAsApp.FileName = dllName; + KnownCommandLine->RunDllAsApp.ProcedureName = procedureName; + } + break; + case ComSurrogateProcessType: + { + // dllhost.exe /processid: + + static PH_STRINGREF inprocServer32Name = PH_STRINGREF_INIT(L"InprocServer32"); + + SIZE_T i; + ULONG_PTR indexOfProcessId; + PPH_STRING argPart; + PPH_STRING guidString; + UNICODE_STRING guidStringUs; + GUID guid; + HANDLE rootKeyHandle; + HANDLE inprocServer32KeyHandle; + PPH_STRING fileName; + + i = 0; + + // Get the dllhost.exe part. + + argPart = PhParseCommandLinePart(&CommandLine->sr, &i); + + if (!argPart) + return FALSE; + + PhDereferenceObject(argPart); + + // Get the argument part. + + while (i < (ULONG)CommandLine->Length / 2 && CommandLine->Buffer[i] == ' ') + i++; + + argPart = PhParseCommandLinePart(&CommandLine->sr, &i); + + if (!argPart) + return FALSE; + + PH_AUTO(argPart); + + // Find "/processid:"; the GUID is just after that. + + _wcsupr(argPart->Buffer); + indexOfProcessId = PhFindStringInString(argPart, 0, L"/PROCESSID:"); + + if (indexOfProcessId == -1) + return FALSE; + + guidString = PhaSubstring( + argPart, + indexOfProcessId + 11, + (ULONG)argPart->Length / 2 - indexOfProcessId - 11 + ); + PhStringRefToUnicodeString(&guidString->sr, &guidStringUs); + + if (!NT_SUCCESS(RtlGUIDFromString( + &guidStringUs, + &guid + ))) + return FALSE; + + KnownCommandLine->ComSurrogate.Guid = guid; + KnownCommandLine->ComSurrogate.Name = NULL; + KnownCommandLine->ComSurrogate.FileName = NULL; + + // Lookup the GUID in the registry to determine the name and file name. + + if (NT_SUCCESS(PhOpenKey( + &rootKeyHandle, + KEY_READ, + PH_KEY_CLASSES_ROOT, + &PhaConcatStrings2(L"CLSID\\", guidString->Buffer)->sr, + 0 + ))) + { + KnownCommandLine->ComSurrogate.Name = + PH_AUTO(PhQueryRegistryString(rootKeyHandle, NULL)); + + if (NT_SUCCESS(PhOpenKey( + &inprocServer32KeyHandle, + KEY_READ, + rootKeyHandle, + &inprocServer32Name, + 0 + ))) + { + KnownCommandLine->ComSurrogate.FileName = + PH_AUTO(PhQueryRegistryString(inprocServer32KeyHandle, NULL)); + + if (fileName = PH_AUTO(PhExpandEnvironmentStrings( + &KnownCommandLine->ComSurrogate.FileName->sr + ))) + { + KnownCommandLine->ComSurrogate.FileName = fileName; + } + + NtClose(inprocServer32KeyHandle); + } + + NtClose(rootKeyHandle); + } + else if (NT_SUCCESS(PhOpenKey( + &rootKeyHandle, + KEY_READ, + PH_KEY_CLASSES_ROOT, + &PhaConcatStrings2(L"AppID\\", guidString->Buffer)->sr, + 0 + ))) + { + KnownCommandLine->ComSurrogate.Name = + PH_AUTO(PhQueryRegistryString(rootKeyHandle, NULL)); + NtClose(rootKeyHandle); + } + } + break; + default: + return FALSE; + } + + return TRUE; +} + +VOID PhEnumChildWindows( + _In_opt_ HWND hWnd, + _In_ ULONG Limit, + _In_ WNDENUMPROC Callback, + _In_ LPARAM lParam + ) +{ + HWND childWindow = NULL; + ULONG i = 0; + + while (i < Limit && (childWindow = FindWindowEx(hWnd, childWindow, NULL, NULL))) + { + if (!Callback(childWindow, lParam)) + return; + + i++; + } +} + +typedef struct _GET_PROCESS_MAIN_WINDOW_CONTEXT +{ + HWND Window; + HWND ImmersiveWindow; + HANDLE ProcessId; + BOOLEAN IsImmersive; +} GET_PROCESS_MAIN_WINDOW_CONTEXT, *PGET_PROCESS_MAIN_WINDOW_CONTEXT; + +BOOL CALLBACK PhpGetProcessMainWindowEnumWindowsProc( + _In_ HWND hwnd, + _In_ LPARAM lParam + ) +{ + PGET_PROCESS_MAIN_WINDOW_CONTEXT context = (PGET_PROCESS_MAIN_WINDOW_CONTEXT)lParam; + ULONG processId; + HWND parentWindow; + WINDOWINFO windowInfo; + + if (!IsWindowVisible(hwnd)) + return TRUE; + + GetWindowThreadProcessId(hwnd, &processId); + + if (UlongToHandle(processId) == context->ProcessId && + !((parentWindow = GetParent(hwnd)) && IsWindowVisible(parentWindow)) && // skip windows with a visible parent + PhGetWindowTextEx(hwnd, PH_GET_WINDOW_TEXT_INTERNAL | PH_GET_WINDOW_TEXT_LENGTH_ONLY, NULL) != 0) // skip windows with no title + { + if (!context->ImmersiveWindow && context->IsImmersive && + GetProp(hwnd, L"Windows.ImmersiveShell.IdentifyAsMainCoreWindow")) + { + context->ImmersiveWindow = hwnd; + } + + windowInfo.cbSize = sizeof(WINDOWINFO); + + if (!context->Window && GetWindowInfo(hwnd, &windowInfo) && (windowInfo.dwStyle & WS_DLGFRAME)) + { + context->Window = hwnd; + + // If we're not looking at an immersive process, there's no need to search any more windows. + if (!context->IsImmersive) + return FALSE; + } + } + + return TRUE; +} + +HWND PhGetProcessMainWindow( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle + ) +{ + GET_PROCESS_MAIN_WINDOW_CONTEXT context; + HANDLE processHandle = NULL; + + memset(&context, 0, sizeof(GET_PROCESS_MAIN_WINDOW_CONTEXT)); + context.ProcessId = ProcessId; + + if (ProcessHandle) + processHandle = ProcessHandle; + else + PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessId); + + if (processHandle && IsImmersiveProcess_I) + context.IsImmersive = IsImmersiveProcess_I(processHandle); + + PhEnumChildWindows(NULL, 0x800, PhpGetProcessMainWindowEnumWindowsProc, (LPARAM)&context); + + if (!ProcessHandle && processHandle) + NtClose(processHandle); + + return context.ImmersiveWindow ? context.ImmersiveWindow : context.Window; +} + +PPH_STRING PhGetServiceRelevantFileName( + _In_ PPH_STRINGREF ServiceName, + _In_ SC_HANDLE ServiceHandle + ) +{ + PPH_STRING fileName = NULL; + LPQUERY_SERVICE_CONFIG config; + + if (config = PhGetServiceConfig(ServiceHandle)) + { + PhGetServiceDllParameter(ServiceName, &fileName); + + if (!fileName) + { + PPH_STRING commandLine; + + commandLine = PhCreateString(config->lpBinaryPathName); + + if (config->dwServiceType & SERVICE_WIN32) + { + PH_STRINGREF dummyFileName; + PH_STRINGREF dummyArguments; + + PhParseCommandLineFuzzy(&commandLine->sr, &dummyFileName, &dummyArguments, &fileName); + + if (!fileName) + PhSwapReference(&fileName, commandLine); + } + else + { + fileName = PhGetFileName(commandLine); + } + + PhDereferenceObject(commandLine); + } + + PhFree(config); + } + + return fileName; +} + +PPH_STRING PhEscapeStringForDelimiter( + _In_ PPH_STRING String, + _In_ WCHAR Delimiter + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T length; + SIZE_T i; + WCHAR temp[2]; + + length = String->Length / 2; + PhInitializeStringBuilder(&stringBuilder, String->Length / 2 * 3); + + temp[0] = '\\'; + + for (i = 0; i < length; i++) + { + if (String->Buffer[i] == '\\' || String->Buffer[i] == Delimiter) + { + temp[1] = String->Buffer[i]; + PhAppendStringBuilderEx(&stringBuilder, temp, 4); + } + else + { + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); + } + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +PPH_STRING PhUnescapeStringForDelimiter( + _In_ PPH_STRING String, + _In_ WCHAR Delimiter + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T length; + SIZE_T i; + + length = String->Length / 2; + PhInitializeStringBuilder(&stringBuilder, String->Length / 2 * 3); + + for (i = 0; i < length; i++) + { + if (String->Buffer[i] == '\\') + { + if (i != length - 1) + { + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i + 1]); + i++; + } + else + { + // Trailing backslash. Just ignore it. + break; + } + } + else + { + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); + } + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +PPH_STRING PhGetOpaqueXmlNodeText( + _In_ mxml_node_t *node + ) +{ + if (node->child && node->child->type == MXML_OPAQUE && node->child->value.opaque) + { + return PhConvertUtf8ToUtf16(node->child->value.opaque); + } + else + { + return PhReferenceEmptyString(); + } +} + +VOID PhSearchOnlineString( + _In_ HWND hWnd, + _In_ PWSTR String + ) +{ + PhShellExecuteUserString(hWnd, L"SearchEngine", String, TRUE, NULL); +} + +VOID PhShellExecuteUserString( + _In_ HWND hWnd, + _In_ PWSTR Setting, + _In_ PWSTR String, + _In_ BOOLEAN UseShellExecute, + _In_opt_ PWSTR ErrorMessage + ) +{ + static PH_STRINGREF replacementToken = PH_STRINGREF_INIT(L"%s"); + + PPH_STRING executeString; + PH_STRINGREF stringBefore; + PH_STRINGREF stringMiddle; + PH_STRINGREF stringAfter; + PPH_STRING ntMessage; + + executeString = PhGetStringSetting(Setting); + + // Make sure the user executable string is absolute. We can't use RtlDetermineDosPathNameType_U + // here because the string may be a URL. + if (PhFindCharInString(executeString, 0, ':') == -1) + PhMoveReference(&executeString, PhConcatStringRef2(&PhApplicationDirectory->sr, &executeString->sr)); + + // Replace the token with the string, or use the original string if the token is not present. + if (PhSplitStringRefAtString(&executeString->sr, &replacementToken, FALSE, &stringBefore, &stringAfter)) + { + PhInitializeStringRef(&stringMiddle, String); + PhMoveReference(&executeString, PhConcatStringRef3(&stringBefore, &stringMiddle, &stringAfter)); + } + + if (UseShellExecute) + { + PhShellExecute(hWnd, executeString->Buffer, NULL); + } + else + { + NTSTATUS status; + + status = PhCreateProcessWin32(NULL, executeString->Buffer, NULL, NULL, 0, NULL, NULL, NULL); + + if (!NT_SUCCESS(status)) + { + if (ErrorMessage) + { + ntMessage = PhGetNtMessage(status); + PhShowError(hWnd, L"Unable to execute the command: %s\n%s", PhGetStringOrDefault(ntMessage, L"An unknown error occurred."), ErrorMessage); + PhDereferenceObject(ntMessage); + } + else + { + PhShowStatus(hWnd, L"Unable to execute the command", status, 0); + } + } + } + + PhDereferenceObject(executeString); +} + +VOID PhLoadSymbolProviderOptions( + _Inout_ PPH_SYMBOL_PROVIDER SymbolProvider + ) +{ + PPH_STRING searchPath; + + PhSetOptionsSymbolProvider( + SYMOPT_UNDNAME, + PhGetIntegerSetting(L"DbgHelpUndecorate") ? SYMOPT_UNDNAME : 0 + ); + + searchPath = PhGetStringSetting(L"DbgHelpSearchPath"); + + if (searchPath->Length != 0) + PhSetSearchPathSymbolProvider(SymbolProvider, searchPath->Buffer); + + PhDereferenceObject(searchPath); +} + +PWSTR PhMakeContextAtom( + VOID + ) +{ + PH_DEFINE_MAKE_ATOM(L"PH2_Context"); +} + +/** + * Copies a string into a NMLVGETINFOTIP structure. + * + * \param GetInfoTip The NMLVGETINFOTIP structure. + * \param Tip The string to copy. + * + * \remarks The text is truncated if it is too long. + */ +VOID PhCopyListViewInfoTip( + _Inout_ LPNMLVGETINFOTIP GetInfoTip, + _In_ PPH_STRINGREF Tip + ) +{ + ULONG copyIndex; + ULONG bufferRemaining; + ULONG copyLength; + + if (GetInfoTip->dwFlags == 0) + { + copyIndex = (ULONG)PhCountStringZ(GetInfoTip->pszText) + 1; // plus one for newline + + if (GetInfoTip->cchTextMax - copyIndex < 2) // need at least two bytes + return; + + bufferRemaining = GetInfoTip->cchTextMax - copyIndex - 1; + GetInfoTip->pszText[copyIndex - 1] = '\n'; + } + else + { + copyIndex = 0; + bufferRemaining = GetInfoTip->cchTextMax; + } + + copyLength = min((ULONG)Tip->Length / 2, bufferRemaining - 1); + memcpy( + &GetInfoTip->pszText[copyIndex], + Tip->Buffer, + copyLength * 2 + ); + GetInfoTip->pszText[copyIndex + copyLength] = 0; +} + +VOID PhCopyListView( + _In_ HWND ListViewHandle + ) +{ + PPH_STRING text; + + text = PhGetListViewText(ListViewHandle); + PhSetClipboardString(ListViewHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID PhHandleListViewNotifyForCopy( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle + ) +{ + PhHandleListViewNotifyBehaviors(lParam, ListViewHandle, PH_LIST_VIEW_CTRL_C_BEHAVIOR); +} + +VOID PhHandleListViewNotifyBehaviors( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle, + _In_ ULONG Behaviors + ) +{ + if (((LPNMHDR)lParam)->hwndFrom == ListViewHandle && ((LPNMHDR)lParam)->code == LVN_KEYDOWN) + { + LPNMLVKEYDOWN keyDown = (LPNMLVKEYDOWN)lParam; + + switch (keyDown->wVKey) + { + case 'C': + if (Behaviors & PH_LIST_VIEW_CTRL_C_BEHAVIOR) + { + if (GetKeyState(VK_CONTROL) < 0) + PhCopyListView(ListViewHandle); + } + break; + case 'A': + if (Behaviors & PH_LIST_VIEW_CTRL_A_BEHAVIOR) + { + if (GetKeyState(VK_CONTROL) < 0) + PhSetStateAllListViewItems(ListViewHandle, LVIS_SELECTED, LVIS_SELECTED); + } + break; + } + } +} + +BOOLEAN PhGetListViewContextMenuPoint( + _In_ HWND ListViewHandle, + _Out_ PPOINT Point + ) +{ + INT selectedIndex; + RECT bounds; + RECT clientRect; + + // The user pressed a key to display the context menu. + // Suggest where the context menu should display. + + if ((selectedIndex = ListView_GetNextItem(ListViewHandle, -1, LVNI_SELECTED)) != -1) + { + if (ListView_GetItemRect(ListViewHandle, selectedIndex, &bounds, LVIR_BOUNDS)) + { + Point->x = bounds.left + PhSmallIconSize.X / 2; + Point->y = bounds.top + PhSmallIconSize.Y / 2; + + GetClientRect(ListViewHandle, &clientRect); + + if (Point->x < 0 || Point->y < 0 || Point->x >= clientRect.right || Point->y >= clientRect.bottom) + { + // The menu is going to be outside of the control. Just put it at the top-left. + Point->x = 0; + Point->y = 0; + } + + ClientToScreen(ListViewHandle, Point); + + return TRUE; + } + } + + Point->x = 0; + Point->y = 0; + ClientToScreen(ListViewHandle, Point); + + return FALSE; +} + +HFONT PhDuplicateFontWithNewWeight( + _In_ HFONT Font, + _In_ LONG NewWeight + ) +{ + LOGFONT logFont; + + if (GetObject(Font, sizeof(LOGFONT), &logFont)) + { + logFont.lfWeight = NewWeight; + return CreateFontIndirect(&logFont); + } + else + { + return NULL; + } +} + +VOID PhSetWindowOpacity( + _In_ HWND WindowHandle, + _In_ ULONG OpacityPercent + ) +{ + if (OpacityPercent == 0) + { + // Make things a bit faster by removing the WS_EX_LAYERED bit. + PhSetWindowExStyle(WindowHandle, WS_EX_LAYERED, 0); + RedrawWindow(WindowHandle, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + return; + } + + PhSetWindowExStyle(WindowHandle, WS_EX_LAYERED, WS_EX_LAYERED); + + // Disallow opacity values of less than 10%. + OpacityPercent = min(OpacityPercent, 90); + + // The opacity value is backwards - 0 means opaque, 100 means transparent. + SetLayeredWindowAttributes( + WindowHandle, + 0, + (BYTE)(255 * (100 - OpacityPercent) / 100), + LWA_ALPHA + ); +} + +VOID PhLoadWindowPlacementFromSetting( + _In_opt_ PWSTR PositionSettingName, + _In_opt_ PWSTR SizeSettingName, + _In_ HWND WindowHandle + ) +{ + PH_RECTANGLE windowRectangle; + + if (PositionSettingName && SizeSettingName) + { + RECT rectForAdjust; + + windowRectangle.Position = PhGetIntegerPairSetting(PositionSettingName); + windowRectangle.Size = PhGetScalableIntegerPairSetting(SizeSettingName, TRUE).Pair; + PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); + + // Let the window adjust for the minimum size if needed. + rectForAdjust = PhRectangleToRect(windowRectangle); + SendMessage(WindowHandle, WM_SIZING, WMSZ_BOTTOMRIGHT, (LPARAM)&rectForAdjust); + windowRectangle = PhRectToRectangle(rectForAdjust); + + MoveWindow(WindowHandle, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + } + else + { + PH_INTEGER_PAIR position; + PH_INTEGER_PAIR size; + ULONG flags; + + flags = SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER; + + if (PositionSettingName) + { + position = PhGetIntegerPairSetting(PositionSettingName); + flags &= ~SWP_NOMOVE; + } + else + { + position.X = 0; + position.Y = 0; + } + + if (SizeSettingName) + { + size = PhGetScalableIntegerPairSetting(SizeSettingName, TRUE).Pair; + flags &= ~SWP_NOSIZE; + } + else + { + size.X = 16; + size.Y = 16; + } + + SetWindowPos(WindowHandle, NULL, position.X, position.Y, size.X, size.Y, flags); + } +} + +VOID PhSaveWindowPlacementToSetting( + _In_opt_ PWSTR PositionSettingName, + _In_opt_ PWSTR SizeSettingName, + _In_ HWND WindowHandle + ) +{ + WINDOWPLACEMENT placement = { sizeof(placement) }; + PH_RECTANGLE windowRectangle; + MONITORINFO monitorInfo = { sizeof(MONITORINFO) }; + + GetWindowPlacement(WindowHandle, &placement); + windowRectangle = PhRectToRectangle(placement.rcNormalPosition); + + // The rectangle is in workspace coordinates. Convert the values back to screen coordinates. + if (GetMonitorInfo(MonitorFromRect(&placement.rcNormalPosition, MONITOR_DEFAULTTOPRIMARY), &monitorInfo)) + { + windowRectangle.Left += monitorInfo.rcWork.left - monitorInfo.rcMonitor.left; + windowRectangle.Top += monitorInfo.rcWork.top - monitorInfo.rcMonitor.top; + } + + if (PositionSettingName) + PhSetIntegerPairSetting(PositionSettingName, windowRectangle.Position); + if (SizeSettingName) + PhSetScalableIntegerPairSetting2(SizeSettingName, windowRectangle.Size); +} + +VOID PhLoadListViewColumnsFromSetting( + _In_ PWSTR Name, + _In_ HWND ListViewHandle + ) +{ + PPH_STRING string; + + string = PhGetStringSetting(Name); + PhLoadListViewColumnSettings(ListViewHandle, string); + PhDereferenceObject(string); +} + +VOID PhSaveListViewColumnsToSetting( + _In_ PWSTR Name, + _In_ HWND ListViewHandle + ) +{ + PPH_STRING string; + + string = PhSaveListViewColumnSettings(ListViewHandle); + PhSetStringSetting2(Name, &string->sr); + PhDereferenceObject(string); +} + +PPH_STRING PhGetPhVersion( + VOID + ) +{ + PH_FORMAT format[5]; + + PhInitFormatU(&format[0], PHAPP_VERSION_MAJOR); + PhInitFormatC(&format[1], '.'); + PhInitFormatU(&format[2], PHAPP_VERSION_MINOR); + PhInitFormatC(&format[3], '.'); + PhInitFormatU(&format[4], PHAPP_VERSION_REVISION); + + return PhFormat(format, 5, 16); +} + +VOID PhGetPhVersionNumbers( + _Out_opt_ PULONG MajorVersion, + _Out_opt_ PULONG MinorVersion, + _Reserved_ PULONG Reserved, + _Out_opt_ PULONG RevisionNumber + ) +{ + if (MajorVersion) + *MajorVersion = PHAPP_VERSION_MAJOR; + if (MinorVersion) + *MinorVersion = PHAPP_VERSION_MINOR; + if (RevisionNumber) + *RevisionNumber = PHAPP_VERSION_REVISION; +} + +VOID PhWritePhTextHeader( + _Inout_ PPH_FILE_STREAM FileStream + ) +{ + PPH_STRING version; + LARGE_INTEGER time; + SYSTEMTIME systemTime; + PPH_STRING dateString; + PPH_STRING timeString; + + PhWriteStringAsUtf8FileStream2(FileStream, L"Process Hacker "); + + if (version = PhGetPhVersion()) + { + PhWriteStringAsUtf8FileStream(FileStream, &version->sr); + PhDereferenceObject(version); + } + + PhWriteStringFormatAsUtf8FileStream(FileStream, L"\r\nWindows NT %u.%u", PhOsVersion.dwMajorVersion, PhOsVersion.dwMinorVersion); + + if (PhOsVersion.szCSDVersion[0] != 0) + PhWriteStringFormatAsUtf8FileStream(FileStream, L" %s", PhOsVersion.szCSDVersion); + +#ifdef _WIN64 + PhWriteStringAsUtf8FileStream2(FileStream, L" (64-bit)"); +#else + PhWriteStringAsUtf8FileStream2(FileStream, L" (32-bit)"); +#endif + + PhQuerySystemTime(&time); + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + + dateString = PhFormatDate(&systemTime, NULL); + timeString = PhFormatTime(&systemTime, NULL); + PhWriteStringFormatAsUtf8FileStream(FileStream, L"\r\n%s %s\r\n\r\n", dateString->Buffer, timeString->Buffer); + PhDereferenceObject(dateString); + PhDereferenceObject(timeString); +} + +BOOLEAN PhShellProcessHacker( + _In_opt_ HWND hWnd, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_ ULONG AppFlags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ) +{ + return PhShellProcessHackerEx( + hWnd, + NULL, + Parameters, + ShowWindowType, + Flags, + AppFlags, + Timeout, + ProcessHandle + ); +} + +VOID PhpAppendCommandLineArgument( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PWSTR Name, + _In_ PPH_STRINGREF Value + ) +{ + PPH_STRING temp; + + PhAppendStringBuilder2(StringBuilder, L" -"); + PhAppendStringBuilder2(StringBuilder, Name); + PhAppendStringBuilder2(StringBuilder, L" \""); + temp = PhEscapeCommandLinePart(Value); + PhAppendStringBuilder(StringBuilder, &temp->sr); + PhDereferenceObject(temp); + PhAppendCharStringBuilder(StringBuilder, '\"'); +} + +BOOLEAN PhShellProcessHackerEx( + _In_opt_ HWND hWnd, + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_ ULONG AppFlags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ) +{ + BOOLEAN result; + PH_STRING_BUILDER sb; + PWSTR parameters; + + if (AppFlags & PH_SHELL_APP_PROPAGATE_PARAMETERS) + { + PhInitializeStringBuilder(&sb, 128); + + // Propagate parameters. + + if (PhStartupParameters.NoSettings) + { + PhAppendStringBuilder2(&sb, L" -nosettings"); + } + else if (PhStartupParameters.SettingsFileName && (PhSettingsFileName || (AppFlags & PH_SHELL_APP_PROPAGATE_PARAMETERS_FORCE_SETTINGS))) + { + PPH_STRINGREF fileName; + + if (PhSettingsFileName) + fileName = &PhSettingsFileName->sr; + else + fileName = &PhStartupParameters.SettingsFileName->sr; + + PhpAppendCommandLineArgument(&sb, L"settings", fileName); + } + + if (PhStartupParameters.NoKph) + PhAppendStringBuilder2(&sb, L" -nokph"); + if (PhStartupParameters.InstallKph) + PhAppendStringBuilder2(&sb, L" -installkph"); + if (PhStartupParameters.UninstallKph) + PhAppendStringBuilder2(&sb, L" -uninstallkph"); + if (PhStartupParameters.Debug) + PhAppendStringBuilder2(&sb, L" -debug"); + if (PhStartupParameters.NoPlugins) + PhAppendStringBuilder2(&sb, L" -noplugins"); + if (PhStartupParameters.NewInstance) + PhAppendStringBuilder2(&sb, L" -newinstance"); + + if (PhStartupParameters.SelectPid != 0) + PhAppendFormatStringBuilder(&sb, L" -selectpid %u", PhStartupParameters.SelectPid); + + if (PhStartupParameters.PriorityClass != 0) + { + CHAR value = 0; + + switch (PhStartupParameters.PriorityClass) + { + case PROCESS_PRIORITY_CLASS_REALTIME: + value = L'r'; + break; + case PROCESS_PRIORITY_CLASS_HIGH: + value = L'h'; + break; + case PROCESS_PRIORITY_CLASS_NORMAL: + value = L'n'; + break; + case PROCESS_PRIORITY_CLASS_IDLE: + value = L'l'; + break; + } + + if (value != 0) + { + PhAppendStringBuilder2(&sb, L" -priority "); + PhAppendCharStringBuilder(&sb, value); + } + } + + if (PhStartupParameters.PluginParameters) + { + ULONG i; + + for (i = 0; i < PhStartupParameters.PluginParameters->Count; i++) + { + PPH_STRING value = PhStartupParameters.PluginParameters->Items[i]; + PhpAppendCommandLineArgument(&sb, L"plugin", &value->sr); + } + } + + if (PhStartupParameters.SelectTab) + PhpAppendCommandLineArgument(&sb, L"selecttab", &PhStartupParameters.SelectTab->sr); + + if (!(AppFlags & PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY)) + { + if (PhStartupParameters.ShowVisible) + PhAppendStringBuilder2(&sb, L" -v"); + if (PhStartupParameters.ShowHidden) + PhAppendStringBuilder2(&sb, L" -hide"); + } + + // Add user-specified parameters last so they can override the propagated parameters. + if (Parameters) + { + PhAppendCharStringBuilder(&sb, ' '); + PhAppendStringBuilder2(&sb, Parameters); + } + + if (sb.String->Length != 0 && sb.String->Buffer[0] == ' ') + parameters = sb.String->Buffer + 1; + else + parameters = sb.String->Buffer; + } + else + { + parameters = Parameters; + } + + result = PhShellExecuteEx( + hWnd, + FileName ? FileName : PhApplicationFileName->Buffer, + parameters, + ShowWindowType, + Flags, + Timeout, + ProcessHandle + ); + + if (AppFlags & PH_SHELL_APP_PROPAGATE_PARAMETERS) + PhDeleteStringBuilder(&sb); + + return result; +} + +BOOLEAN PhCreateProcessIgnoreIfeoDebugger( + _In_ PWSTR FileName + ) +{ + BOOLEAN result; + BOOL (NTAPI *debugSetProcessKillOnExit)(BOOL); + BOOL (NTAPI *debugActiveProcessStop)(DWORD); + BOOLEAN originalValue; + STARTUPINFO startupInfo; + PROCESS_INFORMATION processInfo; + + if (!(debugSetProcessKillOnExit = PhGetModuleProcAddress(L"kernel32.dll", "DebugSetProcessKillOnExit")) || + !(debugActiveProcessStop = PhGetModuleProcAddress(L"kernel32.dll", "DebugActiveProcessStop"))) + return FALSE; + + result = FALSE; + + // This is NOT thread-safe. + originalValue = NtCurrentPeb()->ReadImageFileExecOptions; + NtCurrentPeb()->ReadImageFileExecOptions = FALSE; + + memset(&startupInfo, 0, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); + + // The combination of ReadImageFileExecOptions = FALSE and the DEBUG_PROCESS flag + // allows us to skip the Debugger IFEO value. + if (CreateProcess(FileName, NULL, NULL, NULL, FALSE, DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &startupInfo, &processInfo)) + { + // Stop debugging the process now. + debugSetProcessKillOnExit(FALSE); + debugActiveProcessStop(processInfo.dwProcessId); + result = TRUE; + } + + if (processInfo.hProcess) + NtClose(processInfo.hProcess); + if (processInfo.hThread) + NtClose(processInfo.hThread); + + NtCurrentPeb()->ReadImageFileExecOptions = originalValue; + + return result; +} + +VOID PhInitializeTreeNewColumnMenu( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data + ) +{ + PhInitializeTreeNewColumnMenuEx(Data, 0); +} + +VOID PhInitializeTreeNewColumnMenuEx( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data, + _In_ ULONG Flags + ) +{ + PPH_EMENU_ITEM resetSortMenuItem = NULL; + PPH_EMENU_ITEM sizeColumnToFitMenuItem; + PPH_EMENU_ITEM sizeAllColumnsToFitMenuItem; + PPH_EMENU_ITEM hideColumnMenuItem; + PPH_EMENU_ITEM chooseColumnsMenuItem; + ULONG minimumNumberOfColumns; + + Data->Menu = PhCreateEMenu(); + Data->Selection = NULL; + Data->ProcessedId = 0; + + sizeColumnToFitMenuItem = PhCreateEMenuItem(0, PH_TN_COLUMN_MENU_SIZE_COLUMN_TO_FIT_ID, L"Size column to fit", NULL, NULL); + sizeAllColumnsToFitMenuItem = PhCreateEMenuItem(0, PH_TN_COLUMN_MENU_SIZE_ALL_COLUMNS_TO_FIT_ID, L"Size all columns to fit", NULL, NULL); + + if (!(Flags & PH_TN_COLUMN_MENU_NO_VISIBILITY)) + { + hideColumnMenuItem = PhCreateEMenuItem(0, PH_TN_COLUMN_MENU_HIDE_COLUMN_ID, L"Hide column", NULL, NULL); + chooseColumnsMenuItem = PhCreateEMenuItem(0, PH_TN_COLUMN_MENU_CHOOSE_COLUMNS_ID, L"Choose columns...", NULL, NULL); + } + + if (Flags & PH_TN_COLUMN_MENU_SHOW_RESET_SORT) + { + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + TreeNew_GetSort(Data->TreeNewHandle, &sortColumn, &sortOrder); + + if (sortOrder != Data->DefaultSortOrder || (Data->DefaultSortOrder != NoSortOrder && sortColumn != Data->DefaultSortColumn)) + resetSortMenuItem = PhCreateEMenuItem(0, PH_TN_COLUMN_MENU_RESET_SORT_ID, L"Reset sort", NULL, NULL); + } + + PhInsertEMenuItem(Data->Menu, sizeColumnToFitMenuItem, -1); + PhInsertEMenuItem(Data->Menu, sizeAllColumnsToFitMenuItem, -1); + + if (!(Flags & PH_TN_COLUMN_MENU_NO_VISIBILITY)) + { + PhInsertEMenuItem(Data->Menu, hideColumnMenuItem, -1); + + if (resetSortMenuItem) + PhInsertEMenuItem(Data->Menu, resetSortMenuItem, -1); + + PhInsertEMenuItem(Data->Menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, L"", NULL, NULL), -1); + PhInsertEMenuItem(Data->Menu, chooseColumnsMenuItem, -1); + + if (TreeNew_GetFixedColumn(Data->TreeNewHandle)) + minimumNumberOfColumns = 2; // don't allow user to remove all normal columns (the fixed column can never be removed) + else + minimumNumberOfColumns = 1; + + if (!Data->MouseEvent || !Data->MouseEvent->Column || + Data->MouseEvent->Column->Fixed || // don't allow the fixed column to be hidden + TreeNew_GetVisibleColumnCount(Data->TreeNewHandle) < minimumNumberOfColumns + 1 + ) + { + hideColumnMenuItem->Flags |= PH_EMENU_DISABLED; + } + } + else + { + if (resetSortMenuItem) + PhInsertEMenuItem(Data->Menu, resetSortMenuItem, -1); + } + + if (!Data->MouseEvent || !Data->MouseEvent->Column) + { + sizeColumnToFitMenuItem->Flags |= PH_EMENU_DISABLED; + } +} + +VOID PhpEnsureValidSortColumnTreeNew( + _Inout_ HWND TreeNewHandle, + _In_ ULONG DefaultSortColumn, + _In_ PH_SORT_ORDER DefaultSortOrder + ) +{ + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + // Make sure the column we're sorting by is actually visible, and if not, don't sort anymore. + + TreeNew_GetSort(TreeNewHandle, &sortColumn, &sortOrder); + + if (sortOrder != NoSortOrder) + { + PH_TREENEW_COLUMN column; + + TreeNew_GetColumn(TreeNewHandle, sortColumn, &column); + + if (!column.Visible) + { + if (DefaultSortOrder != NoSortOrder) + { + // Make sure the default sort column is visible. + TreeNew_GetColumn(TreeNewHandle, DefaultSortColumn, &column); + + if (!column.Visible) + { + ULONG maxId; + ULONG id; + BOOLEAN found; + + // Use the first visible column. + maxId = TreeNew_GetMaxId(TreeNewHandle); + id = 0; + found = FALSE; + + while (id <= maxId) + { + if (TreeNew_GetColumn(TreeNewHandle, id, &column)) + { + if (column.Visible) + { + DefaultSortColumn = id; + found = TRUE; + break; + } + } + + id++; + } + + if (!found) + { + DefaultSortColumn = 0; + DefaultSortOrder = NoSortOrder; + } + } + } + + TreeNew_SetSort(TreeNewHandle, DefaultSortColumn, DefaultSortOrder); + } + } +} + +BOOLEAN PhHandleTreeNewColumnMenu( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data + ) +{ + if (!Data->Selection) + return FALSE; + + switch (Data->Selection->Id) + { + case PH_TN_COLUMN_MENU_RESET_SORT_ID: + { + TreeNew_SetSort(Data->TreeNewHandle, Data->DefaultSortColumn, Data->DefaultSortOrder); + } + break; + case PH_TN_COLUMN_MENU_SIZE_COLUMN_TO_FIT_ID: + { + if (Data->MouseEvent && Data->MouseEvent->Column) + { + TreeNew_AutoSizeColumn(Data->TreeNewHandle, Data->MouseEvent->Column->Id, 0); + } + } + break; + case PH_TN_COLUMN_MENU_SIZE_ALL_COLUMNS_TO_FIT_ID: + { + ULONG maxId; + ULONG id; + + maxId = TreeNew_GetMaxId(Data->TreeNewHandle); + id = 0; + + while (id <= maxId) + { + TreeNew_AutoSizeColumn(Data->TreeNewHandle, id, 0); + id++; + } + } + break; + case PH_TN_COLUMN_MENU_HIDE_COLUMN_ID: + { + PH_TREENEW_COLUMN column; + + if (Data->MouseEvent && Data->MouseEvent->Column && !Data->MouseEvent->Column->Fixed) + { + column.Id = Data->MouseEvent->Column->Id; + column.Visible = FALSE; + TreeNew_SetColumn(Data->TreeNewHandle, TN_COLUMN_FLAG_VISIBLE, &column); + PhpEnsureValidSortColumnTreeNew(Data->TreeNewHandle, Data->DefaultSortColumn, Data->DefaultSortOrder); + InvalidateRect(Data->TreeNewHandle, NULL, FALSE); + } + } + break; + case PH_TN_COLUMN_MENU_CHOOSE_COLUMNS_ID: + { + PhShowChooseColumnsDialog(Data->TreeNewHandle, Data->TreeNewHandle, PH_CONTROL_TYPE_TREE_NEW); + PhpEnsureValidSortColumnTreeNew(Data->TreeNewHandle, Data->DefaultSortColumn, Data->DefaultSortOrder); + } + break; + default: + return FALSE; + } + + Data->ProcessedId = Data->Selection->Id; + + return TRUE; +} + +VOID PhDeleteTreeNewColumnMenu( + _In_ PPH_TN_COLUMN_MENU_DATA Data + ) +{ + if (Data->Menu) + { + PhDestroyEMenu(Data->Menu); + Data->Menu = NULL; + } +} + +VOID PhInitializeTreeNewFilterSupport( + _Out_ PPH_TN_FILTER_SUPPORT Support, + _In_ HWND TreeNewHandle, + _In_ PPH_LIST NodeList + ) +{ + Support->FilterList = NULL; + Support->TreeNewHandle = TreeNewHandle; + Support->NodeList = NodeList; +} + +VOID PhDeleteTreeNewFilterSupport( + _In_ PPH_TN_FILTER_SUPPORT Support + ) +{ + PhDereferenceObject(Support->FilterList); +} + +PPH_TN_FILTER_ENTRY PhAddTreeNewFilter( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TN_FILTER_FUNCTION Filter, + _In_opt_ PVOID Context + ) +{ + PPH_TN_FILTER_ENTRY entry; + + entry = PhAllocate(sizeof(PH_TN_FILTER_ENTRY)); + entry->Filter = Filter; + entry->Context = Context; + + if (!Support->FilterList) + Support->FilterList = PhCreateList(2); + + PhAddItemList(Support->FilterList, entry); + + return entry; +} + +VOID PhRemoveTreeNewFilter( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TN_FILTER_ENTRY Entry + ) +{ + ULONG index; + + if (!Support->FilterList) + return; + + index = PhFindItemList(Support->FilterList, Entry); + + if (index != -1) + { + PhRemoveItemList(Support->FilterList, index); + PhFree(Entry); + } +} + +BOOLEAN PhApplyTreeNewFiltersToNode( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TREENEW_NODE Node + ) +{ + BOOLEAN show; + ULONG i; + + show = TRUE; + + if (Support->FilterList) + { + for (i = 0; i < Support->FilterList->Count; i++) + { + PPH_TN_FILTER_ENTRY entry; + + entry = Support->FilterList->Items[i]; + + if (!entry->Filter(Node, entry->Context)) + { + show = FALSE; + break; + } + } + } + + return show; +} + +VOID PhApplyTreeNewFilters( + _In_ PPH_TN_FILTER_SUPPORT Support + ) +{ + ULONG i; + + for (i = 0; i < Support->NodeList->Count; i++) + { + PPH_TREENEW_NODE node; + + node = Support->NodeList->Items[i]; + node->Visible = PhApplyTreeNewFiltersToNode(Support, node); + + if (!node->Visible && node->Selected) + { + node->Selected = FALSE; + } + } + + TreeNew_NodesStructured(Support->TreeNewHandle); +} + +VOID NTAPI PhpCopyCellEMenuItemDeleteFunction( + _In_ struct _PH_EMENU_ITEM *Item + ) +{ + PPH_COPY_CELL_CONTEXT context; + + context = Item->Context; + PhDereferenceObject(context->MenuItemText); + PhFree(context); +} + +BOOLEAN PhInsertCopyCellEMenuItem( + _In_ struct _PH_EMENU_ITEM *Menu, + _In_ ULONG InsertAfterId, + _In_ HWND TreeNewHandle, + _In_ PPH_TREENEW_COLUMN Column + ) +{ + PPH_EMENU_ITEM parentItem; + ULONG indexInParent; + PPH_COPY_CELL_CONTEXT context; + PH_STRINGREF columnText; + PPH_STRING escapedText; + PPH_STRING menuItemText; + PPH_EMENU_ITEM copyCellItem; + + if (!Column) + return FALSE; + + if (!PhFindEMenuItemEx(Menu, 0, NULL, InsertAfterId, &parentItem, &indexInParent)) + return FALSE; + + indexInParent++; + + context = PhAllocate(sizeof(PH_COPY_CELL_CONTEXT)); + context->TreeNewHandle = TreeNewHandle; + context->Id = Column->Id; + + PhInitializeStringRef(&columnText, Column->Text); + escapedText = PhEscapeStringForMenuPrefix(&columnText); + menuItemText = PhFormatString(L"Copy \"%s\"", escapedText->Buffer); + PhDereferenceObject(escapedText); + copyCellItem = PhCreateEMenuItem(0, ID_COPY_CELL, menuItemText->Buffer, NULL, context); + copyCellItem->DeleteFunction = PhpCopyCellEMenuItemDeleteFunction; + context->MenuItemText = menuItemText; + + if (Column->CustomDraw) + copyCellItem->Flags |= PH_EMENU_DISABLED; + + PhInsertEMenuItem(parentItem, copyCellItem, indexInParent); + + return TRUE; +} + +BOOLEAN PhHandleCopyCellEMenuItem( + _In_ struct _PH_EMENU_ITEM *SelectedItem + ) +{ + PPH_COPY_CELL_CONTEXT context; + PH_STRING_BUILDER stringBuilder; + ULONG count; + ULONG selectedCount; + ULONG i; + PPH_TREENEW_NODE node; + PH_TREENEW_GET_CELL_TEXT getCellText; + + if (!SelectedItem) + return FALSE; + if (SelectedItem->Id != ID_COPY_CELL) + return FALSE; + + context = SelectedItem->Context; + + PhInitializeStringBuilder(&stringBuilder, 0x100); + count = TreeNew_GetFlatNodeCount(context->TreeNewHandle); + selectedCount = 0; + + for (i = 0; i < count; i++) + { + node = TreeNew_GetFlatNode(context->TreeNewHandle, i); + + if (node && node->Selected) + { + selectedCount++; + + getCellText.Flags = 0; + getCellText.Node = node; + getCellText.Id = context->Id; + PhInitializeEmptyStringRef(&getCellText.Text); + TreeNew_GetCellText(context->TreeNewHandle, &getCellText); + + PhAppendStringBuilder(&stringBuilder, &getCellText.Text); + PhAppendStringBuilder2(&stringBuilder, L"\r\n"); + } + } + + if (stringBuilder.String->Length != 0 && selectedCount == 1) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + PhSetClipboardString(context->TreeNewHandle, &stringBuilder.String->sr); + PhDeleteStringBuilder(&stringBuilder); + + return TRUE; +} + +BOOLEAN PhpSelectFavoriteInRegedit( + _In_ HWND RegeditWindow, + _In_ PPH_STRINGREF FavoriteName, + _In_ BOOLEAN UsePhSvc + ) +{ + HMENU menu; + HMENU favoritesMenu; + ULONG count; + ULONG i; + ULONG id = -1; + + if (!(menu = GetMenu(RegeditWindow))) + return FALSE; + + // Cause the Registry Editor to refresh the Favorites menu. + if (UsePhSvc) + PhSvcCallSendMessage(RegeditWindow, WM_MENUSELECT, MAKEWPARAM(3, MF_POPUP), (LPARAM)menu); + else + SendMessage(RegeditWindow, WM_MENUSELECT, MAKEWPARAM(3, MF_POPUP), (LPARAM)menu); + + if (!(favoritesMenu = GetSubMenu(menu, 3))) + return FALSE; + + // Find our entry. + + count = GetMenuItemCount(favoritesMenu); + + if (count == -1) + return FALSE; + if (count > 1000) + count = 1000; + + for (i = 3; i < count; i++) + { + MENUITEMINFO info = { sizeof(MENUITEMINFO) }; + WCHAR buffer[32]; + + info.fMask = MIIM_ID | MIIM_STRING; + info.dwTypeData = buffer; + info.cch = sizeof(buffer) / sizeof(WCHAR); + GetMenuItemInfo(favoritesMenu, i, TRUE, &info); + + if (info.cch == FavoriteName->Length / 2) + { + PH_STRINGREF text; + + text.Buffer = buffer; + text.Length = info.cch * 2; + + if (PhEqualStringRef(&text, FavoriteName, TRUE)) + { + id = info.wID; + break; + } + } + } + + if (id == -1) + return FALSE; + + // Activate our entry. + if (UsePhSvc) + PhSvcCallSendMessage(RegeditWindow, WM_COMMAND, MAKEWPARAM(id, 0), 0); + else + SendMessage(RegeditWindow, WM_COMMAND, MAKEWPARAM(id, 0), 0); + + // "Close" the Favorites menu and restore normal status bar text. + if (UsePhSvc) + PhSvcCallPostMessage(RegeditWindow, WM_MENUSELECT, MAKEWPARAM(0, 0xffff), 0); + else + PostMessage(RegeditWindow, WM_MENUSELECT, MAKEWPARAM(0, 0xffff), 0); + + // Bring regedit to the top. + if (IsIconic(RegeditWindow)) + { + ShowWindow(RegeditWindow, SW_RESTORE); + SetForegroundWindow(RegeditWindow); + } + else + { + SetForegroundWindow(RegeditWindow); + } + + return TRUE; +} + +/** + * Opens a key in the Registry Editor. If the Registry Editor is already open, + * the specified key is selected in the Registry Editor. + * + * \param hWnd A handle to the parent window. + * \param KeyName The key name to open. + */ +BOOLEAN PhShellOpenKey2( + _In_ HWND hWnd, + _In_ PPH_STRING KeyName + ) +{ + static PH_STRINGREF favoritesKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit\\Favorites"); + + BOOLEAN result = FALSE; + HWND regeditWindow; + HANDLE favoritesKeyHandle; + WCHAR favoriteName[32]; + UNICODE_STRING valueName; + PH_STRINGREF valueNameSr; + PPH_STRING expandedKeyName; + + regeditWindow = FindWindow(L"RegEdit_RegEdit", NULL); + + if (!regeditWindow) + { + PhShellOpenKey(hWnd, KeyName); + return TRUE; + } + + if (!PhGetOwnTokenAttributes().Elevated) + { + if (!PhUiConnectToPhSvc(hWnd, FALSE)) + return FALSE; + } + + // Create our entry in Favorites. + + if (!NT_SUCCESS(PhCreateKey( + &favoritesKeyHandle, + KEY_WRITE, + PH_KEY_CURRENT_USER, + &favoritesKeyName, + 0, + 0, + NULL + ))) + goto CleanupExit; + + memcpy(favoriteName, L"A_ProcessHacker", 15 * sizeof(WCHAR)); + PhGenerateRandomAlphaString(&favoriteName[15], 16); + RtlInitUnicodeString(&valueName, favoriteName); + PhUnicodeStringToStringRef(&valueName, &valueNameSr); + + expandedKeyName = PhExpandKeyName(KeyName, TRUE); + NtSetValueKey(favoritesKeyHandle, &valueName, 0, REG_SZ, expandedKeyName->Buffer, (ULONG)expandedKeyName->Length + 2); + PhDereferenceObject(expandedKeyName); + + // Select our entry in regedit. + result = PhpSelectFavoriteInRegedit(regeditWindow, &valueNameSr, !PhGetOwnTokenAttributes().Elevated); + + NtDeleteValueKey(favoritesKeyHandle, &valueName); + NtClose(favoritesKeyHandle); + +CleanupExit: + if (!PhGetOwnTokenAttributes().Elevated) + PhUiDisconnectFromPhSvc(); + + return result; +} + +PPH_STRING PhPcre2GetErrorMessage( + _In_ INT ErrorCode + ) +{ + PPH_STRING buffer; + SIZE_T bufferLength; + INT_PTR returnLength; + + bufferLength = 128 * sizeof(WCHAR); + buffer = PhCreateStringEx(NULL, bufferLength); + + while (TRUE) + { + if ((returnLength = pcre2_get_error_message(ErrorCode, buffer->Buffer, bufferLength / sizeof(WCHAR) + 1)) >= 0) + break; + + PhDereferenceObject(buffer); + bufferLength *= 2; + + if (bufferLength > 0x1000 * sizeof(WCHAR)) + break; + + buffer = PhCreateStringEx(NULL, bufferLength); + } + + if (returnLength < 0) + return NULL; + + buffer->Length = returnLength * sizeof(WCHAR); + return buffer; +} diff --git a/ProcessHacker/chcol.c b/ProcessHacker/chcol.c new file mode 100644 index 0000000..7823cf6 --- /dev/null +++ b/ProcessHacker/chcol.c @@ -0,0 +1,422 @@ +/* + * Process Hacker - + * column chooser + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct _COLUMNS_DIALOG_CONTEXT +{ + HWND ControlHandle; + ULONG Type; + PPH_LIST Columns; + + HWND InactiveList; + HWND ActiveList; +} COLUMNS_DIALOG_CONTEXT, *PCOLUMNS_DIALOG_CONTEXT; + +INT_PTR CALLBACK PhpColumnsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowChooseColumnsDialog( + _In_ HWND ParentWindowHandle, + _In_ HWND ControlHandle, + _In_ ULONG Type + ) +{ + COLUMNS_DIALOG_CONTEXT context; + + context.ControlHandle = ControlHandle; + context.Type = Type; + + if (Type == PH_CONTROL_TYPE_TREE_NEW) + context.Columns = PhCreateList(TreeNew_GetColumnCount(ControlHandle)); + else + return; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_CHOOSECOLUMNS), + ParentWindowHandle, + PhpColumnsDlgProc, + (LPARAM)&context + ); + + PhDereferenceObject(context.Columns); +} + +static int __cdecl PhpColumnsCompareDisplayIndexTn( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_TREENEW_COLUMN column1 = *(PPH_TREENEW_COLUMN *)elem1; + PPH_TREENEW_COLUMN column2 = *(PPH_TREENEW_COLUMN *)elem2; + + return uintcmp(column1->DisplayIndex, column2->DisplayIndex); +} + +_Success_(return != -1) +static ULONG IndexOfStringInList( + _In_ PPH_LIST List, + _In_ PWSTR String + ) +{ + ULONG i; + + for (i = 0; i < List->Count; i++) + { + if (PhEqualString2(List->Items[i], String, FALSE)) + return i; + } + + return -1; +} + +INT_PTR CALLBACK PhpColumnsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOLUMNS_DIALOG_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PCOLUMNS_DIALOG_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + else + { + context = (PCOLUMNS_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG count; + ULONG total; + ULONG i; + PPH_LIST displayOrderList = NULL; + + context->InactiveList = GetDlgItem(hwndDlg, IDC_INACTIVE); + context->ActiveList = GetDlgItem(hwndDlg, IDC_ACTIVE); + + if (context->Type == PH_CONTROL_TYPE_TREE_NEW) + { + PH_TREENEW_COLUMN column; + + count = 0; + total = TreeNew_GetColumnCount(context->ControlHandle); + i = 0; + + displayOrderList = PhCreateList(total); + + while (count < total) + { + if (TreeNew_GetColumn(context->ControlHandle, i, &column)) + { + PPH_TREENEW_COLUMN copy; + + if (column.Fixed) + { + i++; + total--; + continue; + } + + copy = PhAllocateCopy(&column, sizeof(PH_TREENEW_COLUMN)); + PhAddItemList(context->Columns, copy); + count++; + + if (column.Visible) + { + PhAddItemList(displayOrderList, copy); + } + else + { + ListBox_AddString(context->InactiveList, column.Text); + } + } + + i++; + } + + qsort(displayOrderList->Items, displayOrderList->Count, sizeof(PVOID), PhpColumnsCompareDisplayIndexTn); + } + + if (displayOrderList) + { + for (i = 0; i < displayOrderList->Count; i++) + { + if (context->Type == PH_CONTROL_TYPE_TREE_NEW) + { + PPH_TREENEW_COLUMN copy = displayOrderList->Items[i]; + + ListBox_AddString(context->ActiveList, copy->Text); + } + } + + PhDereferenceObject(displayOrderList); + } + + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_INACTIVE, LBN_SELCHANGE), (LPARAM)context->InactiveList); + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_ACTIVE, LBN_SELCHANGE), (LPARAM)context->ActiveList); + } + break; + case WM_DESTROY: + { + ULONG i; + + for (i = 0; i < context->Columns->Count; i++) + PhFree(context->Columns->Items[i]); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { +#define ORDER_LIMIT 100 + PPH_LIST activeList; + ULONG activeCount; + ULONG i; + INT orderArray[ORDER_LIMIT]; + INT maxOrder; + + memset(orderArray, 0, sizeof(orderArray)); + maxOrder = 0; + + activeCount = ListBox_GetCount(context->ActiveList); + activeList = PhCreateList(activeCount); + + for (i = 0; i < activeCount; i++) + PhAddItemList(activeList, PhGetListBoxString(context->ActiveList, i)); + + if (context->Type == PH_CONTROL_TYPE_TREE_NEW) + { + // Apply visiblity settings and build the order array. + + TreeNew_SetRedraw(context->ControlHandle, FALSE); + + for (i = 0; i < context->Columns->Count; i++) + { + PPH_TREENEW_COLUMN column = context->Columns->Items[i]; + ULONG index; + + index = IndexOfStringInList(activeList, column->Text); + column->Visible = index != -1; + + TreeNew_SetColumn(context->ControlHandle, TN_COLUMN_FLAG_VISIBLE, column); + + if (column->Visible && index < ORDER_LIMIT) + { + orderArray[index] = column->Id; + + if ((ULONG)maxOrder < index + 1) + maxOrder = index + 1; + } + } + + // Apply display order. + TreeNew_SetColumnOrderArray(context->ControlHandle, maxOrder, orderArray); + + TreeNew_SetRedraw(context->ControlHandle, TRUE); + + PhDereferenceObject(activeList); + + InvalidateRect(context->ControlHandle, NULL, FALSE); + } + + EndDialog(hwndDlg, IDOK); + } + break; + case IDC_INACTIVE: + { + switch (HIWORD(wParam)) + { + case LBN_DBLCLK: + { + SendMessage(hwndDlg, WM_COMMAND, IDC_SHOW, 0); + } + break; + case LBN_SELCHANGE: + { + INT sel = ListBox_GetCurSel(context->InactiveList); + + EnableWindow(GetDlgItem(hwndDlg, IDC_SHOW), sel != -1); + } + break; + } + } + break; + case IDC_ACTIVE: + { + switch (HIWORD(wParam)) + { + case LBN_DBLCLK: + { + SendMessage(hwndDlg, WM_COMMAND, IDC_HIDE, 0); + } + break; + case LBN_SELCHANGE: + { + INT sel = ListBox_GetCurSel(context->ActiveList); + INT count = ListBox_GetCount(context->ActiveList); + + EnableWindow(GetDlgItem(hwndDlg, IDC_HIDE), sel != -1 && count != 1); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEUP), sel != 0 && sel != -1); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEDOWN), sel != count - 1 && sel != -1); + } + break; + } + } + break; + case IDC_SHOW: + { + INT sel; + INT count; + PPH_STRING string; + + sel = ListBox_GetCurSel(context->InactiveList); + count = ListBox_GetCount(context->InactiveList); + + if (string = PhGetListBoxString(context->InactiveList, sel)) + { + ListBox_DeleteString(context->InactiveList, sel); + ListBox_AddString(context->ActiveList, string->Buffer); + PhDereferenceObject(string); + + count--; + + if (sel >= count - 1) + sel = count - 1; + + ListBox_SetCurSel(context->InactiveList, sel); + + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_INACTIVE, LBN_SELCHANGE), (LPARAM)context->InactiveList); + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_ACTIVE, LBN_SELCHANGE), (LPARAM)context->ActiveList); + } + } + break; + case IDC_HIDE: + { + INT sel; + INT count; + PPH_STRING string; + + sel = ListBox_GetCurSel(context->ActiveList); + count = ListBox_GetCount(context->ActiveList); + + if (count != 1) + { + if (string = PhGetListBoxString(context->ActiveList, sel)) + { + ListBox_DeleteString(context->ActiveList, sel); + ListBox_AddString(context->InactiveList, string->Buffer); + PhDereferenceObject(string); + + count--; + + if (sel >= count - 1) + sel = count - 1; + + ListBox_SetCurSel(context->ActiveList, sel); + + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_INACTIVE, LBN_SELCHANGE), (LPARAM)context->InactiveList); + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_ACTIVE, LBN_SELCHANGE), (LPARAM)context->ActiveList); + } + } + } + break; + case IDC_MOVEUP: + { + INT sel; + INT count; + PPH_STRING string; + + sel = ListBox_GetCurSel(context->ActiveList); + count = ListBox_GetCount(context->ActiveList); + + if (sel != 0) + { + if (string = PhGetListBoxString(context->ActiveList, sel)) + { + ListBox_DeleteString(context->ActiveList, sel); + ListBox_InsertString(context->ActiveList, sel - 1, string->Buffer); + PhDereferenceObject(string); + + sel -= 1; + ListBox_SetCurSel(context->ActiveList, sel); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEUP), sel != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEDOWN), sel != count - 1); + } + } + } + break; + case IDC_MOVEDOWN: + { + INT sel; + INT count; + PPH_STRING string; + + sel = ListBox_GetCurSel(context->ActiveList); + count = ListBox_GetCount(context->ActiveList); + + if (sel != count - 1) + { + if (string = PhGetListBoxString(context->ActiveList, sel)) + { + ListBox_DeleteString(context->ActiveList, sel); + ListBox_InsertString(context->ActiveList, sel + 1, string->Buffer); + PhDereferenceObject(string); + + sel += 1; + ListBox_SetCurSel(context->ActiveList, sel); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEUP), sel != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEDOWN), sel != count - 1); + } + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/chdlg.c b/ProcessHacker/chdlg.c new file mode 100644 index 0000000..b906ade --- /dev/null +++ b/ProcessHacker/chdlg.c @@ -0,0 +1,358 @@ +/* + * Process Hacker - + * choice dialog + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct _CHOICE_DIALOG_CONTEXT +{ + PWSTR Title; + PWSTR Message; + PWSTR *Choices; + ULONG NumberOfChoices; + PWSTR Option; + ULONG Flags; + PPH_STRING *SelectedChoice; + PBOOLEAN SelectedOption; + PWSTR SavedChoicesSettingName; + + HWND ComboBoxHandle; +} CHOICE_DIALOG_CONTEXT, *PCHOICE_DIALOG_CONTEXT; + +INT_PTR CALLBACK PhpChoiceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +/** + * Prompts the user for input. + * + * \remarks If \c PH_CHOICE_DIALOG_PASSWORD is specified, the string + * returned in \a SelectedChoice is NOT auto-dereferenced. + */ +BOOLEAN PhaChoiceDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR Title, + _In_ PWSTR Message, + _In_opt_ PWSTR *Choices, + _In_opt_ ULONG NumberOfChoices, + _In_opt_ PWSTR Option, + _In_ ULONG Flags, + _Inout_ PPH_STRING *SelectedChoice, + _Inout_opt_ PBOOLEAN SelectedOption, + _In_opt_ PWSTR SavedChoicesSettingName + ) +{ + CHOICE_DIALOG_CONTEXT context; + + context.Title = Title; + context.Message = Message; + context.Choices = Choices; + context.NumberOfChoices = NumberOfChoices; + context.Option = Option; + context.Flags = Flags; + context.SelectedChoice = SelectedChoice; + context.SelectedOption = SelectedOption; + context.SavedChoicesSettingName = SavedChoicesSettingName; + + return DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_CHOOSE), + ParentWindowHandle, + PhpChoiceDlgProc, + (LPARAM)&context + ) == IDOK; +} + +INT_PTR CALLBACK PhpChoiceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PCHOICE_DIALOG_CONTEXT context = (PCHOICE_DIALOG_CONTEXT)lParam; + ULONG type; + SIZE_T i; + HWND comboBoxHandle; + HWND checkBoxHandle; + RECT checkBoxRect; + RECT rect; + ULONG diff; + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + SetWindowText(hwndDlg, context->Title); + SetWindowText(GetDlgItem(hwndDlg, IDC_MESSAGE), context->Message); + + type = context->Flags & PH_CHOICE_DIALOG_TYPE_MASK; + + // Select the control to show, depending on the type. This is + // because it is impossible to change the style of the combo box + // after it is created. + switch (type) + { + case PH_CHOICE_DIALOG_USER_CHOICE: + comboBoxHandle = GetDlgItem(hwndDlg, IDC_CHOICEUSER); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHOICEUSER), SW_SHOW); + break; + case PH_CHOICE_DIALOG_PASSWORD: + comboBoxHandle = GetDlgItem(hwndDlg, IDC_CHOICESIMPLE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHOICESIMPLE), SW_SHOW); + + // Disable combo box features since it isn't a combo box. + context->SavedChoicesSettingName = NULL; + break; + case PH_CHOICE_DIALOG_CHOICE: + default: + comboBoxHandle = GetDlgItem(hwndDlg, IDC_CHOICE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHOICE), SW_SHOW); + break; + } + + context->ComboBoxHandle = comboBoxHandle; + + checkBoxHandle = GetDlgItem(hwndDlg, IDC_OPTION); + + if (type == PH_CHOICE_DIALOG_PASSWORD) + { + // Nothing + } + else if (type == PH_CHOICE_DIALOG_USER_CHOICE && context->SavedChoicesSettingName) + { + PPH_STRING savedChoices = PhGetStringSetting(context->SavedChoicesSettingName); + ULONG_PTR indexOfDelim; + PPH_STRING savedChoice; + + i = 0; + + // Split the saved choices using the delimiter. + while (i < savedChoices->Length / 2) + { + // BUG BUG BUG - what if the user saves "\s"? + indexOfDelim = PhFindStringInString(savedChoices, i, L"\\s"); + + if (indexOfDelim == -1) + indexOfDelim = savedChoices->Length / 2; + + savedChoice = PhSubstring(savedChoices, i, indexOfDelim - i); + + if (savedChoice->Length != 0) + { + PPH_STRING unescaped; + + unescaped = PhUnescapeStringForDelimiter(savedChoice, '\\'); + ComboBox_InsertString(comboBoxHandle, -1, unescaped->Buffer); + PhDereferenceObject(unescaped); + } + + PhDereferenceObject(savedChoice); + + i = indexOfDelim + 2; + } + + PhDereferenceObject(savedChoices); + } + else + { + for (i = 0; i < context->NumberOfChoices; i++) + { + ComboBox_AddString(comboBoxHandle, context->Choices[i]); + } + + context->SavedChoicesSettingName = NULL; // make sure we don't try to save the choices + } + + if (type == PH_CHOICE_DIALOG_PASSWORD) + { + if (*context->SelectedChoice) + SetWindowText(comboBoxHandle, (*context->SelectedChoice)->Buffer); + + Edit_SetSel(comboBoxHandle, 0, -1); + } + else if (type == PH_CHOICE_DIALOG_USER_CHOICE || type == PH_CHOICE_DIALOG_CHOICE) + { + // If we failed to choose a default choice based on what was specified, + // select the first one if possible, or set the text directly. + if (!(*context->SelectedChoice) || PhSelectComboBoxString( + comboBoxHandle, (*context->SelectedChoice)->Buffer, FALSE) == CB_ERR) + { + if (type == PH_CHOICE_DIALOG_USER_CHOICE && *context->SelectedChoice) + { + SetWindowText(comboBoxHandle, (*context->SelectedChoice)->Buffer); + } + else if (type == PH_CHOICE_DIALOG_CHOICE && context->NumberOfChoices != 0) + { + ComboBox_SetCurSel(comboBoxHandle, 0); + } + } + + if (type == PH_CHOICE_DIALOG_USER_CHOICE) + ComboBox_SetEditSel(comboBoxHandle, 0, -1); + } + + if (context->Option) + { + SetWindowText(checkBoxHandle, context->Option); + + if (context->SelectedOption) + Button_SetCheck(checkBoxHandle, *context->SelectedOption ? BST_CHECKED : BST_UNCHECKED); + } + else + { + // Hide the check box and move the buttons up. + + ShowWindow(checkBoxHandle, SW_HIDE); + GetWindowRect(checkBoxHandle, &checkBoxRect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&checkBoxRect, 2); + GetWindowRect(GetDlgItem(hwndDlg, IDOK), &rect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&rect, 2); + diff = rect.top - checkBoxRect.top; + + // OK + rect.top -= diff; + rect.bottom -= diff; + SetWindowPos(GetDlgItem(hwndDlg, IDOK), NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + + // Cancel + GetWindowRect(GetDlgItem(hwndDlg, IDCANCEL), &rect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&rect, 2); + rect.top -= diff; + rect.bottom -= diff; + SetWindowPos(GetDlgItem(hwndDlg, IDCANCEL), NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + + // Window + GetWindowRect(hwndDlg, &rect); + rect.bottom -= diff; + SetWindowPos(hwndDlg, NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)comboBoxHandle, TRUE); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PCHOICE_DIALOG_CONTEXT context = (PCHOICE_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + PPH_STRING selectedChoice; + + if ((context->Flags & PH_CHOICE_DIALOG_TYPE_MASK) != PH_CHOICE_DIALOG_PASSWORD) + { + selectedChoice = PH_AUTO(PhGetWindowText(context->ComboBoxHandle)); + *context->SelectedChoice = selectedChoice; + } + else + { + // Password values are never auto-dereferenced. + selectedChoice = PhGetWindowText(context->ComboBoxHandle); + *context->SelectedChoice = selectedChoice; + } + + if (context->Option && context->SelectedOption) + *context->SelectedOption = Button_GetCheck(GetDlgItem(hwndDlg, IDC_OPTION)) == BST_CHECKED; + + if (context->SavedChoicesSettingName) + { + PH_STRING_BUILDER savedChoices; + ULONG i; + ULONG choicesToSave = PH_CHOICE_DIALOG_SAVED_CHOICES; + PPH_STRING choice; + PPH_STRING escaped; + + PhInitializeStringBuilder(&savedChoices, 100); + + // Push the selected choice to the top, then save the others. + + if (selectedChoice->Length != 0) + { + escaped = PhEscapeStringForDelimiter(selectedChoice, '\\'); + PhAppendStringBuilder(&savedChoices, &escaped->sr); + PhDereferenceObject(escaped); + PhAppendStringBuilder2(&savedChoices, L"\\s"); + } + + for (i = 1; i < choicesToSave; i++) + { + choice = PhGetComboBoxString(context->ComboBoxHandle, i - 1); + + if (!choice) + break; + + // Don't save the choice if it's the same as the one + // entered by the user (since we already saved it above). + if (PhEqualString(choice, selectedChoice, FALSE)) + { + PhDereferenceObject(choice); + choicesToSave++; // useless for now, but may be needed in the future + continue; + } + + escaped = PhEscapeStringForDelimiter(choice, '\\'); + PhAppendStringBuilder(&savedChoices, &escaped->sr); + PhDereferenceObject(escaped); + PhDereferenceObject(choice); + + PhAppendStringBuilder2(&savedChoices, L"\\s"); + } + + if (PhEndsWithString2(savedChoices.String, L"\\s", FALSE)) + PhRemoveEndStringBuilder(&savedChoices, 2); + + PhSetStringSetting2(context->SavedChoicesSettingName, &savedChoices.String->sr); + PhDeleteStringBuilder(&savedChoices); + } + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/chproc.c b/ProcessHacker/chproc.c new file mode 100644 index 0000000..0fd2319 --- /dev/null +++ b/ProcessHacker/chproc.c @@ -0,0 +1,315 @@ +/* + * Process Hacker - + * choose process dialog + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +typedef struct _CHOOSE_PROCESS_DIALOG_CONTEXT +{ + PWSTR Message; + HANDLE ProcessId; + + PH_LAYOUT_MANAGER LayoutManager; + RECT MinimumSize; + HIMAGELIST ImageList; + HWND ListViewHandle; +} CHOOSE_PROCESS_DIALOG_CONTEXT, *PCHOOSE_PROCESS_DIALOG_CONTEXT; + +INT_PTR CALLBACK PhpChooseProcessDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +BOOLEAN PhShowChooseProcessDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR Message, + _Out_ PHANDLE ProcessId + ) +{ + CHOOSE_PROCESS_DIALOG_CONTEXT context; + + context.Message = Message; + context.ProcessId = NULL; + + if (DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_CHOOSEPROCESS), + ParentWindowHandle, + PhpChooseProcessDlgProc, + (LPARAM)&context + ) == IDOK) + { + *ProcessId = context.ProcessId; + + return TRUE; + } + else + { + return FALSE; + } +} + +static VOID PhpRefreshProcessList( + _In_ HWND hwndDlg, + _In_ PCHOOSE_PROCESS_DIALOG_CONTEXT Context + ) +{ + NTSTATUS status; + HWND lvHandle; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + + lvHandle = Context->ListViewHandle; + + ListView_DeleteAllItems(lvHandle); + ImageList_RemoveAll(Context->ImageList); + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + { + PhShowStatus(hwndDlg, L"Unable to enumerate processes", status, 0); + return; + } + + ExtendedListView_SetRedraw(lvHandle, FALSE); + + process = PH_FIRST_PROCESS(processes); + + do + { + INT lvItemIndex; + PPH_STRING name; + HANDLE processHandle; + PPH_STRING fileName = NULL; + HICON icon = NULL; + WCHAR processIdString[PH_INT32_STR_LEN_1]; + PPH_STRING userName = NULL; + INT imageIndex; + + if (process->UniqueProcessId != SYSTEM_IDLE_PROCESS_ID) + name = PhCreateStringFromUnicodeString(&process->ImageName); + else + name = PhCreateString(SYSTEM_IDLE_PROCESS_NAME); + + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, name->Buffer, process->UniqueProcessId); + PhDereferenceObject(name); + + if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess, process->UniqueProcessId))) + { + HANDLE tokenHandle; + PTOKEN_USER user; + + if (!WINDOWS_HAS_IMAGE_FILE_NAME_BY_PROCESS_ID && process->UniqueProcessId != SYSTEM_PROCESS_ID) + PhGetProcessImageFileName(processHandle, &fileName); + + if (NT_SUCCESS(PhOpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle))) + { + if (NT_SUCCESS(PhGetTokenUser(tokenHandle, &user))) + { + userName = PhGetSidFullName(user->User.Sid, TRUE, NULL); + PhFree(user); + } + + NtClose(tokenHandle); + } + + NtClose(processHandle); + } + + if (process->UniqueProcessId == SYSTEM_IDLE_PROCESS_ID && !userName && PhLocalSystemName) + PhSetReference(&userName, PhLocalSystemName); + + if (WINDOWS_HAS_IMAGE_FILE_NAME_BY_PROCESS_ID && process->UniqueProcessId != SYSTEM_PROCESS_ID) + PhGetProcessImageFileNameByProcessId(process->UniqueProcessId, &fileName); + + if (process->UniqueProcessId == SYSTEM_PROCESS_ID) + fileName = PhGetKernelFileName(); + + if (fileName) + PhMoveReference(&fileName, PhGetFileName(fileName)); + + icon = PhGetFileShellIcon(PhGetString(fileName), L".exe", FALSE); + + // Icon + if (icon) + { + imageIndex = ImageList_AddIcon(Context->ImageList, icon); + PhSetListViewItemImageIndex(Context->ListViewHandle, lvItemIndex, imageIndex); + DestroyIcon(icon); + } + + // PID + PhPrintUInt32(processIdString, HandleToUlong(process->UniqueProcessId)); + PhSetListViewSubItem(Context->ListViewHandle, lvItemIndex, 1, processIdString); + + // User Name + PhSetListViewSubItem(Context->ListViewHandle, lvItemIndex, 2, PhGetString(userName)); + + if (userName) PhDereferenceObject(userName); + if (fileName) PhDereferenceObject(fileName); + } while (process = PH_NEXT_PROCESS(process)); + + PhFree(processes); + + ExtendedListView_SortItems(lvHandle); + ExtendedListView_SetRedraw(lvHandle, TRUE); +} + +INT_PTR CALLBACK PhpChooseProcessDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCHOOSE_PROCESS_DIALOG_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PCHOOSE_PROCESS_DIALOG_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + else + { + context = (PCHOOSE_PROCESS_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + SetDlgItemText(hwndDlg, IDC_MESSAGE, context->Message); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_MESSAGE), NULL, + PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LIST), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDCANCEL), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_REFRESH), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhLayoutManagerLayout(&context->LayoutManager); + + context->MinimumSize.left = 0; + context->MinimumSize.top = 0; + context->MinimumSize.right = 280; + context->MinimumSize.bottom = 170; + MapDialogRect(hwndDlg, &context->MinimumSize); + + context->ListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + context->ImageList = ImageList_Create(PhSmallIconSize.X, PhSmallIconSize.Y, ILC_COLOR32 | ILC_MASK, 0, 40); + + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 180, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 60, L"PID"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 160, L"User name"); + PhSetExtendedListView(lvHandle); + + ListView_SetImageList(lvHandle, context->ImageList, LVSIL_SMALL); + + PhpRefreshProcessList(hwndDlg, context); + + EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + } + break; + case WM_DESTROY: + { + ImageList_Destroy(context->ImageList); + PhDeleteLayoutManager(&context->LayoutManager); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + EndDialog(hwndDlg, IDCANCEL); + } + break; + case IDOK: + { + if (ListView_GetSelectedCount(context->ListViewHandle) == 1) + { + context->ProcessId = (HANDLE)PhGetSelectedListViewItemParam(context->ListViewHandle); + EndDialog(hwndDlg, IDOK); + } + } + break; + case IDC_REFRESH: + { + PhpRefreshProcessList(hwndDlg, context); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + EnableWindow(GetDlgItem(hwndDlg, IDOK), ListView_GetSelectedCount(context->ListViewHandle) == 1); + } + break; + case NM_DBLCLK: + { + SendMessage(hwndDlg, WM_COMMAND, IDOK, 0); + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, context->MinimumSize.right, context->MinimumSize.bottom); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/cmdmode.c b/ProcessHacker/cmdmode.c new file mode 100644 index 0000000..3b34061 --- /dev/null +++ b/ProcessHacker/cmdmode.c @@ -0,0 +1,451 @@ +/* + * Process Hacker - + * command line action mode + * + * Copyright (C) 2010-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +NTSTATUS PhpGetDllBaseRemote( + _In_ HANDLE ProcessHandle, + _In_ PPH_STRINGREF BaseDllName, + _Out_ PVOID *DllBase + ); + +static HWND CommandModeWindowHandle; + +#define PH_COMMAND_OPTION_HWND 1 + +BOOLEAN NTAPI PhpCommandModeOptionCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + ULONG64 integer; + + if (Option) + { + switch (Option->Id) + { + case PH_COMMAND_OPTION_HWND: + if (PhStringToInteger64(&Value->sr, 10, &integer)) + CommandModeWindowHandle = (HWND)integer; + break; + } + } + + return TRUE; +} + +NTSTATUS PhCommandModeStart( + VOID + ) +{ + static PH_COMMAND_LINE_OPTION options[] = + { + { PH_COMMAND_OPTION_HWND, L"hwnd", MandatoryArgumentType } + }; + NTSTATUS status = STATUS_SUCCESS; + PH_STRINGREF commandLine; + + PhUnicodeStringToStringRef(&NtCurrentPeb()->ProcessParameters->CommandLine, &commandLine); + + PhParseCommandLine( + &commandLine, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS, + PhpCommandModeOptionCallback, + NULL + ); + + if (PhEqualString2(PhStartupParameters.CommandType, L"process", TRUE)) + { + SIZE_T i; + SIZE_T processIdLength; + HANDLE processId; + HANDLE processHandle; + + if (!PhStartupParameters.CommandObject) + return STATUS_INVALID_PARAMETER; + + processIdLength = PhStartupParameters.CommandObject->Length / 2; + + for (i = 0; i < processIdLength; i++) + { + if (!PhIsDigitCharacter(PhStartupParameters.CommandObject->Buffer[i])) + break; + } + + if (i == processIdLength) + { + ULONG64 processId64; + + if (!PhStringToInteger64(&PhStartupParameters.CommandObject->sr, 10, &processId64)) + return STATUS_INVALID_PARAMETER; + + processId = (HANDLE)processId64; + } + else + { + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + return status; + + if (!(process = PhFindProcessInformationByImageName(processes, &PhStartupParameters.CommandObject->sr))) + { + PhFree(processes); + return STATUS_NOT_FOUND; + } + + processId = process->UniqueProcessId; + PhFree(processes); + } + + if (PhEqualString2(PhStartupParameters.CommandAction, L"terminate", TRUE)) + { + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_TERMINATE, processId))) + { + status = NtTerminateProcess(processHandle, STATUS_SUCCESS); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"suspend", TRUE)) + { + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_SUSPEND_RESUME, processId))) + { + status = NtSuspendProcess(processHandle); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"resume", TRUE)) + { + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_SUSPEND_RESUME, processId))) + { + status = NtResumeProcess(processHandle); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"priority", TRUE)) + { + UCHAR priority; + + if (!PhStartupParameters.CommandValue) + return STATUS_INVALID_PARAMETER; + + if (PhEqualString2(PhStartupParameters.CommandValue, L"idle", TRUE)) + priority = PROCESS_PRIORITY_CLASS_IDLE; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"normal", TRUE)) + priority = PROCESS_PRIORITY_CLASS_NORMAL; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"high", TRUE)) + priority = PROCESS_PRIORITY_CLASS_HIGH; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"realtime", TRUE)) + priority = PROCESS_PRIORITY_CLASS_REALTIME; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"abovenormal", TRUE)) + priority = PROCESS_PRIORITY_CLASS_ABOVE_NORMAL; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"belownormal", TRUE)) + priority = PROCESS_PRIORITY_CLASS_BELOW_NORMAL; + else + return STATUS_INVALID_PARAMETER; + + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_SET_INFORMATION, processId))) + { + PROCESS_PRIORITY_CLASS priorityClass; + priorityClass.Foreground = FALSE; + priorityClass.PriorityClass = priority; + status = NtSetInformationProcess(processHandle, ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS)); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"iopriority", TRUE)) + { + ULONG ioPriority; + + if (!PhStartupParameters.CommandValue) + return STATUS_INVALID_PARAMETER; + + if (PhEqualString2(PhStartupParameters.CommandValue, L"verylow", TRUE)) + ioPriority = 0; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"low", TRUE)) + ioPriority = 1; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"normal", TRUE)) + ioPriority = 2; + else if (PhEqualString2(PhStartupParameters.CommandValue, L"high", TRUE)) + ioPriority = 3; + else + return STATUS_INVALID_PARAMETER; + + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_SET_INFORMATION, processId))) + { + status = PhSetProcessIoPriority(processHandle, ioPriority); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"pagepriority", TRUE)) + { + ULONG64 pagePriority64; + ULONG pagePriority; + + if (!PhStartupParameters.CommandValue) + return STATUS_INVALID_PARAMETER; + + PhStringToInteger64(&PhStartupParameters.CommandValue->sr, 10, &pagePriority64); + pagePriority = (ULONG)pagePriority64; + + if (NT_SUCCESS(status = PhOpenProcessPublic(&processHandle, PROCESS_SET_INFORMATION, processId))) + { + status = NtSetInformationProcess( + processHandle, + ProcessPagePriority, + &pagePriority, + sizeof(ULONG) + ); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"injectdll", TRUE)) + { + if (!PhStartupParameters.CommandValue) + return STATUS_INVALID_PARAMETER; + + if (NT_SUCCESS(status = PhOpenProcessPublic( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, + processId + ))) + { + LARGE_INTEGER timeout; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + status = PhInjectDllProcess( + processHandle, + PhStartupParameters.CommandValue->Buffer, + &timeout + ); + NtClose(processHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"unloaddll", TRUE)) + { + if (!PhStartupParameters.CommandValue) + return STATUS_INVALID_PARAMETER; + + if (NT_SUCCESS(status = PhOpenProcessPublic( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, + processId + ))) + { + PVOID baseAddress; + + if (NT_SUCCESS(status = PhpGetDllBaseRemote( + processHandle, + &PhStartupParameters.CommandValue->sr, + &baseAddress + ))) + { + LARGE_INTEGER timeout; + + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + status = PhUnloadDllProcess( + processHandle, + baseAddress, + &timeout + ); + } + + NtClose(processHandle); + } + } + } + else if (PhEqualString2(PhStartupParameters.CommandType, L"service", TRUE)) + { + SC_HANDLE serviceHandle; + SERVICE_STATUS serviceStatus; + + if (!PhStartupParameters.CommandObject) + return STATUS_INVALID_PARAMETER; + + if (PhEqualString2(PhStartupParameters.CommandAction, L"start", TRUE)) + { + if (!(serviceHandle = PhOpenService( + PhStartupParameters.CommandObject->Buffer, + SERVICE_START + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + if (!StartService(serviceHandle, 0, NULL)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"continue", TRUE)) + { + if (!(serviceHandle = PhOpenService( + PhStartupParameters.CommandObject->Buffer, + SERVICE_PAUSE_CONTINUE + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + if (!ControlService(serviceHandle, SERVICE_CONTROL_CONTINUE, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"pause", TRUE)) + { + if (!(serviceHandle = PhOpenService( + PhStartupParameters.CommandObject->Buffer, + SERVICE_PAUSE_CONTINUE + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + if (!ControlService(serviceHandle, SERVICE_CONTROL_PAUSE, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"stop", TRUE)) + { + if (!(serviceHandle = PhOpenService( + PhStartupParameters.CommandObject->Buffer, + SERVICE_STOP + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + if (!ControlService(serviceHandle, SERVICE_CONTROL_STOP, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"delete", TRUE)) + { + if (!(serviceHandle = PhOpenService( + PhStartupParameters.CommandObject->Buffer, + DELETE + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + if (!DeleteService(serviceHandle)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandType, L"thread", TRUE)) + { + ULONG64 threadId64; + HANDLE threadId; + HANDLE threadHandle; + + if (!PhStartupParameters.CommandObject) + return STATUS_INVALID_PARAMETER; + + if (!PhStringToInteger64(&PhStartupParameters.CommandObject->sr, 10, &threadId64)) + return STATUS_INVALID_PARAMETER; + + threadId = (HANDLE)threadId64; + + if (PhEqualString2(PhStartupParameters.CommandAction, L"terminate", TRUE)) + { + if (NT_SUCCESS(status = PhOpenThreadPublic(&threadHandle, THREAD_TERMINATE, threadId))) + { + status = NtTerminateThread(threadHandle, STATUS_SUCCESS); + NtClose(threadHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"suspend", TRUE)) + { + if (NT_SUCCESS(status = PhOpenThreadPublic(&threadHandle, THREAD_SUSPEND_RESUME, threadId))) + { + status = NtSuspendThread(threadHandle, NULL); + NtClose(threadHandle); + } + } + else if (PhEqualString2(PhStartupParameters.CommandAction, L"resume", TRUE)) + { + if (NT_SUCCESS(status = PhOpenThreadPublic(&threadHandle, THREAD_SUSPEND_RESUME, threadId))) + { + status = NtResumeThread(threadHandle, NULL); + NtClose(threadHandle); + } + } + } + + return status; +} + +typedef struct _GET_DLL_BASE_REMOTE_CONTEXT +{ + PH_STRINGREF BaseDllName; + PVOID DllBase; +} GET_DLL_BASE_REMOTE_CONTEXT, *PGET_DLL_BASE_REMOTE_CONTEXT; + +static BOOLEAN PhpGetDllBaseRemoteCallback( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ) +{ + PGET_DLL_BASE_REMOTE_CONTEXT context = Context; + PH_STRINGREF baseDllName; + + PhUnicodeStringToStringRef(&Module->BaseDllName, &baseDllName); + + if (PhEqualStringRef(&baseDllName, &context->BaseDllName, TRUE)) + { + context->DllBase = Module->DllBase; + return FALSE; + } + + return TRUE; +} + +NTSTATUS PhpGetDllBaseRemote( + _In_ HANDLE ProcessHandle, + _In_ PPH_STRINGREF BaseDllName, + _Out_ PVOID *DllBase + ) +{ + NTSTATUS status; + GET_DLL_BASE_REMOTE_CONTEXT context; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; +#endif + + context.BaseDllName = *BaseDllName; + context.DllBase = NULL; + +#ifdef _WIN64 + PhGetProcessIsWow64(ProcessHandle, &isWow64); + + if (isWow64) + status = PhEnumProcessModules32(ProcessHandle, PhpGetDllBaseRemoteCallback, &context); + if (!context.DllBase) +#endif + status = PhEnumProcessModules(ProcessHandle, PhpGetDllBaseRemoteCallback, &context); + + if (NT_SUCCESS(status)) + *DllBase = context.DllBase; + + return status; +} diff --git a/ProcessHacker/colmgr.c b/ProcessHacker/colmgr.c new file mode 100644 index 0000000..db6f7e1 --- /dev/null +++ b/ProcessHacker/colmgr.c @@ -0,0 +1,709 @@ +/* + * Process Hacker - + * tree new column manager + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +typedef struct _PH_CM_SORT_CONTEXT +{ + PPH_PLUGIN_TREENEW_SORT_FUNCTION SortFunction; + ULONG SubId; + PVOID Context; + PPH_CM_POST_SORT_FUNCTION PostSortFunction; + PH_SORT_ORDER SortOrder; +} PH_CM_SORT_CONTEXT, *PPH_CM_SORT_CONTEXT; + +VOID PhCmInitializeManager( + _Out_ PPH_CM_MANAGER Manager, + _In_ HWND Handle, + _In_ ULONG MinId, + _In_ PPH_CM_POST_SORT_FUNCTION PostSortFunction + ) +{ + Manager->Handle = Handle; + Manager->MinId = MinId; + Manager->NextId = MinId; + Manager->PostSortFunction = PostSortFunction; + InitializeListHead(&Manager->ColumnListHead); + Manager->NotifyList = NULL; +} + +VOID PhCmDeleteManager( + _In_ PPH_CM_MANAGER Manager + ) +{ + PLIST_ENTRY listEntry; + PPH_CM_COLUMN column; + + listEntry = Manager->ColumnListHead.Flink; + + while (listEntry != &Manager->ColumnListHead) + { + column = CONTAINING_RECORD(listEntry, PH_CM_COLUMN, ListEntry); + listEntry = listEntry->Flink; + + PhFree(column); + } + + if (Manager->NotifyList) + PhDereferenceObject(Manager->NotifyList); +} + +PPH_CM_COLUMN PhCmCreateColumn( + _Inout_ PPH_CM_MANAGER Manager, + _In_ PPH_TREENEW_COLUMN Column, + _In_ struct _PH_PLUGIN *Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PVOID SortFunction + ) +{ + PPH_CM_COLUMN column; + PH_TREENEW_COLUMN tnColumn; + + column = PhAllocate(sizeof(PH_CM_COLUMN)); + memset(column, 0, sizeof(PH_CM_COLUMN)); + column->Id = Manager->NextId++; + column->Plugin = Plugin; + column->SubId = SubId; + column->Context = Context; + column->SortFunction = SortFunction; + InsertTailList(&Manager->ColumnListHead, &column->ListEntry); + + memset(&tnColumn, 0, sizeof(PH_TREENEW_COLUMN)); + tnColumn.Id = column->Id; + tnColumn.Context = column; + tnColumn.Visible = Column->Visible; + tnColumn.CustomDraw = Column->CustomDraw; + tnColumn.SortDescending = Column->SortDescending; + tnColumn.Text = Column->Text; + tnColumn.Width = Column->Width; + tnColumn.Alignment = Column->Alignment; + tnColumn.DisplayIndex = Column->Visible ? Column->DisplayIndex : -1; + tnColumn.TextFlags = Column->TextFlags; + TreeNew_AddColumn(Manager->Handle, &tnColumn); + + return column; +} + +PPH_CM_COLUMN PhCmFindColumn( + _In_ PPH_CM_MANAGER Manager, + _In_ PPH_STRINGREF PluginName, + _In_ ULONG SubId + ) +{ + PLIST_ENTRY listEntry; + PPH_CM_COLUMN column; + + listEntry = Manager->ColumnListHead.Flink; + + while (listEntry != &Manager->ColumnListHead) + { + column = CONTAINING_RECORD(listEntry, PH_CM_COLUMN, ListEntry); + + if (column->SubId == SubId && PhEqualStringRef(PluginName, &column->Plugin->AppContext.AppName, FALSE)) + return column; + + listEntry = listEntry->Flink; + } + + return NULL; +} + +VOID PhCmSetNotifyPlugin( + _In_ PPH_CM_MANAGER Manager, + _In_ struct _PH_PLUGIN *Plugin + ) +{ + if (!Manager->NotifyList) + { + Manager->NotifyList = PhCreateList(8); + } + else + { + if (PhFindItemList(Manager->NotifyList, Plugin) != -1) + return; + } + + PhAddItemList(Manager->NotifyList, Plugin); +} + +BOOLEAN PhCmForwardMessage( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_ PPH_CM_MANAGER Manager + ) +{ + PH_PLUGIN_TREENEW_MESSAGE pluginMessage; + PPH_PLUGIN plugin; + + if (Message == TreeNewDestroying) + return FALSE; + + switch (Message) + { + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PH_TREENEW_COLUMN tnColumn; + PPH_CM_COLUMN column; + + if (getCellText->Id < Manager->MinId) + return FALSE; + + if (!TreeNew_GetColumn(hwnd, getCellText->Id, &tnColumn)) + return FALSE; + + column = tnColumn.Context; + pluginMessage.SubId = column->SubId; + pluginMessage.Context = column->Context; + plugin = column->Plugin; + } + break; + case TreeNewCustomDraw: + { + PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1; + PPH_CM_COLUMN column; + + if (customDraw->Column->Id < Manager->MinId) + return FALSE; + + column = customDraw->Column->Context; + pluginMessage.SubId = column->SubId; + pluginMessage.Context = column->Context; + plugin = column->Plugin; + } + break; + case TreeNewColumnResized: + { + PPH_TREENEW_COLUMN tlColumn = Parameter1; + PPH_CM_COLUMN column; + + if (tlColumn->Id < Manager->MinId) + return FALSE; + + column = tlColumn->Context; + pluginMessage.SubId = column->SubId; + pluginMessage.Context = column->Context; + plugin = column->Plugin; + } + break; + default: + { + // Some plugins want to be notified about all messages. + if (Manager->NotifyList) + { + ULONG i; + + for (i = 0; i < Manager->NotifyList->Count; i++) + { + plugin = Manager->NotifyList->Items[i]; + + pluginMessage.TreeNewHandle = hwnd; + pluginMessage.Message = Message; + pluginMessage.Parameter1 = Parameter1; + pluginMessage.Parameter2 = Parameter2; + pluginMessage.SubId = 0; + pluginMessage.Context = NULL; + + PhInvokeCallback(PhGetPluginCallback(plugin, PluginCallbackTreeNewMessage), &pluginMessage); + } + } + } + return FALSE; + } + + pluginMessage.TreeNewHandle = hwnd; + pluginMessage.Message = Message; + pluginMessage.Parameter1 = Parameter1; + pluginMessage.Parameter2 = Parameter2; + + PhInvokeCallback(PhGetPluginCallback(plugin, PluginCallbackTreeNewMessage), &pluginMessage); + + return TRUE; +} + +static int __cdecl PhCmpSortFunction( + _In_ void *context, + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_CM_SORT_CONTEXT sortContext = context; + PVOID node1 = *(PVOID *)elem1; + PVOID node2 = *(PVOID *)elem2; + LONG result; + + result = sortContext->SortFunction(node1, node2, sortContext->SubId, sortContext->Context); + + return sortContext->PostSortFunction(result, node1, node2, sortContext->SortOrder); +} + +BOOLEAN PhCmForwardSort( + _In_ PPH_TREENEW_NODE *Nodes, + _In_ ULONG NumberOfNodes, + _In_ ULONG SortColumn, + _In_ PH_SORT_ORDER SortOrder, + _In_ PPH_CM_MANAGER Manager + ) +{ + PH_TREENEW_COLUMN tnColumn; + PPH_CM_COLUMN column; + PH_CM_SORT_CONTEXT sortContext; + + if (SortColumn < Manager->MinId) + return FALSE; + + if (!TreeNew_GetColumn(Manager->Handle, SortColumn, &tnColumn)) + return TRUE; + + column = tnColumn.Context; + + if (!column->SortFunction) + return TRUE; + + sortContext.SortFunction = column->SortFunction; + sortContext.SubId = column->SubId; + sortContext.Context = column->Context; + sortContext.PostSortFunction = Manager->PostSortFunction; + sortContext.SortOrder = SortOrder; + qsort_s(Nodes, NumberOfNodes, sizeof(PVOID), PhCmpSortFunction, &sortContext); + + return TRUE; +} + +BOOLEAN PhCmLoadSettings( + _In_ HWND TreeNewHandle, + _In_ PPH_STRINGREF Settings + ) +{ + return PhCmLoadSettingsEx(TreeNewHandle, NULL, 0, Settings, NULL); +} + +BOOLEAN PhCmLoadSettingsEx( + _In_ HWND TreeNewHandle, + _In_opt_ PPH_CM_MANAGER Manager, + _In_ ULONG Flags, + _In_ PPH_STRINGREF Settings, + _In_opt_ PPH_STRINGREF SortSettings + ) +{ + BOOLEAN result = FALSE; + PH_STRINGREF scalePart; + PH_STRINGREF columnPart; + PH_STRINGREF remainingColumnPart; + PH_STRINGREF valuePart; + PH_STRINGREF subPart; + ULONG64 integer; + ULONG scale; + ULONG total; + BOOLEAN hasFixedColumn; + ULONG count; + ULONG i; + PPH_HASHTABLE columnHashtable; + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_KEY_VALUE_PAIR pair; + LONG orderArray[PH_CM_ORDER_LIMIT]; + LONG maxOrder; + + if (Settings->Length != 0) + { + columnHashtable = PhCreateSimpleHashtable(20); + + remainingColumnPart = *Settings; + + if (remainingColumnPart.Length != 0 && remainingColumnPart.Buffer[0] == '@') + { + PhSkipStringRef(&remainingColumnPart, sizeof(WCHAR)); + PhSplitStringRefAtChar(&remainingColumnPart, '|', &scalePart, &remainingColumnPart); + + if (scalePart.Length == 0 || !PhStringToInteger64(&scalePart, 10, &integer)) + goto CleanupExit; + + scale = (ULONG)integer; + } + else + { + scale = PhGlobalDpi; + } + + while (remainingColumnPart.Length != 0) + { + PPH_TREENEW_COLUMN column; + ULONG id; + ULONG displayIndex; + ULONG width; + + PhSplitStringRefAtChar(&remainingColumnPart, '|', &columnPart, &remainingColumnPart); + + if (columnPart.Length != 0) + { + // Id + + PhSplitStringRefAtChar(&columnPart, ',', &valuePart, &columnPart); + + if (valuePart.Length == 0) + goto CleanupExit; + + if (valuePart.Buffer[0] == '+') + { + PH_STRINGREF pluginName; + ULONG subId; + PPH_CM_COLUMN cmColumn; + + // This is a plugin-owned column. + + if (!Manager) + continue; + if (!PhEmParseCompoundId(&valuePart, &pluginName, &subId)) + continue; + + cmColumn = PhCmFindColumn(Manager, &pluginName, subId); + + if (!cmColumn) + continue; // can't find the column, skip this part + + id = cmColumn->Id; + } + else + { + if (!PhStringToInteger64(&valuePart, 10, &integer)) + goto CleanupExit; + + id = (ULONG)integer; + } + + // Display Index + + PhSplitStringRefAtChar(&columnPart, ',', &valuePart, &columnPart); + + if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY)) + { + if (valuePart.Length == 0 || !PhStringToInteger64(&valuePart, 10, &integer)) + goto CleanupExit; + + displayIndex = (ULONG)integer; + } + else + { + if (valuePart.Length != 0) + goto CleanupExit; + + displayIndex = -1; + } + + // Width + + if (columnPart.Length == 0 || !PhStringToInteger64(&columnPart, 10, &integer)) + goto CleanupExit; + + width = (ULONG)integer; + + if (scale != PhGlobalDpi && scale != 0) + width = PhMultiplyDivide(width, PhGlobalDpi, scale); + + column = PhAllocate(sizeof(PH_TREENEW_COLUMN)); + column->Id = id; + column->DisplayIndex = displayIndex; + column->Width = width; + PhAddItemSimpleHashtable(columnHashtable, UlongToPtr(column->Id), column); + } + } + + TreeNew_SetRedraw(TreeNewHandle, FALSE); + + // Set visibility and width. + + i = 0; + count = 0; + total = TreeNew_GetColumnCount(TreeNewHandle); + hasFixedColumn = !!TreeNew_GetFixedColumn(TreeNewHandle); + memset(orderArray, 0, sizeof(orderArray)); + maxOrder = 0; + + while (count < total) + { + PH_TREENEW_COLUMN setColumn; + PPH_TREENEW_COLUMN *columnPtr; + + if (TreeNew_GetColumn(TreeNewHandle, i, &setColumn)) + { + columnPtr = (PPH_TREENEW_COLUMN *)PhFindItemSimpleHashtable(columnHashtable, UlongToPtr(i)); + + if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY)) + { + if (columnPtr) + { + setColumn.Visible = TRUE; + setColumn.Width = (*columnPtr)->Width; + TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_FLAG_VISIBLE | TN_COLUMN_WIDTH, &setColumn); + + if (!setColumn.Fixed) + { + // For compatibility reasons, normal columns have their display indicies stored + // one higher than usual (so they start from 1, not 0). Fix that here. + if (hasFixedColumn && (*columnPtr)->DisplayIndex != 0) + (*columnPtr)->DisplayIndex--; + + if ((*columnPtr)->DisplayIndex < PH_CM_ORDER_LIMIT) + { + orderArray[(*columnPtr)->DisplayIndex] = i; + + if ((ULONG)maxOrder < (*columnPtr)->DisplayIndex + 1) + maxOrder = (*columnPtr)->DisplayIndex + 1; + } + } + } + else if (!setColumn.Fixed) // never hide the fixed column + { + setColumn.Visible = FALSE; + TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_FLAG_VISIBLE, &setColumn); + } + } + else + { + if (columnPtr) + { + setColumn.Width = (*columnPtr)->Width; + TreeNew_SetColumn(TreeNewHandle, TN_COLUMN_WIDTH, &setColumn); + } + } + + count++; + } + + i++; + } + + if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY)) + { + // Set the order array. + TreeNew_SetColumnOrderArray(TreeNewHandle, maxOrder, orderArray); + } + + TreeNew_SetRedraw(TreeNewHandle, TRUE); + + result = TRUE; + +CleanupExit: + PhBeginEnumHashtable(columnHashtable, &enumContext); + + while (pair = PhNextEnumHashtable(&enumContext)) + PhFree(pair->Value); + + PhDereferenceObject(columnHashtable); + } + + // Load sort settings. + + if (SortSettings && SortSettings->Length != 0) + { + PhSplitStringRefAtChar(SortSettings, ',', &valuePart, &subPart); + + if (valuePart.Length != 0 && subPart.Length != 0) + { + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + sortColumn = -1; + + if (valuePart.Buffer[0] == '+') + { + PH_STRINGREF pluginName; + ULONG subId; + PPH_CM_COLUMN cmColumn; + + if ( + Manager && + PhEmParseCompoundId(&valuePart, &pluginName, &subId) && + (cmColumn = PhCmFindColumn(Manager, &pluginName, subId)) + ) + { + sortColumn = cmColumn->Id; + } + } + else + { + PhStringToInteger64(&valuePart, 10, &integer); + sortColumn = (ULONG)integer; + } + + PhStringToInteger64(&subPart, 10, &integer); + sortOrder = (PH_SORT_ORDER)integer; + + if (sortColumn != -1 && sortOrder <= DescendingSortOrder) + { + TreeNew_SetSort(TreeNewHandle, sortColumn, sortOrder); + } + } + } + + return result; +} + +PPH_STRING PhCmSaveSettings( + _In_ HWND TreeNewHandle + ) +{ + return PhCmSaveSettingsEx(TreeNewHandle, NULL, 0, NULL); +} + +PPH_STRING PhCmSaveSettingsEx( + _In_ HWND TreeNewHandle, + _In_opt_ PPH_CM_MANAGER Manager, + _In_ ULONG Flags, + _Out_opt_ PPH_STRING *SortSettings + ) +{ + PH_STRING_BUILDER stringBuilder; + ULONG i = 0; + ULONG count = 0; + ULONG total; + ULONG increment; + PH_TREENEW_COLUMN column; + + total = TreeNew_GetColumnCount(TreeNewHandle); + + if (TreeNew_GetFixedColumn(TreeNewHandle)) + increment = 1; // the first normal column should have a display index that starts with 1, for compatibility + else + increment = 0; + + PhInitializeStringBuilder(&stringBuilder, 100); + + PhAppendFormatStringBuilder(&stringBuilder, L"@%u|", PhGlobalDpi); + + while (count < total) + { + if (TreeNew_GetColumn(TreeNewHandle, i, &column)) + { + if (!(Flags & PH_CM_COLUMN_WIDTHS_ONLY)) + { + if (column.Visible) + { + if (!Manager || i < Manager->MinId) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%u,%u,%u|", + i, + column.Fixed ? 0 : column.DisplayIndex + increment, + column.Width + ); + } + else + { + PPH_CM_COLUMN cmColumn; + + cmColumn = column.Context; + PhAppendFormatStringBuilder( + &stringBuilder, + L"+%s+%u,%u,%u|", + cmColumn->Plugin->Name.Buffer, + cmColumn->SubId, + column.DisplayIndex + increment, + column.Width + ); + } + } + } + else + { + if (!Manager || i < Manager->MinId) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%u,,%u|", + i, + column.Width + ); + } + else + { + PPH_CM_COLUMN cmColumn; + + cmColumn = column.Context; + PhAppendFormatStringBuilder( + &stringBuilder, + L"+%s+%u,,%u|", + cmColumn->Plugin->Name.Buffer, + cmColumn->SubId, + column.Width + ); + } + } + + count++; + } + + i++; + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + if (SortSettings) + { + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + if (TreeNew_GetSort(TreeNewHandle, &sortColumn, &sortOrder)) + { + if (sortOrder != NoSortOrder) + { + if (!Manager || sortColumn < Manager->MinId) + { + *SortSettings = PhFormatString(L"%u,%u", sortColumn, sortOrder); + } + else + { + PH_TREENEW_COLUMN column; + PPH_CM_COLUMN cmColumn; + + if (TreeNew_GetColumn(TreeNewHandle, sortColumn, &column)) + { + cmColumn = column.Context; + *SortSettings = PhFormatString(L"+%s+%u,%u", cmColumn->Plugin->Name.Buffer, cmColumn->SubId, sortOrder); + } + else + { + *SortSettings = PhReferenceEmptyString(); + } + } + } + else + { + *SortSettings = PhCreateString(L"0,0"); + } + } + else + { + *SortSettings = PhReferenceEmptyString(); + } + } + + return PhFinalStringBuilderString(&stringBuilder); +} diff --git a/ProcessHacker/dbgcon.c b/ProcessHacker/dbgcon.c new file mode 100644 index 0000000..9830b55 --- /dev/null +++ b/ProcessHacker/dbgcon.c @@ -0,0 +1,1693 @@ +/* + * Process Hacker - + * debug console + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This is a simple debugging console which is able to explore phlib's + * systems easily. Commands are provided to debug reference counting + * problems and memory usage, as well as to do general performance testing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _STRING_TABLE_ENTRY +{ + PPH_STRING String; + ULONG_PTR Count; +} STRING_TABLE_ENTRY, *PSTRING_TABLE_ENTRY; + +BOOL ConsoleHandlerRoutine( + _In_ DWORD dwCtrlType + ); + +VOID PhpPrintHashtableStatistics( + _In_ PPH_HASHTABLE Hashtable + ); + +NTSTATUS PhpDebugConsoleThreadStart( + _In_ PVOID Parameter + ); + +extern PH_FREE_LIST PhObjectSmallFreeList; + +static HANDLE DebugConsoleThreadHandle; +static PPH_SYMBOL_PROVIDER DebugConsoleSymbolProvider; + +static PPH_HASHTABLE ObjectListSnapshot = NULL; +#ifdef DEBUG +static PPH_LIST NewObjectList = NULL; +static PH_QUEUED_LOCK NewObjectListLock; +#endif + +static BOOLEAN ShowAllLeaks = FALSE; +static BOOLEAN InLeakDetection = FALSE; +static ULONG NumberOfLeaks; +static ULONG NumberOfLeaksShown; + +VOID PhShowDebugConsole( + VOID + ) +{ + if (AllocConsole()) + { + HMENU menu; + + // Disable the close button because it's impossible to handle + // those events. + menu = GetSystemMenu(GetConsoleWindow(), FALSE); + EnableMenuItem(menu, SC_CLOSE, MF_GRAYED | MF_DISABLED); + DeleteMenu(menu, SC_CLOSE, 0); + + // Set a handler so we can catch Ctrl+C and Ctrl+Break. + SetConsoleCtrlHandler(ConsoleHandlerRoutine, TRUE); + + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + freopen("CONIN$", "r", stdin); + DebugConsoleThreadHandle = PhCreateThread(0, PhpDebugConsoleThreadStart, NULL); + } + else + { + HWND consoleWindow; + + consoleWindow = GetConsoleWindow(); + + // Console window already exists, so bring it to the top. + if (IsIconic(consoleWindow)) + ShowWindow(consoleWindow, SW_RESTORE); + else + BringWindowToTop(consoleWindow); + + return; + } +} + +VOID PhCloseDebugConsole( + VOID + ) +{ + freopen("NUL", "w", stdout); + freopen("NUL", "w", stderr); + freopen("NUL", "r", stdin); + + FreeConsole(); +} + +static BOOL ConsoleHandlerRoutine( + _In_ DWORD dwCtrlType + ) +{ + switch (dwCtrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + PhCloseDebugConsole(); + return TRUE; + } + + return FALSE; +} + +static BOOLEAN NTAPI PhpLoadCurrentProcessSymbolsCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PhLoadModuleSymbolProvider((PPH_SYMBOL_PROVIDER)Context, Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, Module->Size); + + return TRUE; +} + +static PWSTR PhpGetSymbolForAddress( + _In_ PVOID Address + ) +{ + return PH_AUTO_T(PH_STRING, PhGetSymbolFromAddress( + DebugConsoleSymbolProvider, (ULONG64)Address, NULL, NULL, NULL, NULL + ))->Buffer; +} + +static VOID PhpPrintObjectInfo( + _In_ PPH_OBJECT_HEADER ObjectHeader, + _In_ LONG RefToSubtract + ) +{ + PVOID object; + PPH_OBJECT_TYPE objectType; + WCHAR c = ' '; + + object = PhObjectHeaderToObject(ObjectHeader); + wprintf(L"%Ix", (ULONG_PTR)object); + objectType = PhGetObjectType(object); + + wprintf(L"\t% 20s", objectType->Name); + + if (ObjectHeader->Flags & PH_OBJECT_FROM_SMALL_FREE_LIST) + c = 'f'; + else if (ObjectHeader->Flags & PH_OBJECT_FROM_TYPE_FREE_LIST) + c = 'F'; + + wprintf(L"\t%4d %c", ObjectHeader->RefCount - RefToSubtract, c); + + if (!objectType) + { + // Dummy + } + else if (objectType == PhObjectTypeObject) + { + wprintf(L"\t%.32s", ((PPH_OBJECT_TYPE)object)->Name); + } + else if (objectType == PhStringType) + { + wprintf(L"\t%.32s", ((PPH_STRING)object)->Buffer); + } + else if (objectType == PhBytesType) + { + wprintf(L"\t%.32S", ((PPH_BYTES)object)->Buffer); + } + else if (objectType == PhListType) + { + wprintf(L"\tCount: %u", ((PPH_LIST)object)->Count); + } + else if (objectType == PhPointerListType) + { + wprintf(L"\tCount: %u", ((PPH_POINTER_LIST)object)->Count); + } + else if (objectType == PhHashtableType) + { + wprintf(L"\tCount: %u", ((PPH_HASHTABLE)object)->Count); + } + else if (objectType == PhProcessItemType) + { + wprintf( + L"\t%.28s (%d)", + ((PPH_PROCESS_ITEM)object)->ProcessName->Buffer, + HandleToLong(((PPH_PROCESS_ITEM)object)->ProcessId) + ); + } + else if (objectType == PhServiceItemType) + { + wprintf(L"\t%s", ((PPH_SERVICE_ITEM)object)->Name->Buffer); + } + else if (objectType == PhThreadItemType) + { + wprintf(L"\tTID: %u", HandleToUlong(((PPH_THREAD_ITEM)object)->ThreadId)); + } + + wprintf(L"\n"); +} + +static VOID PhpDumpObjectInfo( + _In_ PPH_OBJECT_HEADER ObjectHeader + ) +{ + PVOID object; + PPH_OBJECT_TYPE objectType; + + object = PhObjectHeaderToObject(ObjectHeader); + objectType = PhGetObjectType(object); + + __try + { + wprintf(L"Type: %s\n", objectType->Name); + wprintf(L"Reference count: %d\n", ObjectHeader->RefCount); + wprintf(L"Flags: %x\n", ObjectHeader->Flags); + + if (objectType == PhObjectTypeObject) + { + wprintf(L"Name: %s\n", ((PPH_OBJECT_TYPE)object)->Name); + wprintf(L"Number of objects: %u\n", ((PPH_OBJECT_TYPE)object)->NumberOfObjects); + wprintf(L"Flags: %u\n", ((PPH_OBJECT_TYPE)object)->Flags); + wprintf(L"Type index: %u\n", ((PPH_OBJECT_TYPE)object)->TypeIndex); + wprintf(L"Free list count: %u\n", ((PPH_OBJECT_TYPE)object)->FreeList.Count); + } + else if (objectType == PhStringType) + { + wprintf(L"%s\n", ((PPH_STRING)object)->Buffer); + } + else if (objectType == PhBytesType) + { + wprintf(L"%S\n", ((PPH_BYTES)object)->Buffer); + } + else if (objectType == PhHashtableType) + { + PhpPrintHashtableStatistics((PPH_HASHTABLE)object); + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + wprintf(L"Error.\n"); + } +} + +static VOID PhpPrintHashtableStatistics( + _In_ PPH_HASHTABLE Hashtable + ) +{ + ULONG i; + ULONG expectedLookupMisses = 0; + + wprintf(L"Count: %u\n", Hashtable->Count); + wprintf(L"Allocated buckets: %u\n", Hashtable->AllocatedBuckets); + wprintf(L"Allocated entries: %u\n", Hashtable->AllocatedEntries); + wprintf(L"Next free entry: %d\n", Hashtable->FreeEntry); + wprintf(L"Next usable entry: %d\n", Hashtable->NextEntry); + + wprintf(L"Equal function: %s\n", PhpGetSymbolForAddress(Hashtable->EqualFunction)); + wprintf(L"Hash function: %s\n", PhpGetSymbolForAddress(Hashtable->HashFunction)); + + wprintf(L"\nBuckets:\n"); + + for (i = 0; i < Hashtable->AllocatedBuckets; i++) + { + ULONG index; + ULONG count = 0; + + // Count the number of entries in this bucket. + + index = Hashtable->Buckets[i]; + + while (index != -1) + { + index = PH_HASHTABLE_GET_ENTRY(Hashtable, index)->Next; + count++; + } + + if (count != 0) + { + expectedLookupMisses += count - 1; + } + + if (count != 0) + { + wprintf(L"%lu: ", i); + + // Print out the entry indicies. + + index = Hashtable->Buckets[i]; + + while (index != -1) + { + wprintf(L"%lu", index); + + index = PH_HASHTABLE_GET_ENTRY(Hashtable, index)->Next; + count--; + + if (count != 0) + wprintf(L", "); + } + + wprintf(L"\n"); + } + else + { + //wprintf(L"%u: (empty)\n"); + } + } + + wprintf(L"\nExpected lookup misses: %lu\n", expectedLookupMisses); +} + +#ifdef DEBUG +static VOID PhpDebugCreateObjectHook( + _In_ PVOID Object, + _In_ SIZE_T Size, + _In_ ULONG Flags, + _In_ PPH_OBJECT_TYPE ObjectType + ) +{ + PhAcquireQueuedLockExclusive(&NewObjectListLock); + + if (NewObjectList) + { + PhReferenceObject(Object); + PhAddItemList(NewObjectList, Object); + } + + PhReleaseQueuedLockExclusive(&NewObjectListLock); +} +#endif + +#ifdef DEBUG +static VOID PhpDeleteNewObjectList( + VOID + ) +{ + if (NewObjectList) + { + ULONG i; + + for (i = 0; i < NewObjectList->Count; i++) + PhDereferenceObject(NewObjectList->Items[i]); + + PhDereferenceObject(NewObjectList); + NewObjectList = NULL; + } +} +#endif + +static BOOLEAN PhpStringHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PSTRING_TABLE_ENTRY entry1 = Entry1; + PSTRING_TABLE_ENTRY entry2 = Entry2; + + return PhEqualString(entry1->String, entry2->String, FALSE); +} + +static ULONG PhpStringHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PSTRING_TABLE_ENTRY entry = Entry; + + return PhHashBytes((PUCHAR)entry->String->Buffer, entry->String->Length); +} + +static int __cdecl PhpStringEntryCompareByCount( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PSTRING_TABLE_ENTRY entry1 = *(PSTRING_TABLE_ENTRY *)elem1; + PSTRING_TABLE_ENTRY entry2 = *(PSTRING_TABLE_ENTRY *)elem2; + + return uintptrcmp(entry2->Count, entry1->Count); +} + +static NTSTATUS PhpLeakEnumerationRoutine( + _In_ LONG Reserved, + _In_ PVOID HeapHandle, + _In_ PVOID BaseAddress, + _In_ SIZE_T BlockSize, + _In_ ULONG StackTraceDepth, + _In_ PVOID *StackTrace + ) +{ + ULONG i; + + if (!InLeakDetection) + return 0; + + if (!HeapHandle) // means no more entries + return 0; + + if (ShowAllLeaks || HeapHandle == PhHeapHandle) + { + wprintf(L"Leak at 0x%Ix (%Iu bytes). Stack trace:\n", (ULONG_PTR)BaseAddress, BlockSize); + + for (i = 0; i < StackTraceDepth; i++) + { + PPH_STRING symbol; + + symbol = PhGetSymbolFromAddress(DebugConsoleSymbolProvider, (ULONG64)StackTrace[i], NULL, NULL, NULL, NULL); + + if (symbol) + wprintf(L"\t%s\n", symbol->Buffer); + else + wprintf(L"\t?\n"); + + PhDereferenceObject(symbol); + } + + NumberOfLeaksShown++; + } + + NumberOfLeaks++; + + return 0; +} + +typedef struct _STOPWATCH +{ + LARGE_INTEGER StartCounter; + LARGE_INTEGER EndCounter; + LARGE_INTEGER Frequency; +} STOPWATCH, *PSTOPWATCH; + +static VOID PhInitializeStopwatch( + _Out_ PSTOPWATCH Stopwatch + ) +{ + Stopwatch->StartCounter.QuadPart = 0; + Stopwatch->EndCounter.QuadPart = 0; +} + +static VOID PhStartStopwatch( + _Inout_ PSTOPWATCH Stopwatch + ) +{ + NtQueryPerformanceCounter(&Stopwatch->StartCounter, &Stopwatch->Frequency); +} + +static VOID PhStopStopwatch( + _Inout_ PSTOPWATCH Stopwatch + ) +{ + NtQueryPerformanceCounter(&Stopwatch->EndCounter, NULL); +} + +static ULONG PhGetMillisecondsStopwatch( + _In_ PSTOPWATCH Stopwatch + ) +{ + LARGE_INTEGER countsPerMs; + + countsPerMs = Stopwatch->Frequency; + countsPerMs.QuadPart /= 1000; + + return (ULONG)((Stopwatch->EndCounter.QuadPart - Stopwatch->StartCounter.QuadPart) / + countsPerMs.QuadPart); +} + +typedef VOID (FASTCALL *PPHF_RW_LOCK_FUNCTION)( + _In_ PVOID Parameter + ); + +typedef struct _RW_TEST_CONTEXT +{ + PWSTR Name; + + PPHF_RW_LOCK_FUNCTION AcquireExclusive; + PPHF_RW_LOCK_FUNCTION AcquireShared; + PPHF_RW_LOCK_FUNCTION ReleaseExclusive; + PPHF_RW_LOCK_FUNCTION ReleaseShared; + + PVOID Parameter; +} RW_TEST_CONTEXT, *PRW_TEST_CONTEXT; + +static PH_BARRIER RwStartBarrier; +static LONG RwReadersActive; +static LONG RwWritersActive; + +static NTSTATUS PhpRwLockTestThreadStart( + _In_ PVOID Parameter + ) +{ +#define RW_ITERS 10000 +#define RW_READ_ITERS 100 +#define RW_WRITE_ITERS 10 +#define RW_READ_SPIN_ITERS 60 +#define RW_WRITE_SPIN_ITERS 200 + + RW_TEST_CONTEXT context = *(PRW_TEST_CONTEXT)Parameter; + ULONG i; + ULONG j; + ULONG k; + ULONG m; + + PhWaitForBarrier(&RwStartBarrier, FALSE); + + for (i = 0; i < RW_ITERS; i++) + { + for (j = 0; j < RW_READ_ITERS; j++) + { + // Read zone + + context.AcquireShared(context.Parameter); + _InterlockedIncrement(&RwReadersActive); + + for (m = 0; m < RW_READ_SPIN_ITERS; m++) + YieldProcessor(); + + if (RwWritersActive != 0) + { + wprintf(L"[fail]: writers active in read zone!\n"); + NtWaitForSingleObject(NtCurrentProcess(), FALSE, NULL); + } + + _InterlockedDecrement(&RwReadersActive); + context.ReleaseShared(context.Parameter); + + // Spin for a while + + for (m = 0; m < 10; m++) + YieldProcessor(); + + if (j == RW_READ_ITERS / 2) + { + // Write zone + + for (k = 0; k < RW_WRITE_ITERS; k++) + { + context.AcquireExclusive(context.Parameter); + _InterlockedIncrement(&RwWritersActive); + + for (m = 0; m < RW_WRITE_SPIN_ITERS; m++) + YieldProcessor(); + + if (RwReadersActive != 0) + { + wprintf(L"[fail]: readers active in write zone!\n"); + NtWaitForSingleObject(NtCurrentProcess(), FALSE, NULL); + } + + _InterlockedDecrement(&RwWritersActive); + context.ReleaseExclusive(context.Parameter); + } + } + } + } + + return STATUS_SUCCESS; +} + +static VOID PhpTestRwLock( + _In_ PRW_TEST_CONTEXT Context + ) +{ +#define RW_PROCESSORS 4 + + STOPWATCH stopwatch; + ULONG i; + HANDLE threadHandles[RW_PROCESSORS]; + + // Dummy + + Context->AcquireExclusive(Context->Parameter); + Context->ReleaseExclusive(Context->Parameter); + Context->AcquireShared(Context->Parameter); + Context->ReleaseShared(Context->Parameter); + + // Null test + + PhStartStopwatch(&stopwatch); + + for (i = 0; i < 2000000; i++) + { + Context->AcquireExclusive(Context->Parameter); + Context->ReleaseExclusive(Context->Parameter); + Context->AcquireShared(Context->Parameter); + Context->ReleaseShared(Context->Parameter); + } + + PhStopStopwatch(&stopwatch); + + wprintf(L"[null] %s: %ums\n", Context->Name, PhGetMillisecondsStopwatch(&stopwatch)); + + // Stress test + + PhInitializeBarrier(&RwStartBarrier, RW_PROCESSORS + 1); + RwReadersActive = 0; + RwWritersActive = 0; + + for (i = 0; i < RW_PROCESSORS; i++) + { + threadHandles[i] = PhCreateThread(0, PhpRwLockTestThreadStart, Context); + } + + PhWaitForBarrier(&RwStartBarrier, FALSE); + PhStartStopwatch(&stopwatch); + NtWaitForMultipleObjects(RW_PROCESSORS, threadHandles, WaitAll, FALSE, NULL); + PhStopStopwatch(&stopwatch); + + for (i = 0; i < RW_PROCESSORS; i++) + NtClose(threadHandles[i]); + + wprintf(L"[strs] %s: %ums\n", Context->Name, PhGetMillisecondsStopwatch(&stopwatch)); +} + +VOID FASTCALL PhfAcquireCriticalSection( + _In_ PRTL_CRITICAL_SECTION CriticalSection + ) +{ + RtlEnterCriticalSection(CriticalSection); +} + +VOID FASTCALL PhfReleaseCriticalSection( + _In_ PRTL_CRITICAL_SECTION CriticalSection + ) +{ + RtlLeaveCriticalSection(CriticalSection); +} + +VOID FASTCALL PhfReleaseQueuedLockExclusiveUsingInline( + _In_ PPH_QUEUED_LOCK QueuedLock + ) +{ + PhReleaseQueuedLockExclusive(QueuedLock); +} + +NTSTATUS PhpDebugConsoleThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + BOOLEAN exit = FALSE; + + PhInitializeAutoPool(&autoPool); + + DebugConsoleSymbolProvider = PhCreateSymbolProvider(NtCurrentProcessId()); + + { + WCHAR buffer[512]; + UNICODE_STRING name = RTL_CONSTANT_STRING(L"_NT_SYMBOL_PATH"); + UNICODE_STRING var; + PPH_STRING newSearchPath; + + var.Buffer = buffer; + var.MaximumLength = sizeof(buffer); + + if (!NT_SUCCESS(RtlQueryEnvironmentVariable_U(NULL, &name, &var))) + buffer[0] = 0; + + newSearchPath = PhFormatString(L"%s;%s", buffer, PhApplicationDirectory->Buffer); + PhSetSearchPathSymbolProvider(DebugConsoleSymbolProvider, newSearchPath->Buffer); + PhDereferenceObject(newSearchPath); + } + + PhEnumGenericModules(NtCurrentProcessId(), NtCurrentProcess(), + 0, PhpLoadCurrentProcessSymbolsCallback, DebugConsoleSymbolProvider); + +#ifdef DEBUG + PhInitializeQueuedLock(&NewObjectListLock); + PhDbgCreateObjectHook = PhpDebugCreateObjectHook; +#endif + + wprintf(L"Press Ctrl+C or type \"exit\" to close the debug console. Type \"help\" for a list of commands.\n"); + + while (!exit) + { + static PWSTR delims = L" \t"; + static PWSTR commandDebugOnly = L"This command is not available on non-debug builds.\n"; + + WCHAR line[201]; + ULONG inputLength; + PWSTR context; + PWSTR command; + + wprintf(L"dbg>"); + + if (!fgetws(line, sizeof(line) / 2 - 1, stdin)) + break; + + // Remove the terminating new line character. + + inputLength = (ULONG)PhCountStringZ(line); + + if (inputLength != 0) + line[inputLength - 1] = 0; + + context = NULL; + command = wcstok_s(line, delims, &context); + + if (!command) + { + continue; + } + else if (PhEqualStringZ(command, L"help", TRUE)) + { + wprintf( + L"Commands:\n" + L"exit\n" + L"testperf\n" + L"testlocks\n" + L"stats\n" + L"objects [type-name-filter]\n" + L"objtrace object-address\n" + L"objmksnap\n" + L"objcmpsnap\n" + L"objmknew\n" + L"objdelnew\n" + L"objviewnew\n" + L"dumpobj\n" + L"dumpautopool\n" + L"threads\n" + L"provthreads\n" + L"workqueues\n" + L"procrecords\n" + L"procitem\n" + L"uniquestr\n" + L"enableleakdetect\n" + L"leakdetect\n" + L"mem\n" + ); + } + else if (PhEqualStringZ(command, L"exit", TRUE)) + { + PhCloseDebugConsole(); + exit = TRUE; + } + else if (PhEqualStringZ(command, L"testperf", TRUE)) + { + STOPWATCH stopwatch; + ULONG i; + PPH_STRING testString; + RTL_CRITICAL_SECTION testCriticalSection; + PH_FAST_LOCK testFastLock; + PH_QUEUED_LOCK testQueuedLock; + + // Control (string reference counting) + + testString = PhCreateString(L""); + PhReferenceObject(testString); + PhDereferenceObject(testString); + PhStartStopwatch(&stopwatch); + + for (i = 0; i < 10000000; i++) + { + PhReferenceObject(testString); + PhDereferenceObject(testString); + } + + PhStopStopwatch(&stopwatch); + PhDereferenceObject(testString); + + wprintf(L"Referencing: %ums\n", PhGetMillisecondsStopwatch(&stopwatch)); + + // Critical section + + RtlInitializeCriticalSection(&testCriticalSection); + RtlEnterCriticalSection(&testCriticalSection); + RtlLeaveCriticalSection(&testCriticalSection); + PhStartStopwatch(&stopwatch); + + for (i = 0; i < 10000000; i++) + { + RtlEnterCriticalSection(&testCriticalSection); + RtlLeaveCriticalSection(&testCriticalSection); + } + + PhStopStopwatch(&stopwatch); + RtlDeleteCriticalSection(&testCriticalSection); + + wprintf(L"Critical section: %ums\n", PhGetMillisecondsStopwatch(&stopwatch)); + + // Fast lock + + PhInitializeFastLock(&testFastLock); + PhAcquireFastLockExclusive(&testFastLock); + PhReleaseFastLockExclusive(&testFastLock); + PhStartStopwatch(&stopwatch); + + for (i = 0; i < 10000000; i++) + { + PhAcquireFastLockExclusive(&testFastLock); + PhReleaseFastLockExclusive(&testFastLock); + } + + PhStopStopwatch(&stopwatch); + PhDeleteFastLock(&testFastLock); + + wprintf(L"Fast lock: %ums\n", PhGetMillisecondsStopwatch(&stopwatch)); + + // Queued lock + + PhInitializeQueuedLock(&testQueuedLock); + PhAcquireQueuedLockExclusive(&testQueuedLock); + PhReleaseQueuedLockExclusive(&testQueuedLock); + PhStartStopwatch(&stopwatch); + + for (i = 0; i < 10000000; i++) + { + PhAcquireQueuedLockExclusive(&testQueuedLock); + PhReleaseQueuedLockExclusive(&testQueuedLock); + } + + PhStopStopwatch(&stopwatch); + + wprintf(L"Queued lock: %ums\n", PhGetMillisecondsStopwatch(&stopwatch)); + } + else if (PhEqualStringZ(command, L"testlocks", TRUE)) + { + RW_TEST_CONTEXT testContext; + PH_FAST_LOCK fastLock; + PH_QUEUED_LOCK queuedLock; + RTL_CRITICAL_SECTION criticalSection; + + testContext.Name = L"FastLock"; + testContext.AcquireExclusive = PhfAcquireFastLockExclusive; + testContext.AcquireShared = PhfAcquireFastLockShared; + testContext.ReleaseExclusive = PhfReleaseFastLockExclusive; + testContext.ReleaseShared = PhfReleaseFastLockShared; + testContext.Parameter = &fastLock; + PhInitializeFastLock(&fastLock); + PhpTestRwLock(&testContext); + PhDeleteFastLock(&fastLock); + + testContext.Name = L"QueuedLock"; + testContext.AcquireExclusive = PhfAcquireQueuedLockExclusive; + testContext.AcquireShared = PhfAcquireQueuedLockShared; + testContext.ReleaseExclusive = PhfReleaseQueuedLockExclusive; + testContext.ReleaseShared = PhfReleaseQueuedLockShared; + testContext.Parameter = &queuedLock; + PhInitializeQueuedLock(&queuedLock); + PhpTestRwLock(&testContext); + + testContext.Name = L"CriticalSection"; + testContext.AcquireExclusive = PhfAcquireCriticalSection; + testContext.AcquireShared = PhfAcquireCriticalSection; + testContext.ReleaseExclusive = PhfReleaseCriticalSection; + testContext.ReleaseShared = PhfReleaseCriticalSection; + testContext.Parameter = &criticalSection; + RtlInitializeCriticalSection(&criticalSection); + PhpTestRwLock(&testContext); + RtlDeleteCriticalSection(&criticalSection); + + testContext.Name = L"QueuedLockMutex"; + testContext.AcquireExclusive = PhfAcquireQueuedLockExclusive; + testContext.AcquireShared = PhfAcquireQueuedLockExclusive; + testContext.ReleaseExclusive = PhfReleaseQueuedLockExclusive; + testContext.ReleaseShared = PhfReleaseQueuedLockExclusive; + testContext.Parameter = &queuedLock; + PhInitializeQueuedLock(&queuedLock); + PhpTestRwLock(&testContext); + } + else if (PhEqualStringZ(command, L"stats", TRUE)) + { +#ifdef DEBUG + wprintf(L"Object small free list count: %u\n", PhObjectSmallFreeList.Count); + wprintf(L"Statistics:\n"); +#define PRINT_STATISTIC(Name) wprintf(L#Name L": %u\n", PhLibStatisticsBlock.Name); + + PRINT_STATISTIC(BaseThreadsCreated); + PRINT_STATISTIC(BaseThreadsCreateFailed); + PRINT_STATISTIC(BaseStringBuildersCreated); + PRINT_STATISTIC(BaseStringBuildersResized); + PRINT_STATISTIC(RefObjectsCreated); + PRINT_STATISTIC(RefObjectsDestroyed); + PRINT_STATISTIC(RefObjectsAllocated); + PRINT_STATISTIC(RefObjectsFreed); + PRINT_STATISTIC(RefObjectsAllocatedFromSmallFreeList); + PRINT_STATISTIC(RefObjectsFreedToSmallFreeList); + PRINT_STATISTIC(RefObjectsAllocatedFromTypeFreeList); + PRINT_STATISTIC(RefObjectsFreedToTypeFreeList); + PRINT_STATISTIC(RefObjectsDeleteDeferred); + PRINT_STATISTIC(RefAutoPoolsCreated); + PRINT_STATISTIC(RefAutoPoolsDestroyed); + PRINT_STATISTIC(RefAutoPoolsDynamicAllocated); + PRINT_STATISTIC(RefAutoPoolsDynamicResized); + PRINT_STATISTIC(QlBlockSpins); + PRINT_STATISTIC(QlBlockWaits); + PRINT_STATISTIC(QlAcquireExclusiveBlocks); + PRINT_STATISTIC(QlAcquireSharedBlocks); + PRINT_STATISTIC(WqWorkQueueThreadsCreated); + PRINT_STATISTIC(WqWorkQueueThreadsCreateFailed); + PRINT_STATISTIC(WqWorkItemsQueued); + +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objects", TRUE)) + { +#ifdef DEBUG + PWSTR typeFilter = wcstok_s(NULL, delims, &context); + PLIST_ENTRY currentEntry; + ULONG totalNumberOfObjects = 0; + //SIZE_T totalNumberOfBytes = 0; + + if (typeFilter) + _wcslwr(typeFilter); + + PhAcquireQueuedLockShared(&PhDbgObjectListLock); + + currentEntry = PhDbgObjectListHead.Flink; + + while (currentEntry != &PhDbgObjectListHead) + { + PPH_OBJECT_HEADER objectHeader; + WCHAR typeName[32]; + + objectHeader = CONTAINING_RECORD(currentEntry, PH_OBJECT_HEADER, ObjectListEntry); + + // Make sure the object isn't being destroyed. + if (!PhReferenceObjectSafe(PhObjectHeaderToObject(objectHeader))) + { + currentEntry = currentEntry->Flink; + continue; + } + + totalNumberOfObjects++; + //totalNumberOfBytes += objectHeader->Size; + + if (typeFilter) + { + wcscpy_s(typeName, sizeof(typeName) / 2, PhGetObjectType(PhObjectHeaderToObject(objectHeader))->Name); + _wcslwr(typeName); + } + + if ( + !typeFilter || + (typeFilter && wcsstr(typeName, typeFilter)) + ) + { + PhpPrintObjectInfo(objectHeader, 1); + } + + currentEntry = currentEntry->Flink; + PhDereferenceObjectDeferDelete(PhObjectHeaderToObject(objectHeader)); + } + + PhReleaseQueuedLockShared(&PhDbgObjectListLock); + + wprintf(L"\n"); + wprintf(L"Total number: %lu\n", totalNumberOfObjects); + /*wprintf(L"Total size (excl. header): %s\n", + ((PPH_STRING)PH_AUTO(PhFormatSize(totalNumberOfBytes, 1)))->Buffer);*/ + wprintf(L"Total overhead (header): %s\n", + ((PPH_STRING)PH_AUTO( + PhFormatSize(PhAddObjectHeaderSize(0) * totalNumberOfObjects, 1) + ))->Buffer); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objtrace", TRUE)) + { +#ifdef DEBUG + PWSTR objectAddress = wcstok_s(NULL, delims, &context); + PH_STRINGREF objectAddressString; + ULONG64 address; + + if (!objectAddress) + { + wprintf(L"Missing object address.\n"); + goto EndCommand; + } + + PhInitializeStringRef(&objectAddressString, objectAddress); + + if (PhStringToInteger64(&objectAddressString, 16, &address)) + { + PPH_OBJECT_HEADER objectHeader = (PPH_OBJECT_HEADER)PhObjectToObjectHeader((PVOID)address); + PVOID stackBackTrace[16]; + ULONG i; + + // The address may not be valid. + __try + { + memcpy(stackBackTrace, objectHeader->StackBackTrace, 16 * sizeof(PVOID)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + PPH_STRING message; + + message = PH_AUTO(PhGetNtMessage(GetExceptionCode())); + wprintf(L"Error: %s\n", PhGetString(message)); + + goto EndCommand; + } + + for (i = 0; i < 16; i++) + { + if (!stackBackTrace[i]) + break; + + wprintf(L"%s\n", PhpGetSymbolForAddress(stackBackTrace[i])); + } + } + else + { + wprintf(L"Invalid object address.\n"); + } +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objmksnap", TRUE)) + { +#ifdef DEBUG + PLIST_ENTRY currentEntry; + + if (ObjectListSnapshot) + { + PhDereferenceObject(ObjectListSnapshot); + ObjectListSnapshot = NULL; + } + + ObjectListSnapshot = PhCreateSimpleHashtable(100); + + PhAcquireQueuedLockShared(&PhDbgObjectListLock); + + currentEntry = PhDbgObjectListHead.Flink; + + while (currentEntry != &PhDbgObjectListHead) + { + PPH_OBJECT_HEADER objectHeader; + + objectHeader = CONTAINING_RECORD(currentEntry, PH_OBJECT_HEADER, ObjectListEntry); + currentEntry = currentEntry->Flink; + + if (PhObjectHeaderToObject(objectHeader) != ObjectListSnapshot) + PhAddItemSimpleHashtable(ObjectListSnapshot, objectHeader, NULL); + } + + PhReleaseQueuedLockShared(&PhDbgObjectListLock); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objcmpsnap", TRUE)) + { +#ifdef DEBUG + PLIST_ENTRY currentEntry; + PPH_LIST newObjects; + ULONG i; + + if (!ObjectListSnapshot) + { + wprintf(L"No snapshot.\n"); + goto EndCommand; + } + + newObjects = PhCreateList(10); + + PhAcquireQueuedLockShared(&PhDbgObjectListLock); + + currentEntry = PhDbgObjectListHead.Flink; + + while (currentEntry != &PhDbgObjectListHead) + { + PPH_OBJECT_HEADER objectHeader; + + objectHeader = CONTAINING_RECORD(currentEntry, PH_OBJECT_HEADER, ObjectListEntry); + currentEntry = currentEntry->Flink; + + if ( + PhObjectHeaderToObject(objectHeader) != ObjectListSnapshot && + PhObjectHeaderToObject(objectHeader) != newObjects + ) + { + if (!PhFindItemSimpleHashtable(ObjectListSnapshot, objectHeader)) + { + if (PhReferenceObjectSafe(PhObjectHeaderToObject(objectHeader))) + PhAddItemList(newObjects, objectHeader); + } + } + } + + PhReleaseQueuedLockShared(&PhDbgObjectListLock); + + for (i = 0; i < newObjects->Count; i++) + { + PPH_OBJECT_HEADER objectHeader = newObjects->Items[i]; + + PhpPrintObjectInfo(objectHeader, 1); + + PhDereferenceObject(PhObjectHeaderToObject(objectHeader)); + } + + PhDereferenceObject(newObjects); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objmknew", TRUE)) + { +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&NewObjectListLock); + PhpDeleteNewObjectList(); + PhReleaseQueuedLockExclusive(&NewObjectListLock); + + // Creation needs to be done outside of the lock, + // otherwise a deadlock will occur. + NewObjectList = PhCreateList(100); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objdelnew", TRUE)) + { +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&NewObjectListLock); + PhpDeleteNewObjectList(); + PhReleaseQueuedLockExclusive(&NewObjectListLock); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"objviewnew", TRUE)) + { +#ifdef DEBUG + ULONG i; + + PhAcquireQueuedLockExclusive(&NewObjectListLock); + + if (!NewObjectList) + { + wprintf(L"Object creation hooking not active.\n"); + PhReleaseQueuedLockExclusive(&NewObjectListLock); + goto EndCommand; + } + + for (i = 0; i < NewObjectList->Count; i++) + { + PhpPrintObjectInfo(PhObjectToObjectHeader(NewObjectList->Items[i]), 1); + } + + PhReleaseQueuedLockExclusive(&NewObjectListLock); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"dumpobj", TRUE)) + { + PWSTR addressString = wcstok_s(NULL, delims, &context); + PH_STRINGREF addressStringRef; + ULONG64 address; + + if (!addressString) + goto EndCommand; + + PhInitializeStringRef(&addressStringRef, addressString); + + if (PhStringToInteger64(&addressStringRef, 16, &address)) + { + PhpDumpObjectInfo((PPH_OBJECT_HEADER)PhObjectToObjectHeader((PVOID)address)); + } + } + else if (PhEqualStringZ(command, L"dumpautopool", TRUE)) + { + PWSTR addressString = wcstok_s(NULL, delims, &context); + PH_STRINGREF addressStringRef; + ULONG64 address; + + if (!addressString) + goto EndCommand; + + PhInitializeStringRef(&addressStringRef, addressString); + + if (PhStringToInteger64(&addressStringRef, 16, &address)) + { + PPH_AUTO_POOL userAutoPool = (PPH_AUTO_POOL)address; + ULONG i; + + __try + { + wprintf(L"Static count: %u\n", userAutoPool->StaticCount); + wprintf(L"Dynamic count: %u\n", userAutoPool->DynamicCount); + wprintf(L"Dynamic allocated: %u\n", userAutoPool->DynamicAllocated); + + wprintf(L"Static objects:\n"); + + for (i = 0; i < userAutoPool->StaticCount; i++) + PhpPrintObjectInfo(PhObjectToObjectHeader(userAutoPool->StaticObjects[i]), 0); + + wprintf(L"Dynamic objects:\n"); + + for (i = 0; i < userAutoPool->DynamicCount; i++) + PhpPrintObjectInfo(PhObjectToObjectHeader(userAutoPool->DynamicObjects[i]), 0); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + goto EndCommand; + } + } + } + else if (PhEqualStringZ(command, L"threads", TRUE)) + { +#ifdef DEBUG + PLIST_ENTRY currentEntry; + + PhAcquireQueuedLockShared(&PhDbgThreadListLock); + + currentEntry = PhDbgThreadListHead.Flink; + + while (currentEntry != &PhDbgThreadListHead) + { + PPHP_BASE_THREAD_DBG dbg; + + dbg = CONTAINING_RECORD(currentEntry, PHP_BASE_THREAD_DBG, ListEntry); + + wprintf(L"Thread %u\n", HandleToUlong(dbg->ClientId.UniqueThread)); + wprintf(L"\tStart Address: %s\n", PhpGetSymbolForAddress(dbg->StartAddress)); + wprintf(L"\tParameter: %Ix\n", (ULONG_PTR)dbg->Parameter); + wprintf(L"\tCurrent auto-pool: %Ix\n", (ULONG_PTR)dbg->CurrentAutoPool); + + currentEntry = currentEntry->Flink; + } + + PhReleaseQueuedLockShared(&PhDbgThreadListLock); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"provthreads", TRUE)) + { +#ifdef DEBUG + ULONG i; + + if (PhDbgProviderList) + { + PhAcquireQueuedLockShared(&PhDbgProviderListLock); + + for (i = 0; i < PhDbgProviderList->Count; i++) + { + PPH_PROVIDER_THREAD providerThread = PhDbgProviderList->Items[i]; + THREAD_BASIC_INFORMATION basicInfo; + PLIST_ENTRY providerEntry; + + if (providerThread->ThreadHandle) + { + PhGetThreadBasicInformation(providerThread->ThreadHandle, &basicInfo); + wprintf(L"Thread %u\n", HandleToUlong(basicInfo.ClientId.UniqueThread)); + } + else + { + wprintf(L"Thread not running\n"); + } + + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + providerEntry = providerThread->ListHead.Flink; + + while (providerEntry != &providerThread->ListHead) + { + PPH_PROVIDER_REGISTRATION registration; + + registration = CONTAINING_RECORD(providerEntry, PH_PROVIDER_REGISTRATION, ListEntry); + + wprintf(L"\tProvider registration at %Ix\n", (ULONG_PTR)registration); + wprintf(L"\t\tEnabled: %s\n", registration->Enabled ? L"Yes" : L"No"); + wprintf(L"\t\tFunction: %s\n", PhpGetSymbolForAddress(registration->Function)); + + if (registration->Object) + { + wprintf(L"\t\tObject:\n"); + PhpPrintObjectInfo(PhObjectToObjectHeader(registration->Object), 0); + } + + providerEntry = providerEntry->Flink; + } + + PhReleaseQueuedLockExclusive(&providerThread->Lock); + + wprintf(L"\n"); + } + + PhReleaseQueuedLockShared(&PhDbgProviderListLock); + } +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"workqueues", TRUE)) + { +#ifdef DEBUG + ULONG i; + + if (PhDbgWorkQueueList) + { + PhAcquireQueuedLockShared(&PhDbgWorkQueueListLock); + + for (i = 0; i < PhDbgWorkQueueList->Count; i++) + { + PPH_WORK_QUEUE workQueue = PhDbgWorkQueueList->Items[i]; + PLIST_ENTRY workQueueItemEntry; + + wprintf(L"Work queue at %s\n", PhpGetSymbolForAddress(workQueue)); + wprintf(L"Maximum threads: %u\n", workQueue->MaximumThreads); + wprintf(L"Minimum threads: %u\n", workQueue->MinimumThreads); + wprintf(L"No work timeout: %d\n", workQueue->NoWorkTimeout); + + wprintf(L"Current threads: %u\n", workQueue->CurrentThreads); + wprintf(L"Busy count: %u\n", workQueue->BusyCount); + + PhAcquireQueuedLockExclusive(&workQueue->QueueLock); + + // List the items backwards. + workQueueItemEntry = workQueue->QueueListHead.Blink; + + while (workQueueItemEntry != &workQueue->QueueListHead) + { + PPH_WORK_QUEUE_ITEM workQueueItem; + + workQueueItem = CONTAINING_RECORD(workQueueItemEntry, PH_WORK_QUEUE_ITEM, ListEntry); + + wprintf(L"\tWork queue item at %Ix\n", (ULONG_PTR)workQueueItem); + wprintf(L"\t\tFunction: %s\n", PhpGetSymbolForAddress(workQueueItem->Function)); + wprintf(L"\t\tContext: %Ix\n", (ULONG_PTR)workQueueItem->Context); + + workQueueItemEntry = workQueueItemEntry->Blink; + } + + PhReleaseQueuedLockExclusive(&workQueue->QueueLock); + + wprintf(L"\n"); + } + + PhReleaseQueuedLockShared(&PhDbgWorkQueueListLock); + } +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"procrecords", TRUE)) + { + PPH_PROCESS_RECORD record; + ULONG i; + SYSTEMTIME systemTime; + PPH_PROCESS_RECORD startRecord; + + PhAcquireQueuedLockShared(&PhProcessRecordListLock); + + for (i = 0; i < PhProcessRecordList->Count; i++) + { + record = (PPH_PROCESS_RECORD)PhProcessRecordList->Items[i]; + + PhLargeIntegerToLocalSystemTime(&systemTime, &record->CreateTime); + wprintf(L"Records for %s %s:\n", + ((PPH_STRING)PH_AUTO(PhFormatDate(&systemTime, NULL)))->Buffer, + ((PPH_STRING)PH_AUTO(PhFormatTime(&systemTime, NULL)))->Buffer + ); + + startRecord = record; + + do + { + wprintf(L"\tRecord at %Ix: %s (%u) (refs: %d)\n", (ULONG_PTR)record, record->ProcessName->Buffer, HandleToUlong(record->ProcessId), record->RefCount); + + if (record->FileName) + wprintf(L"\t\t%s\n", record->FileName->Buffer); + + record = CONTAINING_RECORD(record->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry); + } while (record != startRecord); + } + + PhReleaseQueuedLockShared(&PhProcessRecordListLock); + } + else if (PhEqualStringZ(command, L"procitem", TRUE)) + { + PWSTR filterString; + PH_STRINGREF filterRef; + ULONG64 filter64; + LONG_PTR processIdFilter = -9; // can't use -2, -1 or 0 because they're all used for process IDs + ULONG_PTR processAddressFilter = 0; + PWSTR imageNameFilter = NULL; + BOOLEAN showAll = FALSE; + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + ULONG i; + + filterString = wcstok_s(NULL, delims, &context); + + if (filterString) + { + PhInitializeStringRef(&filterRef, filterString); + + if (PhStringToInteger64(&filterRef, 10, &filter64)) + processIdFilter = (LONG_PTR)filter64; + if (PhStringToInteger64(&filterRef, 16, &filter64)) + processAddressFilter = (ULONG_PTR)filter64; + + imageNameFilter = filterString; + } + else + { + showAll = TRUE; + } + + PhEnumProcessItems(&processes, &numberOfProcesses); + + for (i = 0; i < numberOfProcesses; i++) + { + PPH_PROCESS_ITEM process = processes[i]; + + if ( + showAll || + (processIdFilter != -9 && (LONG_PTR)process->ProcessId == processIdFilter) || + (processAddressFilter != 0 && (ULONG_PTR)process == processAddressFilter) || + (imageNameFilter && PhMatchWildcards(imageNameFilter, process->ProcessName->Buffer, TRUE)) + ) + { + wprintf(L"Process item at %Ix: %s (%u)\n", (ULONG_PTR)process, process->ProcessName->Buffer, HandleToUlong(process->ProcessId)); + wprintf(L"\tRecord at %Ix\n", (ULONG_PTR)process->Record); + wprintf(L"\tQuery handle %Ix\n", (ULONG_PTR)process->QueryHandle); + wprintf(L"\tFile name at %Ix: %s\n", (ULONG_PTR)process->FileName, PhGetStringOrDefault(process->FileName, L"(null)")); + wprintf(L"\tCommand line at %Ix: %s\n", (ULONG_PTR)process->CommandLine, PhGetStringOrDefault(process->CommandLine, L"(null)")); + wprintf(L"\tFlags: %u\n", process->Flags); + wprintf(L"\n"); + } + } + + PhDereferenceObjects(processes, numberOfProcesses); + } + else if (PhEqualStringZ(command, L"uniquestr", TRUE)) + { +#ifdef DEBUG + PLIST_ENTRY currentEntry; + PPH_HASHTABLE hashtable; + PPH_LIST list; + PSTRING_TABLE_ENTRY stringEntry; + ULONG enumerationKey; + ULONG i; + + hashtable = PhCreateHashtable( + sizeof(STRING_TABLE_ENTRY), + PhpStringHashtableEqualFunction, + PhpStringHashtableHashFunction, + 1024 + ); + + PhAcquireQueuedLockShared(&PhDbgObjectListLock); + + currentEntry = PhDbgObjectListHead.Flink; + + while (currentEntry != &PhDbgObjectListHead) + { + PPH_OBJECT_HEADER objectHeader; + PPH_STRING string; + STRING_TABLE_ENTRY localStringEntry; + BOOLEAN added; + + objectHeader = CONTAINING_RECORD(currentEntry, PH_OBJECT_HEADER, ObjectListEntry); + currentEntry = currentEntry->Flink; + string = PhObjectHeaderToObject(objectHeader); + + // Make sure this is a string. + if (PhGetObjectType(string) != PhStringType) + continue; + + // Make sure the object isn't being destroyed. + if (!PhReferenceObjectSafe(string)) + continue; + + localStringEntry.String = string; + stringEntry = PhAddEntryHashtableEx(hashtable, &localStringEntry, &added); + + if (added) + { + stringEntry->Count = 1; + PhReferenceObject(string); + } + else + { + stringEntry->Count++; + } + + PhDereferenceObjectDeferDelete(string); + } + + PhReleaseQueuedLockShared(&PhDbgObjectListLock); + + // Sort the string entries by count. + + list = PhCreateList(hashtable->Count); + + enumerationKey = 0; + + while (PhEnumHashtable(hashtable, &stringEntry, &enumerationKey)) + { + PhAddItemList(list, stringEntry); + } + + qsort(list->Items, list->Count, sizeof(PSTRING_TABLE_ENTRY), PhpStringEntryCompareByCount); + + // Display... + + for (i = 0; i < 40 && i < list->Count; i++) + { + stringEntry = list->Items[i]; + wprintf(L"%Iu\t%.64s\n", stringEntry->Count, stringEntry->String->Buffer); + } + + wprintf(L"\nTotal unique strings: %u\n", list->Count); + + // Cleanup + + for (i = 0; i < list->Count; i++) + { + stringEntry = list->Items[i]; + PhDereferenceObject(stringEntry->String); + } + + PhDereferenceObject(list); + PhDereferenceObject(hashtable); +#else + wprintf(commandDebugOnly); +#endif + } + else if (PhEqualStringZ(command, L"enableleakdetect", TRUE)) + { + HEAP_DEBUGGING_INFORMATION debuggingInfo; + + memset(&debuggingInfo, 0, sizeof(HEAP_DEBUGGING_INFORMATION)); + debuggingInfo.StackTraceDepth = 32; + debuggingInfo.HeapLeakEnumerationRoutine = PhpLeakEnumerationRoutine; + + if (!NT_SUCCESS(RtlSetHeapInformation(NULL, HeapDebuggingInformation, &debuggingInfo, sizeof(HEAP_DEBUGGING_INFORMATION)))) + { + wprintf(L"Unable to initialize heap debugging. Make sure that you are using Windows 7 or above."); + } + } + else if (PhEqualStringZ(command, L"leakdetect", TRUE)) + { + VOID (NTAPI *rtlDetectHeapLeaks)(VOID); + PWSTR options = wcstok_s(NULL, delims, &context); + + rtlDetectHeapLeaks = PhGetModuleProcAddress(L"ntdll.dll", "RtlDetectHeapLeaks"); + + if (!(NtCurrentPeb()->NtGlobalFlag & FLG_USER_STACK_TRACE_DB)) + { + wprintf(L"Warning: user-mode stack trace database is not enabled. Stack traces will not be displayed.\n"); + } + + ShowAllLeaks = FALSE; + + if (options) + { + if (PhEqualStringZ(options, L"all", TRUE)) + ShowAllLeaks = TRUE; + } + + if (rtlDetectHeapLeaks) + { + InLeakDetection = TRUE; + NumberOfLeaks = 0; + NumberOfLeaksShown = 0; + rtlDetectHeapLeaks(); + InLeakDetection = FALSE; + + wprintf(L"\nNumber of leaks: %lu (%lu displayed)\n", NumberOfLeaks, NumberOfLeaksShown); + } + } + else if (PhEqualStringZ(command, L"mem", TRUE)) + { + PWSTR addressString; + PWSTR bytesString; + PH_STRINGREF addressStringRef; + PH_STRINGREF bytesStringRef; + ULONG64 address64; + ULONG64 numberOfBytes64; + PUCHAR address; + ULONG numberOfBytes; + ULONG blockSize; + UCHAR buffer[16]; + ULONG i; + + addressString = wcstok_s(NULL, delims, &context); + + if (!addressString) + goto PrintMemUsage; + + bytesString = wcstok_s(NULL, delims, &context); + + if (!bytesString) + { + bytesString = L"16"; + } + + PhInitializeStringRef(&addressStringRef, addressString); + PhInitializeStringRef(&bytesStringRef, bytesString); + + if (PhStringToInteger64(&addressStringRef, 16, &address64) && PhStringToInteger64(&bytesStringRef, 10, &numberOfBytes64)) + { + address = (PUCHAR)address64; + numberOfBytes = (ULONG)numberOfBytes64; + + if (numberOfBytes > 256) + { + wprintf(L"Number of bytes must be 256 or smaller.\n"); + goto EndCommand; + } + + blockSize = sizeof(buffer); + + while (numberOfBytes != 0) + { + if (blockSize > numberOfBytes) + blockSize = numberOfBytes; + + __try + { + memcpy(buffer, address, blockSize); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + wprintf(L"Error reading address near %Ix.\n", (ULONG_PTR)address); + goto EndCommand; + } + + // Print hex dump + for (i = 0; i < blockSize; i++) + wprintf(L"%02x ", buffer[i]); + + // Fill remaining space (for last, possibly partial block) + for (; i < sizeof(buffer); i++) + wprintf(L" "); + + wprintf(L" "); + + // Print ASCII dump + for (i = 0; i < blockSize; i++) + putwchar((ULONG)(buffer[i] - ' ') <= (ULONG)('~' - ' ') ? buffer[i] : '.'); + + wprintf(L"\n"); + + address += blockSize; + numberOfBytes -= blockSize; + } + } + + goto EndCommand; +PrintMemUsage: + wprintf(L"Usage: mem address [numberOfBytes]\n"); + wprintf(L"Example: mem 12345678 16\n"); + } + else + { + wprintf(L"Unrecognized command.\n"); + goto EndCommand; // get rid of the compiler warning about the label being unreferenced + } + +EndCommand: + PhDrainAutoPool(&autoPool); + } + + PhDereferenceObject(DebugConsoleSymbolProvider); + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} diff --git a/ProcessHacker/extmgr.c b/ProcessHacker/extmgr.c new file mode 100644 index 0000000..d86051f --- /dev/null +++ b/ProcessHacker/extmgr.c @@ -0,0 +1,226 @@ +/* + * Process Hacker - + * extension manager + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The extension manager provides support for generic extensions. It sits directly + * underneath the plugin manager, and has no knowledge of plugin details (how they are + * loaded, plugin information, etc.). + */ + +#include +#include + +LIST_ENTRY PhEmAppContextListHead = { &PhEmAppContextListHead, &PhEmAppContextListHead }; +ULONG PhEmAppContextCount = 0; +PH_EM_OBJECT_TYPE_STATE PhEmObjectTypeState[EmMaximumObjectType] = { 0 }; + +/** + * Initializes the extension manager module. + */ +VOID PhEmInitialization( + VOID + ) +{ + ULONG i; + + for (i = 0; i < EmMaximumObjectType; i++) + { + InitializeListHead(&PhEmObjectTypeState[i].ExtensionListHead); + } +} + +/** + * Initializes an extension application context. + * + * \param AppContext The application context. + * \param AppName The application name. + */ +VOID PhEmInitializeAppContext( + _Out_ PPH_EM_APP_CONTEXT AppContext, + _In_ PPH_STRINGREF AppName + ) +{ + AppContext->AppName = *AppName; + memset(AppContext->Extensions, 0, sizeof(AppContext->Extensions)); + + InsertTailList(&PhEmAppContextListHead, &AppContext->ListEntry); + PhEmAppContextCount++; +} + +/** + * Sets the object extension size and callbacks for an object type. + * + * \param AppContext The application context. + * \param ObjectType The type of object for which the extension is being registered. + * \param ExtensionSize The size of the extension, in bytes. + * \param CreateCallback The object creation callback. + * \param DeleteCallback The object deletion callback. + */ +VOID PhEmSetObjectExtension( + _Inout_ PPH_EM_APP_CONTEXT AppContext, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ SIZE_T ExtensionSize, + _In_opt_ PPH_EM_OBJECT_CALLBACK CreateCallback, + _In_opt_ PPH_EM_OBJECT_CALLBACK DeleteCallback + ) +{ + PPH_EM_OBJECT_TYPE_STATE objectTypeState; + PPH_EM_OBJECT_EXTENSION objectExtension; + + objectTypeState = &PhEmObjectTypeState[ObjectType]; + objectExtension = AppContext->Extensions[ObjectType]; + + if (!objectExtension) + { + objectExtension = PhAllocate(sizeof(PH_EM_OBJECT_EXTENSION)); + memset(objectExtension, 0, sizeof(PH_EM_OBJECT_EXTENSION)); + InsertTailList(&objectTypeState->ExtensionListHead, &objectExtension->ListEntry); + AppContext->Extensions[ObjectType] = objectExtension; + + objectExtension->ExtensionSize = ExtensionSize; + objectExtension->ExtensionOffset = objectTypeState->ExtensionOffset; + + objectTypeState->ExtensionOffset += ExtensionSize; + } + + objectExtension->Callbacks[EmObjectCreate] = CreateCallback; + objectExtension->Callbacks[EmObjectDelete] = DeleteCallback; +} + +/** + * Gets the object extension for an object. + * + * \param AppContext The application context. + * \param ObjectType The type of object for which an extension has been registered. + * \param Object The object. + */ +PVOID PhEmGetObjectExtension( + _In_ PPH_EM_APP_CONTEXT AppContext, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Object + ) +{ + PPH_EM_OBJECT_EXTENSION objectExtension; + + objectExtension = AppContext->Extensions[ObjectType]; + + if (!objectExtension) + return NULL; + + return (PCHAR)Object + PhEmObjectTypeState[ObjectType].InitialSize + objectExtension->ExtensionOffset; +} + +/** + * Determines the size of an object, including extensions. + * + * \param ObjectType The object type. + * \param InitialSize The initial size of the object. + */ +SIZE_T PhEmGetObjectSize( + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ SIZE_T InitialSize + ) +{ + PhEmObjectTypeState[ObjectType].InitialSize = InitialSize; + + return InitialSize + PhEmObjectTypeState[ObjectType].ExtensionOffset; +} + +/** + * Invokes callbacks for an object operation. + * + * \param ObjectType The object type. + * \param Object The object. + * \param Operation The operation being performed. + */ +VOID PhEmCallObjectOperation( + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Object, + _In_ PH_EM_OBJECT_OPERATION Operation + ) +{ + PPH_EM_OBJECT_TYPE_STATE objectTypeState; + PLIST_ENTRY listEntry; + PPH_EM_OBJECT_EXTENSION objectExtension; + + if (PhEmAppContextCount == 0) + return; + + objectTypeState = &PhEmObjectTypeState[ObjectType]; + + listEntry = objectTypeState->ExtensionListHead.Flink; + + while (listEntry != &objectTypeState->ExtensionListHead) + { + objectExtension = CONTAINING_RECORD(listEntry, PH_EM_OBJECT_EXTENSION, ListEntry); + + if (objectExtension->Callbacks[Operation]) + { + objectExtension->Callbacks[Operation]( + Object, + ObjectType, + (PCHAR)Object + objectTypeState->InitialSize + objectExtension->ExtensionOffset + ); + } + + listEntry = listEntry->Flink; + } +} + +/** + * Parses an application name and sub-ID pair. + * + * \param CompoundId The compound identifier. + * \param AppName A variable which receives the application name. + * \param SubId A variable which receives the sub-ID. + */ +BOOLEAN PhEmParseCompoundId( + _In_ PPH_STRINGREF CompoundId, + _Out_ PPH_STRINGREF AppName, + _Out_ PULONG SubId + ) +{ + PH_STRINGREF firstPart; + PH_STRINGREF secondPart; + ULONG64 integer; + + firstPart = *CompoundId; + + if (firstPart.Length == 0) + return FALSE; + if (firstPart.Buffer[0] != '+') + return FALSE; + + PhSkipStringRef(&firstPart, sizeof(WCHAR)); + PhSplitStringRefAtChar(&firstPart, '+', &firstPart, &secondPart); + + if (firstPart.Length == 0 || secondPart.Length == 0) + return FALSE; + + if (!PhStringToInteger64(&secondPart, 10, &integer)) + return FALSE; + + *AppName = firstPart; + *SubId = (ULONG)integer; + + return TRUE; +} diff --git a/ProcessHacker/findobj.c b/ProcessHacker/findobj.c new file mode 100644 index 0000000..6895bc6 --- /dev/null +++ b/ProcessHacker/findobj.c @@ -0,0 +1,1011 @@ +/* + * Process Hacker - + * object search + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcre/pcre2.h" +#include + +#define WM_PH_SEARCH_UPDATE (WM_APP + 801) +#define WM_PH_SEARCH_FINISHED (WM_APP + 802) + +typedef enum _PHP_OBJECT_RESULT_TYPE +{ + HandleSearchResult, + ModuleSearchResult, + MappedFileSearchResult +} PHP_OBJECT_RESULT_TYPE; + +typedef struct _PHP_OBJECT_SEARCH_RESULT +{ + HANDLE ProcessId; + PHP_OBJECT_RESULT_TYPE ResultType; + + HANDLE Handle; + PPH_STRING TypeName; + PPH_STRING Name; + PPH_STRING ProcessName; + + WCHAR HandleString[PH_PTR_STR_LEN_1]; + + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Info; +} PHP_OBJECT_SEARCH_RESULT, *PPHP_OBJECT_SEARCH_RESULT; + +INT_PTR CALLBACK PhpFindObjectsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +NTSTATUS PhpFindObjectsThreadStart( + _In_ PVOID Parameter + ); + +HWND PhFindObjectsWindowHandle = NULL; +HWND PhFindObjectsListViewHandle = NULL; +static PH_LAYOUT_MANAGER WindowLayoutManager; +static RECT MinimumSize; + +static HANDLE SearchThreadHandle = NULL; +static BOOLEAN SearchStop; +static PPH_STRING SearchString; +static pcre2_code *SearchRegexCompiledExpression; +static pcre2_match_data *SearchRegexMatchData; +static PPH_LIST SearchResults = NULL; +static ULONG SearchResultsAddIndex; +static PH_QUEUED_LOCK SearchResultsLock = PH_QUEUED_LOCK_INIT; + +static ULONG64 SearchPointer; +static BOOLEAN UseSearchPointer; + +VOID PhShowFindObjectsDialog( + VOID + ) +{ + if (!PhFindObjectsWindowHandle) + { + PhFindObjectsWindowHandle = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_FINDOBJECTS), + PhMainWndHandle, + PhpFindObjectsDlgProc + ); + } + + if (!IsWindowVisible(PhFindObjectsWindowHandle)) + ShowWindow(PhFindObjectsWindowHandle, SW_SHOW); + + SetForegroundWindow(PhFindObjectsWindowHandle); +} + +VOID PhpInitializeFindObjMenu( + _In_ PPH_EMENU Menu, + _In_ PPHP_OBJECT_SEARCH_RESULT *Results, + _In_ ULONG NumberOfResults + ) +{ + BOOLEAN allCanBeClosed = TRUE; + ULONG i; + + if (NumberOfResults == 1) + { + PH_HANDLE_ITEM_INFO info; + + info.ProcessId = Results[0]->ProcessId; + info.Handle = Results[0]->Handle; + info.TypeName = Results[0]->TypeName; + info.BestObjectName = Results[0]->Name; + PhInsertHandleObjectPropertiesEMenuItems(Menu, ID_OBJECT_PROPERTIES, FALSE, &info); + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_OBJECT_COPY, TRUE); + } + + for (i = 0; i < NumberOfResults; i++) + { + if (Results[i]->ResultType != HandleSearchResult) + { + allCanBeClosed = FALSE; + break; + } + } + + PhEnableEMenuItem(Menu, ID_OBJECT_CLOSE, allCanBeClosed); +} + +INT NTAPI PhpObjectProcessCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPHP_OBJECT_SEARCH_RESULT item1 = Item1; + PPHP_OBJECT_SEARCH_RESULT item2 = Item2; + INT result; + + result = PhCompareStringWithNull(item1->ProcessName, item2->ProcessName, TRUE); + + if (result != 0) + return result; + else + return uintptrcmp((ULONG_PTR)item1->ProcessId, (ULONG_PTR)item2->ProcessId); +} + +INT NTAPI PhpObjectTypeCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPHP_OBJECT_SEARCH_RESULT item1 = Item1; + PPHP_OBJECT_SEARCH_RESULT item2 = Item2; + + return PhCompareString(item1->TypeName, item2->TypeName, TRUE); +} + +INT NTAPI PhpObjectNameCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPHP_OBJECT_SEARCH_RESULT item1 = Item1; + PPHP_OBJECT_SEARCH_RESULT item2 = Item2; + + return PhCompareString(item1->Name, item2->Name, TRUE); +} + +INT NTAPI PhpObjectHandleCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPHP_OBJECT_SEARCH_RESULT item1 = Item1; + PPHP_OBJECT_SEARCH_RESULT item2 = Item2; + + return uintptrcmp((ULONG_PTR)item1->Handle, (ULONG_PTR)item2->Handle); +} + +static INT_PTR CALLBACK PhpFindObjectsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + PhFindObjectsListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_RESULTS); + + PhInitializeLayoutManager(&WindowLayoutManager, hwndDlg); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_FILTER), + NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_REGEX), + NULL, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDOK), + NULL, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddLayoutItem(&WindowLayoutManager, lvHandle, + NULL, PH_ANCHOR_ALL); + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = 150; + MinimumSize.bottom = 100; + MapDialogRect(hwndDlg, &MinimumSize); + + PhRegisterDialog(hwndDlg); + + PhLoadWindowPlacementFromSetting(L"FindObjWindowPosition", L"FindObjWindowSize", hwndDlg); + + PhSetListViewStyle(lvHandle, TRUE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 100, L"Process"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Type"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 200, L"Name"); + PhAddListViewColumn(lvHandle, 3, 3, 3, LVCFMT_LEFT, 80, L"Handle"); + + PhSetExtendedListView(lvHandle); + ExtendedListView_SetSortFast(lvHandle, TRUE); + ExtendedListView_SetCompareFunction(lvHandle, 0, PhpObjectProcessCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 1, PhpObjectTypeCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 2, PhpObjectNameCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 3, PhpObjectHandleCompareFunction); + PhLoadListViewColumnsFromSetting(L"FindObjListViewColumns", lvHandle); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_REGEX), PhGetIntegerSetting(L"FindObjRegex") ? BST_CHECKED : BST_UNCHECKED); + } + break; + case WM_DESTROY: + { + PhSetIntegerSetting(L"FindObjRegex", Button_GetCheck(GetDlgItem(hwndDlg, IDC_REGEX)) == BST_CHECKED); + PhSaveWindowPlacementToSetting(L"FindObjWindowPosition", L"FindObjWindowSize", hwndDlg); + PhSaveListViewColumnsToSetting(L"FindObjListViewColumns", PhFindObjectsListViewHandle); + } + break; + case WM_SHOWWINDOW: + { + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_FILTER), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_FILTER), 0, -1); + } + break; + case WM_CLOSE: + { + ShowWindow(hwndDlg, SW_HIDE); + // IMPORTANT + // Set the result to 0 so the default dialog message + // handler doesn't invoke IDCANCEL, which will send + // WM_CLOSE, creating an infinite loop. + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 0); + } + return TRUE; + case WM_SETCURSOR: + { + if (SearchThreadHandle) + { + SetCursor(LoadCursor(NULL, IDC_WAIT)); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDOK: + { + // Don't continue if the user requested cancellation. + if (SearchStop) + break; + + if (!SearchThreadHandle) + { + ULONG i; + + PhMoveReference(&SearchString, PhGetWindowText(GetDlgItem(hwndDlg, IDC_FILTER))); + + if (SearchRegexCompiledExpression) + { + pcre2_code_free(SearchRegexCompiledExpression); + SearchRegexCompiledExpression = NULL; + } + + if (SearchRegexMatchData) + { + pcre2_match_data_free(SearchRegexMatchData); + SearchRegexMatchData = NULL; + } + + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_REGEX)) == BST_CHECKED) + { + int errorCode; + PCRE2_SIZE errorOffset; + + SearchRegexCompiledExpression = pcre2_compile( + SearchString->Buffer, + SearchString->Length / sizeof(WCHAR), + PCRE2_CASELESS | PCRE2_DOTALL, + &errorCode, + &errorOffset, + NULL + ); + + if (!SearchRegexCompiledExpression) + { + PhShowError(hwndDlg, L"Unable to compile the regular expression: \"%s\" at position %zu.", + PhGetStringOrDefault(PH_AUTO(PhPcre2GetErrorMessage(errorCode)), L"Unknown error"), + errorOffset + ); + break; + } + + SearchRegexMatchData = pcre2_match_data_create_from_pattern(SearchRegexCompiledExpression, NULL); + } + + // Clean up previous results. + + ListView_DeleteAllItems(PhFindObjectsListViewHandle); + + if (SearchResults) + { + for (i = 0; i < SearchResults->Count; i++) + { + PPHP_OBJECT_SEARCH_RESULT searchResult = SearchResults->Items[i]; + + PhDereferenceObject(searchResult->TypeName); + PhDereferenceObject(searchResult->Name); + + if (searchResult->ProcessName) + PhDereferenceObject(searchResult->ProcessName); + + PhFree(searchResult); + } + + PhDereferenceObject(SearchResults); + } + + // Start the search. + + SearchResults = PhCreateList(128); + SearchResultsAddIndex = 0; + + SearchThreadHandle = PhCreateThread(0, PhpFindObjectsThreadStart, NULL); + + if (!SearchThreadHandle) + { + PhClearReference(&SearchResults); + break; + } + + SetDlgItemText(hwndDlg, IDOK, L"Cancel"); + + SetCursor(LoadCursor(NULL, IDC_WAIT)); + } + else + { + SearchStop = TRUE; + EnableWindow(GetDlgItem(hwndDlg, IDOK), FALSE); + } + } + break; + case IDCANCEL: + { + SendMessage(hwndDlg, WM_CLOSE, 0, 0); + } + break; + case ID_OBJECT_CLOSE: + { + PPHP_OBJECT_SEARCH_RESULT *results; + ULONG numberOfResults; + ULONG i; + + PhGetSelectedListViewItemParams( + PhFindObjectsListViewHandle, + &results, + &numberOfResults + ); + + if (numberOfResults != 0 && PhShowConfirmMessage( + hwndDlg, + L"close", + numberOfResults == 1 ? L"the selected handle" : L"the selected handles", + L"Closing handles may cause system instability and data corruption.", + FALSE + )) + { + for (i = 0; i < numberOfResults; i++) + { + NTSTATUS status; + HANDLE processHandle; + + if (results[i]->ResultType != HandleSearchResult) + continue; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + results[i]->ProcessId + ))) + { + if (NT_SUCCESS(status = NtDuplicateObject( + processHandle, + results[i]->Handle, + NULL, + NULL, + 0, + 0, + DUPLICATE_CLOSE_SOURCE + ))) + { + PhRemoveListViewItem(PhFindObjectsListViewHandle, + PhFindListViewItemByParam(PhFindObjectsListViewHandle, 0, results[i])); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + if (!PhShowContinueStatus(hwndDlg, + PhaFormatString(L"Unable to close \"%s\"", results[i]->Name->Buffer)->Buffer, + status, + 0 + )) + break; + } + } + } + + PhFree(results); + } + break; + case ID_HANDLE_OBJECTPROPERTIES1: + case ID_HANDLE_OBJECTPROPERTIES2: + { + PPHP_OBJECT_SEARCH_RESULT result = + PhGetSelectedListViewItemParam(PhFindObjectsListViewHandle); + + if (result) + { + PH_HANDLE_ITEM_INFO info; + + info.ProcessId = result->ProcessId; + info.Handle = result->Handle; + info.TypeName = result->TypeName; + info.BestObjectName = result->Name; + + if (LOWORD(wParam) == ID_HANDLE_OBJECTPROPERTIES1) + PhShowHandleObjectProperties1(hwndDlg, &info); + else + PhShowHandleObjectProperties2(hwndDlg, &info); + } + } + break; + case ID_OBJECT_GOTOOWNINGPROCESS: + { + PPHP_OBJECT_SEARCH_RESULT result = + PhGetSelectedListViewItemParam(PhFindObjectsListViewHandle); + + if (result) + { + PPH_PROCESS_NODE processNode; + + if (processNode = PhFindProcessNode(result->ProcessId)) + { + ProcessHacker_SelectTabPage(PhMainWndHandle, 0); + ProcessHacker_SelectProcessNode(PhMainWndHandle, processNode); + ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE); + } + } + } + break; + case ID_OBJECT_PROPERTIES: + { + PPHP_OBJECT_SEARCH_RESULT result = + PhGetSelectedListViewItemParam(PhFindObjectsListViewHandle); + + if (result) + { + if (result->ResultType == HandleSearchResult) + { + PPH_HANDLE_ITEM handleItem; + + handleItem = PhCreateHandleItem(&result->Info); + + handleItem->BestObjectName = handleItem->ObjectName = result->Name; + PhReferenceObjectEx(result->Name, 2); + + handleItem->TypeName = result->TypeName; + PhReferenceObject(result->TypeName); + + PhShowHandleProperties( + hwndDlg, + result->ProcessId, + handleItem + ); + PhDereferenceObject(handleItem); + } + else + { + // DLL or Mapped File. Just show file properties. + PhShellProperties(hwndDlg, result->Name->Buffer); + } + } + } + break; + case ID_OBJECT_COPY: + { + PhCopyListView(PhFindObjectsListViewHandle); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case NM_DBLCLK: + { + if (header->hwndFrom == PhFindObjectsListViewHandle) + { + SendMessage(hwndDlg, WM_COMMAND, ID_OBJECT_PROPERTIES, 0); + } + } + break; + case LVN_KEYDOWN: + { + if (header->hwndFrom == PhFindObjectsListViewHandle) + { + LPNMLVKEYDOWN keyDown = (LPNMLVKEYDOWN)header; + + switch (keyDown->wVKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(hwndDlg, WM_COMMAND, ID_OBJECT_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + PhSetStateAllListViewItems(PhFindObjectsListViewHandle, LVIS_SELECTED, LVIS_SELECTED); + break; + case VK_DELETE: + SendMessage(hwndDlg, WM_COMMAND, ID_OBJECT_CLOSE, 0); + break; + } + } + } + break; + } + } + break; + case WM_CONTEXTMENU: + { + if ((HWND)wParam == PhFindObjectsListViewHandle) + { + POINT point; + PPHP_OBJECT_SEARCH_RESULT *results; + ULONG numberOfResults; + + point.x = (SHORT)LOWORD(lParam); + point.y = (SHORT)HIWORD(lParam); + + if (point.x == -1 && point.y == -1) + PhGetListViewContextMenuPoint((HWND)wParam, &point); + + PhGetSelectedListViewItemParams(PhFindObjectsListViewHandle, &results, &numberOfResults); + + if (numberOfResults != 0) + { + PPH_EMENU menu; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_FINDOBJ), 0); + PhSetFlagsEMenuItem(menu, ID_OBJECT_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhpInitializeFindObjMenu(menu, results, numberOfResults); + PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + point.x, + point.y + ); + PhDestroyEMenu(menu); + } + + PhFree(results); + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&WindowLayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_PH_SEARCH_UPDATE: + { + HWND lvHandle; + ULONG i; + + lvHandle = GetDlgItem(hwndDlg, IDC_RESULTS); + + ExtendedListView_SetRedraw(lvHandle, FALSE); + + PhAcquireQueuedLockExclusive(&SearchResultsLock); + + for (i = SearchResultsAddIndex; i < SearchResults->Count; i++) + { + PPHP_OBJECT_SEARCH_RESULT searchResult = SearchResults->Items[i]; + CLIENT_ID clientId; + PPH_PROCESS_ITEM processItem; + PPH_STRING clientIdName; + INT lvItemIndex; + + clientId.UniqueProcess = searchResult->ProcessId; + clientId.UniqueThread = NULL; + + processItem = PhReferenceProcessItem(clientId.UniqueProcess); + clientIdName = PhGetClientIdNameEx(&clientId, processItem ? processItem->ProcessName : NULL); + + lvItemIndex = PhAddListViewItem( + lvHandle, + MAXINT, + clientIdName->Buffer, + searchResult + ); + + PhDereferenceObject(clientIdName); + + if (processItem) + { + PhSetReference(&searchResult->ProcessName, processItem->ProcessName); + PhDereferenceObject(processItem); + } + else + { + searchResult->ProcessName = NULL; + } + + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, searchResult->TypeName->Buffer); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, searchResult->Name->Buffer); + PhSetListViewSubItem(lvHandle, lvItemIndex, 3, searchResult->HandleString); + } + + SearchResultsAddIndex = i; + + PhReleaseQueuedLockExclusive(&SearchResultsLock); + + ExtendedListView_SetRedraw(lvHandle, TRUE); + } + break; + case WM_PH_SEARCH_FINISHED: + { + NTSTATUS handleSearchStatus = (NTSTATUS)wParam; + + // Add any un-added items. + SendMessage(hwndDlg, WM_PH_SEARCH_UPDATE, 0, 0); + + NtWaitForSingleObject(SearchThreadHandle, FALSE, NULL); + NtClose(SearchThreadHandle); + SearchThreadHandle = NULL; + SearchStop = FALSE; + + ExtendedListView_SortItems(GetDlgItem(hwndDlg, IDC_RESULTS)); + + SetDlgItemText(hwndDlg, IDOK, L"Find"); + EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE); + + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + if (handleSearchStatus == STATUS_INSUFFICIENT_RESOURCES) + { + PhShowWarning( + hwndDlg, + L"Unable to search for handles because the total number of handles on the system is too large. " + L"Please check if there are any processes with an extremely large number of handles open." + ); + } + } + break; + } + + return FALSE; +} + +static BOOLEAN MatchSearchString( + _In_ PPH_STRINGREF Input + ) +{ + if (SearchRegexCompiledExpression && SearchRegexMatchData) + { + return pcre2_match( + SearchRegexCompiledExpression, + Input->Buffer, + Input->Length / sizeof(WCHAR), + 0, + 0, + SearchRegexMatchData, + NULL + ) >= 0; + } + else + { + return PhFindStringInStringRef(Input, &SearchString->sr, TRUE) != -1; + } +} + +typedef struct _SEARCH_HANDLE_CONTEXT +{ + BOOLEAN NeedToFree; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleInfo; + HANDLE ProcessHandle; +} SEARCH_HANDLE_CONTEXT, *PSEARCH_HANDLE_CONTEXT; + +static NTSTATUS NTAPI SearchHandleFunction( + _In_ PVOID Parameter + ) +{ + PSEARCH_HANDLE_CONTEXT context = Parameter; + PPH_STRING typeName; + PPH_STRING bestObjectName; + + if (!SearchStop && NT_SUCCESS(PhGetHandleInformation( + context->ProcessHandle, + (HANDLE)context->HandleInfo->HandleValue, + context->HandleInfo->ObjectTypeIndex, + NULL, + &typeName, + NULL, + &bestObjectName + ))) + { + PPH_STRING upperBestObjectName; + + upperBestObjectName = PhDuplicateString(bestObjectName); + _wcsupr(upperBestObjectName->Buffer); + + if (MatchSearchString(&upperBestObjectName->sr) || + (UseSearchPointer && context->HandleInfo->Object == (PVOID)SearchPointer)) + { + PPHP_OBJECT_SEARCH_RESULT searchResult; + + searchResult = PhAllocate(sizeof(PHP_OBJECT_SEARCH_RESULT)); + searchResult->ProcessId = (HANDLE)context->HandleInfo->UniqueProcessId; + searchResult->ResultType = HandleSearchResult; + searchResult->Handle = (HANDLE)context->HandleInfo->HandleValue; + searchResult->TypeName = typeName; + searchResult->Name = bestObjectName; + PhPrintPointer(searchResult->HandleString, (PVOID)searchResult->Handle); + searchResult->Info = *context->HandleInfo; + + PhAcquireQueuedLockExclusive(&SearchResultsLock); + + PhAddItemList(SearchResults, searchResult); + + // Update the search results in batches of 40. + if (SearchResults->Count % 40 == 0) + PostMessage(PhFindObjectsWindowHandle, WM_PH_SEARCH_UPDATE, 0, 0); + + PhReleaseQueuedLockExclusive(&SearchResultsLock); + } + else + { + PhDereferenceObject(typeName); + PhDereferenceObject(bestObjectName); + } + + PhDereferenceObject(upperBestObjectName); + } + + if (context->NeedToFree) + PhFree(context); + + return STATUS_SUCCESS; +} + +static BOOLEAN NTAPI EnumModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PPH_STRING upperFileName; + + upperFileName = PhDuplicateString(Module->FileName); + _wcsupr(upperFileName->Buffer); + + if (MatchSearchString(&upperFileName->sr) || + (UseSearchPointer && Module->BaseAddress == (PVOID)SearchPointer)) + { + PPHP_OBJECT_SEARCH_RESULT searchResult; + PWSTR typeName; + + switch (Module->Type) + { + case PH_MODULE_TYPE_MAPPED_FILE: + typeName = L"Mapped file"; + break; + case PH_MODULE_TYPE_MAPPED_IMAGE: + typeName = L"Mapped image"; + break; + default: + typeName = L"DLL"; + break; + } + + searchResult = PhAllocate(sizeof(PHP_OBJECT_SEARCH_RESULT)); + searchResult->ProcessId = (HANDLE)Context; + searchResult->ResultType = (Module->Type == PH_MODULE_TYPE_MAPPED_FILE || Module->Type == PH_MODULE_TYPE_MAPPED_IMAGE) ? MappedFileSearchResult : ModuleSearchResult; + searchResult->Handle = (HANDLE)Module->BaseAddress; + searchResult->TypeName = PhCreateString(typeName); + PhSetReference(&searchResult->Name, Module->FileName); + PhPrintPointer(searchResult->HandleString, Module->BaseAddress); + memset(&searchResult->Info, 0, sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)); + + PhAcquireQueuedLockExclusive(&SearchResultsLock); + + PhAddItemList(SearchResults, searchResult); + + // Update the search results in batches of 40. + if (SearchResults->Count % 40 == 0) + PostMessage(PhFindObjectsWindowHandle, WM_PH_SEARCH_UPDATE, 0, 0); + + PhReleaseQueuedLockExclusive(&SearchResultsLock); + } + + PhDereferenceObject(upperFileName); + + return TRUE; +} + +static NTSTATUS PhpFindObjectsThreadStart( + _In_ PVOID Parameter + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PSYSTEM_HANDLE_INFORMATION_EX handles; + PPH_HASHTABLE processHandleHashtable; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + ULONG i; + + // Refuse to search with no filter. + if (SearchString->Length == 0) + goto Exit; + + // Try to get a search pointer from the search string. + UseSearchPointer = PhStringToInteger64(&SearchString->sr, 0, &SearchPointer); + + _wcsupr(SearchString->Buffer); + + if (NT_SUCCESS(status = PhEnumHandlesEx(&handles))) + { + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static ULONG fileObjectTypeIndex = -1; + + BOOLEAN useWorkQueue = FALSE; + PH_WORK_QUEUE workQueue; + processHandleHashtable = PhCreateSimpleHashtable(8); + + if (!KphIsConnected() && WindowsVersion >= WINDOWS_VISTA) + { + useWorkQueue = TRUE; + PhInitializeWorkQueue(&workQueue, 1, 20, 1000); + + if (PhBeginInitOnce(&initOnce)) + { + UNICODE_STRING fileTypeName; + + RtlInitUnicodeString(&fileTypeName, L"File"); + fileObjectTypeIndex = PhGetObjectTypeNumber(&fileTypeName); + PhEndInitOnce(&initOnce); + } + } + + for (i = 0; i < handles->NumberOfHandles; i++) + { + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = &handles->Handles[i]; + PVOID *processHandlePtr; + HANDLE processHandle; + + if (SearchStop) + break; + + // Open a handle to the process if we don't already have one. + + processHandlePtr = PhFindItemSimpleHashtable( + processHandleHashtable, + (PVOID)handleInfo->UniqueProcessId + ); + + if (processHandlePtr) + { + processHandle = (HANDLE)*processHandlePtr; + } + else + { + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + (HANDLE)handleInfo->UniqueProcessId + ))) + { + PhAddItemSimpleHashtable( + processHandleHashtable, + (PVOID)handleInfo->UniqueProcessId, + processHandle + ); + } + else + { + continue; + } + } + + if (useWorkQueue && handleInfo->ObjectTypeIndex == (USHORT)fileObjectTypeIndex) + { + PSEARCH_HANDLE_CONTEXT searchHandleContext; + + searchHandleContext = PhAllocate(sizeof(SEARCH_HANDLE_CONTEXT)); + searchHandleContext->NeedToFree = TRUE; + searchHandleContext->HandleInfo = handleInfo; + searchHandleContext->ProcessHandle = processHandle; + PhQueueItemWorkQueue(&workQueue, SearchHandleFunction, searchHandleContext); + } + else + { + SEARCH_HANDLE_CONTEXT searchHandleContext; + + searchHandleContext.NeedToFree = FALSE; + searchHandleContext.HandleInfo = handleInfo; + searchHandleContext.ProcessHandle = processHandle; + SearchHandleFunction(&searchHandleContext); + } + } + + if (useWorkQueue) + { + PhWaitForWorkQueue(&workQueue); + PhDeleteWorkQueue(&workQueue); + } + + { + PPH_KEY_VALUE_PAIR entry; + + i = 0; + + while (PhEnumHashtable(processHandleHashtable, &entry, &i)) + NtClose((HANDLE)entry->Value); + } + + PhDereferenceObject(processHandleHashtable); + PhFree(handles); + } + + if (NT_SUCCESS(PhEnumProcesses(&processes))) + { + process = PH_FIRST_PROCESS(processes); + + do + { + PhEnumGenericModules( + process->UniqueProcessId, + NULL, + PH_ENUM_GENERIC_MAPPED_FILES | PH_ENUM_GENERIC_MAPPED_IMAGES, + EnumModulesCallback, + (PVOID)process->UniqueProcessId + ); + } while (process = PH_NEXT_PROCESS(process)); + + PhFree(processes); + } + +Exit: + PostMessage(PhFindObjectsWindowHandle, WM_PH_SEARCH_FINISHED, status, 0); + + return STATUS_SUCCESS; +} diff --git a/ProcessHacker/gdihndl.c b/ProcessHacker/gdihndl.c new file mode 100644 index 0000000..d20d489 --- /dev/null +++ b/ProcessHacker/gdihndl.c @@ -0,0 +1,378 @@ +/* + * Process Hacker - + * GDI handles dialog + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct _GDI_HANDLES_CONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + PPH_LIST List; +} GDI_HANDLES_CONTEXT, *PGDI_HANDLES_CONTEXT; + +typedef struct _PH_GDI_HANDLE_ITEM +{ + PGDI_HANDLE_ENTRY Entry; + ULONG Handle; + PVOID Object; + PWSTR TypeName; + PPH_STRING Information; +} PH_GDI_HANDLE_ITEM, *PPH_GDI_HANDLE_ITEM; + +INT_PTR CALLBACK PhpGdiHandlesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowGdiHandlesDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + GDI_HANDLES_CONTEXT context; + ULONG i; + + context.ProcessItem = ProcessItem; + context.List = PhCreateList(20); + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_GDIHANDLES), + ParentWindowHandle, + PhpGdiHandlesDlgProc, + (LPARAM)&context + ); + + for (i = 0; i < context.List->Count; i++) + { + PPH_GDI_HANDLE_ITEM gdiHandleItem = context.List->Items[i]; + + if (gdiHandleItem->Information) + PhDereferenceObject(gdiHandleItem->Information); + + PhFree(context.List->Items[i]); + } + + PhDereferenceObject(context.List); +} + +PWSTR PhpGetGdiHandleTypeName( + _In_ ULONG Unique + ) +{ + switch (GDI_CLIENT_TYPE_FROM_UNIQUE(Unique)) + { + case GDI_CLIENT_ALTDC_TYPE: + return L"Alt. DC"; + case GDI_CLIENT_BITMAP_TYPE: + return L"Bitmap"; + case GDI_CLIENT_BRUSH_TYPE: + return L"Brush"; + case GDI_CLIENT_CLIENTOBJ_TYPE: + return L"Client Object"; + case GDI_CLIENT_DIBSECTION_TYPE: + return L"DIB Section"; + case GDI_CLIENT_DC_TYPE: + return L"DC"; + case GDI_CLIENT_EXTPEN_TYPE: + return L"ExtPen"; + case GDI_CLIENT_FONT_TYPE: + return L"Font"; + case GDI_CLIENT_METADC16_TYPE: + return L"Metafile DC"; + case GDI_CLIENT_METAFILE_TYPE: + return L"Enhanced Metafile"; + case GDI_CLIENT_METAFILE16_TYPE: + return L"Metafile"; + case GDI_CLIENT_PALETTE_TYPE: + return L"Palette"; + case GDI_CLIENT_PEN_TYPE: + return L"Pen"; + case GDI_CLIENT_REGION_TYPE: + return L"Region"; + default: + return NULL; + } +} + +PPH_STRING PhpGetGdiHandleInformation( + _In_ ULONG Handle + ) +{ + HGDIOBJ handle; + + handle = (HGDIOBJ)UlongToPtr(Handle); + + switch (GDI_CLIENT_TYPE_FROM_HANDLE(Handle)) + { + case GDI_CLIENT_BITMAP_TYPE: + case GDI_CLIENT_DIBSECTION_TYPE: + { + BITMAP bitmap; + + if (GetObject(handle, sizeof(BITMAP), &bitmap)) + { + return PhFormatString( + L"Width: %u, Height: %u, Depth: %u", + bitmap.bmWidth, + bitmap.bmHeight, + bitmap.bmBitsPixel + ); + } + } + break; + case GDI_CLIENT_BRUSH_TYPE: + { + LOGBRUSH brush; + + if (GetObject(handle, sizeof(LOGBRUSH), &brush)) + { + return PhFormatString( + L"Style: %u, Color: 0x%08x, Hatch: 0x%Ix", + brush.lbStyle, + _byteswap_ulong(brush.lbColor), + brush.lbHatch + ); + } + } + break; + case GDI_CLIENT_EXTPEN_TYPE: + { + EXTLOGPEN pen; + + if (GetObject(handle, sizeof(EXTLOGPEN), &pen)) + { + return PhFormatString( + L"Style: 0x%x, Width: %u, Color: 0x%08x", + pen.elpPenStyle, + pen.elpWidth, + _byteswap_ulong(pen.elpColor) + ); + } + } + break; + case GDI_CLIENT_FONT_TYPE: + { + LOGFONT font; + + if (GetObject(handle, sizeof(LOGFONT), &font)) + { + return PhFormatString( + L"Face: %s, Height: %d", + font.lfFaceName, + font.lfHeight + ); + } + } + break; + case GDI_CLIENT_PALETTE_TYPE: + { + USHORT count; + + if (GetObject(handle, sizeof(USHORT), &count)) + { + return PhFormatString( + L"Entries: %u", + (ULONG)count + ); + } + } + break; + case GDI_CLIENT_PEN_TYPE: + { + LOGPEN pen; + + if (GetObject(handle, sizeof(LOGPEN), &pen)) + { + return PhFormatString( + L"Style: %u, Width: %u, Color: 0x%08x", + pen.lopnStyle, + pen.lopnWidth.x, + _byteswap_ulong(pen.lopnColor) + ); + } + } + break; + } + + return NULL; +} + +VOID PhpRefreshGdiHandles( + _In_ HWND hwndDlg, + _In_ PGDI_HANDLES_CONTEXT Context + ) +{ + HWND lvHandle; + ULONG i; + PGDI_SHARED_MEMORY gdiShared; + USHORT processId; + PGDI_HANDLE_ENTRY handle; + PPH_GDI_HANDLE_ITEM gdiHandleItem; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + ListView_DeleteAllItems(lvHandle); + ExtendedListView_SetRedraw(lvHandle, FALSE); + + for (i = 0; i < Context->List->Count; i++) + { + gdiHandleItem = Context->List->Items[i]; + + if (gdiHandleItem->Information) + PhDereferenceObject(gdiHandleItem->Information); + + PhFree(Context->List->Items[i]); + } + + PhClearList(Context->List); + + gdiShared = (PGDI_SHARED_MEMORY)NtCurrentPeb()->GdiSharedHandleTable; + processId = (USHORT)Context->ProcessItem->ProcessId; + + for (i = 0; i < GDI_MAX_HANDLE_COUNT; i++) + { + PWSTR typeName; + INT lvItemIndex; + WCHAR pointer[PH_PTR_STR_LEN_1]; + + handle = &gdiShared->Handles[i]; + + if (handle->Owner.ProcessId != processId) + continue; + + typeName = PhpGetGdiHandleTypeName(handle->Unique); + + if (!typeName) + continue; + + gdiHandleItem = PhAllocate(sizeof(PH_GDI_HANDLE_ITEM)); + gdiHandleItem->Entry = handle; + gdiHandleItem->Handle = GDI_MAKE_HANDLE(i, handle->Unique); + gdiHandleItem->Object = handle->Object; + gdiHandleItem->TypeName = typeName; + gdiHandleItem->Information = PhpGetGdiHandleInformation(gdiHandleItem->Handle); + PhAddItemList(Context->List, gdiHandleItem); + + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, gdiHandleItem->TypeName, gdiHandleItem); + PhPrintPointer(pointer, UlongToPtr(gdiHandleItem->Handle)); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, pointer); + PhPrintPointer(pointer, gdiHandleItem->Object); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, pointer); + PhSetListViewSubItem(lvHandle, lvItemIndex, 3, PhGetString(gdiHandleItem->Information)); + } + + ExtendedListView_SortItems(lvHandle); + ExtendedListView_SetRedraw(lvHandle, TRUE); +} + +INT NTAPI PhpGdiHandleHandleCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPH_GDI_HANDLE_ITEM item1 = Item1; + PPH_GDI_HANDLE_ITEM item2 = Item2; + + return uintcmp(item1->Handle, item2->Handle); +} + +INT NTAPI PhpGdiHandleObjectCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PPH_GDI_HANDLE_ITEM item1 = Item1; + PPH_GDI_HANDLE_ITEM item2 = Item2; + + return uintptrcmp((ULONG_PTR)item1->Object, (ULONG_PTR)item2->Object); +} + +INT_PTR CALLBACK PhpGdiHandlesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PGDI_HANDLES_CONTEXT context = (PGDI_HANDLES_CONTEXT)lParam; + HWND lvHandle; + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 100, L"Type"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"Handle"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 80, L"Object"); + PhAddListViewColumn(lvHandle, 3, 3, 3, LVCFMT_LEFT, 200, L"Information"); + + PhSetExtendedListView(lvHandle); + ExtendedListView_SetCompareFunction(lvHandle, 1, PhpGdiHandleHandleCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 2, PhpGdiHandleObjectCompareFunction); + ExtendedListView_AddFallbackColumn(lvHandle, 0); + ExtendedListView_AddFallbackColumn(lvHandle, 1); + + PhpRefreshGdiHandles(hwndDlg, context); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_REFRESH: + { + PhpRefreshGdiHandles(hwndDlg, (PGDI_HANDLES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom())); + } + break; + } + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/hidnproc.c b/ProcessHacker/hidnproc.c new file mode 100644 index 0000000..2e58b6f --- /dev/null +++ b/ProcessHacker/hidnproc.c @@ -0,0 +1,1246 @@ +/* + * Process Hacker - + * hidden processes detection + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * There are two methods of hidden process detection implemented in this module. + * + * Brute Force. This attempts to open all possible PIDs within a certain range + * in order to find processes which have been unlinked from the active process + * list (EPROCESS.ActiveProcessLinks). This method is not effective when + * either NtOpenProcess is hooked or PsLookupProcessByProcessId is hooked + * (KProcessHacker cannot bypass this). + * + * CSR Handles. This enumerates handles in all running CSR processes, and works + * even when a process has been unlinked from the active process list and + * has been removed from the client ID table (PspCidTable). However, the method + * does not detect native executables since CSR is not notified about them. + * Some rootkits hook NtQuerySystemInformation in order to modify the returned + * handle information; Process Hacker bypasses this by using KProcessHacker, + * which calls ExEnumHandleTable directly. Note that both process and thread + * handles are examined. + */ + +#include +#include +#include +#include +#include +#include +#include + +INT_PTR CALLBACK PhpHiddenProcessesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +COLORREF NTAPI PhpHiddenProcessesColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ); + +BOOLEAN NTAPI PhpHiddenProcessesCallback( + _In_ PPH_HIDDEN_PROCESS_ENTRY Process, + _In_opt_ PVOID Context + ); + +PPH_PROCESS_ITEM PhpCreateProcessItemForHiddenProcess( + _In_ PPH_HIDDEN_PROCESS_ENTRY Entry + ); + +HWND PhHiddenProcessesWindowHandle = NULL; +HWND PhHiddenProcessesListViewHandle = NULL; +static PH_LAYOUT_MANAGER WindowLayoutManager; +static RECT MinimumSize; + +static PH_HIDDEN_PROCESS_METHOD ProcessesMethod; +static PPH_LIST ProcessesList = NULL; +static ULONG NumberOfHiddenProcesses; +static ULONG NumberOfTerminatedProcesses; + +VOID PhShowHiddenProcessesDialog( + VOID + ) +{ + if (!KphIsConnected()) + { + PhShowWarning( + PhMainWndHandle, + L"Hidden process detection cannot function properly without KProcessHacker. " + L"Make sure Process Hacker is running with administrative privileges." + ); + } + + if (!PhHiddenProcessesWindowHandle) + { + PhHiddenProcessesWindowHandle = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_HIDDENPROCESSES), + PhMainWndHandle, + PhpHiddenProcessesDlgProc + ); + } + + if (!IsWindowVisible(PhHiddenProcessesWindowHandle)) + ShowWindow(PhHiddenProcessesWindowHandle, SW_SHOW); + else + SetForegroundWindow(PhHiddenProcessesWindowHandle); +} + +static INT_PTR CALLBACK PhpHiddenProcessesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + PhHiddenProcessesListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_PROCESSES); + + PhInitializeLayoutManager(&WindowLayoutManager, hwndDlg); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_INTRO), + NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + PhAddLayoutItem(&WindowLayoutManager, lvHandle, + NULL, PH_ANCHOR_ALL); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_DESCRIPTION), + NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM | PH_LAYOUT_FORCE_INVALIDATE); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_METHOD), + NULL, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_TERMINATE), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_SAVE), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_SCAN), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDOK), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = 330; + MinimumSize.bottom = 140; + MapDialogRect(hwndDlg, &MinimumSize); + + PhRegisterDialog(hwndDlg); + + PhLoadWindowPlacementFromSetting(L"HiddenProcessesWindowPosition", L"HiddenProcessesWindowSize", hwndDlg); + + PhSetListViewStyle(lvHandle, TRUE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 320, L"Process"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 60, L"PID"); + + PhSetExtendedListView(lvHandle); + PhLoadListViewColumnsFromSetting(L"HiddenProcessesListViewColumns", lvHandle); + ExtendedListView_AddFallbackColumn(lvHandle, 0); + ExtendedListView_AddFallbackColumn(lvHandle, 1); + ExtendedListView_SetItemColorFunction(lvHandle, PhpHiddenProcessesColorFunction); + + ComboBox_AddString(GetDlgItem(hwndDlg, IDC_METHOD), L"Brute force"); + ComboBox_AddString(GetDlgItem(hwndDlg, IDC_METHOD), L"CSR handles"); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_METHOD), L"CSR handles", FALSE); + + EnableWindow(GetDlgItem(hwndDlg, IDC_TERMINATE), FALSE); + } + break; + case WM_DESTROY: + { + PhSaveWindowPlacementToSetting(L"HiddenProcessesWindowPosition", L"HiddenProcessesWindowSize", hwndDlg); + PhSaveListViewColumnsToSetting(L"HiddenProcessesListViewColumns", PhHiddenProcessesListViewHandle); + } + break; + case WM_CLOSE: + { + // Hide, don't close. + ShowWindow(hwndDlg, SW_HIDE); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, 0); + } + return TRUE; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + { + SendMessage(hwndDlg, WM_CLOSE, 0, 0); + } + break; + case IDC_SCAN: + { + NTSTATUS status; + PPH_STRING method; + + method = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_METHOD))); + + if (ProcessesList) + { + ULONG i; + + for (i = 0; i < ProcessesList->Count; i++) + { + PPH_HIDDEN_PROCESS_ENTRY entry = ProcessesList->Items[i]; + + if (entry->FileName) + PhDereferenceObject(entry->FileName); + + PhFree(entry); + } + + PhDereferenceObject(ProcessesList); + } + + ListView_DeleteAllItems(PhHiddenProcessesListViewHandle); + + ProcessesList = PhCreateList(40); + + ProcessesMethod = + PhEqualString2(method, L"Brute force", TRUE) ? + BruteForceScanMethod : + CsrHandlesScanMethod; + NumberOfHiddenProcesses = 0; + NumberOfTerminatedProcesses = 0; + + ExtendedListView_SetRedraw(PhHiddenProcessesListViewHandle, FALSE); + status = PhEnumHiddenProcesses( + ProcessesMethod, + PhpHiddenProcessesCallback, + NULL + ); + ExtendedListView_SortItems(PhHiddenProcessesListViewHandle); + ExtendedListView_SetRedraw(PhHiddenProcessesListViewHandle, TRUE); + + if (NT_SUCCESS(status)) + { + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, + PhaFormatString(L"%u hidden process(es), %u terminated process(es).", + NumberOfHiddenProcesses, NumberOfTerminatedProcesses)->Buffer + ); + InvalidateRect(GetDlgItem(hwndDlg, IDC_DESCRIPTION), NULL, TRUE); + } + else + { + PhShowStatus(hwndDlg, L"Unable to perform the scan", status, 0); + } + } + break; + case IDC_TERMINATE: + { + PPH_HIDDEN_PROCESS_ENTRY *entries; + ULONG numberOfEntries; + ULONG i; + + PhGetSelectedListViewItemParams(PhHiddenProcessesListViewHandle, &entries, &numberOfEntries); + + if (numberOfEntries != 0) + { + if (!PhGetIntegerSetting(L"EnableWarnings") || + PhShowConfirmMessage( + hwndDlg, + L"terminate", + L"the selected process(es)", + L"Terminating a hidden process may cause the system to become unstable " + L"or crash.", + TRUE + )) + { + NTSTATUS status; + HANDLE processHandle; + BOOLEAN refresh; + + refresh = FALSE; + + for (i = 0; i < numberOfEntries; i++) + { + if (ProcessesMethod == BruteForceScanMethod) + { + status = PhOpenProcess( + &processHandle, + PROCESS_TERMINATE, + entries[i]->ProcessId + ); + } + else + { + status = PhOpenProcessByCsrHandles( + &processHandle, + PROCESS_TERMINATE, + entries[i]->ProcessId + ); + } + + if (NT_SUCCESS(status)) + { + status = PhTerminateProcess(processHandle, STATUS_SUCCESS); + NtClose(processHandle); + + if (NT_SUCCESS(status)) + refresh = TRUE; + } + else + { + PhShowStatus(hwndDlg, L"Unable to terminate the process", status, 0); + } + } + + if (refresh) + { + LARGE_INTEGER interval; + + // Sleep for a bit before continuing. It seems to help avoid + // BSODs. + interval.QuadPart = -250 * PH_TIMEOUT_MS; + NtDelayExecution(FALSE, &interval); + SendMessage(hwndDlg, WM_COMMAND, IDC_SCAN, 0); + } + } + } + + PhFree(entries); + } + break; + case IDC_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Text files (*.txt)", L"*.txt" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, L"Hidden Processes.txt"); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + PhWriteStringAsUtf8FileStream(fileStream, &PhUnicodeByteOrderMark); + PhWritePhTextHeader(fileStream); + PhWriteStringAsUtf8FileStream2(fileStream, L"Method: "); + PhWriteStringAsUtf8FileStream2(fileStream, + ProcessesMethod == BruteForceScanMethod ? L"Brute Force\r\n" : L"CSR Handles\r\n"); + PhWriteStringFormatAsUtf8FileStream( + fileStream, + L"Hidden: %u\r\nTerminated: %u\r\n\r\n", + NumberOfHiddenProcesses, + NumberOfTerminatedProcesses + ); + + if (ProcessesList) + { + ULONG i; + + for (i = 0; i < ProcessesList->Count; i++) + { + PPH_HIDDEN_PROCESS_ENTRY entry = ProcessesList->Items[i]; + + if (entry->Type == HiddenProcess) + PhWriteStringAsUtf8FileStream2(fileStream, L"[HIDDEN] "); + else if (entry->Type == TerminatedProcess) + PhWriteStringAsUtf8FileStream2(fileStream, L"[Terminated] "); + else if (entry->Type != NormalProcess) + continue; + + PhWriteStringFormatAsUtf8FileStream( + fileStream, + L"%s (%u)\r\n", + entry->FileName->Buffer, + HandleToUlong(entry->ProcessId) + ); + } + } + + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + PhHandleListViewNotifyBehaviors(lParam, PhHiddenProcessesListViewHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == PhHiddenProcessesListViewHandle) + { + EnableWindow( + GetDlgItem(hwndDlg, IDC_TERMINATE), + ListView_GetSelectedCount(PhHiddenProcessesListViewHandle) > 0 + ); + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == PhHiddenProcessesListViewHandle) + { + PPH_HIDDEN_PROCESS_ENTRY entry; + + entry = PhGetSelectedListViewItemParam(PhHiddenProcessesListViewHandle); + + if (entry) + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhpCreateProcessItemForHiddenProcess(entry)) + { + ProcessHacker_ShowProcessProperties(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + else + { + PhShowError(hwndDlg, L"Unable to create a process structure for the selected process."); + } + } + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&WindowLayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_CTLCOLORSTATIC: + { + if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_DESCRIPTION)) + { + if (NumberOfHiddenProcesses != 0) + { + SetTextColor((HDC)wParam, RGB(0xff, 0x00, 0x00)); + } + + SetBkColor((HDC)wParam, GetSysColor(COLOR_3DFACE)); + + return (INT_PTR)GetSysColorBrush(COLOR_3DFACE); + } + } + break; + } + + REFLECT_MESSAGE_DLG(hwndDlg, PhHiddenProcessesListViewHandle, uMsg, wParam, lParam); + + return FALSE; +} + +static COLORREF NTAPI PhpHiddenProcessesColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PPH_HIDDEN_PROCESS_ENTRY entry = Param; + + switch (entry->Type) + { + case UnknownProcess: + case HiddenProcess: + return RGB(0xff, 0x00, 0x00); + case TerminatedProcess: + return RGB(0x77, 0x77, 0x77); + } + + return GetSysColor(COLOR_WINDOW); +} + +static BOOLEAN NTAPI PhpHiddenProcessesCallback( + _In_ PPH_HIDDEN_PROCESS_ENTRY Process, + _In_opt_ PVOID Context + ) +{ + PPH_HIDDEN_PROCESS_ENTRY entry; + INT lvItemIndex; + WCHAR pidString[PH_INT32_STR_LEN_1]; + + entry = PhAllocateCopy(Process, sizeof(PH_HIDDEN_PROCESS_ENTRY)); + + if (entry->FileName) + PhReferenceObject(entry->FileName); + + PhAddItemList(ProcessesList, entry); + + lvItemIndex = PhAddListViewItem(PhHiddenProcessesListViewHandle, MAXINT, + PhGetStringOrDefault(entry->FileName, L"(unknown)"), entry); + PhPrintUInt32(pidString, HandleToUlong(entry->ProcessId)); + PhSetListViewSubItem(PhHiddenProcessesListViewHandle, lvItemIndex, 1, pidString); + + if (entry->Type == HiddenProcess) + NumberOfHiddenProcesses++; + else if (entry->Type == TerminatedProcess) + NumberOfTerminatedProcesses++; + + return TRUE; +} + +static PPH_PROCESS_ITEM PhpCreateProcessItemForHiddenProcess( + _In_ PPH_HIDDEN_PROCESS_ENTRY Entry + ) +{ + NTSTATUS status; + PPH_PROCESS_ITEM processItem; + PPH_PROCESS_ITEM idleProcessItem; + HANDLE processHandle; + PROCESS_BASIC_INFORMATION basicInfo; + KERNEL_USER_TIMES times; + PROCESS_PRIORITY_CLASS priorityClass; + ULONG handleCount; + HANDLE processHandle2; + + if (Entry->Type == NormalProcess) + { + processItem = PhReferenceProcessItem(Entry->ProcessId); + + if (processItem) + return processItem; + } + + processItem = PhCreateProcessItem(Entry->ProcessId); + + // Mark the process as terminated if necessary. + if (Entry->Type == TerminatedProcess) + processItem->State |= PH_PROCESS_ITEM_REMOVED; + + // We need a process record. Just use the record of System Idle Process. + if (idleProcessItem = PhReferenceProcessItem(SYSTEM_IDLE_PROCESS_ID)) + { + processItem->Record = idleProcessItem->Record; + PhReferenceProcessRecord(processItem->Record); + } + else + { + PhDereferenceObject(processItem); + return NULL; + } + + // Set up the file name and process name. + + PhSwapReference(&processItem->FileName, Entry->FileName); + + if (processItem->FileName) + { + processItem->ProcessName = PhGetBaseName(processItem->FileName); + } + else + { + processItem->ProcessName = PhCreateString(L"Unknown"); + } + + if (ProcessesMethod == BruteForceScanMethod) + { + status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Entry->ProcessId + ); + } + else + { + status = PhOpenProcessByCsrHandles( + &processHandle, + ProcessQueryAccess, + Entry->ProcessId + ); + } + + if (NT_SUCCESS(status)) + { + // Basic information and not-so-dynamic information + + processItem->QueryHandle = processHandle; + + if (NT_SUCCESS(PhGetProcessBasicInformation(processHandle, &basicInfo))) + { + processItem->ParentProcessId = basicInfo.InheritedFromUniqueProcessId; + processItem->BasePriority = basicInfo.BasePriority; + } + + PhGetProcessSessionId(processHandle, &processItem->SessionId); + + PhPrintUInt32(processItem->ParentProcessIdString, HandleToUlong(processItem->ParentProcessId)); + PhPrintUInt32(processItem->SessionIdString, processItem->SessionId); + + if (NT_SUCCESS(PhGetProcessTimes(processHandle, ×))) + { + processItem->CreateTime = times.CreateTime; + processItem->KernelTime = times.KernelTime; + processItem->UserTime = times.UserTime; + } + + // TODO: Token information? + + if (NT_SUCCESS(NtQueryInformationProcess( + processHandle, + ProcessPriorityClass, + &priorityClass, + sizeof(PROCESS_PRIORITY_CLASS), + NULL + ))) + { + processItem->PriorityClass = priorityClass.PriorityClass; + } + + if (NT_SUCCESS(NtQueryInformationProcess( + processHandle, + ProcessHandleCount, + &handleCount, + sizeof(ULONG), + NULL + ))) + { + processItem->NumberOfHandles = handleCount; + } + } + + // Stage 1 + // Some copy and paste magic here... + + if (processItem->FileName) + { + // Small icon, large icon. + ExtractIconEx( + processItem->FileName->Buffer, + 0, + &processItem->LargeIcon, + &processItem->SmallIcon, + 1 + ); + + // Version info. + PhInitializeImageVersionInfo(&processItem->VersionInfo, processItem->FileName->Buffer); + } + + // Use the default EXE icon if we didn't get the file's icon. + { + if (!processItem->SmallIcon || !processItem->LargeIcon) + { + if (processItem->SmallIcon) + { + DestroyIcon(processItem->SmallIcon); + processItem->SmallIcon = NULL; + } + else if (processItem->LargeIcon) + { + DestroyIcon(processItem->LargeIcon); + processItem->LargeIcon = NULL; + } + + PhGetStockApplicationIcon(&processItem->SmallIcon, &processItem->LargeIcon); + processItem->SmallIcon = CopyIcon(processItem->SmallIcon); + processItem->LargeIcon = CopyIcon(processItem->LargeIcon); + } + } + + // Command line + + status = PhOpenProcess( + &processHandle2, + ProcessQueryAccess | PROCESS_VM_READ, + Entry->ProcessId + ); + + if (NT_SUCCESS(status)) + { + PPH_STRING commandLine; + ULONG i; + + if (NT_SUCCESS(status = PhGetProcessCommandLine(processHandle2, &commandLine))) + { + // Some command lines (e.g. from taskeng.exe) have nulls in them. + // Since Windows can't display them, we'll replace them with + // spaces. + for (i = 0; i < (ULONG)commandLine->Length / 2; i++) + { + if (commandLine->Buffer[i] == 0) + commandLine->Buffer[i] = ' '; + } + } + + if (NT_SUCCESS(status)) + { + processItem->CommandLine = commandLine; + } + + NtClose(processHandle2); + } + + // TODO: Other stage 1 tasks. + + PhSetEvent(&processItem->Stage1Event); + + return processItem; +} + +NTSTATUS PhpEnumHiddenProcessesBruteForce( + _In_ PPH_ENUM_HIDDEN_PROCESSES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + PPH_LIST pids; + ULONG pid; + BOOLEAN stop = FALSE; + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + return status; + + pids = PhCreateList(40); + + process = PH_FIRST_PROCESS(processes); + + do + { + PhAddItemList(pids, process->UniqueProcessId); + } while (process = PH_NEXT_PROCESS(process)); + + PhFree(processes); + + for (pid = 8; pid <= 65536; pid += 4) + { + NTSTATUS status2; + HANDLE processHandle; + PH_HIDDEN_PROCESS_ENTRY entry; + KERNEL_USER_TIMES times; + PPH_STRING fileName; + + status2 = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + UlongToHandle(pid) + ); + + if (NT_SUCCESS(status2)) + { + entry.ProcessId = UlongToHandle(pid); + + if (NT_SUCCESS(status2 = PhGetProcessTimes( + processHandle, + × + )) && + NT_SUCCESS(status2 = PhGetProcessImageFileName( + processHandle, + &fileName + ))) + { + entry.FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + + if (times.ExitTime.QuadPart != 0) + entry.Type = TerminatedProcess; + else if (PhFindItemList(pids, UlongToHandle(pid)) != -1) + entry.Type = NormalProcess; + else + entry.Type = HiddenProcess; + + if (!Callback(&entry, Context)) + stop = TRUE; + + PhDereferenceObject(entry.FileName); + } + + NtClose(processHandle); + } + + // Use an alternative method if we don't have sufficient access. + if (status2 == STATUS_ACCESS_DENIED && WindowsVersion >= WINDOWS_VISTA) + { + if (NT_SUCCESS(status2 = PhGetProcessImageFileNameByProcessId(UlongToHandle(pid), &fileName))) + { + entry.ProcessId = UlongToHandle(pid); + entry.FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + + if (PhFindItemList(pids, UlongToHandle(pid)) != -1) + entry.Type = NormalProcess; + else + entry.Type = HiddenProcess; + + if (!Callback(&entry, Context)) + stop = TRUE; + + PhDereferenceObject(entry.FileName); + } + } + + if (status2 == STATUS_INVALID_CID || status2 == STATUS_INVALID_PARAMETER) + status2 = STATUS_SUCCESS; + + if (!NT_SUCCESS(status2)) + { + entry.ProcessId = UlongToHandle(pid); + entry.FileName = NULL; + entry.Type = UnknownProcess; + + if (!Callback(&entry, Context)) + stop = TRUE; + } + + if (stop) + break; + } + + PhDereferenceObject(pids); + + return status; +} + +typedef struct _CSR_HANDLES_CONTEXT +{ + PPH_ENUM_HIDDEN_PROCESSES_CALLBACK Callback; + PVOID Context; + PPH_LIST Pids; +} CSR_HANDLES_CONTEXT, *PCSR_HANDLES_CONTEXT; + +static BOOLEAN NTAPI PhpCsrProcessHandlesCallback( + _In_ PPH_CSR_HANDLE_INFO Handle, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + BOOLEAN cont = TRUE; + PCSR_HANDLES_CONTEXT context = Context; + HANDLE processHandle; + KERNEL_USER_TIMES times; + PPH_STRING fileName; + PH_HIDDEN_PROCESS_ENTRY entry; + + entry.ProcessId = Handle->ProcessId; + + if (NT_SUCCESS(status = PhOpenProcessByCsrHandle( + &processHandle, + ProcessQueryAccess, + Handle + ))) + { + if (NT_SUCCESS(status = PhGetProcessTimes( + processHandle, + × + )) && + NT_SUCCESS(status = PhGetProcessImageFileName( + processHandle, + &fileName + ))) + { + entry.FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + + if (times.ExitTime.QuadPart != 0) + entry.Type = TerminatedProcess; + else if (PhFindItemList(context->Pids, Handle->ProcessId) != -1) + entry.Type = NormalProcess; + else + entry.Type = HiddenProcess; + + if (!context->Callback(&entry, context->Context)) + cont = FALSE; + + PhDereferenceObject(entry.FileName); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + entry.FileName = NULL; + entry.Type = UnknownProcess; + + if (!context->Callback(&entry, context->Context)) + cont = FALSE; + } + + return cont; +} + +NTSTATUS PhpEnumHiddenProcessesCsrHandles( + _In_ PPH_ENUM_HIDDEN_PROCESSES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + PPH_LIST pids; + CSR_HANDLES_CONTEXT context; + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + return status; + + pids = PhCreateList(40); + + process = PH_FIRST_PROCESS(processes); + + do + { + PhAddItemList(pids, process->UniqueProcessId); + } while (process = PH_NEXT_PROCESS(process)); + + PhFree(processes); + + context.Callback = Callback; + context.Context = Context; + context.Pids = pids; + + status = PhEnumCsrProcessHandles(PhpCsrProcessHandlesCallback, &context); + + PhDereferenceObject(pids); + + return status; +} + +NTSTATUS PhEnumHiddenProcesses( + _In_ PH_HIDDEN_PROCESS_METHOD Method, + _In_ PPH_ENUM_HIDDEN_PROCESSES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + if (Method == BruteForceScanMethod) + { + return PhpEnumHiddenProcessesBruteForce( + Callback, + Context + ); + } + else + { + return PhpEnumHiddenProcessesCsrHandles( + Callback, + Context + ); + } +} + +NTSTATUS PhpOpenCsrProcesses( + _Out_ PHANDLE *ProcessHandles, + _Out_ PULONG NumberOfProcessHandles + ) +{ + NTSTATUS status; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + PPH_LIST processHandleList; + + if (!NT_SUCCESS(status = PhEnumProcesses(&processes))) + return status; + + processHandleList = PhCreateList(8); + + process = PH_FIRST_PROCESS(processes); + + do + { + HANDLE processHandle; + PH_KNOWN_PROCESS_TYPE knownProcessType; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_DUP_HANDLE, + process->UniqueProcessId + ))) + { + if (NT_SUCCESS(PhGetProcessKnownType( + processHandle, + &knownProcessType + )) && + (knownProcessType & KnownProcessTypeMask) == WindowsSubsystemProcessType) + { + PhAddItemList(processHandleList, processHandle); + } + else + { + NtClose(processHandle); + } + } + } while (process = PH_NEXT_PROCESS(process)); + + PhFree(processes); + + *ProcessHandles = PhAllocateCopy(processHandleList->Items, processHandleList->Count * sizeof(HANDLE)); + *NumberOfProcessHandles = processHandleList->Count; + + PhDereferenceObject(processHandleList); + + return status; +} + +NTSTATUS PhpGetCsrHandleProcessId( + _Inout_ PPH_CSR_HANDLE_INFO Handle + ) +{ + NTSTATUS status; + PROCESS_BASIC_INFORMATION processBasicInfo; + THREAD_BASIC_INFORMATION threadBasicInfo; + + Handle->IsThreadHandle = FALSE; + Handle->ProcessId = NULL; + + // Assume the handle is a process handle, and get the + // process ID. + + status = KphQueryInformationObject( + Handle->CsrProcessHandle, + Handle->Handle, + KphObjectProcessBasicInformation, + &processBasicInfo, + sizeof(PROCESS_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + Handle->ProcessId = processBasicInfo.UniqueProcessId; + } + else + { + // We failed to get the process ID. Assume the handle + // is a thread handle, and get the process ID. + + status = KphQueryInformationObject( + Handle->CsrProcessHandle, + Handle->Handle, + KphObjectThreadBasicInformation, + &threadBasicInfo, + sizeof(THREAD_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + Handle->ProcessId = threadBasicInfo.ClientId.UniqueProcess; + Handle->IsThreadHandle = TRUE; + } + } + + return status; +} + +NTSTATUS PhEnumCsrProcessHandles( + _In_ PPH_ENUM_CSR_PROCESS_HANDLES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PHANDLE csrProcessHandles; + ULONG numberOfCsrProcessHandles; + ULONG i; + BOOLEAN stop = FALSE; + PPH_LIST pids; + + if (!NT_SUCCESS(status = PhpOpenCsrProcesses( + &csrProcessHandles, + &numberOfCsrProcessHandles + ))) + return status; + + pids = PhCreateList(40); + + for (i = 0; i < numberOfCsrProcessHandles; i++) + { + PKPH_PROCESS_HANDLE_INFORMATION handles; + ULONG j; + + if (stop) + break; + + if (NT_SUCCESS(KphEnumerateProcessHandles2(csrProcessHandles[i], &handles))) + { + for (j = 0; j < handles->HandleCount; j++) + { + PH_CSR_HANDLE_INFO handle; + + handle.CsrProcessHandle = csrProcessHandles[i]; + handle.Handle = handles->Handles[j].Handle; + + // Get the process ID associated with the handle. + // This call will fail if the handle is not a + // process or thread handle. + if (!NT_SUCCESS(PhpGetCsrHandleProcessId(&handle))) + continue; + + // Avoid duplicate PIDs. + if (PhFindItemList(pids, handle.ProcessId) != -1) + continue; + + PhAddItemList(pids, handle.ProcessId); + + if (!Callback(&handle, Context)) + { + stop = TRUE; + break; + } + } + + PhFree(handles); + } + } + + PhDereferenceObject(pids); + + for (i = 0; i < numberOfCsrProcessHandles; i++) + NtClose(csrProcessHandles[i]); + + PhFree(csrProcessHandles); + + return status; +} + +NTSTATUS PhOpenProcessByCsrHandle( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PPH_CSR_HANDLE_INFO Handle + ) +{ + NTSTATUS status; + + if (!Handle->IsThreadHandle) + { + status = NtDuplicateObject( + Handle->CsrProcessHandle, + Handle->Handle, + NtCurrentProcess(), + ProcessHandle, + DesiredAccess, + 0, + 0 + ); + } + else + { + HANDLE threadHandle; + + if (!NT_SUCCESS(status = NtDuplicateObject( + Handle->CsrProcessHandle, + Handle->Handle, + NtCurrentProcess(), + &threadHandle, + ThreadQueryAccess, + 0, + 0 + ))) + return status; + + status = KphOpenThreadProcess( + threadHandle, + DesiredAccess, + ProcessHandle + ); + NtClose(threadHandle); + } + + return status; +} + +typedef struct _OPEN_PROCESS_BY_CSR_CONTEXT +{ + NTSTATUS Status; + PHANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + HANDLE ProcessId; +} OPEN_PROCESS_BY_CSR_CONTEXT, *POPEN_PROCESS_BY_CSR_CONTEXT; + +static BOOLEAN NTAPI PhpOpenProcessByCsrHandlesCallback( + _In_ PPH_CSR_HANDLE_INFO Handle, + _In_opt_ PVOID Context + ) +{ + POPEN_PROCESS_BY_CSR_CONTEXT context = Context; + + if (Handle->ProcessId == context->ProcessId) + { + context->Status = PhOpenProcessByCsrHandle( + context->ProcessHandle, + context->DesiredAccess, + Handle + ); + + return FALSE; + } + + return TRUE; +} + +NTSTATUS PhOpenProcessByCsrHandles( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ) +{ + NTSTATUS status; + OPEN_PROCESS_BY_CSR_CONTEXT context; + + context.Status = STATUS_INVALID_CID; + context.ProcessHandle = ProcessHandle; + context.DesiredAccess = DesiredAccess; + context.ProcessId = ProcessId; + + if (!NT_SUCCESS(status = PhEnumCsrProcessHandles( + PhpOpenProcessByCsrHandlesCallback, + &context + ))) + return status; + + return context.Status; +} diff --git a/ProcessHacker/hndllist.c b/ProcessHacker/hndllist.c new file mode 100644 index 0000000..3152f5b --- /dev/null +++ b/ProcessHacker/hndllist.c @@ -0,0 +1,751 @@ +/* + * Process Hacker - + * handle list + * + * Copyright (C) 2011-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhpHandleNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG PhpHandleNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpDestroyHandleNode( + _In_ PPH_HANDLE_NODE HandleNode + ); + +VOID PhpRemoveHandleNode( + _In_ PPH_HANDLE_NODE HandleNode, + _In_ PPH_HANDLE_LIST_CONTEXT Context + ); + +LONG PhpHandleTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpHandleTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID PhInitializeHandleList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + HWND hwnd; + + memset(Context, 0, sizeof(PH_HANDLE_LIST_CONTEXT)); + Context->EnableStateHighlighting = TRUE; + + Context->NodeHashtable = PhCreateHashtable( + sizeof(PPH_HANDLE_NODE), + PhpHandleNodeHashtableEqualFunction, + PhpHandleNodeHashtableHashFunction, + 100 + ); + Context->NodeList = PhCreateList(100); + + Context->ParentWindowHandle = ParentWindowHandle; + Context->TreeNewHandle = TreeNewHandle; + hwnd = TreeNewHandle; + PhSetControlTheme(hwnd, L"explorer"); + + TreeNew_SetCallback(hwnd, PhpHandleTreeNewCallback, Context); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHHNTLC_TYPE, TRUE, L"Type", 100, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumn(hwnd, PHHNTLC_NAME, TRUE, L"Name", 200, PH_ALIGN_LEFT, 1, 0); + PhAddTreeNewColumn(hwnd, PHHNTLC_HANDLE, TRUE, L"Handle", 80, PH_ALIGN_LEFT, 2, 0); + + PhAddTreeNewColumn(hwnd, PHHNTLC_OBJECTADDRESS, FALSE, L"Object address", 80, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHHNTLC_ATTRIBUTES, FALSE, L"Attributes", 120, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumn(hwnd, PHHNTLC_GRANTEDACCESS, FALSE, L"Granted access", 80, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHHNTLC_GRANTEDACCESSSYMBOLIC, FALSE, L"Granted access (symbolic)", 140, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHHNTLC_ORIGINALNAME, FALSE, L"Original name", 200, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHHNTLC_FILESHAREACCESS, FALSE, L"File share access", 50, PH_ALIGN_LEFT, -1, 0, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetSort(hwnd, 0, AscendingSortOrder); + + PhCmInitializeManager(&Context->Cm, hwnd, PHHNTLC_MAXIMUM, PhpHandleTreeNewPostSortFunction); +} + +VOID PhDeleteHandleList( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + ULONG i; + + PhCmDeleteManager(&Context->Cm); + + for (i = 0; i < Context->NodeList->Count; i++) + PhpDestroyHandleNode(Context->NodeList->Items[i]); + + PhDereferenceObject(Context->NodeHashtable); + PhDereferenceObject(Context->NodeList); +} + +BOOLEAN PhpHandleNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_HANDLE_NODE handleNode1 = *(PPH_HANDLE_NODE *)Entry1; + PPH_HANDLE_NODE handleNode2 = *(PPH_HANDLE_NODE *)Entry2; + + return handleNode1->Handle == handleNode2->Handle; +} + +ULONG PhpHandleNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return HandleToUlong((*(PPH_HANDLE_NODE *)Entry)->Handle) / 4; +} + +VOID PhLoadSettingsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"HandleTreeListColumns"); + sortSettings = PhGetStringSetting(L"HandleTreeListSort"); + PhCmLoadSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSaveSettingsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &sortSettings); + PhSetStringSetting2(L"HandleTreeListColumns", &settings->sr); + PhSetStringSetting2(L"HandleTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSetOptionsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ BOOLEAN HideUnnamedHandles + ) +{ + ULONG i; + BOOLEAN modified; + + if (Context->HideUnnamedHandles != HideUnnamedHandles) + { + Context->HideUnnamedHandles = HideUnnamedHandles; + + modified = FALSE; + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_HANDLE_NODE node = Context->NodeList->Items[i]; + BOOLEAN visible; + + visible = TRUE; + + if (HideUnnamedHandles && PhIsNullOrEmptyString(node->HandleItem->BestObjectName)) + visible = FALSE; + + if (node->Node.Visible != visible) + { + node->Node.Visible = visible; + modified = TRUE; + + if (!visible) + node->Node.Selected = FALSE; + } + } + + if (modified) + { + TreeNew_NodesStructured(Context->TreeNewHandle); + } + } +} + +PPH_HANDLE_NODE PhAddHandleNode( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_ITEM HandleItem, + _In_ ULONG RunId + ) +{ + PPH_HANDLE_NODE handleNode; + + handleNode = PhAllocate(PhEmGetObjectSize(EmHandleNodeType, sizeof(PH_HANDLE_NODE))); + memset(handleNode, 0, sizeof(PH_HANDLE_NODE)); + PhInitializeTreeNewNode(&handleNode->Node); + + if (Context->EnableStateHighlighting && RunId != 1) + { + PhChangeShStateTn( + &handleNode->Node, + &handleNode->ShState, + &Context->NodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + handleNode->Handle = HandleItem->Handle; + handleNode->HandleItem = HandleItem; + PhReferenceObject(HandleItem); + + memset(handleNode->TextCache, 0, sizeof(PH_STRINGREF) * PHHNTLC_MAXIMUM); + handleNode->Node.TextCache = handleNode->TextCache; + handleNode->Node.TextCacheSize = PHHNTLC_MAXIMUM; + + PhAddEntryHashtable(Context->NodeHashtable, &handleNode); + PhAddItemList(Context->NodeList, handleNode); + + if (Context->HideUnnamedHandles && PhIsNullOrEmptyString(HandleItem->BestObjectName)) + handleNode->Node.Visible = FALSE; + + PhEmCallObjectOperation(EmHandleNodeType, handleNode, EmObjectCreate); + + TreeNew_NodesStructured(Context->TreeNewHandle); + + return handleNode; +} + +PPH_HANDLE_NODE PhFindHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ HANDLE Handle + ) +{ + PH_HANDLE_NODE lookupHandleNode; + PPH_HANDLE_NODE lookupHandleNodePtr = &lookupHandleNode; + PPH_HANDLE_NODE *handleNode; + + lookupHandleNode.Handle = Handle; + + handleNode = (PPH_HANDLE_NODE *)PhFindEntryHashtable( + Context->NodeHashtable, + &lookupHandleNodePtr + ); + + if (handleNode) + return *handleNode; + else + return NULL; +} + +VOID PhRemoveHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_NODE HandleNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashtable(Context->NodeHashtable, &HandleNode); + + if (Context->EnableStateHighlighting) + { + PhChangeShStateTn( + &HandleNode->Node, + &HandleNode->ShState, + &Context->NodeStateList, + RemovingItemState, + PhCsColorRemoved, + Context->TreeNewHandle + ); + } + else + { + PhpRemoveHandleNode(HandleNode, Context); + } +} + +VOID PhpDestroyHandleNode( + _In_ PPH_HANDLE_NODE HandleNode + ) +{ + PhEmCallObjectOperation(EmHandleNodeType, HandleNode, EmObjectDelete); + + if (HandleNode->GrantedAccessSymbolicText) PhDereferenceObject(HandleNode->GrantedAccessSymbolicText); + + PhDereferenceObject(HandleNode->HandleItem); + + PhFree(HandleNode); +} + +VOID PhpRemoveHandleNode( + _In_ PPH_HANDLE_NODE HandleNode, + _In_ PPH_HANDLE_LIST_CONTEXT Context // PH_TICK_SH_STATE requires this parameter to be after HandleNode + ) +{ + ULONG index; + + // Remove from list and cleanup. + + if ((index = PhFindItemList(Context->NodeList, HandleNode)) != -1) + PhRemoveItemList(Context->NodeList, index); + + PhpDestroyHandleNode(HandleNode); + + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhUpdateHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_NODE HandleNode + ) +{ + memset(HandleNode->TextCache, 0, sizeof(PH_STRINGREF) * PHHNTLC_MAXIMUM); + + PhInvalidateTreeNewNode(&HandleNode->Node, TN_CACHE_COLOR); + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhTickHandleNodes( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + PH_TICK_SH_STATE_TN(PH_HANDLE_NODE, ShState, Context->NodeStateList, PhpRemoveHandleNode, PhCsHighlightingDuration, Context->TreeNewHandle, TRUE, NULL, Context); +} + +#define SORT_FUNCTION(Column) PhpHandleTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpHandleTreeNewCompare##Column( \ + _In_ void *_context, \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_HANDLE_NODE node1 = *(PPH_HANDLE_NODE *)_elem1; \ + PPH_HANDLE_NODE node2 = *(PPH_HANDLE_NODE *)_elem2; \ + PPH_HANDLE_ITEM handleItem1 = node1->HandleItem; \ + PPH_HANDLE_ITEM handleItem2 = node2->HandleItem; \ + PPH_HANDLE_LIST_CONTEXT context = (PPH_HANDLE_LIST_CONTEXT)_context; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = uintptrcmp((ULONG_PTR)node1->Handle, (ULONG_PTR)node2->Handle); \ + \ + return PhModifySort(sortResult, context->TreeNewSortOrder); \ +} + +LONG PhpHandleTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = uintptrcmp((ULONG_PTR)((PPH_HANDLE_NODE)Node1)->Handle, (ULONG_PTR)((PPH_HANDLE_NODE)Node2)->Handle); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(Type) +{ + sortResult = PhCompareString(handleItem1->TypeName, handleItem2->TypeName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Name) +{ + sortResult = PhCompareStringWithNull(handleItem1->BestObjectName, handleItem2->BestObjectName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Handle) +{ + sortResult = uintptrcmp((ULONG_PTR)node1->Handle, (ULONG_PTR)node2->Handle); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ObjectAddress) +{ + sortResult = uintptrcmp((ULONG_PTR)handleItem1->Object, (ULONG_PTR)handleItem2->Object); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Attributes) +{ + sortResult = uintcmp(handleItem1->Attributes, handleItem2->Attributes); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(GrantedAccess) +{ + sortResult = uintcmp(handleItem1->GrantedAccess, handleItem2->GrantedAccess); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(OriginalName) +{ + sortResult = PhCompareStringWithNull(handleItem1->ObjectName, handleItem2->ObjectName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileShareAccess) +{ + sortResult = uintcmp(handleItem1->FileFlags & (PH_HANDLE_FILE_SHARED_MASK), handleItem2->FileFlags & (PH_HANDLE_FILE_SHARED_MASK)); + + // Make sure all file handles get grouped together regardless of share access. + if (sortResult == 0) + sortResult = intcmp(PhEqualString2(handleItem1->TypeName, L"File", TRUE), PhEqualString2(handleItem2->TypeName, L"File", TRUE)); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpHandleTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_HANDLE_LIST_CONTEXT context; + PPH_HANDLE_NODE node; + + context = Context; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &context->Cm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Type), + SORT_FUNCTION(Name), + SORT_FUNCTION(Handle), + SORT_FUNCTION(ObjectAddress), + SORT_FUNCTION(Attributes), + SORT_FUNCTION(GrantedAccess), + SORT_FUNCTION(GrantedAccess), // Granted Access (Symbolic) + SORT_FUNCTION(OriginalName), + SORT_FUNCTION(FileShareAccess) + }; + int (__cdecl *sortFunction)(void *, const void *, const void *); + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)context->NodeList->Items, + context->NodeList->Count, + context->TreeNewSortColumn, + context->TreeNewSortOrder, + &context->Cm + )) + { + if (context->TreeNewSortColumn < PHHNTLC_MAXIMUM) + sortFunction = sortFunctions[context->TreeNewSortColumn]; + else + sortFunction = NULL; + } + else + { + sortFunction = NULL; + } + + if (sortFunction) + { + qsort_s(context->NodeList->Items, context->NodeList->Count, sizeof(PVOID), sortFunction, context); + } + + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeList->Items; + getChildren->NumberOfChildren = context->NodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_HANDLE_ITEM handleItem; + + node = (PPH_HANDLE_NODE)getCellText->Node; + handleItem = node->HandleItem; + + switch (getCellText->Id) + { + case PHHNTLC_TYPE: + getCellText->Text = handleItem->TypeName->sr; + break; + case PHHNTLC_NAME: + getCellText->Text = PhGetStringRef(handleItem->BestObjectName); + break; + case PHHNTLC_HANDLE: + PhInitializeStringRefLongHint(&getCellText->Text, handleItem->HandleString); + break; + case PHHNTLC_OBJECTADDRESS: + PhInitializeStringRefLongHint(&getCellText->Text, handleItem->ObjectString); + break; + case PHHNTLC_ATTRIBUTES: + switch (handleItem->Attributes & (OBJ_PROTECT_CLOSE | OBJ_INHERIT)) + { + case OBJ_PROTECT_CLOSE: + PhInitializeStringRef(&getCellText->Text, L"Protected"); + break; + case OBJ_INHERIT: + PhInitializeStringRef(&getCellText->Text, L"Inherit"); + break; + case OBJ_PROTECT_CLOSE | OBJ_INHERIT: + PhInitializeStringRef(&getCellText->Text, L"Protected, Inherit"); + break; + } + break; + case PHHNTLC_GRANTEDACCESS: + PhInitializeStringRefLongHint(&getCellText->Text, handleItem->GrantedAccessString); + break; + case PHHNTLC_GRANTEDACCESSSYMBOLIC: + if (handleItem->GrantedAccess != 0) + { + if (!node->GrantedAccessSymbolicText) + { + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + if (PhGetAccessEntries(handleItem->TypeName->Buffer, &accessEntries, &numberOfAccessEntries)) + { + node->GrantedAccessSymbolicText = PhGetAccessString(handleItem->GrantedAccess, accessEntries, numberOfAccessEntries); + PhFree(accessEntries); + } + else + { + node->GrantedAccessSymbolicText = PhReferenceEmptyString(); + } + } + + if (node->GrantedAccessSymbolicText->Length != 0) + getCellText->Text = node->GrantedAccessSymbolicText->sr; + else + PhInitializeStringRefLongHint(&getCellText->Text, handleItem->GrantedAccessString); + } + break; + case PHHNTLC_ORIGINALNAME: + getCellText->Text = PhGetStringRef(handleItem->ObjectName); + break; + case PHHNTLC_FILESHAREACCESS: + if (handleItem->FileFlags & PH_HANDLE_FILE_SHARED_MASK) + { + node->FileShareAccessText[0] = '-'; + node->FileShareAccessText[1] = '-'; + node->FileShareAccessText[2] = '-'; + node->FileShareAccessText[3] = 0; + + if (handleItem->FileFlags & PH_HANDLE_FILE_SHARED_READ) + node->FileShareAccessText[0] = 'R'; + if (handleItem->FileFlags & PH_HANDLE_FILE_SHARED_WRITE) + node->FileShareAccessText[1] = 'W'; + if (handleItem->FileFlags & PH_HANDLE_FILE_SHARED_DELETE) + node->FileShareAccessText[2] = 'D'; + + PhInitializeStringRef(&getCellText->Text, node->FileShareAccessText); + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + PPH_HANDLE_ITEM handleItem; + + node = (PPH_HANDLE_NODE)getNodeColor->Node; + handleItem = node->HandleItem; + + if (!handleItem) + ; // Dummy + else if (PhCsUseColorProtectedHandles && (handleItem->Attributes & OBJ_PROTECT_CLOSE)) + getNodeColor->BackColor = PhCsColorProtectedHandles; + else if (PhCsUseColorInheritHandles && (handleItem->Attributes & OBJ_INHERIT)) + getNodeColor->BackColor = PhCsColorInheritHandles; + + getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR; + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_HANDLE_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(context->TreeNewHandle, 0, -1); + break; + case VK_DELETE: + // Pass a 1 in lParam to indicate that warnings should be enabled. + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_HANDLE_CLOSE, 1); + break; + case VK_RETURN: + if (GetKeyState(VK_CONTROL) >= 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_HANDLE_PROPERTIES, 0); + else + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_HANDLE_OBJECTPROPERTIES1, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = AscendingSortOrder; + PhInitializeTreeNewColumnMenu(&data); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_HANDLE_PROPERTIES, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_SHOWCONTEXTMENU, (LPARAM)contextMenu); + } + return TRUE; + case TreeNewGetDialogCode: + { + PULONG code = Parameter2; + + if (PtrToUlong(Parameter1) == VK_RETURN) + { + *code = DLGC_WANTMESSAGE; + return TRUE; + } + } + return FALSE; + } + + return FALSE; +} + +PPH_HANDLE_ITEM PhGetSelectedHandleItem( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + PPH_HANDLE_ITEM handleItem = NULL; + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_HANDLE_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + { + handleItem = node->HandleItem; + break; + } + } + + return handleItem; +} + +VOID PhGetSelectedHandleItems( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _Out_ PPH_HANDLE_ITEM **Handles, + _Out_ PULONG NumberOfHandles + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_HANDLE_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->HandleItem); + } + + *NumberOfHandles = (ULONG)array.Count; + *Handles = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllHandleNodes( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ) +{ + TreeNew_DeselectRange(Context->TreeNewHandle, 0, -1); +} diff --git a/ProcessHacker/hndlmenu.c b/ProcessHacker/hndlmenu.c new file mode 100644 index 0000000..dcbe4e5 --- /dev/null +++ b/ProcessHacker/hndlmenu.c @@ -0,0 +1,377 @@ +/* + * Process Hacker - + * common handle menu items + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +VOID PhInsertHandleObjectPropertiesEMenuItems( + _In_ struct _PH_EMENU_ITEM *Menu, + _In_ ULONG InsertBeforeId, + _In_ BOOLEAN EnableShortcut, + _In_ PPH_HANDLE_ITEM_INFO Info + ) +{ + PPH_EMENU_ITEM parentItem; + ULONG indexInParent; + + if (!PhFindEMenuItemEx(Menu, 0, NULL, InsertBeforeId, &parentItem, &indexInParent)) + return; + + if (PhEqualString2(Info->TypeName, L"File", TRUE) || PhEqualString2(Info->TypeName, L"DLL", TRUE) || + PhEqualString2(Info->TypeName, L"Mapped file", TRUE) || PhEqualString2(Info->TypeName, L"Mapped image", TRUE)) + { + if (PhEqualString2(Info->TypeName, L"File", TRUE)) + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES2, L"File properties", NULL, NULL), indexInParent); + + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES1, PhaAppendCtrlEnter(L"Open &file location", EnableShortcut), NULL, NULL), indexInParent); + } + else if (PhEqualString2(Info->TypeName, L"Key", TRUE)) + { + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES1, PhaAppendCtrlEnter(L"Open key", EnableShortcut), NULL, NULL), indexInParent); + } + else if (PhEqualString2(Info->TypeName, L"Process", TRUE)) + { + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES1, PhaAppendCtrlEnter(L"Process properties", EnableShortcut), NULL, NULL), indexInParent); + } + else if (PhEqualString2(Info->TypeName, L"Section", TRUE)) + { + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES1, PhaAppendCtrlEnter(L"Read/Write memory", EnableShortcut), NULL, NULL), indexInParent); + } + else if (PhEqualString2(Info->TypeName, L"Thread", TRUE)) + { + PhInsertEMenuItem(parentItem, PhCreateEMenuItem(0, ID_HANDLE_OBJECTPROPERTIES1, PhaAppendCtrlEnter(L"Go to thread", EnableShortcut), NULL, NULL), indexInParent); + } +} + +static NTSTATUS PhpDuplicateHandleFromProcessItem( + _Out_ PHANDLE NewHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId, + _In_ HANDLE Handle + ) +{ + NTSTATUS status; + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + ProcessId + ))) + return status; + + status = NtDuplicateObject( + processHandle, + Handle, + NtCurrentProcess(), + NewHandle, + DesiredAccess, + 0, + 0 + ); + NtClose(processHandle); + + return status; +} + +static VOID PhpShowProcessPropContext( + _In_ PVOID Parameter + ) +{ + PhShowProcessProperties(Parameter); + PhDereferenceObject(Parameter); +} + +VOID PhShowHandleObjectProperties1( + _In_ HWND hWnd, + _In_ PPH_HANDLE_ITEM_INFO Info + ) +{ + if (PhEqualString2(Info->TypeName, L"File", TRUE) || PhEqualString2(Info->TypeName, L"DLL", TRUE) || + PhEqualString2(Info->TypeName, L"Mapped file", TRUE) || PhEqualString2(Info->TypeName, L"Mapped image", TRUE)) + { + if (Info->BestObjectName) + PhShellExploreFile(hWnd, Info->BestObjectName->Buffer); + else + PhShowError(hWnd, L"Unable to open file location because the object is unnamed."); + } + else if (PhEqualString2(Info->TypeName, L"Key", TRUE)) + { + if (Info->BestObjectName) + PhShellOpenKey2(hWnd, Info->BestObjectName); + else + PhShowError(hWnd, L"Unable to open key because the object is unnamed."); + } + else if (PhEqualString2(Info->TypeName, L"Process", TRUE)) + { + HANDLE processHandle; + HANDLE processId; + PPH_PROCESS_ITEM targetProcessItem; + + processId = NULL; + + if (KphIsConnected()) + { + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Info->ProcessId + ))) + { + PROCESS_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(KphQueryInformationObject( + processHandle, + Info->Handle, + KphObjectProcessBasicInformation, + &basicInfo, + sizeof(PROCESS_BASIC_INFORMATION), + NULL + ))) + { + processId = basicInfo.UniqueProcessId; + } + + NtClose(processHandle); + } + } + else + { + HANDLE handle; + PROCESS_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhpDuplicateHandleFromProcessItem( + &handle, + ProcessQueryAccess, + Info->ProcessId, + Info->Handle + ))) + { + if (NT_SUCCESS(PhGetProcessBasicInformation(handle, &basicInfo))) + processId = basicInfo.UniqueProcessId; + + NtClose(handle); + } + } + + if (processId) + { + targetProcessItem = PhReferenceProcessItem(processId); + + if (targetProcessItem) + { + ProcessHacker_ShowProcessProperties(PhMainWndHandle, targetProcessItem); + PhDereferenceObject(targetProcessItem); + } + else + { + PhShowError(hWnd, L"The process does not exist."); + } + } + } + else if (PhEqualString2(Info->TypeName, L"Section", TRUE)) + { + HANDLE handle = NULL; + BOOLEAN readOnly = FALSE; + + if (!NT_SUCCESS(PhpDuplicateHandleFromProcessItem( + &handle, + SECTION_QUERY | SECTION_MAP_READ | SECTION_MAP_WRITE, + Info->ProcessId, + Info->Handle + ))) + { + PhpDuplicateHandleFromProcessItem( + &handle, + SECTION_QUERY | SECTION_MAP_READ, + Info->ProcessId, + Info->Handle + ); + readOnly = TRUE; + } + + if (handle) + { + NTSTATUS status; + PPH_STRING sectionName = NULL; + SECTION_BASIC_INFORMATION basicInfo; + SIZE_T viewSize = PH_MAX_SECTION_EDIT_SIZE; + PVOID viewBase = NULL; + BOOLEAN tooBig = FALSE; + + PhGetHandleInformation(NtCurrentProcess(), handle, -1, NULL, NULL, NULL, §ionName); + + if (NT_SUCCESS(PhGetSectionBasicInformation(handle, &basicInfo))) + { + if (basicInfo.MaximumSize.QuadPart <= PH_MAX_SECTION_EDIT_SIZE) + viewSize = (SIZE_T)basicInfo.MaximumSize.QuadPart; + else + tooBig = TRUE; + + status = NtMapViewOfSection( + handle, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + readOnly ? PAGE_READONLY : PAGE_READWRITE + ); + + if (status == STATUS_SECTION_PROTECTION && !readOnly) + { + status = NtMapViewOfSection( + handle, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ); + } + + if (NT_SUCCESS(status)) + { + PPH_SHOWMEMORYEDITOR showMemoryEditor = PhAllocate(sizeof(PH_SHOWMEMORYEDITOR)); + + if (tooBig) + PhShowWarning(hWnd, L"The section size is greater than 32 MB. Only the first 32 MB will be available for editing."); + + memset(showMemoryEditor, 0, sizeof(PH_SHOWMEMORYEDITOR)); + showMemoryEditor->ProcessId = NtCurrentProcessId(); + showMemoryEditor->BaseAddress = viewBase; + showMemoryEditor->RegionSize = viewSize; + showMemoryEditor->SelectOffset = -1; + showMemoryEditor->SelectLength = 0; + showMemoryEditor->Title = sectionName ? PhConcatStrings2(L"Section - ", sectionName->Buffer) : PhCreateString(L"Section"); + showMemoryEditor->Flags = PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION; + ProcessHacker_ShowMemoryEditor(PhMainWndHandle, showMemoryEditor); + } + else + { + PhShowStatus(hWnd, L"Unable to map a view of the section", status, 0); + } + } + + PhClearReference(§ionName); + + NtClose(handle); + } + } + else if (PhEqualString2(Info->TypeName, L"Thread", TRUE)) + { + HANDLE processHandle; + CLIENT_ID clientId; + PPH_PROCESS_ITEM targetProcessItem; + PPH_PROCESS_PROPCONTEXT propContext; + + clientId.UniqueProcess = NULL; + clientId.UniqueThread = NULL; + + if (KphIsConnected()) + { + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Info->ProcessId + ))) + { + THREAD_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(KphQueryInformationObject( + processHandle, + Info->Handle, + KphObjectThreadBasicInformation, + &basicInfo, + sizeof(THREAD_BASIC_INFORMATION), + NULL + ))) + { + clientId = basicInfo.ClientId; + } + + NtClose(processHandle); + } + } + else + { + HANDLE handle; + THREAD_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhpDuplicateHandleFromProcessItem( + &handle, + ThreadQueryAccess, + Info->ProcessId, + Info->Handle + ))) + { + if (NT_SUCCESS(PhGetThreadBasicInformation(handle, &basicInfo))) + clientId = basicInfo.ClientId; + + NtClose(handle); + } + } + + if (clientId.UniqueProcess) + { + targetProcessItem = PhReferenceProcessItem(clientId.UniqueProcess); + + if (targetProcessItem) + { + propContext = PhCreateProcessPropContext(PhMainWndHandle, targetProcessItem); + PhDereferenceObject(targetProcessItem); + PhSetSelectThreadIdProcessPropContext(propContext, clientId.UniqueThread); + ProcessHacker_Invoke(PhMainWndHandle, PhpShowProcessPropContext, propContext); + } + else + { + PhShowError(hWnd, L"The process does not exist."); + } + } + } +} + +VOID PhShowHandleObjectProperties2( + _In_ HWND hWnd, + _In_ PPH_HANDLE_ITEM_INFO Info + ) +{ + if (PhEqualString2(Info->TypeName, L"File", TRUE) || PhEqualString2(Info->TypeName, L"DLL", TRUE) || + PhEqualString2(Info->TypeName, L"Mapped file", TRUE) || PhEqualString2(Info->TypeName, L"Mapped image", TRUE)) + { + if (Info->BestObjectName) + PhShellProperties(hWnd, Info->BestObjectName->Buffer); + else + PhShowError(hWnd, L"Unable to open file properties because the object is unnamed."); + } +} diff --git a/ProcessHacker/hndlprp.c b/ProcessHacker/hndlprp.c new file mode 100644 index 0000000..3a48ad4 --- /dev/null +++ b/ProcessHacker/hndlprp.c @@ -0,0 +1,331 @@ +/* + * Process Hacker - + * handle properties + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +typedef struct _HANDLE_PROPERTIES_CONTEXT +{ + HANDLE ProcessId; + PPH_HANDLE_ITEM HandleItem; +} HANDLE_PROPERTIES_CONTEXT, *PHANDLE_PROPERTIES_CONTEXT; + +INT_PTR CALLBACK PhpHandleGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static NTSTATUS PhpDuplicateHandleFromProcess( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PHANDLE_PROPERTIES_CONTEXT context = Context; + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + context->ProcessId + ))) + return status; + + status = NtDuplicateObject( + processHandle, + context->HandleItem->Handle, + NtCurrentProcess(), + Handle, + DesiredAccess, + 0, + 0 + ); + NtClose(processHandle); + + return status; +} + +VOID PhShowHandleProperties( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM HandleItem + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[16]; + HANDLE_PROPERTIES_CONTEXT context; + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + context.ProcessId = ProcessId; + context.HandleItem = HandleItem; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = L"Handle"; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // General page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_HNDLGENERAL); + propSheetPage.pfnDlgProc = PhpHandleGeneralDlgProc; + propSheetPage.lParam = (LPARAM)&context; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Object-specific page + if (!HandleItem->TypeName) + { + // Dummy + } + else if (PhEqualString2(HandleItem->TypeName, L"Event", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateEventPage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"EventPair", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateEventPairPage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Job", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateJobPage( + PhpDuplicateHandleFromProcess, + &context, + NULL + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Mutant", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateMutantPage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Section", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateSectionPage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Semaphore", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateSemaphorePage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Timer", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateTimerPage( + PhpDuplicateHandleFromProcess, + &context + ); + } + else if (PhEqualString2(HandleItem->TypeName, L"Token", TRUE)) + { + pages[propSheetHeader.nPages++] = PhCreateTokenPage( + PhpDuplicateHandleFromProcess, + &context, + NULL + ); + } + + // Security page + stdObjectSecurity.OpenObject = PhpDuplicateHandleFromProcess; + stdObjectSecurity.ObjectType = HandleItem->TypeName->Buffer; + stdObjectSecurity.Context = &context; + + if (PhGetAccessEntries(HandleItem->TypeName->Buffer, &accessEntries, &numberOfAccessEntries)) + { + pages[propSheetHeader.nPages++] = PhCreateSecurityPage( + PhGetStringOrEmpty(HandleItem->BestObjectName), + PhStdGetObjectSecurity, + PhStdSetObjectSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + + if (PhPluginsEnabled) + { + PH_PLUGIN_OBJECT_PROPERTIES objectProperties; + PH_PLUGIN_HANDLE_PROPERTIES_CONTEXT propertiesContext; + + propertiesContext.ProcessId = ProcessId; + propertiesContext.HandleItem = HandleItem; + + objectProperties.Parameter = &propertiesContext; + objectProperties.NumberOfPages = propSheetHeader.nPages; + objectProperties.MaximumNumberOfPages = sizeof(pages) / sizeof(HPROPSHEETPAGE); + objectProperties.Pages = pages; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackHandlePropertiesInitializing), &objectProperties); + + propSheetHeader.nPages = objectProperties.NumberOfPages; + } + + PhModalPropertySheet(&propSheetHeader); +} + +INT_PTR CALLBACK PhpHandleGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PHANDLE_PROPERTIES_CONTEXT context = (PHANDLE_PROPERTIES_CONTEXT)propSheetPage->lParam; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + HANDLE processHandle; + OBJECT_BASIC_INFORMATION basicInfo; + BOOLEAN haveBasicInfo = FALSE; + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + + SetDlgItemText(hwndDlg, IDC_NAME, PhGetString(context->HandleItem->BestObjectName)); + SetDlgItemText(hwndDlg, IDC_TYPE, context->HandleItem->TypeName->Buffer); + SetDlgItemText(hwndDlg, IDC_ADDRESS, context->HandleItem->ObjectString); + + if (PhGetAccessEntries( + context->HandleItem->TypeName->Buffer, + &accessEntries, + &numberOfAccessEntries + )) + { + PPH_STRING accessString; + PPH_STRING grantedAccessString; + + accessString = PH_AUTO(PhGetAccessString( + context->HandleItem->GrantedAccess, + accessEntries, + numberOfAccessEntries + )); + + if (accessString->Length != 0) + { + grantedAccessString = PhaFormatString( + L"%s (%s)", + context->HandleItem->GrantedAccessString, + accessString->Buffer + ); + SetDlgItemText(hwndDlg, IDC_GRANTED_ACCESS, grantedAccessString->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_GRANTED_ACCESS, context->HandleItem->GrantedAccessString); + } + + PhFree(accessEntries); + } + else + { + SetDlgItemText(hwndDlg, IDC_GRANTED_ACCESS, context->HandleItem->GrantedAccessString); + } + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + context->ProcessId + ))) + { + if (NT_SUCCESS(PhGetHandleInformation( + processHandle, + context->HandleItem->Handle, + -1, + &basicInfo, + NULL, + NULL, + NULL + ))) + { + SetDlgItemInt(hwndDlg, IDC_REFERENCES, basicInfo.PointerCount, FALSE); + SetDlgItemInt(hwndDlg, IDC_HANDLES, basicInfo.HandleCount, FALSE); + SetDlgItemInt(hwndDlg, IDC_PAGED, basicInfo.PagedPoolCharge, FALSE); + SetDlgItemInt(hwndDlg, IDC_NONPAGED, basicInfo.NonPagedPoolCharge, FALSE); + + haveBasicInfo = TRUE; + } + + NtClose(processHandle); + } + + if (!haveBasicInfo) + { + SetDlgItemText(hwndDlg, IDC_REFERENCES, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_HANDLES, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_PAGED, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_NONPAGED, L"Unknown"); + } + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_BASICINFORMATION)); + } + return TRUE; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/hndlprv.c b/ProcessHacker/hndlprv.c new file mode 100644 index 0000000..71f9d7f --- /dev/null +++ b/ProcessHacker/hndlprv.c @@ -0,0 +1,718 @@ +/* + * Process Hacker - + * handle provider + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +typedef struct _PHP_CREATE_HANDLE_ITEM_CONTEXT +{ + PPH_HANDLE_PROVIDER Provider; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle; +} PHP_CREATE_HANDLE_ITEM_CONTEXT, *PPHP_CREATE_HANDLE_ITEM_CONTEXT; + +VOID NTAPI PhpHandleProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpHandleItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +PPH_OBJECT_TYPE PhHandleProviderType; +PPH_OBJECT_TYPE PhHandleItemType; + +BOOLEAN PhHandleProviderInitialization( + VOID + ) +{ + PhHandleProviderType = PhCreateObjectType(L"HandleProvider", 0, PhpHandleProviderDeleteProcedure); + PhHandleItemType = PhCreateObjectType(L"HandleItem", 0, PhpHandleItemDeleteProcedure); + + return TRUE; +} + +PPH_HANDLE_PROVIDER PhCreateHandleProvider( + _In_ HANDLE ProcessId + ) +{ + PPH_HANDLE_PROVIDER handleProvider; + + handleProvider = PhCreateObject( + PhEmGetObjectSize(EmHandleProviderType, sizeof(PH_HANDLE_PROVIDER)), + PhHandleProviderType + ); + + handleProvider->HandleHashSetSize = 128; + handleProvider->HandleHashSet = PhCreateHashSet(handleProvider->HandleHashSetSize); + handleProvider->HandleHashSetCount = 0; + PhInitializeQueuedLock(&handleProvider->HandleHashSetLock); + + PhInitializeCallback(&handleProvider->HandleAddedEvent); + PhInitializeCallback(&handleProvider->HandleModifiedEvent); + PhInitializeCallback(&handleProvider->HandleRemovedEvent); + PhInitializeCallback(&handleProvider->UpdatedEvent); + + handleProvider->ProcessId = ProcessId; + handleProvider->ProcessHandle = NULL; + + handleProvider->RunStatus = PhOpenProcess( + &handleProvider->ProcessHandle, + PROCESS_DUP_HANDLE, + ProcessId + ); + + handleProvider->TempListHashtable = PhCreateSimpleHashtable(20); + + PhEmCallObjectOperation(EmHandleProviderType, handleProvider, EmObjectCreate); + + return handleProvider; +} + +VOID PhpHandleProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_HANDLE_PROVIDER handleProvider = (PPH_HANDLE_PROVIDER)Object; + + PhEmCallObjectOperation(EmHandleProviderType, handleProvider, EmObjectDelete); + + // Dereference all handle items (we referenced them + // when we added them to the hashtable). + PhDereferenceAllHandleItems(handleProvider); + + PhFree(handleProvider->HandleHashSet); + PhDeleteCallback(&handleProvider->HandleAddedEvent); + PhDeleteCallback(&handleProvider->HandleModifiedEvent); + PhDeleteCallback(&handleProvider->HandleRemovedEvent); + + if (handleProvider->ProcessHandle) NtClose(handleProvider->ProcessHandle); + + PhDereferenceObject(handleProvider->TempListHashtable); +} + +PPH_HANDLE_ITEM PhCreateHandleItem( + _In_opt_ PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle + ) +{ + PPH_HANDLE_ITEM handleItem; + + handleItem = PhCreateObject( + PhEmGetObjectSize(EmHandleItemType, sizeof(PH_HANDLE_ITEM)), + PhHandleItemType + ); + memset(handleItem, 0, sizeof(PH_HANDLE_ITEM)); + + if (Handle) + { + handleItem->Handle = (HANDLE)Handle->HandleValue; + PhPrintPointer(handleItem->HandleString, (PVOID)handleItem->Handle); + handleItem->Object = Handle->Object; + PhPrintPointer(handleItem->ObjectString, handleItem->Object); + handleItem->Attributes = Handle->HandleAttributes; + handleItem->GrantedAccess = (ACCESS_MASK)Handle->GrantedAccess; + PhPrintPointer(handleItem->GrantedAccessString, UlongToPtr(handleItem->GrantedAccess)); + } + + PhEmCallObjectOperation(EmHandleItemType, handleItem, EmObjectCreate); + + return handleItem; +} + +VOID PhpHandleItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_HANDLE_ITEM handleItem = (PPH_HANDLE_ITEM)Object; + + PhEmCallObjectOperation(EmHandleItemType, handleItem, EmObjectDelete); + + if (handleItem->TypeName) PhDereferenceObject(handleItem->TypeName); + if (handleItem->ObjectName) PhDereferenceObject(handleItem->ObjectName); + if (handleItem->BestObjectName) PhDereferenceObject(handleItem->BestObjectName); +} + +FORCEINLINE BOOLEAN PhCompareHandleItem( + _In_ PPH_HANDLE_ITEM Value1, + _In_ PPH_HANDLE_ITEM Value2 + ) +{ + return Value1->Handle == Value2->Handle; +} + +FORCEINLINE ULONG PhHashHandleItem( + _In_ PPH_HANDLE_ITEM Value + ) +{ + return HandleToUlong(Value->Handle) / 4; +} + +PPH_HANDLE_ITEM PhpLookupHandleItem( + _In_ PPH_HANDLE_PROVIDER HandleProvider, + _In_ HANDLE Handle + ) +{ + PH_HANDLE_ITEM lookupHandleItem; + PPH_HASH_ENTRY entry; + PPH_HANDLE_ITEM handleItem; + + lookupHandleItem.Handle = Handle; + entry = PhFindEntryHashSet( + HandleProvider->HandleHashSet, + HandleProvider->HandleHashSetSize, + PhHashHandleItem(&lookupHandleItem) + ); + + for (; entry; entry = entry->Next) + { + handleItem = CONTAINING_RECORD(entry, PH_HANDLE_ITEM, HashEntry); + + if (PhCompareHandleItem(&lookupHandleItem, handleItem)) + return handleItem; + } + + return NULL; +} + +PPH_HANDLE_ITEM PhReferenceHandleItem( + _In_ PPH_HANDLE_PROVIDER HandleProvider, + _In_ HANDLE Handle + ) +{ + PPH_HANDLE_ITEM handleItem; + + PhAcquireQueuedLockShared(&HandleProvider->HandleHashSetLock); + + handleItem = PhpLookupHandleItem(HandleProvider, Handle); + + if (handleItem) + PhReferenceObject(handleItem); + + PhReleaseQueuedLockShared(&HandleProvider->HandleHashSetLock); + + return handleItem; +} + +VOID PhDereferenceAllHandleItems( + _In_ PPH_HANDLE_PROVIDER HandleProvider + ) +{ + ULONG i; + PPH_HASH_ENTRY entry; + PPH_HANDLE_ITEM handleItem; + + PhAcquireQueuedLockExclusive(&HandleProvider->HandleHashSetLock); + + for (i = 0; i < HandleProvider->HandleHashSetSize; i++) + { + entry = HandleProvider->HandleHashSet[i]; + + while (entry) + { + handleItem = CONTAINING_RECORD(entry, PH_HANDLE_ITEM, HashEntry); + entry = entry->Next; + PhDereferenceObject(handleItem); + } + } + + PhReleaseQueuedLockExclusive(&HandleProvider->HandleHashSetLock); +} + +VOID PhpAddHandleItem( + _In_ PPH_HANDLE_PROVIDER HandleProvider, + _In_ _Assume_refs_(1) PPH_HANDLE_ITEM HandleItem + ) +{ + if (HandleProvider->HandleHashSetSize < HandleProvider->HandleHashSetCount + 1) + { + PhResizeHashSet( + &HandleProvider->HandleHashSet, + &HandleProvider->HandleHashSetSize, + HandleProvider->HandleHashSetSize * 2 + ); + } + + PhAddEntryHashSet( + HandleProvider->HandleHashSet, + HandleProvider->HandleHashSetSize, + &HandleItem->HashEntry, + PhHashHandleItem(HandleItem) + ); + HandleProvider->HandleHashSetCount++; +} + +VOID PhpRemoveHandleItem( + _In_ PPH_HANDLE_PROVIDER HandleProvider, + _In_ PPH_HANDLE_ITEM HandleItem + ) +{ + PhRemoveEntryHashSet(HandleProvider->HandleHashSet, HandleProvider->HandleHashSetSize, &HandleItem->HashEntry); + HandleProvider->HandleHashSetCount--; + PhDereferenceObject(HandleItem); +} + +/** + * Enumerates all handles in a process. + * + * \param ProcessId The ID of the process. + * \param ProcessHandle A handle to the process. + * \param Handles A variable which receives a pointer to a buffer containing + * information about the handles. + * \param FilterNeeded A variable which receives a boolean indicating + * whether the handle information needs to be filtered by process ID. + */ +NTSTATUS PhEnumHandlesGeneric( + _In_ HANDLE ProcessId, + _In_ HANDLE ProcessHandle, + _Out_ PSYSTEM_HANDLE_INFORMATION_EX *Handles, + _Out_ PBOOLEAN FilterNeeded + ) +{ + NTSTATUS status; + + // There are three ways of enumerating handles: + // * When KProcessHacker is available, using KphEnumerateProcessHandles + // is the most efficient method. + // * On Windows XP and later, NtQuerySystemInformation with + // SystemExtendedHandleInformation can be used. + // * Otherwise, NtQuerySystemInformation with SystemHandleInformation + // can be used. + + if (KphIsConnected()) + { + PKPH_PROCESS_HANDLE_INFORMATION handles; + PSYSTEM_HANDLE_INFORMATION_EX convertedHandles; + ULONG i; + + // Enumerate handles using KProcessHacker. Unlike with NtQuerySystemInformation, + // this only enumerates handles for a single process and saves a lot of processing. + + if (NT_SUCCESS(status = KphEnumerateProcessHandles2(ProcessHandle, &handles))) + { + convertedHandles = PhAllocate( + FIELD_OFFSET(SYSTEM_HANDLE_INFORMATION_EX, Handles) + + sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) * handles->HandleCount + ); + + convertedHandles->NumberOfHandles = handles->HandleCount; + + for (i = 0; i < handles->HandleCount; i++) + { + convertedHandles->Handles[i].Object = handles->Handles[i].Object; + convertedHandles->Handles[i].UniqueProcessId = (ULONG_PTR)ProcessId; + convertedHandles->Handles[i].HandleValue = (ULONG_PTR)handles->Handles[i].Handle; + convertedHandles->Handles[i].GrantedAccess = (ULONG)handles->Handles[i].GrantedAccess; + convertedHandles->Handles[i].CreatorBackTraceIndex = 0; + convertedHandles->Handles[i].ObjectTypeIndex = handles->Handles[i].ObjectTypeIndex; + convertedHandles->Handles[i].HandleAttributes = handles->Handles[i].HandleAttributes; + } + + PhFree(handles); + + *Handles = convertedHandles; + *FilterNeeded = FALSE; + + return status; + } + } + + if (WindowsVersion >= WINDOWS_XP) + { + PSYSTEM_HANDLE_INFORMATION_EX handles; + + // Enumerate handles using the new method; no conversion + // necessary. + + if (!NT_SUCCESS(status = PhEnumHandlesEx(&handles))) + return status; + + *Handles = handles; + *FilterNeeded = TRUE; + } + else + { + PSYSTEM_HANDLE_INFORMATION handles; + PSYSTEM_HANDLE_INFORMATION_EX convertedHandles; + ULONG count; + ULONG allocatedCount; + ULONG i; + + // Enumerate handles using the old info class and convert + // the relevant entries to the new format. + + if (!NT_SUCCESS(status = PhEnumHandles(&handles))) + return status; + + count = 0; + allocatedCount = 100; + + convertedHandles = PhAllocate( + FIELD_OFFSET(SYSTEM_HANDLE_INFORMATION_EX, Handles) + + sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) * allocatedCount + ); + + for (i = 0; i < handles->NumberOfHandles; i++) + { + if ((HANDLE)handles->Handles[i].UniqueProcessId != ProcessId) + continue; + + if (count == allocatedCount) + { + allocatedCount *= 2; + convertedHandles = PhReAllocate( + convertedHandles, + FIELD_OFFSET(SYSTEM_HANDLE_INFORMATION_EX, Handles) + + sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) * allocatedCount + ); + } + + convertedHandles->Handles[count].Object = handles->Handles[i].Object; + convertedHandles->Handles[count].UniqueProcessId = (ULONG_PTR)handles->Handles[i].UniqueProcessId; + convertedHandles->Handles[count].HandleValue = (ULONG_PTR)handles->Handles[i].HandleValue; + convertedHandles->Handles[count].GrantedAccess = handles->Handles[i].GrantedAccess; + convertedHandles->Handles[count].CreatorBackTraceIndex = handles->Handles[i].CreatorBackTraceIndex; + convertedHandles->Handles[count].ObjectTypeIndex = handles->Handles[i].ObjectTypeIndex; + convertedHandles->Handles[count].HandleAttributes = (ULONG)handles->Handles[i].HandleAttributes; + + count++; + } + + convertedHandles->NumberOfHandles = count; + + PhFree(handles); + + *Handles = convertedHandles; + *FilterNeeded = FALSE; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhpCreateHandleItemFunction( + _In_ PVOID Parameter + ) +{ + PPHP_CREATE_HANDLE_ITEM_CONTEXT context = Parameter; + PPH_HANDLE_ITEM handleItem; + + handleItem = PhCreateHandleItem(context->Handle); + + PhGetHandleInformationEx( + context->Provider->ProcessHandle, + handleItem->Handle, + context->Handle->ObjectTypeIndex, + 0, + NULL, + NULL, + &handleItem->TypeName, + &handleItem->ObjectName, + &handleItem->BestObjectName, + NULL + ); + + if (handleItem->TypeName) + { + // Add the handle item to the hashtable. + PhAcquireQueuedLockExclusive(&context->Provider->HandleHashSetLock); + PhpAddHandleItem(context->Provider, handleItem); + PhReleaseQueuedLockExclusive(&context->Provider->HandleHashSetLock); + + // Raise the handle added event. + PhInvokeCallback(&context->Provider->HandleAddedEvent, handleItem); + } + else + { + PhDereferenceObject(handleItem); + } + + PhFree(context); + + return STATUS_SUCCESS; +} + +VOID PhHandleProviderUpdate( + _In_ PVOID Object + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static ULONG fileObjectTypeIndex = -1; + + PPH_HANDLE_PROVIDER handleProvider = (PPH_HANDLE_PROVIDER)Object; + PSYSTEM_HANDLE_INFORMATION_EX handleInfo; + BOOLEAN filterNeeded; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handles; + ULONG numberOfHandles; + ULONG i; + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_KEY_VALUE_PAIR handlePair; + BOOLEAN useWorkQueue = FALSE; + PH_WORK_QUEUE workQueue; + + if (!handleProvider->ProcessHandle) + goto UpdateExit; + + if (!NT_SUCCESS(handleProvider->RunStatus = PhEnumHandlesGeneric( + handleProvider->ProcessId, + handleProvider->ProcessHandle, + &handleInfo, + &filterNeeded + ))) + goto UpdateExit; + + if (!KphIsConnected() && WindowsVersion >= WINDOWS_VISTA) + { + useWorkQueue = TRUE; + PhInitializeWorkQueue(&workQueue, 1, 20, 1000); + + if (PhBeginInitOnce(&initOnce)) + { + UNICODE_STRING fileTypeName; + + RtlInitUnicodeString(&fileTypeName, L"File"); + fileObjectTypeIndex = PhGetObjectTypeNumber(&fileTypeName); + PhEndInitOnce(&initOnce); + } + } + + handles = handleInfo->Handles; + numberOfHandles = (ULONG)handleInfo->NumberOfHandles; + + // Make a list of the relevant handles. + if (filterNeeded) + { + for (i = 0; i < (ULONG)numberOfHandles; i++) + { + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle = &handles[i]; + + if (handle->UniqueProcessId == (ULONG_PTR)handleProvider->ProcessId) + { + PhAddItemSimpleHashtable( + handleProvider->TempListHashtable, + (PVOID)handle->HandleValue, + handle + ); + } + } + } + else + { + for (i = 0; i < (ULONG)numberOfHandles; i++) + { + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle = &handles[i]; + + PhAddItemSimpleHashtable( + handleProvider->TempListHashtable, + (PVOID)handle->HandleValue, + handle + ); + } + } + + // Look for closed handles. + { + PPH_LIST handlesToRemove = NULL; + PPH_HASH_ENTRY entry; + PPH_HANDLE_ITEM handleItem; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX *tempHashtableValue; + + for (i = 0; i < handleProvider->HandleHashSetSize; i++) + { + for (entry = handleProvider->HandleHashSet[i]; entry; entry = entry->Next) + { + BOOLEAN found = FALSE; + + handleItem = CONTAINING_RECORD(entry, PH_HANDLE_ITEM, HashEntry); + + // Check if the handle still exists. + + tempHashtableValue = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX *)PhFindItemSimpleHashtable( + handleProvider->TempListHashtable, + (PVOID)(handleItem->Handle) + ); + + if (tempHashtableValue) + { + // Also compare the object pointers to make sure a + // different object wasn't re-opened with the same + // handle value. This isn't 100% accurate as pool + // addresses may be re-used, but it works well. + if (handleItem->Object == (*tempHashtableValue)->Object) + { + found = TRUE; + } + } + + if (!found) + { + // Raise the handle removed event. + PhInvokeCallback(&handleProvider->HandleRemovedEvent, handleItem); + + if (!handlesToRemove) + handlesToRemove = PhCreateList(2); + + PhAddItemList(handlesToRemove, handleItem); + } + } + } + + if (handlesToRemove) + { + PhAcquireQueuedLockExclusive(&handleProvider->HandleHashSetLock); + + for (i = 0; i < handlesToRemove->Count; i++) + { + PhpRemoveHandleItem( + handleProvider, + (PPH_HANDLE_ITEM)handlesToRemove->Items[i] + ); + } + + PhReleaseQueuedLockExclusive(&handleProvider->HandleHashSetLock); + PhDereferenceObject(handlesToRemove); + } + } + + // Look for new handles and update existing ones. + + PhBeginEnumHashtable(handleProvider->TempListHashtable, &enumContext); + + while (handlePair = PhNextEnumHashtable(&enumContext)) + { + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle = handlePair->Value; + PPH_HANDLE_ITEM handleItem; + + handleItem = PhpLookupHandleItem(handleProvider, (HANDLE)handle->HandleValue); + + if (!handleItem) + { + // When we don't have KPH, query handle information in parallel to take full advantage of the + // PhCallWithTimeout functionality. + if (useWorkQueue && handle->ObjectTypeIndex == fileObjectTypeIndex) + { + PPHP_CREATE_HANDLE_ITEM_CONTEXT context; + + context = PhAllocate(sizeof(PHP_CREATE_HANDLE_ITEM_CONTEXT)); + context->Provider = handleProvider; + context->Handle = handle; + PhQueueItemWorkQueue(&workQueue, PhpCreateHandleItemFunction, context); + continue; + } + + handleItem = PhCreateHandleItem(handle); + + PhGetHandleInformationEx( + handleProvider->ProcessHandle, + handleItem->Handle, + handle->ObjectTypeIndex, + 0, + NULL, + NULL, + &handleItem->TypeName, + &handleItem->ObjectName, + &handleItem->BestObjectName, + NULL + ); + + // We need at least a type name to continue. + if (!handleItem->TypeName) + { + PhDereferenceObject(handleItem); + continue; + } + + if (PhEqualString2(handleItem->TypeName, L"File", TRUE) && KphIsConnected()) + { + KPH_FILE_OBJECT_INFORMATION objectInfo; + + if (NT_SUCCESS(KphQueryInformationObject( + handleProvider->ProcessHandle, + handleItem->Handle, + KphObjectFileObjectInformation, + &objectInfo, + sizeof(KPH_FILE_OBJECT_INFORMATION), + NULL + ))) + { + if (objectInfo.SharedRead) + handleItem->FileFlags |= PH_HANDLE_FILE_SHARED_READ; + if (objectInfo.SharedWrite) + handleItem->FileFlags |= PH_HANDLE_FILE_SHARED_WRITE; + if (objectInfo.SharedDelete) + handleItem->FileFlags |= PH_HANDLE_FILE_SHARED_DELETE; + } + } + + // Add the handle item to the hashtable. + PhAcquireQueuedLockExclusive(&handleProvider->HandleHashSetLock); + PhpAddHandleItem(handleProvider, handleItem); + PhReleaseQueuedLockExclusive(&handleProvider->HandleHashSetLock); + + // Raise the handle added event. + PhInvokeCallback(&handleProvider->HandleAddedEvent, handleItem); + } + else + { + BOOLEAN modified = FALSE; + + if (handleItem->Attributes != handle->HandleAttributes) + { + handleItem->Attributes = handle->HandleAttributes; + modified = TRUE; + } + + if (modified) + { + // Raise the handle modified event. + PhInvokeCallback(&handleProvider->HandleModifiedEvent, handleItem); + } + } + } + + if (useWorkQueue) + { + PhWaitForWorkQueue(&workQueue); + PhDeleteWorkQueue(&workQueue); + } + + PhFree(handleInfo); + + // Re-create the temporary hashtable if it got too big. + if (handleProvider->TempListHashtable->AllocatedEntries > 8192) + { + PhDereferenceObject(handleProvider->TempListHashtable); + handleProvider->TempListHashtable = PhCreateSimpleHashtable(512); + } + else + { + PhClearHashtable(handleProvider->TempListHashtable); + } + +UpdateExit: + PhInvokeCallback(&handleProvider->UpdatedEvent, NULL); +} diff --git a/ProcessHacker/hndlstat.c b/ProcessHacker/hndlstat.c new file mode 100644 index 0000000..6bf3a9f --- /dev/null +++ b/ProcessHacker/hndlstat.c @@ -0,0 +1,238 @@ +/* + * Process Hacker - + * handle statistics window + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct _HANDLE_STATISTICS_ENTRY +{ + PPH_STRING Name; + ULONG Count; +} HANDLE_STATISTICS_ENTRY, *PHANDLE_STATISTICS_ENTRY; + +typedef struct _HANDLE_STATISTICS_CONTEXT +{ + HANDLE ProcessId; + HANDLE ProcessHandle; + + PSYSTEM_HANDLE_INFORMATION_EX Handles; + HANDLE_STATISTICS_ENTRY Entries[MAX_OBJECT_TYPE_NUMBER]; +} HANDLE_STATISTICS_CONTEXT, *PHANDLE_STATISTICS_CONTEXT; + +INT_PTR CALLBACK PhpHandleStatisticsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowHandleStatisticsDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId + ) +{ + NTSTATUS status; + HANDLE_STATISTICS_CONTEXT context; + BOOLEAN filterNeeded; + ULONG i; + + context.ProcessId = ProcessId; + + if (!NT_SUCCESS(status = PhOpenProcess( + &context.ProcessHandle, + PROCESS_DUP_HANDLE, + ProcessId + ))) + { + PhShowStatus(ParentWindowHandle, L"Unable to open the process", status, 0); + return; + } + + status = PhEnumHandlesGeneric( + context.ProcessId, + context.ProcessHandle, + &context.Handles, + &filterNeeded + ); + + if (!NT_SUCCESS(status)) + { + NtClose(context.ProcessHandle); + PhShowStatus(ParentWindowHandle, L"Unable to enumerate process handles", status, 0); + return; + } + + memset(&context.Entries, 0, sizeof(context.Entries)); + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_HANDLESTATS), + ParentWindowHandle, + PhpHandleStatisticsDlgProc, + (LPARAM)&context + ); + + for (i = 0; i < MAX_OBJECT_TYPE_NUMBER; i++) + { + if (context.Entries[i].Name) + PhDereferenceObject(context.Entries[i].Name); + } + + PhFree(context.Handles); + NtClose(context.ProcessHandle); +} + +static INT NTAPI PhpTypeCountCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PHANDLE_STATISTICS_ENTRY entry1 = Item1; + PHANDLE_STATISTICS_ENTRY entry2 = Item2; + + return uintcmp(entry1->Count, entry2->Count); +} + +INT_PTR CALLBACK PhpHandleStatisticsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PHANDLE_STATISTICS_CONTEXT context = (PHANDLE_STATISTICS_CONTEXT)lParam; + HANDLE processId; + ULONG_PTR i; + HWND lvHandle; + + processId = context->ProcessId; + + for (i = 0; i < context->Handles->NumberOfHandles; i++) + { + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo; + PHANDLE_STATISTICS_ENTRY entry; + PPH_STRING typeName; + + handleInfo = &context->Handles->Handles[i]; + + if (handleInfo->UniqueProcessId != (ULONG_PTR)processId) + continue; + if (handleInfo->ObjectTypeIndex >= MAX_OBJECT_TYPE_NUMBER) + continue; + + entry = &context->Entries[handleInfo->ObjectTypeIndex]; + + if (!entry->Name) + { + typeName = NULL; + PhGetHandleInformation( + context->ProcessHandle, + (HANDLE)handleInfo->HandleValue, + handleInfo->ObjectTypeIndex, + NULL, + &typeName, + NULL, + NULL + ); + entry->Name = typeName; + } + + entry->Count++; + } + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 140, L"Type"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Count"); + + PhSetExtendedListView(lvHandle); + ExtendedListView_SetCompareFunction(lvHandle, 1, PhpTypeCountCompareFunction); + + for (i = 0; i < MAX_OBJECT_TYPE_NUMBER; i++) + { + PHANDLE_STATISTICS_ENTRY entry; + PPH_STRING unknownType; + PPH_STRING countString; + INT lvItemIndex; + + entry = &context->Entries[i]; + + if (entry->Count == 0) + continue; + + unknownType = NULL; + + if (!entry->Name) + unknownType = PhFormatString(L"(unknown: %u)", i); + + countString = PhFormatUInt64(entry->Count, TRUE); + + lvItemIndex = PhAddListViewItem( + lvHandle, + MAXINT, + entry->Name ? entry->Name->Buffer : unknownType->Buffer, + entry + ); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, countString->Buffer); + + PhDereferenceObject(countString); + + if (unknownType) + PhDereferenceObject(unknownType); + } + + ExtendedListView_SortItems(lvHandle); + } + break; + case WM_DESTROY: + { + // Nothing + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/include/actions.h b/ProcessHacker/include/actions.h new file mode 100644 index 0000000..ee775ec --- /dev/null +++ b/ProcessHacker/include/actions.h @@ -0,0 +1,368 @@ +#ifndef PH_ACTIONS_H +#define PH_ACTIONS_H + +typedef enum _PH_ACTION_ELEVATION_LEVEL +{ + NeverElevateAction = 0, + PromptElevateAction = 1, + AlwaysElevateAction = 2 +} PH_ACTION_ELEVATION_LEVEL; + +// begin_phapppub +typedef enum _PH_PHSVC_MODE +{ + ElevatedPhSvcMode, + Wow64PhSvcMode +} PH_PHSVC_MODE; + +PHAPPAPI +BOOLEAN +NTAPI +PhUiConnectToPhSvc( + _In_opt_ HWND hWnd, + _In_ BOOLEAN ConnectOnly + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiConnectToPhSvcEx( + _In_opt_ HWND hWnd, + _In_ PH_PHSVC_MODE Mode, + _In_ BOOLEAN ConnectOnly + ); + +PHAPPAPI +VOID +NTAPI +PhUiDisconnectFromPhSvc( + VOID + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiLockComputer( + _In_ HWND hWnd + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiLogoffComputer( + _In_ HWND hWnd + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSleepComputer( + _In_ HWND hWnd + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiHibernateComputer( + _In_ HWND hWnd + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiRestartComputer( + _In_ HWND hWnd, + _In_ ULONG Flags + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiShutdownComputer( + _In_ HWND hWnd, + _In_ ULONG Flags + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiConnectSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiDisconnectSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiLogoffSession( + _In_ HWND hWnd, + _In_ ULONG SessionId + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiTerminateProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiTerminateTreeProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSuspendProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiResumeProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiRestartProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiDebugProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiReduceWorkingSetProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetVirtualizationProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process, + _In_ BOOLEAN Enable + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiDetachFromDebuggerProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiInjectDllProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetIoPriorityProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses, + _In_ IO_PRIORITY_HINT IoPriority + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetPagePriorityProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process, + _In_ ULONG PagePriority + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetPriorityProcesses( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses, + _In_ ULONG PriorityClass + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiStartService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiContinueService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiPauseService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiStopService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiDeleteService( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM Service + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiCloseConnections( + _In_ HWND hWnd, + _In_ PPH_NETWORK_ITEM *Connections, + _In_ ULONG NumberOfConnections + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiTerminateThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSuspendThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiResumeThreads( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetPriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ LONG Increment + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetIoPriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ IO_PRIORITY_HINT IoPriority + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetPagePriorityThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread, + _In_ ULONG PagePriority + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiUnloadModule( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_MODULE_ITEM Module + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiFreeMemory( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_MEMORY_ITEM MemoryItem, + _In_ BOOLEAN Free + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiCloseHandles( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM *Handles, + _In_ ULONG NumberOfHandles, + _In_ BOOLEAN Warn + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhUiSetAttributesHandle( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM Handle, + _In_ ULONG Attributes + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/colmgr.h b/ProcessHacker/include/colmgr.h new file mode 100644 index 0000000..6d54898 --- /dev/null +++ b/ProcessHacker/include/colmgr.h @@ -0,0 +1,118 @@ +#ifndef PH_COLMGR_H +#define PH_COLMGR_H + +#define PH_CM_ORDER_LIMIT 160 + +// begin_phapppub +typedef LONG (NTAPI *PPH_CM_POST_SORT_FUNCTION)( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); +// end_phapppub + +typedef struct _PH_CM_MANAGER +{ + HWND Handle; + ULONG MinId; + ULONG NextId; + PPH_CM_POST_SORT_FUNCTION PostSortFunction; + LIST_ENTRY ColumnListHead; + PPH_LIST NotifyList; +} PH_CM_MANAGER, *PPH_CM_MANAGER; + +typedef struct _PH_CM_COLUMN +{ + LIST_ENTRY ListEntry; + ULONG Id; + struct _PH_PLUGIN *Plugin; + ULONG SubId; + PVOID Context; + PVOID SortFunction; +} PH_CM_COLUMN, *PPH_CM_COLUMN; + +VOID PhCmInitializeManager( + _Out_ PPH_CM_MANAGER Manager, + _In_ HWND Handle, + _In_ ULONG MinId, + _In_ PPH_CM_POST_SORT_FUNCTION PostSortFunction + ); + +VOID PhCmDeleteManager( + _In_ PPH_CM_MANAGER Manager + ); + +PPH_CM_COLUMN PhCmCreateColumn( + _Inout_ PPH_CM_MANAGER Manager, + _In_ PPH_TREENEW_COLUMN Column, + _In_ struct _PH_PLUGIN *Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PVOID SortFunction + ); + +PPH_CM_COLUMN PhCmFindColumn( + _In_ PPH_CM_MANAGER Manager, + _In_ PPH_STRINGREF PluginName, + _In_ ULONG SubId + ); + +VOID PhCmSetNotifyPlugin( + _In_ PPH_CM_MANAGER Manager, + _In_ struct _PH_PLUGIN *Plugin + ); + +BOOLEAN PhCmForwardMessage( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_ PPH_CM_MANAGER Manager + ); + +BOOLEAN PhCmForwardSort( + _In_ PPH_TREENEW_NODE *Nodes, + _In_ ULONG NumberOfNodes, + _In_ ULONG SortColumn, + _In_ PH_SORT_ORDER SortOrder, + _In_ PPH_CM_MANAGER Manager + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhCmLoadSettings( + _In_ HWND TreeNewHandle, + _In_ PPH_STRINGREF Settings + ); +// end_phapppub + +#define PH_CM_COLUMN_WIDTHS_ONLY 0x1 + +BOOLEAN PhCmLoadSettingsEx( + _In_ HWND TreeNewHandle, + _In_opt_ PPH_CM_MANAGER Manager, + _In_ ULONG Flags, + _In_ PPH_STRINGREF Settings, + _In_opt_ PPH_STRINGREF SortSettings + ); + +// begin_phapppub +PHAPPAPI +PPH_STRING +NTAPI +PhCmSaveSettings( + _In_ HWND TreeNewHandle + ); +// end_phapppub + +PPH_STRING PhCmSaveSettingsEx( + _In_ HWND TreeNewHandle, + _In_opt_ PPH_CM_MANAGER Manager, + _In_ ULONG Flags, + _Out_opt_ PPH_STRING *SortSettings + ); + +#endif diff --git a/ProcessHacker/include/extmgr.h b/ProcessHacker/include/extmgr.h new file mode 100644 index 0000000..321e78d --- /dev/null +++ b/ProcessHacker/include/extmgr.h @@ -0,0 +1,51 @@ +#ifndef PH_EXTMGR_H +#define PH_EXTMGR_H + +// begin_phapppub +typedef enum _PH_EM_OBJECT_TYPE +{ + EmProcessItemType, + EmProcessNodeType, + EmServiceItemType, + EmServiceNodeType, + EmNetworkItemType, + EmNetworkNodeType, + EmThreadItemType, + EmThreadNodeType, + EmModuleItemType, + EmModuleNodeType, + EmHandleItemType, + EmHandleNodeType, + EmThreadsContextType, + EmModulesContextType, + EmHandlesContextType, + EmThreadProviderType, + EmModuleProviderType, + EmHandleProviderType, + EmMemoryNodeType, + EmMemoryContextType, + EmMaximumObjectType +} PH_EM_OBJECT_TYPE; + +typedef enum _PH_EM_OBJECT_OPERATION +{ + EmObjectCreate, + EmObjectDelete, + EmMaximumObjectOperation +} PH_EM_OBJECT_OPERATION; + +typedef VOID (NTAPI *PPH_EM_OBJECT_CALLBACK)( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ); +// end_phapppub + +typedef struct _PH_EM_APP_CONTEXT +{ + LIST_ENTRY ListEntry; + PH_STRINGREF AppName; + struct _PH_EM_OBJECT_EXTENSION *Extensions[EmMaximumObjectType]; +} PH_EM_APP_CONTEXT, *PPH_EM_APP_CONTEXT; + +#endif diff --git a/ProcessHacker/include/extmgri.h b/ProcessHacker/include/extmgri.h new file mode 100644 index 0000000..932a8e5 --- /dev/null +++ b/ProcessHacker/include/extmgri.h @@ -0,0 +1,61 @@ +#ifndef PH_EXTMGRI_H +#define PH_EXTMGRI_H + +#include + +typedef struct _PH_EM_OBJECT_TYPE_STATE +{ + SIZE_T InitialSize; + SIZE_T ExtensionOffset; + LIST_ENTRY ExtensionListHead; +} PH_EM_OBJECT_TYPE_STATE, *PPH_EM_OBJECT_TYPE_STATE; + +typedef struct _PH_EM_OBJECT_EXTENSION +{ + LIST_ENTRY ListEntry; + SIZE_T ExtensionSize; + SIZE_T ExtensionOffset; + PPH_EM_OBJECT_CALLBACK Callbacks[EmMaximumObjectOperation]; +} PH_EM_OBJECT_EXTENSION, *PPH_EM_OBJECT_EXTENSION; + +VOID PhEmInitialization( + VOID + ); + +VOID PhEmInitializeAppContext( + _Out_ PPH_EM_APP_CONTEXT AppContext, + _In_ PPH_STRINGREF AppName + ); + +VOID PhEmSetObjectExtension( + _Inout_ PPH_EM_APP_CONTEXT AppContext, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ SIZE_T ExtensionSize, + _In_opt_ PPH_EM_OBJECT_CALLBACK CreateCallback, + _In_opt_ PPH_EM_OBJECT_CALLBACK DeleteCallback + ); + +PVOID PhEmGetObjectExtension( + _In_ PPH_EM_APP_CONTEXT AppContext, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Object + ); + +SIZE_T PhEmGetObjectSize( + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ SIZE_T InitialSize + ); + +VOID PhEmCallObjectOperation( + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Object, + _In_ PH_EM_OBJECT_OPERATION Operation + ); + +BOOLEAN PhEmParseCompoundId( + _In_ PPH_STRINGREF CompoundId, + _Out_ PPH_STRINGREF AppName, + _Out_ PULONG SubId + ); + +#endif diff --git a/ProcessHacker/include/heapstruct.h b/ProcessHacker/include/heapstruct.h new file mode 100644 index 0000000..c85b1fb --- /dev/null +++ b/ProcessHacker/include/heapstruct.h @@ -0,0 +1,69 @@ +#ifndef PH_HEAPSTRUCT_H +#define PH_HEAPSTRUCT_H + +// Not the actual structure, but has the same size. +typedef struct _HEAP_ENTRY +{ + PVOID Data1; + PVOID Data2; +} HEAP_ENTRY, *PHEAP_ENTRY; + +#define HEAP_SEGMENT_SIGNATURE 0xffeeffee + +// First few fields of HEAP_SEGMENT, VISTA and above +typedef struct _HEAP_SEGMENT +{ + HEAP_ENTRY HeapEntry; + ULONG SegmentSignature; + ULONG SegmentFlags; + LIST_ENTRY SegmentListEntry; + struct _HEAP *Heap; + + // ... +} HEAP_SEGMENT, *PHEAP_SEGMENT; + +// First few fields of HEAP_SEGMENT, WS03 and below +typedef struct _HEAP_SEGMENT_OLD +{ + HEAP_ENTRY Entry; + ULONG Signature; + ULONG Flags; + struct _HEAP *Heap; + + // ... +} HEAP_SEGMENT_OLD, *PHEAP_SEGMENT_OLD; + +// 32-bit versions + +typedef struct _HEAP_ENTRY32 +{ + WOW64_POINTER(PVOID) Data1; + WOW64_POINTER(PVOID) Data2; +} HEAP_ENTRY32, *PHEAP_ENTRY32; + +typedef struct _HEAP_SEGMENT32 +{ + HEAP_ENTRY32 HeapEntry; + ULONG SegmentSignature; + ULONG SegmentFlags; + LIST_ENTRY32 SegmentListEntry; + WOW64_POINTER(struct _HEAP *) Heap; + + // ... +} HEAP_SEGMENT32, *PHEAP_SEGMENT32; + +typedef struct _HEAP_SEGMENT_OLD32 +{ + HEAP_ENTRY32 Entry; + ULONG Signature; + ULONG Flags; + WOW64_POINTER(struct _HEAP *) Heap; + + // ... +} HEAP_SEGMENT_OLD32, *PHEAP_SEGMENT_OLD32; + +#define HEAP_SEGMENT_MAX_SIZE \ + (max(sizeof(HEAP_SEGMENT), max(sizeof(HEAP_SEGMENT_OLD), \ + max(sizeof(HEAP_SEGMENT32), sizeof(HEAP_SEGMENT_OLD32))))) + +#endif diff --git a/ProcessHacker/include/hidnproc.h b/ProcessHacker/include/hidnproc.h new file mode 100644 index 0000000..471f9f9 --- /dev/null +++ b/ProcessHacker/include/hidnproc.h @@ -0,0 +1,75 @@ +#ifndef PH_HIDNPROC_H +#define PH_HIDNPROC_H + +typedef enum _PH_HIDDEN_PROCESS_METHOD +{ + BruteForceScanMethod, + CsrHandlesScanMethod +} PH_HIDDEN_PROCESS_METHOD; + +typedef enum _PH_HIDDEN_PROCESS_TYPE +{ + UnknownProcess, + NormalProcess, + HiddenProcess, + TerminatedProcess +} PH_HIDDEN_PROCESS_TYPE; + +typedef struct _PH_HIDDEN_PROCESS_ENTRY +{ + HANDLE ProcessId; + PPH_STRING FileName; + PH_HIDDEN_PROCESS_TYPE Type; +} PH_HIDDEN_PROCESS_ENTRY, *PPH_HIDDEN_PROCESS_ENTRY; + +typedef struct _PH_CSR_HANDLE_INFO +{ + HANDLE CsrProcessHandle; + HANDLE Handle; + BOOLEAN IsThreadHandle; + + HANDLE ProcessId; +} PH_CSR_HANDLE_INFO, *PPH_CSR_HANDLE_INFO; + +typedef BOOLEAN (NTAPI *PPH_ENUM_HIDDEN_PROCESSES_CALLBACK)( + _In_ PPH_HIDDEN_PROCESS_ENTRY Process, + _In_opt_ PVOID Context + ); + +NTSTATUS +NTAPI +PhEnumHiddenProcesses( + _In_ PH_HIDDEN_PROCESS_METHOD Method, + _In_ PPH_ENUM_HIDDEN_PROCESSES_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +typedef BOOLEAN (NTAPI *PPH_ENUM_CSR_PROCESS_HANDLES_CALLBACK)( + _In_ PPH_CSR_HANDLE_INFO Handle, + _In_opt_ PVOID Context + ); + +NTSTATUS +NTAPI +PhEnumCsrProcessHandles( + _In_ PPH_ENUM_CSR_PROCESS_HANDLES_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +NTSTATUS +NTAPI +PhOpenProcessByCsrHandle( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PPH_CSR_HANDLE_INFO Handle + ); + +NTSTATUS +NTAPI +PhOpenProcessByCsrHandles( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ); + +#endif diff --git a/ProcessHacker/include/hndllist.h b/ProcessHacker/include/hndllist.h new file mode 100644 index 0000000..579b06c --- /dev/null +++ b/ProcessHacker/include/hndllist.h @@ -0,0 +1,119 @@ +#ifndef PH_HNDLLIST_H +#define PH_HNDLLIST_H + +#include +#include + +// Columns + +#define PHHNTLC_TYPE 0 +#define PHHNTLC_NAME 1 +#define PHHNTLC_HANDLE 2 + +#define PHHNTLC_OBJECTADDRESS 3 +#define PHHNTLC_ATTRIBUTES 4 +#define PHHNTLC_GRANTEDACCESS 5 +#define PHHNTLC_GRANTEDACCESSSYMBOLIC 6 +#define PHHNTLC_ORIGINALNAME 7 +#define PHHNTLC_FILESHAREACCESS 8 + +#define PHHNTLC_MAXIMUM 9 + +// begin_phapppub +typedef struct _PH_HANDLE_NODE +{ + PH_TREENEW_NODE Node; + + PH_SH_STATE ShState; + + HANDLE Handle; + PPH_HANDLE_ITEM HandleItem; +// end_phapppub + + PH_STRINGREF TextCache[PHHNTLC_MAXIMUM]; + + PPH_STRING GrantedAccessSymbolicText; + WCHAR FileShareAccessText[4]; +// begin_phapppub +} PH_HANDLE_NODE, *PPH_HANDLE_NODE; +// end_phapppub + +typedef struct _PH_HANDLE_LIST_CONTEXT +{ + HWND ParentWindowHandle; + HWND TreeNewHandle; + ULONG TreeNewSortColumn; + PH_SORT_ORDER TreeNewSortOrder; + PH_CM_MANAGER Cm; + BOOLEAN HideUnnamedHandles; + + PPH_HASHTABLE NodeHashtable; + PPH_LIST NodeList; + + BOOLEAN EnableStateHighlighting; + PPH_POINTER_LIST NodeStateList; +} PH_HANDLE_LIST_CONTEXT, *PPH_HANDLE_LIST_CONTEXT; + +VOID PhInitializeHandleList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_HANDLE_LIST_CONTEXT Context + ); + +VOID PhDeleteHandleList( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ); + +VOID PhLoadSettingsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context + ); + +VOID PhSaveSettingsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context + ); + +VOID PhSetOptionsHandleList( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ BOOLEAN HideUnnamedHandles + ); + +PPH_HANDLE_NODE PhAddHandleNode( + _Inout_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_ITEM HandleItem, + _In_ ULONG RunId + ); + +PPH_HANDLE_NODE PhFindHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ HANDLE Handle + ); + +VOID PhRemoveHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_NODE HandleNode + ); + +VOID PhUpdateHandleNode( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _In_ PPH_HANDLE_NODE HandleNode + ); + +VOID PhTickHandleNodes( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ); + +PPH_HANDLE_ITEM PhGetSelectedHandleItem( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ); + +VOID PhGetSelectedHandleItems( + _In_ PPH_HANDLE_LIST_CONTEXT Context, + _Out_ PPH_HANDLE_ITEM **Handles, + _Out_ PULONG NumberOfHandles + ); + +VOID PhDeselectAllHandleNodes( + _In_ PPH_HANDLE_LIST_CONTEXT Context + ); + +#endif diff --git a/ProcessHacker/include/hndlmenu.h b/ProcessHacker/include/hndlmenu.h new file mode 100644 index 0000000..b1ba921 --- /dev/null +++ b/ProcessHacker/include/hndlmenu.h @@ -0,0 +1,33 @@ +#ifndef PH_HNDLMENU_H +#define PH_HNDLMENU_H + +typedef struct _PH_HANDLE_ITEM_INFO +{ + HANDLE ProcessId; + HANDLE Handle; + PPH_STRING TypeName; + PPH_STRING BestObjectName; +} PH_HANDLE_ITEM_INFO, *PPH_HANDLE_ITEM_INFO; + +#define PhaAppendCtrlEnter(Text, Enable) ((Enable) ? PhaConcatStrings2((Text), L"\tCtrl+Enter")->Buffer : (Text)) + +VOID PhInsertHandleObjectPropertiesEMenuItems( + _In_ struct _PH_EMENU_ITEM *Menu, + _In_ ULONG InsertBeforeId, + _In_ BOOLEAN EnableShortcut, + _In_ PPH_HANDLE_ITEM_INFO Info + ); + +#define PH_MAX_SECTION_EDIT_SIZE (32 * 1024 * 1024) // 32 MB + +VOID PhShowHandleObjectProperties1( + _In_ HWND hWnd, + _In_ PPH_HANDLE_ITEM_INFO Info + ); + +VOID PhShowHandleObjectProperties2( + _In_ HWND hWnd, + _In_ PPH_HANDLE_ITEM_INFO Info + ); + +#endif diff --git a/ProcessHacker/include/hndlprv.h b/ProcessHacker/include/hndlprv.h new file mode 100644 index 0000000..7238239 --- /dev/null +++ b/ProcessHacker/include/hndlprv.h @@ -0,0 +1,84 @@ +#ifndef PH_HNDLPRV_H +#define PH_HNDLPRV_H + +extern PPH_OBJECT_TYPE PhHandleProviderType; +extern PPH_OBJECT_TYPE PhHandleItemType; + +// begin_phapppub +#define PH_HANDLE_FILE_SHARED_READ 0x1 +#define PH_HANDLE_FILE_SHARED_WRITE 0x2 +#define PH_HANDLE_FILE_SHARED_DELETE 0x4 +#define PH_HANDLE_FILE_SHARED_MASK 0x7 + +typedef struct _PH_HANDLE_ITEM +{ + PH_HASH_ENTRY HashEntry; + + HANDLE Handle; + PVOID Object; + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG FileFlags; + + PPH_STRING TypeName; + PPH_STRING ObjectName; + PPH_STRING BestObjectName; + + WCHAR HandleString[PH_PTR_STR_LEN_1]; + WCHAR ObjectString[PH_PTR_STR_LEN_1]; + WCHAR GrantedAccessString[PH_PTR_STR_LEN_1]; +} PH_HANDLE_ITEM, *PPH_HANDLE_ITEM; + +typedef struct _PH_HANDLE_PROVIDER +{ + PPH_HASH_ENTRY *HandleHashSet; + ULONG HandleHashSetSize; + ULONG HandleHashSetCount; + PH_QUEUED_LOCK HandleHashSetLock; + + PH_CALLBACK HandleAddedEvent; + PH_CALLBACK HandleModifiedEvent; + PH_CALLBACK HandleRemovedEvent; + PH_CALLBACK UpdatedEvent; + + HANDLE ProcessId; + HANDLE ProcessHandle; + + PPH_HASHTABLE TempListHashtable; + NTSTATUS RunStatus; +} PH_HANDLE_PROVIDER, *PPH_HANDLE_PROVIDER; +// end_phapppub + +BOOLEAN PhHandleProviderInitialization( + VOID + ); + +PPH_HANDLE_PROVIDER PhCreateHandleProvider( + _In_ HANDLE ProcessId + ); + +PPH_HANDLE_ITEM PhCreateHandleItem( + _In_opt_ PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handle + ); + +PPH_HANDLE_ITEM PhReferenceHandleItem( + _In_ PPH_HANDLE_PROVIDER HandleProvider, + _In_ HANDLE Handle + ); + +VOID PhDereferenceAllHandleItems( + _In_ PPH_HANDLE_PROVIDER HandleProvider + ); + +NTSTATUS PhEnumHandlesGeneric( + _In_ HANDLE ProcessId, + _In_ HANDLE ProcessHandle, + _Out_ PSYSTEM_HANDLE_INFORMATION_EX *Handles, + _Out_ PBOOLEAN FilterNeeded + ); + +VOID PhHandleProviderUpdate( + _In_ PVOID Object + ); + +#endif diff --git a/ProcessHacker/include/mainwndp.h b/ProcessHacker/include/mainwndp.h new file mode 100644 index 0000000..497ad6b --- /dev/null +++ b/ProcessHacker/include/mainwndp.h @@ -0,0 +1,450 @@ +#ifndef PH_MAINWNDP_H +#define PH_MAINWNDP_H + +#define PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_1 250 +#define PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_2 750 +#define PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_LONG_TERM 1000 + +#define TIMER_FLUSH_PROCESS_QUERY_DATA 1 +#define TIMER_ICON_CLICK_ACTIVATE 2 +#define TIMER_ICON_RESTORE_HOVER 3 + +LRESULT CALLBACK PhMwpWndProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Initialization + +BOOLEAN PhMwpInitializeWindowClass( + VOID + ); + +VOID PhMwpInitializeProviders( + VOID + ); + +VOID PhMwpApplyUpdateInterval( + _In_ ULONG Interval + ); + +VOID PhMwpInitializeControls( + VOID + ); + +NTSTATUS PhMwpDelayedLoadFunction( + _In_ PVOID Parameter + ); + +PPH_STRING PhMwpFindDbghelpPath( + VOID + ); + +// Event handlers + +VOID PhMwpOnDestroy( + VOID + ); + +VOID PhMwpOnEndSession( + VOID + ); + +VOID PhMwpOnSettingChange( + VOID + ); + +VOID PhMwpOnCommand( + _In_ ULONG Id + ); + +VOID PhMwpOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ); + +BOOLEAN PhMwpOnSysCommand( + _In_ ULONG Type, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ); + +VOID PhMwpOnMenuCommand( + _In_ ULONG Index, + _In_ HMENU Menu + ); + +VOID PhMwpOnInitMenuPopup( + _In_ HMENU Menu, + _In_ ULONG Index, + _In_ BOOLEAN IsWindowMenu + ); + +VOID PhMwpOnSize( + VOID + ); + +VOID PhMwpOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ); + +VOID PhMwpOnSetFocus( + VOID + ); + +VOID PhMwpOnTimer( + _In_ ULONG Id + ); + +BOOLEAN PhMwpOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ); + +VOID PhMwpOnWtsSessionChange( + _In_ ULONG Reason, + _In_ ULONG SessionId + ); + +ULONG_PTR PhMwpOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ); + +// Callbacks + +VOID NTAPI PhMwpProcessAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpProcessModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpProcessRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpServiceAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpServiceModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpServiceRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpServicesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpNetworkItemAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpNetworkItemModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpNetworkItemRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI PhMwpNetworkItemsUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +// Settings + +VOID PhMwpLoadSettings( + VOID + ); + +VOID PhMwpSaveSettings( + VOID + ); + +VOID PhMwpSaveWindowState( + VOID + ); + +// Misc. + +VOID PhMwpSymInitHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID PhMwpUpdateLayoutPadding( + VOID + ); + +VOID PhMwpApplyLayoutPadding( + _Inout_ PRECT Rect, + _In_ PRECT Padding + ); + +VOID PhMwpLayout( + _Inout_ HDWP *DeferHandle + ); + +VOID PhMwpSetCheckOpacityMenu( + _In_ BOOLEAN AssumeAllUnchecked, + _In_ ULONG Opacity + ); + +VOID PhMwpSetupComputerMenu( + _In_ PPH_EMENU_ITEM Root + ); + +BOOLEAN PhMwpExecuteComputerCommand( + _In_ ULONG Id + ); + +VOID PhMwpActivateWindow( + _In_ BOOLEAN Toggle + ); + +// Main menu + +VOID PhMwpInitializeMainMenu( + _In_ HMENU Menu + ); + +VOID PhMwpDispatchMenuCommand( + _In_ HMENU MenuHandle, + _In_ ULONG ItemIndex, + _In_ ULONG ItemId, + _In_ ULONG_PTR ItemData + ); + +ULONG_PTR PhMwpLegacyAddPluginMenuItem( + _In_ PPH_ADDMENUITEM AddMenuItem + ); + +HBITMAP PhMwpGetShieldBitmap( + VOID + ); + +VOID PhMwpInitializeSubMenu( + _In_ PPH_EMENU Menu, + _In_ ULONG Index + ); + +PPH_EMENU_ITEM PhMwpFindTrayIconsMenuItem( + _In_ PPH_EMENU Menu + ); + +VOID PhMwpInitializeSectionMenuItems( + _In_ PPH_EMENU Menu, + _In_ ULONG StartIndex + ); + +// Tab control + +VOID PhMwpLayoutTabControl( + _Inout_ HDWP *DeferHandle + ); + +VOID PhMwpNotifyTabControl( + _In_ NMHDR *Header + ); + +VOID PhMwpSelectionChangedTabControl( + _In_ ULONG OldIndex + ); + +PPH_ADDITIONAL_TAB_PAGE PhMwpAddTabPage( + _In_ PPH_ADDITIONAL_TAB_PAGE TabPage + ); + +VOID PhMwpSelectTabPage( + _In_ ULONG Index + ); + +INT PhMwpFindTabPageIndex( + _In_ PWSTR Text + ); + +// Notifications + +VOID PhMwpAddIconProcesses( + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG NumberOfProcesses + ); + +VOID PhMwpShowIconContextMenu( + _In_ POINT Location + ); + +VOID PhMwpClearLastNotificationDetails( + VOID + ); + +BOOLEAN PhMwpPluginNotifyEvent( + _In_ ULONG Type, + _In_ PVOID Parameter + ); + +// Processes + +VOID PhMwpShowProcessProperties( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +BOOLEAN PhMwpCurrentUserProcessTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +BOOLEAN PhMwpSignedProcessTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +BOOLEAN PhMwpExecuteProcessPriorityCommand( + _In_ ULONG Id, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +BOOLEAN PhMwpExecuteProcessIoPriorityCommand( + _In_ ULONG Id, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +VOID PhMwpSetProcessMenuPriorityChecks( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ BOOLEAN SetPriority, + _In_ BOOLEAN SetIoPriority, + _In_ BOOLEAN SetPagePriority + ); + +VOID PhMwpInitializeProcessMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ); + +VOID PhMwpOnProcessAdded( + _In_ _Assume_refs_(1) PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG RunId + ); + +VOID PhMwpOnProcessModified( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +VOID PhMwpOnProcessRemoved( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +VOID PhMwpOnProcessesUpdated( + VOID + ); + +// Services + +VOID PhMwpNeedServiceTreeList( + VOID + ); + +BOOLEAN PhMwpDriverServiceTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +VOID PhMwpInitializeServiceMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_SERVICE_ITEM *Services, + _In_ ULONG NumberOfServices + ); + +VOID PhMwpOnServiceAdded( + _In_ _Assume_refs_(1) PPH_SERVICE_ITEM ServiceItem, + _In_ ULONG RunId + ); + +VOID PhMwpOnServiceModified( + _In_ struct _PH_SERVICE_MODIFIED_DATA *ServiceModifiedData + ); + +VOID PhMwpOnServiceRemoved( + _In_ PPH_SERVICE_ITEM ServiceItem + ); + +VOID PhMwpOnServicesUpdated( + VOID + ); + +// Network + +VOID PhMwpNeedNetworkTreeList( + VOID + ); + +BOOLEAN PhMwpCurrentUserNetworkTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +BOOLEAN PhMwpSignedNetworkTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +VOID PhMwpInitializeNetworkMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_NETWORK_ITEM *NetworkItems, + _In_ ULONG NumberOfNetworkItems + ); + +VOID PhMwpOnNetworkItemAdded( + _In_ ULONG RunId, + _In_ _Assume_refs_(1) PPH_NETWORK_ITEM NetworkItem + ); + +VOID PhMwpOnNetworkItemModified( + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +VOID PhMwpOnNetworkItemRemoved( + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +VOID PhMwpOnNetworkItemsUpdated( + VOID + ); + +// Users + +VOID PhMwpUpdateUsersMenu( + VOID + ); + +#endif diff --git a/ProcessHacker/include/memlist.h b/ProcessHacker/include/memlist.h new file mode 100644 index 0000000..c5f3dcd --- /dev/null +++ b/ProcessHacker/include/memlist.h @@ -0,0 +1,116 @@ +#ifndef PH_MEMLIST_H +#define PH_MEMLIST_H + +#include +#include + +// Columns + +#define PHMMTLC_BASEADDRESS 0 +#define PHMMTLC_TYPE 1 +#define PHMMTLC_SIZE 2 +#define PHMMTLC_PROTECTION 3 +#define PHMMTLC_USE 4 +#define PHMMTLC_TOTALWS 5 +#define PHMMTLC_PRIVATEWS 6 +#define PHMMTLC_SHAREABLEWS 7 +#define PHMMTLC_SHAREDWS 8 +#define PHMMTLC_LOCKEDWS 9 +#define PHMMTLC_COMMITTED 10 +#define PHMMTLC_PRIVATE 11 + +#define PHMMTLC_MAXIMUM 12 + +// begin_phapppub +typedef struct _PH_MEMORY_NODE +{ + PH_TREENEW_NODE Node; + + BOOLEAN IsAllocationBase; + BOOLEAN Reserved1; + USHORT Reserved2; + PPH_MEMORY_ITEM MemoryItem; + + struct _PH_MEMORY_NODE *Parent; + PPH_LIST Children; +// end_phapppub + + PH_STRINGREF TextCache[PHMMTLC_MAXIMUM]; + + WCHAR BaseAddressText[PH_PTR_STR_LEN_1]; + WCHAR TypeText[30]; + PPH_STRING SizeText; + WCHAR ProtectionText[17]; + PPH_STRING UseText; + PPH_STRING TotalWsText; + PPH_STRING PrivateWsText; + PPH_STRING ShareableWsText; + PPH_STRING SharedWsText; + PPH_STRING LockedWsText; + PPH_STRING CommittedText; + PPH_STRING PrivateText; +// begin_phapppub +} PH_MEMORY_NODE, *PPH_MEMORY_NODE; +// end_phapppub + +typedef struct _PH_MEMORY_LIST_CONTEXT +{ + HWND ParentWindowHandle; + HWND TreeNewHandle; + ULONG TreeNewSortColumn; + PH_SORT_ORDER TreeNewSortOrder; + PH_CM_MANAGER Cm; + BOOLEAN HideFreeRegions; + + PPH_LIST AllocationBaseNodeList; // Allocation base nodes (list should always be sorted by base address) + PPH_LIST RegionNodeList; // Memory region nodes +} PH_MEMORY_LIST_CONTEXT, *PPH_MEMORY_LIST_CONTEXT; + +VOID PhInitializeMemoryList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhDeleteMemoryList( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhLoadSettingsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhSaveSettingsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhSetOptionsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ BOOLEAN HideFreeRegions + ); + +VOID PhReplaceMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ PPH_MEMORY_ITEM_LIST List + ); + +VOID PhUpdateMemoryNode( + _In_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ PPH_MEMORY_NODE MemoryNode + ); + +PPH_MEMORY_NODE PhGetSelectedMemoryNode( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhGetSelectedMemoryNodes( + _In_ PPH_MEMORY_LIST_CONTEXT Context, + _Out_ PPH_MEMORY_NODE **MemoryNodes, + _Out_ PULONG NumberOfMemoryNodes + ); + +VOID PhDeselectAllMemoryNodes( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ); + +#endif diff --git a/ProcessHacker/include/memprv.h b/ProcessHacker/include/memprv.h new file mode 100644 index 0000000..0d5fe3a --- /dev/null +++ b/ProcessHacker/include/memprv.h @@ -0,0 +1,148 @@ +#ifndef PH_MEMPRV_H +#define PH_MEMPRV_H + +extern PPH_OBJECT_TYPE PhMemoryItemType; + +// begin_phapppub +typedef enum _PH_MEMORY_REGION_TYPE +{ + UnknownRegion, + CustomRegion, + UnusableRegion, + MappedFileRegion, + UserSharedDataRegion, + PebRegion, + Peb32Region, + TebRegion, + Teb32Region, // Not used + StackRegion, + Stack32Region, + HeapRegion, + Heap32Region, + HeapSegmentRegion, + HeapSegment32Region +} PH_MEMORY_REGION_TYPE; + +typedef struct _PH_MEMORY_ITEM +{ + LIST_ENTRY ListEntry; + PH_AVL_LINKS Links; + + union + { + struct + { + PVOID BaseAddress; + PVOID AllocationBase; + ULONG AllocationProtect; + SIZE_T RegionSize; + ULONG State; + ULONG Protect; + ULONG Type; + }; + MEMORY_BASIC_INFORMATION BasicInfo; + }; + + struct _PH_MEMORY_ITEM *AllocationBaseItem; + + SIZE_T CommittedSize; + SIZE_T PrivateSize; + + SIZE_T TotalWorkingSetPages; + SIZE_T PrivateWorkingSetPages; + SIZE_T SharedWorkingSetPages; + SIZE_T ShareableWorkingSetPages; + SIZE_T LockedWorkingSetPages; + + PH_MEMORY_REGION_TYPE RegionType; + + union + { + struct + { + PPH_STRING Text; + BOOLEAN PropertyOfAllocationBase; + } Custom; + struct + { + PPH_STRING FileName; + } MappedFile; + struct + { + HANDLE ThreadId; + } Teb; + struct + { + HANDLE ThreadId; + } Stack; + struct + { + ULONG Index; + } Heap; + struct + { + struct _PH_MEMORY_ITEM *HeapItem; + } HeapSegment; + } u; +} PH_MEMORY_ITEM, *PPH_MEMORY_ITEM; + +typedef struct _PH_MEMORY_ITEM_LIST +{ + HANDLE ProcessId; + PH_AVL_TREE Set; + LIST_ENTRY ListHead; +} PH_MEMORY_ITEM_LIST, *PPH_MEMORY_ITEM_LIST; +// end_phapppub + +BOOLEAN PhMemoryProviderInitialization( + VOID + ); + +VOID PhGetMemoryProtectionString( + _In_ ULONG Protection, + _Out_writes_(17) PWSTR String + ); + +PWSTR PhGetMemoryStateString( + _In_ ULONG State + ); + +PWSTR PhGetMemoryTypeString( + _In_ ULONG Type + ); + +PPH_MEMORY_ITEM PhCreateMemoryItem( + VOID + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhDeleteMemoryItemList( + _In_ PPH_MEMORY_ITEM_LIST List + ); + +PHAPPAPI +PPH_MEMORY_ITEM +NTAPI +PhLookupMemoryItemList( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ PVOID Address + ); + +#define PH_QUERY_MEMORY_IGNORE_FREE 0x1 +#define PH_QUERY_MEMORY_REGION_TYPE 0x2 +#define PH_QUERY_MEMORY_WS_COUNTERS 0x4 + +PHAPPAPI +NTSTATUS +NTAPI +PhQueryMemoryItemList( + _In_ HANDLE ProcessId, + _In_ ULONG Flags, + _Out_ PPH_MEMORY_ITEM_LIST List + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/memsrch.h b/ProcessHacker/include/memsrch.h new file mode 100644 index 0000000..5804a8f --- /dev/null +++ b/ProcessHacker/include/memsrch.h @@ -0,0 +1,61 @@ +#ifndef PH_MEMSRCH_H +#define PH_MEMSRCH_H + +typedef struct _PH_MEMORY_RESULT +{ + LONG RefCount; + PVOID Address; + SIZE_T Length; + PH_STRINGREF Display; +} PH_MEMORY_RESULT, *PPH_MEMORY_RESULT; + +typedef VOID (NTAPI *PPH_MEMORY_RESULT_CALLBACK)( + _In_ _Assume_refs_(1) PPH_MEMORY_RESULT Result, + _In_opt_ PVOID Context + ); + +#define PH_DISPLAY_BUFFER_COUNT (PAGE_SIZE * 2 - 1) + +typedef struct _PH_MEMORY_SEARCH_OPTIONS +{ + BOOLEAN Cancel; + PPH_MEMORY_RESULT_CALLBACK Callback; + PVOID Context; +} PH_MEMORY_SEARCH_OPTIONS, *PPH_MEMORY_SEARCH_OPTIONS; + +typedef struct _PH_MEMORY_STRING_OPTIONS +{ + PH_MEMORY_SEARCH_OPTIONS Header; + + ULONG MinimumLength; + BOOLEAN DetectUnicode; + ULONG MemoryTypeMask; +} PH_MEMORY_STRING_OPTIONS, *PPH_MEMORY_STRING_OPTIONS; + +PVOID PhAllocateForMemorySearch( + _In_ SIZE_T Size + ); + +VOID PhFreeForMemorySearch( + _In_ _Post_invalid_ PVOID Memory + ); + +PVOID PhCreateMemoryResult( + _In_ PVOID Address, + _In_ SIZE_T Length + ); + +VOID PhReferenceMemoryResult( + _In_ PPH_MEMORY_RESULT Result + ); + +VOID PhDereferenceMemoryResult( + _In_ PPH_MEMORY_RESULT Result + ); + +VOID PhDereferenceMemoryResults( + _In_reads_(NumberOfResults) PPH_MEMORY_RESULT *Results, + _In_ ULONG NumberOfResults + ); + +#endif diff --git a/ProcessHacker/include/miniinfo.h b/ProcessHacker/include/miniinfo.h new file mode 100644 index 0000000..4cd9dfb --- /dev/null +++ b/ProcessHacker/include/miniinfo.h @@ -0,0 +1,222 @@ +#ifndef PH_MINIINFO_H +#define PH_MINIINFO_H + +#include + +// begin_phapppub +// Section + +typedef VOID (NTAPI *PPH_MINIINFO_SET_SECTION_TEXT)( + _In_ struct _PH_MINIINFO_SECTION *Section, + _In_opt_ PPH_STRING Text + ); + +typedef struct _PH_MINIINFO_PARAMETERS +{ + HWND ContainerWindowHandle; + HWND MiniInfoWindowHandle; + + HFONT Font; + HFONT MediumFont; + ULONG FontHeight; + ULONG FontAverageWidth; + ULONG MediumFontHeight; + ULONG MediumFontAverageWidth; + + PPH_MINIINFO_SET_SECTION_TEXT SetSectionText; +} PH_MINIINFO_PARAMETERS, *PPH_MINIINFO_PARAMETERS; + +typedef enum _PH_MINIINFO_SECTION_MESSAGE +{ + MiniInfoCreate, + MiniInfoDestroy, + MiniInfoTick, + MiniInfoSectionChanging, // PPH_MINIINFO_SECTION Parameter1 + MiniInfoShowing, // BOOLEAN Parameter1 (Showing) + MiniInfoCreateDialog, // PPH_MINIINFO_CREATE_DIALOG Parameter1 + MaxMiniInfoMessage +} PH_MINIINFO_SECTION_MESSAGE; + +typedef BOOLEAN (NTAPI *PPH_MINIINFO_SECTION_CALLBACK)( + _In_ struct _PH_MINIINFO_SECTION *Section, + _In_ PH_MINIINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +typedef struct _PH_MINIINFO_CREATE_DIALOG +{ + BOOLEAN CustomCreate; + + // Parameters for default create + PVOID Instance; + PWSTR Template; + DLGPROC DialogProc; + PVOID Parameter; +} PH_MINIINFO_CREATE_DIALOG, *PPH_MINIINFO_CREATE_DIALOG; + +#define PH_MINIINFO_SECTION_NO_UPPER_MARGINS 0x1 +// end_phapppub + +// begin_phapppub +typedef struct _PH_MINIINFO_SECTION +{ + // Public + + // Initialization + PH_STRINGREF Name; + ULONG Flags; + PPH_MINIINFO_SECTION_CALLBACK Callback; + PVOID Context; + PVOID Reserved1[3]; + + PPH_MINIINFO_PARAMETERS Parameters; + PVOID Reserved2[3]; +// end_phapppub + + // Private + + struct + { + ULONG SpareFlags : 32; + }; + HWND DialogHandle; + PPH_STRING Text; +// begin_phapppub +} PH_MINIINFO_SECTION, *PPH_MINIINFO_SECTION; +// end_phapppub + +typedef enum _PH_MINIINFO_PIN_TYPE +{ + MiniInfoManualPinType, // User pin + MiniInfoIconPinType, // Notification icon + MiniInfoActivePinType, // Window is active + MiniInfoHoverPinType, // Cursor is over mini info window + MiniInfoChildControlPinType, // Interacting with child control + MaxMiniInfoPinType +} PH_MINIINFO_PIN_TYPE; + +#define PH_MINIINFO_ACTIVATE_WINDOW 0x1 +#define PH_MINIINFO_LOAD_POSITION 0x2 +#define PH_MINIINFO_DONT_CHANGE_SECTION_IF_PINNED 0x4 + +VOID PhPinMiniInformation( + _In_ PH_MINIINFO_PIN_TYPE PinType, + _In_ LONG PinCount, + _In_opt_ ULONG PinDelayMs, + _In_ ULONG Flags, + _In_opt_ PWSTR SectionName, + _In_opt_ PPOINT SourcePoint + ); + +// begin_phapppub +// List section + +typedef enum _PH_MINIINFO_LIST_SECTION_MESSAGE +{ + MiListSectionCreate, + MiListSectionDestroy, + MiListSectionTick, + MiListSectionShowing, // BOOLEAN Parameter1 (Showing) + MiListSectionDialogCreated, // HWND Parameter1 + MiListSectionSortProcessList, // PPH_MINIINFO_LIST_SECTION_SORT_LIST Parameter1 + MiListSectionAssignSortData, // PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA Parameter1 + MiListSectionSortGroupList, // PPH_MINIINFO_LIST_SECTION_SORT_LIST Parameter1 + MiListSectionGetTitleText, // PPH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT Parameter1 + MiListSectionGetUsageText, // PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT Parameter1 + MiListSectionInitializeContextMenu, // PPH_MINIINFO_LIST_SECTION_MENU_INFORMATION Parameter1 + MiListSectionHandleContextMenu, // PPH_MINIINFO_LIST_SECTION_MENU_INFORMATION Parameter1 + MaxMiListSectionMessage +} PH_MINIINFO_LIST_SECTION_MESSAGE; + +typedef BOOLEAN (NTAPI *PPH_MINIINFO_LIST_SECTION_CALLBACK)( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +// The list section performs the following steps when constructing the list of process groups: +// 1. MiListSectionSortProcessList is sent in order to sort the process list. +// 2. A small number of process groups is created from the first few processes in the sorted list (typically high +// resource consumers). +// 3. MiListSectionAssignSortData is sent for each process group so that the user can assign custom sort data to +// each process group. +// 4. MiListSectionSortGroupList is sent in order to ensure that the process groups are correctly sorted by resource +// usage. +// The user also has access to the sort data when handling MiListSectionGetTitleText and MiListSectionGetUsageText. + +typedef struct _PH_MINIINFO_LIST_SECTION_SORT_DATA +{ + PH_TREENEW_NODE DoNotModify; + ULONGLONG UserData[4]; +} PH_MINIINFO_LIST_SECTION_SORT_DATA, *PPH_MINIINFO_LIST_SECTION_SORT_DATA; + +typedef struct _PH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA +{ + PPH_PROCESS_GROUP ProcessGroup; + PPH_MINIINFO_LIST_SECTION_SORT_DATA SortData; +} PH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA, *PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA; + +typedef struct _PH_MINIINFO_LIST_SECTION_SORT_LIST +{ + // MiListSectionSortProcessList: List of PPH_PROCESS_NODE + // MiListSectionSortGroupList: List of PPH_MINIINFO_LIST_SECTION_SORT_DATA + PPH_LIST List; +} PH_MINIINFO_LIST_SECTION_SORT_LIST, *PPH_MINIINFO_LIST_SECTION_SORT_LIST; + +typedef struct _PH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT +{ + PPH_PROCESS_GROUP ProcessGroup; + PPH_MINIINFO_LIST_SECTION_SORT_DATA SortData; + PPH_STRING Title; // Top line (may already contain a string) + PPH_STRING Subtitle; // Bottom line (may already contain a string) + COLORREF TitleColor; + COLORREF SubtitleColor; +} PH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT, *PPH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT; + +typedef struct _PH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT +{ + PPH_PROCESS_GROUP ProcessGroup; + PPH_MINIINFO_LIST_SECTION_SORT_DATA SortData; + PPH_STRING Line1; // Top line + PPH_STRING Line2; // Bottom line + COLORREF Line1Color; + COLORREF Line2Color; +} PH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT, *PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT; + +typedef struct _PH_MINIINFO_LIST_SECTION_MENU_INFORMATION +{ + PPH_PROCESS_GROUP ProcessGroup; + PPH_MINIINFO_LIST_SECTION_SORT_DATA SortData; + PPH_TREENEW_CONTEXT_MENU ContextMenu; + struct _PH_EMENU_ITEM *SelectedItem; +} PH_MINIINFO_LIST_SECTION_MENU_INFORMATION, *PPH_MINIINFO_LIST_SECTION_MENU_INFORMATION; +// end_phapppub + +// begin_phapppub +typedef struct _PH_MINIINFO_LIST_SECTION +{ + // Public + + PPH_MINIINFO_SECTION Section; // State + HWND DialogHandle; // State + HWND TreeNewHandle; // State + PVOID Context; // Initialization + PPH_MINIINFO_LIST_SECTION_CALLBACK Callback; // Initialization +// end_phapppub + + // Private + + PH_LAYOUT_MANAGER LayoutManager; + ULONG RunCount; + LONG SuspendUpdate; + PPH_LIST ProcessGroupList; + PPH_LIST NodeList; + HANDLE SelectedRepresentativeProcessId; + LARGE_INTEGER SelectedRepresentativeCreateTime; +// begin_phapppub +} PH_MINIINFO_LIST_SECTION, *PPH_MINIINFO_LIST_SECTION; +// end_phapppub + +#endif \ No newline at end of file diff --git a/ProcessHacker/include/miniinfop.h b/ProcessHacker/include/miniinfop.h new file mode 100644 index 0000000..9d3b082 --- /dev/null +++ b/ProcessHacker/include/miniinfop.h @@ -0,0 +1,403 @@ +#ifndef PH_MINIINFOP_H +#define PH_MINIINFOP_H + +// Constants + +#define MIP_CONTAINER_CLASSNAME L"ProcessHackerMiniInfo" + +#define MIP_TIMER_PIN_FIRST 1 +#define MIP_TIMER_PIN_LAST (MIP_TIMER_PIN_FIRST + MaxMiniInfoPinType - 1) + +#define MIP_MSG_FIRST (WM_APP + 150) +#define MIP_MSG_UPDATE (WM_APP + 150) +#define MIP_MSG_LAST (WM_APP + 151) + +#define MIP_UNPIN_CHILD_CONTROL_DELAY 1000 +#define MIP_UNPIN_HOVER_DELAY 250 + +#define MIP_REFRESH_AUTOMATICALLY_PINNED 0x1 +#define MIP_REFRESH_AUTOMATICALLY_UNPINNED 0x2 +#define MIP_REFRESH_AUTOMATICALLY_FLAG(Pinned) \ + ((Pinned) ? MIP_REFRESH_AUTOMATICALLY_PINNED : MIP_REFRESH_AUTOMATICALLY_UNPINNED) + +// Misc. + +#define SET_BUTTON_ICON(hwndDlg, Id, Icon) \ + SendMessage(GetDlgItem(hwndDlg, (Id)), BM_SETIMAGE, IMAGE_ICON, (LPARAM)(Icon)) + +// Dialog procedure + +LRESULT CALLBACK PhMipContainerWndProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhMipMiniInfoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Container event handlers + +VOID PhMipContainerOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ); + +VOID PhMipContainerOnActivate( + _In_ ULONG Type, + _In_ BOOLEAN Minimized + ); + +VOID PhMipContainerOnSize( + VOID + ); + +VOID PhMipContainerOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ); + +VOID PhMipContainerOnExitSizeMove( + VOID + ); + +BOOLEAN PhMipContainerOnEraseBkgnd( + _In_ HDC hdc + ); + +VOID PhMipContainerOnTimer( + _In_ ULONG Id + ); + +// Child dialog event handlers + +VOID PhMipOnInitDialog( + VOID + ); + +VOID PhMipOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ); + +VOID PhMipOnCommand( + _In_ ULONG Id, + _In_ ULONG Code + ); + +BOOLEAN PhMipOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ); + +BOOLEAN PhMipOnCtlColorXxx( + _In_ ULONG Message, + _In_ HWND hwnd, + _In_ HDC hdc, + _Out_ HBRUSH *Brush + ); + +BOOLEAN PhMipOnDrawItem( + _In_ ULONG_PTR Id, + _In_ DRAWITEMSTRUCT *DrawItemStruct + ); + +VOID PhMipOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ); + +// Framework + +typedef enum _PH_MIP_ADJUST_PIN_RESULT +{ + NoAdjustPinResult, + ShowAdjustPinResult, + HideAdjustPinResult +} PH_MIP_ADJUST_PIN_RESULT; + +BOOLEAN NTAPI PhMipMessageLoopFilter( + _In_ PMSG Message, + _In_ PVOID Context + ); + +VOID NTAPI PhMipUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +PH_MIP_ADJUST_PIN_RESULT PhMipAdjustPin( + _In_ PH_MINIINFO_PIN_TYPE PinType, + _In_ LONG PinCount + ); + +VOID PhMipCalculateWindowRectangle( + _In_ PPOINT SourcePoint, + _Out_ PPH_RECTANGLE WindowRectangle + ); + +VOID PhMipInitializeParameters( + VOID + ); + +PPH_MINIINFO_SECTION PhMipCreateSection( + _In_ PPH_MINIINFO_SECTION Template + ); + +VOID PhMipDestroySection( + _In_ PPH_MINIINFO_SECTION Section + ); + +PPH_MINIINFO_SECTION PhMipFindSection( + _In_ PPH_STRINGREF Name + ); + +PPH_MINIINFO_SECTION PhMipCreateInternalSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_SECTION_CALLBACK Callback + ); + +VOID PhMipCreateSectionDialog( + _In_ PPH_MINIINFO_SECTION Section + ); + +VOID PhMipChangeSection( + _In_ PPH_MINIINFO_SECTION NewSection + ); + +VOID PhMipSetSectionText( + _In_ struct _PH_MINIINFO_SECTION *Section, + _In_opt_ PPH_STRING Text + ); + +VOID PhMipUpdateSectionText( + _In_ PPH_MINIINFO_SECTION Section + ); + +VOID PhMipLayout( + VOID + ); + +VOID PhMipBeginChildControlPin( + VOID + ); + +VOID PhMipEndChildControlPin( + VOID + ); + +VOID PhMipRefresh( + VOID + ); + +VOID PhMipToggleRefreshAutomatically( + VOID + ); + +VOID PhMipSetPinned( + _In_ BOOLEAN Pinned + ); + +VOID PhMipShowSectionMenu( + VOID + ); + +VOID PhMipShowOptionsMenu( + VOID + ); + +LRESULT CALLBACK PhMipSectionControlHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// List-based section + +#define MIP_MAX_PROCESS_GROUPS 15 +#define MIP_SINGLE_COLUMN_ID 0 + +#define MIP_CELL_PADDING 5 +#define MIP_ICON_PADDING 3 +#define MIP_INNER_PADDING 3 + +typedef struct _PH_MIP_GROUP_NODE +{ + union + { + PH_TREENEW_NODE Node; + PH_MINIINFO_LIST_SECTION_SORT_DATA SortData; + }; + PPH_PROCESS_GROUP ProcessGroup; + HANDLE RepresentativeProcessId; + LARGE_INTEGER RepresentativeCreateTime; + BOOLEAN RepresentativeIsHung; + + PPH_STRING TooltipText; +} PH_MIP_GROUP_NODE, *PPH_MIP_GROUP_NODE; + +PPH_MINIINFO_LIST_SECTION PhMipCreateListSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_LIST_SECTION Template + ); + +PPH_MINIINFO_LIST_SECTION PhMipCreateInternalListSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_LIST_SECTION_CALLBACK Callback + ); + +BOOLEAN PhMipListSectionCallback( + _In_ PPH_MINIINFO_SECTION Section, + _In_ PH_MINIINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +INT_PTR CALLBACK PhMipListSectionDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhMipListSectionSortFunction( + _In_ PPH_LIST List, + _In_opt_ PVOID Context + ); + +VOID PhMipTickListSection( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ); + +VOID PhMipClearListSection( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ); + +LONG PhMipCalculateRowHeight( + VOID + ); + +PPH_MIP_GROUP_NODE PhMipAddGroupNode( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_PROCESS_GROUP ProcessGroup + ); + +VOID PhMipDestroyGroupNode( + _In_ PPH_MIP_GROUP_NODE Node + ); + +BOOLEAN PhMipListSectionTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +PPH_STRING PhMipGetGroupNodeTooltip( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_MIP_GROUP_NODE Node + ); + +PPH_MIP_GROUP_NODE PhMipGetSelectedGroupNode( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ); + +VOID PhMipShowListSectionContextMenu( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ); + +VOID PhMipHandleListSectionCommand( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_PROCESS_GROUP ProcessGroup, + _In_ ULONG Id + ); + +// CPU section + +BOOLEAN PhMipCpuListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl PhMipCpuListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl PhMipCpuListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +// Commit charge section + +BOOLEAN PhMipCommitListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl PhMipCommitListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl PhMipCommitListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +// Physical memory section + +BOOLEAN PhMipPhysicalListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl PhMipPhysicalListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl PhMipPhysicalListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +// I/O section + +BOOLEAN PhMipIoListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl PhMipIoListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl PhMipIoListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +#endif \ No newline at end of file diff --git a/ProcessHacker/include/modlist.h b/ProcessHacker/include/modlist.h new file mode 100644 index 0000000..d665a65 --- /dev/null +++ b/ProcessHacker/include/modlist.h @@ -0,0 +1,132 @@ +#ifndef PH_MODLIST_H +#define PH_MODLIST_H + +#include +#include + +// Columns + +#define PHMOTLC_NAME 0 +#define PHMOTLC_BASEADDRESS 1 +#define PHMOTLC_SIZE 2 +#define PHMOTLC_DESCRIPTION 3 + +#define PHMOTLC_COMPANYNAME 4 +#define PHMOTLC_VERSION 5 +#define PHMOTLC_FILENAME 6 + +#define PHMOTLC_TYPE 7 +#define PHMOTLC_LOADCOUNT 8 +#define PHMOTLC_VERIFICATIONSTATUS 9 +#define PHMOTLC_VERIFIEDSIGNER 10 +#define PHMOTLC_ASLR 11 +#define PHMOTLC_TIMESTAMP 12 +#define PHMOTLC_CFGUARD 13 +#define PHMOTLC_LOADTIME 14 +#define PHMOTLC_LOADREASON 15 +#define PHMOTLC_FILEMODIFIEDTIME 16 +#define PHMOTLC_FILESIZE 17 + +#define PHMOTLC_MAXIMUM 18 + +// begin_phapppub +typedef struct _PH_MODULE_NODE +{ + PH_TREENEW_NODE Node; + + PH_SH_STATE ShState; + + PPH_MODULE_ITEM ModuleItem; +// end_phapppub + + PH_STRINGREF TextCache[PHMOTLC_MAXIMUM]; + + ULONG ValidMask; + + PPH_STRING TooltipText; + + PPH_STRING SizeText; + WCHAR LoadCountText[PH_INT32_STR_LEN_1]; + PPH_STRING TimeStampText; + PPH_STRING LoadTimeText; + PPH_STRING FileModifiedTimeText; + PPH_STRING FileSizeText; +// begin_phapppub +} PH_MODULE_NODE, *PPH_MODULE_NODE; +// end_phapppub + +typedef struct _PH_MODULE_LIST_CONTEXT +{ + HWND ParentWindowHandle; + HWND TreeNewHandle; + ULONG TreeNewSortColumn; + PH_SORT_ORDER TreeNewSortOrder; + PH_CM_MANAGER Cm; + + PPH_HASHTABLE NodeHashtable; + PPH_LIST NodeList; + + BOOLEAN EnableStateHighlighting; + PPH_POINTER_LIST NodeStateList; + + HFONT BoldFont; +} PH_MODULE_LIST_CONTEXT, *PPH_MODULE_LIST_CONTEXT; + +VOID PhInitializeModuleList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_MODULE_LIST_CONTEXT Context + ); + +VOID PhDeleteModuleList( + _In_ PPH_MODULE_LIST_CONTEXT Context + ); + +VOID PhLoadSettingsModuleList( + _Inout_ PPH_MODULE_LIST_CONTEXT Context + ); + +VOID PhSaveSettingsModuleList( + _Inout_ PPH_MODULE_LIST_CONTEXT Context + ); + +PPH_MODULE_NODE PhAddModuleNode( + _Inout_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_ITEM ModuleItem, + _In_ ULONG RunId + ); + +PPH_MODULE_NODE PhFindModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_ITEM ModuleItem + ); + +VOID PhRemoveModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_NODE ModuleNode + ); + +VOID PhUpdateModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_NODE ModuleNode + ); + +VOID PhTickModuleNodes( + _In_ PPH_MODULE_LIST_CONTEXT Context + ); + +PPH_MODULE_ITEM PhGetSelectedModuleItem( + _In_ PPH_MODULE_LIST_CONTEXT Context + ); + +VOID PhGetSelectedModuleItems( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _Out_ PPH_MODULE_ITEM **Modules, + _Out_ PULONG NumberOfModules + ); + +VOID PhDeselectAllModuleNodes( + _In_ PPH_MODULE_LIST_CONTEXT Context + ); + +#endif diff --git a/ProcessHacker/include/modprv.h b/ProcessHacker/include/modprv.h new file mode 100644 index 0000000..b08bc9c --- /dev/null +++ b/ProcessHacker/include/modprv.h @@ -0,0 +1,80 @@ +#ifndef PH_MODPRV_H +#define PH_MODPRV_H + +extern PPH_OBJECT_TYPE PhModuleProviderType; +extern PPH_OBJECT_TYPE PhModuleItemType; + +// begin_phapppub +typedef struct _PH_MODULE_ITEM +{ + PVOID BaseAddress; + ULONG Size; + ULONG Flags; + ULONG Type; + USHORT LoadReason; + USHORT LoadCount; + PPH_STRING Name; + PPH_STRING FileName; + PH_IMAGE_VERSION_INFO VersionInfo; + + WCHAR BaseAddressString[PH_PTR_STR_LEN_1]; + + BOOLEAN IsFirst; + BOOLEAN JustProcessed; + + enum _VERIFY_RESULT VerifyResult; + PPH_STRING VerifySignerName; + + ULONG ImageTimeDateStamp; + USHORT ImageCharacteristics; + USHORT ImageDllCharacteristics; + + LARGE_INTEGER LoadTime; + + LARGE_INTEGER FileLastWriteTime; + LARGE_INTEGER FileEndOfFile; +} PH_MODULE_ITEM, *PPH_MODULE_ITEM; + +typedef struct _PH_MODULE_PROVIDER +{ + PPH_HASHTABLE ModuleHashtable; + PH_FAST_LOCK ModuleHashtableLock; + PH_CALLBACK ModuleAddedEvent; + PH_CALLBACK ModuleModifiedEvent; + PH_CALLBACK ModuleRemovedEvent; + PH_CALLBACK UpdatedEvent; + + HANDLE ProcessId; + HANDLE ProcessHandle; + PPH_STRING PackageFullName; + SLIST_HEADER QueryListHead; + NTSTATUS RunStatus; +} PH_MODULE_PROVIDER, *PPH_MODULE_PROVIDER; +// end_phapppub + +BOOLEAN PhModuleProviderInitialization( + VOID + ); + +PPH_MODULE_PROVIDER PhCreateModuleProvider( + _In_ HANDLE ProcessId + ); + +PPH_MODULE_ITEM PhCreateModuleItem( + VOID + ); + +PPH_MODULE_ITEM PhReferenceModuleItem( + _In_ PPH_MODULE_PROVIDER ModuleProvider, + _In_ PVOID BaseAddress + ); + +VOID PhDereferenceAllModuleItems( + _In_ PPH_MODULE_PROVIDER ModuleProvider + ); + +VOID PhModuleProviderUpdate( + _In_ PVOID Object + ); + +#endif diff --git a/ProcessHacker/include/netlist.h b/ProcessHacker/include/netlist.h new file mode 100644 index 0000000..13d09c3 --- /dev/null +++ b/ProcessHacker/include/netlist.h @@ -0,0 +1,119 @@ +#ifndef PH_NETLIST_H +#define PH_NETLIST_H + +#include + +// Columns + +#define PHNETLC_PROCESS 0 +#define PHNETLC_LOCALADDRESS 1 +#define PHNETLC_LOCALPORT 2 +#define PHNETLC_REMOTEADDRESS 3 +#define PHNETLC_REMOTEPORT 4 +#define PHNETLC_PROTOCOL 5 +#define PHNETLC_STATE 6 +#define PHNETLC_OWNER 7 +#define PHNETLC_TIMESTAMP 8 +#define PHNETLC_MAXIMUM 9 + +// begin_phapppub +typedef struct _PH_NETWORK_NODE +{ + PH_TREENEW_NODE Node; + + PH_SH_STATE ShState; + + PPH_NETWORK_ITEM NetworkItem; +// end_phapppub + + PH_STRINGREF TextCache[PHNETLC_MAXIMUM]; + + LONG UniqueId; + PPH_STRING ProcessNameText; + PH_STRINGREF LocalAddressText; + PH_STRINGREF RemoteAddressText; + PPH_STRING TimeStampText; + + PPH_STRING TooltipText; +// begin_phapppub +} PH_NETWORK_NODE, *PPH_NETWORK_NODE; +// end_phapppub + +VOID PhNetworkTreeListInitialization( + VOID + ); + +VOID PhInitializeNetworkTreeList( + _In_ HWND hwnd + ); + +VOID PhLoadSettingsNetworkTreeList( + VOID + ); + +VOID PhSaveSettingsNetworkTreeList( + VOID + ); + +// begin_phapppub +PHAPPAPI +struct _PH_TN_FILTER_SUPPORT * +NTAPI +PhGetFilterSupportNetworkTreeList( + VOID + ); +// end_phapppub + +PPH_NETWORK_NODE PhAddNetworkNode( + _In_ PPH_NETWORK_ITEM NetworkItem, + _In_ ULONG RunId + ); + +// begin_phapppub +PHAPPAPI +PPH_NETWORK_NODE +NTAPI +PhFindNetworkNode( + _In_ PPH_NETWORK_ITEM NetworkItem + ); +// end_phapppub + +VOID PhRemoveNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ); + +VOID PhUpdateNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ); + +VOID PhTickNetworkNodes( + VOID + ); + +PPH_NETWORK_ITEM PhGetSelectedNetworkItem( + VOID + ); + +VOID PhGetSelectedNetworkItems( + _Out_ PPH_NETWORK_ITEM **NetworkItems, + _Out_ PULONG NumberOfNetworkItems + ); + +VOID PhDeselectAllNetworkNodes( + VOID + ); + +VOID PhSelectAndEnsureVisibleNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ); + +VOID PhCopyNetworkList( + VOID + ); + +VOID PhWriteNetworkList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ); + +#endif diff --git a/ProcessHacker/include/netprv.h b/ProcessHacker/include/netprv.h new file mode 100644 index 0000000..beaecea --- /dev/null +++ b/ProcessHacker/include/netprv.h @@ -0,0 +1,86 @@ +#ifndef PH_NETPRV_H +#define PH_NETPRV_H + +extern PPH_OBJECT_TYPE PhNetworkItemType; +PHAPPAPI extern PH_CALLBACK PhNetworkItemAddedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhNetworkItemModifiedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhNetworkItemRemovedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhNetworkItemsUpdatedEvent; // phapppub + +extern BOOLEAN PhEnableNetworkProviderResolve; + +// begin_phapppub +#define PH_NETWORK_OWNER_INFO_SIZE 16 + +typedef struct _PH_NETWORK_ITEM +{ + ULONG ProtocolType; + PH_IP_ENDPOINT LocalEndpoint; + PH_IP_ENDPOINT RemoteEndpoint; + ULONG State; + HANDLE ProcessId; + + PPH_STRING ProcessName; + HICON ProcessIcon; + BOOLEAN ProcessIconValid; + PPH_STRING OwnerName; + + BOOLEAN JustResolved; + + WCHAR LocalAddressString[65]; + WCHAR LocalPortString[PH_INT32_STR_LEN_1]; + WCHAR RemoteAddressString[65]; + WCHAR RemotePortString[PH_INT32_STR_LEN_1]; + PPH_STRING LocalHostString; + PPH_STRING RemoteHostString; + + LARGE_INTEGER CreateTime; + ULONGLONG OwnerInfo[PH_NETWORK_OWNER_INFO_SIZE]; +} PH_NETWORK_ITEM, *PPH_NETWORK_ITEM; +// end_phapppub + +BOOLEAN PhNetworkProviderInitialization( + VOID + ); + +PPH_NETWORK_ITEM PhCreateNetworkItem( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_NETWORK_ITEM +NTAPI +PhReferenceNetworkItem( + _In_ ULONG ProtocolType, + _In_ PPH_IP_ENDPOINT LocalEndpoint, + _In_ PPH_IP_ENDPOINT RemoteEndpoint, + _In_ HANDLE ProcessId + ); +// end_phapppub + +PPH_STRING PhGetHostNameFromAddress( + _In_ PPH_IP_ADDRESS Address + ); + +VOID PhNetworkProviderUpdate( + _In_ PVOID Object + ); + +// begin_phapppub +PHAPPAPI +PWSTR +NTAPI +PhGetProtocolTypeName( + _In_ ULONG ProtocolType + ); + +PHAPPAPI +PWSTR +NTAPI +PhGetTcpStateName( + _In_ ULONG State + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/notifico.h b/ProcessHacker/include/notifico.h new file mode 100644 index 0000000..b0a8d61 --- /dev/null +++ b/ProcessHacker/include/notifico.h @@ -0,0 +1,171 @@ +#ifndef PH_NOTIFICO_H +#define PH_NOTIFICO_H + +#define PH_ICON_MINIMUM 0x1 +#define PH_ICON_CPU_HISTORY 0x1 +#define PH_ICON_IO_HISTORY 0x2 +#define PH_ICON_COMMIT_HISTORY 0x4 +#define PH_ICON_PHYSICAL_HISTORY 0x8 +#define PH_ICON_CPU_USAGE 0x10 +#define PH_ICON_DEFAULT_MAXIMUM 0x20 +#define PH_ICON_DEFAULT_ALL 0x1f + +#define PH_ICON_LIMIT 0x80000000 +#define PH_ICON_ALL 0xffffffff + +// begin_phapppub +typedef VOID (NTAPI *PPH_NF_UPDATE_REGISTERED_ICON)( + _In_ struct _PH_NF_ICON *Icon + ); + +typedef VOID (NTAPI *PPH_NF_BEGIN_BITMAP)( + _Out_ PULONG Width, + _Out_ PULONG Height, + _Out_ HBITMAP *Bitmap, + _Out_opt_ PVOID *Bits, + _Out_ HDC *Hdc, + _Out_ HBITMAP *OldBitmap + ); + +typedef struct _PH_NF_POINTERS +{ + PPH_NF_UPDATE_REGISTERED_ICON UpdateRegisteredIcon; + PPH_NF_BEGIN_BITMAP BeginBitmap; +} PH_NF_POINTERS, *PPH_NF_POINTERS; + +#define PH_NF_UPDATE_IS_BITMAP 0x1 +#define PH_NF_UPDATE_DESTROY_RESOURCE 0x2 + +typedef VOID (NTAPI *PPH_NF_ICON_UPDATE_CALLBACK)( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ); + +typedef BOOLEAN (NTAPI *PPH_NF_ICON_MESSAGE_CALLBACK)( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ); + +// Special messages +// The message type is stored in LOWORD(LParam), and the message data is in WParam. + +#define PH_NF_MSG_SHOWMINIINFOSECTION (WM_APP + 1) + +typedef struct _PH_NF_MSG_SHOWMINIINFOSECTION_DATA +{ + PWSTR SectionName; // NULL to leave unchanged +} PH_NF_MSG_SHOWMINIINFOSECTION_DATA, *PPH_NF_MSG_SHOWMINIINFOSECTION_DATA; + +// Structures and internal functions + +#define PH_NF_ICON_UNAVAILABLE 0x1 +#define PH_NF_ICON_SHOW_MINIINFO 0x2 +// end_phapppub + +// begin_phapppub +typedef struct _PH_NF_ICON +{ + // Public + + struct _PH_PLUGIN *Plugin; + ULONG SubId; + PVOID Context; + PPH_NF_POINTERS Pointers; +// end_phapppub + + // Private + + PWSTR Text; + ULONG Flags; + ULONG IconId; + PPH_NF_ICON_UPDATE_CALLBACK UpdateCallback; + PPH_NF_ICON_MESSAGE_CALLBACK MessageCallback; +// begin_phapppub +} PH_NF_ICON, *PPH_NF_ICON; +// end_phapppub + +VOID PhNfLoadStage1( + VOID + ); + +VOID PhNfLoadStage2( + VOID + ); + +VOID PhNfSaveSettings( + VOID + ); + +VOID PhNfUninitialization( + VOID + ); + +VOID PhNfForwardMessage( + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ); + +ULONG PhNfGetMaximumIconId( + VOID + ); + +ULONG PhNfTestIconMask( + _In_ ULONG Id + ); + +VOID PhNfSetVisibleIcon( + _In_ ULONG Id, + _In_ BOOLEAN Visible + ); + +BOOLEAN PhNfShowBalloonTip( + _In_opt_ ULONG Id, + _In_ PWSTR Title, + _In_ PWSTR Text, + _In_ ULONG Timeout, + _In_ ULONG Flags + ); + +HICON PhNfBitmapToIcon( + _In_ HBITMAP Bitmap + ); + +PPH_NF_ICON PhNfRegisterIcon( + _In_ struct _PH_PLUGIN *Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PWSTR Text, + _In_ ULONG Flags, + _In_opt_ PPH_NF_ICON_UPDATE_CALLBACK UpdateCallback, + _In_opt_ PPH_NF_ICON_MESSAGE_CALLBACK MessageCallback + ); + +PPH_NF_ICON PhNfGetIconById( + _In_ ULONG Id + ); + +PPH_NF_ICON PhNfFindIcon( + _In_ PPH_STRINGREF PluginName, + _In_ ULONG SubId + ); + +VOID PhNfNotifyMiniInfoPinned( + _In_ BOOLEAN Pinned + ); + +// begin_phapppub +// Public registration data + +typedef struct _PH_NF_ICON_REGISTRATION_DATA +{ + PPH_NF_ICON_UPDATE_CALLBACK UpdateCallback; + PPH_NF_ICON_MESSAGE_CALLBACK MessageCallback; +} PH_NF_ICON_REGISTRATION_DATA, *PPH_NF_ICON_REGISTRATION_DATA; +// end_phapppub + +#endif diff --git a/ProcessHacker/include/notificop.h b/ProcessHacker/include/notificop.h new file mode 100644 index 0000000..5c512ec --- /dev/null +++ b/ProcessHacker/include/notificop.h @@ -0,0 +1,107 @@ +#ifndef PH_NOTIFICOP_H +#define PH_NOTIFICOP_H + +typedef struct _PH_NF_BITMAP +{ + BOOLEAN Initialized; + HDC Hdc; + BITMAPINFOHEADER Header; + HBITMAP Bitmap; + PVOID Bits; +} PH_NF_BITMAP, *PPH_NF_BITMAP; + +HICON PhNfpGetBlackIcon( + VOID + ); + +BOOLEAN PhNfpAddNotifyIcon( + _In_ ULONG Id + ); + +BOOLEAN PhNfpRemoveNotifyIcon( + _In_ ULONG Id + ); + +BOOLEAN PhNfpModifyNotifyIcon( + _In_ ULONG Id, + _In_ ULONG Flags, + _In_opt_ PPH_STRING Text, + _In_opt_ HICON Icon + ); + +VOID PhNfpProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID PhNfpUpdateRegisteredIcon( + _In_ PPH_NF_ICON Icon + ); + +VOID PhNfpBeginBitmap( + _Out_ PULONG Width, + _Out_ PULONG Height, + _Out_ HBITMAP *Bitmap, + _Out_opt_ PVOID *Bits, + _Out_ HDC *Hdc, + _Out_ HBITMAP *OldBitmap + ); + +VOID PhNfpBeginBitmap2( + _Inout_ PPH_NF_BITMAP Context, + _Out_ PULONG Width, + _Out_ PULONG Height, + _Out_ HBITMAP *Bitmap, + _Out_opt_ PVOID *Bits, + _Out_ HDC *Hdc, + _Out_ HBITMAP *OldBitmap + ); + +VOID PhNfpUpdateIconCpuHistory( + VOID + ); + +VOID PhNfpUpdateIconIoHistory( + VOID + ); + +VOID PhNfpUpdateIconCommitHistory( + VOID + ); + +VOID PhNfpUpdateIconPhysicalHistory( + VOID + ); + +VOID PhNfpUpdateIconCpuUsage( + VOID + ); + +BOOLEAN PhNfpGetShowMiniInfoSectionData( + _In_ ULONG IconIndex, + _In_ PPH_NF_ICON RegisteredIcon, + _Out_ PPH_NF_MSG_SHOWMINIINFOSECTION_DATA Data + ); + +#define NFP_ICON_CLICK_ACTIVATE_DELAY 140 +#define NFP_ICON_RESTORE_HOVER_DELAY 1000 + +VOID PhNfpIconClickActivateTimerProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ UINT_PTR idEvent, + _In_ DWORD dwTime + ); + +VOID PhNfpDisableHover( + VOID + ); + +VOID PhNfpIconRestoreHoverTimerProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ UINT_PTR idEvent, + _In_ DWORD dwTime + ); + +#endif diff --git a/ProcessHacker/include/phapp.h b/ProcessHacker/include/phapp.h new file mode 100644 index 0000000..ff7bede --- /dev/null +++ b/ProcessHacker/include/phapp.h @@ -0,0 +1,1577 @@ +#ifndef PHAPP_H +#define PHAPP_H + +#if !defined(_PHAPP_) +#define PHAPPAPI __declspec(dllimport) +#else +#define PHAPPAPI __declspec(dllexport) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../resource.h" + +#define KPH_ERROR_MESSAGE (L"KProcessHacker does not support your operating system " \ + L"or could not be loaded. Make sure Process Hacker is running " \ + L"with administrative privileges.") + +typedef struct _PH_SYMBOL_PROVIDER *PPH_SYMBOL_PROVIDER; // phapppub + +// main + +typedef struct _PH_STARTUP_PARAMETERS +{ + union + { + struct + { + ULONG NoSettings : 1; + ULONG ShowVisible : 1; + ULONG ShowHidden : 1; + ULONG CommandMode : 1; + ULONG NoKph : 1; + ULONG InstallKph : 1; + ULONG UninstallKph : 1; + ULONG Debug : 1; + ULONG ShowOptions : 1; + ULONG PhSvc : 1; + ULONG NoPlugins : 1; + ULONG NewInstance : 1; + ULONG Elevate : 1; + ULONG Silent : 1; + ULONG Help : 1; + ULONG Spare : 17; + }; + ULONG Flags; + }; + + PPH_STRING SettingsFileName; + + HWND WindowHandle; + POINT Point; + + PPH_STRING CommandType; + PPH_STRING CommandObject; + PPH_STRING CommandAction; + PPH_STRING CommandValue; + + PPH_STRING RunAsServiceMode; + + ULONG SelectPid; + ULONG PriorityClass; + + PPH_LIST PluginParameters; + PPH_STRING SelectTab; + PPH_STRING SysInfo; +} PH_STARTUP_PARAMETERS, *PPH_STARTUP_PARAMETERS; + +extern PPH_STRING PhApplicationDirectory; +extern PPH_STRING PhApplicationFileName; +PHAPPAPI extern HFONT PhApplicationFont; // phapppub +extern PPH_STRING PhCurrentUserName; +extern HINSTANCE PhInstanceHandle; +extern PPH_STRING PhLocalSystemName; +extern BOOLEAN PhPluginsEnabled; +extern PPH_STRING PhSettingsFileName; +extern PH_INTEGER_PAIR PhSmallIconSize; +extern PH_INTEGER_PAIR PhLargeIconSize; +extern PH_STARTUP_PARAMETERS PhStartupParameters; + +extern PH_PROVIDER_THREAD PhPrimaryProviderThread; +extern PH_PROVIDER_THREAD PhSecondaryProviderThread; + +#define PH_SCALE_DPI(Value) PhMultiplyDivide(Value, PhGlobalDpi, 96) + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhRegisterDialog( + _In_ HWND DialogWindowHandle + ); + +PHAPPAPI +VOID +NTAPI +PhUnregisterDialog( + _In_ HWND DialogWindowHandle + ); + +typedef BOOLEAN (NTAPI *PPH_MESSAGE_LOOP_FILTER)( + _In_ PMSG Message, + _In_ PVOID Context + ); + +typedef struct _PH_MESSAGE_LOOP_FILTER_ENTRY +{ + PPH_MESSAGE_LOOP_FILTER Filter; + PVOID Context; +} PH_MESSAGE_LOOP_FILTER_ENTRY, *PPH_MESSAGE_LOOP_FILTER_ENTRY; + +PHAPPAPI +struct _PH_MESSAGE_LOOP_FILTER_ENTRY * +NTAPI +PhRegisterMessageLoopFilter( + _In_ PPH_MESSAGE_LOOP_FILTER Filter, + _In_opt_ PVOID Context + ); + +PHAPPAPI +VOID +NTAPI +PhUnregisterMessageLoopFilter( + _In_ struct _PH_MESSAGE_LOOP_FILTER_ENTRY *FilterEntry + ); +// end_phapppub + +VOID PhInitializeFont( + _In_ HWND hWnd + ); + +// appsup + +extern GUID XP_CONTEXT_GUID; +extern GUID VISTA_CONTEXT_GUID; +extern GUID WIN7_CONTEXT_GUID; +extern GUID WIN8_CONTEXT_GUID; +extern GUID WINBLUE_CONTEXT_GUID; +extern GUID WINTHRESHOLD_CONTEXT_GUID; + +typedef struct PACKAGE_ID PACKAGE_ID; + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhGetProcessIsSuspended( + _In_ PSYSTEM_PROCESS_INFORMATION Process + ); +// end_phapppub + +NTSTATUS PhGetProcessSwitchContext( + _In_ HANDLE ProcessHandle, + _Out_ PGUID Guid + ); + +PPH_STRING PhGetProcessPackageFullName( + _In_ HANDLE ProcessHandle + ); + +PACKAGE_ID *PhPackageIdFromFullName( + _In_ PWSTR PackageFullName + ); + +PPH_STRING PhGetPackagePath( + _In_ PACKAGE_ID *PackageId + ); + +// begin_phapppub +typedef enum _PH_KNOWN_PROCESS_TYPE +{ + UnknownProcessType, + SystemProcessType, // ntoskrnl/ntkrnlpa/... + SessionManagerProcessType, // smss + WindowsSubsystemProcessType, // csrss + WindowsStartupProcessType, // wininit + ServiceControlManagerProcessType, // services + LocalSecurityAuthorityProcessType, // lsass + LocalSessionManagerProcessType, // lsm + WindowsLogonProcessType, // winlogon + ServiceHostProcessType, // svchost + RunDllAsAppProcessType, // rundll32 + ComSurrogateProcessType, // dllhost + TaskHostProcessType, // taskeng, taskhost, taskhostex + ExplorerProcessType, // explorer + UmdfHostProcessType, // wudfhost + MaximumProcessType, + KnownProcessTypeMask = 0xffff, + + KnownProcessWow64 = 0x20000 +} PH_KNOWN_PROCESS_TYPE; + +PHAPPAPI +NTSTATUS +NTAPI +PhGetProcessKnownType( + _In_ HANDLE ProcessHandle, + _Out_ PH_KNOWN_PROCESS_TYPE *KnownProcessType + ); + +typedef union _PH_KNOWN_PROCESS_COMMAND_LINE +{ + struct + { + PPH_STRING GroupName; + } ServiceHost; + struct + { + PPH_STRING FileName; + PPH_STRING ProcedureName; + } RunDllAsApp; + struct + { + GUID Guid; + PPH_STRING Name; // optional + PPH_STRING FileName; // optional + } ComSurrogate; +} PH_KNOWN_PROCESS_COMMAND_LINE, *PPH_KNOWN_PROCESS_COMMAND_LINE; + +PHAPPAPI +BOOLEAN +NTAPI +PhaGetProcessKnownCommandLine( + _In_ PPH_STRING CommandLine, + _In_ PH_KNOWN_PROCESS_TYPE KnownProcessType, + _Out_ PPH_KNOWN_PROCESS_COMMAND_LINE KnownCommandLine + ); +// end_phapppub + +VOID PhEnumChildWindows( + _In_opt_ HWND hWnd, + _In_ ULONG Limit, + _In_ WNDENUMPROC Callback, + _In_ LPARAM lParam + ); + +HWND PhGetProcessMainWindow( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle + ); + +PPH_STRING PhGetServiceRelevantFileName( + _In_ PPH_STRINGREF ServiceName, + _In_ SC_HANDLE ServiceHandle + ); + +PPH_STRING PhEscapeStringForDelimiter( + _In_ PPH_STRING String, + _In_ WCHAR Delimiter + ); + +PPH_STRING PhUnescapeStringForDelimiter( + _In_ PPH_STRING String, + _In_ WCHAR Delimiter + ); + +typedef struct mxml_node_s mxml_node_t; + +PPH_STRING PhGetOpaqueXmlNodeText( + _In_ mxml_node_t *node + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhSearchOnlineString( + _In_ HWND hWnd, + _In_ PWSTR String + ); + +PHAPPAPI +VOID +NTAPI +PhShellExecuteUserString( + _In_ HWND hWnd, + _In_ PWSTR Setting, + _In_ PWSTR String, + _In_ BOOLEAN UseShellExecute, + _In_opt_ PWSTR ErrorMessage + ); + +PHAPPAPI +VOID +NTAPI +PhLoadSymbolProviderOptions( + _Inout_ PPH_SYMBOL_PROVIDER SymbolProvider + ); +// end_phapppub + +PWSTR PhMakeContextAtom( + VOID + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhCopyListViewInfoTip( + _Inout_ LPNMLVGETINFOTIP GetInfoTip, + _In_ PPH_STRINGREF Tip + ); + +PHAPPAPI +VOID +NTAPI +PhCopyListView( + _In_ HWND ListViewHandle + ); + +PHAPPAPI +VOID +NTAPI +PhHandleListViewNotifyForCopy( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle + ); +// end_phapppub + +#define PH_LIST_VIEW_CTRL_C_BEHAVIOR 0x1 +#define PH_LIST_VIEW_CTRL_A_BEHAVIOR 0x2 +#define PH_LIST_VIEW_DEFAULT_1_BEHAVIORS (PH_LIST_VIEW_CTRL_C_BEHAVIOR | PH_LIST_VIEW_CTRL_A_BEHAVIOR) + +VOID PhHandleListViewNotifyBehaviors( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle, + _In_ ULONG Behaviors + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhGetListViewContextMenuPoint( + _In_ HWND ListViewHandle, + _Out_ PPOINT Point + ); +// end_phapppub + +HFONT PhDuplicateFontWithNewWeight( + _In_ HFONT Font, + _In_ LONG NewWeight + ); + +VOID PhSetWindowOpacity( + _In_ HWND WindowHandle, + _In_ ULONG OpacityPercent + ); + +#define PH_OPACITY_TO_ID(Opacity) (ID_OPACITY_10 + (10 - (Opacity) / 10) - 1) +#define PH_ID_TO_OPACITY(Id) (100 - (((Id) - ID_OPACITY_10) + 1) * 10) + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhLoadWindowPlacementFromSetting( + _In_opt_ PWSTR PositionSettingName, + _In_opt_ PWSTR SizeSettingName, + _In_ HWND WindowHandle + ); + +PHAPPAPI +VOID +NTAPI +PhSaveWindowPlacementToSetting( + _In_opt_ PWSTR PositionSettingName, + _In_opt_ PWSTR SizeSettingName, + _In_ HWND WindowHandle + ); + +PHAPPAPI +VOID +NTAPI +PhLoadListViewColumnsFromSetting( + _In_ PWSTR Name, + _In_ HWND ListViewHandle + ); + +PHAPPAPI +VOID +NTAPI +PhSaveListViewColumnsToSetting( + _In_ PWSTR Name, + _In_ HWND ListViewHandle + ); + +PHAPPAPI +PPH_STRING +NTAPI +PhGetPhVersion( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhGetPhVersionNumbers( + _Out_opt_ PULONG MajorVersion, + _Out_opt_ PULONG MinorVersion, + _Reserved_ PULONG Reserved, + _Out_opt_ PULONG RevisionNumber + ); + +PHAPPAPI +VOID +NTAPI +PhWritePhTextHeader( + _Inout_ PPH_FILE_STREAM FileStream + ); + +#define PH_SHELL_APP_PROPAGATE_PARAMETERS 0x1 +#define PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY 0x2 +#define PH_SHELL_APP_PROPAGATE_PARAMETERS_FORCE_SETTINGS 0x4 + +PHAPPAPI +BOOLEAN +NTAPI +PhShellProcessHacker( + _In_opt_ HWND hWnd, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_ ULONG AppFlags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ); +// end_phapppub + +BOOLEAN PhShellProcessHackerEx( + _In_opt_ HWND hWnd, + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_ ULONG AppFlags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ); + +BOOLEAN PhCreateProcessIgnoreIfeoDebugger( + _In_ PWSTR FileName + ); + +// begin_phapppub +typedef struct _PH_TN_COLUMN_MENU_DATA +{ + HWND TreeNewHandle; + PPH_TREENEW_HEADER_MOUSE_EVENT MouseEvent; + ULONG DefaultSortColumn; + PH_SORT_ORDER DefaultSortOrder; + + struct _PH_EMENU_ITEM *Menu; + struct _PH_EMENU_ITEM *Selection; + ULONG ProcessedId; +} PH_TN_COLUMN_MENU_DATA, *PPH_TN_COLUMN_MENU_DATA; + +#define PH_TN_COLUMN_MENU_HIDE_COLUMN_ID ((ULONG)-1) +#define PH_TN_COLUMN_MENU_CHOOSE_COLUMNS_ID ((ULONG)-2) +#define PH_TN_COLUMN_MENU_SIZE_COLUMN_TO_FIT_ID ((ULONG)-3) +#define PH_TN_COLUMN_MENU_SIZE_ALL_COLUMNS_TO_FIT_ID ((ULONG)-4) +#define PH_TN_COLUMN_MENU_RESET_SORT_ID ((ULONG)-5) + +PHAPPAPI +VOID +NTAPI +PhInitializeTreeNewColumnMenu( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data + ); +// end_phapppub + +#define PH_TN_COLUMN_MENU_NO_VISIBILITY 0x1 +#define PH_TN_COLUMN_MENU_SHOW_RESET_SORT 0x2 + +VOID PhInitializeTreeNewColumnMenuEx( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data, + _In_ ULONG Flags + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhHandleTreeNewColumnMenu( + _Inout_ PPH_TN_COLUMN_MENU_DATA Data + ); + +PHAPPAPI +VOID +NTAPI +PhDeleteTreeNewColumnMenu( + _In_ PPH_TN_COLUMN_MENU_DATA Data + ); + +typedef struct _PH_TN_FILTER_SUPPORT +{ + PPH_LIST FilterList; + HWND TreeNewHandle; + PPH_LIST NodeList; +} PH_TN_FILTER_SUPPORT, *PPH_TN_FILTER_SUPPORT; + +typedef BOOLEAN (NTAPI *PPH_TN_FILTER_FUNCTION)( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +typedef struct _PH_TN_FILTER_ENTRY +{ + PPH_TN_FILTER_FUNCTION Filter; + PVOID Context; +} PH_TN_FILTER_ENTRY, *PPH_TN_FILTER_ENTRY; + +PHAPPAPI +VOID +NTAPI +PhInitializeTreeNewFilterSupport( + _Out_ PPH_TN_FILTER_SUPPORT Support, + _In_ HWND TreeNewHandle, + _In_ PPH_LIST NodeList + ); + +PHAPPAPI +VOID +NTAPI +PhDeleteTreeNewFilterSupport( + _In_ PPH_TN_FILTER_SUPPORT Support + ); + +PHAPPAPI +PPH_TN_FILTER_ENTRY +NTAPI +PhAddTreeNewFilter( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TN_FILTER_FUNCTION Filter, + _In_opt_ PVOID Context + ); + +PHAPPAPI +VOID +NTAPI +PhRemoveTreeNewFilter( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TN_FILTER_ENTRY Entry + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhApplyTreeNewFiltersToNode( + _In_ PPH_TN_FILTER_SUPPORT Support, + _In_ PPH_TREENEW_NODE Node + ); + +PHAPPAPI +VOID +NTAPI +PhApplyTreeNewFilters( + _In_ PPH_TN_FILTER_SUPPORT Support + ); +// end_phapppub + +typedef struct _PH_COPY_CELL_CONTEXT +{ + HWND TreeNewHandle; + ULONG Id; // column ID + PPH_STRING MenuItemText; +} PH_COPY_CELL_CONTEXT, *PPH_COPY_CELL_CONTEXT; + +BOOLEAN PhInsertCopyCellEMenuItem( + _In_ struct _PH_EMENU_ITEM *Menu, + _In_ ULONG InsertAfterId, + _In_ HWND TreeNewHandle, + _In_ PPH_TREENEW_COLUMN Column + ); + +BOOLEAN PhHandleCopyCellEMenuItem( + _In_ struct _PH_EMENU_ITEM *SelectedItem + ); + +BOOLEAN PhShellOpenKey2( + _In_ HWND hWnd, + _In_ PPH_STRING KeyName + ); + +PPH_STRING PhPcre2GetErrorMessage( + _In_ INT ErrorCode + ); + +#define PH_LOAD_SHARED_ICON_SMALL(Name) PhLoadIcon(PhInstanceHandle, (Name), PH_LOAD_ICON_SHARED | PH_LOAD_ICON_SIZE_SMALL, 0, 0) +#define PH_LOAD_SHARED_ICON_LARGE(Name) PhLoadIcon(PhInstanceHandle, (Name), PH_LOAD_ICON_SHARED | PH_LOAD_ICON_SIZE_LARGE, 0, 0) + +FORCEINLINE PVOID PhpGenericPropertyPageHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ PWSTR ContextName + ) +{ + PVOID context; + + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + + context = (PVOID)propSheetPage->lParam; + SetProp(hwndDlg, ContextName, (HANDLE)context); + } + break; + case WM_DESTROY: + { + context = (PVOID)GetProp(hwndDlg, ContextName); + RemoveProp(hwndDlg, ContextName); + } + break; + default: + { + context = (PVOID)GetProp(hwndDlg, ContextName); + } + break; + } + + return context; +} + +// mainwnd + +#define PH_MAINWND_CLASSNAME L"ProcessHacker" // phapppub + +PHAPPAPI extern HWND PhMainWndHandle; // phapppub +extern BOOLEAN PhMainWndExiting; + +#define WM_PH_FIRST (WM_APP + 99) +#define WM_PH_ACTIVATE (WM_APP + 99) +#define PH_ACTIVATE_REPLY 0x1119 + +#define WM_PH_PROCESS_ADDED (WM_APP + 101) +#define WM_PH_PROCESS_MODIFIED (WM_APP + 102) +#define WM_PH_PROCESS_REMOVED (WM_APP + 103) +#define WM_PH_PROCESSES_UPDATED (WM_APP + 104) + +#define WM_PH_SERVICE_ADDED (WM_APP + 105) +#define WM_PH_SERVICE_MODIFIED (WM_APP + 106) +#define WM_PH_SERVICE_REMOVED (WM_APP + 107) +#define WM_PH_SERVICES_UPDATED (WM_APP + 108) + +#define WM_PH_NETWORK_ITEM_ADDED (WM_APP + 109) +#define WM_PH_NETWORK_ITEM_MODIFIED (WM_APP + 110) +#define WM_PH_NETWORK_ITEM_REMOVED (WM_APP + 111) +#define WM_PH_NETWORK_ITEMS_UPDATED (WM_APP + 112) + +// begin_phapppub +#define WM_PH_SHOW_PROCESS_PROPERTIES (WM_APP + 120) +#define WM_PH_DESTROY (WM_APP + 121) +#define WM_PH_SAVE_ALL_SETTINGS (WM_APP + 122) +#define WM_PH_PREPARE_FOR_EARLY_SHUTDOWN (WM_APP + 123) +#define WM_PH_CANCEL_EARLY_SHUTDOWN (WM_APP + 124) +// end_phapppub +#define WM_PH_DELAYED_LOAD_COMPLETED (WM_APP + 125) +#define WM_PH_NOTIFY_ICON_MESSAGE (WM_APP + 126) +// begin_phapppub +#define WM_PH_TOGGLE_VISIBLE (WM_APP + 127) +#define WM_PH_SHOW_MEMORY_EDITOR (WM_APP + 128) +#define WM_PH_SHOW_MEMORY_RESULTS (WM_APP + 129) +#define WM_PH_SELECT_TAB_PAGE (WM_APP + 130) +#define WM_PH_GET_CALLBACK_LAYOUT_PADDING (WM_APP + 131) +#define WM_PH_INVALIDATE_LAYOUT_PADDING (WM_APP + 132) +#define WM_PH_SELECT_PROCESS_NODE (WM_APP + 133) +#define WM_PH_SELECT_SERVICE_ITEM (WM_APP + 134) +#define WM_PH_SELECT_NETWORK_ITEM (WM_APP + 135) +// end_phapppub +#define WM_PH_UPDATE_FONT (WM_APP + 136) +#define WM_PH_GET_FONT (WM_APP + 137) +// begin_phapppub +#define WM_PH_INVOKE (WM_APP + 138) +#define WM_PH_ADD_MENU_ITEM (WM_APP + 139) +#define WM_PH_ADD_TAB_PAGE (WM_APP + 140) +#define WM_PH_REFRESH (WM_APP + 141) +#define WM_PH_GET_UPDATE_AUTOMATICALLY (WM_APP + 142) +#define WM_PH_SET_UPDATE_AUTOMATICALLY (WM_APP + 143) +// end_phapppub +#define WM_PH_ICON_CLICK (WM_APP + 144) +#define WM_PH_LAST (WM_APP + 144) + +// begin_phapppub +#define ProcessHacker_ShowProcessProperties(hWnd, ProcessItem) \ + SendMessage(hWnd, WM_PH_SHOW_PROCESS_PROPERTIES, 0, (LPARAM)(ProcessItem)) +#define ProcessHacker_Destroy(hWnd) \ + SendMessage(hWnd, WM_PH_DESTROY, 0, 0) +#define ProcessHacker_SaveAllSettings(hWnd) \ + SendMessage(hWnd, WM_PH_SAVE_ALL_SETTINGS, 0, 0) +#define ProcessHacker_PrepareForEarlyShutdown(hWnd) \ + SendMessage(hWnd, WM_PH_PREPARE_FOR_EARLY_SHUTDOWN, 0, 0) +#define ProcessHacker_CancelEarlyShutdown(hWnd) \ + SendMessage(hWnd, WM_PH_CANCEL_EARLY_SHUTDOWN, 0, 0) +#define ProcessHacker_ToggleVisible(hWnd, AlwaysShow) \ + SendMessage(hWnd, WM_PH_TOGGLE_VISIBLE, (WPARAM)(AlwaysShow), 0) +#define ProcessHacker_ShowMemoryEditor(hWnd, ShowMemoryEditor) \ + PostMessage(hWnd, WM_PH_SHOW_MEMORY_EDITOR, 0, (LPARAM)(ShowMemoryEditor)) +#define ProcessHacker_ShowMemoryResults(hWnd, ShowMemoryResults) \ + PostMessage(hWnd, WM_PH_SHOW_MEMORY_RESULTS, 0, (LPARAM)(ShowMemoryResults)) +#define ProcessHacker_SelectTabPage(hWnd, Index) \ + SendMessage(hWnd, WM_PH_SELECT_TAB_PAGE, (WPARAM)(Index), 0) +#define ProcessHacker_GetCallbackLayoutPadding(hWnd) \ + ((PPH_CALLBACK)SendMessage(hWnd, WM_PH_GET_CALLBACK_LAYOUT_PADDING, 0, 0)) +#define ProcessHacker_InvalidateLayoutPadding(hWnd) \ + SendMessage(hWnd, WM_PH_INVALIDATE_LAYOUT_PADDING, 0, 0) +#define ProcessHacker_SelectProcessNode(hWnd, ProcessNode) \ + SendMessage(hWnd, WM_PH_SELECT_PROCESS_NODE, 0, (LPARAM)(ProcessNode)) +#define ProcessHacker_SelectServiceItem(hWnd, ServiceItem) \ + SendMessage(hWnd, WM_PH_SELECT_SERVICE_ITEM, 0, (LPARAM)(ServiceItem)) +#define ProcessHacker_SelectNetworkItem(hWnd, NetworkItem) \ + SendMessage(hWnd, WM_PH_SELECT_NETWORK_ITEM, 0, (LPARAM)(NetworkItem)) +#define ProcessHacker_Invoke(hWnd, Function, Parameter) \ + PostMessage(hWnd, WM_PH_INVOKE, (WPARAM)(Parameter), (LPARAM)(Function)) +#define ProcessHacker_AddMenuItem(hWnd, AddMenuItem) \ + ((ULONG_PTR)SendMessage(hWnd, WM_PH_ADD_MENU_ITEM, 0, (LPARAM)(AddMenuItem))) +#define ProcessHacker_AddTabPage(hWnd, TabPage) \ + ((PPH_ADDITIONAL_TAB_PAGE)SendMessage(hWnd, WM_PH_ADD_TAB_PAGE, 0, (LPARAM)(TabPage))) +#define ProcessHacker_Refresh(hWnd) \ + SendMessage(hWnd, WM_PH_REFRESH, 0, 0) +#define ProcessHacker_GetUpdateAutomatically(hWnd) \ + ((BOOLEAN)SendMessage(hWnd, WM_PH_GET_UPDATE_AUTOMATICALLY, 0, 0)) +#define ProcessHacker_SetUpdateAutomatically(hWnd, Value) \ + SendMessage(hWnd, WM_PH_SET_UPDATE_AUTOMATICALLY, (WPARAM)(Value), 0) +// end_phapppub +#define ProcessHacker_IconClick(hWnd) \ + SendMessage(hWnd, WM_PH_ICON_CLICK, 0, 0) + +typedef struct _PH_SHOWMEMORYEDITOR +{ + HANDLE ProcessId; + PVOID BaseAddress; + SIZE_T RegionSize; + ULONG SelectOffset; + ULONG SelectLength; + PPH_STRING Title; + ULONG Flags; +} PH_SHOWMEMORYEDITOR, *PPH_SHOWMEMORYEDITOR; + +typedef struct _PH_SHOWMEMORYRESULTS +{ + HANDLE ProcessId; + PPH_LIST Results; +} PH_SHOWMEMORYRESULTS, *PPH_SHOWMEMORYRESULTS; + +// begin_phapppub +typedef struct _PH_LAYOUT_PADDING_DATA +{ + RECT Padding; +} PH_LAYOUT_PADDING_DATA, *PPH_LAYOUT_PADDING_DATA; + +typedef struct _PH_ADDMENUITEM +{ + _In_ PVOID Plugin; + _In_ ULONG Location; + _In_opt_ PWSTR InsertAfter; + _In_ ULONG Flags; + _In_ ULONG Id; + _In_ PWSTR Text; + _In_opt_ PVOID Context; +} PH_ADDMENUITEM, *PPH_ADDMENUITEM; + +typedef HWND (NTAPI *PPH_TAB_PAGE_CREATE_FUNCTION)( + _In_ PVOID Context + ); + +typedef VOID (NTAPI *PPH_TAB_PAGE_CALLBACK_FUNCTION)( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ); + +typedef struct _PH_ADDITIONAL_TAB_PAGE +{ + PWSTR Text; + PVOID Context; + PPH_TAB_PAGE_CREATE_FUNCTION CreateFunction; + HWND WindowHandle; + INT Index; + PPH_TAB_PAGE_CALLBACK_FUNCTION SelectionChangedCallback; + PPH_TAB_PAGE_CALLBACK_FUNCTION SaveContentCallback; + PPH_TAB_PAGE_CALLBACK_FUNCTION FontChangedCallback; + PPH_TAB_PAGE_CALLBACK_FUNCTION InitializeSectionMenuItemsCallback; + PVOID Reserved[3]; +} PH_ADDITIONAL_TAB_PAGE, *PPH_ADDITIONAL_TAB_PAGE; +// end_phapppub + +// begin_phapppub +#define PH_NOTIFY_MINIMUM 0x1 +#define PH_NOTIFY_PROCESS_CREATE 0x1 +#define PH_NOTIFY_PROCESS_DELETE 0x2 +#define PH_NOTIFY_SERVICE_CREATE 0x4 +#define PH_NOTIFY_SERVICE_DELETE 0x8 +#define PH_NOTIFY_SERVICE_START 0x10 +#define PH_NOTIFY_SERVICE_STOP 0x20 +#define PH_NOTIFY_MAXIMUM 0x40 +#define PH_NOTIFY_VALID_MASK 0x3f +// end_phapppub + +BOOLEAN PhMainWndInitialization( + _In_ INT ShowCommand + ); + +VOID PhLoadDbgHelpFromPath( + _In_ PWSTR DbgHelpPath + ); + +VOID PhAddMiniProcessMenuItems( + _Inout_ struct _PH_EMENU_ITEM *Menu, + _In_ HANDLE ProcessId + ); + +BOOLEAN PhHandleMiniProcessMenuItem( + _Inout_ struct _PH_EMENU_ITEM *MenuItem + ); + +VOID PhShowIconContextMenu( + _In_ POINT Location + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhShowIconNotification( + _In_ PWSTR Title, + _In_ PWSTR Text, + _In_ ULONG Flags + ); +// end_phapppub + +VOID PhShowDetailsForIconNotification( + VOID + ); + +VOID PhShowProcessContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ); + +VOID PhShowServiceContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ); + +VOID PhShowNetworkContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ); + +// plugin + +extern PH_AVL_TREE PhPluginsByName; + +VOID PhPluginsInitialization( + VOID + ); + +BOOLEAN PhIsPluginDisabled( + _In_ PPH_STRINGREF BaseName + ); + +VOID PhSetPluginDisabled( + _In_ PPH_STRINGREF BaseName, + _In_ BOOLEAN Disable + ); + +VOID PhLoadPlugins( + VOID + ); + +VOID PhUnloadPlugins( + VOID + ); + +struct _PH_PLUGIN *PhFindPlugin2( + _In_ PPH_STRINGREF Name + ); + +// procprp + +#define PH_PROCESS_PROPCONTEXT_MAXPAGES 20 + +typedef struct _PH_PROCESS_PROPCONTEXT *PPH_PROCESS_PROPCONTEXT; // phapppub + +typedef struct _PH_PROCESS_PROPCONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + HWND WindowHandle; + PH_EVENT CreatedEvent; + PPH_STRING Title; + PROPSHEETHEADER PropSheetHeader; + HPROPSHEETPAGE *PropSheetPages; + + HANDLE SelectThreadId; +} PH_PROCESS_PROPCONTEXT, *PPH_PROCESS_PROPCONTEXT; + +// begin_phapppub +typedef struct _PH_PROCESS_PROPPAGECONTEXT +{ + PPH_PROCESS_PROPCONTEXT PropContext; + PVOID Context; + PROPSHEETPAGE PropSheetPage; + + BOOLEAN LayoutInitialized; +} PH_PROCESS_PROPPAGECONTEXT, *PPH_PROCESS_PROPPAGECONTEXT; +// end_phapppub + +BOOLEAN PhProcessPropInitialization( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_PROCESS_PROPCONTEXT +NTAPI +PhCreateProcessPropContext( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ); +// end_phapppub + +VOID PhRefreshProcessPropContext( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhSetSelectThreadIdProcessPropContext( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ HANDLE ThreadId + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhAddProcessPropPage( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ _Assume_refs_(1) PPH_PROCESS_PROPPAGECONTEXT PropPageContext + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhAddProcessPropPage2( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ HPROPSHEETPAGE PropSheetPageHandle + ); + +PHAPPAPI +PPH_PROCESS_PROPPAGECONTEXT +NTAPI +PhCreateProcessPropPageContext( + _In_ LPCWSTR Template, + _In_ DLGPROC DlgProc, + _In_opt_ PVOID Context + ); + +PHAPPAPI +PPH_PROCESS_PROPPAGECONTEXT +NTAPI +PhCreateProcessPropPageContextEx( + _In_opt_ PVOID InstanceHandle, + _In_ LPCWSTR Template, + _In_ DLGPROC DlgProc, + _In_opt_ PVOID Context + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhPropPageDlgProcHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam, + _Out_ LPPROPSHEETPAGE *PropSheetPage, + _Out_ PPH_PROCESS_PROPPAGECONTEXT *PropPageContext, + _Out_ PPH_PROCESS_ITEM *ProcessItem + ); + +PHAPPAPI +VOID +NTAPI +PhPropPageDlgProcDestroy( + _In_ HWND hwndDlg + ); + +#define PH_PROP_PAGE_TAB_CONTROL_PARENT ((PPH_LAYOUT_ITEM)0x1) + +PHAPPAPI +PPH_LAYOUT_ITEM +NTAPI +PhAddPropPageLayoutItem( + _In_ HWND hwnd, + _In_ HWND Handle, + _In_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor + ); + +PHAPPAPI +VOID +NTAPI +PhDoPropPageLayout( + _In_ HWND hwnd + ); + +FORCEINLINE +PPH_LAYOUT_ITEM +PhBeginPropPageLayout( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_PROPPAGECONTEXT PropPageContext + ) +{ + if (!PropPageContext->LayoutInitialized) + { + return PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + } + else + { + return NULL; + } +} + +FORCEINLINE +VOID +PhEndPropPageLayout( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_PROPPAGECONTEXT PropPageContext + ) +{ + PhDoPropPageLayout(hwndDlg); + PropPageContext->LayoutInitialized = TRUE; +} + +PHAPPAPI +BOOLEAN +NTAPI +PhShowProcessProperties( + _In_ PPH_PROCESS_PROPCONTEXT Context + ); +// end_phapppub + +// log + +#define PH_LOG_ENTRY_PROCESS_FIRST 1 +#define PH_LOG_ENTRY_PROCESS_CREATE 1 +#define PH_LOG_ENTRY_PROCESS_DELETE 2 +#define PH_LOG_ENTRY_PROCESS_LAST 2 + +#define PH_LOG_ENTRY_SERVICE_FIRST 3 +#define PH_LOG_ENTRY_SERVICE_CREATE 3 +#define PH_LOG_ENTRY_SERVICE_DELETE 4 +#define PH_LOG_ENTRY_SERVICE_START 5 +#define PH_LOG_ENTRY_SERVICE_STOP 6 +#define PH_LOG_ENTRY_SERVICE_CONTINUE 7 +#define PH_LOG_ENTRY_SERVICE_PAUSE 8 +#define PH_LOG_ENTRY_SERVICE_LAST 8 + +#define PH_LOG_ENTRY_MESSAGE 9 // phapppub + +typedef struct _PH_LOG_ENTRY *PPH_LOG_ENTRY; // phapppub + +typedef struct _PH_LOG_ENTRY +{ + UCHAR Type; + UCHAR Reserved1; + USHORT Flags; + LARGE_INTEGER Time; + union + { + struct + { + HANDLE ProcessId; + PPH_STRING Name; + HANDLE ParentProcessId; + PPH_STRING ParentName; + NTSTATUS ExitStatus; + } Process; + struct + { + PPH_STRING Name; + PPH_STRING DisplayName; + } Service; + PPH_STRING Message; + }; + UCHAR Buffer[1]; +} PH_LOG_ENTRY, *PPH_LOG_ENTRY; + +extern PH_CIRCULAR_BUFFER_PVOID PhLogBuffer; +PHAPPAPI extern PH_CALLBACK PhLoggedCallback; // phapppub + +VOID PhLogInitialization( + VOID + ); + +VOID PhClearLogEntries( + VOID + ); + +VOID PhLogProcessEntry( + _In_ UCHAR Type, + _In_ HANDLE ProcessId, + _In_opt_ HANDLE QueryHandle, + _In_ PPH_STRING Name, + _In_opt_ HANDLE ParentProcessId, + _In_opt_ PPH_STRING ParentName + ); + +VOID PhLogServiceEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Name, + _In_ PPH_STRING DisplayName + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhLogMessageEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Message + ); + +PHAPPAPI +PPH_STRING +NTAPI +PhFormatLogEntry( + _In_ PPH_LOG_ENTRY Entry + ); +// end_phapppub + +// dbgcon + +VOID PhShowDebugConsole( + VOID + ); + +// itemtips + +PPH_STRING PhGetProcessTooltipText( + _In_ PPH_PROCESS_ITEM Process, + _Out_opt_ PULONG ValidToTickCount + ); + +PPH_STRING PhGetServiceTooltipText( + _In_ PPH_SERVICE_ITEM Service + ); + +// cmdmode + +NTSTATUS PhCommandModeStart( + VOID + ); + +// anawait + +VOID PhUiAnalyzeWaitThread( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PPH_SYMBOL_PROVIDER SymbolProvider + ); + +// mdump + +BOOLEAN PhUiCreateDumpFileProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ); + +// about + +VOID PhShowAboutDialog( + _In_ HWND ParentWindowHandle + ); + +PPH_STRING PhGetDiagnosticsString( + VOID + ); + +// affinity + +VOID PhShowProcessAffinityDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_opt_ PPH_THREAD_ITEM ThreadItem + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhShowProcessAffinityDialog2( + _In_ HWND ParentWindowHandle, + _In_ ULONG_PTR AffinityMask, + _Out_ PULONG_PTR NewAffinityMask + ); +// end_phapppub + +// chcol + +#define PH_CONTROL_TYPE_TREE_NEW 1 + +VOID PhShowChooseColumnsDialog( + _In_ HWND ParentWindowHandle, + _In_ HWND ControlHandle, + _In_ ULONG Type + ); + +// chdlg + +// begin_phapppub +#define PH_CHOICE_DIALOG_SAVED_CHOICES 10 + +#define PH_CHOICE_DIALOG_CHOICE 0x0 +#define PH_CHOICE_DIALOG_USER_CHOICE 0x1 +#define PH_CHOICE_DIALOG_PASSWORD 0x2 +#define PH_CHOICE_DIALOG_TYPE_MASK 0x3 + +PHAPPAPI +BOOLEAN +NTAPI +PhaChoiceDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR Title, + _In_ PWSTR Message, + _In_opt_ PWSTR *Choices, + _In_opt_ ULONG NumberOfChoices, + _In_opt_ PWSTR Option, + _In_ ULONG Flags, + _Inout_ PPH_STRING *SelectedChoice, + _Inout_opt_ PBOOLEAN SelectedOption, + _In_opt_ PWSTR SavedChoicesSettingName + ); +// end_phapppub + +// chproc + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhShowChooseProcessDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR Message, + _Out_ PHANDLE ProcessId + ); +// end_phapppub + +// findobj + +VOID PhShowFindObjectsDialog( + VOID + ); + +// gdihndl + +VOID PhShowGdiHandlesDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +// hidnproc + +VOID PhShowHiddenProcessesDialog( + VOID + ); + +// hndlprp + +VOID PhShowHandleProperties( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM HandleItem + ); + +// hndlstat + +VOID PhShowHandleStatisticsDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId + ); + +// infodlg + +VOID PhShowInformationDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR String, + _Reserved_ ULONG Flags + ); + +// jobprp + +VOID PhShowJobProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ PWSTR Title + ); + +HPROPSHEETPAGE PhCreateJobPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ DLGPROC HookProc + ); + +// logwnd + +VOID PhShowLogDialog( + VOID + ); + +// memedit + +#define PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION 0x1 + +VOID PhShowMemoryEditorDialog( + _In_ HANDLE ProcessId, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize, + _In_ ULONG SelectOffset, + _In_ ULONG SelectLength, + _In_opt_ PPH_STRING Title, + _In_ ULONG Flags + ); + +// memlists + +VOID PhShowMemoryListsDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ VOID (NTAPI *RegisterDialog)(HWND), + _In_opt_ VOID (NTAPI *UnregisterDialog)(HWND) + ); + +// memprot + +VOID PhShowMemoryProtectDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_MEMORY_ITEM MemoryItem + ); + +// memrslt + +VOID PhShowMemoryResultsDialog( + _In_ HANDLE ProcessId, + _In_ PPH_LIST Results + ); + +// memsrch + +VOID PhShowMemoryStringDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +// mtgndlg + +VOID PhShowProcessMitigationPolicyDialog( + _In_ HWND ParentWindowHandle, + _In_ struct _PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION *Information + ); + +// netstk + +VOID PhShowNetworkStackDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +// ntobjprp + +HPROPSHEETPAGE PhCreateEventPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +HPROPSHEETPAGE PhCreateEventPairPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +HPROPSHEETPAGE PhCreateMutantPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +HPROPSHEETPAGE PhCreateSectionPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +HPROPSHEETPAGE PhCreateSemaphorePage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +HPROPSHEETPAGE PhCreateTimerPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ); + +// options + +VOID PhShowOptionsDialog( + _In_ HWND ParentWindowHandle + ); + +// pagfiles + +VOID PhShowPagefilesDialog( + _In_ HWND ParentWindowHandle + ); + +// plugman + +VOID PhShowPluginsDialog( + _In_ HWND ParentWindowHandle + ); + +// procrec + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhShowProcessRecordDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_RECORD Record + ); +// end_phapppub + +// runas + +typedef struct _PH_RUNAS_SERVICE_PARAMETERS +{ + ULONG ProcessId; + PWSTR UserName; + PWSTR Password; + ULONG LogonType; + ULONG SessionId; + PWSTR CurrentDirectory; + PWSTR CommandLine; + PWSTR FileName; + PWSTR DesktopName; + BOOLEAN UseLinkedToken; + PWSTR ServiceName; +} PH_RUNAS_SERVICE_PARAMETERS, *PPH_RUNAS_SERVICE_PARAMETERS; + +VOID PhShowRunAsDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ HANDLE ProcessId + ); + +NTSTATUS PhExecuteRunAsCommand( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ); + +// begin_phapppub +PHAPPAPI +NTSTATUS +NTAPI +PhExecuteRunAsCommand2( + _In_ HWND hWnd, + _In_ PWSTR Program, + _In_opt_ PWSTR UserName, + _In_opt_ PWSTR Password, + _In_opt_ ULONG LogonType, + _In_opt_ HANDLE ProcessIdWithToken, + _In_ ULONG SessionId, + _In_ PWSTR DesktopName, + _In_ BOOLEAN UseLinkedToken + ); +// end_phapppub + +NTSTATUS PhRunAsServiceStart( + _In_ PPH_STRING ServiceName + ); + +NTSTATUS PhInvokeRunAsService( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ); + +// sessmsg + +VOID PhShowSessionSendMessageDialog( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ); + +// sessprp + +VOID PhShowSessionProperties( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ); + +// sessshad + +VOID PhShowSessionShadowDialog( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ); + +// srvcr + +VOID PhShowCreateServiceDialog( + _In_ HWND ParentWindowHandle + ); + +// srvctl + +// begin_phapppub +#define WM_PH_SET_LIST_VIEW_SETTINGS (WM_APP + 701) + +PHAPPAPI +HWND +NTAPI +PhCreateServiceListControl( + _In_ HWND ParentWindowHandle, + _In_ PPH_SERVICE_ITEM *Services, + _In_ ULONG NumberOfServices + ); +// end_phapppub + +// srvprp + +VOID PhShowServiceProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_SERVICE_ITEM ServiceItem + ); + +// thrdstk + +VOID PhShowThreadStackDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PPH_THREAD_PROVIDER ThreadProvider + ); + +// tokprp + +PPH_STRING PhGetGroupAttributesString( + _In_ ULONG Attributes + ); + +PWSTR PhGetPrivilegeAttributesString( + _In_ ULONG Attributes + ); + +VOID PhShowTokenProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ PWSTR Title + ); + +HPROPSHEETPAGE PhCreateTokenPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ DLGPROC HookProc + ); + +#endif diff --git a/ProcessHacker/include/phappres.h b/ProcessHacker/include/phappres.h new file mode 100644 index 0000000..d00e86c --- /dev/null +++ b/ProcessHacker/include/phappres.h @@ -0,0 +1,34 @@ +// Notes: +// * Do not use /* comments */ since ISPP is buggy and it will throw an error. + +#ifndef PH_PHAPPRES_H +#define PH_PHAPPRES_H + +#include "phapprev.h" + +#define PHAPP_VERSION_MAJOR 2 +#define PHAPP_VERSION_MINOR 39 +#define PHAPP_VERSION_BUILD 0 + +#if (PHAPP_VERSION_BUILD == 0) +#define TWO_DIGIT_VER 1 +#else +#define THREE_DIGIT_VER 1 +#endif + +#define DO_MAKE_STR(x) #x +#define MAKE_STR(x) DO_MAKE_STR(x) + +#ifndef ISPP_INVOKED + +#if defined(TWO_DIGIT_VER) +#define PHAPP_VERSION_STRING MAKE_STR(PHAPP_VERSION_MAJOR) "." MAKE_STR(PHAPP_VERSION_MINOR) ".0" "." MAKE_STR(PHAPP_VERSION_REVISION) +#elif defined(THREE_DIGIT_VER) +#define PHAPP_VERSION_STRING MAKE_STR(PHAPP_VERSION_MAJOR) "." MAKE_STR(PHAPP_VERSION_MINOR) "." MAKE_STR(PHAPP_VERSION_BUILD) "." MAKE_STR(PHAPP_VERSION_REVISION) +#endif + +#define PHAPP_VERSION_NUMBER PHAPP_VERSION_MAJOR,PHAPP_VERSION_MINOR,PHAPP_VERSION_BUILD,PHAPP_VERSION_REVISION + +#endif // ISPP_INVOKED + +#endif // PHAPPRES_H diff --git a/ProcessHacker/include/phapprev_in.h b/ProcessHacker/include/phapprev_in.h new file mode 100644 index 0000000..c1e4031 --- /dev/null +++ b/ProcessHacker/include/phapprev_in.h @@ -0,0 +1,6 @@ +#ifndef PHAPPREV_H +#define PHAPPREV_H + +#define PHAPP_VERSION_REVISION $COMMITS$ + +#endif // PHAPPREV_H diff --git a/ProcessHacker/include/phfwddef.h b/ProcessHacker/include/phfwddef.h new file mode 100644 index 0000000..05adc3b --- /dev/null +++ b/ProcessHacker/include/phfwddef.h @@ -0,0 +1,31 @@ +#ifndef PH_PHFWDDEF_H +#define PH_PHFWDDEF_H + +// begin_phapppub +// Providers + +typedef struct _PH_PROCESS_ITEM *PPH_PROCESS_ITEM; +typedef struct _PH_PROCESS_RECORD *PPH_PROCESS_RECORD; +typedef struct _PH_SERVICE_ITEM *PPH_SERVICE_ITEM; +typedef struct _PH_NETWORK_ITEM *PPH_NETWORK_ITEM; +typedef struct _PH_MODULE_ITEM *PPH_MODULE_ITEM; +typedef struct _PH_MODULE_PROVIDER *PPH_MODULE_PROVIDER; +typedef struct _PH_THREAD_ITEM *PPH_THREAD_ITEM; +typedef struct _PH_THREAD_PROVIDER *PPH_THREAD_PROVIDER; +typedef struct _PH_HANDLE_ITEM *PPH_HANDLE_ITEM; +typedef struct _PH_HANDLE_PROVIDER *PPH_HANDLE_PROVIDER; +typedef struct _PH_MEMORY_ITEM *PPH_MEMORY_ITEM; +typedef struct _PH_MEMORY_ITEM_LIST *PPH_MEMORY_ITEM_LIST; + +// uimodels + +typedef struct _PH_PROCESS_NODE *PPH_PROCESS_NODE; +typedef struct _PH_SERVICE_NODE *PPH_SERVICE_NODE; +typedef struct _PH_NETWORK_NODE *PPH_NETWORK_NODE; +typedef struct _PH_MODULE_NODE *PPH_MODULE_NODE; +typedef struct _PH_THREAD_NODE *PPH_THREAD_NODE; +typedef struct _PH_HANDLE_NODE *PPH_HANDLE_NODE; +typedef struct _PH_MEMORY_NODE *PPH_MEMORY_NODE; +// end_phapppub + +#endif diff --git a/ProcessHacker/include/phplug.h b/ProcessHacker/include/phplug.h new file mode 100644 index 0000000..68ea560 --- /dev/null +++ b/ProcessHacker/include/phplug.h @@ -0,0 +1,675 @@ +#ifndef PH_PHPLUG_H +#define PH_PHPLUG_H + +#include +#include +#include + +// begin_phapppub +// Callbacks + +typedef enum _PH_GENERAL_CALLBACK +{ + GeneralCallbackMainWindowShowing = 0, // INT ShowCommand [main thread] + GeneralCallbackProcessesUpdated = 1, // [main thread] + GeneralCallbackGetProcessHighlightingColor = 2, // PPH_PLUGIN_GET_HIGHLIGHTING_COLOR Data [main thread] + GeneralCallbackGetProcessTooltipText = 3, // PPH_PLUGIN_GET_TOOLTIP_TEXT Data [main thread] + GeneralCallbackProcessPropertiesInitializing = 4, // PPH_PLUGIN_PROCESS_PROPCONTEXT Data [properties thread] + GeneralCallbackMainMenuInitializing = 5, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackNotifyEvent = 6, // PPH_PLUGIN_NOTIFY_EVENT Data [main thread] + GeneralCallbackServicePropertiesInitializing = 7, // PPH_PLUGIN_OBJECT_PROPERTIES Data [properties thread] + GeneralCallbackHandlePropertiesInitializing = 8, // PPH_PLUGIN_OBJECT_PROPERTIES Data [properties thread] + GeneralCallbackProcessMenuInitializing = 9, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackServiceMenuInitializing = 10, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackNetworkMenuInitializing = 11, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackIconMenuInitializing = 12, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackThreadMenuInitializing = 13, // PPH_PLUGIN_MENU_INFORMATION Data [properties thread] + GeneralCallbackModuleMenuInitializing = 14, // PPH_PLUGIN_MENU_INFORMATION Data [properties thread] + GeneralCallbackMemoryMenuInitializing = 15, // PPH_PLUGIN_MENU_INFORMATION Data [properties thread] + GeneralCallbackHandleMenuInitializing = 16, // PPH_PLUGIN_MENU_INFORMATION Data [properties thread] + GeneralCallbackProcessTreeNewInitializing = 17, // PPH_PLUGIN_TREENEW_INFORMATION Data [main thread] + GeneralCallbackServiceTreeNewInitializing = 18, // PPH_PLUGIN_TREENEW_INFORMATION Data [main thread] + GeneralCallbackNetworkTreeNewInitializing = 19, // PPH_PLUGIN_TREENEW_INFORMATION Data [main thread] + GeneralCallbackModuleTreeNewInitializing = 20, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackModuleTreeNewUninitializing = 21, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackThreadTreeNewInitializing = 22, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackThreadTreeNewUninitializing = 23, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackHandleTreeNewInitializing = 24, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackHandleTreeNewUninitializing = 25, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackThreadStackControl = 26, // PPH_PLUGIN_THREAD_STACK_CONTROL Data [properties thread] + GeneralCallbackSystemInformationInitializing = 27, // PPH_PLUGIN_SYSINFO_POINTERS Data [system information thread] + GeneralCallbackMainWindowTabChanged = 28, // INT NewIndex [main thread] + GeneralCallbackMemoryTreeNewInitializing = 29, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackMemoryTreeNewUninitializing = 30, // PPH_PLUGIN_TREENEW_INFORMATION Data [properties thread] + GeneralCallbackMemoryItemListControl = 31, // PPH_PLUGIN_MEMORY_ITEM_LIST_CONTROL Data [properties thread] + GeneralCallbackMiniInformationInitializing = 32, // PPH_PLUGIN_MINIINFO_POINTERS Data [main thread] + GeneralCallbackMiListSectionMenuInitializing = 33, // PPH_PLUGIN_MENU_INFORMATION Data [main thread] + GeneralCallbackMaximum +} PH_GENERAL_CALLBACK, *PPH_GENERAL_CALLBACK; + +typedef enum _PH_PLUGIN_CALLBACK +{ + PluginCallbackLoad = 0, // PPH_LIST Parameters [main thread] // list of strings, might be NULL + PluginCallbackUnload = 1, // [main thread] + PluginCallbackShowOptions = 2, // HWND ParentWindowHandle [main thread] + PluginCallbackMenuItem = 3, // PPH_PLUGIN_MENU_ITEM MenuItem [main/properties thread] + PluginCallbackTreeNewMessage = 4, // PPH_PLUGIN_TREENEW_MESSAGE Message [main/properties thread] + PluginCallbackPhSvcRequest = 5, // PPH_PLUGIN_PHSVC_REQUEST Message [phsvc thread] + PluginCallbackMenuHook = 6, // PH_PLUGIN_MENU_HOOK_INFORMATION MenuHookInfo [menu thread] + PluginCallbackMaximum +} PH_PLUGIN_CALLBACK, *PPH_PLUGIN_CALLBACK; + +typedef struct _PH_PLUGIN_GET_HIGHLIGHTING_COLOR +{ + // Parameter is: + // PPH_PROCESS_ITEM for GeneralCallbackGetProcessHighlightingColor + + PVOID Parameter; + COLORREF BackColor; + BOOLEAN Handled; + BOOLEAN Cache; +} PH_PLUGIN_GET_HIGHLIGHTING_COLOR, *PPH_PLUGIN_GET_HIGHLIGHTING_COLOR; + +typedef struct _PH_PLUGIN_GET_TOOLTIP_TEXT +{ + // Parameter is: + // PPH_PROCESS_ITEM for GeneralCallbackGetProcessTooltipText + + PVOID Parameter; + PPH_STRING_BUILDER StringBuilder; + ULONG ValidForMs; +} PH_PLUGIN_GET_TOOLTIP_TEXT, *PPH_PLUGIN_GET_TOOLTIP_TEXT; + +typedef struct _PH_PLUGIN_PROCESS_PROPCONTEXT +{ + PPH_PROCESS_PROPCONTEXT PropContext; + PPH_PROCESS_ITEM ProcessItem; +} PH_PLUGIN_PROCESS_PROPCONTEXT, *PPH_PLUGIN_PROCESS_PROPCONTEXT; + +typedef struct _PH_PLUGIN_NOTIFY_EVENT +{ + // Parameter is: + // PPH_PROCESS_ITEM for Type = PH_NOTIFY_PROCESS_* + // PPH_SERVICE_ITEM for Type = PH_NOTIFY_SERVICE_* + + ULONG Type; + BOOLEAN Handled; + PVOID Parameter; +} PH_PLUGIN_NOTIFY_EVENT, *PPH_PLUGIN_NOTIFY_EVENT; + +typedef struct _PH_PLUGIN_OBJECT_PROPERTIES +{ + // Parameter is: + // PPH_SERVICE_ITEM for GeneralCallbackServicePropertiesInitializing + // PPH_PLUGIN_HANDLE_PROPERTIES_CONTEXT for GeneralCallbackHandlePropertiesInitializing + + PVOID Parameter; + ULONG NumberOfPages; + ULONG MaximumNumberOfPages; + HPROPSHEETPAGE *Pages; +} PH_PLUGIN_OBJECT_PROPERTIES, *PPH_PLUGIN_OBJECT_PROPERTIES; + +typedef struct _PH_PLUGIN_HANDLE_PROPERTIES_CONTEXT +{ + HANDLE ProcessId; + PPH_HANDLE_ITEM HandleItem; +} PH_PLUGIN_HANDLE_PROPERTIES_CONTEXT, *PPH_PLUGIN_HANDLE_PROPERTIES_CONTEXT; + +typedef struct _PH_EMENU_ITEM *PPH_EMENU_ITEM, *PPH_EMENU; + +#define PH_PLUGIN_MENU_DISALLOW_HOOKS 0x1 + +typedef struct _PH_PLUGIN_MENU_INFORMATION +{ + PPH_EMENU Menu; + HWND OwnerWindow; + + union + { + struct + { + PVOID Reserved[8]; // Reserve space for future expansion of this union + } DoNotUse; + struct + { + ULONG SubMenuIndex; + } MainMenu; + struct + { + PPH_PROCESS_ITEM *Processes; + ULONG NumberOfProcesses; + } Process; + struct + { + PPH_SERVICE_ITEM *Services; + ULONG NumberOfServices; + } Service; + struct + { + PPH_NETWORK_ITEM *NetworkItems; + ULONG NumberOfNetworkItems; + } Network; + struct + { + HANDLE ProcessId; + PPH_THREAD_ITEM *Threads; + ULONG NumberOfThreads; + } Thread; + struct + { + HANDLE ProcessId; + PPH_MODULE_ITEM *Modules; + ULONG NumberOfModules; + } Module; + struct + { + HANDLE ProcessId; + PPH_MEMORY_NODE *MemoryNodes; + ULONG NumberOfMemoryNodes; + } Memory; + struct + { + HANDLE ProcessId; + PPH_HANDLE_ITEM *Handles; + ULONG NumberOfHandles; + } Handle; + struct + { + PPH_STRINGREF SectionName; + PPH_PROCESS_GROUP ProcessGroup; + } MiListSection; + } u; + + ULONG Flags; + PPH_LIST PluginHookList; +} PH_PLUGIN_MENU_INFORMATION, *PPH_PLUGIN_MENU_INFORMATION; + +C_ASSERT(RTL_FIELD_SIZE(PH_PLUGIN_MENU_INFORMATION, u) == RTL_FIELD_SIZE(PH_PLUGIN_MENU_INFORMATION, u.DoNotUse)); + +typedef struct _PH_PLUGIN_MENU_HOOK_INFORMATION +{ + PPH_PLUGIN_MENU_INFORMATION MenuInfo; + PPH_EMENU SelectedItem; + PVOID Context; + BOOLEAN Handled; +} PH_PLUGIN_MENU_HOOK_INFORMATION, *PPH_PLUGIN_MENU_HOOK_INFORMATION; + +typedef struct _PH_PLUGIN_TREENEW_INFORMATION +{ + HWND TreeNewHandle; + PVOID CmData; + PVOID SystemContext; // e.g. PPH_THREADS_CONTEXT +} PH_PLUGIN_TREENEW_INFORMATION, *PPH_PLUGIN_TREENEW_INFORMATION; + +typedef enum _PH_PLUGIN_THREAD_STACK_CONTROL_TYPE +{ + PluginThreadStackInitializing, + PluginThreadStackUninitializing, + PluginThreadStackResolveSymbol, + PluginThreadStackGetTooltip, + PluginThreadStackWalkStack, + PluginThreadStackBeginDefaultWalkStack, + PluginThreadStackEndDefaultWalkStack, + PluginThreadStackMaximum +} PH_PLUGIN_THREAD_STACK_CONTROL_TYPE; + +typedef struct _PH_SYMBOL_PROVIDER *PPH_SYMBOL_PROVIDER; +typedef struct _PH_THREAD_STACK_FRAME *PPH_THREAD_STACK_FRAME; + +typedef BOOLEAN (NTAPI *PPH_PLUGIN_WALK_THREAD_STACK_CALLBACK)( + _In_ PPH_THREAD_STACK_FRAME StackFrame, + _In_opt_ PVOID Context + ); + +typedef struct _PH_PLUGIN_THREAD_STACK_CONTROL +{ + PH_PLUGIN_THREAD_STACK_CONTROL_TYPE Type; + PVOID UniqueKey; + + union + { + struct + { + HANDLE ProcessId; + HANDLE ThreadId; + HANDLE ThreadHandle; + PPH_SYMBOL_PROVIDER SymbolProvider; + BOOLEAN CustomWalk; + } Initializing; + struct + { + PPH_THREAD_STACK_FRAME StackFrame; + PPH_STRING Symbol; + } ResolveSymbol; + struct + { + PPH_THREAD_STACK_FRAME StackFrame; + PPH_STRING_BUILDER StringBuilder; + } GetTooltip; + struct + { + NTSTATUS Status; + HANDLE ThreadHandle; + HANDLE ProcessHandle; + PCLIENT_ID ClientId; + ULONG Flags; + PPH_PLUGIN_WALK_THREAD_STACK_CALLBACK Callback; + PVOID CallbackContext; + } WalkStack; + } u; +} PH_PLUGIN_THREAD_STACK_CONTROL, *PPH_PLUGIN_THREAD_STACK_CONTROL; + +typedef enum _PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL_TYPE +{ + PluginMemoryItemListInitialized, + PluginMemoryItemListMaximum +} PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL_TYPE; + +typedef struct _PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL +{ + PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL_TYPE Type; + + union + { + struct + { + PPH_MEMORY_ITEM_LIST List; + } Initialized; + } u; +} PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL, *PPH_PLUGIN_MEMORY_ITEM_LIST_CONTROL; + +typedef PPH_SYSINFO_SECTION (NTAPI *PPH_SYSINFO_CREATE_SECTION)( + _In_ PPH_SYSINFO_SECTION Template + ); + +typedef PPH_SYSINFO_SECTION (NTAPI *PPH_SYSINFO_FIND_SECTION)( + _In_ PPH_STRINGREF Name + ); + +typedef VOID (NTAPI *PPH_SYSINFO_ENTER_SECTION_VIEW)( + _In_ PPH_SYSINFO_SECTION NewSection + ); + +typedef VOID (NTAPI *PPH_SYSINFO_RESTORE_SUMMARY_VIEW)( + VOID + ); + +typedef struct _PH_PLUGIN_SYSINFO_POINTERS +{ + HWND WindowHandle; + PPH_SYSINFO_CREATE_SECTION CreateSection; + PPH_SYSINFO_FIND_SECTION FindSection; + PPH_SYSINFO_ENTER_SECTION_VIEW EnterSectionView; + PPH_SYSINFO_RESTORE_SUMMARY_VIEW RestoreSummaryView; +} PH_PLUGIN_SYSINFO_POINTERS, *PPH_PLUGIN_SYSINFO_POINTERS; + +typedef PPH_MINIINFO_SECTION (NTAPI *PPH_MINIINFO_CREATE_SECTION)( + _In_ PPH_MINIINFO_SECTION Template + ); + +typedef PPH_MINIINFO_SECTION (NTAPI *PPH_MINIINFO_FIND_SECTION)( + _In_ PPH_STRINGREF Name + ); + +typedef PPH_MINIINFO_LIST_SECTION (NTAPI *PPH_MINIINFO_CREATE_LIST_SECTION)( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_LIST_SECTION Template + ); + +typedef struct _PH_PLUGIN_MINIINFO_POINTERS +{ + HWND WindowHandle; + PPH_MINIINFO_CREATE_SECTION CreateSection; + PPH_MINIINFO_FIND_SECTION FindSection; + PPH_MINIINFO_CREATE_LIST_SECTION CreateListSection; +} PH_PLUGIN_MINIINFO_POINTERS, *PPH_PLUGIN_MINIINFO_POINTERS; + +typedef struct _PH_PLUGIN_TREENEW_MESSAGE +{ + HWND TreeNewHandle; + PH_TREENEW_MESSAGE Message; + PVOID Parameter1; + PVOID Parameter2; + ULONG SubId; + PVOID Context; +} PH_PLUGIN_TREENEW_MESSAGE, *PPH_PLUGIN_TREENEW_MESSAGE; + +typedef LONG (NTAPI *PPH_PLUGIN_TREENEW_SORT_FUNCTION)( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ); + +typedef NTSTATUS (NTAPI *PPHSVC_SERVER_PROBE_BUFFER)( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ ULONG Alignment, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *Pointer + ); + +typedef NTSTATUS (NTAPI *PPHSVC_SERVER_CAPTURE_BUFFER)( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *CapturedBuffer + ); + +typedef NTSTATUS (NTAPI *PPHSVC_SERVER_CAPTURE_STRING)( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PPH_STRING *CapturedString + ); + +typedef struct _PH_PLUGIN_PHSVC_REQUEST +{ + ULONG SubId; + NTSTATUS ReturnStatus; + PVOID InBuffer; + ULONG InLength; + PVOID OutBuffer; + ULONG OutLength; + + PPHSVC_SERVER_PROBE_BUFFER ProbeBuffer; + PPHSVC_SERVER_CAPTURE_BUFFER CaptureBuffer; + PPHSVC_SERVER_CAPTURE_STRING CaptureString; +} PH_PLUGIN_PHSVC_REQUEST, *PPH_PLUGIN_PHSVC_REQUEST; + +typedef VOID (NTAPI *PPHSVC_CLIENT_FREE_HEAP)( + _In_ PVOID Memory + ); + +typedef PVOID (NTAPI *PPHSVC_CLIENT_CREATE_STRING)( + _In_opt_ PVOID String, + _In_ SIZE_T Length, + _Out_ PPH_RELATIVE_STRINGREF StringRef + ); + +typedef struct _PH_PLUGIN_PHSVC_CLIENT +{ + HANDLE ServerProcessId; + PPHSVC_CLIENT_FREE_HEAP FreeHeap; + PPHSVC_CLIENT_CREATE_STRING CreateString; +} PH_PLUGIN_PHSVC_CLIENT, *PPH_PLUGIN_PHSVC_CLIENT; + +// Plugin structures + +typedef struct _PH_PLUGIN_INFORMATION +{ + PWSTR DisplayName; + PWSTR Author; + PWSTR Description; + PWSTR Url; + BOOLEAN HasOptions; + BOOLEAN Reserved1[3]; + PVOID Interface; +} PH_PLUGIN_INFORMATION, *PPH_PLUGIN_INFORMATION; + +#define PH_PLUGIN_FLAG_RESERVED 0x1 +// end_phapppub + +// begin_phapppub +typedef struct _PH_PLUGIN +{ + // Public + + PH_AVL_LINKS Links; + + PVOID Reserved; + PVOID DllBase; +// end_phapppub + + // Private + + PPH_STRING FileName; + ULONG Flags; + PH_STRINGREF Name; + PH_PLUGIN_INFORMATION Information; + + PH_CALLBACK Callbacks[PluginCallbackMaximum]; + PH_EM_APP_CONTEXT AppContext; +// begin_phapppub +} PH_PLUGIN, *PPH_PLUGIN; +// end_phapppub + +// begin_phapppub +// Plugin API + +PHAPPAPI +PPH_PLUGIN +NTAPI +PhRegisterPlugin( + _In_ PWSTR Name, + _In_ PVOID DllBase, + _Out_opt_ PPH_PLUGIN_INFORMATION *Information + ); + +PHAPPAPI +PPH_PLUGIN +NTAPI +PhFindPlugin( + _In_ PWSTR Name + ); + +PHAPPAPI +PPH_PLUGIN_INFORMATION +NTAPI +PhGetPluginInformation( + _In_ PPH_PLUGIN Plugin + ); + +PHAPPAPI +PPH_CALLBACK +NTAPI +PhGetPluginCallback( + _In_ PPH_PLUGIN Plugin, + _In_ PH_PLUGIN_CALLBACK Callback + ); + +PHAPPAPI +PPH_CALLBACK +NTAPI +PhGetGeneralCallback( + _In_ PH_GENERAL_CALLBACK Callback + ); + +PHAPPAPI +ULONG +NTAPI +PhPluginReserveIds( + _In_ ULONG Count + ); + +typedef VOID (NTAPI *PPH_PLUGIN_MENU_ITEM_DELETE_FUNCTION)( + _In_ struct _PH_PLUGIN_MENU_ITEM *MenuItem + ); + +typedef struct _PH_PLUGIN_MENU_ITEM +{ + PPH_PLUGIN Plugin; + ULONG Id; + ULONG Reserved1; + PVOID Context; + + HWND OwnerWindow; // valid only when the menu item is chosen + PVOID Reserved2; + PVOID Reserved3; + PPH_PLUGIN_MENU_ITEM_DELETE_FUNCTION DeleteFunction; // valid only for EMENU-based menu items +} PH_PLUGIN_MENU_ITEM, *PPH_PLUGIN_MENU_ITEM; + +// Location +#define PH_MENU_ITEM_LOCATION_VIEW 1 +#define PH_MENU_ITEM_LOCATION_TOOLS 2 + +// Id flags (non-functional) +#define PH_MENU_ITEM_SUB_MENU 0x80000000 +#define PH_MENU_ITEM_RETURN_MENU 0x40000000 +#define PH_MENU_ITEM_VALID_FLAGS 0xc0000000 + +PHAPPAPI +ULONG_PTR +NTAPI +PhPluginAddMenuItem( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG_PTR Location, + _In_opt_ PWSTR InsertAfter, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ PVOID Context + ); + +typedef struct _PH_PLUGIN_SYSTEM_STATISTICS +{ + PSYSTEM_PERFORMANCE_INFORMATION Performance; + + ULONG NumberOfProcesses; + ULONG NumberOfThreads; + ULONG NumberOfHandles; + + FLOAT CpuKernelUsage; + FLOAT CpuUserUsage; + + PH_UINT64_DELTA IoReadDelta; + PH_UINT64_DELTA IoWriteDelta; + PH_UINT64_DELTA IoOtherDelta; + + ULONG CommitPages; + ULONG PhysicalPages; + + HANDLE MaxCpuProcessId; + HANDLE MaxIoProcessId; + + PPH_CIRCULAR_BUFFER_FLOAT CpuKernelHistory; + PPH_CIRCULAR_BUFFER_FLOAT CpuUserHistory; + PPH_CIRCULAR_BUFFER_FLOAT *CpusKernelHistory; + PPH_CIRCULAR_BUFFER_FLOAT *CpusUserHistory; + PPH_CIRCULAR_BUFFER_ULONG64 IoReadHistory; + PPH_CIRCULAR_BUFFER_ULONG64 IoWriteHistory; + PPH_CIRCULAR_BUFFER_ULONG64 IoOtherHistory; + PPH_CIRCULAR_BUFFER_ULONG CommitHistory; + PPH_CIRCULAR_BUFFER_ULONG PhysicalHistory; + PPH_CIRCULAR_BUFFER_ULONG MaxCpuHistory; // ID of max. CPU process + PPH_CIRCULAR_BUFFER_ULONG MaxIoHistory; // ID of max. I/O process + PPH_CIRCULAR_BUFFER_FLOAT MaxCpuUsageHistory; + PPH_CIRCULAR_BUFFER_ULONG64 MaxIoReadOtherHistory; + PPH_CIRCULAR_BUFFER_ULONG64 MaxIoWriteHistory; +} PH_PLUGIN_SYSTEM_STATISTICS, *PPH_PLUGIN_SYSTEM_STATISTICS; + +PHAPPAPI +VOID +NTAPI +PhPluginGetSystemStatistics( + _Out_ PPH_PLUGIN_SYSTEM_STATISTICS Statistics + ); + +PHAPPAPI +PPH_EMENU_ITEM +NTAPI +PhPluginCreateEMenuItem( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG Flags, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ PVOID Context + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhPluginAddMenuHook( + _Inout_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_ PPH_PLUGIN Plugin, + _In_opt_ PVOID Context + ); +// end_phapppub + +VOID +NTAPI +PhPluginInitializeMenuInfo( + _Out_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_opt_ PPH_EMENU Menu, + _In_ HWND OwnerWindow, + _In_ ULONG Flags + ); + +BOOLEAN +NTAPI +PhPluginTriggerEMenuItem( + _In_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_ PPH_EMENU_ITEM Item + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhPluginAddTreeNewColumn( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID CmData, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_opt_ PPH_PLUGIN_TREENEW_SORT_FUNCTION SortFunction + ); + +PHAPPAPI +VOID +NTAPI +PhPluginSetObjectExtension( + _In_ PPH_PLUGIN Plugin, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ ULONG ExtensionSize, + _In_opt_ PPH_EM_OBJECT_CALLBACK CreateCallback, + _In_opt_ PPH_EM_OBJECT_CALLBACK DeleteCallback + ); + +PHAPPAPI +PVOID +NTAPI +PhPluginGetObjectExtension( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType + ); + +PHAPPAPI +struct _PH_NF_ICON * +NTAPI +PhPluginRegisterIcon( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PWSTR Text, + _In_ ULONG Flags, + _In_ struct _PH_NF_ICON_REGISTRATION_DATA *RegistrationData + ); + +PHAPPAPI +VOID +NTAPI +PhPluginEnableTreeNewNotify( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID CmData + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhPluginQueryPhSvc( + _Out_ PPH_PLUGIN_PHSVC_CLIENT Client + ); + +PHAPPAPI +NTSTATUS +NTAPI +PhPluginCallPhSvc( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG SubId, + _In_reads_bytes_opt_(InLength) PVOID InBuffer, + _In_ ULONG InLength, + _Out_writes_bytes_opt_(OutLength) PVOID OutBuffer, + _In_ ULONG OutLength + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/phsvc.h b/ProcessHacker/include/phsvc.h new file mode 100644 index 0000000..d2f7625 --- /dev/null +++ b/ProcessHacker/include/phsvc.h @@ -0,0 +1,238 @@ +#ifndef PH_PHSVC_H +#define PH_PHSVC_H + +#include + +#define PHSVC_SHARED_SECTION_SIZE (512 * 1024) + +// svcmain + +typedef struct _PHSVC_STOP +{ + BOOLEAN Stop; + HANDLE Event1; + HANDLE Event2; +} PHSVC_STOP, *PPHSVC_STOP; + +NTSTATUS PhSvcMain( + _In_opt_ PUNICODE_STRING PortName, + _In_opt_ PLARGE_INTEGER Timeout, + _Inout_opt_ PPHSVC_STOP Stop + ); + +VOID PhSvcStop( + _Inout_ PPHSVC_STOP Stop + ); + +// svcclient + +typedef struct _PHSVC_CLIENT +{ + LIST_ENTRY ListEntry; + + PH_EVENT ReadyEvent; + CLIENT_ID ClientId; + HANDLE PortHandle; + PVOID ClientViewBase; + PVOID ClientViewLimit; +} PHSVC_CLIENT, *PPHSVC_CLIENT; + +NTSTATUS PhSvcClientInitialization( + VOID + ); + +PPHSVC_CLIENT PhSvcCreateClient( + _In_opt_ PCLIENT_ID ClientId + ); + +PPHSVC_CLIENT PhSvcReferenceClientByClientId( + _In_ PCLIENT_ID ClientId + ); + +PPHSVC_CLIENT PhSvcGetCurrentClient( + VOID + ); + +BOOLEAN PhSvcAttachClient( + _In_ PPHSVC_CLIENT Client + ); + +VOID PhSvcDetachClient( + _In_ PPHSVC_CLIENT Client + ); + +// svcapiport + +typedef struct _PHSVC_THREAD_CONTEXT +{ + PPHSVC_CLIENT CurrentClient; + PPHSVC_CLIENT OldClient; +} PHSVC_THREAD_CONTEXT, *PPHSVC_THREAD_CONTEXT; + +NTSTATUS PhSvcApiPortInitialization( + _In_ PUNICODE_STRING PortName + ); + +PPHSVC_THREAD_CONTEXT PhSvcGetCurrentThreadContext( + VOID + ); + +VOID PhSvcHandleConnectionRequest( + _In_ PPORT_MESSAGE PortMessage + ); + +// svcapi + +NTSTATUS PhSvcApiInitialization( + VOID + ); + +typedef NTSTATUS (NTAPI *PPHSVC_API_PROCEDURE)( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +VOID PhSvcDispatchApiCall( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload, + _Out_ PHANDLE ReplyPortHandle + ); + +PVOID PhSvcValidateString( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ ULONG Alignment + ); + +NTSTATUS PhSvcProbeBuffer( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ ULONG Alignment, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *Pointer + ); + +NTSTATUS PhSvcCaptureBuffer( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *CapturedBuffer + ); + +NTSTATUS PhSvcCaptureString( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PPH_STRING *CapturedString + ); + +NTSTATUS PhSvcCaptureSid( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PSID *CapturedSid + ); + +NTSTATUS PhSvcCaptureSecurityDescriptor( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _In_ SECURITY_INFORMATION RequiredInformation, + _Out_ PSECURITY_DESCRIPTOR *CapturedSecurityDescriptor + ); + +NTSTATUS PhSvcApiDefault( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiPlugin( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiExecuteRunAsCommand( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiUnloadDriver( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiControlProcess( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiControlService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiCreateService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiChangeServiceConfig( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiChangeServiceConfig2( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiSetTcpEntry( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiControlThread( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiAddAccountRight( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiInvokeRunAsService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiIssueMemoryListCommand( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiPostMessage( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiSendMessage( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiCreateProcessIgnoreIfeoDebugger( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiSetServiceSecurity( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiLoadDbgHelp( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +NTSTATUS PhSvcApiWriteMiniDumpProcess( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ); + +#endif diff --git a/ProcessHacker/include/phsvcapi.h b/ProcessHacker/include/phsvcapi.h new file mode 100644 index 0000000..cb94580 --- /dev/null +++ b/ProcessHacker/include/phsvcapi.h @@ -0,0 +1,310 @@ +#ifndef PH_PHSVCAPI_H +#define PH_PHSVCAPI_H + +#define PHSVC_PORT_NAME (L"\\BaseNamedObjects\\PhSvcApiPort") +#define PHSVC_WOW64_PORT_NAME (L"\\BaseNamedObjects\\PhSvcWow64ApiPort") + +typedef enum _PHSVC_API_NUMBER +{ + PhSvcPluginApiNumber = 1, + PhSvcExecuteRunAsCommandApiNumber = 2, + PhSvcUnloadDriverApiNumber = 3, + PhSvcControlProcessApiNumber = 4, + PhSvcControlServiceApiNumber = 5, + PhSvcCreateServiceApiNumber = 6, + PhSvcChangeServiceConfigApiNumber = 7, + PhSvcChangeServiceConfig2ApiNumber = 8, + PhSvcSetTcpEntryApiNumber = 9, + PhSvcControlThreadApiNumber = 10, + PhSvcAddAccountRightApiNumber = 11, + PhSvcInvokeRunAsServiceApiNumber = 12, + PhSvcIssueMemoryListCommandApiNumber = 13, + PhSvcPostMessageApiNumber = 14, + PhSvcSendMessageApiNumber = 15, + PhSvcCreateProcessIgnoreIfeoDebuggerApiNumber = 16, + PhSvcSetServiceSecurityApiNumber = 17, + PhSvcLoadDbgHelpApiNumber = 18, // WOW64 compatible + PhSvcWriteMiniDumpProcessApiNumber = 19, // WOW64 compatible + PhSvcMaximumApiNumber +} PHSVC_API_NUMBER, *PPHSVC_API_NUMBER; + +typedef struct _PHSVC_API_CONNECTINFO +{ + ULONG ServerProcessId; +} PHSVC_API_CONNECTINFO, *PPHSVC_API_CONNECTINFO; + +typedef union _PHSVC_API_PLUGIN +{ + struct + { + PH_RELATIVE_STRINGREF ApiId; + ULONG Data[30]; + } i; + struct + { + ULONG Data[32]; + } o; +} PHSVC_API_PLUGIN, *PPHSVC_API_PLUGIN; + +typedef union _PHSVC_API_EXECUTERUNASCOMMAND +{ + struct + { + ULONG ProcessId; + PH_RELATIVE_STRINGREF UserName; + PH_RELATIVE_STRINGREF Password; + ULONG LogonType; + ULONG SessionId; + PH_RELATIVE_STRINGREF CurrentDirectory; + PH_RELATIVE_STRINGREF CommandLine; + PH_RELATIVE_STRINGREF FileName; + PH_RELATIVE_STRINGREF DesktopName; + BOOLEAN UseLinkedToken; + PH_RELATIVE_STRINGREF ServiceName; + } i; +} PHSVC_API_EXECUTERUNASCOMMAND, *PPHSVC_API_EXECUTERUNASCOMMAND; + +typedef union _PHSVC_API_UNLOADDRIVER +{ + struct + { + PVOID BaseAddress; + PH_RELATIVE_STRINGREF Name; + } i; +} PHSVC_API_UNLOADDRIVER, *PPHSVC_API_UNLOADDRIVER; + +typedef enum _PHSVC_API_CONTROLPROCESS_COMMAND +{ + PhSvcControlProcessTerminate = 1, + PhSvcControlProcessSuspend, + PhSvcControlProcessResume, + PhSvcControlProcessPriority, + PhSvcControlProcessIoPriority +} PHSVC_API_CONTROLPROCESS_COMMAND; + +typedef union _PHSVC_API_CONTROLPROCESS +{ + struct + { + HANDLE ProcessId; + PHSVC_API_CONTROLPROCESS_COMMAND Command; + ULONG Argument; + } i; +} PHSVC_API_CONTROLPROCESS, *PPHSVC_API_CONTROLPROCESS; + +typedef enum _PHSVC_API_CONTROLSERVICE_COMMAND +{ + PhSvcControlServiceStart = 1, + PhSvcControlServiceContinue, + PhSvcControlServicePause, + PhSvcControlServiceStop, + PhSvcControlServiceDelete +} PHSVC_API_CONTROLSERVICE_COMMAND; + +typedef union _PHSVC_API_CONTROLSERVICE +{ + struct + { + PH_RELATIVE_STRINGREF ServiceName; + PHSVC_API_CONTROLSERVICE_COMMAND Command; + } i; +} PHSVC_API_CONTROLSERVICE, *PPHSVC_API_CONTROLSERVICE; + +typedef union _PHSVC_API_CREATESERVICE +{ + struct + { + // ServiceName is the only required string. + PH_RELATIVE_STRINGREF ServiceName; + PH_RELATIVE_STRINGREF DisplayName; + ULONG ServiceType; + ULONG StartType; + ULONG ErrorControl; + PH_RELATIVE_STRINGREF BinaryPathName; + PH_RELATIVE_STRINGREF LoadOrderGroup; + PH_RELATIVE_STRINGREF Dependencies; + PH_RELATIVE_STRINGREF ServiceStartName; + PH_RELATIVE_STRINGREF Password; + BOOLEAN TagIdSpecified; + } i; + struct + { + ULONG TagId; + } o; +} PHSVC_API_CREATESERVICE, *PPHSVC_API_CREATESERVICE; + +typedef union _PHSVC_API_CHANGESERVICECONFIG +{ + struct + { + PH_RELATIVE_STRINGREF ServiceName; + ULONG ServiceType; + ULONG StartType; + ULONG ErrorControl; + PH_RELATIVE_STRINGREF BinaryPathName; + PH_RELATIVE_STRINGREF LoadOrderGroup; + PH_RELATIVE_STRINGREF Dependencies; + PH_RELATIVE_STRINGREF ServiceStartName; + PH_RELATIVE_STRINGREF Password; + PH_RELATIVE_STRINGREF DisplayName; + BOOLEAN TagIdSpecified; + } i; + struct + { + ULONG TagId; + } o; +} PHSVC_API_CHANGESERVICECONFIG, *PPHSVC_API_CHANGESERVICECONFIG; + +typedef union _PHSVC_API_CHANGESERVICECONFIG2 +{ + struct + { + PH_RELATIVE_STRINGREF ServiceName; + ULONG InfoLevel; + PH_RELATIVE_STRINGREF Info; + } i; +} PHSVC_API_CHANGESERVICECONFIG2, *PPHSVC_API_CHANGESERVICECONFIG2; + +typedef union _PHSVC_API_SETTCPENTRY +{ + struct + { + ULONG State; + ULONG LocalAddress; + ULONG LocalPort; + ULONG RemoteAddress; + ULONG RemotePort; + } i; +} PHSVC_API_SETTCPENTRY, *PPHSVC_API_SETTCPENTRY; + +typedef enum _PHSVC_API_CONTROLTHREAD_COMMAND +{ + PhSvcControlThreadTerminate = 1, + PhSvcControlThreadSuspend, + PhSvcControlThreadResume, + PhSvcControlThreadIoPriority +} PHSVC_API_CONTROLTHREAD_COMMAND; + +typedef union _PHSVC_API_CONTROLTHREAD +{ + struct + { + HANDLE ThreadId; + PHSVC_API_CONTROLTHREAD_COMMAND Command; + ULONG Argument; + } i; +} PHSVC_API_CONTROLTHREAD, *PPHSVC_API_CONTROLTHREAD; + +typedef union _PHSVC_API_ADDACCOUNTRIGHT +{ + struct + { + PH_RELATIVE_STRINGREF AccountSid; + PH_RELATIVE_STRINGREF UserRight; + } i; +} PHSVC_API_ADDACCOUNTRIGHT, *PPHSVC_API_ADDACCOUNTRIGHT; + +typedef union _PHSVC_API_ISSUEMEMORYLISTCOMMAND +{ + struct + { + SYSTEM_MEMORY_LIST_COMMAND Command; + } i; +} PHSVC_API_ISSUEMEMORYLISTCOMMAND, *PPHSVC_API_ISSUEMEMORYLISTCOMMAND; + +typedef union _PHSVC_API_POSTMESSAGE +{ + struct + { + HWND hWnd; + UINT Msg; + WPARAM wParam; + LPARAM lParam; + } i; +} PHSVC_API_POSTMESSAGE, *PPHSVC_API_POSTMESSAGE; + +typedef union _PHSVC_API_CREATEPROCESSIGNOREIFEODEBUGGER +{ + struct + { + PH_RELATIVE_STRINGREF FileName; + } i; +} PHSVC_API_CREATEPROCESSIGNOREIFEODEBUGGER, *PPHSVC_API_CREATEPROCESSIGNOREIFEODEBUGGER; + +typedef union _PHSVC_API_SETSERVICESECURITY +{ + struct + { + PH_RELATIVE_STRINGREF ServiceName; + SECURITY_INFORMATION SecurityInformation; + PH_RELATIVE_STRINGREF SecurityDescriptor; + } i; +} PHSVC_API_SETSERVICESECURITY, *PPHSVC_API_SETSERVICESECURITY; + +typedef union _PHSVC_API_LOADDBGHELP +{ + struct + { + PH_RELATIVE_STRINGREF DbgHelpPath; + } i; +} PHSVC_API_LOADDBGHELP, *PPHSVC_API_LOADDBGHELP; + +typedef union _PHSVC_API_WRITEMINIDUMPPROCESS +{ + struct + { + ULONG LocalProcessHandle; + ULONG ProcessId; + ULONG LocalFileHandle; + ULONG DumpType; + } i; +} PHSVC_API_WRITEMINIDUMPPROCESS, *PPHSVC_API_WRITEMINIDUMPPROCESS; + +typedef union _PHSVC_API_PAYLOAD +{ + PHSVC_API_CONNECTINFO ConnectInfo; + struct + { + PHSVC_API_NUMBER ApiNumber; + NTSTATUS ReturnStatus; + + union + { + PHSVC_API_PLUGIN Plugin; + PHSVC_API_EXECUTERUNASCOMMAND ExecuteRunAsCommand; + PHSVC_API_UNLOADDRIVER UnloadDriver; + PHSVC_API_CONTROLPROCESS ControlProcess; + PHSVC_API_CONTROLSERVICE ControlService; + PHSVC_API_CREATESERVICE CreateService; + PHSVC_API_CHANGESERVICECONFIG ChangeServiceConfig; + PHSVC_API_CHANGESERVICECONFIG2 ChangeServiceConfig2; + PHSVC_API_SETTCPENTRY SetTcpEntry; + PHSVC_API_CONTROLTHREAD ControlThread; + PHSVC_API_ADDACCOUNTRIGHT AddAccountRight; + PHSVC_API_ISSUEMEMORYLISTCOMMAND IssueMemoryListCommand; + PHSVC_API_POSTMESSAGE PostMessage; + PHSVC_API_CREATEPROCESSIGNOREIFEODEBUGGER CreateProcessIgnoreIfeoDebugger; + PHSVC_API_SETSERVICESECURITY SetServiceSecurity; + PHSVC_API_LOADDBGHELP LoadDbgHelp; + PHSVC_API_WRITEMINIDUMPPROCESS WriteMiniDumpProcess; + } u; + }; +} PHSVC_API_PAYLOAD, *PPHSVC_API_PAYLOAD; + +typedef struct _PHSVC_API_MSG +{ + PORT_MESSAGE h; + PHSVC_API_PAYLOAD p; +} PHSVC_API_MSG, *PPHSVC_API_MSG; + +typedef struct _PHSVC_API_MSG64 +{ + PORT_MESSAGE64 h; + PHSVC_API_PAYLOAD p; +} PHSVC_API_MSG64, *PPHSVC_API_MSG64; + +C_ASSERT(FIELD_OFFSET(PHSVC_API_PAYLOAD, u) == 8); +C_ASSERT(sizeof(PHSVC_API_MSG) <= PORT_TOTAL_MAXIMUM_MESSAGE_LENGTH); +C_ASSERT(sizeof(PHSVC_API_MSG64) <= PORT_TOTAL_MAXIMUM_MESSAGE_LENGTH); + +#endif diff --git a/ProcessHacker/include/phsvccl.h b/ProcessHacker/include/phsvccl.h new file mode 100644 index 0000000..93fe474 --- /dev/null +++ b/ProcessHacker/include/phsvccl.h @@ -0,0 +1,155 @@ +#ifndef PH_PHSVCCL_H +#define PH_PHSVCCL_H + +#include + +extern HANDLE PhSvcClServerProcessId; + +NTSTATUS PhSvcConnectToServer( + _In_ PUNICODE_STRING PortName, + _In_opt_ SIZE_T PortSectionSize + ); + +VOID PhSvcDisconnectFromServer( + VOID + ); + +VOID PhSvcpFreeHeap( + _In_ PVOID Memory + ); + +PVOID PhSvcpCreateString( + _In_opt_ PVOID String, + _In_ SIZE_T Length, + _Out_ PPH_RELATIVE_STRINGREF StringRef + ); + +NTSTATUS PhSvcCallPlugin( + _In_ PPH_STRINGREF ApiId, + _In_reads_bytes_opt_(InLength) PVOID InBuffer, + _In_ ULONG InLength, + _Out_writes_bytes_opt_(OutLength) PVOID OutBuffer, + _In_ ULONG OutLength + ); + +NTSTATUS PhSvcCallExecuteRunAsCommand( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ); + +NTSTATUS PhSvcCallUnloadDriver( + _In_opt_ PVOID BaseAddress, + _In_opt_ PWSTR Name + ); + +NTSTATUS PhSvcCallControlProcess( + _In_ HANDLE ProcessId, + _In_ PHSVC_API_CONTROLPROCESS_COMMAND Command, + _In_ ULONG Argument + ); + +NTSTATUS PhSvcCallControlService( + _In_ PWSTR ServiceName, + _In_ PHSVC_API_CONTROLSERVICE_COMMAND Command + ); + +NTSTATUS PhSvcCallCreateService( + _In_ PWSTR ServiceName, + _In_opt_ PWSTR DisplayName, + _In_ ULONG ServiceType, + _In_ ULONG StartType, + _In_ ULONG ErrorControl, + _In_opt_ PWSTR BinaryPathName, + _In_opt_ PWSTR LoadOrderGroup, + _Out_opt_ PULONG TagId, + _In_opt_ PWSTR Dependencies, + _In_opt_ PWSTR ServiceStartName, + _In_opt_ PWSTR Password + ); + +// begin_phapppub +PHAPPAPI +NTSTATUS PhSvcCallChangeServiceConfig( + _In_ PWSTR ServiceName, + _In_ ULONG ServiceType, + _In_ ULONG StartType, + _In_ ULONG ErrorControl, + _In_opt_ PWSTR BinaryPathName, + _In_opt_ PWSTR LoadOrderGroup, + _Out_opt_ PULONG TagId, + _In_opt_ PWSTR Dependencies, + _In_opt_ PWSTR ServiceStartName, + _In_opt_ PWSTR Password, + _In_opt_ PWSTR DisplayName + ); + +PHAPPAPI +NTSTATUS PhSvcCallChangeServiceConfig2( + _In_ PWSTR ServiceName, + _In_ ULONG InfoLevel, + _In_ PVOID Info + ); +// end_phapppub + +NTSTATUS PhSvcCallSetTcpEntry( + _In_ PVOID TcpRow + ); + +NTSTATUS PhSvcCallControlThread( + _In_ HANDLE ThreadId, + _In_ PHSVC_API_CONTROLTHREAD_COMMAND Command, + _In_ ULONG Argument + ); + +NTSTATUS PhSvcCallAddAccountRight( + _In_ PSID AccountSid, + _In_ PUNICODE_STRING UserRight + ); + +NTSTATUS PhSvcCallInvokeRunAsService( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ); + +NTSTATUS PhSvcCallIssueMemoryListCommand( + _In_ SYSTEM_MEMORY_LIST_COMMAND Command + ); + +// begin_phapppub +PHAPPAPI +NTSTATUS PhSvcCallPostMessage( + _In_opt_ HWND hWnd, + _In_ UINT Msg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PHAPPAPI +NTSTATUS PhSvcCallSendMessage( + _In_opt_ HWND hWnd, + _In_ UINT Msg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); +// end_phapppub + +NTSTATUS PhSvcCallCreateProcessIgnoreIfeoDebugger( + _In_ PWSTR FileName + ); + +NTSTATUS PhSvcCallSetServiceSecurity( + _In_ PWSTR ServiceName, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSTATUS PhSvcCallLoadDbgHelp( + _In_ PWSTR DbgHelpPath + ); + +NTSTATUS PhSvcCallWriteMiniDumpProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE FileHandle, + _In_ ULONG DumpType + ); + +#endif diff --git a/ProcessHacker/include/phuisup.h b/ProcessHacker/include/phuisup.h new file mode 100644 index 0000000..b7160da --- /dev/null +++ b/ProcessHacker/include/phuisup.h @@ -0,0 +1,213 @@ +#ifndef PH_PHUISUP_H +#define PH_PHUISUP_H + +// begin_phapppub +// Common state highlighting support + +typedef struct _PH_SH_STATE +{ + PH_ITEM_STATE State; + HANDLE StateListHandle; + ULONG TickCount; +} PH_SH_STATE, *PPH_SH_STATE; +// end_phapppub + +FORCEINLINE VOID PhChangeShStateTn( + _Inout_ PPH_TREENEW_NODE Node, + _Inout_ PPH_SH_STATE ShState, + _Inout_ PPH_POINTER_LIST *StateList, + _In_ PH_ITEM_STATE NewState, + _In_ COLORREF NewTempBackColor, + _In_opt_ HWND TreeNewHandleForUpdate + ) +{ + if (!*StateList) + *StateList = PhCreatePointerList(4); + + if (ShState->State == NormalItemState) + ShState->StateListHandle = PhAddItemPointerList(*StateList, Node); + + ShState->TickCount = GetTickCount(); + ShState->State = NewState; + + Node->UseTempBackColor = TRUE; + Node->TempBackColor = NewTempBackColor; + + if (TreeNewHandleForUpdate) + TreeNew_InvalidateNode(TreeNewHandleForUpdate, Node); +} + +#define PH_TICK_SH_STATE_TN(NodeType, ShStateFieldName, StateList, RemoveFunction, HighlightingDuration, TreeNewHandleForUpdate, Invalidate, FullyInvalidated, ...) \ + do { \ + NodeType *node; \ + ULONG enumerationKey = 0; \ + ULONG tickCount; \ + BOOLEAN preferFullInvalidate; \ + HANDLE stateListHandle; \ + BOOLEAN redrawDisabled = FALSE; \ + BOOLEAN needsFullInvalidate = FALSE; \ +\ + if (!StateList || StateList->Count == 0) \ + break; \ +\ + tickCount = GetTickCount(); \ + preferFullInvalidate = StateList->Count > 8; \ +\ + while (PhEnumPointerList(StateList, &enumerationKey, &node)) \ + { \ + if (PhRoundNumber(tickCount - node->ShStateFieldName.TickCount, 100) < (HighlightingDuration)) \ + continue; \ +\ + stateListHandle = node->ShStateFieldName.StateListHandle; \ +\ + if (node->ShStateFieldName.State == NewItemState) \ + { \ + node->ShStateFieldName.State = NormalItemState; \ + ((PPH_TREENEW_NODE)node)->UseTempBackColor = FALSE; \ + if (Invalidate) \ + { \ + if (preferFullInvalidate) \ + { \ + needsFullInvalidate = TRUE; \ + } \ + else \ + { \ + TreeNew_InvalidateNode(TreeNewHandleForUpdate, node); \ + } \ + } \ + } \ + else if (node->ShStateFieldName.State == RemovingItemState) \ + { \ + if (TreeNewHandleForUpdate) \ + { \ + if (!redrawDisabled) \ + { \ + TreeNew_SetRedraw((TreeNewHandleForUpdate), FALSE); \ + redrawDisabled = TRUE; \ + } \ + } \ +\ + RemoveFunction(node, __VA_ARGS__); \ + needsFullInvalidate = TRUE; \ + } \ +\ + PhRemoveItemPointerList(StateList, stateListHandle); \ + } \ +\ + if (TreeNewHandleForUpdate) \ + { \ + if (redrawDisabled) \ + TreeNew_SetRedraw((TreeNewHandleForUpdate), TRUE); \ + if (needsFullInvalidate) \ + { \ + InvalidateRect((TreeNewHandleForUpdate), NULL, FALSE); \ + if (FullyInvalidated) \ + *((PBOOLEAN)FullyInvalidated) = TRUE; \ + } \ + } \ + } while (0) + +// Provider event queues + +typedef enum _PH_PROVIDER_EVENT_TYPE +{ + ProviderAddedEvent = 1, + ProviderModifiedEvent = 2, + ProviderRemovedEvent = 3 +} PH_PROVIDER_EVENT_TYPE; + +typedef struct _PH_PROVIDER_EVENT +{ + ULONG_PTR TypeAndObject; + ULONG RunId; +} PH_PROVIDER_EVENT, *PPH_PROVIDER_EVENT; + +#define PH_PROVIDER_EVENT_TYPE_MASK 0x3 +#define PH_PROVIDER_EVENT_OBJECT_MASK (~(ULONG_PTR)0x3) +#define PH_PROVIDER_EVENT_TYPE(Event) ((ULONG)(Event).TypeAndObject & PH_PROVIDER_EVENT_TYPE_MASK) +#define PH_PROVIDER_EVENT_OBJECT(Event) ((PVOID)((Event).TypeAndObject & PH_PROVIDER_EVENT_OBJECT_MASK)) + +typedef struct _PH_PROVIDER_EVENT_QUEUE +{ + PH_ARRAY Array; + PH_QUEUED_LOCK Lock; +} PH_PROVIDER_EVENT_QUEUE, *PPH_PROVIDER_EVENT_QUEUE; + +FORCEINLINE VOID PhInitializeProviderEventQueue( + _Out_ PPH_PROVIDER_EVENT_QUEUE EventQueue, + _In_ SIZE_T InitialCapacity + ) +{ + PhInitializeArray(&EventQueue->Array, sizeof(PH_PROVIDER_EVENT), InitialCapacity); + PhInitializeQueuedLock(&EventQueue->Lock); +} + +FORCEINLINE VOID PhDeleteProviderEventQueue( + _Inout_ PPH_PROVIDER_EVENT_QUEUE EventQueue + ) +{ + PPH_PROVIDER_EVENT events; + SIZE_T i; + + events = EventQueue->Array.Items; + + for (i = 0; i < EventQueue->Array.Count; i++) + { + if (PH_PROVIDER_EVENT_TYPE(events[i]) == ProviderAddedEvent) + PhDereferenceObject(PH_PROVIDER_EVENT_OBJECT(events[i])); + } + + PhDeleteArray(&EventQueue->Array); +} + +FORCEINLINE VOID PhPushProviderEventQueue( + _Inout_ PPH_PROVIDER_EVENT_QUEUE EventQueue, + _In_ PH_PROVIDER_EVENT_TYPE Type, + _In_opt_ PVOID Object, + _In_ ULONG RunId + ) +{ + PH_PROVIDER_EVENT event; + + assert(!(PtrToUlong(Object) & PH_PROVIDER_EVENT_TYPE_MASK)); + event.TypeAndObject = (ULONG_PTR)Object | Type; + event.RunId = RunId; + + PhAcquireQueuedLockExclusive(&EventQueue->Lock); + PhAddItemArray(&EventQueue->Array, &event); + PhReleaseQueuedLockExclusive(&EventQueue->Lock); +} + +FORCEINLINE PPH_PROVIDER_EVENT PhFlushProviderEventQueue( + _Inout_ PPH_PROVIDER_EVENT_QUEUE EventQueue, + _In_ ULONG UpToRunId, + _Out_ PULONG Count + ) +{ + PPH_PROVIDER_EVENT availableEvents; + PPH_PROVIDER_EVENT events = NULL; + SIZE_T count; + + PhAcquireQueuedLockExclusive(&EventQueue->Lock); + availableEvents = EventQueue->Array.Items; + + for (count = 0; count < EventQueue->Array.Count; count++) + { + if ((LONG)(UpToRunId - availableEvents[count].RunId) < 0) + break; + } + + if (count != 0) + { + events = PhAllocateCopy(availableEvents, count * sizeof(PH_PROVIDER_EVENT)); + PhRemoveItemsArray(&EventQueue->Array, 0, count); + } + + PhReleaseQueuedLockExclusive(&EventQueue->Lock); + + *Count = (ULONG)count; + + return events; +} + +#endif diff --git a/ProcessHacker/include/procgrp.h b/ProcessHacker/include/procgrp.h new file mode 100644 index 0000000..0a6c947 --- /dev/null +++ b/ProcessHacker/include/procgrp.h @@ -0,0 +1,32 @@ +#ifndef PH_PROCGRP_H +#define PH_PROCGRP_H + +// begin_phapppub +typedef struct _PH_PROCESS_GROUP +{ + PPH_PROCESS_ITEM Representative; // An element of Processes (no extra reference added) + PPH_LIST Processes; // List of PPH_PROCESS_ITEM + HWND WindowHandle; // Window handle of representative +} PH_PROCESS_GROUP, *PPH_PROCESS_GROUP; +// end_phapppub + +typedef VOID (NTAPI *PPH_SORT_LIST_FUNCTION)( + _In_ PPH_LIST List, + _In_opt_ PVOID Context + ); + +#define PH_GROUP_PROCESSES_DONT_GROUP 0x1 +#define PH_GROUP_PROCESSES_FILE_PATH 0x2 + +PPH_LIST PhCreateProcessGroupList( + _In_opt_ PPH_SORT_LIST_FUNCTION SortListFunction, // Sort a list of PPH_PROCESS_NODE + _In_opt_ PVOID Context, + _In_ ULONG MaximumGroups, + _In_ ULONG Flags + ); + +VOID PhFreeProcessGroupList( + _In_ PPH_LIST List + ); + +#endif \ No newline at end of file diff --git a/ProcessHacker/include/procmtgn.h b/ProcessHacker/include/procmtgn.h new file mode 100644 index 0000000..480e4d1 --- /dev/null +++ b/ProcessHacker/include/procmtgn.h @@ -0,0 +1,31 @@ +#ifndef PH_PROCMITIGATION_H +#define PH_PROCMITIGATION_H + +typedef struct _PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION +{ + PVOID Pointers[MaxProcessMitigationPolicy]; + PROCESS_MITIGATION_DEP_POLICY DEPPolicy; + PROCESS_MITIGATION_ASLR_POLICY ASLRPolicy; + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY StrictHandleCheckPolicy; + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY SystemCallDisablePolicy; + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY ExtensionPointDisablePolicy; + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY DynamicCodePolicy; + PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY ControlFlowGuardPolicy; + PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY SignaturePolicy; + PROCESS_MITIGATION_FONT_DISABLE_POLICY FontDisablePolicy; + PROCESS_MITIGATION_IMAGE_LOAD_POLICY ImageLoadPolicy; +} PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION, *PPH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION; + +NTSTATUS PhGetProcessMitigationPolicy( + _In_ HANDLE ProcessHandle, + _Out_ PPH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION Information + ); + +BOOLEAN PhDescribeProcessMitigationPolicy( + _In_ PROCESS_MITIGATION_POLICY Policy, + _In_ PVOID Data, + _Out_opt_ PPH_STRING *ShortDescription, + _Out_opt_ PPH_STRING *LongDescription + ); + +#endif diff --git a/ProcessHacker/include/procprpp.h b/ProcessHacker/include/procprpp.h new file mode 100644 index 0000000..ec616dc --- /dev/null +++ b/ProcessHacker/include/procprpp.h @@ -0,0 +1,341 @@ +#ifndef PH_PROCPRPP_H +#define PH_PROCPRPP_H + +#include +#include +#include +#include +#include + +typedef struct _PH_PROCESS_PROPSHEETCONTEXT +{ + WNDPROC OldWndProc; + PH_LAYOUT_MANAGER LayoutManager; + PPH_LAYOUT_ITEM TabPageItem; + BOOLEAN LayoutInitialized; +} PH_PROCESS_PROPSHEETCONTEXT, *PPH_PROCESS_PROPSHEETCONTEXT; + +VOID NTAPI PhpProcessPropContextDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +INT CALLBACK PhpPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ); + +PPH_PROCESS_PROPSHEETCONTEXT PhpGetPropSheetContext( + _In_ HWND hwnd + ); + +LRESULT CALLBACK PhpPropSheetWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID NTAPI PhpProcessPropPageContextDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +INT CALLBACK PhpStandardPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +FORCEINLINE BOOLEAN PhpPropPageDlgProcHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam, + _Out_ LPPROPSHEETPAGE *PropSheetPage, + _Out_ PPH_PROCESS_PROPPAGECONTEXT *PropPageContext, + _Out_ PPH_PROCESS_ITEM *ProcessItem + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + + if (uMsg == WM_INITDIALOG) + { + // Save the context. + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)lParam); + } + + propSheetPage = (LPPROPSHEETPAGE)GetProp(hwndDlg, PhMakeContextAtom()); + + if (!propSheetPage) + return FALSE; + + *PropSheetPage = propSheetPage; + *PropPageContext = propPageContext = (PPH_PROCESS_PROPPAGECONTEXT)propSheetPage->lParam; + *ProcessItem = propPageContext->PropContext->ProcessItem; + + return TRUE; +} + +FORCEINLINE VOID PhpPropPageDlgProcDestroy( + _In_ HWND hwndDlg + ) +{ + RemoveProp(hwndDlg, PhMakeContextAtom()); +} + +#define SET_BUTTON_ICON(Id, Icon) \ + SendMessage(GetDlgItem(hwndDlg, (Id)), BM_SETIMAGE, IMAGE_ICON, (LPARAM)(Icon)) + +INT_PTR CALLBACK PhpProcessGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessStatisticsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessPerformanceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessThreadsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +NTSTATUS NTAPI PhpOpenProcessTokenForPage( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ); + +INT_PTR CALLBACK PhpProcessTokenHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessModulesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessMemoryDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessEnvironmentDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessHandlesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +NTSTATUS NTAPI PhpOpenProcessJobForPage( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ); + +INT_PTR CALLBACK PhpProcessJobHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpProcessServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +extern PH_STRINGREF PhpLoadingText; + +#define WM_PH_THREADS_UPDATED (WM_APP + 200) +#define WM_PH_THREAD_SELECTION_CHANGED (WM_APP + 201) + +// begin_phapppub +typedef struct _PH_THREADS_CONTEXT +{ + PPH_THREAD_PROVIDER Provider; + PH_CALLBACK_REGISTRATION ProviderRegistration; + PH_CALLBACK_REGISTRATION AddedEventRegistration; + PH_CALLBACK_REGISTRATION ModifiedEventRegistration; + PH_CALLBACK_REGISTRATION RemovedEventRegistration; + PH_CALLBACK_REGISTRATION UpdatedEventRegistration; + PH_CALLBACK_REGISTRATION LoadingStateChangedEventRegistration; + + HWND WindowHandle; +// end_phapppub + + union + { + PH_THREAD_LIST_CONTEXT ListContext; + struct + { + HWND Private; // phapppub + HWND TreeNewHandle; // phapppub + } PublicUse; + }; + PH_PROVIDER_EVENT_QUEUE EventQueue; +// begin_phapppub +} PH_THREADS_CONTEXT, *PPH_THREADS_CONTEXT; +// end_phapppub + +#define WM_PH_MODULES_UPDATED (WM_APP + 210) + +// begin_phapppub +typedef struct _PH_MODULES_CONTEXT +{ + PPH_MODULE_PROVIDER Provider; + PH_PROVIDER_REGISTRATION ProviderRegistration; + PH_CALLBACK_REGISTRATION AddedEventRegistration; + PH_CALLBACK_REGISTRATION ModifiedEventRegistration; + PH_CALLBACK_REGISTRATION RemovedEventRegistration; + PH_CALLBACK_REGISTRATION UpdatedEventRegistration; + + HWND WindowHandle; +// end_phapppub + + union + { + PH_MODULE_LIST_CONTEXT ListContext; + struct + { + HWND Private; // phapppub + HWND TreeNewHandle; // phapppub + } PublicUse; + }; + PH_PROVIDER_EVENT_QUEUE EventQueue; + NTSTATUS LastRunStatus; + PPH_STRING ErrorMessage; +// begin_phapppub +} PH_MODULES_CONTEXT, *PPH_MODULES_CONTEXT; +// end_phapppub + +#define WM_PH_HANDLES_UPDATED (WM_APP + 220) + +// begin_phapppub +typedef struct _PH_HANDLES_CONTEXT +{ + PPH_HANDLE_PROVIDER Provider; + PH_PROVIDER_REGISTRATION ProviderRegistration; + PH_CALLBACK_REGISTRATION AddedEventRegistration; + PH_CALLBACK_REGISTRATION ModifiedEventRegistration; + PH_CALLBACK_REGISTRATION RemovedEventRegistration; + PH_CALLBACK_REGISTRATION UpdatedEventRegistration; + + HWND WindowHandle; +// end_phapppub + + union + { + PH_HANDLE_LIST_CONTEXT ListContext; + struct + { + HWND Private; // phapppub + HWND TreeNewHandle; // phapppub + } PublicUse; + }; + PH_PROVIDER_EVENT_QUEUE EventQueue; + BOOLEAN SelectedHandleProtected; + BOOLEAN SelectedHandleInherit; + NTSTATUS LastRunStatus; + PPH_STRING ErrorMessage; +// begin_phapppub +} PH_HANDLES_CONTEXT, *PPH_HANDLES_CONTEXT; +// end_phapppub + +// begin_phapppub +typedef struct _PH_MEMORY_CONTEXT +{ + HANDLE ProcessId; + HWND WindowHandle; +// end_phapppub + + union + { + PH_MEMORY_LIST_CONTEXT ListContext; + struct + { + HWND Private; // phapppub + HWND TreeNewHandle; // phapppub + } PublicUse; + }; + PH_MEMORY_ITEM_LIST MemoryItemList; + BOOLEAN MemoryItemListValid; + NTSTATUS LastRunStatus; + PPH_STRING ErrorMessage; +// begin_phapppub +} PH_MEMORY_CONTEXT, *PPH_MEMORY_CONTEXT; +// end_phapppub + +#define WM_PH_STATISTICS_UPDATE (WM_APP + 231) + +typedef struct _PH_STATISTICS_CONTEXT +{ + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + + HWND WindowHandle; + BOOLEAN Enabled; + HANDLE ProcessHandle; +} PH_STATISTICS_CONTEXT, *PPH_STATISTICS_CONTEXT; + +#define WM_PH_PERFORMANCE_UPDATE (WM_APP + 241) + +typedef struct _PH_PERFORMANCE_CONTEXT +{ + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + + HWND WindowHandle; + + PH_GRAPH_STATE CpuGraphState; + PH_GRAPH_STATE PrivateGraphState; + PH_GRAPH_STATE IoGraphState; + + HWND CpuGraphHandle; + HWND PrivateGraphHandle; + HWND IoGraphHandle; +} PH_PERFORMANCE_CONTEXT, *PPH_PERFORMANCE_CONTEXT; + +typedef struct _PH_ENVIRONMENT_ITEM +{ + PPH_STRING Name; + PPH_STRING Value; +} PH_ENVIRONMENT_ITEM, *PPH_ENVIRONMENT_ITEM; + +typedef struct _PH_ENVIRONMENT_CONTEXT +{ + HWND ListViewHandle; + PH_ARRAY Items; +} PH_ENVIRONMENT_CONTEXT, *PPH_ENVIRONMENT_CONTEXT; + +#endif diff --git a/ProcessHacker/include/procprv.h b/ProcessHacker/include/procprv.h new file mode 100644 index 0000000..250c4c5 --- /dev/null +++ b/ProcessHacker/include/procprv.h @@ -0,0 +1,409 @@ +#ifndef PH_PROCPRV_H +#define PH_PROCPRV_H + +#define PH_RECORD_MAX_USAGE +#define PH_ENABLE_VERIFY_CACHE + +extern PPH_OBJECT_TYPE PhProcessItemType; + +PHAPPAPI extern PH_CALLBACK PhProcessAddedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhProcessModifiedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhProcessRemovedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhProcessesUpdatedEvent; // phapppub + +extern PPH_LIST PhProcessRecordList; +extern PH_QUEUED_LOCK PhProcessRecordListLock; + +extern ULONG PhStatisticsSampleCount; +extern BOOLEAN PhEnableProcessQueryStage2; +extern BOOLEAN PhEnablePurgeProcessRecords; +extern BOOLEAN PhEnableCycleCpuUsage; + +extern PVOID PhProcessInformation; // only can be used if running on same thread as process provider +extern ULONG PhProcessInformationSequenceNumber; +extern SYSTEM_PERFORMANCE_INFORMATION PhPerfInformation; +extern PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PhCpuInformation; +extern SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PhCpuTotals; +extern ULONG PhTotalProcesses; +extern ULONG PhTotalThreads; +extern ULONG PhTotalHandles; + +extern ULONG64 PhCpuTotalCycleDelta; +extern PLARGE_INTEGER PhCpuIdleCycleTime; // cycle time for Idle +extern PLARGE_INTEGER PhCpuSystemCycleTime; // cycle time for DPCs and Interrupts +extern PH_UINT64_DELTA PhCpuIdleCycleDelta; +extern PH_UINT64_DELTA PhCpuSystemCycleDelta; + +extern FLOAT PhCpuKernelUsage; +extern FLOAT PhCpuUserUsage; +extern PFLOAT PhCpusKernelUsage; +extern PFLOAT PhCpusUserUsage; + +extern PH_UINT64_DELTA PhCpuKernelDelta; +extern PH_UINT64_DELTA PhCpuUserDelta; +extern PH_UINT64_DELTA PhCpuIdleDelta; + +extern PPH_UINT64_DELTA PhCpusKernelDelta; +extern PPH_UINT64_DELTA PhCpusUserDelta; +extern PPH_UINT64_DELTA PhCpusIdleDelta; + +extern PH_UINT64_DELTA PhIoReadDelta; +extern PH_UINT64_DELTA PhIoWriteDelta; +extern PH_UINT64_DELTA PhIoOtherDelta; + +extern PH_CIRCULAR_BUFFER_FLOAT PhCpuKernelHistory; +extern PH_CIRCULAR_BUFFER_FLOAT PhCpuUserHistory; +//extern PH_CIRCULAR_BUFFER_FLOAT PhCpuOtherHistory; + +extern PPH_CIRCULAR_BUFFER_FLOAT PhCpusKernelHistory; +extern PPH_CIRCULAR_BUFFER_FLOAT PhCpusUserHistory; +//extern PPH_CIRCULAR_BUFFER_FLOAT PhCpusOtherHistory; + +extern PH_CIRCULAR_BUFFER_ULONG64 PhIoReadHistory; +extern PH_CIRCULAR_BUFFER_ULONG64 PhIoWriteHistory; +extern PH_CIRCULAR_BUFFER_ULONG64 PhIoOtherHistory; + +extern PH_CIRCULAR_BUFFER_ULONG PhCommitHistory; +extern PH_CIRCULAR_BUFFER_ULONG PhPhysicalHistory; + +extern PH_CIRCULAR_BUFFER_ULONG PhMaxCpuHistory; +extern PH_CIRCULAR_BUFFER_ULONG PhMaxIoHistory; +#ifdef PH_RECORD_MAX_USAGE +extern PH_CIRCULAR_BUFFER_FLOAT PhMaxCpuUsageHistory; +extern PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoReadOtherHistory; +extern PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoWriteHistory; +#endif + +// begin_phapppub +#define DPCS_PROCESS_ID ((HANDLE)(LONG_PTR)-2) +#define INTERRUPTS_PROCESS_ID ((HANDLE)(LONG_PTR)-3) + +// DPCs, Interrupts and System Idle Process are not real. +// Non-"real" processes can never be opened. +#define PH_IS_REAL_PROCESS_ID(ProcessId) ((LONG_PTR)(ProcessId) > 0) + +// DPCs and Interrupts are fake, but System Idle Process is not. +#define PH_IS_FAKE_PROCESS_ID(ProcessId) ((LONG_PTR)(ProcessId) < 0) + +// The process item has been removed. +#define PH_PROCESS_ITEM_REMOVED 0x1 +// end_phapppub + +#define PH_INTEGRITY_STR_LEN 10 +#define PH_INTEGRITY_STR_LEN_1 (PH_INTEGRITY_STR_LEN + 1) + +// begin_phapppub +typedef enum _VERIFY_RESULT VERIFY_RESULT; +typedef struct _PH_PROCESS_RECORD *PPH_PROCESS_RECORD; + +typedef struct _PH_PROCESS_ITEM +{ + PH_HASH_ENTRY HashEntry; + ULONG State; + PPH_PROCESS_RECORD Record; + + // Basic + + HANDLE ProcessId; + HANDLE ParentProcessId; + PPH_STRING ProcessName; + ULONG SessionId; + + LARGE_INTEGER CreateTime; + + // Handles + + HANDLE QueryHandle; + + // Parameters + + PPH_STRING FileName; + PPH_STRING CommandLine; + + // File + + HICON SmallIcon; + HICON LargeIcon; + PH_IMAGE_VERSION_INFO VersionInfo; + + // Security + + PPH_STRING UserName; + TOKEN_ELEVATION_TYPE ElevationType; + MANDATORY_LEVEL IntegrityLevel; + PWSTR IntegrityString; + + // Other + + PPH_STRING JobName; + HANDLE ConsoleHostProcessId; + + // Signature, Packed + + VERIFY_RESULT VerifyResult; + PPH_STRING VerifySignerName; + ULONG ImportFunctions; + ULONG ImportModules; + + // Flags + + union + { + ULONG Flags; + struct + { + ULONG UpdateIsDotNet : 1; + ULONG IsBeingDebugged : 1; + ULONG IsDotNet : 1; + ULONG IsElevated : 1; + ULONG IsInJob : 1; + ULONG IsInSignificantJob : 1; + ULONG IsPacked : 1; + ULONG Reserved : 1; + ULONG IsSuspended : 1; + ULONG IsWow64 : 1; + ULONG IsImmersive : 1; + ULONG IsWow64Valid : 1; + ULONG IsPartiallySuspended : 1; + ULONG AddedEventSent : 1; + ULONG Spare : 18; + }; + }; + + // Misc. + + ULONG JustProcessed; + PH_EVENT Stage1Event; + + PPH_POINTER_LIST ServiceList; + PH_QUEUED_LOCK ServiceListLock; + + WCHAR ProcessIdString[PH_INT32_STR_LEN_1]; + WCHAR ParentProcessIdString[PH_INT32_STR_LEN_1]; + WCHAR SessionIdString[PH_INT32_STR_LEN_1]; + + // Dynamic + + KPRIORITY BasePriority; + ULONG PriorityClass; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + ULONG NumberOfHandles; + ULONG NumberOfThreads; + + FLOAT CpuUsage; // Below Windows 7, sum of kernel and user CPU usage; above Windows 7, cycle-based CPU usage. + FLOAT CpuKernelUsage; + FLOAT CpuUserUsage; + + PH_UINT64_DELTA CpuKernelDelta; + PH_UINT64_DELTA CpuUserDelta; + PH_UINT64_DELTA IoReadDelta; + PH_UINT64_DELTA IoWriteDelta; + PH_UINT64_DELTA IoOtherDelta; + PH_UINT64_DELTA IoReadCountDelta; + PH_UINT64_DELTA IoWriteCountDelta; + PH_UINT64_DELTA IoOtherCountDelta; + PH_UINT32_DELTA ContextSwitchesDelta; + PH_UINT32_DELTA PageFaultsDelta; + PH_UINT64_DELTA CycleTimeDelta; // since WIN7 + + VM_COUNTERS_EX VmCounters; + IO_COUNTERS IoCounters; + SIZE_T WorkingSetPrivateSize; // since VISTA + ULONG PeakNumberOfThreads; // since WIN7 + ULONG HardFaultCount; // since WIN7 + + ULONG SequenceNumber; + PH_CIRCULAR_BUFFER_FLOAT CpuKernelHistory; + PH_CIRCULAR_BUFFER_FLOAT CpuUserHistory; + PH_CIRCULAR_BUFFER_ULONG64 IoReadHistory; + PH_CIRCULAR_BUFFER_ULONG64 IoWriteHistory; + PH_CIRCULAR_BUFFER_ULONG64 IoOtherHistory; + PH_CIRCULAR_BUFFER_SIZE_T PrivateBytesHistory; + //PH_CIRCULAR_BUFFER_SIZE_T WorkingSetHistory; + + // New fields + PH_UINTPTR_DELTA PrivateBytesDelta; + PPH_STRING PackageFullName; + + PH_QUEUED_LOCK RemoveLock; +} PH_PROCESS_ITEM, *PPH_PROCESS_ITEM; +// end_phapppub + +// begin_phapppub +// The process itself is dead. +#define PH_PROCESS_RECORD_DEAD 0x1 +// An extra reference has been added to the process record for the statistics system. +#define PH_PROCESS_RECORD_STAT_REF 0x2 + +typedef struct _PH_PROCESS_RECORD +{ + LIST_ENTRY ListEntry; + LONG RefCount; + ULONG Flags; + + HANDLE ProcessId; + HANDLE ParentProcessId; + ULONG SessionId; + LARGE_INTEGER CreateTime; + LARGE_INTEGER ExitTime; + + PPH_STRING ProcessName; + PPH_STRING FileName; + PPH_STRING CommandLine; + /*PPH_STRING UserName;*/ +} PH_PROCESS_RECORD, *PPH_PROCESS_RECORD; +// end_phapppub + +BOOLEAN PhProcessProviderInitialization( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_STRING +NTAPI +PhGetClientIdName( + _In_ PCLIENT_ID ClientId + ); + +PHAPPAPI +PPH_STRING +NTAPI +PhGetClientIdNameEx( + _In_ PCLIENT_ID ClientId, + _In_opt_ PPH_STRING ProcessName + ); + +PHAPPAPI +PWSTR +NTAPI +PhGetProcessPriorityClassString( + _In_ ULONG PriorityClass + ); +// end_phapppub + +PPH_PROCESS_ITEM PhCreateProcessItem( + _In_ HANDLE ProcessId + ); + +// begin_phapppub +PHAPPAPI +PPH_PROCESS_ITEM +NTAPI +PhReferenceProcessItem( + _In_ HANDLE ProcessId + ); + +PHAPPAPI +VOID +NTAPI +PhEnumProcessItems( + _Out_opt_ PPH_PROCESS_ITEM **ProcessItems, + _Out_ PULONG NumberOfProcessItems + ); +// end_phapppub + +typedef struct _PH_VERIFY_FILE_INFO *PPH_VERIFY_FILE_INFO; + +VERIFY_RESULT PhVerifyFileWithAdditionalCatalog( + _In_ PPH_VERIFY_FILE_INFO Information, + _In_opt_ PWSTR PackageFullName, + _Out_opt_ PPH_STRING *SignerName + ); + +VERIFY_RESULT PhVerifyFileCached( + _In_ PPH_STRING FileName, + _In_opt_ PWSTR PackageFullName, + _Out_opt_ PPH_STRING *SignerName, + _In_ BOOLEAN CachedOnly + ); + +// begin_phapppub +PHAPPAPI +BOOLEAN +NTAPI +PhGetStatisticsTime( + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Index, + _Out_ PLARGE_INTEGER Time + ); + +PHAPPAPI +PPH_STRING +NTAPI +PhGetStatisticsTimeString( + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Index + ); +// end_phapppub + +VOID PhFlushProcessQueryData( + _In_ BOOLEAN SendModifiedEvent + ); + +VOID PhProcessProviderUpdate( + _In_ PVOID Object + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhReferenceProcessRecord( + _In_ PPH_PROCESS_RECORD ProcessRecord + ); + +PHAPPAPI +BOOLEAN +NTAPI +PhReferenceProcessRecordSafe( + _In_ PPH_PROCESS_RECORD ProcessRecord + ); + +PHAPPAPI +VOID +NTAPI +PhReferenceProcessRecordForStatistics( + _In_ PPH_PROCESS_RECORD ProcessRecord + ); + +PHAPPAPI +VOID +NTAPI +PhDereferenceProcessRecord( + _In_ PPH_PROCESS_RECORD ProcessRecord + ); + +PHAPPAPI +PPH_PROCESS_RECORD +NTAPI +PhFindProcessRecord( + _In_opt_ HANDLE ProcessId, + _In_ PLARGE_INTEGER Time + ); +// end_phapppub + +VOID PhPurgeProcessRecords( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_PROCESS_ITEM +NTAPI +PhReferenceProcessItemForParent( + _In_ HANDLE ParentProcessId, + _In_ HANDLE ProcessId, + _In_ PLARGE_INTEGER CreateTime + ); + +PHAPPAPI +PPH_PROCESS_ITEM +NTAPI +PhReferenceProcessItemForRecord( + _In_ PPH_PROCESS_RECORD Record + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/proctree.h b/ProcessHacker/include/proctree.h new file mode 100644 index 0000000..bce21d4 --- /dev/null +++ b/ProcessHacker/include/proctree.h @@ -0,0 +1,362 @@ +#ifndef PH_PROCTREE_H +#define PH_PROCTREE_H + +#include + +// Columns + +// Default columns should go first +#define PHPRTLC_NAME 0 +#define PHPRTLC_PID 1 +#define PHPRTLC_CPU 2 +#define PHPRTLC_IOTOTALRATE 3 +#define PHPRTLC_PRIVATEBYTES 4 +#define PHPRTLC_USERNAME 5 +#define PHPRTLC_DESCRIPTION 6 + +#define PHPRTLC_COMPANYNAME 7 +#define PHPRTLC_VERSION 8 +#define PHPRTLC_FILENAME 9 +#define PHPRTLC_COMMANDLINE 10 +#define PHPRTLC_PEAKPRIVATEBYTES 11 +#define PHPRTLC_WORKINGSET 12 +#define PHPRTLC_PEAKWORKINGSET 13 +#define PHPRTLC_PRIVATEWS 14 +#define PHPRTLC_SHAREDWS 15 +#define PHPRTLC_SHAREABLEWS 16 +#define PHPRTLC_VIRTUALSIZE 17 +#define PHPRTLC_PEAKVIRTUALSIZE 18 +#define PHPRTLC_PAGEFAULTS 19 +#define PHPRTLC_SESSIONID 20 +#define PHPRTLC_PRIORITYCLASS 21 +#define PHPRTLC_BASEPRIORITY 22 + +#define PHPRTLC_THREADS 23 +#define PHPRTLC_HANDLES 24 +#define PHPRTLC_GDIHANDLES 25 +#define PHPRTLC_USERHANDLES 26 +#define PHPRTLC_IORORATE 27 +#define PHPRTLC_IOWRATE 28 +#define PHPRTLC_INTEGRITY 29 +#define PHPRTLC_IOPRIORITY 30 +#define PHPRTLC_PAGEPRIORITY 31 +#define PHPRTLC_STARTTIME 32 +#define PHPRTLC_TOTALCPUTIME 33 +#define PHPRTLC_KERNELCPUTIME 34 +#define PHPRTLC_USERCPUTIME 35 +#define PHPRTLC_VERIFICATIONSTATUS 36 +#define PHPRTLC_VERIFIEDSIGNER 37 +#define PHPRTLC_ASLR 38 +#define PHPRTLC_RELATIVESTARTTIME 39 +#define PHPRTLC_BITS 40 +#define PHPRTLC_ELEVATION 41 +#define PHPRTLC_WINDOWTITLE 42 +#define PHPRTLC_WINDOWSTATUS 43 +#define PHPRTLC_CYCLES 44 +#define PHPRTLC_CYCLESDELTA 45 +#define PHPRTLC_CPUHISTORY 46 +#define PHPRTLC_PRIVATEBYTESHISTORY 47 +#define PHPRTLC_IOHISTORY 48 +#define PHPRTLC_DEP 49 +#define PHPRTLC_VIRTUALIZED 50 +#define PHPRTLC_CONTEXTSWITCHES 51 +#define PHPRTLC_CONTEXTSWITCHESDELTA 52 +#define PHPRTLC_PAGEFAULTSDELTA 53 + +#define PHPRTLC_IOREADS 54 +#define PHPRTLC_IOWRITES 55 +#define PHPRTLC_IOOTHER 56 +#define PHPRTLC_IOREADBYTES 57 +#define PHPRTLC_IOWRITEBYTES 58 +#define PHPRTLC_IOOTHERBYTES 59 +#define PHPRTLC_IOREADSDELTA 60 +#define PHPRTLC_IOWRITESDELTA 61 +#define PHPRTLC_IOOTHERDELTA 62 + +#define PHPRTLC_OSCONTEXT 63 +#define PHPRTLC_PAGEDPOOL 64 +#define PHPRTLC_PEAKPAGEDPOOL 65 +#define PHPRTLC_NONPAGEDPOOL 66 +#define PHPRTLC_PEAKNONPAGEDPOOL 67 +#define PHPRTLC_MINIMUMWORKINGSET 68 +#define PHPRTLC_MAXIMUMWORKINGSET 69 +#define PHPRTLC_PRIVATEBYTESDELTA 70 +#define PHPRTLC_SUBSYSTEM 71 +#define PHPRTLC_PACKAGENAME 72 +#define PHPRTLC_APPID 73 +#define PHPRTLC_DPIAWARENESS 74 +#define PHPRTLC_CFGUARD 75 +#define PHPRTLC_TIMESTAMP 76 +#define PHPRTLC_FILEMODIFIEDTIME 77 +#define PHPRTLC_FILESIZE 78 + +#define PHPRTLC_MAXIMUM 79 +#define PHPRTLC_IOGROUP_COUNT 9 + +#define PHPN_WSCOUNTERS 0x1 +#define PHPN_GDIUSERHANDLES 0x2 +#define PHPN_IOPAGEPRIORITY 0x4 +#define PHPN_WINDOW 0x8 +#define PHPN_DEPSTATUS 0x10 +#define PHPN_TOKEN 0x20 +#define PHPN_OSCONTEXT 0x40 +#define PHPN_QUOTALIMITS 0x80 +#define PHPN_IMAGE 0x100 +#define PHPN_APPID 0x200 +#define PHPN_DPIAWARENESS 0x400 +#define PHPN_FILEATTRIBUTES 0x800 + +// begin_phapppub +typedef struct _PH_PROCESS_NODE +{ + PH_TREENEW_NODE Node; + + PH_HASH_ENTRY HashEntry; + + PH_SH_STATE ShState; + + HANDLE ProcessId; + PPH_PROCESS_ITEM ProcessItem; + + struct _PH_PROCESS_NODE *Parent; + PPH_LIST Children; +// end_phapppub + + PH_STRINGREF TextCache[PHPRTLC_MAXIMUM]; + + PH_STRINGREF DescriptionText; + + // If the user has selected certain columns we need extra information that isn't retrieved by + // the process provider. + ULONG ValidMask; + + // WS counters + PH_PROCESS_WS_COUNTERS WsCounters; + // GDI, USER handles + ULONG GdiHandles; + ULONG UserHandles; + // I/O, page priority + IO_PRIORITY_HINT IoPriority; + ULONG PagePriority; + // Window + HWND WindowHandle; + PPH_STRING WindowText; + BOOLEAN WindowHung; + // DEP status + ULONG DepStatus; + // Token + BOOLEAN VirtualizationAllowed; + BOOLEAN VirtualizationEnabled; + // OS Context + GUID OsContextGuid; + ULONG OsContextVersion; + // Quota limits + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + // Image + ULONG ImageTimeDateStamp; + USHORT ImageCharacteristics; + USHORT ImageReserved; + USHORT ImageSubsystem; + USHORT ImageDllCharacteristics; + // App ID + PPH_STRING AppIdText; + // Cycles (Vista only) + PH_UINT64_DELTA CyclesDelta; + // DPI awareness + ULONG DpiAwareness; + // File attributes + LARGE_INTEGER FileLastWriteTime; + LARGE_INTEGER FileEndOfFile; + + PPH_STRING TooltipText; + ULONG TooltipTextValidToTickCount; + + // Text buffers + WCHAR CpuUsageText[PH_INT32_STR_LEN_1]; + PPH_STRING IoTotalRateText; + PPH_STRING PrivateBytesText; + PPH_STRING PeakPrivateBytesText; + PPH_STRING WorkingSetText; + PPH_STRING PeakWorkingSetText; + PPH_STRING PrivateWsText; + PPH_STRING SharedWsText; + PPH_STRING ShareableWsText; + PPH_STRING VirtualSizeText; + PPH_STRING PeakVirtualSizeText; + PPH_STRING PageFaultsText; + WCHAR BasePriorityText[PH_INT32_STR_LEN_1]; + WCHAR ThreadsText[PH_INT32_STR_LEN_1 + 3]; + WCHAR HandlesText[PH_INT32_STR_LEN_1 + 3]; + WCHAR GdiHandlesText[PH_INT32_STR_LEN_1 + 3]; + WCHAR UserHandlesText[PH_INT32_STR_LEN_1 + 3]; + PPH_STRING IoRoRateText; + PPH_STRING IoWRateText; + WCHAR PagePriorityText[PH_INT32_STR_LEN_1]; + PPH_STRING StartTimeText; + PPH_STRING TotalCpuTimeText; + PPH_STRING KernelCpuTimeText; + PPH_STRING UserCpuTimeText; + PPH_STRING RelativeStartTimeText; + PPH_STRING WindowTitleText; + PPH_STRING CyclesText; + PPH_STRING CyclesDeltaText; + PPH_STRING ContextSwitchesText; + PPH_STRING ContextSwitchesDeltaText; + PPH_STRING PageFaultsDeltaText; + PPH_STRING IoGroupText[PHPRTLC_IOGROUP_COUNT]; + PPH_STRING PagedPoolText; + PPH_STRING PeakPagedPoolText; + PPH_STRING NonPagedPoolText; + PPH_STRING PeakNonPagedPoolText; + PPH_STRING MinimumWorkingSetText; + PPH_STRING MaximumWorkingSetText; + PPH_STRING PrivateBytesDeltaText; + PPH_STRING TimeStampText; + PPH_STRING FileModifiedTimeText; + PPH_STRING FileSizeText; + + // Graph buffers + PH_GRAPH_BUFFERS CpuGraphBuffers; + PH_GRAPH_BUFFERS PrivateGraphBuffers; + PH_GRAPH_BUFFERS IoGraphBuffers; +// begin_phapppub +} PH_PROCESS_NODE, *PPH_PROCESS_NODE; +// end_phapppub + +VOID PhProcessTreeListInitialization( + VOID + ); + +VOID PhInitializeProcessTreeList( + _In_ HWND hwnd + ); + +VOID PhLoadSettingsProcessTreeList( + VOID + ); + +VOID PhSaveSettingsProcessTreeList( + VOID + ); + +VOID PhReloadSettingsProcessTreeList( + VOID + ); + +// begin_phapppub +PHAPPAPI +struct _PH_TN_FILTER_SUPPORT * +NTAPI +PhGetFilterSupportProcessTreeList( + VOID + ); +// end_phapppub + +PPH_PROCESS_NODE PhAddProcessNode( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG RunId + ); + +// begin_phapppub +PHAPPAPI +PPH_PROCESS_NODE +NTAPI +PhFindProcessNode( + _In_ HANDLE ProcessId + ); +// end_phapppub + +VOID PhRemoveProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhUpdateProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ); +// end_phapppub + +VOID PhTickProcessNodes( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_PROCESS_ITEM +NTAPI +PhGetSelectedProcessItem( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhGetSelectedProcessItems( + _Out_ PPH_PROCESS_ITEM **Processes, + _Out_ PULONG NumberOfProcesses + ); + +PHAPPAPI +VOID +NTAPI +PhDeselectAllProcessNodes( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhExpandAllProcessNodes( + _In_ BOOLEAN Expand + ); + +PHAPPAPI +VOID +NTAPI +PhInvalidateAllProcessNodes( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhSelectAndEnsureVisibleProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ); +// end_phapppub + +VOID PhSelectAndEnsureVisibleProcessNodes( + _In_ PPH_PROCESS_NODE *ProcessNodes, + _In_ ULONG NumberOfProcessNodes + ); + +PPH_LIST PhGetProcessTreeListLines( + _In_ HWND TreeListHandle, + _In_ ULONG NumberOfNodes, + _In_ PPH_LIST RootNodes, + _In_ ULONG Mode + ); + +VOID PhCopyProcessTree( + VOID + ); + +VOID PhWriteProcessTree( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ); + +// begin_phapppub +PHAPPAPI +PPH_LIST +NTAPI +PhDuplicateProcessNodeList( + VOID + ); +// end_phapppub + +#endif diff --git a/ProcessHacker/include/settings.h b/ProcessHacker/include/settings.h new file mode 100644 index 0000000..345f2e5 --- /dev/null +++ b/ProcessHacker/include/settings.h @@ -0,0 +1,227 @@ +#ifndef PH_SETTINGS_H +#define PH_SETTINGS_H + +// begin_phapppub +typedef enum _PH_SETTING_TYPE +{ + StringSettingType, + IntegerSettingType, + IntegerPairSettingType, + ScalableIntegerPairSettingType +} PH_SETTING_TYPE, PPH_SETTING_TYPE; +// end_phapppub + +typedef struct _PH_SETTING +{ + PH_SETTING_TYPE Type; + PH_STRINGREF Name; + PH_STRINGREF DefaultValue; + + union + { + PVOID Pointer; + ULONG Integer; + PH_INTEGER_PAIR IntegerPair; + } u; +} PH_SETTING, *PPH_SETTING; + +VOID PhSettingsInitialization( + VOID + ); + +VOID PhUpdateCachedSettings( + VOID + ); + +// begin_phapppub +_May_raise_ +PHAPPAPI +ULONG +NTAPI +PhGetIntegerSetting( + _In_ PWSTR Name + ); + +_May_raise_ +PHAPPAPI +PH_INTEGER_PAIR +NTAPI +PhGetIntegerPairSetting( + _In_ PWSTR Name + ); + +_May_raise_ +PHAPPAPI +PH_SCALABLE_INTEGER_PAIR +NTAPI +PhGetScalableIntegerPairSetting( + _In_ PWSTR Name, + _In_ BOOLEAN ScaleToCurrent + ); + +_May_raise_ +PHAPPAPI +PPH_STRING +NTAPI +PhGetStringSetting( + _In_ PWSTR Name + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetIntegerSetting( + _In_ PWSTR Name, + _In_ ULONG Value + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetIntegerPairSetting( + _In_ PWSTR Name, + _In_ PH_INTEGER_PAIR Value + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetScalableIntegerPairSetting( + _In_ PWSTR Name, + _In_ PH_SCALABLE_INTEGER_PAIR Value + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetScalableIntegerPairSetting2( + _In_ PWSTR Name, + _In_ PH_INTEGER_PAIR Value + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetStringSetting( + _In_ PWSTR Name, + _In_ PWSTR Value + ); + +_May_raise_ +PHAPPAPI +VOID +NTAPI +PhSetStringSetting2( + _In_ PWSTR Name, + _In_ PPH_STRINGREF Value + ); +// end_phapppub + +VOID PhClearIgnoredSettings( + VOID + ); + +VOID PhConvertIgnoredSettings( + VOID + ); + +NTSTATUS PhLoadSettings( + _In_ PWSTR FileName + ); + +NTSTATUS PhSaveSettings( + _In_ PWSTR FileName + ); + +VOID PhResetSettings( + VOID + ); + +#define PhaGetStringSetting(Name) PH_AUTO_T(PH_STRING, PhGetStringSetting(Name)) // phapppub + +// begin_phapppub +// High-level settings creation + +typedef struct _PH_SETTING_CREATE +{ + PH_SETTING_TYPE Type; + PWSTR Name; + PWSTR DefaultValue; +} PH_SETTING_CREATE, *PPH_SETTING_CREATE; + +PHAPPAPI +VOID +NTAPI +PhAddSettings( + _In_ PPH_SETTING_CREATE Settings, + _In_ ULONG NumberOfSettings + ); +// end_phapppub + +// Cached settings + +#undef EXT + +#ifdef PH_SETTINGS_PRIVATE +#define EXT +#else +#define EXT extern +#endif + +EXT ULONG PhCsCollapseServicesOnStart; +EXT ULONG PhCsForceNoParent; +EXT ULONG PhCsHighlightingDuration; +EXT ULONG PhCsPropagateCpuUsage; +EXT ULONG PhCsScrollToNewProcesses; +EXT ULONG PhCsShowCpuBelow001; +EXT ULONG PhCsUpdateInterval; + +EXT ULONG PhCsColorNew; +EXT ULONG PhCsColorRemoved; +EXT ULONG PhCsUseColorOwnProcesses; +EXT ULONG PhCsColorOwnProcesses; +EXT ULONG PhCsUseColorSystemProcesses; +EXT ULONG PhCsColorSystemProcesses; +EXT ULONG PhCsUseColorServiceProcesses; +EXT ULONG PhCsColorServiceProcesses; +EXT ULONG PhCsUseColorJobProcesses; +EXT ULONG PhCsColorJobProcesses; +EXT ULONG PhCsUseColorWow64Processes; +EXT ULONG PhCsColorWow64Processes; +EXT ULONG PhCsUseColorDebuggedProcesses; +EXT ULONG PhCsColorDebuggedProcesses; +EXT ULONG PhCsUseColorElevatedProcesses; +EXT ULONG PhCsColorElevatedProcesses; +EXT ULONG PhCsUseColorImmersiveProcesses; +EXT ULONG PhCsColorImmersiveProcesses; +EXT ULONG PhCsUseColorSuspended; +EXT ULONG PhCsColorSuspended; +EXT ULONG PhCsUseColorDotNet; +EXT ULONG PhCsColorDotNet; +EXT ULONG PhCsUseColorPacked; +EXT ULONG PhCsColorPacked; +EXT ULONG PhCsUseColorGuiThreads; +EXT ULONG PhCsColorGuiThreads; +EXT ULONG PhCsUseColorRelocatedModules; +EXT ULONG PhCsColorRelocatedModules; +EXT ULONG PhCsUseColorProtectedHandles; +EXT ULONG PhCsColorProtectedHandles; +EXT ULONG PhCsUseColorInheritHandles; +EXT ULONG PhCsColorInheritHandles; +EXT ULONG PhCsGraphShowText; +EXT ULONG PhCsGraphColorMode; +EXT ULONG PhCsColorCpuKernel; +EXT ULONG PhCsColorCpuUser; +EXT ULONG PhCsColorIoReadOther; +EXT ULONG PhCsColorIoWrite; +EXT ULONG PhCsColorPrivate; +EXT ULONG PhCsColorPhysical; + +#define PH_SET_INTEGER_CACHED_SETTING(Name, Value) (PhSetIntegerSetting(L#Name, PhCs##Name = (Value))) + +#endif diff --git a/ProcessHacker/include/settingsp.h b/ProcessHacker/include/settingsp.h new file mode 100644 index 0000000..8b31daa --- /dev/null +++ b/ProcessHacker/include/settingsp.h @@ -0,0 +1,46 @@ +#ifndef PH_SETTINGSP_H +#define PH_SETTINGSP_H + +#include + +BOOLEAN NTAPI PhpSettingsHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpSettingsHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpAddSetting( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_STRINGREF Name, + _In_ PPH_STRINGREF DefaultValue + ); + +ULONG PhpGetCurrentScale( + VOID + ); + +PPH_STRING PhpSettingToString( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_SETTING Setting + ); + +BOOLEAN PhpSettingFromString( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_STRINGREF StringRef, + _In_opt_ PPH_STRING String, + _Inout_ PPH_SETTING Setting + ); + +VOID PhpFreeSettingValue( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_SETTING Setting + ); + +PVOID PhpLookupSetting( + _In_ PPH_STRINGREF Name + ); + +#endif diff --git a/ProcessHacker/include/srvlist.h b/ProcessHacker/include/srvlist.h new file mode 100644 index 0000000..4998232 --- /dev/null +++ b/ProcessHacker/include/srvlist.h @@ -0,0 +1,153 @@ +#ifndef PH_SRVLIST_H +#define PH_SRVLIST_H + +#include + +// Columns + +#define PHSVTLC_NAME 0 +#define PHSVTLC_DISPLAYNAME 1 +#define PHSVTLC_TYPE 2 +#define PHSVTLC_STATUS 3 +#define PHSVTLC_STARTTYPE 4 +#define PHSVTLC_PID 5 + +#define PHSVTLC_BINARYPATH 6 +#define PHSVTLC_ERRORCONTROL 7 +#define PHSVTLC_GROUP 8 +#define PHSVTLC_DESCRIPTION 9 +#define PHSVTLC_KEYMODIFIEDTIME 10 + +#define PHSVTLC_MAXIMUM 11 + +#define PHSN_CONFIG 0x1 +#define PHSN_DESCRIPTION 0x2 +#define PHSN_KEY 0x4 + +// begin_phapppub +typedef struct _PH_SERVICE_NODE +{ + PH_TREENEW_NODE Node; + + PH_SH_STATE ShState; + + PPH_SERVICE_ITEM ServiceItem; +// end_phapppub + + PH_STRINGREF TextCache[PHSVTLC_MAXIMUM]; + + ULONG ValidMask; + + WCHAR StartTypeText[12 + 24 + 1]; + // Config + PPH_STRING BinaryPath; + PPH_STRING LoadOrderGroup; + // Description + PPH_STRING Description; + // Key + LARGE_INTEGER KeyLastWriteTime; + + PPH_STRING TooltipText; + + PPH_STRING KeyModifiedTimeText; +// begin_phapppub +} PH_SERVICE_NODE, *PPH_SERVICE_NODE; +// end_phapppub + +VOID PhServiceTreeListInitialization( + VOID + ); + +VOID PhInitializeServiceTreeList( + _In_ HWND hwnd + ); + +VOID PhLoadSettingsServiceTreeList( + VOID + ); + +VOID PhSaveSettingsServiceTreeList( + VOID + ); + +// begin_phapppub +PHAPPAPI +struct _PH_TN_FILTER_SUPPORT * +NTAPI +PhGetFilterSupportServiceTreeList( + VOID + ); +// end_phapppub + +PPH_SERVICE_NODE PhAddServiceNode( + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ ULONG RunId + ); + +// begin_phapppub +PHAPPAPI +PPH_SERVICE_NODE +NTAPI +PhFindServiceNode( + _In_ PPH_SERVICE_ITEM ServiceItem + ); +// end_phapppub + +VOID PhRemoveServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhUpdateServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ); +// end_phapppub + +VOID PhTickServiceNodes( + VOID + ); + +// begin_phapppub +PHAPPAPI +PPH_SERVICE_ITEM +NTAPI +PhGetSelectedServiceItem( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhGetSelectedServiceItems( + _Out_ PPH_SERVICE_ITEM **Services, + _Out_ PULONG NumberOfServices + ); + +PHAPPAPI +VOID +NTAPI +PhDeselectAllServiceNodes( + VOID + ); + +PHAPPAPI +VOID +NTAPI +PhSelectAndEnsureVisibleServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ); +// end_phapppub + +VOID PhCopyServiceList( + VOID + ); + +VOID PhWriteServiceList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ); + +#endif diff --git a/ProcessHacker/include/srvprv.h b/ProcessHacker/include/srvprv.h new file mode 100644 index 0000000..a01349d --- /dev/null +++ b/ProcessHacker/include/srvprv.h @@ -0,0 +1,96 @@ +#ifndef PH_SRVPRV_H +#define PH_SRVPRV_H + +extern PPH_OBJECT_TYPE PhServiceItemType; + +PHAPPAPI extern PH_CALLBACK PhServiceAddedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhServiceModifiedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhServiceRemovedEvent; // phapppub +PHAPPAPI extern PH_CALLBACK PhServicesUpdatedEvent; // phapppub + +extern BOOLEAN PhEnableServiceNonPoll; + +// begin_phapppub +typedef struct _PH_SERVICE_ITEM +{ + PH_STRINGREF Key; // points to Name + PPH_STRING Name; + PPH_STRING DisplayName; + + // State + ULONG Type; + ULONG State; + ULONG ControlsAccepted; + ULONG Flags; // e.g. SERVICE_RUNS_IN_SYSTEM_PROCESS + HANDLE ProcessId; + + // Config + ULONG StartType; + ULONG ErrorControl; +// end_phapppub + BOOLEAN DelayedStart; + BOOLEAN HasTriggers; + + BOOLEAN PendingProcess; + BOOLEAN NeedsConfigUpdate; + + WCHAR ProcessIdString[PH_INT32_STR_LEN_1]; +// begin_phapppub +} PH_SERVICE_ITEM, *PPH_SERVICE_ITEM; +// end_phapppub + +// begin_phapppub +typedef struct _PH_SERVICE_MODIFIED_DATA +{ + PPH_SERVICE_ITEM Service; + PH_SERVICE_ITEM OldService; +} PH_SERVICE_MODIFIED_DATA, *PPH_SERVICE_MODIFIED_DATA; + +typedef enum _PH_SERVICE_CHANGE +{ + ServiceStarted, + ServiceContinued, + ServicePaused, + ServiceStopped +} PH_SERVICE_CHANGE, *PPH_SERVICE_CHANGE; +// end_phapppub + +BOOLEAN PhServiceProviderInitialization( + VOID + ); + +PPH_SERVICE_ITEM PhCreateServiceItem( + _In_opt_ LPENUM_SERVICE_STATUS_PROCESS Information + ); + +// begin_phapppub +PHAPPAPI +PPH_SERVICE_ITEM +NTAPI +PhReferenceServiceItem( + _In_ PWSTR Name + ); +// end_phapppub + +VOID PhMarkNeedsConfigUpdateServiceItem( + _In_ PPH_SERVICE_ITEM ServiceItem + ); + +// begin_phapppub +PHAPPAPI +PH_SERVICE_CHANGE +NTAPI +PhGetServiceChange( + _In_ PPH_SERVICE_MODIFIED_DATA Data + ); +// end_phapppub + +VOID PhUpdateProcessItemServices( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +VOID PhServiceProviderUpdate( + _In_ PVOID Object + ); + +#endif diff --git a/ProcessHacker/include/sysinfo.h b/ProcessHacker/include/sysinfo.h new file mode 100644 index 0000000..903141e --- /dev/null +++ b/ProcessHacker/include/sysinfo.h @@ -0,0 +1,166 @@ +#ifndef PH_SYSINFO_H +#define PH_SYSINFO_H + +// begin_phapppub +typedef enum _PH_SYSINFO_VIEW_TYPE +{ + SysInfoSummaryView, + SysInfoSectionView +} PH_SYSINFO_VIEW_TYPE; + +typedef VOID (NTAPI *PPH_SYSINFO_COLOR_SETUP_FUNCTION)( + _Out_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ COLORREF Color1, + _In_ COLORREF Color2 + ); + +typedef struct _PH_SYSINFO_PARAMETERS +{ + HWND SysInfoWindowHandle; + HWND ContainerWindowHandle; + + HFONT Font; + HFONT MediumFont; + HFONT LargeFont; + ULONG FontHeight; + ULONG FontAverageWidth; + ULONG MediumFontHeight; + ULONG MediumFontAverageWidth; + COLORREF GraphBackColor; + COLORREF PanelForeColor; + PPH_SYSINFO_COLOR_SETUP_FUNCTION ColorSetupFunction; + + ULONG MinimumGraphHeight; + ULONG SectionViewGraphHeight; + ULONG PanelWidth; +// end_phapppub + + ULONG PanelPadding; + ULONG WindowPadding; + ULONG GraphPadding; + ULONG SmallGraphWidth; + ULONG SmallGraphPadding; + ULONG SeparatorWidth; + ULONG CpuPadding; + ULONG MemoryPadding; +// begin_phapppub +} PH_SYSINFO_PARAMETERS, *PPH_SYSINFO_PARAMETERS; + +typedef enum _PH_SYSINFO_SECTION_MESSAGE +{ + SysInfoCreate, + SysInfoDestroy, + SysInfoTick, + SysInfoViewChanging, // PH_SYSINFO_VIEW_TYPE Parameter1, PPH_SYSINFO_SECTION Parameter2 + SysInfoCreateDialog, // PPH_SYSINFO_CREATE_DIALOG Parameter1 + SysInfoGraphGetDrawInfo, // PPH_GRAPH_DRAW_INFO Parameter1 + SysInfoGraphGetTooltipText, // PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT Parameter1 + SysInfoGraphDrawPanel, // PPH_SYSINFO_DRAW_PANEL Parameter1 + MaxSysInfoMessage +} PH_SYSINFO_SECTION_MESSAGE; + +typedef BOOLEAN (NTAPI *PPH_SYSINFO_SECTION_CALLBACK)( + _In_ struct _PH_SYSINFO_SECTION *Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +typedef struct _PH_SYSINFO_CREATE_DIALOG +{ + BOOLEAN CustomCreate; + + // Parameters for default create + PVOID Instance; + PWSTR Template; + DLGPROC DialogProc; + PVOID Parameter; +} PH_SYSINFO_CREATE_DIALOG, *PPH_SYSINFO_CREATE_DIALOG; + +typedef struct _PH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT +{ + ULONG Index; + PH_STRINGREF Text; +} PH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT, *PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT; + +typedef struct _PH_SYSINFO_DRAW_PANEL +{ + HDC hdc; + RECT Rect; + BOOLEAN CustomDraw; + + // Parameters for default draw + PPH_STRING Title; + PPH_STRING SubTitle; + PPH_STRING SubTitleOverflow; +} PH_SYSINFO_DRAW_PANEL, *PPH_SYSINFO_DRAW_PANEL; +// end_phapppub + +// begin_phapppub +typedef struct _PH_SYSINFO_SECTION +{ + // Public + + // Initialization + PH_STRINGREF Name; + ULONG Flags; + PPH_SYSINFO_SECTION_CALLBACK Callback; + PVOID Context; + PVOID Reserved[3]; + + // State + HWND GraphHandle; + PH_GRAPH_STATE GraphState; + PPH_SYSINFO_PARAMETERS Parameters; + PVOID Reserved2[3]; +// end_phapppub + + // Private + + struct + { + ULONG GraphHot : 1; + ULONG PanelHot : 1; + ULONG HasFocus : 1; + ULONG HideFocus : 1; + ULONG SpareFlags : 28; + }; + HWND DialogHandle; + HWND PanelHandle; + ULONG PanelId; + WNDPROC GraphOldWndProc; + WNDPROC PanelOldWndProc; +// begin_phapppub +} PH_SYSINFO_SECTION, *PPH_SYSINFO_SECTION; +// end_phapppub + +VOID PhSiNotifyChangeSettings( + VOID + ); + +// begin_phapppub +PHAPPAPI +VOID +NTAPI +PhSiSetColorsGraphDrawInfo( + _Out_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ COLORREF Color1, + _In_ COLORREF Color2 + ); + +PHAPPAPI +PPH_STRING +NTAPI +PhSiSizeLabelYFunction( + _In_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ ULONG DataIndex, + _In_ FLOAT Value, + _In_ FLOAT Parameter + ); +// end_phapppub + +VOID PhShowSystemInformationDialog( + _In_opt_ PWSTR SectionName + ); + +#endif \ No newline at end of file diff --git a/ProcessHacker/include/sysinfop.h b/ProcessHacker/include/sysinfop.h new file mode 100644 index 0000000..a319791 --- /dev/null +++ b/ProcessHacker/include/sysinfop.h @@ -0,0 +1,439 @@ +#ifndef PH_SYSINFOP_H +#define PH_SYSINFOP_H + +// Constants + +#define PH_SYSINFO_FADE_ADD 50 +#define PH_SYSINFO_PANEL_PADDING 3 +#define PH_SYSINFO_WINDOW_PADDING 13 +#define PH_SYSINFO_GRAPH_PADDING 9 +#define PH_SYSINFO_SMALL_GRAPH_WIDTH 48 +#define PH_SYSINFO_SMALL_GRAPH_PADDING 5 +#define PH_SYSINFO_SEPARATOR_WIDTH 2 + +#define PH_SYSINFO_CPU_PADDING 5 +#define PH_SYSINFO_MEMORY_PADDING 3 + +#define SI_MSG_SYSINFO_FIRST (WM_APP + 150) +#define SI_MSG_SYSINFO_ACTIVATE (WM_APP + 150) +#define SI_MSG_SYSINFO_UPDATE (WM_APP + 151) +#define SI_MSG_SYSINFO_CHANGE_SETTINGS (WM_APP + 152) +#define SI_MSG_SYSINFO_LAST (WM_APP + 152) + +#define SWP_NO_ACTIVATE_MOVE_SIZE_ZORDER (SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) +#define SWP_SHOWWINDOW_ONLY (SWP_NO_ACTIVATE_MOVE_SIZE_ZORDER | SWP_SHOWWINDOW) +#define SWP_HIDEWINDOW_ONLY (SWP_NO_ACTIVATE_MOVE_SIZE_ZORDER | SWP_HIDEWINDOW) + +// Thread & window + +extern HWND PhSipWindow; + +NTSTATUS PhSipSysInfoThreadStart( + _In_ PVOID Parameter + ); + +INT_PTR CALLBACK PhSipSysInfoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhSipContainerDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Event handlers + +VOID PhSipOnInitDialog( + VOID + ); + +VOID PhSipOnDestroy( + VOID + ); + +VOID PhSipOnNcDestroy( + VOID + ); + +VOID PhSipOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ); + +BOOLEAN PhSipOnSysCommand( + _In_ ULONG Type, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ); + +VOID PhSipOnSize( + VOID + ); + +VOID PhSipOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ); + +VOID PhSipOnThemeChanged( + VOID + ); + +VOID PhSipOnCommand( + _In_ ULONG Id, + _In_ ULONG Code + ); + +BOOLEAN PhSipOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ); + +BOOLEAN PhSipOnDrawItem( + _In_ ULONG_PTR Id, + _In_ DRAWITEMSTRUCT *DrawItemStruct + ); + +VOID PhSipOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ); + +// Framework + +VOID PhSipRegisterDialog( + _In_ HWND DialogWindowHandle + ); + +VOID PhSipUnregisterDialog( + _In_ HWND DialogWindowHandle + ); + +VOID PhSipInitializeParameters( + VOID + ); + +VOID PhSipDeleteParameters( + VOID + ); + +VOID PhSipUpdateColorParameters( + VOID + ); + +PPH_SYSINFO_SECTION PhSipCreateSection( + _In_ PPH_SYSINFO_SECTION Template + ); + +VOID PhSipDestroySection( + _In_ PPH_SYSINFO_SECTION Section + ); + +PPH_SYSINFO_SECTION PhSipFindSection( + _In_ PPH_STRINGREF Name + ); + +PPH_SYSINFO_SECTION PhSipCreateInternalSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_SYSINFO_SECTION_CALLBACK Callback + ); + +VOID PhSipDrawRestoreSummaryPanel( + _In_ HDC hdc, + _In_ PRECT Rect + ); + +VOID PhSipDrawSeparator( + _In_ HDC hdc, + _In_ PRECT Rect + ); + +VOID PhSipDrawPanel( + _In_ PPH_SYSINFO_SECTION Section, + _In_ HDC hdc, + _In_ PRECT Rect + ); + +VOID PhSipDefaultDrawPanel( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PPH_SYSINFO_DRAW_PANEL DrawPanel + ); + +VOID PhSipLayoutSummaryView( + VOID + ); + +VOID PhSipLayoutSectionView( + VOID + ); + +VOID PhSipEnterSectionView( + _In_ PPH_SYSINFO_SECTION NewSection + ); + +VOID PhSipEnterSectionViewInner( + _In_ PPH_SYSINFO_SECTION Section, + _In_ BOOLEAN FromSummaryView, + _Inout_ HDWP *DeferHandle, + _Inout_ HDWP *ContainerDeferHandle + ); + +VOID PhSipRestoreSummaryView( + VOID + ); + +VOID PhSipCreateSectionDialog( + _In_ PPH_SYSINFO_SECTION Section + ); + +LRESULT CALLBACK PhSipGraphHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +LRESULT CALLBACK PhSipPanelHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Misc. + +VOID PhSipUpdateThemeData( + VOID + ); + +VOID PhSipSetAlwaysOnTop( + VOID + ); + +VOID PhSipSaveWindowState( + VOID + ); + +VOID NTAPI PhSipSysInfoUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +PPH_STRING PhSipFormatSizeWithPrecision( + _In_ ULONG64 Size, + _In_ USHORT Precision + ); + +// CPU section + +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8 +{ + ULONG Hits; + UCHAR PercentFrequency; +} SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8, *PSYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8; + +BOOLEAN PhSipCpuSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +VOID PhSipInitializeCpuDialog( + VOID + ); + +VOID PhSipUninitializeCpuDialog( + VOID + ); + +VOID PhSipTickCpuDialog( + VOID + ); + +INT_PTR CALLBACK PhSipCpuDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhSipCpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhSipCreateCpuGraphs( + VOID + ); + +VOID PhSipLayoutCpuGraphs( + VOID + ); + +VOID PhSipSetOneGraphPerCpu( + VOID + ); + +VOID PhSipNotifyCpuGraph( + _In_ ULONG Index, + _In_ NMHDR *Header + ); + +VOID PhSipUpdateCpuGraphs( + VOID + ); + +VOID PhSipUpdateCpuPanel( + VOID + ); + +PPH_PROCESS_RECORD PhSipReferenceMaxCpuRecord( + _In_ LONG Index + ); + +PPH_STRING PhSipGetMaxCpuString( + _In_ LONG Index + ); + +VOID PhSipGetCpuBrandString( + _Out_writes_(49) PWSTR BrandString + ); + +BOOLEAN PhSipGetCpuFrequencyFromDistribution( + _Out_ DOUBLE *Fraction + ); + +NTSTATUS PhSipQueryProcessorPerformanceDistribution( + _Out_ PVOID *Buffer + ); + +// Memory section + +BOOLEAN PhSipMemorySectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +VOID PhSipInitializeMemoryDialog( + VOID + ); + +VOID PhSipUninitializeMemoryDialog( + VOID + ); + +VOID PhSipTickMemoryDialog( + VOID + ); + +INT_PTR CALLBACK PhSipMemoryDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhSipMemoryPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhSipLayoutMemoryGraphs( + VOID + ); + +VOID PhSipNotifyCommitGraph( + _In_ NMHDR *Header + ); + +VOID PhSipNotifyPhysicalGraph( + _In_ NMHDR *Header + ); + +VOID PhSipUpdateMemoryGraphs( + VOID + ); + +VOID PhSipUpdateMemoryPanel( + VOID + ); + +NTSTATUS PhSipLoadMmAddresses( + _In_ PVOID Parameter + ); + +VOID PhSipGetPoolLimits( + _Out_ PSIZE_T Paged, + _Out_ PSIZE_T NonPaged + ); + +// I/O section + +BOOLEAN PhSipIoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +VOID PhSipInitializeIoDialog( + VOID + ); + +VOID PhSipUninitializeIoDialog( + VOID + ); + +VOID PhSipTickIoDialog( + VOID + ); + +INT_PTR CALLBACK PhSipIoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhSipIoPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhSipNotifyIoGraph( + _In_ NMHDR *Header + ); + +VOID PhSipUpdateIoGraph( + VOID + ); + +VOID PhSipUpdateIoPanel( + VOID + ); + +PPH_PROCESS_RECORD PhSipReferenceMaxIoRecord( + _In_ LONG Index + ); + +PPH_STRING PhSipGetMaxIoString( + _In_ LONG Index + ); + +#endif diff --git a/ProcessHacker/include/thrdlist.h b/ProcessHacker/include/thrdlist.h new file mode 100644 index 0000000..f1132eb --- /dev/null +++ b/ProcessHacker/include/thrdlist.h @@ -0,0 +1,115 @@ +#ifndef PH_THRDLIST_H +#define PH_THRDLIST_H + +#include +#include + +// Columns + +#define PHTHTLC_TID 0 +#define PHTHTLC_CPU 1 +#define PHTHTLC_CYCLESDELTA 2 +#define PHTHTLC_STARTADDRESS 3 +#define PHTHTLC_PRIORITY 4 +#define PHTHTLC_SERVICE 5 + +#define PHTHTLC_MAXIMUM 6 + +// begin_phapppub +typedef struct _PH_THREAD_NODE +{ + PH_TREENEW_NODE Node; + + PH_SH_STATE ShState; + + HANDLE ThreadId; + PPH_THREAD_ITEM ThreadItem; +// end_phapppub + + PH_STRINGREF TextCache[PHTHTLC_MAXIMUM]; + + ULONG ValidMask; + + WCHAR CpuUsageText[PH_INT32_STR_LEN_1]; + PPH_STRING CyclesDeltaText; // used for Context Switches Delta as well + PPH_STRING StartAddressText; + PPH_STRING PriorityText; +// begin_phapppub +} PH_THREAD_NODE, *PPH_THREAD_NODE; +// end_phapppub + +typedef struct _PH_THREAD_LIST_CONTEXT +{ + HWND ParentWindowHandle; + HWND TreeNewHandle; + ULONG TreeNewSortColumn; + PH_SORT_ORDER TreeNewSortOrder; + PH_CM_MANAGER Cm; + BOOLEAN UseCycleTime; + BOOLEAN HasServices; + + PPH_HASHTABLE NodeHashtable; + PPH_LIST NodeList; + + BOOLEAN EnableStateHighlighting; + PPH_POINTER_LIST NodeStateList; +} PH_THREAD_LIST_CONTEXT, *PPH_THREAD_LIST_CONTEXT; + +VOID PhInitializeThreadList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_THREAD_LIST_CONTEXT Context + ); + +VOID PhDeleteThreadList( + _In_ PPH_THREAD_LIST_CONTEXT Context + ); + +VOID PhLoadSettingsThreadList( + _Inout_ PPH_THREAD_LIST_CONTEXT Context + ); + +VOID PhSaveSettingsThreadList( + _Inout_ PPH_THREAD_LIST_CONTEXT Context + ); + +PPH_THREAD_NODE PhAddThreadNode( + _Inout_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_ITEM ThreadItem, + _In_ BOOLEAN FirstRun + ); + +PPH_THREAD_NODE PhFindThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ HANDLE ThreadId + ); + +VOID PhRemoveThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_NODE ThreadNode + ); + +VOID PhUpdateThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_NODE ThreadNode + ); + +VOID PhTickThreadNodes( + _In_ PPH_THREAD_LIST_CONTEXT Context + ); + +PPH_THREAD_ITEM PhGetSelectedThreadItem( + _In_ PPH_THREAD_LIST_CONTEXT Context + ); + +VOID PhGetSelectedThreadItems( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _Out_ PPH_THREAD_ITEM **Threads, + _Out_ PULONG NumberOfThreads + ); + +VOID PhDeselectAllThreadNodes( + _In_ PPH_THREAD_LIST_CONTEXT Context + ); + +#endif diff --git a/ProcessHacker/include/thrdprv.h b/ProcessHacker/include/thrdprv.h new file mode 100644 index 0000000..ef6fe42 --- /dev/null +++ b/ProcessHacker/include/thrdprv.h @@ -0,0 +1,115 @@ +#ifndef PH_THRDPRV_H +#define PH_THRDPRV_H + +extern PPH_OBJECT_TYPE PhThreadProviderType; +extern PPH_OBJECT_TYPE PhThreadItemType; + +// begin_phapppub +typedef struct _PH_THREAD_ITEM +{ + HANDLE ThreadId; + + LARGE_INTEGER CreateTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + + FLOAT CpuUsage; + PH_UINT64_DELTA CpuKernelDelta; + PH_UINT64_DELTA CpuUserDelta; + + PH_UINT32_DELTA ContextSwitchesDelta; + PH_UINT64_DELTA CyclesDelta; + LONG Priority; + LONG BasePriority; + ULONG64 StartAddress; + PPH_STRING StartAddressString; + PPH_STRING StartAddressFileName; + enum _PH_SYMBOL_RESOLVE_LEVEL StartAddressResolveLevel; + KTHREAD_STATE State; + KWAIT_REASON WaitReason; + LONG BasePriorityIncrement; + PPH_STRING ServiceName; + + HANDLE ThreadHandle; + + BOOLEAN IsGuiThread; + BOOLEAN JustResolved; + + WCHAR ThreadIdString[PH_INT32_STR_LEN_1]; +} PH_THREAD_ITEM, *PPH_THREAD_ITEM; + +typedef enum _PH_KNOWN_PROCESS_TYPE PH_KNOWN_PROCESS_TYPE; + +typedef struct _PH_THREAD_PROVIDER +{ + PPH_HASHTABLE ThreadHashtable; + PH_FAST_LOCK ThreadHashtableLock; + PH_CALLBACK ThreadAddedEvent; + PH_CALLBACK ThreadModifiedEvent; + PH_CALLBACK ThreadRemovedEvent; + PH_CALLBACK UpdatedEvent; + PH_CALLBACK LoadingStateChangedEvent; + + HANDLE ProcessId; + HANDLE ProcessHandle; + BOOLEAN HasServices; + BOOLEAN HasServicesKnown; + BOOLEAN Terminating; + struct _PH_SYMBOL_PROVIDER *SymbolProvider; + + SLIST_HEADER QueryListHead; + PH_QUEUED_LOCK LoadSymbolsLock; + LONG SymbolsLoading; + ULONG64 RunId; + ULONG64 SymbolsLoadedRunId; +} PH_THREAD_PROVIDER, *PPH_THREAD_PROVIDER; +// end_phapppub + +BOOLEAN PhThreadProviderInitialization( + VOID + ); + +PPH_THREAD_PROVIDER PhCreateThreadProvider( + _In_ HANDLE ProcessId + ); + +VOID PhRegisterThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _Out_ PPH_CALLBACK_REGISTRATION CallbackRegistration + ); + +VOID PhUnregisterThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PPH_CALLBACK_REGISTRATION CallbackRegistration + ); + +VOID PhSetTerminatingThreadProvider( + _Inout_ PPH_THREAD_PROVIDER ThreadProvider + ); + +VOID PhLoadSymbolsThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ); + +PPH_THREAD_ITEM PhCreateThreadItem( + _In_ HANDLE ThreadId + ); + +PPH_THREAD_ITEM PhReferenceThreadItem( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ HANDLE ThreadId + ); + +VOID PhDereferenceAllThreadItems( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ); + +PPH_STRING PhGetBasePriorityIncrementString( + _In_ LONG Increment + ); + +VOID PhThreadProviderInitialUpdate( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ); + +#endif diff --git a/ProcessHacker/infodlg.c b/ProcessHacker/infodlg.c new file mode 100644 index 0000000..147cd6a --- /dev/null +++ b/ProcessHacker/infodlg.c @@ -0,0 +1,212 @@ +/* + * Process Hacker - + * information dialog + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +typedef struct _INFORMATION_CONTEXT +{ + PWSTR String; + ULONG Flags; +} INFORMATION_CONTEXT, *PINFORMATION_CONTEXT; + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +static INT_PTR CALLBACK PhpInformationDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PINFORMATION_CONTEXT context = (PINFORMATION_CONTEXT)lParam; + PPH_LAYOUT_MANAGER layoutManager; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + SetDlgItemText(hwndDlg, IDC_TEXT, context->String); + + layoutManager = PhAllocate(sizeof(PH_LAYOUT_MANAGER)); + PhInitializeLayoutManager(layoutManager, hwndDlg); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_TEXT), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_COPY), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_SAVE), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 200; + rect.bottom = 140; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + SetProp(hwndDlg, L"LayoutManager", (HANDLE)layoutManager); + SetProp(hwndDlg, L"String", (HANDLE)context->String); + } + break; + case WM_DESTROY: + { + PPH_LAYOUT_MANAGER layoutManager; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhDeleteLayoutManager(layoutManager); + PhFree(layoutManager); + RemoveProp(hwndDlg, L"String"); + RemoveProp(hwndDlg, L"LayoutManager"); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_COPY: + { + HWND editControl; + LONG selStart; + LONG selEnd; + PWSTR buffer; + PH_STRINGREF string; + + editControl = GetDlgItem(hwndDlg, IDC_TEXT); + SendMessage(editControl, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); + buffer = (PWSTR)GetProp(hwndDlg, L"String"); + + if (selStart == selEnd) + { + // Select and copy the entire string. + PhInitializeStringRefLongHint(&string, buffer); + Edit_SetSel(editControl, 0, -1); + } + else + { + string.Buffer = buffer + selStart; + string.Length = (selEnd - selStart) * 2; + } + + PhSetClipboardString(hwndDlg, &string); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)editControl, TRUE); + } + break; + case IDC_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Text files (*.txt)", L"*.txt" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, L"Information.txt"); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + PH_STRINGREF string; + + PhWriteStringAsUtf8FileStream(fileStream, &PhUnicodeByteOrderMark); + PhInitializeStringRef(&string, (PWSTR)GetProp(hwndDlg, L"String")); + PhWriteStringAsUtf8FileStream(fileStream, &string); + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + case WM_SIZE: + { + PPH_LAYOUT_MANAGER layoutManager; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhLayoutManagerLayout(layoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + } + + return FALSE; +} + +VOID PhShowInformationDialog( + _In_ HWND ParentWindowHandle, + _In_ PWSTR String, + _Reserved_ ULONG Flags + ) +{ + INFORMATION_CONTEXT context; + + context.String = String; + context.Flags = Flags; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_INFORMATION), + ParentWindowHandle, + PhpInformationDlgProc, + (LPARAM)&context + ); +} diff --git a/ProcessHacker/itemtips.c b/ProcessHacker/itemtips.c new file mode 100644 index 0000000..99cc221 --- /dev/null +++ b/ProcessHacker/itemtips.c @@ -0,0 +1,701 @@ +/* + * Process Hacker - + * item tooltips + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#define CINTERFACE +#define COBJMACROS +#include + +VOID PhpFillUmdfDrivers( + _In_ PPH_PROCESS_ITEM Process, + _Inout_ PPH_STRING_BUILDER Drivers + ); + +VOID PhpFillRunningTasks( + _In_ PPH_PROCESS_ITEM Process, + _Inout_ PPH_STRING_BUILDER Tasks + ); + +static PH_STRINGREF StandardIndent = PH_STRINGREF_INIT(L" "); + +VOID PhpAppendStringWithLineBreaks( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PPH_STRINGREF String, + _In_ ULONG CharactersPerLine, + _In_opt_ PPH_STRINGREF IndentAfterFirstLine + ) +{ + PH_STRINGREF line; + SIZE_T bytesPerLine; + BOOLEAN afterFirstLine; + SIZE_T bytesToAppend; + + line = *String; + bytesPerLine = CharactersPerLine * sizeof(WCHAR); + afterFirstLine = FALSE; + + while (line.Length != 0) + { + bytesToAppend = line.Length; + + if (bytesToAppend > bytesPerLine) + bytesToAppend = bytesPerLine; + + if (afterFirstLine) + { + PhAppendCharStringBuilder(StringBuilder, '\n'); + + if (IndentAfterFirstLine) + PhAppendStringBuilder(StringBuilder, IndentAfterFirstLine); + } + + PhAppendStringBuilderEx(StringBuilder, line.Buffer, bytesToAppend); + afterFirstLine = TRUE; + PhSkipStringRef(&line, bytesToAppend); + } +} + +static int __cdecl ServiceForTooltipCompare( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)elem1; + PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)elem2; + + return PhCompareString(serviceItem1->Name, serviceItem2->Name, TRUE); +} + +PPH_STRING PhGetProcessTooltipText( + _In_ PPH_PROCESS_ITEM Process, + _Out_opt_ PULONG ValidToTickCount + ) +{ + PH_STRING_BUILDER stringBuilder; + ULONG validForMs = 60 * 60 * 1000; // 1 hour + PPH_STRING tempString; + PH_KNOWN_PROCESS_TYPE knownProcessType = UnknownProcessType; + + PhInitializeStringBuilder(&stringBuilder, 200); + + // Command line + + if (Process->CommandLine) + { + tempString = PhEllipsisString(Process->CommandLine, 100 * 10); + + // This is necessary because the tooltip control seems to use some kind of O(n^9999) word-wrapping + // algorithm. + PhpAppendStringWithLineBreaks(&stringBuilder, &tempString->sr, 100, NULL); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + + PhDereferenceObject(tempString); + } + + // File information + + tempString = PhFormatImageVersionInfo( + Process->FileName, + &Process->VersionInfo, + &StandardIndent, + 0 + ); + + if (!PhIsNullOrEmptyString(tempString)) + { + PhAppendStringBuilder2(&stringBuilder, L"File:\n"); + PhAppendStringBuilder(&stringBuilder, &tempString->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + if (tempString) + PhDereferenceObject(tempString); + + // Known command line information + + if (Process->QueryHandle) + PhGetProcessKnownType(Process->QueryHandle, &knownProcessType); + + if (Process->CommandLine && Process->QueryHandle) + { + PH_KNOWN_PROCESS_COMMAND_LINE knownCommandLine; + + if (knownProcessType != UnknownProcessType && PhaGetProcessKnownCommandLine( + Process->CommandLine, + knownProcessType, + &knownCommandLine + )) + { + switch (knownProcessType & KnownProcessTypeMask) + { + case ServiceHostProcessType: + PhAppendStringBuilder2(&stringBuilder, L"Service group name:\n "); + PhAppendStringBuilder(&stringBuilder, &knownCommandLine.ServiceHost.GroupName->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + break; + case RunDllAsAppProcessType: + { + PH_IMAGE_VERSION_INFO versionInfo; + + if (PhInitializeImageVersionInfo( + &versionInfo, + knownCommandLine.RunDllAsApp.FileName->Buffer + )) + { + tempString = PhFormatImageVersionInfo( + knownCommandLine.RunDllAsApp.FileName, + &versionInfo, + &StandardIndent, + 0 + ); + + if (!PhIsNullOrEmptyString(tempString)) + { + PhAppendStringBuilder2(&stringBuilder, L"Run DLL target file:\n"); + PhAppendStringBuilder(&stringBuilder, &tempString->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + if (tempString) + PhDereferenceObject(tempString); + + PhDeleteImageVersionInfo(&versionInfo); + } + } + break; + case ComSurrogateProcessType: + { + PH_IMAGE_VERSION_INFO versionInfo; + PPH_STRING guidString; + + PhAppendStringBuilder2(&stringBuilder, L"COM target:\n"); + + if (knownCommandLine.ComSurrogate.Name) + { + PhAppendStringBuilder(&stringBuilder, &StandardIndent); + PhAppendStringBuilder(&stringBuilder, &knownCommandLine.ComSurrogate.Name->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + if (guidString = PhFormatGuid(&knownCommandLine.ComSurrogate.Guid)) + { + PhAppendStringBuilder(&stringBuilder, &StandardIndent); + PhAppendStringBuilder(&stringBuilder, &guidString->sr); + PhDereferenceObject(guidString); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + if (knownCommandLine.ComSurrogate.FileName && PhInitializeImageVersionInfo( + &versionInfo, + knownCommandLine.ComSurrogate.FileName->Buffer + )) + { + tempString = PhFormatImageVersionInfo( + knownCommandLine.ComSurrogate.FileName, + &versionInfo, + &StandardIndent, + 0 + ); + + if (!PhIsNullOrEmptyString(tempString)) + { + PhAppendStringBuilder2(&stringBuilder, L"COM target file:\n"); + PhAppendStringBuilder(&stringBuilder, &tempString->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + if (tempString) + PhDereferenceObject(tempString); + + PhDeleteImageVersionInfo(&versionInfo); + } + } + break; + } + } + } + + // Services + + if (Process->ServiceList && Process->ServiceList->Count != 0) + { + ULONG enumerationKey = 0; + PPH_SERVICE_ITEM serviceItem; + PPH_LIST serviceList; + ULONG i; + + // Copy the service list into our own list so we can sort it. + + serviceList = PhCreateList(Process->ServiceList->Count); + + PhAcquireQueuedLockShared(&Process->ServiceListLock); + + while (PhEnumPointerList( + Process->ServiceList, + &enumerationKey, + &serviceItem + )) + { + PhReferenceObject(serviceItem); + PhAddItemList(serviceList, serviceItem); + } + + PhReleaseQueuedLockShared(&Process->ServiceListLock); + + qsort(serviceList->Items, serviceList->Count, sizeof(PPH_SERVICE_ITEM), ServiceForTooltipCompare); + + PhAppendStringBuilder2(&stringBuilder, L"Services:\n"); + + // Add the services. + for (i = 0; i < serviceList->Count; i++) + { + serviceItem = serviceList->Items[i]; + + PhAppendStringBuilder(&stringBuilder, &StandardIndent); + PhAppendStringBuilder(&stringBuilder, &serviceItem->Name->sr); + PhAppendStringBuilder2(&stringBuilder, L" ("); + PhAppendStringBuilder(&stringBuilder, &serviceItem->DisplayName->sr); + PhAppendStringBuilder2(&stringBuilder, L")\n"); + } + + PhDereferenceObjects(serviceList->Items, serviceList->Count); + PhDereferenceObject(serviceList); + } + + // Tasks, Drivers + switch (knownProcessType & KnownProcessTypeMask) + { + case TaskHostProcessType: + { + PH_STRING_BUILDER tasks; + + PhInitializeStringBuilder(&tasks, 40); + + PhpFillRunningTasks(Process, &tasks); + + if (tasks.String->Length != 0) + { + PhAppendStringBuilder2(&stringBuilder, L"Tasks:\n"); + PhAppendStringBuilder(&stringBuilder, &tasks.String->sr); + } + + PhDeleteStringBuilder(&tasks); + } + break; + case UmdfHostProcessType: + { + PH_STRING_BUILDER drivers; + + PhInitializeStringBuilder(&drivers, 40); + + PhpFillUmdfDrivers(Process, &drivers); + + if (drivers.String->Length != 0) + { + PhAppendStringBuilder2(&stringBuilder, L"Drivers:\n"); + PhAppendStringBuilder(&stringBuilder, &drivers.String->sr); + } + + PhDeleteStringBuilder(&drivers); + + validForMs = 10 * 1000; // 10 seconds + } + break; + } + + // Plugin + if (PhPluginsEnabled) + { + PH_PLUGIN_GET_TOOLTIP_TEXT getTooltipText; + + getTooltipText.Parameter = Process; + getTooltipText.StringBuilder = &stringBuilder; + getTooltipText.ValidForMs = validForMs; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackGetProcessTooltipText), &getTooltipText); + validForMs = getTooltipText.ValidForMs; + } + + // Notes + + { + PH_STRING_BUILDER notes; + + PhInitializeStringBuilder(¬es, 40); + + if (Process->FileName) + { + if (Process->VerifyResult == VrTrusted) + { + if (!PhIsNullOrEmptyString(Process->VerifySignerName)) + PhAppendFormatStringBuilder(¬es, L" Signer: %s\n", Process->VerifySignerName->Buffer); + else + PhAppendStringBuilder2(¬es, L" Signed.\n"); + } + else if (Process->VerifyResult == VrUnknown) + { + // Nothing + } + else if (Process->VerifyResult != VrNoSignature) + { + PhAppendStringBuilder2(¬es, L" Signature invalid.\n"); + } + } + + if (Process->IsPacked) + { + PhAppendFormatStringBuilder( + ¬es, + L" Image is probably packed (%u imports over %u modules).\n", + Process->ImportFunctions, + Process->ImportModules + ); + } + + if ((ULONG_PTR)Process->ConsoleHostProcessId & ~3) + { + CLIENT_ID clientId; + PWSTR description = L"Console host"; + PPH_STRING clientIdString; + + clientId.UniqueProcess = (HANDLE)((ULONG_PTR)Process->ConsoleHostProcessId & ~3); + clientId.UniqueThread = NULL; + + if ((ULONG_PTR)Process->ConsoleHostProcessId & 2) + description = L"Console application"; + + clientIdString = PhGetClientIdName(&clientId); + PhAppendFormatStringBuilder(¬es, L" %s: %s\n", description, clientIdString->Buffer); + PhDereferenceObject(clientIdString); + } + + if (Process->PackageFullName) + { + PhAppendFormatStringBuilder(¬es, L" Package name: %s\n", Process->PackageFullName->Buffer); + } + + if (Process->IsDotNet) + PhAppendStringBuilder2(¬es, L" Process is managed (.NET).\n"); + if (Process->IsElevated) + PhAppendStringBuilder2(¬es, L" Process is elevated.\n"); + if (Process->IsImmersive) + PhAppendStringBuilder2(¬es, L" Process is a Modern UI app.\n"); + if (Process->IsInJob) + PhAppendStringBuilder2(¬es, L" Process is in a job.\n"); + if (Process->IsWow64) + PhAppendStringBuilder2(¬es, L" Process is 32-bit (WOW64).\n"); + + if (notes.String->Length != 0) + { + PhAppendStringBuilder2(&stringBuilder, L"Notes:\n"); + PhAppendStringBuilder(&stringBuilder, ¬es.String->sr); + } + + PhDeleteStringBuilder(¬es); + } + + if (ValidToTickCount) + *ValidToTickCount = GetTickCount() + validForMs; + + // Remove the trailing newline. + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} + +VOID PhpFillUmdfDrivers( + _In_ PPH_PROCESS_ITEM Process, + _Inout_ PPH_STRING_BUILDER Drivers + ) +{ + static PH_STRINGREF activeDevices = PH_STRINGREF_INIT(L"ACTIVE_DEVICES"); + static PH_STRINGREF currentControlSetEnum = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Enum\\"); + + HANDLE processHandle; + ULONG flags = 0; + PVOID environment; + ULONG environmentLength; + ULONG enumerationKey; + PH_ENVIRONMENT_VARIABLE variable; + + if (!NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + Process->ProcessId + ))) + return; + +#ifdef _WIN64 + // Just in case. + if (Process->IsWow64) + flags |= PH_GET_PROCESS_ENVIRONMENT_WOW64; +#endif + + if (NT_SUCCESS(PhGetProcessEnvironment( + processHandle, + flags, + &environment, + &environmentLength + ))) + { + enumerationKey = 0; + + while (PhEnumProcessEnvironmentVariables(environment, environmentLength, &enumerationKey, &variable)) + { + PH_STRINGREF part; + PH_STRINGREF remainingPart; + + if (!PhEqualStringRef(&variable.Name, &activeDevices, TRUE)) + continue; + + remainingPart = variable.Value; + + while (remainingPart.Length != 0) + { + PhSplitStringRefAtChar(&remainingPart, ';', &part, &remainingPart); + + if (part.Length != 0) + { + HANDLE driverKeyHandle; + PPH_STRING driverKeyPath; + + driverKeyPath = PhConcatStringRef2(¤tControlSetEnum, &part); + + if (NT_SUCCESS(PhOpenKey( + &driverKeyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &driverKeyPath->sr, + 0 + ))) + { + PPH_STRING deviceDesc; + PH_STRINGREF deviceName; + PPH_STRING hardwareId; + + if (deviceDesc = PhQueryRegistryString(driverKeyHandle, L"DeviceDesc")) + { + PH_STRINGREF firstPart; + PH_STRINGREF secondPart; + + if (PhSplitStringRefAtLastChar(&deviceDesc->sr, ';', &firstPart, &secondPart)) + deviceName = secondPart; + else + deviceName = deviceDesc->sr; + } + else + { + PhInitializeStringRef(&deviceName, L"Unknown Device"); + } + + hardwareId = PhQueryRegistryString(driverKeyHandle, L"HardwareID"); + + PhAppendStringBuilder(Drivers, &StandardIndent); + PhAppendStringBuilder(Drivers, &deviceName); + + if (hardwareId) + { + PhTrimToNullTerminatorString(hardwareId); + + if (hardwareId->Length != 0) + { + PhAppendStringBuilder2(Drivers, L" ("); + PhAppendStringBuilder(Drivers, &hardwareId->sr); + PhAppendCharStringBuilder(Drivers, ')'); + } + } + + PhAppendCharStringBuilder(Drivers, '\n'); + + PhClearReference(&hardwareId); + PhClearReference(&deviceDesc); + NtClose(driverKeyHandle); + } + + PhDereferenceObject(driverKeyPath); + } + } + + break; + } + + PhFreePage(environment); + } + + NtClose(processHandle); +} + +VOID PhpFillRunningTasks( + _In_ PPH_PROCESS_ITEM Process, + _Inout_ PPH_STRING_BUILDER Tasks + ) +{ + static CLSID CLSID_TaskScheduler_I = { 0x0f87369f, 0xa4e5, 0x4cfc, { 0xbd, 0x3e, 0x73, 0xe6, 0x15, 0x45, 0x72, 0xdd } }; + static IID IID_ITaskService_I = { 0x2faba4c7, 0x4da9, 0x4013, { 0x96, 0x97, 0x20, 0xcc, 0x3f, 0xd4, 0x0f, 0x85 } }; + + ITaskService *taskService; + + if (SUCCEEDED(CoCreateInstance( + &CLSID_TaskScheduler_I, + NULL, + CLSCTX_INPROC_SERVER, + &IID_ITaskService_I, + &taskService + ))) + { + VARIANT empty = { 0 }; + + if (SUCCEEDED(ITaskService_Connect(taskService, empty, empty, empty, empty))) + { + IRunningTaskCollection *runningTasks; + + if (SUCCEEDED(ITaskService_GetRunningTasks( + taskService, + TASK_ENUM_HIDDEN, + &runningTasks + ))) + { + LONG count; + LONG i; + VARIANT index; + + index.vt = VT_INT; + + if (SUCCEEDED(IRunningTaskCollection_get_Count(runningTasks, &count))) + { + for (i = 1; i <= count; i++) // collections are 1-based + { + IRunningTask *runningTask; + + index.lVal = i; + + if (SUCCEEDED(IRunningTaskCollection_get_Item(runningTasks, index, &runningTask))) + { + ULONG pid; + BSTR action = NULL; + BSTR path = NULL; + + if ( + SUCCEEDED(IRunningTask_get_EnginePID(runningTask, &pid)) && + pid == HandleToUlong(Process->ProcessId) + ) + { + IRunningTask_get_CurrentAction(runningTask, &action); + IRunningTask_get_Path(runningTask, &path); + + PhAppendStringBuilder(Tasks, &StandardIndent); + PhAppendStringBuilder2(Tasks, action ? action : L"Unknown action"); + PhAppendStringBuilder2(Tasks, L" ("); + PhAppendStringBuilder2(Tasks, path ? path : L"Unknown path"); + PhAppendStringBuilder2(Tasks, L")\n"); + + if (action) + SysFreeString(action); + if (path) + SysFreeString(path); + } + + IRunningTask_Release(runningTask); + } + } + } + + IRunningTaskCollection_Release(runningTasks); + } + } + + ITaskService_Release(taskService); + } +} + +PPH_STRING PhGetServiceTooltipText( + _In_ PPH_SERVICE_ITEM Service + ) +{ + PH_STRING_BUILDER stringBuilder; + SC_HANDLE serviceHandle; + + PhInitializeStringBuilder(&stringBuilder, 200); + + if (serviceHandle = PhOpenService(Service->Name->Buffer, SERVICE_QUERY_CONFIG)) + { + PPH_STRING fileName; + PPH_STRING description; + + // File information + + if (fileName = PhGetServiceRelevantFileName(&Service->Name->sr, serviceHandle)) + { + PH_IMAGE_VERSION_INFO versionInfo; + PPH_STRING versionInfoText; + + if (PhInitializeImageVersionInfo( + &versionInfo, + fileName->Buffer + )) + { + versionInfoText = PhFormatImageVersionInfo( + fileName, + &versionInfo, + &StandardIndent, + 0 + ); + + if (!PhIsNullOrEmptyString(versionInfoText)) + { + PhAppendStringBuilder2(&stringBuilder, L"File:\n"); + PhAppendStringBuilder(&stringBuilder, &versionInfoText->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + PhClearReference(&versionInfoText); + PhDeleteImageVersionInfo(&versionInfo); + } + + PhDereferenceObject(fileName); + } + + // Description + + if (description = PhGetServiceDescription(serviceHandle)) + { + PhAppendStringBuilder2(&stringBuilder, L"Description:\n "); + PhAppendStringBuilder(&stringBuilder, &description->sr); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + PhDereferenceObject(description); + } + + CloseServiceHandle(serviceHandle); + } + + // Remove the trailing newline. + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} diff --git a/ProcessHacker/jobprp.c b/ProcessHacker/jobprp.c new file mode 100644 index 0000000..944c506 --- /dev/null +++ b/ProcessHacker/jobprp.c @@ -0,0 +1,651 @@ +/* + * Process Hacker - + * job properties + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +typedef struct _JOB_PAGE_CONTEXT +{ + PPH_OPEN_OBJECT OpenObject; + PVOID Context; + DLGPROC HookProc; +} JOB_PAGE_CONTEXT, *PJOB_PAGE_CONTEXT; + +INT CALLBACK PhpJobPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +INT_PTR CALLBACK PhpJobPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhpShowJobAdvancedProperties( + _In_ HWND ParentWindowHandle, + _In_ PJOB_PAGE_CONTEXT Context + ); + +INT_PTR CALLBACK PhpJobStatisticsPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowJobProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ PWSTR Title + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + HPROPSHEETPAGE pages[1]; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = Title ? Title : L"Job"; + propSheetHeader.nPages = 1; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + pages[0] = PhCreateJobPage(OpenObject, Context, NULL); + + PhModalPropertySheet(&propSheetHeader); +} + +HPROPSHEETPAGE PhCreateJobPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ DLGPROC HookProc + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + PROPSHEETPAGE propSheetPage; + PJOB_PAGE_CONTEXT jobPageContext; + + jobPageContext = PhCreateAlloc(sizeof(JOB_PAGE_CONTEXT)); + memset(jobPageContext, 0, sizeof(JOB_PAGE_CONTEXT)); + jobPageContext->OpenObject = OpenObject; + jobPageContext->Context = Context; + jobPageContext->HookProc = HookProc; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USECALLBACK; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OBJJOB); + propSheetPage.pfnDlgProc = PhpJobPageProc; + propSheetPage.lParam = (LPARAM)jobPageContext; + propSheetPage.pfnCallback = PhpJobPropPageProc; + + propSheetPageHandle = CreatePropertySheetPage(&propSheetPage); + // CreatePropertySheetPage would have sent PSPCB_ADDREF (below), + // which would have added a reference. + PhDereferenceObject(jobPageContext); + + return propSheetPageHandle; +} + +INT CALLBACK PhpJobPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PJOB_PAGE_CONTEXT jobPageContext; + + jobPageContext = (PJOB_PAGE_CONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + { + PhReferenceObject(jobPageContext); + } + else if (uMsg == PSPCB_RELEASE) + { + PhDereferenceObject(jobPageContext); + } + + return 1; +} + +FORCEINLINE PJOB_PAGE_CONTEXT PhpJobPageHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return (PJOB_PAGE_CONTEXT)PhpGenericPropertyPageHeader( + hwndDlg, uMsg, wParam, lParam, L"JobPageContext"); +} + +static VOID PhpAddLimit( + _In_ HWND Handle, + _In_ PWSTR Name, + _In_ PWSTR Value + ) +{ + INT lvItemIndex; + + lvItemIndex = PhAddListViewItem(Handle, MAXINT, Name, NULL); + PhSetListViewSubItem(Handle, lvItemIndex, 1, Value); +} + +static VOID PhpAddJobProcesses( + _In_ HWND hwndDlg, + _In_ HANDLE JobHandle + ) +{ + PJOBOBJECT_BASIC_PROCESS_ID_LIST processIdList; + HWND processesLv; + + processesLv = GetDlgItem(hwndDlg, IDC_PROCESSES); + + if (NT_SUCCESS(PhGetJobProcessIdList(JobHandle, &processIdList))) + { + ULONG i; + CLIENT_ID clientId; + PPH_STRING name; + + clientId.UniqueThread = NULL; + + for (i = 0; i < processIdList->NumberOfProcessIdsInList; i++) + { + clientId.UniqueProcess = (HANDLE)processIdList->ProcessIdList[i]; + name = PH_AUTO(PhGetClientIdName(&clientId)); + + PhAddListViewItem(processesLv, MAXINT, PhGetString(name), NULL); + } + + PhFree(processIdList); + } +} + +INT_PTR CALLBACK PhpJobPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PJOB_PAGE_CONTEXT jobPageContext; + + jobPageContext = PhpJobPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!jobPageContext) + return FALSE; + + if (jobPageContext->HookProc) + { + if (jobPageContext->HookProc(hwndDlg, uMsg, wParam, lParam)) + return TRUE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE jobHandle; + HWND processesLv; + HWND limitsLv; + + processesLv = GetDlgItem(hwndDlg, IDC_PROCESSES); + limitsLv = GetDlgItem(hwndDlg, IDC_LIMITS); + PhSetListViewStyle(processesLv, FALSE, TRUE); + PhSetListViewStyle(limitsLv, FALSE, TRUE); + PhSetControlTheme(processesLv, L"explorer"); + PhSetControlTheme(limitsLv, L"explorer"); + + PhAddListViewColumn(processesLv, 0, 0, 0, LVCFMT_LEFT, 240, L"Name"); + + PhAddListViewColumn(limitsLv, 0, 0, 0, LVCFMT_LEFT, 120, L"Name"); + PhAddListViewColumn(limitsLv, 1, 1, 1, LVCFMT_LEFT, 160, L"Value"); + + SetDlgItemText(hwndDlg, IDC_NAME, L"Unknown"); + + if (NT_SUCCESS(jobPageContext->OpenObject( + &jobHandle, + JOB_OBJECT_QUERY, + jobPageContext->Context + ))) + { + PPH_STRING jobObjectName = NULL; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedLimits; + JOBOBJECT_BASIC_UI_RESTRICTIONS basicUiRestrictions; + + // Name + + PhGetHandleInformation( + NtCurrentProcess(), + jobHandle, + -1, + NULL, + NULL, + NULL, + &jobObjectName + ); + PH_AUTO(jobObjectName); + + if (jobObjectName && jobObjectName->Length == 0) + jobObjectName = NULL; + + SetDlgItemText(hwndDlg, IDC_NAME, PhGetStringOrDefault(jobObjectName, L"(unnamed job)")); + + // Processes + PhpAddJobProcesses(hwndDlg, jobHandle); + + // Limits + + if (NT_SUCCESS(PhGetJobExtendedLimits(jobHandle, &extendedLimits))) + { + ULONG flags = extendedLimits.BasicLimitInformation.LimitFlags; + + if (flags & JOB_OBJECT_LIMIT_ACTIVE_PROCESS) + { + WCHAR value[PH_INT32_STR_LEN_1]; + PhPrintUInt32(value, extendedLimits.BasicLimitInformation.ActiveProcessLimit); + PhpAddLimit(limitsLv, L"Active processes", value); + } + + if (flags & JOB_OBJECT_LIMIT_AFFINITY) + { + WCHAR value[PH_PTR_STR_LEN_1]; + PhPrintPointer(value, (PVOID)extendedLimits.BasicLimitInformation.Affinity); + PhpAddLimit(limitsLv, L"Affinity", value); + } + + if (flags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) + { + PhpAddLimit(limitsLv, L"Breakaway OK", L"Enabled"); + } + + if (flags & JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION) + { + PhpAddLimit(limitsLv, L"Die on unhandled exception", L"Enabled"); + } + + if (flags & JOB_OBJECT_LIMIT_JOB_MEMORY) + { + PPH_STRING value = PhaFormatSize(extendedLimits.JobMemoryLimit, -1); + PhpAddLimit(limitsLv, L"Job memory", value->Buffer); + } + + if (flags & JOB_OBJECT_LIMIT_JOB_TIME) + { + WCHAR value[PH_TIMESPAN_STR_LEN_1]; + PhPrintTimeSpan(value, extendedLimits.BasicLimitInformation.PerJobUserTimeLimit.QuadPart, + PH_TIMESPAN_DHMS); + PhpAddLimit(limitsLv, L"Job time", value); + } + + if (flags & JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) + { + PhpAddLimit(limitsLv, L"Kill on job close", L"Enabled"); + } + + if (flags & JOB_OBJECT_LIMIT_PRIORITY_CLASS) + { + PhpAddLimit(limitsLv, L"Priority class", + PhGetProcessPriorityClassString(extendedLimits.BasicLimitInformation.PriorityClass)); + } + + if (flags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) + { + PPH_STRING value = PhaFormatSize(extendedLimits.ProcessMemoryLimit, -1); + PhpAddLimit(limitsLv, L"Process memory", value->Buffer); + } + + if (flags & JOB_OBJECT_LIMIT_PROCESS_TIME) + { + WCHAR value[PH_TIMESPAN_STR_LEN_1]; + PhPrintTimeSpan(value, extendedLimits.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart, + PH_TIMESPAN_DHMS); + PhpAddLimit(limitsLv, L"Process time", value); + } + + if (flags & JOB_OBJECT_LIMIT_SCHEDULING_CLASS) + { + WCHAR value[PH_INT32_STR_LEN_1]; + PhPrintUInt32(value, extendedLimits.BasicLimitInformation.SchedulingClass); + PhpAddLimit(limitsLv, L"Scheduling class", value); + } + + if (flags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) + { + PhpAddLimit(limitsLv, L"Silent breakaway OK", L"Enabled"); + } + + if (flags & JOB_OBJECT_LIMIT_WORKINGSET) + { + PPH_STRING value; + + value = PhaFormatSize(extendedLimits.BasicLimitInformation.MinimumWorkingSetSize, -1); + PhpAddLimit(limitsLv, L"Working set minimum", value->Buffer); + + value = PhaFormatSize(extendedLimits.BasicLimitInformation.MaximumWorkingSetSize, -1); + PhpAddLimit(limitsLv, L"Working set maximum", value->Buffer); + } + } + + if (NT_SUCCESS(PhGetJobBasicUiRestrictions(jobHandle, &basicUiRestrictions))) + { + ULONG flags = basicUiRestrictions.UIRestrictionsClass; + + if (flags & JOB_OBJECT_UILIMIT_DESKTOP) + PhpAddLimit(limitsLv, L"Desktop", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_DISPLAYSETTINGS) + PhpAddLimit(limitsLv, L"Display settings", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_EXITWINDOWS) + PhpAddLimit(limitsLv, L"Exit windows", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_GLOBALATOMS) + PhpAddLimit(limitsLv, L"Global atoms", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_HANDLES) + PhpAddLimit(limitsLv, L"Handles", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_READCLIPBOARD) + PhpAddLimit(limitsLv, L"Read clipboard", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS) + PhpAddLimit(limitsLv, L"System parameters", L"Limited"); + if (flags & JOB_OBJECT_UILIMIT_WRITECLIPBOARD) + PhpAddLimit(limitsLv, L"Write clipboard", L"Limited"); + } + + NtClose(jobHandle); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_TERMINATE: + { + if (PhShowConfirmMessage( + hwndDlg, + L"terminate", + L"the job", + L"Terminating a job will terminate all processes assigned to it.", + TRUE + )) + { + NTSTATUS status; + HANDLE jobHandle; + + if (NT_SUCCESS(status = jobPageContext->OpenObject( + &jobHandle, + JOB_OBJECT_TERMINATE, + jobPageContext->Context + ))) + { + status = NtTerminateJobObject(jobHandle, STATUS_SUCCESS); + NtClose(jobHandle); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to terminate the job", status, 0); + } + } + break; + case IDC_ADD: + { + NTSTATUS status; + HANDLE processId; + HANDLE processHandle; + HANDLE jobHandle; + + while (PhShowChooseProcessDialog( + hwndDlg, + L"Select a process to add to the job permanently.", + &processId + )) + { + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_TERMINATE | PROCESS_SET_QUOTA, + processId + ))) + { + if (NT_SUCCESS(status = jobPageContext->OpenObject( + &jobHandle, + JOB_OBJECT_ASSIGN_PROCESS | JOB_OBJECT_QUERY, + jobPageContext->Context + ))) + { + status = NtAssignProcessToJobObject(jobHandle, processHandle); + + if (NT_SUCCESS(status)) + { + ListView_DeleteAllItems(GetDlgItem(hwndDlg, IDC_PROCESSES)); + PhpAddJobProcesses(hwndDlg, jobHandle); + } + + NtClose(jobHandle); + } + + NtClose(processHandle); + } + + if (NT_SUCCESS(status)) + break; + else + PhShowStatus(hwndDlg, L"Unable to add the process to the job", status, 0); + } + } + break; + case IDC_ADVANCED: + { + PhpShowJobAdvancedProperties(hwndDlg, jobPageContext); + } + break; + } + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyBehaviors(lParam, GetDlgItem(hwndDlg, IDC_PROCESSES), PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + PhHandleListViewNotifyBehaviors(lParam, GetDlgItem(hwndDlg, IDC_LIMITS), PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + } + break; + } + + return FALSE; +} + +VOID PhpShowJobAdvancedProperties( + _In_ HWND ParentWindowHandle, + _In_ PJOB_PAGE_CONTEXT Context + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + HPROPSHEETPAGE pages[2]; + PROPSHEETPAGE statisticsPage; + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = L"Job"; + propSheetHeader.nPages = 2; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // General + + memset(&statisticsPage, 0, sizeof(PROPSHEETPAGE)); + statisticsPage.dwSize = sizeof(PROPSHEETPAGE); + statisticsPage.pszTemplate = MAKEINTRESOURCE(IDD_JOBSTATISTICS); + statisticsPage.pfnDlgProc = PhpJobStatisticsPageProc; + statisticsPage.lParam = (LPARAM)Context; + pages[0] = CreatePropertySheetPage(&statisticsPage); + + // Security + + stdObjectSecurity.OpenObject = Context->OpenObject; + stdObjectSecurity.ObjectType = L"Job"; + stdObjectSecurity.Context = Context->Context; + + if (PhGetAccessEntries(L"Job", &accessEntries, &numberOfAccessEntries)) + { + pages[1] = PhCreateSecurityPage( + L"Job", + PhStdGetObjectSecurity, + PhStdSetObjectSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + + PhModalPropertySheet(&propSheetHeader); +} + +static VOID PhpRefreshJobStatisticsInfo( + _In_ HWND hwndDlg, + _In_ PJOB_PAGE_CONTEXT Context + ) +{ + HANDLE jobHandle = NULL; + JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION basicAndIo; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedLimitInfo; + + Context->OpenObject( + &jobHandle, + JOB_OBJECT_QUERY, + Context->Context + ); + + if (jobHandle && NT_SUCCESS(PhGetJobBasicAndIoAccounting( + jobHandle, + &basicAndIo + ))) + { + WCHAR timeSpan[PH_TIMESPAN_STR_LEN_1]; + + SetDlgItemInt(hwndDlg, IDC_ZACTIVEPROCESSES_V, basicAndIo.BasicInfo.ActiveProcesses, FALSE); + SetDlgItemInt(hwndDlg, IDC_ZTOTALPROCESSES_V, basicAndIo.BasicInfo.TotalProcesses, FALSE); + SetDlgItemInt(hwndDlg, IDC_ZTERMINATEDPROCESSES_V, basicAndIo.BasicInfo.TotalTerminatedProcesses, FALSE); + + PhPrintTimeSpan(timeSpan, basicAndIo.BasicInfo.TotalUserTime.QuadPart, PH_TIMESPAN_HMSM); + SetDlgItemText(hwndDlg, IDC_ZUSERTIME_V, timeSpan); + PhPrintTimeSpan(timeSpan, basicAndIo.BasicInfo.TotalKernelTime.QuadPart, PH_TIMESPAN_HMSM); + SetDlgItemText(hwndDlg, IDC_ZKERNELTIME_V, timeSpan); + PhPrintTimeSpan(timeSpan, basicAndIo.BasicInfo.ThisPeriodTotalUserTime.QuadPart, PH_TIMESPAN_HMSM); + SetDlgItemText(hwndDlg, IDC_ZUSERTIMEPERIOD_V, timeSpan); + PhPrintTimeSpan(timeSpan, basicAndIo.BasicInfo.ThisPeriodTotalKernelTime.QuadPart, PH_TIMESPAN_HMSM); + SetDlgItemText(hwndDlg, IDC_ZKERNELTIMEPERIOD_V, timeSpan); + + SetDlgItemText(hwndDlg, IDC_ZPAGEFAULTS_V, PhaFormatUInt64(basicAndIo.BasicInfo.TotalPageFaultCount, TRUE)->Buffer); + + SetDlgItemText(hwndDlg, IDC_ZIOREADS_V, PhaFormatUInt64(basicAndIo.IoInfo.ReadOperationCount, TRUE)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZIOREADBYTES_V, PhaFormatSize(basicAndIo.IoInfo.ReadTransferCount, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZIOWRITES_V, PhaFormatUInt64(basicAndIo.IoInfo.WriteOperationCount, TRUE)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZIOWRITEBYTES_V, PhaFormatSize(basicAndIo.IoInfo.WriteTransferCount, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZIOOTHER_V, PhaFormatUInt64(basicAndIo.IoInfo.OtherOperationCount, TRUE)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZIOOTHERBYTES_V, PhaFormatSize(basicAndIo.IoInfo.OtherTransferCount, -1)->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_ZACTIVEPROCESSES_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZTOTALPROCESSES_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZTERMINATEDPROCESSES_V, L"Unknown"); + + SetDlgItemText(hwndDlg, IDC_ZUSERTIME_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZKERNELTIME_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZUSERTIMEPERIOD_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZKERNELTIMEPERIOD_V, L"Unknown"); + + SetDlgItemText(hwndDlg, IDC_ZPAGEFAULTS_V, L"Unknown"); + + SetDlgItemText(hwndDlg, IDC_ZIOREADS_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZIOREADBYTES_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZIOWRITES_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZIOWRITEBYTES_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZIOOTHER_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZIOOTHERBYTES_V, L"Unknown"); + } + + if (jobHandle && NT_SUCCESS(PhGetJobExtendedLimits( + jobHandle, + &extendedLimitInfo + ))) + { + SetDlgItemText(hwndDlg, IDC_ZPEAKPROCESSUSAGE_V, PhaFormatSize(extendedLimitInfo.PeakProcessMemoryUsed, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZPEAKJOBUSAGE_V, PhaFormatSize(extendedLimitInfo.PeakJobMemoryUsed, -1)->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_ZPEAKPROCESSUSAGE_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZPEAKJOBUSAGE_V, L"Unknown"); + } + + if (jobHandle) + NtClose(jobHandle); +} + +INT_PTR CALLBACK PhpJobStatisticsPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PJOB_PAGE_CONTEXT jobPageContext; + + jobPageContext = PhpJobPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!jobPageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpRefreshJobStatisticsInfo(hwndDlg, jobPageContext); + SetTimer(hwndDlg, 1, PhGetIntegerSetting(L"UpdateInterval"), NULL); + } + break; + case WM_TIMER: + { + if (wParam == 1) + { + PhpRefreshJobStatisticsInfo(hwndDlg, jobPageContext); + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/log.c b/ProcessHacker/log.c new file mode 100644 index 0000000..6c4a4e2 --- /dev/null +++ b/ProcessHacker/log.c @@ -0,0 +1,239 @@ +/* + * Process Hacker - + * logging system + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +PH_CIRCULAR_BUFFER_PVOID PhLogBuffer; +PHAPPAPI PH_CALLBACK_DECLARE(PhLoggedCallback); + +VOID PhLogInitialization( + VOID + ) +{ + ULONG entries; + + entries = PhGetIntegerSetting(L"LogEntries"); + if (entries > 0x1000) entries = 0x1000; + PhInitializeCircularBuffer_PVOID(&PhLogBuffer, entries); + memset(PhLogBuffer.Data, 0, sizeof(PVOID) * PhLogBuffer.Size); +} + +PPH_LOG_ENTRY PhpCreateLogEntry( + _In_ UCHAR Type + ) +{ + PPH_LOG_ENTRY entry; + + entry = PhAllocate(sizeof(PH_LOG_ENTRY)); + memset(entry, 0, sizeof(PH_LOG_ENTRY)); + + entry->Type = Type; + PhQuerySystemTime(&entry->Time); + + return entry; +} + +VOID PhpFreeLogEntry( + _Inout_ PPH_LOG_ENTRY Entry + ) +{ + if (Entry->Type >= PH_LOG_ENTRY_PROCESS_FIRST && Entry->Type <= PH_LOG_ENTRY_PROCESS_LAST) + { + PhDereferenceObject(Entry->Process.Name); + if (Entry->Process.ParentName) PhDereferenceObject(Entry->Process.ParentName); + } + else if (Entry->Type >= PH_LOG_ENTRY_SERVICE_FIRST && Entry->Type <= PH_LOG_ENTRY_SERVICE_LAST) + { + PhDereferenceObject(Entry->Service.Name); + PhDereferenceObject(Entry->Service.DisplayName); + } + else if (Entry->Type == PH_LOG_ENTRY_MESSAGE) + { + PhDereferenceObject(Entry->Message); + } + + PhFree(Entry); +} + +PPH_LOG_ENTRY PhpCreateProcessLogEntry( + _In_ UCHAR Type, + _In_ HANDLE ProcessId, + _In_opt_ HANDLE QueryHandle, + _In_ PPH_STRING Name, + _In_opt_ HANDLE ParentProcessId, + _In_opt_ PPH_STRING ParentName + ) +{ + PPH_LOG_ENTRY entry; + + entry = PhpCreateLogEntry(Type); + entry->Process.ProcessId = ProcessId; + PhReferenceObject(Name); + entry->Process.Name = Name; + + entry->Process.ParentProcessId = ParentProcessId; + + if (ParentName) + { + PhReferenceObject(ParentName); + entry->Process.ParentName = ParentName; + } + + if (QueryHandle && entry->Type == PH_LOG_ENTRY_PROCESS_DELETE) + { + PROCESS_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhGetProcessBasicInformation(QueryHandle, &basicInfo))) + { + entry->Process.ExitStatus = basicInfo.ExitStatus; + } + } + + return entry; +} + +PPH_LOG_ENTRY PhpCreateServiceLogEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Name, + _In_ PPH_STRING DisplayName + ) +{ + PPH_LOG_ENTRY entry; + + entry = PhpCreateLogEntry(Type); + PhReferenceObject(Name); + entry->Service.Name = Name; + PhReferenceObject(DisplayName); + entry->Service.DisplayName = DisplayName; + + return entry; +} + +PPH_LOG_ENTRY PhpCreateMessageLogEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Message + ) +{ + PPH_LOG_ENTRY entry; + + entry = PhpCreateLogEntry(Type); + PhReferenceObject(Message); + entry->Message = Message; + + return entry; +} + +VOID PhpLogEntry( + _In_ PPH_LOG_ENTRY Entry + ) +{ + PPH_LOG_ENTRY oldEntry; + + oldEntry = PhAddItemCircularBuffer2_PVOID(&PhLogBuffer, Entry); + + if (oldEntry) + PhpFreeLogEntry(oldEntry); + + PhInvokeCallback(&PhLoggedCallback, Entry); +} + +VOID PhClearLogEntries( + VOID + ) +{ + ULONG i; + + for (i = 0; i < PhLogBuffer.Size; i++) + { + if (PhLogBuffer.Data[i]) + PhpFreeLogEntry(PhLogBuffer.Data[i]); + } + + PhClearCircularBuffer_PVOID(&PhLogBuffer); + memset(PhLogBuffer.Data, 0, sizeof(PVOID) * PhLogBuffer.Size); +} + +VOID PhLogProcessEntry( + _In_ UCHAR Type, + _In_ HANDLE ProcessId, + _In_opt_ HANDLE QueryHandle, + _In_ PPH_STRING Name, + _In_opt_ HANDLE ParentProcessId, + _In_opt_ PPH_STRING ParentName + ) +{ + PhpLogEntry(PhpCreateProcessLogEntry(Type, ProcessId, QueryHandle, Name, ParentProcessId, ParentName)); +} + +VOID PhLogServiceEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Name, + _In_ PPH_STRING DisplayName + ) +{ + PhpLogEntry(PhpCreateServiceLogEntry(Type, Name, DisplayName)); +} + +VOID PhLogMessageEntry( + _In_ UCHAR Type, + _In_ PPH_STRING Message + ) +{ + PhpLogEntry(PhpCreateMessageLogEntry(Type, Message)); +} + +PPH_STRING PhFormatLogEntry( + _In_ PPH_LOG_ENTRY Entry + ) +{ + switch (Entry->Type) + { + case PH_LOG_ENTRY_PROCESS_CREATE: + return PhFormatString( + L"Process created: %s (%u) started by %s (%u)", + Entry->Process.Name->Buffer, + HandleToUlong(Entry->Process.ProcessId), + PhGetStringOrDefault(Entry->Process.ParentName, L"Unknown process"), + HandleToUlong(Entry->Process.ParentProcessId) + ); + case PH_LOG_ENTRY_PROCESS_DELETE: + return PhFormatString(L"Process terminated: %s (%u); exit status 0x%x", Entry->Process.Name->Buffer, HandleToUlong(Entry->Process.ProcessId), Entry->Process.ExitStatus); + case PH_LOG_ENTRY_SERVICE_CREATE: + return PhFormatString(L"Service created: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_SERVICE_DELETE: + return PhFormatString(L"Service deleted: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_SERVICE_START: + return PhFormatString(L"Service started: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_SERVICE_STOP: + return PhFormatString(L"Service stopped: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_SERVICE_CONTINUE: + return PhFormatString(L"Service continued: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_SERVICE_PAUSE: + return PhFormatString(L"Service paused: %s (%s)", Entry->Service.Name->Buffer, Entry->Service.DisplayName->Buffer); + case PH_LOG_ENTRY_MESSAGE: + PhReferenceObject(Entry->Message); + return Entry->Message; + default: + return PhReferenceEmptyString(); + } +} diff --git a/ProcessHacker/logwnd.c b/ProcessHacker/logwnd.c new file mode 100644 index 0000000..0b5cc54 --- /dev/null +++ b/ProcessHacker/logwnd.c @@ -0,0 +1,353 @@ +/* + * Process Hacker - + * log window + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#define WM_PH_LOG_UPDATED (WM_APP + 300) + +INT_PTR CALLBACK PhpLogDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +HWND PhLogWindowHandle = NULL; +static PH_LAYOUT_MANAGER WindowLayoutManager; +static RECT MinimumSize; +static HWND ListViewHandle; +static ULONG ListViewCount; +static PH_CALLBACK_REGISTRATION LoggedRegistration; + +VOID PhShowLogDialog( + VOID + ) +{ + if (!PhLogWindowHandle) + { + PhLogWindowHandle = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_LOG), + PhMainWndHandle, + PhpLogDlgProc + ); + PhRegisterDialog(PhLogWindowHandle); + ShowWindow(PhLogWindowHandle, SW_SHOW); + } + + if (IsIconic(PhLogWindowHandle)) + ShowWindow(PhLogWindowHandle, SW_RESTORE); + else + SetForegroundWindow(PhLogWindowHandle); +} + +static VOID NTAPI LoggedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhLogWindowHandle, WM_PH_LOG_UPDATED, 0, 0); +} + +static VOID PhpUpdateLogList( + VOID + ) +{ + ListViewCount = PhLogBuffer.Count; + ListView_SetItemCountEx(ListViewHandle, ListViewCount, LVSICF_NOSCROLL); + + if (ListViewCount >= 2 && Button_GetCheck(GetDlgItem(PhLogWindowHandle, IDC_AUTOSCROLL)) == BST_CHECKED) + { + if (ListView_IsItemVisible(ListViewHandle, ListViewCount - 2)) + { + ListView_EnsureVisible(ListViewHandle, ListViewCount - 1, FALSE); + } + } +} + +static PPH_STRING PhpGetStringForSelectedLogEntries( + _In_ BOOLEAN All + ) +{ + PH_STRING_BUILDER stringBuilder; + ULONG i; + + if (ListViewCount == 0) + return PhReferenceEmptyString(); + + PhInitializeStringBuilder(&stringBuilder, 0x100); + + i = ListViewCount - 1; + + while (TRUE) + { + PPH_LOG_ENTRY entry; + SYSTEMTIME systemTime; + PPH_STRING temp; + + if (!All) + { + // The list view displays the items in reverse order... + if (!(ListView_GetItemState(ListViewHandle, ListViewCount - i - 1, LVIS_SELECTED) & LVIS_SELECTED)) + { + goto ContinueLoop; + } + } + + entry = PhGetItemCircularBuffer_PVOID(&PhLogBuffer, i); + + if (!entry) + goto ContinueLoop; + + PhLargeIntegerToLocalSystemTime(&systemTime, &entry->Time); + temp = PhFormatDateTime(&systemTime); + PhAppendStringBuilder(&stringBuilder, &temp->sr); + PhDereferenceObject(temp); + PhAppendStringBuilder2(&stringBuilder, L": "); + + temp = PhFormatLogEntry(entry); + PhAppendStringBuilder(&stringBuilder, &temp->sr); + PhDereferenceObject(temp); + PhAppendStringBuilder2(&stringBuilder, L"\r\n"); + +ContinueLoop: + + if (i == 0) + break; + + i--; + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +INT_PTR CALLBACK PhpLogDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ListViewHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(ListViewHandle, FALSE, TRUE); + PhSetControlTheme(ListViewHandle, L"explorer"); + PhAddListViewColumn(ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 140, L"Time"); + PhAddListViewColumn(ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 260, L"Message"); + PhLoadListViewColumnsFromSetting(L"LogListViewColumns", ListViewHandle); + + PhInitializeLayoutManager(&WindowLayoutManager, hwndDlg); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_LIST), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_COPY), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_SAVE), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_AUTOSCROLL), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(hwndDlg, IDC_CLEAR), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = 290; + MinimumSize.bottom = 150; + MapDialogRect(hwndDlg, &MinimumSize); + + PhLoadWindowPlacementFromSetting(L"LogWindowPosition", L"LogWindowSize", hwndDlg); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_AUTOSCROLL), BST_CHECKED); + + PhRegisterCallback(&PhLoggedCallback, LoggedCallback, NULL, &LoggedRegistration); + PhpUpdateLogList(); + ListView_EnsureVisible(ListViewHandle, ListViewCount - 1, FALSE); + } + break; + case WM_DESTROY: + { + PhSaveListViewColumnsToSetting(L"LogListViewColumns", ListViewHandle); + PhSaveWindowPlacementToSetting(L"LogWindowPosition", L"LogWindowSize", hwndDlg); + + PhDeleteLayoutManager(&WindowLayoutManager); + + PhUnregisterCallback(&PhLoggedCallback, &LoggedRegistration); + PhUnregisterDialog(PhLogWindowHandle); + PhLogWindowHandle = NULL; + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_CLEAR: + { + PhClearLogEntries(); + PhpUpdateLogList(); + } + break; + case IDC_COPY: + { + PPH_STRING string; + ULONG selectedCount; + + selectedCount = ListView_GetSelectedCount(ListViewHandle); + + if (selectedCount == 0) + { + // User didn't select anything, so copy all items. + string = PhpGetStringForSelectedLogEntries(TRUE); + PhSetStateAllListViewItems(ListViewHandle, LVIS_SELECTED, LVIS_SELECTED); + } + else + { + string = PhpGetStringForSelectedLogEntries(FALSE); + } + + PhSetClipboardString(hwndDlg, &string->sr); + PhDereferenceObject(string); + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)ListViewHandle, TRUE); + } + break; + case IDC_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Text files (*.txt)", L"*.txt" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, L"Process Hacker Log.txt"); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + PPH_STRING string; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + PhWriteStringAsUtf8FileStream(fileStream, &PhUnicodeByteOrderMark); + PhWritePhTextHeader(fileStream); + + string = PhpGetStringForSelectedLogEntries(TRUE); + PhWriteStringAsUtf8FileStreamEx(fileStream, string->Buffer, string->Length); + PhDereferenceObject(string); + + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case LVN_GETDISPINFO: + { + NMLVDISPINFO *dispInfo = (NMLVDISPINFO *)header; + PPH_LOG_ENTRY entry; + + entry = PhGetItemCircularBuffer_PVOID(&PhLogBuffer, ListViewCount - dispInfo->item.iItem - 1); + + if (dispInfo->item.iSubItem == 0) + { + if (dispInfo->item.mask & LVIF_TEXT) + { + SYSTEMTIME systemTime; + PPH_STRING dateTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &entry->Time); + dateTime = PhFormatDateTime(&systemTime); + wcsncpy_s(dispInfo->item.pszText, dispInfo->item.cchTextMax, dateTime->Buffer, _TRUNCATE); + PhDereferenceObject(dateTime); + } + } + else if (dispInfo->item.iSubItem == 1) + { + if (dispInfo->item.mask & LVIF_TEXT) + { + PPH_STRING string; + + string = PhFormatLogEntry(entry); + wcsncpy_s(dispInfo->item.pszText, dispInfo->item.cchTextMax, string->Buffer, _TRUNCATE); + PhDereferenceObject(string); + } + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&WindowLayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_PH_LOG_UPDATED: + { + PhpUpdateLogList(); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/main.c b/ProcessHacker/main.c new file mode 100644 index 0000000..c5e8a1e --- /dev/null +++ b/ProcessHacker/main.c @@ -0,0 +1,1084 @@ +/* + * Process Hacker - + * main program + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LONG PhMainMessageLoop( + VOID + ); + +VOID PhActivatePreviousInstance( + VOID + ); + +VOID PhInitializeCommonControls( + VOID + ); + +VOID PhInitializeKph( + VOID + ); + +BOOLEAN PhInitializeAppSystem( + VOID + ); + +VOID PhpInitializeSettings( + VOID + ); + +VOID PhpProcessStartupParameters( + VOID + ); + +VOID PhpEnablePrivileges( + VOID + ); + +PPH_STRING PhApplicationDirectory; +PPH_STRING PhApplicationFileName; +PHAPPAPI HFONT PhApplicationFont; +PPH_STRING PhCurrentUserName = NULL; +HINSTANCE PhInstanceHandle; +PPH_STRING PhLocalSystemName = NULL; +BOOLEAN PhPluginsEnabled = FALSE; +PPH_STRING PhSettingsFileName = NULL; +PH_INTEGER_PAIR PhSmallIconSize = { 16, 16 }; +PH_INTEGER_PAIR PhLargeIconSize = { 32, 32 }; +PH_STARTUP_PARAMETERS PhStartupParameters; + +PH_PROVIDER_THREAD PhPrimaryProviderThread; +PH_PROVIDER_THREAD PhSecondaryProviderThread; + +static PPH_LIST DialogList = NULL; +static PPH_LIST FilterList = NULL; +static PH_AUTO_POOL BaseAutoPool; + +INT WINAPI wWinMain( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ PWSTR lpCmdLine, + _In_ INT nCmdShow + ) +{ + LONG result; +#ifdef DEBUG + PHP_BASE_THREAD_DBG dbg; +#endif + HANDLE currentTokenHandle; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +#ifndef DEBUG + SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); +#endif + + PhInstanceHandle = (HINSTANCE)NtCurrentPeb()->ImageBaseAddress; + + if (!NT_SUCCESS(PhInitializePhLib())) + return 1; + if (!PhInitializeAppSystem()) + return 1; + + PhInitializeCommonControls(); + + currentTokenHandle = PhGetOwnTokenAttributes().TokenHandle; + + if (currentTokenHandle) + { + PTOKEN_USER tokenUser; + + if (NT_SUCCESS(PhGetTokenUser(currentTokenHandle, &tokenUser))) + { + PhCurrentUserName = PhGetSidFullName(tokenUser->User.Sid, TRUE, NULL); + PhFree(tokenUser); + } + } + + PhLocalSystemName = PhGetSidFullName(&PhSeLocalSystemSid, TRUE, NULL); + + // There has been a report of the above call failing. + if (!PhLocalSystemName) + PhLocalSystemName = PhCreateString(L"NT AUTHORITY\\SYSTEM"); + + PhApplicationFileName = PhGetApplicationFileName(); + PhApplicationDirectory = PhGetApplicationDirectory(); + + // Just in case + if (!PhApplicationFileName) + PhApplicationFileName = PhCreateString(L"ProcessHacker.exe"); + if (!PhApplicationDirectory) + PhApplicationDirectory = PhReferenceEmptyString(); + + PhpProcessStartupParameters(); + PhSettingsInitialization(); + PhpEnablePrivileges(); + + if (PhStartupParameters.RunAsServiceMode) + { + RtlExitUserProcess(PhRunAsServiceStart(PhStartupParameters.RunAsServiceMode)); + } + + PhpInitializeSettings(); + + // Activate a previous instance if required. + if (PhGetIntegerSetting(L"AllowOnlyOneInstance") && + !PhStartupParameters.NewInstance && + !PhStartupParameters.ShowOptions && + !PhStartupParameters.CommandMode && + !PhStartupParameters.PhSvc) + { + PhActivatePreviousInstance(); + } + + if (PhGetIntegerSetting(L"EnableKph") && !PhStartupParameters.NoKph && !PhStartupParameters.CommandMode && !PhIsExecutingInWow64()) + PhInitializeKph(); + + if (PhStartupParameters.CommandMode && PhStartupParameters.CommandType && PhStartupParameters.CommandAction) + { + NTSTATUS status; + + status = PhCommandModeStart(); + + if (!NT_SUCCESS(status) && !PhStartupParameters.Silent) + { + PhShowStatus(NULL, L"Unable to execute the command", status, 0); + } + + RtlExitUserProcess(status); + } + +#ifdef DEBUG + dbg.ClientId = NtCurrentTeb()->ClientId; + dbg.StartAddress = wWinMain; + dbg.Parameter = NULL; + InsertTailList(&PhDbgThreadListHead, &dbg.ListEntry); + TlsSetValue(PhDbgThreadDbgTlsIndex, &dbg); +#endif + + PhInitializeAutoPool(&BaseAutoPool); + + PhEmInitialization(); + PhGuiSupportInitialization(); + PhTreeNewInitialization(); + PhGraphControlInitialization(); + PhHexEditInitialization(); + PhColorBoxInitialization(); + + PhSmallIconSize.X = GetSystemMetrics(SM_CXSMICON); + PhSmallIconSize.Y = GetSystemMetrics(SM_CYSMICON); + PhLargeIconSize.X = GetSystemMetrics(SM_CXICON); + PhLargeIconSize.Y = GetSystemMetrics(SM_CYICON); + + if (PhStartupParameters.ShowOptions) + { + // Elevated options dialog for changing the value of Replace Task Manager with Process Hacker. + PhShowOptionsDialog(PhStartupParameters.WindowHandle); + RtlExitUserProcess(STATUS_SUCCESS); + } + +#ifndef DEBUG + if (PhIsExecutingInWow64() && !PhStartupParameters.PhSvc) + { + PhShowWarning( + NULL, + L"You are attempting to run the 32-bit version of Process Hacker on 64-bit Windows. " + L"Most features will not work correctly.\n\n" + L"Please run the 64-bit version of Process Hacker instead." + ); + } +#endif + + PhPluginsEnabled = PhGetIntegerSetting(L"EnablePlugins") && !PhStartupParameters.NoPlugins; + + if (PhPluginsEnabled) + { + PhPluginsInitialization(); + PhLoadPlugins(); + } + + if (PhStartupParameters.PhSvc) + { + MSG message; + + // Turn the feedback cursor off. + PostMessage(NULL, WM_NULL, 0, 0); + GetMessage(&message, NULL, 0, 0); + + RtlExitUserProcess(PhSvcMain(NULL, NULL, NULL)); + } + + // Create a mutant for the installer. + { + HANDLE mutantHandle; + OBJECT_ATTRIBUTES oa; + UNICODE_STRING mutantName; + + RtlInitUnicodeString(&mutantName, L"\\BaseNamedObjects\\ProcessHacker2Mutant"); + InitializeObjectAttributes( + &oa, + &mutantName, + 0, + NULL, + NULL + ); + + NtCreateMutant(&mutantHandle, MUTANT_ALL_ACCESS, &oa, FALSE); + } + + // Set priority. + { + PROCESS_PRIORITY_CLASS priorityClass; + + priorityClass.Foreground = FALSE; + priorityClass.PriorityClass = PROCESS_PRIORITY_CLASS_HIGH; + + if (PhStartupParameters.PriorityClass != 0) + priorityClass.PriorityClass = (UCHAR)PhStartupParameters.PriorityClass; + + NtSetInformationProcess(NtCurrentProcess(), ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS)); + } + + if (!PhMainWndInitialization(nCmdShow)) + { + PhShowError(NULL, L"Unable to initialize the main window."); + return 1; + } + + PhDrainAutoPool(&BaseAutoPool); + + result = PhMainMessageLoop(); + RtlExitUserProcess(result); +} + +LONG PhMainMessageLoop( + VOID + ) +{ + BOOL result; + MSG message; + HACCEL acceleratorTable; + + acceleratorTable = LoadAccelerators(PhInstanceHandle, MAKEINTRESOURCE(IDR_MAINWND_ACCEL)); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + BOOLEAN processed = FALSE; + ULONG i; + + if (result == -1) + return 1; + + if (FilterList) + { + for (i = 0; i < FilterList->Count; i++) + { + PPH_MESSAGE_LOOP_FILTER_ENTRY entry = FilterList->Items[i]; + + if (entry->Filter(&message, entry->Context)) + { + processed = TRUE; + break; + } + } + } + + if (!processed) + { + if ( + message.hwnd == PhMainWndHandle || + IsChild(PhMainWndHandle, message.hwnd) + ) + { + if (TranslateAccelerator(PhMainWndHandle, acceleratorTable, &message)) + processed = TRUE; + } + + if (DialogList) + { + for (i = 0; i < DialogList->Count; i++) + { + if (IsDialogMessage((HWND)DialogList->Items[i], &message)) + { + processed = TRUE; + break; + } + } + } + } + + if (!processed) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&BaseAutoPool); + } + + return (LONG)message.wParam; +} + +VOID PhRegisterDialog( + _In_ HWND DialogWindowHandle + ) +{ + if (!DialogList) + DialogList = PhCreateList(2); + + PhAddItemList(DialogList, (PVOID)DialogWindowHandle); +} + +VOID PhUnregisterDialog( + _In_ HWND DialogWindowHandle + ) +{ + ULONG indexOfDialog; + + if (!DialogList) + return; + + indexOfDialog = PhFindItemList(DialogList, (PVOID)DialogWindowHandle); + + if (indexOfDialog != -1) + PhRemoveItemList(DialogList, indexOfDialog); +} + +struct _PH_MESSAGE_LOOP_FILTER_ENTRY *PhRegisterMessageLoopFilter( + _In_ PPH_MESSAGE_LOOP_FILTER Filter, + _In_opt_ PVOID Context + ) +{ + PPH_MESSAGE_LOOP_FILTER_ENTRY entry; + + if (!FilterList) + FilterList = PhCreateList(2); + + entry = PhAllocate(sizeof(PH_MESSAGE_LOOP_FILTER_ENTRY)); + entry->Filter = Filter; + entry->Context = Context; + PhAddItemList(FilterList, entry); + + return entry; +} + +VOID PhUnregisterMessageLoopFilter( + _In_ struct _PH_MESSAGE_LOOP_FILTER_ENTRY *FilterEntry + ) +{ + ULONG indexOfFilter; + + if (!FilterList) + return; + + indexOfFilter = PhFindItemList(FilterList, FilterEntry); + + if (indexOfFilter != -1) + PhRemoveItemList(FilterList, indexOfFilter); + + PhFree(FilterEntry); +} + +VOID PhActivatePreviousInstance( + VOID + ) +{ + HWND hwnd; + + hwnd = FindWindow(PH_MAINWND_CLASSNAME, NULL); + + if (hwnd) + { + ULONG_PTR result; + + SendMessageTimeout(hwnd, WM_PH_ACTIVATE, PhStartupParameters.SelectPid, 0, SMTO_BLOCK, 5000, &result); + + if (result == PH_ACTIVATE_REPLY) + { + SetForegroundWindow(hwnd); + RtlExitUserProcess(STATUS_SUCCESS); + } + } +} + +VOID PhInitializeCommonControls( + VOID + ) +{ + INITCOMMONCONTROLSEX icex; + + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = + ICC_LINK_CLASS | + ICC_LISTVIEW_CLASSES | + ICC_PROGRESS_CLASS | + ICC_TAB_CLASSES + ; + + InitCommonControlsEx(&icex); +} + +HFONT PhpCreateFont( + _In_ HWND hWnd, + _In_ PWSTR Name, + _In_ ULONG Size, + _In_ ULONG Weight + ) +{ + HFONT font; + HDC hdc; + + hdc = GetDC(hWnd); + + if (hdc) + { + font = CreateFont( + -(LONG)PhMultiplyDivide(Size, PhGlobalDpi, 72), + 0, + 0, + 0, + Weight, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + DEFAULT_PITCH, + Name + ); + ReleaseDC(hWnd, hdc); + + return font; + } + else + { + return NULL; + } +} + +VOID PhInitializeFont( + _In_ HWND hWnd + ) +{ + NONCLIENTMETRICS metrics = { sizeof(metrics) }; + BOOLEAN success; + HDC hdc; + + if (hdc = GetDC(hWnd)) + { + PhGlobalDpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(hWnd, hdc); + } + else + { + PhGlobalDpi = 96; + } + + success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0); + + if ( + !(PhApplicationFont = PhpCreateFont(hWnd, L"Microsoft Sans Serif", 8, FW_NORMAL)) && + !(PhApplicationFont = PhpCreateFont(hWnd, L"Tahoma", 8, FW_NORMAL)) + ) + { + if (success) + PhApplicationFont = CreateFontIndirect(&metrics.lfMessageFont); + else + PhApplicationFont = NULL; + } +} + +PUCHAR PhpReadSignature( + _In_ PWSTR FileName, + _Out_ PULONG SignatureSize + ) +{ + NTSTATUS status; + HANDLE fileHandle; + PUCHAR signature; + ULONG bufferSize; + IO_STATUS_BLOCK iosb; + + if (!NT_SUCCESS(PhCreateFileWin32(&fileHandle, FileName, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT))) + { + return NULL; + } + + bufferSize = 1024; + signature = PhAllocate(bufferSize); + + status = NtReadFile(fileHandle, NULL, NULL, NULL, &iosb, signature, bufferSize, NULL, NULL); + NtClose(fileHandle); + + if (NT_SUCCESS(status)) + { + *SignatureSize = (ULONG)iosb.Information; + return signature; + } + else + { + PhFree(signature); + return NULL; + } +} + +VOID PhInitializeKph( + VOID + ) +{ + static PH_STRINGREF kprocesshacker = PH_STRINGREF_INIT(L"kprocesshacker.sys"); + static PH_STRINGREF processhackerSig = PH_STRINGREF_INIT(L"ProcessHacker.sig"); + + PPH_STRING kprocesshackerFileName; + PPH_STRING processhackerSigFileName; + KPH_PARAMETERS parameters; + PUCHAR signature; + ULONG signatureSize; + + if (WindowsVersion < WINDOWS_7) + return; + + kprocesshackerFileName = PhConcatStringRef2(&PhApplicationDirectory->sr, &kprocesshacker); + processhackerSigFileName = PhConcatStringRef2(&PhApplicationDirectory->sr, &processhackerSig); + + parameters.SecurityLevel = KphSecurityPrivilegeCheck; + parameters.CreateDynamicConfiguration = TRUE; + KphConnect2Ex(KPH_DEVICE_SHORT_NAME, kprocesshackerFileName->Buffer, ¶meters); + + if (signature = PhpReadSignature(processhackerSigFileName->Buffer, &signatureSize)) + { + KphVerifyClient(signature, signatureSize); + PhFree(signature); + } + + PhDereferenceObject(kprocesshackerFileName); + PhDereferenceObject(processhackerSigFileName); +} + +BOOLEAN PhInitializeAppSystem( + VOID + ) +{ + PhApplicationName = L"Process Hacker"; + + if (!PhProcessProviderInitialization()) + return FALSE; + if (!PhServiceProviderInitialization()) + return FALSE; + if (!PhNetworkProviderInitialization()) + return FALSE; + if (!PhModuleProviderInitialization()) + return FALSE; + if (!PhThreadProviderInitialization()) + return FALSE; + if (!PhHandleProviderInitialization()) + return FALSE; + if (!PhMemoryProviderInitialization()) + return FALSE; + if (!PhProcessPropInitialization()) + return FALSE; + + PhSetHandleClientIdFunction(PhGetClientIdName); + + return TRUE; +} + +VOID PhpInitializeSettings( + VOID + ) +{ + NTSTATUS status; + + if (!PhStartupParameters.NoSettings) + { + static PH_STRINGREF settingsSuffix = PH_STRINGREF_INIT(L".settings.xml"); + PPH_STRING settingsFileName; + + // There are three possible locations for the settings file: + // 1. The file name given in the command line. + // 2. A file named ProcessHacker.exe.settings.xml in the program directory. (This changes + // based on the executable file name.) + // 3. The default location. + + // 1. File specified in command line + if (PhStartupParameters.SettingsFileName) + { + // Get an absolute path now. + PhSettingsFileName = PhGetFullPath(PhStartupParameters.SettingsFileName->Buffer, NULL); + } + + // 2. File in program directory + if (!PhSettingsFileName) + { + settingsFileName = PhConcatStringRef2(&PhApplicationFileName->sr, &settingsSuffix); + + if (RtlDoesFileExists_U(settingsFileName->Buffer)) + { + PhSettingsFileName = settingsFileName; + } + else + { + PhDereferenceObject(settingsFileName); + } + } + + // 3. Default location + if (!PhSettingsFileName) + { + PhSettingsFileName = PhGetKnownLocation(CSIDL_APPDATA, L"\\Process Hacker 2\\settings.xml"); + } + + if (PhSettingsFileName) + { + status = PhLoadSettings(PhSettingsFileName->Buffer); + + // If we didn't find the file, it will be created. Otherwise, + // there was probably a parsing error and we don't want to + // change anything. + if (status == STATUS_FILE_CORRUPT_ERROR) + { + if (PhShowMessage( + NULL, + MB_ICONWARNING | MB_YESNO, + L"Process Hacker's settings file is corrupt. Do you want to reset it?\n" + L"If you select No, the settings system will not function properly." + ) == IDYES) + { + HANDLE fileHandle; + IO_STATUS_BLOCK isb; + CHAR data[] = ""; + + // This used to delete the file. But it's better to keep the file there + // and overwrite it with some valid XML, especially with case (2) above. + if (NT_SUCCESS(PhCreateFileWin32( + &fileHandle, + PhSettingsFileName->Buffer, + FILE_GENERIC_WRITE, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OVERWRITE, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + NtWriteFile(fileHandle, NULL, NULL, NULL, &isb, data, sizeof(data) - 1, NULL, NULL); + NtClose(fileHandle); + } + } + else + { + // Pretend we don't have a settings store so bad things + // don't happen. + PhDereferenceObject(PhSettingsFileName); + PhSettingsFileName = NULL; + } + } + } + } + + // Apply basic global settings. + PhMaxSizeUnit = PhGetIntegerSetting(L"MaxSizeUnit"); + + if (PhGetIntegerSetting(L"SampleCountAutomatic")) + { + ULONG sampleCount; + + sampleCount = (GetSystemMetrics(SM_CXVIRTUALSCREEN) + 1) / 2; + + if (sampleCount > 2048) + sampleCount = 2048; + + PhSetIntegerSetting(L"SampleCount", sampleCount); + } +} + +#define PH_ARG_SETTINGS 1 +#define PH_ARG_NOSETTINGS 2 +#define PH_ARG_SHOWVISIBLE 3 +#define PH_ARG_SHOWHIDDEN 4 +#define PH_ARG_COMMANDMODE 5 +#define PH_ARG_COMMANDTYPE 6 +#define PH_ARG_COMMANDOBJECT 7 +#define PH_ARG_COMMANDACTION 8 +#define PH_ARG_COMMANDVALUE 9 +#define PH_ARG_RUNASSERVICEMODE 10 +#define PH_ARG_NOKPH 11 +#define PH_ARG_INSTALLKPH 12 +#define PH_ARG_UNINSTALLKPH 13 +#define PH_ARG_DEBUG 14 +#define PH_ARG_HWND 15 +#define PH_ARG_POINT 16 +#define PH_ARG_SHOWOPTIONS 17 +#define PH_ARG_PHSVC 18 +#define PH_ARG_NOPLUGINS 19 +#define PH_ARG_NEWINSTANCE 20 +#define PH_ARG_ELEVATE 21 +#define PH_ARG_SILENT 22 +#define PH_ARG_HELP 23 +#define PH_ARG_SELECTPID 24 +#define PH_ARG_PRIORITY 25 +#define PH_ARG_PLUGIN 26 +#define PH_ARG_SELECTTAB 27 +#define PH_ARG_SYSINFO 28 + +BOOLEAN NTAPI PhpCommandLineOptionCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + ULONG64 integer; + + if (Option) + { + switch (Option->Id) + { + case PH_ARG_SETTINGS: + PhSwapReference(&PhStartupParameters.SettingsFileName, Value); + break; + case PH_ARG_NOSETTINGS: + PhStartupParameters.NoSettings = TRUE; + break; + case PH_ARG_SHOWVISIBLE: + PhStartupParameters.ShowVisible = TRUE; + break; + case PH_ARG_SHOWHIDDEN: + PhStartupParameters.ShowHidden = TRUE; + break; + case PH_ARG_COMMANDMODE: + PhStartupParameters.CommandMode = TRUE; + break; + case PH_ARG_COMMANDTYPE: + PhSwapReference(&PhStartupParameters.CommandType, Value); + break; + case PH_ARG_COMMANDOBJECT: + PhSwapReference(&PhStartupParameters.CommandObject, Value); + break; + case PH_ARG_COMMANDACTION: + PhSwapReference(&PhStartupParameters.CommandAction, Value); + break; + case PH_ARG_COMMANDVALUE: + PhSwapReference(&PhStartupParameters.CommandValue, Value); + break; + case PH_ARG_RUNASSERVICEMODE: + PhSwapReference(&PhStartupParameters.RunAsServiceMode, Value); + break; + case PH_ARG_NOKPH: + PhStartupParameters.NoKph = TRUE; + break; + case PH_ARG_INSTALLKPH: + PhStartupParameters.InstallKph = TRUE; + break; + case PH_ARG_UNINSTALLKPH: + PhStartupParameters.UninstallKph = TRUE; + break; + case PH_ARG_DEBUG: + PhStartupParameters.Debug = TRUE; + break; + case PH_ARG_HWND: + if (PhStringToInteger64(&Value->sr, 16, &integer)) + PhStartupParameters.WindowHandle = (HWND)(ULONG_PTR)integer; + break; + case PH_ARG_POINT: + { + PH_STRINGREF xString; + PH_STRINGREF yString; + + if (PhSplitStringRefAtChar(&Value->sr, ',', &xString, &yString)) + { + LONG64 x; + LONG64 y; + + if (PhStringToInteger64(&xString, 10, &x) && PhStringToInteger64(&yString, 10, &y)) + { + PhStartupParameters.Point.x = (LONG)x; + PhStartupParameters.Point.y = (LONG)y; + } + } + } + break; + case PH_ARG_SHOWOPTIONS: + PhStartupParameters.ShowOptions = TRUE; + break; + case PH_ARG_PHSVC: + PhStartupParameters.PhSvc = TRUE; + break; + case PH_ARG_NOPLUGINS: + PhStartupParameters.NoPlugins = TRUE; + break; + case PH_ARG_NEWINSTANCE: + PhStartupParameters.NewInstance = TRUE; + break; + case PH_ARG_ELEVATE: + PhStartupParameters.Elevate = TRUE; + break; + case PH_ARG_SILENT: + PhStartupParameters.Silent = TRUE; + break; + case PH_ARG_HELP: + PhStartupParameters.Help = TRUE; + break; + case PH_ARG_SELECTPID: + if (PhStringToInteger64(&Value->sr, 0, &integer)) + PhStartupParameters.SelectPid = (ULONG)integer; + break; + case PH_ARG_PRIORITY: + if (PhEqualString2(Value, L"r", TRUE)) + PhStartupParameters.PriorityClass = PROCESS_PRIORITY_CLASS_REALTIME; + else if (PhEqualString2(Value, L"h", TRUE)) + PhStartupParameters.PriorityClass = PROCESS_PRIORITY_CLASS_HIGH; + else if (PhEqualString2(Value, L"n", TRUE)) + PhStartupParameters.PriorityClass = PROCESS_PRIORITY_CLASS_NORMAL; + else if (PhEqualString2(Value, L"l", TRUE)) + PhStartupParameters.PriorityClass = PROCESS_PRIORITY_CLASS_IDLE; + break; + case PH_ARG_PLUGIN: + if (!PhStartupParameters.PluginParameters) + PhStartupParameters.PluginParameters = PhCreateList(3); + PhAddItemList(PhStartupParameters.PluginParameters, PhReferenceObject(Value)); + break; + case PH_ARG_SELECTTAB: + PhSwapReference(&PhStartupParameters.SelectTab, Value); + break; + case PH_ARG_SYSINFO: + PhSwapReference(&PhStartupParameters.SysInfo, Value ? Value : PhReferenceEmptyString()); + break; + } + } + else + { + PPH_STRING upperValue; + + upperValue = PhDuplicateString(Value); + _wcsupr(upperValue->Buffer); + + if (PhFindStringInString(upperValue, 0, L"TASKMGR.EXE") != -1) + { + // User probably has Process Hacker replacing Task Manager. Force + // the main window to start visible. + PhStartupParameters.ShowVisible = TRUE; + } + + PhDereferenceObject(upperValue); + } + + return TRUE; +} + +VOID PhpProcessStartupParameters( + VOID + ) +{ + static PH_COMMAND_LINE_OPTION options[] = + { + { PH_ARG_SETTINGS, L"settings", MandatoryArgumentType }, + { PH_ARG_NOSETTINGS, L"nosettings", NoArgumentType }, + { PH_ARG_SHOWVISIBLE, L"v", NoArgumentType }, + { PH_ARG_SHOWHIDDEN, L"hide", NoArgumentType }, + { PH_ARG_COMMANDMODE, L"c", NoArgumentType }, + { PH_ARG_COMMANDTYPE, L"ctype", MandatoryArgumentType }, + { PH_ARG_COMMANDOBJECT, L"cobject", MandatoryArgumentType }, + { PH_ARG_COMMANDACTION, L"caction", MandatoryArgumentType }, + { PH_ARG_COMMANDVALUE, L"cvalue", MandatoryArgumentType }, + { PH_ARG_RUNASSERVICEMODE, L"ras", MandatoryArgumentType }, + { PH_ARG_NOKPH, L"nokph", NoArgumentType }, + { PH_ARG_INSTALLKPH, L"installkph", NoArgumentType }, + { PH_ARG_UNINSTALLKPH, L"uninstallkph", NoArgumentType }, + { PH_ARG_DEBUG, L"debug", NoArgumentType }, + { PH_ARG_HWND, L"hwnd", MandatoryArgumentType }, + { PH_ARG_POINT, L"point", MandatoryArgumentType }, + { PH_ARG_SHOWOPTIONS, L"showoptions", NoArgumentType }, + { PH_ARG_PHSVC, L"phsvc", NoArgumentType }, + { PH_ARG_NOPLUGINS, L"noplugins", NoArgumentType }, + { PH_ARG_NEWINSTANCE, L"newinstance", NoArgumentType }, + { PH_ARG_ELEVATE, L"elevate", NoArgumentType }, + { PH_ARG_SILENT, L"s", NoArgumentType }, + { PH_ARG_HELP, L"help", NoArgumentType }, + { PH_ARG_SELECTPID, L"selectpid", MandatoryArgumentType }, + { PH_ARG_PRIORITY, L"priority", MandatoryArgumentType }, + { PH_ARG_PLUGIN, L"plugin", MandatoryArgumentType }, + { PH_ARG_SELECTTAB, L"selecttab", MandatoryArgumentType }, + { PH_ARG_SYSINFO, L"sysinfo", OptionalArgumentType } + }; + PH_STRINGREF commandLine; + + PhUnicodeStringToStringRef(&NtCurrentPeb()->ProcessParameters->CommandLine, &commandLine); + + memset(&PhStartupParameters, 0, sizeof(PH_STARTUP_PARAMETERS)); + + if (!PhParseCommandLine( + &commandLine, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS | PH_COMMAND_LINE_IGNORE_FIRST_PART, + PhpCommandLineOptionCallback, + NULL + ) || PhStartupParameters.Help) + { + PhShowInformation( + NULL, + L"Command line options:\n\n" + L"-c\n" + L"-ctype command-type\n" + L"-cobject command-object\n" + L"-caction command-action\n" + L"-cvalue command-value\n" + L"-debug\n" + L"-elevate\n" + L"-help\n" + L"-hide\n" + L"-installkph\n" + L"-newinstance\n" + L"-nokph\n" + L"-noplugins\n" + L"-nosettings\n" + L"-plugin pluginname:value\n" + L"-priority r|h|n|l\n" + L"-s\n" + L"-selectpid pid-to-select\n" + L"-selecttab name-of-tab-to-select\n" + L"-settings filename\n" + L"-sysinfo [section-name]\n" + L"-uninstallkph\n" + L"-v\n" + ); + + if (PhStartupParameters.Help) + RtlExitUserProcess(STATUS_SUCCESS); + } + + if (PhStartupParameters.InstallKph) + { + NTSTATUS status; + PPH_STRING kprocesshackerFileName; + KPH_PARAMETERS parameters; + + kprocesshackerFileName = PhConcatStrings2(PhApplicationDirectory->Buffer, L"\\kprocesshacker.sys"); + + parameters.SecurityLevel = KphSecuritySignatureCheck; + parameters.CreateDynamicConfiguration = TRUE; + + status = KphInstallEx(KPH_DEVICE_SHORT_NAME, kprocesshackerFileName->Buffer, ¶meters); + + if (!NT_SUCCESS(status) && !PhStartupParameters.Silent) + PhShowStatus(NULL, L"Unable to install KProcessHacker", status, 0); + + RtlExitUserProcess(status); + } + + if (PhStartupParameters.UninstallKph) + { + NTSTATUS status; + + status = KphUninstall(KPH_DEVICE_SHORT_NAME); + + if (!NT_SUCCESS(status) && !PhStartupParameters.Silent) + PhShowStatus(NULL, L"Unable to uninstall KProcessHacker", status, 0); + + RtlExitUserProcess(status); + } + + if (PhStartupParameters.Elevate && !PhGetOwnTokenAttributes().Elevated) + { + PhShellProcessHacker( + NULL, + NULL, + SW_SHOW, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_FORCE_SETTINGS, + 0, + NULL + ); + RtlExitUserProcess(STATUS_SUCCESS); + } + + if (PhStartupParameters.Debug) + { + // The symbol provider won't work if this is chosen. + PhShowDebugConsole(); + } +} + +VOID PhpEnablePrivileges( + VOID + ) +{ + HANDLE tokenHandle; + + if (NT_SUCCESS(NtOpenProcessToken( + NtCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES, + &tokenHandle + ))) + { + CHAR privilegesBuffer[FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES) * 8]; + PTOKEN_PRIVILEGES privileges; + ULONG i; + + privileges = (PTOKEN_PRIVILEGES)privilegesBuffer; + privileges->PrivilegeCount = 8; + + for (i = 0; i < privileges->PrivilegeCount; i++) + { + privileges->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED; + privileges->Privileges[i].Luid.HighPart = 0; + } + + privileges->Privileges[0].Luid.LowPart = SE_DEBUG_PRIVILEGE; + privileges->Privileges[1].Luid.LowPart = SE_INC_BASE_PRIORITY_PRIVILEGE; + privileges->Privileges[2].Luid.LowPart = SE_INC_WORKING_SET_PRIVILEGE; + privileges->Privileges[3].Luid.LowPart = SE_LOAD_DRIVER_PRIVILEGE; + privileges->Privileges[4].Luid.LowPart = SE_PROF_SINGLE_PROCESS_PRIVILEGE; + privileges->Privileges[5].Luid.LowPart = SE_RESTORE_PRIVILEGE; + privileges->Privileges[6].Luid.LowPart = SE_SHUTDOWN_PRIVILEGE; + privileges->Privileges[7].Luid.LowPart = SE_TAKE_OWNERSHIP_PRIVILEGE; + + NtAdjustPrivilegesToken( + tokenHandle, + FALSE, + privileges, + 0, + NULL, + NULL + ); + + NtClose(tokenHandle); + } +} diff --git a/ProcessHacker/mainwnd.c b/ProcessHacker/mainwnd.c new file mode 100644 index 0000000..fff79eb --- /dev/null +++ b/ProcessHacker/mainwnd.c @@ -0,0 +1,5313 @@ +/* + * Process Hacker - + * main window + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RUNAS_MODE_ADMIN 1 +#define RUNAS_MODE_LIMITED 2 + +PHAPPAPI HWND PhMainWndHandle; +BOOLEAN PhMainWndExiting = FALSE; +HMENU PhMainWndMenuHandle; + +static BOOLEAN NeedsMaximize = FALSE; +static ULONG NeedsSelectPid = 0; +static BOOLEAN AlwaysOnTop = FALSE; + +static BOOLEAN DelayedLoadCompleted = FALSE; +static ULONG NotifyIconNotifyMask; + +static PH_CALLBACK_DECLARE(LayoutPaddingCallback); +static RECT LayoutPadding = { 0, 0, 0, 0 }; +static BOOLEAN LayoutPaddingValid = TRUE; +static HWND TabControlHandle; +static INT ProcessesTabIndex; +static INT ServicesTabIndex; +static INT NetworkTabIndex; +static INT MaxTabIndex; +static INT OldTabIndex; +static PPH_LIST AdditionalTabPageList = NULL; +static HWND ProcessTreeListHandle; +static HWND ServiceTreeListHandle; +static HWND NetworkTreeListHandle; +static HFONT CurrentCustomFont; + +static BOOLEAN NetworkFirstTime = TRUE; +static BOOLEAN ServiceTreeListLoaded = FALSE; +static BOOLEAN NetworkTreeListLoaded = FALSE; +static HMENU SubMenuHandles[5]; +static PPH_EMENU SubMenuObjects[5]; +static PPH_LIST LegacyAddMenuItemList; +static BOOLEAN UsersMenuInitialized = FALSE; +static BOOLEAN UpdateAutomatically = TRUE; + +static PH_CALLBACK_REGISTRATION SymInitRegistration; + +static PH_PROVIDER_REGISTRATION ProcessProviderRegistration; +static PH_CALLBACK_REGISTRATION ProcessAddedRegistration; +static PH_CALLBACK_REGISTRATION ProcessModifiedRegistration; +static PH_CALLBACK_REGISTRATION ProcessRemovedRegistration; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; +static BOOLEAN ProcessesNeedsRedraw = FALSE; +static PPH_PROCESS_NODE ProcessToScrollTo = NULL; + +static PH_PROVIDER_REGISTRATION ServiceProviderRegistration; +static PH_CALLBACK_REGISTRATION ServiceAddedRegistration; +static PH_CALLBACK_REGISTRATION ServiceModifiedRegistration; +static PH_CALLBACK_REGISTRATION ServiceRemovedRegistration; +static PH_CALLBACK_REGISTRATION ServicesUpdatedRegistration; +static PPH_POINTER_LIST ServicesPendingList; +static BOOLEAN ServicesNeedsRedraw = FALSE; + +static PH_PROVIDER_REGISTRATION NetworkProviderRegistration; +static PH_CALLBACK_REGISTRATION NetworkItemAddedRegistration; +static PH_CALLBACK_REGISTRATION NetworkItemModifiedRegistration; +static PH_CALLBACK_REGISTRATION NetworkItemRemovedRegistration; +static PH_CALLBACK_REGISTRATION NetworkItemsUpdatedRegistration; +static BOOLEAN NetworkNeedsRedraw = FALSE; + +static ULONG SelectedRunAsMode; +static HWND SelectedProcessWindowHandle; +static BOOLEAN SelectedProcessVirtualizationEnabled; +static ULONG SelectedUserSessionId; + +static PPH_TN_FILTER_ENTRY CurrentUserFilterEntry = NULL; +static PPH_TN_FILTER_ENTRY SignedFilterEntry = NULL; +//static PPH_TN_FILTER_ENTRY CurrentUserNetworkFilterEntry = NULL; +//static PPH_TN_FILTER_ENTRY SignedNetworkFilterEntry = NULL; +static PPH_TN_FILTER_ENTRY DriverFilterEntry = NULL; + +static ULONG LastNotificationType; +static union +{ + HANDLE ProcessId; + PPH_STRING ServiceName; +} LastNotificationDetails; + +BOOLEAN PhMainWndInitialization( + _In_ INT ShowCommand + ) +{ + PH_STRING_BUILDER stringBuilder; + PH_RECTANGLE windowRectangle; + + if (PhGetIntegerSetting(L"FirstRun")) + { + PPH_STRING autoDbghelpPath; + + // Try to set up the dbghelp path automatically if this is the first run. + if (autoDbghelpPath = PH_AUTO(PhMwpFindDbghelpPath())) + PhSetStringSetting2(L"DbgHelpPath", &autoDbghelpPath->sr); + + PhSetIntegerSetting(L"FirstRun", FALSE); + } + + // This was added to be able to delay-load dbghelp.dll and symsrv.dll. + PhRegisterCallback(&PhSymInitCallback, PhMwpSymInitHandler, NULL, &SymInitRegistration); + + PhMwpInitializeProviders(); + + if (!PhMwpInitializeWindowClass()) + return FALSE; + + windowRectangle.Position = PhGetIntegerPairSetting(L"MainWindowPosition"); + windowRectangle.Size = PhGetScalableIntegerPairSetting(L"MainWindowSize", TRUE).Pair; + + // Create the window title. + + PhInitializeStringBuilder(&stringBuilder, 100); + PhAppendStringBuilder2(&stringBuilder, L"Process Hacker"); + + if (PhCurrentUserName) + { + PhAppendStringBuilder2(&stringBuilder, L" ["); + PhAppendStringBuilder(&stringBuilder, &PhCurrentUserName->sr); + PhAppendCharStringBuilder(&stringBuilder, ']'); + if (KphIsConnected()) PhAppendCharStringBuilder(&stringBuilder, '+'); + } + + if (WINDOWS_HAS_UAC && PhGetOwnTokenAttributes().ElevationType == TokenElevationTypeFull) + PhAppendStringBuilder2(&stringBuilder, L" (Administrator)"); + + // Create the window. + + PhMainWndHandle = CreateWindow( + PH_MAINWND_CLASSNAME, + stringBuilder.String->Buffer, + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, + windowRectangle.Left, + windowRectangle.Top, + windowRectangle.Width, + windowRectangle.Height, + NULL, + NULL, + PhInstanceHandle, + NULL + ); + PhDeleteStringBuilder(&stringBuilder); + PhMainWndMenuHandle = GetMenu(PhMainWndHandle); + + if (!PhMainWndHandle) + return FALSE; + + PhMwpInitializeMainMenu(PhMainWndMenuHandle); + + // Choose a more appropriate rectangle for the window. + PhAdjustRectangleToWorkingArea(PhMainWndHandle, &windowRectangle); + MoveWindow(PhMainWndHandle, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + + // Allow WM_PH_ACTIVATE to pass through UIPI. + if (WINDOWS_HAS_UAC) + ChangeWindowMessageFilter_I(WM_PH_ACTIVATE, MSGFLT_ADD); + + PhMwpOnSettingChange(); + + // Initialize child controls. + PhMwpInitializeControls(); + + PhMwpLoadSettings(); + PhLogInitialization(); + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhMwpDelayedLoadFunction, NULL); + + PhMwpSelectionChangedTabControl(-1); + + // Perform a layout. + PhMwpOnSize(); + + PhStartProviderThread(&PhPrimaryProviderThread); + PhStartProviderThread(&PhSecondaryProviderThread); + + // See PhMwpOnTimer for more details. + if (PhCsUpdateInterval > PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_1) + SetTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA, PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_1, NULL); + + UpdateWindow(PhMainWndHandle); + + if ((PhStartupParameters.ShowHidden || PhGetIntegerSetting(L"StartHidden")) && PhNfTestIconMask(PH_ICON_ALL)) + ShowCommand = SW_HIDE; + if (PhStartupParameters.ShowVisible) + ShowCommand = SW_SHOW; + + if (PhGetIntegerSetting(L"MainWindowState") == SW_MAXIMIZE) + { + if (ShowCommand != SW_HIDE) + { + ShowCommand = SW_MAXIMIZE; + } + else + { + // We can't maximize it while having it hidden. Set it as pending. + NeedsMaximize = TRUE; + } + } + + NeedsSelectPid = PhStartupParameters.SelectPid; + + if (PhPluginsEnabled) + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMainWindowShowing), IntToPtr(ShowCommand)); + + if (PhStartupParameters.SelectTab) + { + INT tabIndex = PhMwpFindTabPageIndex(PhStartupParameters.SelectTab->Buffer); + + if (tabIndex != -1) + PhMwpSelectTabPage(tabIndex); + } + + if (PhStartupParameters.SysInfo) + PhShowSystemInformationDialog(PhStartupParameters.SysInfo->Buffer); + + if (ShowCommand != SW_HIDE) + ShowWindow(PhMainWndHandle, ShowCommand); + + if (PhGetIntegerSetting(L"MiniInfoWindowPinned")) + PhPinMiniInformation(MiniInfoManualPinType, 1, 0, PH_MINIINFO_LOAD_POSITION, NULL, NULL); + + return TRUE; +} + +LRESULT CALLBACK PhMwpWndProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_DESTROY: + { + PhMwpOnDestroy(); + } + break; + case WM_ENDSESSION: + { + PhMwpOnEndSession(); + } + break; + case WM_SETTINGCHANGE: + { + PhMwpOnSettingChange(); + } + break; + case WM_COMMAND: + { + PhMwpOnCommand(LOWORD(wParam)); + } + break; + case WM_SHOWWINDOW: + { + PhMwpOnShowWindow(!!wParam, (ULONG)lParam); + } + break; + case WM_SYSCOMMAND: + { + if (PhMwpOnSysCommand((ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) + return 0; + } + break; + case WM_MENUCOMMAND: + { + PhMwpOnMenuCommand((ULONG)wParam, (HMENU)lParam); + } + break; + case WM_INITMENUPOPUP: + { + PhMwpOnInitMenuPopup((HMENU)wParam, LOWORD(lParam), !!HIWORD(lParam)); + } + break; + case WM_SIZE: + { + PhMwpOnSize(); + } + break; + case WM_SIZING: + { + PhMwpOnSizing((ULONG)wParam, (PRECT)lParam); + } + break; + case WM_SETFOCUS: + { + PhMwpOnSetFocus(); + } + break; + case WM_TIMER: + { + PhMwpOnTimer((ULONG)wParam); + } + break; + case WM_NOTIFY: + { + LRESULT result; + + if (PhMwpOnNotify((NMHDR *)lParam, &result)) + return result; + } + break; + case WM_WTSSESSION_CHANGE: + { + PhMwpOnWtsSessionChange((ULONG)wParam, (ULONG)lParam); + } + break; + } + + if (uMsg >= WM_PH_FIRST && uMsg <= WM_PH_LAST) + { + return PhMwpOnUserMessage(uMsg, wParam, lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +BOOLEAN PhMwpInitializeWindowClass( + VOID + ) +{ + WNDCLASSEX wcex; + + memset(&wcex, 0, sizeof(WNDCLASSEX)); + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = 0; + wcex.lpfnWndProc = PhMwpWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = PhInstanceHandle; + wcex.hIcon = LoadIcon(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + //wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MAINWND); + wcex.lpszClassName = PH_MAINWND_CLASSNAME; + wcex.hIconSm = (HICON)LoadImage(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER), IMAGE_ICON, 16, 16, 0); + + if (!RegisterClassEx(&wcex)) + return FALSE; + + return TRUE; +} + +VOID PhMwpInitializeProviders( + VOID + ) +{ + ULONG interval; + + interval = PhGetIntegerSetting(L"UpdateInterval"); + + if (interval == 0) + { + interval = 1000; + PH_SET_INTEGER_CACHED_SETTING(UpdateInterval, interval); + } + + PhInitializeProviderThread(&PhPrimaryProviderThread, interval); + PhInitializeProviderThread(&PhSecondaryProviderThread, interval); + + PhRegisterProvider(&PhPrimaryProviderThread, PhProcessProviderUpdate, NULL, &ProcessProviderRegistration); + PhSetEnabledProvider(&ProcessProviderRegistration, TRUE); + PhRegisterProvider(&PhPrimaryProviderThread, PhServiceProviderUpdate, NULL, &ServiceProviderRegistration); + PhSetEnabledProvider(&ServiceProviderRegistration, TRUE); + PhRegisterProvider(&PhPrimaryProviderThread, PhNetworkProviderUpdate, NULL, &NetworkProviderRegistration); +} + +VOID PhMwpApplyUpdateInterval( + _In_ ULONG Interval + ) +{ + PhSetIntervalProviderThread(&PhPrimaryProviderThread, Interval); + PhSetIntervalProviderThread(&PhSecondaryProviderThread, Interval); + + if (Interval > PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_LONG_TERM) + SetTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA, PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_LONG_TERM, NULL); + else + KillTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA); // Might not exist +} + +VOID PhMwpInitializeControls( + VOID + ) +{ + ULONG thinRows; + + TabControlHandle = CreateWindow( + WC_TABCONTROL, + NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | TCS_MULTILINE, + 0, + 0, + 3, + 3, + PhMainWndHandle, + NULL, + PhInstanceHandle, + NULL + ); + SendMessage(TabControlHandle, WM_SETFONT, (WPARAM)PhApplicationFont, FALSE); + BringWindowToTop(TabControlHandle); + ProcessesTabIndex = PhAddTabControlTab(TabControlHandle, 0, L"Processes"); + ServicesTabIndex = PhAddTabControlTab(TabControlHandle, 1, L"Services"); + NetworkTabIndex = PhAddTabControlTab(TabControlHandle, 2, L"Network"); + MaxTabIndex = NetworkTabIndex; + + thinRows = PhGetIntegerSetting(L"ThinRows") ? TN_STYLE_THIN_ROWS : 0; + + ProcessTreeListHandle = CreateWindow( + PH_TREENEW_CLASSNAME, + NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_BORDER | TN_STYLE_ICONS | TN_STYLE_DOUBLE_BUFFERED | TN_STYLE_ANIMATE_DIVIDER | thinRows, + 0, + 0, + 3, + 3, + PhMainWndHandle, + (HMENU)ID_MAINWND_PROCESSTL, + PhLibImageBase, + NULL + ); + BringWindowToTop(ProcessTreeListHandle); + + ServiceTreeListHandle = CreateWindow( + PH_TREENEW_CLASSNAME, + NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_BORDER | TN_STYLE_ICONS | TN_STYLE_DOUBLE_BUFFERED | thinRows, + 0, + 0, + 3, + 3, + PhMainWndHandle, + (HMENU)ID_MAINWND_SERVICETL, + PhLibImageBase, + NULL + ); + BringWindowToTop(ServiceTreeListHandle); + + NetworkTreeListHandle = CreateWindow( + PH_TREENEW_CLASSNAME, + NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_BORDER | TN_STYLE_ICONS | TN_STYLE_DOUBLE_BUFFERED | thinRows, + 0, + 0, + 3, + 3, + PhMainWndHandle, + (HMENU)ID_MAINWND_NETWORKTL, + PhLibImageBase, + NULL + ); + BringWindowToTop(NetworkTreeListHandle); + + PhProcessTreeListInitialization(); + PhInitializeProcessTreeList(ProcessTreeListHandle); + + PhServiceTreeListInitialization(); + PhInitializeServiceTreeList(ServiceTreeListHandle); + + PhNetworkTreeListInitialization(); + PhInitializeNetworkTreeList(NetworkTreeListHandle); + + PhRegisterCallback( + &PhProcessAddedEvent, + PhMwpProcessAddedHandler, + NULL, + &ProcessAddedRegistration + ); + PhRegisterCallback( + &PhProcessModifiedEvent, + PhMwpProcessModifiedHandler, + NULL, + &ProcessModifiedRegistration + ); + PhRegisterCallback( + &PhProcessRemovedEvent, + PhMwpProcessRemovedHandler, + NULL, + &ProcessRemovedRegistration + ); + PhRegisterCallback( + &PhProcessesUpdatedEvent, + PhMwpProcessesUpdatedHandler, + NULL, + &ProcessesUpdatedRegistration + ); + + PhRegisterCallback( + &PhServiceAddedEvent, + PhMwpServiceAddedHandler, + NULL, + &ServiceAddedRegistration + ); + PhRegisterCallback( + &PhServiceModifiedEvent, + PhMwpServiceModifiedHandler, + NULL, + &ServiceModifiedRegistration + ); + PhRegisterCallback( + &PhServiceRemovedEvent, + PhMwpServiceRemovedHandler, + NULL, + &ServiceRemovedRegistration + ); + PhRegisterCallback( + &PhServicesUpdatedEvent, + PhMwpServicesUpdatedHandler, + NULL, + &ServicesUpdatedRegistration + ); + + PhRegisterCallback( + &PhNetworkItemAddedEvent, + PhMwpNetworkItemAddedHandler, + NULL, + &NetworkItemAddedRegistration + ); + PhRegisterCallback( + &PhNetworkItemModifiedEvent, + PhMwpNetworkItemModifiedHandler, + NULL, + &NetworkItemModifiedRegistration + ); + PhRegisterCallback( + &PhNetworkItemRemovedEvent, + PhMwpNetworkItemRemovedHandler, + NULL, + &NetworkItemRemovedRegistration + ); + PhRegisterCallback( + &PhNetworkItemsUpdatedEvent, + PhMwpNetworkItemsUpdatedHandler, + NULL, + &NetworkItemsUpdatedRegistration + ); +} + +NTSTATUS PhMwpDelayedLoadFunction( + _In_ PVOID Parameter + ) +{ + // Register for window station notifications. + WinStationRegisterConsoleNotification(NULL, PhMainWndHandle, WNOTIFY_ALL_SESSIONS); + + PhNfLoadStage2(); + + // Make sure we get closed late in the shutdown process. + SetProcessShutdownParameters(0x100, 0); + + DelayedLoadCompleted = TRUE; + //PostMessage(PhMainWndHandle, WM_PH_DELAYED_LOAD_COMPLETED, 0, 0); + + return STATUS_SUCCESS; +} + +PPH_STRING PhMwpFindDbghelpPath( + VOID + ) +{ + static struct + { + ULONG Folder; + PWSTR AppendPath; + } locations[] = + { +#ifdef _WIN64 + { CSIDL_PROGRAM_FILESX86, L"\\Windows Kits\\10\\Debuggers\\x64\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILESX86, L"\\Windows Kits\\8.1\\Debuggers\\x64\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILESX86, L"\\Windows Kits\\8.0\\Debuggers\\x64\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILES, L"\\Debugging Tools for Windows (x64)\\dbghelp.dll" } +#else + { CSIDL_PROGRAM_FILES, L"\\Windows Kits\\10\\Debuggers\\x86\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILES, L"\\Windows Kits\\8.1\\Debuggers\\x86\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILES, L"\\Windows Kits\\8.0\\Debuggers\\x86\\dbghelp.dll" }, + { CSIDL_PROGRAM_FILES, L"\\Debugging Tools for Windows (x86)\\dbghelp.dll" } +#endif + }; + + PPH_STRING path; + ULONG i; + + for (i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) + { + path = PhGetKnownLocation(locations[i].Folder, locations[i].AppendPath); + + if (path) + { + if (RtlDoesFileExists_U(path->Buffer)) + return path; + + PhDereferenceObject(path); + } + } + + return NULL; +} + +VOID PhMwpOnDestroy( + VOID + ) +{ + // Notify plugins that we are shutting down. + + if (PhPluginsEnabled) + PhUnloadPlugins(); + + if (!PhMainWndExiting) + ProcessHacker_SaveAllSettings(PhMainWndHandle); + + PhNfUninitialization(); + + PostQuitMessage(0); +} + +VOID PhMwpOnEndSession( + VOID + ) +{ + PhMwpOnDestroy(); +} + +VOID PhMwpOnSettingChange( + VOID + ) +{ + if (PhApplicationFont) + DeleteObject(PhApplicationFont); + + PhInitializeFont(PhMainWndHandle); + + SendMessage(TabControlHandle, WM_SETFONT, (WPARAM)PhApplicationFont, FALSE); +} + +VOID PhMwpOnCommand( + _In_ ULONG Id + ) +{ + switch (Id) + { + case ID_ESC_EXIT: + { + if (PhGetIntegerSetting(L"HideOnClose")) + { + if (PhNfTestIconMask(PH_ICON_ALL)) + ShowWindow(PhMainWndHandle, SW_HIDE); + } + else if (PhGetIntegerSetting(L"CloseOnEscape")) + { + ProcessHacker_Destroy(PhMainWndHandle); + } + } + break; + case ID_HACKER_RUN: + { + if (RunFileDlg) + { + SelectedRunAsMode = 0; + RunFileDlg(PhMainWndHandle, NULL, NULL, NULL, NULL, 0); + } + } + break; + case ID_HACKER_RUNASADMINISTRATOR: + { + if (RunFileDlg) + { + SelectedRunAsMode = RUNAS_MODE_ADMIN; + RunFileDlg( + PhMainWndHandle, + NULL, + NULL, + NULL, + L"Type the name of a program that will be opened under alternate credentials.", + 0 + ); + } + } + break; + case ID_HACKER_RUNASLIMITEDUSER: + { + if (RunFileDlg) + { + SelectedRunAsMode = RUNAS_MODE_LIMITED; + RunFileDlg( + PhMainWndHandle, + NULL, + NULL, + NULL, + L"Type the name of a program that will be opened under standard user privileges.", + 0 + ); + } + } + break; + case ID_HACKER_RUNAS: + { + PhShowRunAsDialog(PhMainWndHandle, NULL); + } + break; + case ID_HACKER_SHOWDETAILSFORALLPROCESSES: + { + ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); + + if (PhShellProcessHacker( + PhMainWndHandle, + L"-v", + SW_SHOW, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY, + 0, + NULL + )) + { + ProcessHacker_Destroy(PhMainWndHandle); + } + else + { + ProcessHacker_CancelEarlyShutdown(PhMainWndHandle); + } + } + break; + case ID_HACKER_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Text files (*.txt;*.log)", L"*.txt;*.log" }, + { L"Comma-separated values (*.csv)", L"*.csv" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog = PhCreateSaveFileDialog(); + ULONG selectedTab = TabCtrl_GetCurSel(TabControlHandle); + PWSTR tabText = L"Output"; + PPH_ADDITIONAL_TAB_PAGE selectedTabPage = NULL; + + if (selectedTab == ProcessesTabIndex) + { + tabText = L"Processes"; + } + else if (selectedTab == ServicesTabIndex) + { + tabText = L"Services"; + } + else if (selectedTab == NetworkTabIndex) + { + tabText = L"Network"; + } + else if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (tabPage->Index == selectedTab) + { + selectedTabPage = tabPage; + tabText = selectedTabPage->Text; + break; + } + } + } + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, PhaFormatString(L"Process Hacker %s.txt", tabText)->Buffer); + + if (PhShowFileDialog(PhMainWndHandle, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + ULONG filterIndex; + PPH_FILE_STREAM fileStream; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + filterIndex = PhGetFileDialogFilterIndex(fileDialog); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + ULONG mode; + + if (filterIndex == 2) + mode = PH_EXPORT_MODE_CSV; + else + mode = PH_EXPORT_MODE_TABS; + + PhWriteStringAsUtf8FileStream(fileStream, &PhUnicodeByteOrderMark); + PhWritePhTextHeader(fileStream); + + if (selectedTab == ProcessesTabIndex) + { + PhWriteProcessTree(fileStream, mode); + } + else if (selectedTab == ServicesTabIndex) + { + PhWriteServiceList(fileStream, mode); + } + else if (selectedTab == NetworkTabIndex) + { + PhWriteNetworkList(fileStream, mode); + } + else if (selectedTabPage) + { + if (selectedTabPage->SaveContentCallback) + { + selectedTabPage->SaveContentCallback(fileStream, UlongToPtr(mode), NULL, selectedTabPage->Context); + } + } + + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(PhMainWndHandle, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + case ID_HACKER_FINDHANDLESORDLLS: + { + PhShowFindObjectsDialog(); + } + break; + case ID_HACKER_OPTIONS: + { + PhShowOptionsDialog(PhMainWndHandle); + } + break; + case ID_HACKER_PLUGINS: + { + PhShowPluginsDialog(PhMainWndHandle); + } + break; + case ID_COMPUTER_LOCK: + case ID_COMPUTER_LOGOFF: + case ID_COMPUTER_SLEEP: + case ID_COMPUTER_HIBERNATE: + case ID_COMPUTER_RESTART: + case ID_COMPUTER_RESTARTBOOTOPTIONS: + case ID_COMPUTER_SHUTDOWN: + case ID_COMPUTER_SHUTDOWNHYBRID: + PhMwpExecuteComputerCommand(Id); + break; + case ID_HACKER_EXIT: + ProcessHacker_Destroy(PhMainWndHandle); + break; + case ID_VIEW_SYSTEMINFORMATION: + PhShowSystemInformationDialog(NULL); + break; + case ID_TRAYICONS_CPUHISTORY: + case ID_TRAYICONS_CPUUSAGE: + case ID_TRAYICONS_IOHISTORY: + case ID_TRAYICONS_COMMITHISTORY: + case ID_TRAYICONS_PHYSICALMEMORYHISTORY: + { + ULONG i; + + switch (Id) + { + case ID_TRAYICONS_CPUHISTORY: + i = PH_ICON_CPU_HISTORY; + break; + case ID_TRAYICONS_CPUUSAGE: + i = PH_ICON_CPU_USAGE; + break; + case ID_TRAYICONS_IOHISTORY: + i = PH_ICON_IO_HISTORY; + break; + case ID_TRAYICONS_COMMITHISTORY: + i = PH_ICON_COMMIT_HISTORY; + break; + case ID_TRAYICONS_PHYSICALMEMORYHISTORY: + i = PH_ICON_PHYSICAL_HISTORY; + break; + } + + PhNfSetVisibleIcon(i, !PhNfTestIconMask(i)); + } + break; + case ID_VIEW_HIDEPROCESSESFROMOTHERUSERS: + { + if (!CurrentUserFilterEntry) + { + CurrentUserFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportProcessTreeList(), PhMwpCurrentUserProcessTreeFilter, NULL); + } + else + { + PhRemoveTreeNewFilter(PhGetFilterSupportProcessTreeList(), CurrentUserFilterEntry); + CurrentUserFilterEntry = NULL; + } + + PhApplyTreeNewFilters(PhGetFilterSupportProcessTreeList()); + + PhSetIntegerSetting(L"HideOtherUserProcesses", !!CurrentUserFilterEntry); + } + break; + case ID_VIEW_HIDESIGNEDPROCESSES: + { + if (!SignedFilterEntry) + { + if (!PhEnableProcessQueryStage2) + { + PhShowInformation( + PhMainWndHandle, + L"This filter cannot function because digital signature checking is not enabled. " + L"Enable it in Options > Advanced and restart Process Hacker." + ); + } + + SignedFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportProcessTreeList(), PhMwpSignedProcessTreeFilter, NULL); + } + else + { + PhRemoveTreeNewFilter(PhGetFilterSupportProcessTreeList(), SignedFilterEntry); + SignedFilterEntry = NULL; + } + + PhApplyTreeNewFilters(PhGetFilterSupportProcessTreeList()); + + PhSetIntegerSetting(L"HideSignedProcesses", !!SignedFilterEntry); + } + break; + case ID_VIEW_SCROLLTONEWPROCESSES: + { + PH_SET_INTEGER_CACHED_SETTING(ScrollToNewProcesses, !PhCsScrollToNewProcesses); + } + break; + case ID_VIEW_SHOWCPUBELOW001: + { + PH_SET_INTEGER_CACHED_SETTING(ShowCpuBelow001, !PhCsShowCpuBelow001); + PhInvalidateAllProcessNodes(); + } + break; + case ID_VIEW_HIDEDRIVERSERVICES: + { + if (!DriverFilterEntry) + { + DriverFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportServiceTreeList(), PhMwpDriverServiceTreeFilter, NULL); + } + else + { + PhRemoveTreeNewFilter(PhGetFilterSupportServiceTreeList(), DriverFilterEntry); + DriverFilterEntry = NULL; + } + + PhApplyTreeNewFilters(PhGetFilterSupportServiceTreeList()); + + PhSetIntegerSetting(L"HideDriverServices", !!DriverFilterEntry); + } + break; + case ID_VIEW_ALWAYSONTOP: + { + AlwaysOnTop = !AlwaysOnTop; + SetWindowPos(PhMainWndHandle, AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + PhSetIntegerSetting(L"MainWindowAlwaysOnTop", AlwaysOnTop); + } + break; + case ID_OPACITY_10: + case ID_OPACITY_20: + case ID_OPACITY_30: + case ID_OPACITY_40: + case ID_OPACITY_50: + case ID_OPACITY_60: + case ID_OPACITY_70: + case ID_OPACITY_80: + case ID_OPACITY_90: + case ID_OPACITY_OPAQUE: + { + ULONG opacity; + + opacity = PH_ID_TO_OPACITY(Id); + PhSetIntegerSetting(L"MainWindowOpacity", opacity); + PhSetWindowOpacity(PhMainWndHandle, opacity); + } + break; + case ID_VIEW_REFRESH: + { + PhBoostProvider(&ProcessProviderRegistration, NULL); + PhBoostProvider(&ServiceProviderRegistration, NULL); + } + break; + case ID_UPDATEINTERVAL_FAST: + case ID_UPDATEINTERVAL_NORMAL: + case ID_UPDATEINTERVAL_BELOWNORMAL: + case ID_UPDATEINTERVAL_SLOW: + case ID_UPDATEINTERVAL_VERYSLOW: + { + ULONG interval; + + switch (Id) + { + case ID_UPDATEINTERVAL_FAST: + interval = 500; + break; + case ID_UPDATEINTERVAL_NORMAL: + interval = 1000; + break; + case ID_UPDATEINTERVAL_BELOWNORMAL: + interval = 2000; + break; + case ID_UPDATEINTERVAL_SLOW: + interval = 5000; + break; + case ID_UPDATEINTERVAL_VERYSLOW: + interval = 10000; + break; + } + + PH_SET_INTEGER_CACHED_SETTING(UpdateInterval, interval); + PhMwpApplyUpdateInterval(interval); + } + break; + case ID_VIEW_UPDATEAUTOMATICALLY: + { + UpdateAutomatically = !UpdateAutomatically; + + PhSetEnabledProvider(&ProcessProviderRegistration, UpdateAutomatically); + PhSetEnabledProvider(&ServiceProviderRegistration, UpdateAutomatically); + + if (TabCtrl_GetCurSel(TabControlHandle) == NetworkTabIndex) + PhSetEnabledProvider(&NetworkProviderRegistration, UpdateAutomatically); + } + break; + case ID_TOOLS_CREATESERVICE: + { + PhShowCreateServiceDialog(PhMainWndHandle); + } + break; + case ID_TOOLS_HIDDENPROCESSES: + { + PhShowHiddenProcessesDialog(); + } + break; + case ID_TOOLS_INSPECTEXECUTABLEFILE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Executable files (*.exe;*.dll;*.ocx;*.sys;*.scr;*.cpl)", L"*.exe;*.dll;*.ocx;*.sys;*.scr;*.cpl" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog = PhCreateOpenFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + if (PhShowFileDialog(PhMainWndHandle, fileDialog)) + { + PhShellExecuteUserString( + PhMainWndHandle, + L"ProgramInspectExecutables", + PH_AUTO_T(PH_STRING, PhGetFileDialogFileName(fileDialog))->Buffer, + FALSE, + L"Make sure the PE Viewer executable file is present." + ); + } + + PhFreeFileDialog(fileDialog); + } + break; + case ID_TOOLS_PAGEFILES: + { + PhShowPagefilesDialog(PhMainWndHandle); + } + break; + case ID_TOOLS_STARTTASKMANAGER: + { + PPH_STRING systemDirectory; + PPH_STRING taskmgrFileName; + + systemDirectory = PH_AUTO(PhGetSystemDirectory()); + taskmgrFileName = PH_AUTO(PhConcatStrings2(systemDirectory->Buffer, L"\\taskmgr.exe")); + + if (WindowsVersion >= WINDOWS_8 && !PhGetOwnTokenAttributes().Elevated) + { + if (PhUiConnectToPhSvc(PhMainWndHandle, FALSE)) + { + PhSvcCallCreateProcessIgnoreIfeoDebugger(taskmgrFileName->Buffer); + PhUiDisconnectFromPhSvc(); + } + } + else + { + PhCreateProcessIgnoreIfeoDebugger(taskmgrFileName->Buffer); + } + } + break; + case ID_USER_CONNECT: + { + PhUiConnectSession(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_USER_DISCONNECT: + { + PhUiDisconnectSession(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_USER_LOGOFF: + { + PhUiLogoffSession(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_USER_REMOTECONTROL: + { + PhShowSessionShadowDialog(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_USER_SENDMESSAGE: + { + PhShowSessionSendMessageDialog(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_USER_PROPERTIES: + { + PhShowSessionProperties(PhMainWndHandle, SelectedUserSessionId); + } + break; + case ID_HELP_LOG: + { + PhShowLogDialog(); + } + break; + case ID_HELP_DONATE: + { + PhShellExecute(PhMainWndHandle, L"https://sourceforge.net/project/project_donations.php?group_id=242527", NULL); + } + break; + case ID_HELP_DEBUGCONSOLE: + { + PhShowDebugConsole(); + } + break; + case ID_HELP_ABOUT: + { + PhShowAboutDialog(PhMainWndHandle); + } + break; + case ID_PROCESS_TERMINATE: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + + if (PhUiTerminateProcesses(PhMainWndHandle, processes, numberOfProcesses)) + PhDeselectAllProcessNodes(); + + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_PROCESS_TERMINATETREE: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + + if (PhUiTerminateTreeProcess(PhMainWndHandle, processItem)) + PhDeselectAllProcessNodes(); + + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_SUSPEND: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + PhUiSuspendProcesses(PhMainWndHandle, processes, numberOfProcesses); + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_PROCESS_RESUME: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + PhUiResumeProcesses(PhMainWndHandle, processes, numberOfProcesses); + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_PROCESS_RESTART: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + + if (PhUiRestartProcess(PhMainWndHandle, processItem)) + PhDeselectAllProcessNodes(); + + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_CREATEDUMPFILE: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhUiCreateDumpFileProcess(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_DEBUG: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhUiDebugProcess(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_VIRTUALIZATION: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhUiSetVirtualizationProcess( + PhMainWndHandle, + processItem, + !SelectedProcessVirtualizationEnabled + ); + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_AFFINITY: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhShowProcessAffinityDialog(PhMainWndHandle, processItem, NULL); + PhDereferenceObject(processItem); + } + } + break; + case ID_MISCELLANEOUS_DETACHFROMDEBUGGER: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhUiDetachFromDebuggerProcess(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + } + break; + case ID_MISCELLANEOUS_GDIHANDLES: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhShowGdiHandlesDialog(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + } + break; + case ID_MISCELLANEOUS_INJECTDLL: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhReferenceObject(processItem); + PhUiInjectDllProcess(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + } + break; + case ID_PAGEPRIORITY_VERYLOW: + case ID_PAGEPRIORITY_LOW: + case ID_PAGEPRIORITY_MEDIUM: + case ID_PAGEPRIORITY_BELOWNORMAL: + case ID_PAGEPRIORITY_NORMAL: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + ULONG pagePriority; + + switch (Id) + { + case ID_PAGEPRIORITY_VERYLOW: + pagePriority = MEMORY_PRIORITY_VERY_LOW; + break; + case ID_PAGEPRIORITY_LOW: + pagePriority = MEMORY_PRIORITY_LOW; + break; + case ID_PAGEPRIORITY_MEDIUM: + pagePriority = MEMORY_PRIORITY_MEDIUM; + break; + case ID_PAGEPRIORITY_BELOWNORMAL: + pagePriority = MEMORY_PRIORITY_BELOW_NORMAL; + break; + case ID_PAGEPRIORITY_NORMAL: + pagePriority = MEMORY_PRIORITY_NORMAL; + break; + } + + PhReferenceObject(processItem); + PhUiSetPagePriorityProcess(PhMainWndHandle, processItem, pagePriority); + PhDereferenceObject(processItem); + } + } + break; + case ID_MISCELLANEOUS_REDUCEWORKINGSET: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + PhUiReduceWorkingSetProcesses(PhMainWndHandle, processes, numberOfProcesses); + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_MISCELLANEOUS_RUNAS: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem && processItem->FileName) + { + PhSetStringSetting2(L"RunAsProgram", &processItem->FileName->sr); + PhShowRunAsDialog(PhMainWndHandle, NULL); + } + } + break; + case ID_MISCELLANEOUS_RUNASTHISUSER: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhShowRunAsDialog(PhMainWndHandle, processItem->ProcessId); + } + } + break; + case ID_PRIORITY_REALTIME: + case ID_PRIORITY_HIGH: + case ID_PRIORITY_ABOVENORMAL: + case ID_PRIORITY_NORMAL: + case ID_PRIORITY_BELOWNORMAL: + case ID_PRIORITY_IDLE: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + PhMwpExecuteProcessPriorityCommand(Id, processes, numberOfProcesses); + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_IOPRIORITY_VERYLOW: + case ID_IOPRIORITY_LOW: + case ID_IOPRIORITY_NORMAL: + case ID_IOPRIORITY_HIGH: + { + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + PhReferenceObjects(processes, numberOfProcesses); + PhMwpExecuteProcessIoPriorityCommand(Id, processes, numberOfProcesses); + PhDereferenceObjects(processes, numberOfProcesses); + PhFree(processes); + } + break; + case ID_WINDOW_BRINGTOFRONT: + { + if (IsWindow(SelectedProcessWindowHandle)) + { + WINDOWPLACEMENT placement = { sizeof(placement) }; + + GetWindowPlacement(SelectedProcessWindowHandle, &placement); + + if (placement.showCmd == SW_MINIMIZE) + ShowWindowAsync(SelectedProcessWindowHandle, SW_RESTORE); + else + SetForegroundWindow(SelectedProcessWindowHandle); + } + } + break; + case ID_WINDOW_RESTORE: + { + if (IsWindow(SelectedProcessWindowHandle)) + { + ShowWindowAsync(SelectedProcessWindowHandle, SW_RESTORE); + } + } + break; + case ID_WINDOW_MINIMIZE: + { + if (IsWindow(SelectedProcessWindowHandle)) + { + ShowWindowAsync(SelectedProcessWindowHandle, SW_MINIMIZE); + } + } + break; + case ID_WINDOW_MAXIMIZE: + { + if (IsWindow(SelectedProcessWindowHandle)) + { + ShowWindowAsync(SelectedProcessWindowHandle, SW_MAXIMIZE); + } + } + break; + case ID_WINDOW_CLOSE: + { + if (IsWindow(SelectedProcessWindowHandle)) + { + PostMessage(SelectedProcessWindowHandle, WM_CLOSE, 0, 0); + } + } + break; + case ID_PROCESS_OPENFILELOCATION: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem && processItem->FileName) + { + PhReferenceObject(processItem); + PhShellExploreFile(PhMainWndHandle, processItem->FileName->Buffer); + PhDereferenceObject(processItem); + } + } + break; + case ID_PROCESS_SEARCHONLINE: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + PhSearchOnlineString(PhMainWndHandle, processItem->ProcessName->Buffer); + } + } + break; + case ID_PROCESS_PROPERTIES: + { + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + + if (processItem) + { + // No reference needed; no messages pumped. + PhMwpShowProcessProperties(processItem); + } + } + break; + case ID_PROCESS_COPY: + { + PhCopyProcessTree(); + } + break; + case ID_SERVICE_GOTOPROCESS: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + PPH_PROCESS_NODE processNode; + + if (serviceItem) + { + if (processNode = PhFindProcessNode(serviceItem->ProcessId)) + { + PhMwpSelectTabPage(ProcessesTabIndex); + SetFocus(ProcessTreeListHandle); + PhSelectAndEnsureVisibleProcessNode(processNode); + } + } + } + break; + case ID_SERVICE_START: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + PhReferenceObject(serviceItem); + PhUiStartService(PhMainWndHandle, serviceItem); + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_CONTINUE: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + PhReferenceObject(serviceItem); + PhUiContinueService(PhMainWndHandle, serviceItem); + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_PAUSE: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + PhReferenceObject(serviceItem); + PhUiPauseService(PhMainWndHandle, serviceItem); + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_STOP: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + PhReferenceObject(serviceItem); + PhUiStopService(PhMainWndHandle, serviceItem); + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_DELETE: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + PhReferenceObject(serviceItem); + + if (PhUiDeleteService(PhMainWndHandle, serviceItem)) + PhDeselectAllServiceNodes(); + + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_OPENKEY: + { + static PH_STRINGREF servicesKeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\"); + static PH_STRINGREF hklm = PH_STRINGREF_INIT(L"HKLM\\"); + + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + HANDLE keyHandle; + PPH_STRING serviceKeyName = PH_AUTO(PhConcatStringRef2(&servicesKeyName, &serviceItem->Name->sr)); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &serviceKeyName->sr, + 0 + ))) + { + PPH_STRING hklmServiceKeyName; + + hklmServiceKeyName = PH_AUTO(PhConcatStringRef2(&hklm, &serviceKeyName->sr)); + PhShellOpenKey2(PhMainWndHandle, hklmServiceKeyName); + NtClose(keyHandle); + } + } + } + break; + case ID_SERVICE_OPENFILELOCATION: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + SC_HANDLE serviceHandle; + + if (serviceItem && (serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_CONFIG))) + { + PPH_STRING fileName; + + if (fileName = PhGetServiceRelevantFileName(&serviceItem->Name->sr, serviceHandle)) + { + PhShellExploreFile(PhMainWndHandle, fileName->Buffer); + PhDereferenceObject(fileName); + } + + CloseServiceHandle(serviceHandle); + } + } + break; + case ID_SERVICE_PROPERTIES: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedServiceItem(); + + if (serviceItem) + { + // The object relies on the list view reference, which could + // disappear if we don't reference the object here. + PhReferenceObject(serviceItem); + PhShowServiceProperties(PhMainWndHandle, serviceItem); + PhDereferenceObject(serviceItem); + } + } + break; + case ID_SERVICE_COPY: + { + PhCopyServiceList(); + } + break; + case ID_NETWORK_GOTOPROCESS: + { + PPH_NETWORK_ITEM networkItem = PhGetSelectedNetworkItem(); + PPH_PROCESS_NODE processNode; + + if (networkItem) + { + if (processNode = PhFindProcessNode(networkItem->ProcessId)) + { + PhMwpSelectTabPage(ProcessesTabIndex); + SetFocus(ProcessTreeListHandle); + PhSelectAndEnsureVisibleProcessNode(processNode); + } + } + } + break; + case ID_NETWORK_GOTOSERVICE: + { + PPH_NETWORK_ITEM networkItem = PhGetSelectedNetworkItem(); + PPH_SERVICE_ITEM serviceItem; + + if (networkItem && networkItem->OwnerName) + { + if (serviceItem = PhReferenceServiceItem(networkItem->OwnerName->Buffer)) + { + PhMwpSelectTabPage(ServicesTabIndex); + SetFocus(ServiceTreeListHandle); + ProcessHacker_SelectServiceItem(PhMainWndHandle, serviceItem); + + PhDereferenceObject(serviceItem); + } + } + } + break; + case ID_NETWORK_VIEWSTACK: + { + PPH_NETWORK_ITEM networkItem = PhGetSelectedNetworkItem(); + + if (networkItem) + { + PhReferenceObject(networkItem); + PhShowNetworkStackDialog(PhMainWndHandle, networkItem); + PhDereferenceObject(networkItem); + } + } + break; + case ID_NETWORK_CLOSE: + { + PPH_NETWORK_ITEM *networkItems; + ULONG numberOfNetworkItems; + + PhGetSelectedNetworkItems(&networkItems, &numberOfNetworkItems); + PhReferenceObjects(networkItems, numberOfNetworkItems); + + if (PhUiCloseConnections(PhMainWndHandle, networkItems, numberOfNetworkItems)) + PhDeselectAllNetworkNodes(); + + PhDereferenceObjects(networkItems, numberOfNetworkItems); + PhFree(networkItems); + } + break; + case ID_NETWORK_COPY: + { + PhCopyNetworkList(); + } + break; + case ID_TAB_NEXT: + { + ULONG selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + if (selectedIndex != MaxTabIndex) + selectedIndex++; + else + selectedIndex = 0; + + PhMwpSelectTabPage(selectedIndex); + } + break; + case ID_TAB_PREV: + { + ULONG selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + if (selectedIndex != 0) + selectedIndex--; + else + selectedIndex = MaxTabIndex; + + PhMwpSelectTabPage(selectedIndex); + } + break; + } +} + +VOID PhMwpOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ) +{ + if (NeedsMaximize) + { + ShowWindow(PhMainWndHandle, SW_MAXIMIZE); + NeedsMaximize = FALSE; + } +} + +BOOLEAN PhMwpOnSysCommand( + _In_ ULONG Type, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ) +{ + switch (Type) + { + case SC_CLOSE: + { + if (PhGetIntegerSetting(L"HideOnClose") && PhNfTestIconMask(PH_ICON_ALL)) + { + ShowWindow(PhMainWndHandle, SW_HIDE); + return TRUE; + } + } + break; + case SC_MINIMIZE: + { + // Save the current window state because we may not have a chance to later. + PhMwpSaveWindowState(); + + if (PhGetIntegerSetting(L"HideOnMinimize") && PhNfTestIconMask(PH_ICON_ALL)) + { + ShowWindow(PhMainWndHandle, SW_HIDE); + return TRUE; + } + } + break; + } + + return FALSE; +} + +VOID PhMwpOnMenuCommand( + _In_ ULONG Index, + _In_ HMENU Menu + ) +{ + MENUITEMINFO menuItemInfo; + + menuItemInfo.cbSize = sizeof(MENUITEMINFO); + menuItemInfo.fMask = MIIM_ID | MIIM_DATA; + + if (GetMenuItemInfo(Menu, Index, TRUE, &menuItemInfo)) + { + PhMwpDispatchMenuCommand(Menu, Index, menuItemInfo.wID, menuItemInfo.dwItemData); + } +} + +VOID PhMwpOnInitMenuPopup( + _In_ HMENU Menu, + _In_ ULONG Index, + _In_ BOOLEAN IsWindowMenu + ) +{ + ULONG i; + BOOLEAN found; + MENUINFO menuInfo; + PPH_EMENU menu; + + found = FALSE; + + for (i = 0; i < sizeof(SubMenuHandles) / sizeof(HWND); i++) + { + if (Menu == SubMenuHandles[i]) + { + found = TRUE; + break; + } + } + + if (!found) + return; + + if (Index == 3) + { + // Special case for Users menu. + if (!UsersMenuInitialized) + { + PhMwpUpdateUsersMenu(); + UsersMenuInitialized = TRUE; + } + + return; + } + + // Delete all items in this submenu. + while (DeleteMenu(Menu, 0, MF_BYPOSITION)) ; + + // Delete the previous EMENU for this submenu. + if (SubMenuObjects[Index]) + PhDestroyEMenu(SubMenuObjects[Index]); + + // Make sure the menu style is set correctly. + memset(&menuInfo, 0, sizeof(MENUINFO)); + menuInfo.cbSize = sizeof(MENUINFO); + menuInfo.fMask = MIM_STYLE; + menuInfo.dwStyle = MNS_CHECKORBMP; + SetMenuInfo(Menu, &menuInfo); + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MAINWND), Index); + + PhMwpInitializeSubMenu(menu, Index); + + if (PhPluginsEnabled) + { + PH_PLUGIN_MENU_INFORMATION menuInfo; + + PhPluginInitializeMenuInfo(&menuInfo, menu, PhMainWndHandle, PH_PLUGIN_MENU_DISALLOW_HOOKS); + menuInfo.u.MainMenu.SubMenuIndex = Index; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMainMenuInitializing), &menuInfo); + } + + PhEMenuToHMenu2(Menu, menu, 0, NULL); + SubMenuObjects[Index] = menu; +} + +VOID PhMwpOnSize( + VOID + ) +{ + if (!IsIconic(PhMainWndHandle)) + { + HDWP deferHandle; + + deferHandle = BeginDeferWindowPos(2); + PhMwpLayout(&deferHandle); + EndDeferWindowPos(deferHandle); + } +} + +VOID PhMwpOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ) +{ + PhResizingMinimumSize(DragRectangle, Edge, 400, 340); +} + +VOID PhMwpOnSetFocus( + VOID + ) +{ + INT selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + if (selectedIndex == ProcessesTabIndex) + SetFocus(ProcessTreeListHandle); + else if (selectedIndex == ServicesTabIndex) + SetFocus(ServiceTreeListHandle); + else if (selectedIndex == NetworkTabIndex) + SetFocus(NetworkTreeListHandle); +} + +VOID PhMwpOnTimer( + _In_ ULONG Id + ) +{ + if (Id == TIMER_FLUSH_PROCESS_QUERY_DATA) + { + static ULONG state = 1; + + // If the update interval is too large, the user might have to wait a while before seeing some types of + // process-related data. Here we force an update. + // + // In addition, we force updates shortly after the program starts up to make things appear more quickly. + + switch (state) + { + case 1: + state = 2; + + if (PhCsUpdateInterval > PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_2) + SetTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA, PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_2, NULL); + else + KillTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA); + + break; + case 2: + state = 3; + + if (PhCsUpdateInterval > PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_LONG_TERM) + SetTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA, PH_FLUSH_PROCESS_QUERY_DATA_INTERVAL_LONG_TERM, NULL); + else + KillTimer(PhMainWndHandle, TIMER_FLUSH_PROCESS_QUERY_DATA); + + break; + default: + NOTHING; + break; + } + + PhFlushProcessQueryData(TRUE); + } +} + +BOOLEAN PhMwpOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ) +{ + if (Header->hwndFrom == TabControlHandle) + { + PhMwpNotifyTabControl(Header); + } + else if (Header->code == RFN_VALIDATE) + { + LPNMRUNFILEDLG runFileDlg = (LPNMRUNFILEDLG)Header; + + if (SelectedRunAsMode == RUNAS_MODE_ADMIN) + { + PH_STRINGREF string; + PH_STRINGREF fileName; + PH_STRINGREF arguments; + PPH_STRING fullFileName; + PPH_STRING argumentsString; + + PhInitializeStringRefLongHint(&string, (PWSTR)runFileDlg->lpszFile); + PhParseCommandLineFuzzy(&string, &fileName, &arguments, &fullFileName); + + if (!fullFileName) + fullFileName = PhCreateString2(&fileName); + + argumentsString = PhCreateString2(&arguments); + + if (PhShellExecuteEx(PhMainWndHandle, fullFileName->Buffer, argumentsString->Buffer, + runFileDlg->nShow, PH_SHELL_EXECUTE_ADMIN, 0, NULL)) + { + *Result = RF_CANCEL; + } + else + { + *Result = RF_RETRY; + } + + PhDereferenceObject(fullFileName); + PhDereferenceObject(argumentsString); + + return TRUE; + } + else if (SelectedRunAsMode == RUNAS_MODE_LIMITED) + { + NTSTATUS status; + HANDLE tokenHandle; + HANDLE newTokenHandle; + + if (NT_SUCCESS(status = PhOpenProcessToken( + NtCurrentProcess(), + TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_GROUPS | + TOKEN_ADJUST_DEFAULT | READ_CONTROL | WRITE_DAC, + &tokenHandle + ))) + { + if (NT_SUCCESS(status = PhFilterTokenForLimitedUser( + tokenHandle, + &newTokenHandle + ))) + { + status = PhCreateProcessWin32( + NULL, + (PWSTR)runFileDlg->lpszFile, + NULL, + NULL, + 0, + newTokenHandle, + NULL, + NULL + ); + + NtClose(newTokenHandle); + } + + NtClose(tokenHandle); + } + + if (NT_SUCCESS(status)) + { + *Result = RF_CANCEL; + } + else + { + PhShowStatus(PhMainWndHandle, L"Unable to execute the program", status, 0); + *Result = RF_RETRY; + } + + return TRUE; + } + } + + return FALSE; +} + +VOID PhMwpOnWtsSessionChange( + _In_ ULONG Reason, + _In_ ULONG SessionId + ) +{ + if (Reason == WTS_SESSION_LOGON || Reason == WTS_SESSION_LOGOFF) + { + if (UsersMenuInitialized) + { + PhMwpUpdateUsersMenu(); + } + } +} + +ULONG_PTR PhMwpOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ) +{ + switch (Message) + { + case WM_PH_ACTIVATE: + { + if (!PhMainWndExiting) + { + if (WParam != 0) + { + PPH_PROCESS_NODE processNode; + + if (processNode = PhFindProcessNode((HANDLE)WParam)) + PhSelectAndEnsureVisibleProcessNode(processNode); + } + + if (!IsWindowVisible(PhMainWndHandle)) + { + ShowWindow(PhMainWndHandle, SW_SHOW); + } + + if (IsIconic(PhMainWndHandle)) + { + ShowWindow(PhMainWndHandle, SW_RESTORE); + } + + return PH_ACTIVATE_REPLY; + } + else + { + return 0; + } + } + break; + case WM_PH_SHOW_PROCESS_PROPERTIES: + { + PhMwpShowProcessProperties((PPH_PROCESS_ITEM)LParam); + } + break; + case WM_PH_DESTROY: + { + DestroyWindow(PhMainWndHandle); + } + break; + case WM_PH_SAVE_ALL_SETTINGS: + { + PhMwpSaveSettings(); + } + break; + case WM_PH_PREPARE_FOR_EARLY_SHUTDOWN: + { + PhMwpSaveSettings(); + PhMainWndExiting = TRUE; + } + break; + case WM_PH_CANCEL_EARLY_SHUTDOWN: + { + PhMainWndExiting = FALSE; + } + break; + case WM_PH_DELAYED_LOAD_COMPLETED: + { + // Nothing + } + break; + case WM_PH_NOTIFY_ICON_MESSAGE: + { + PhNfForwardMessage(WParam, LParam); + } + break; + case WM_PH_TOGGLE_VISIBLE: + { + PhMwpActivateWindow(!WParam); + } + break; + case WM_PH_SHOW_MEMORY_EDITOR: + { + PPH_SHOWMEMORYEDITOR showMemoryEditor = (PPH_SHOWMEMORYEDITOR)LParam; + + PhShowMemoryEditorDialog( + showMemoryEditor->ProcessId, + showMemoryEditor->BaseAddress, + showMemoryEditor->RegionSize, + showMemoryEditor->SelectOffset, + showMemoryEditor->SelectLength, + showMemoryEditor->Title, + showMemoryEditor->Flags + ); + PhClearReference(&showMemoryEditor->Title); + PhFree(showMemoryEditor); + } + break; + case WM_PH_SHOW_MEMORY_RESULTS: + { + PPH_SHOWMEMORYRESULTS showMemoryResults = (PPH_SHOWMEMORYRESULTS)LParam; + + PhShowMemoryResultsDialog( + showMemoryResults->ProcessId, + showMemoryResults->Results + ); + PhDereferenceMemoryResults( + (PPH_MEMORY_RESULT *)showMemoryResults->Results->Items, + showMemoryResults->Results->Count + ); + PhDereferenceObject(showMemoryResults->Results); + PhFree(showMemoryResults); + } + break; + case WM_PH_SELECT_TAB_PAGE: + { + ULONG index = (ULONG)WParam; + + PhMwpSelectTabPage(index); + + if (index == ProcessesTabIndex) + SetFocus(ProcessTreeListHandle); + else if (index == ServicesTabIndex) + SetFocus(ServiceTreeListHandle); + else if (index == NetworkTabIndex) + SetFocus(NetworkTreeListHandle); + } + break; + case WM_PH_GET_CALLBACK_LAYOUT_PADDING: + { + return (ULONG_PTR)&LayoutPaddingCallback; + } + break; + case WM_PH_INVALIDATE_LAYOUT_PADDING: + { + LayoutPaddingValid = FALSE; + } + break; + case WM_PH_SELECT_PROCESS_NODE: + { + PhSelectAndEnsureVisibleProcessNode((PPH_PROCESS_NODE)LParam); + } + break; + case WM_PH_SELECT_SERVICE_ITEM: + { + PPH_SERVICE_NODE serviceNode; + + PhMwpNeedServiceTreeList(); + + // For compatibility, LParam is a service item, not node. + if (serviceNode = PhFindServiceNode((PPH_SERVICE_ITEM)LParam)) + { + PhSelectAndEnsureVisibleServiceNode(serviceNode); + } + } + break; + case WM_PH_SELECT_NETWORK_ITEM: + { + PPH_NETWORK_NODE networkNode; + + PhMwpNeedNetworkTreeList(); + + // For compatibility, LParam is a network item, not node. + if (networkNode = PhFindNetworkNode((PPH_NETWORK_ITEM)LParam)) + { + PhSelectAndEnsureVisibleNetworkNode(networkNode); + } + } + break; + case WM_PH_UPDATE_FONT: + { + PPH_STRING fontHexString; + LOGFONT font; + + fontHexString = PhaGetStringSetting(L"Font"); + + if ( + fontHexString->Length / 2 / 2 == sizeof(LOGFONT) && + PhHexStringToBuffer(&fontHexString->sr, (PUCHAR)&font) + ) + { + HFONT newFont; + + newFont = CreateFontIndirect(&font); + + if (newFont) + { + if (CurrentCustomFont) + DeleteObject(CurrentCustomFont); + + CurrentCustomFont = newFont; + + SendMessage(ProcessTreeListHandle, WM_SETFONT, (WPARAM)newFont, TRUE); + SendMessage(ServiceTreeListHandle, WM_SETFONT, (WPARAM)newFont, TRUE); + SendMessage(NetworkTreeListHandle, WM_SETFONT, (WPARAM)newFont, TRUE); + + if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (tabPage->FontChangedCallback) + { + tabPage->FontChangedCallback((PVOID)newFont, NULL, NULL, tabPage->Context); + } + } + } + } + } + } + break; + case WM_PH_GET_FONT: + return SendMessage(ProcessTreeListHandle, WM_GETFONT, 0, 0); + case WM_PH_INVOKE: + { + VOID (NTAPI *function)(PVOID); + + function = (PVOID)LParam; + function((PVOID)WParam); + } + break; + case WM_PH_ADD_MENU_ITEM: + { + PPH_ADDMENUITEM addMenuItem = (PPH_ADDMENUITEM)LParam; + + return PhMwpLegacyAddPluginMenuItem(addMenuItem); + } + break; + case WM_PH_ADD_TAB_PAGE: + { + return (ULONG_PTR)PhMwpAddTabPage((PPH_ADDITIONAL_TAB_PAGE)LParam); + } + break; + case WM_PH_REFRESH: + { + SendMessage(PhMainWndHandle, WM_COMMAND, ID_VIEW_REFRESH, 0); + } + break; + case WM_PH_GET_UPDATE_AUTOMATICALLY: + { + return UpdateAutomatically; + } + break; + case WM_PH_SET_UPDATE_AUTOMATICALLY: + { + if (!!WParam != UpdateAutomatically) + { + SendMessage(PhMainWndHandle, WM_COMMAND, ID_VIEW_UPDATEAUTOMATICALLY, 0); + } + } + break; + case WM_PH_ICON_CLICK: + { + PhMwpActivateWindow(!!PhGetIntegerSetting(L"IconTogglesVisibility")); + } + break; + case WM_PH_PROCESS_ADDED: + { + ULONG runId = (ULONG)WParam; + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)LParam; + + PhMwpOnProcessAdded(processItem, runId); + } + break; + case WM_PH_PROCESS_MODIFIED: + { + PhMwpOnProcessModified((PPH_PROCESS_ITEM)LParam); + } + break; + case WM_PH_PROCESS_REMOVED: + { + PhMwpOnProcessRemoved((PPH_PROCESS_ITEM)LParam); + } + break; + case WM_PH_PROCESSES_UPDATED: + { + PhMwpOnProcessesUpdated(); + } + break; + case WM_PH_SERVICE_ADDED: + { + ULONG runId = (ULONG)WParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)LParam; + + PhMwpOnServiceAdded(serviceItem, runId); + } + break; + case WM_PH_SERVICE_MODIFIED: + { + PPH_SERVICE_MODIFIED_DATA serviceModifiedData = (PPH_SERVICE_MODIFIED_DATA)LParam; + + PhMwpOnServiceModified(serviceModifiedData); + PhFree(serviceModifiedData); + } + break; + case WM_PH_SERVICE_REMOVED: + { + PhMwpOnServiceRemoved((PPH_SERVICE_ITEM)LParam); + } + break; + case WM_PH_SERVICES_UPDATED: + { + PhMwpOnServicesUpdated(); + } + break; + case WM_PH_NETWORK_ITEM_ADDED: + { + ULONG runId = (ULONG)WParam; + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)LParam; + + PhMwpOnNetworkItemAdded(runId, networkItem); + } + break; + case WM_PH_NETWORK_ITEM_MODIFIED: + { + PhMwpOnNetworkItemModified((PPH_NETWORK_ITEM)LParam); + } + break; + case WM_PH_NETWORK_ITEM_REMOVED: + { + PhMwpOnNetworkItemRemoved((PPH_NETWORK_ITEM)LParam); + } + break; + case WM_PH_NETWORK_ITEMS_UPDATED: + { + PhMwpOnNetworkItemsUpdated(); + } + break; + } + + return 0; +} + +VOID NTAPI PhMwpProcessAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter; + + // Reference the process item so it doesn't get deleted before + // we handle the event in the main thread. + PhReferenceObject(processItem); + PostMessage( + PhMainWndHandle, + WM_PH_PROCESS_ADDED, + (WPARAM)PhGetRunIdProvider(&ProcessProviderRegistration), + (LPARAM)processItem + ); +} + +VOID NTAPI PhMwpProcessModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter; + + PostMessage(PhMainWndHandle, WM_PH_PROCESS_MODIFIED, 0, (LPARAM)processItem); +} + +VOID NTAPI PhMwpProcessRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter; + + // We already have a reference to the process item, so we don't need to + // reference it here. + PostMessage(PhMainWndHandle, WM_PH_PROCESS_REMOVED, 0, (LPARAM)processItem); +} + +VOID NTAPI PhMwpProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhMainWndHandle, WM_PH_PROCESSES_UPDATED, 0, 0); +} + +VOID NTAPI PhMwpServiceAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Parameter; + + PhReferenceObject(serviceItem); + PostMessage( + PhMainWndHandle, + WM_PH_SERVICE_ADDED, + PhGetRunIdProvider(&ServiceProviderRegistration), + (LPARAM)serviceItem + ); +} + +VOID NTAPI PhMwpServiceModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_MODIFIED_DATA serviceModifiedData = (PPH_SERVICE_MODIFIED_DATA)Parameter; + PPH_SERVICE_MODIFIED_DATA copy; + + copy = PhAllocateCopy(serviceModifiedData, sizeof(PH_SERVICE_MODIFIED_DATA)); + + PostMessage(PhMainWndHandle, WM_PH_SERVICE_MODIFIED, 0, (LPARAM)copy); +} + +VOID NTAPI PhMwpServiceRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Parameter; + + PostMessage(PhMainWndHandle, WM_PH_SERVICE_REMOVED, 0, (LPARAM)serviceItem); +} + +VOID NTAPI PhMwpServicesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhMainWndHandle, WM_PH_SERVICES_UPDATED, 0, 0); +} + +VOID NTAPI PhMwpNetworkItemAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)Parameter; + + PhReferenceObject(networkItem); + PostMessage( + PhMainWndHandle, + WM_PH_NETWORK_ITEM_ADDED, + PhGetRunIdProvider(&NetworkProviderRegistration), + (LPARAM)networkItem + ); +} + +VOID NTAPI PhMwpNetworkItemModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)Parameter; + + PostMessage(PhMainWndHandle, WM_PH_NETWORK_ITEM_MODIFIED, 0, (LPARAM)networkItem); +} + +VOID NTAPI PhMwpNetworkItemRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)Parameter; + + PostMessage(PhMainWndHandle, WM_PH_NETWORK_ITEM_REMOVED, 0, (LPARAM)networkItem); +} + +VOID NTAPI PhMwpNetworkItemsUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhMainWndHandle, WM_PH_NETWORK_ITEMS_UPDATED, 0, 0); +} + +VOID PhMwpLoadSettings( + VOID + ) +{ + ULONG opacity; + PPH_STRING customFont; + + if (PhGetIntegerSetting(L"MainWindowAlwaysOnTop")) + { + AlwaysOnTop = TRUE; + SetWindowPos(PhMainWndHandle, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSIZE); + } + + opacity = PhGetIntegerSetting(L"MainWindowOpacity"); + + if (opacity != 0) + PhSetWindowOpacity(PhMainWndHandle, opacity); + + PhStatisticsSampleCount = PhGetIntegerSetting(L"SampleCount"); + PhEnableProcessQueryStage2 = !!PhGetIntegerSetting(L"EnableStage2"); + PhEnablePurgeProcessRecords = !PhGetIntegerSetting(L"NoPurgeProcessRecords"); + PhEnableCycleCpuUsage = !!PhGetIntegerSetting(L"EnableCycleCpuUsage"); + PhEnableServiceNonPoll = !!PhGetIntegerSetting(L"EnableServiceNonPoll"); + PhEnableNetworkProviderResolve = !!PhGetIntegerSetting(L"EnableNetworkResolve"); + + PhNfLoadStage1(); + + NotifyIconNotifyMask = PhGetIntegerSetting(L"IconNotifyMask"); + + if (PhGetIntegerSetting(L"HideOtherUserProcesses")) + { + CurrentUserFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportProcessTreeList(), PhMwpCurrentUserProcessTreeFilter, NULL); + } + + if (PhGetIntegerSetting(L"HideSignedProcesses")) + { + SignedFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportProcessTreeList(), PhMwpSignedProcessTreeFilter, NULL); + } + + customFont = PhaGetStringSetting(L"Font"); + + if (customFont->Length / 2 / 2 == sizeof(LOGFONT)) + SendMessage(PhMainWndHandle, WM_PH_UPDATE_FONT, 0, 0); + + PhLoadSettingsProcessTreeList(); + // Service and network list settings are loaded on demand. +} + +VOID PhMwpSaveSettings( + VOID + ) +{ + PhSaveSettingsProcessTreeList(); + if (ServiceTreeListLoaded) + PhSaveSettingsServiceTreeList(); + if (NetworkTreeListLoaded) + PhSaveSettingsNetworkTreeList(); + + PhNfSaveSettings(); + + PhSetIntegerSetting(L"IconNotifyMask", NotifyIconNotifyMask); + + PhSaveWindowPlacementToSetting(L"MainWindowPosition", L"MainWindowSize", PhMainWndHandle); + + PhMwpSaveWindowState(); + + if (PhSettingsFileName) + PhSaveSettings(PhSettingsFileName->Buffer); +} + +VOID PhMwpSaveWindowState( + VOID + ) +{ + WINDOWPLACEMENT placement = { sizeof(placement) }; + + GetWindowPlacement(PhMainWndHandle, &placement); + + if (placement.showCmd == SW_NORMAL) + PhSetIntegerSetting(L"MainWindowState", SW_NORMAL); + else if (placement.showCmd == SW_MAXIMIZE) + PhSetIntegerSetting(L"MainWindowState", SW_MAXIMIZE); +} + +VOID PhLoadDbgHelpFromPath( + _In_ PWSTR DbgHelpPath + ) +{ + HMODULE dbghelpModule; + + if (dbghelpModule = LoadLibrary(DbgHelpPath)) + { + PPH_STRING fullDbghelpPath; + ULONG indexOfFileName; + PH_STRINGREF dbghelpFolder; + PPH_STRING symsrvPath; + + fullDbghelpPath = PhGetDllFileName(dbghelpModule, &indexOfFileName); + + if (fullDbghelpPath) + { + if (indexOfFileName != 0) + { + static PH_STRINGREF symsrvString = PH_STRINGREF_INIT(L"\\symsrv.dll"); + + dbghelpFolder.Buffer = fullDbghelpPath->Buffer; + dbghelpFolder.Length = indexOfFileName * sizeof(WCHAR); + + symsrvPath = PhConcatStringRef2(&dbghelpFolder, &symsrvString); + LoadLibrary(symsrvPath->Buffer); + PhDereferenceObject(symsrvPath); + } + + PhDereferenceObject(fullDbghelpPath); + } + } + else + { + dbghelpModule = LoadLibrary(L"dbghelp.dll"); + } + + PhSymbolProviderCompleteInitialization(dbghelpModule); +} + +VOID PhMwpSymInitHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_STRING dbghelpPath; + + dbghelpPath = PhGetStringSetting(L"DbgHelpPath"); + PhLoadDbgHelpFromPath(dbghelpPath->Buffer); + PhDereferenceObject(dbghelpPath); +} + +VOID PhMwpUpdateLayoutPadding( + VOID + ) +{ + PH_LAYOUT_PADDING_DATA data; + + memset(&data, 0, sizeof(PH_LAYOUT_PADDING_DATA)); + PhInvokeCallback(&LayoutPaddingCallback, &data); + + LayoutPadding = data.Padding; +} + +VOID PhMwpApplyLayoutPadding( + _Inout_ PRECT Rect, + _In_ PRECT Padding + ) +{ + Rect->left += Padding->left; + Rect->top += Padding->top; + Rect->right -= Padding->right; + Rect->bottom -= Padding->bottom; +} + +VOID PhMwpLayout( + _Inout_ HDWP *DeferHandle + ) +{ + RECT rect; + + // Resize the tab control. + // Don't defer the resize. The tab control doesn't repaint properly. + + if (!LayoutPaddingValid) + { + PhMwpUpdateLayoutPadding(); + LayoutPaddingValid = TRUE; + } + + GetClientRect(PhMainWndHandle, &rect); + PhMwpApplyLayoutPadding(&rect, &LayoutPadding); + + SetWindowPos(TabControlHandle, NULL, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + UpdateWindow(TabControlHandle); + + PhMwpLayoutTabControl(DeferHandle); +} + +VOID PhMwpSetupComputerMenu( + _In_ PPH_EMENU_ITEM Root + ) +{ + PPH_EMENU_ITEM menuItem; + + if (WindowsVersion < WINDOWS_8) + { + if (menuItem = PhFindEMenuItem(Root, PH_EMENU_FIND_DESCEND, NULL, ID_COMPUTER_RESTARTBOOTOPTIONS)) + PhDestroyEMenuItem(menuItem); + if (menuItem = PhFindEMenuItem(Root, PH_EMENU_FIND_DESCEND, NULL, ID_COMPUTER_SHUTDOWNHYBRID)) + PhDestroyEMenuItem(menuItem); + } +} + +BOOLEAN PhMwpExecuteComputerCommand( + _In_ ULONG Id + ) +{ + switch (Id) + { + case ID_COMPUTER_LOCK: + PhUiLockComputer(PhMainWndHandle); + return TRUE; + case ID_COMPUTER_LOGOFF: + PhUiLogoffComputer(PhMainWndHandle); + return TRUE; + case ID_COMPUTER_SLEEP: + PhUiSleepComputer(PhMainWndHandle); + return TRUE; + case ID_COMPUTER_HIBERNATE: + PhUiHibernateComputer(PhMainWndHandle); + return TRUE; + case ID_COMPUTER_RESTART: + PhUiRestartComputer(PhMainWndHandle, 0); + return TRUE; + case ID_COMPUTER_RESTARTBOOTOPTIONS: + PhUiRestartComputer(PhMainWndHandle, EWX_BOOTOPTIONS); + return TRUE; + case ID_COMPUTER_SHUTDOWN: + PhUiShutdownComputer(PhMainWndHandle, 0); + return TRUE; + case ID_COMPUTER_SHUTDOWNHYBRID: + PhUiShutdownComputer(PhMainWndHandle, EWX_HYBRID_SHUTDOWN); + return TRUE; + } + + return FALSE; +} + +VOID PhMwpActivateWindow( + _In_ BOOLEAN Toggle + ) +{ + if (IsIconic(PhMainWndHandle)) + { + ShowWindow(PhMainWndHandle, SW_RESTORE); + SetForegroundWindow(PhMainWndHandle); + } + else if (IsWindowVisible(PhMainWndHandle)) + { + if (Toggle) + ShowWindow(PhMainWndHandle, SW_HIDE); + else + SetForegroundWindow(PhMainWndHandle); + } + else + { + ShowWindow(PhMainWndHandle, SW_SHOW); + SetForegroundWindow(PhMainWndHandle); + } +} + +VOID PhMwpInitializeMainMenu( + _In_ HMENU Menu + ) +{ + MENUINFO menuInfo; + ULONG i; + + menuInfo.cbSize = sizeof(MENUINFO); + menuInfo.fMask = MIM_STYLE; + menuInfo.dwStyle = MNS_NOTIFYBYPOS; + SetMenuInfo(Menu, &menuInfo); + + for (i = 0; i < sizeof(SubMenuHandles) / sizeof(HMENU); i++) + { + SubMenuHandles[i] = GetSubMenu(PhMainWndMenuHandle, i); + } +} + +VOID PhMwpDispatchMenuCommand( + _In_ HMENU MenuHandle, + _In_ ULONG ItemIndex, + _In_ ULONG ItemId, + _In_ ULONG_PTR ItemData + ) +{ + switch (ItemId) + { + case ID_PLUGIN_MENU_ITEM: + { + PPH_EMENU_ITEM menuItem; + PH_PLUGIN_MENU_INFORMATION menuInfo; + + menuItem = (PPH_EMENU_ITEM)ItemData; + + if (menuItem) + { + PhPluginInitializeMenuInfo(&menuInfo, NULL, PhMainWndHandle, 0); + PhPluginTriggerEMenuItem(&menuInfo, menuItem); + } + + return; + } + break; + case ID_TRAYICONS_REGISTERED: + { + PPH_EMENU_ITEM menuItem; + + menuItem = (PPH_EMENU_ITEM)ItemData; + + if (menuItem) + { + PPH_NF_ICON icon; + + icon = menuItem->Context; + PhNfSetVisibleIcon(icon->IconId, !PhNfTestIconMask(icon->IconId)); + } + + return; + } + break; + case ID_USER_CONNECT: + case ID_USER_DISCONNECT: + case ID_USER_LOGOFF: + case ID_USER_REMOTECONTROL: + case ID_USER_SENDMESSAGE: + case ID_USER_PROPERTIES: + { + SelectedUserSessionId = (ULONG)ItemData; + } + break; + } + + SendMessage(PhMainWndHandle, WM_COMMAND, ItemId, 0); +} + +ULONG_PTR PhMwpLegacyAddPluginMenuItem( + _In_ PPH_ADDMENUITEM AddMenuItem + ) +{ + PPH_ADDMENUITEM addMenuItem; + PPH_PLUGIN_MENU_ITEM pluginMenuItem; + + if (!LegacyAddMenuItemList) + LegacyAddMenuItemList = PhCreateList(8); + + addMenuItem = PhAllocateCopy(AddMenuItem, sizeof(PH_ADDMENUITEM)); + PhAddItemList(LegacyAddMenuItemList, addMenuItem); + + pluginMenuItem = PhAllocate(sizeof(PH_PLUGIN_MENU_ITEM)); + memset(pluginMenuItem, 0, sizeof(PH_PLUGIN_MENU_ITEM)); + pluginMenuItem->Plugin = AddMenuItem->Plugin; + pluginMenuItem->Id = AddMenuItem->Id; + pluginMenuItem->Context = AddMenuItem->Context; + + addMenuItem->Context = pluginMenuItem; + + return TRUE; +} + +HBITMAP PhMwpGetShieldBitmap( + VOID + ) +{ + static HBITMAP shieldBitmap = NULL; + + if (!shieldBitmap) + { + HICON shieldIcon; + + if (shieldIcon = PhLoadIcon(NULL, IDI_SHIELD, PH_LOAD_ICON_SIZE_SMALL | PH_LOAD_ICON_STRICT, 0, 0)) + { + shieldBitmap = PhIconToBitmap(shieldIcon, PhSmallIconSize.X, PhSmallIconSize.Y); + DestroyIcon(shieldIcon); + } + } + + return shieldBitmap; +} + +VOID PhMwpInitializeSubMenu( + _In_ PPH_EMENU Menu, + _In_ ULONG Index + ) +{ + PPH_EMENU_ITEM menuItem; + + if (Index == 0) // Hacker + { + // Fix some menu items. + if (PhGetOwnTokenAttributes().Elevated) + { + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_HACKER_RUNASADMINISTRATOR)) + PhDestroyEMenuItem(menuItem); + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_HACKER_SHOWDETAILSFORALLPROCESSES)) + PhDestroyEMenuItem(menuItem); + } + else + { + HBITMAP shieldBitmap; + + if (shieldBitmap = PhMwpGetShieldBitmap()) + { + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_HACKER_SHOWDETAILSFORALLPROCESSES)) + menuItem->Bitmap = shieldBitmap; + } + } + + // Fix up the Computer menu. + PhMwpSetupComputerMenu(Menu); + } + else if (Index == 1) // View + { + PPH_EMENU_ITEM trayIconsMenuItem; + ULONG i; + PPH_EMENU_ITEM menuItem; + ULONG id; + ULONG placeholderIndex; + + trayIconsMenuItem = PhMwpFindTrayIconsMenuItem(Menu); + + if (trayIconsMenuItem) + { + ULONG maximum; + PPH_NF_ICON icon; + + // Add menu items for the registered tray icons. + + id = PH_ICON_DEFAULT_MAXIMUM; + maximum = PhNfGetMaximumIconId(); + + for (; id != maximum; id <<= 1) + { + if (icon = PhNfGetIconById(id)) + { + PhInsertEMenuItem(trayIconsMenuItem, PhCreateEMenuItem(0, ID_TRAYICONS_REGISTERED, icon->Text, NULL, icon), -1); + } + } + + // Update the text and check marks on the menu items. + + for (i = 0; i < trayIconsMenuItem->Items->Count; i++) + { + menuItem = trayIconsMenuItem->Items->Items[i]; + + id = -1; + icon = NULL; + + switch (menuItem->Id) + { + case ID_TRAYICONS_CPUHISTORY: + id = PH_ICON_CPU_HISTORY; + break; + case ID_TRAYICONS_IOHISTORY: + id = PH_ICON_IO_HISTORY; + break; + case ID_TRAYICONS_COMMITHISTORY: + id = PH_ICON_COMMIT_HISTORY; + break; + case ID_TRAYICONS_PHYSICALMEMORYHISTORY: + id = PH_ICON_PHYSICAL_HISTORY; + break; + case ID_TRAYICONS_CPUUSAGE: + id = PH_ICON_CPU_USAGE; + break; + case ID_TRAYICONS_REGISTERED: + icon = menuItem->Context; + id = icon->IconId; + break; + } + + if (id != -1) + { + if (PhNfTestIconMask(id)) + menuItem->Flags |= PH_EMENU_CHECKED; + + if (icon && (icon->Flags & PH_NF_ICON_UNAVAILABLE)) + { + PPH_STRING newText; + + newText = PhaConcatStrings2(icon->Text, L" (Unavailable)"); + PhModifyEMenuItem(menuItem, PH_EMENU_MODIFY_TEXT, PH_EMENU_TEXT_OWNED, + PhAllocateCopy(newText->Buffer, newText->Length + sizeof(WCHAR)), NULL); + } + } + } + } + + if (menuItem = PhFindEMenuItemEx(Menu, 0, NULL, ID_VIEW_SECTIONPLACEHOLDER, NULL, &placeholderIndex)) + { + PhDestroyEMenuItem(menuItem); + PhMwpInitializeSectionMenuItems(Menu, placeholderIndex); + } + + if (AlwaysOnTop && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_ALWAYSONTOP))) + menuItem->Flags |= PH_EMENU_CHECKED; + + id = PH_OPACITY_TO_ID(PhGetIntegerSetting(L"MainWindowOpacity")); + + if (menuItem = PhFindEMenuItem(Menu, PH_EMENU_FIND_DESCEND, NULL, id)) + menuItem->Flags |= PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK; + + switch (PhGetIntegerSetting(L"UpdateInterval")) + { + case 500: + id = ID_UPDATEINTERVAL_FAST; + break; + case 1000: + id = ID_UPDATEINTERVAL_NORMAL; + break; + case 2000: + id = ID_UPDATEINTERVAL_BELOWNORMAL; + break; + case 5000: + id = ID_UPDATEINTERVAL_SLOW; + break; + case 10000: + id = ID_UPDATEINTERVAL_VERYSLOW; + break; + default: + id = -1; + break; + } + + if (id != -1 && (menuItem = PhFindEMenuItem(Menu, PH_EMENU_FIND_DESCEND, NULL, id))) + menuItem->Flags |= PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK; + + if (UpdateAutomatically && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_UPDATEAUTOMATICALLY))) + menuItem->Flags |= PH_EMENU_CHECKED; + } + else if (Index == 2) // Tools + { +#ifdef _WIN64 + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_TOOLS_HIDDENPROCESSES)) + PhDestroyEMenuItem(menuItem); +#endif + + // Windows 8 Task Manager requires elevation. + if (WindowsVersion >= WINDOWS_8 && !PhGetOwnTokenAttributes().Elevated) + { + HBITMAP shieldBitmap; + + if (shieldBitmap = PhMwpGetShieldBitmap()) + { + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_TOOLS_STARTTASKMANAGER)) + menuItem->Bitmap = shieldBitmap; + } + } + } + + if (LegacyAddMenuItemList) + { + ULONG i; + PPH_ADDMENUITEM addMenuItem; + + for (i = 0; i < LegacyAddMenuItemList->Count; i++) + { + addMenuItem = LegacyAddMenuItemList->Items[i]; + + if (addMenuItem->Location == Index) + { + ULONG insertIndex; + + if (addMenuItem->InsertAfter) + { + for (insertIndex = 0; insertIndex < Menu->Items->Count; insertIndex++) + { + menuItem = Menu->Items->Items[insertIndex]; + + if (!(menuItem->Flags & PH_EMENU_SEPARATOR) && (PhCompareUnicodeStringZIgnoreMenuPrefix( + addMenuItem->InsertAfter, + menuItem->Text, + TRUE, + TRUE + ) == 0)) + { + insertIndex++; + break; + } + } + } + else + { + insertIndex = 0; + } + + if (addMenuItem->Text[0] == '-' && addMenuItem->Text[1] == 0) + PhInsertEMenuItem(Menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, L"", NULL, NULL), insertIndex); + else + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_PLUGIN_MENU_ITEM, addMenuItem->Text, NULL, addMenuItem->Context), insertIndex); + } + } + } +} + +PPH_EMENU_ITEM PhMwpFindTrayIconsMenuItem( + _In_ PPH_EMENU Menu + ) +{ + ULONG i; + PPH_EMENU_ITEM menuItem; + + for (i = 0; i < Menu->Items->Count; i++) + { + menuItem = Menu->Items->Items[i]; + + if (PhFindEMenuItem(menuItem, 0, NULL, ID_TRAYICONS_CPUHISTORY)) + return menuItem; + } + + return NULL; +} + +VOID PhMwpInitializeSectionMenuItems( + _In_ PPH_EMENU Menu, + _In_ ULONG StartIndex + ) +{ + INT selectedIndex; + PPH_EMENU_ITEM menuItem; + + selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + if (selectedIndex == ProcessesTabIndex) + { + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_VIEW_HIDEPROCESSESFROMOTHERUSERS, L"Hide processes from other users", NULL, NULL), StartIndex); + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_VIEW_HIDESIGNEDPROCESSES, L"Hide signed processes", NULL, NULL), StartIndex + 1); + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_VIEW_SCROLLTONEWPROCESSES, L"Scroll to new processes", NULL, NULL), StartIndex + 2); + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_VIEW_SHOWCPUBELOW001, L"Show CPU below 0.01", NULL, NULL), StartIndex + 3); + + if (CurrentUserFilterEntry && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_HIDEPROCESSESFROMOTHERUSERS))) + menuItem->Flags |= PH_EMENU_CHECKED; + if (SignedFilterEntry && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_HIDESIGNEDPROCESSES))) + menuItem->Flags |= PH_EMENU_CHECKED; + if (PhCsScrollToNewProcesses && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_SCROLLTONEWPROCESSES))) + menuItem->Flags |= PH_EMENU_CHECKED; + + if (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_SHOWCPUBELOW001)) + { + if (WindowsVersion >= WINDOWS_7 && PhEnableCycleCpuUsage) + { + if (PhCsShowCpuBelow001) + menuItem->Flags |= PH_EMENU_CHECKED; + } + else + { + menuItem->Flags |= PH_EMENU_DISABLED; + } + } + } + else if (selectedIndex == ServicesTabIndex) + { + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_VIEW_HIDEDRIVERSERVICES, L"Hide driver services", NULL, NULL), StartIndex); + + if (DriverFilterEntry && (menuItem = PhFindEMenuItem(Menu, 0, NULL, ID_VIEW_HIDEDRIVERSERVICES))) + menuItem->Flags |= PH_EMENU_CHECKED; + } + else if (selectedIndex == NetworkTabIndex) + { + // Remove the extra separator. + PhRemoveEMenuItem(Menu, NULL, StartIndex); + } + else if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (selectedIndex == tabPage->Index) + { + if (tabPage->InitializeSectionMenuItemsCallback) + { + tabPage->InitializeSectionMenuItemsCallback(Menu, UlongToPtr(StartIndex), NULL, tabPage->Context); + } + else + { + // Remove the extra separator. + PhRemoveEMenuItem(Menu, NULL, StartIndex); + } + + break; + } + } + } +} + +VOID PhMwpLayoutTabControl( + _Inout_ HDWP *DeferHandle + ) +{ + RECT rect; + INT selectedIndex; + + if (!LayoutPaddingValid) + { + PhMwpUpdateLayoutPadding(); + LayoutPaddingValid = TRUE; + } + + GetClientRect(PhMainWndHandle, &rect); + PhMwpApplyLayoutPadding(&rect, &LayoutPadding); + TabCtrl_AdjustRect(TabControlHandle, FALSE, &rect); + + selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + if (selectedIndex == ProcessesTabIndex) + { + *DeferHandle = DeferWindowPos(*DeferHandle, ProcessTreeListHandle, NULL, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + } + else if (selectedIndex == ServicesTabIndex) + { + *DeferHandle = DeferWindowPos(*DeferHandle, ServiceTreeListHandle, NULL, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + } + else if (selectedIndex == NetworkTabIndex) + { + *DeferHandle = DeferWindowPos(*DeferHandle, NetworkTreeListHandle, NULL, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + } + else if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (selectedIndex == tabPage->Index) + { + // Create the tab page window if it doesn't exist. + if (!tabPage->WindowHandle) + { + tabPage->WindowHandle = tabPage->CreateFunction(tabPage->Context); + BringWindowToTop(tabPage->WindowHandle); + + if (CurrentCustomFont && tabPage->FontChangedCallback) + tabPage->FontChangedCallback((PVOID)CurrentCustomFont, NULL, NULL, tabPage->Context); + } + + if (tabPage->WindowHandle) + { + *DeferHandle = DeferWindowPos(*DeferHandle, tabPage->WindowHandle, NULL, + rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); + } + + break; + } + } + } +} + +VOID PhMwpNotifyTabControl( + _In_ NMHDR *Header + ) +{ + if (Header->code == TCN_SELCHANGING) + { + OldTabIndex = TabCtrl_GetCurSel(TabControlHandle); + } + else if (Header->code == TCN_SELCHANGE) + { + PhMwpSelectionChangedTabControl(OldTabIndex); + } +} + +VOID PhMwpSelectionChangedTabControl( + _In_ ULONG OldIndex + ) +{ + INT selectedIndex; + HDWP deferHandle; + + selectedIndex = TabCtrl_GetCurSel(TabControlHandle); + + deferHandle = BeginDeferWindowPos(1); + PhMwpLayoutTabControl(&deferHandle); + EndDeferWindowPos(deferHandle); + + // Built-in tabs + + if (selectedIndex == ServicesTabIndex) + PhMwpNeedServiceTreeList(); + + if (selectedIndex == NetworkTabIndex) + { + PhMwpNeedNetworkTreeList(); + + PhSetEnabledProvider(&NetworkProviderRegistration, UpdateAutomatically); + + if (UpdateAutomatically || NetworkFirstTime) + { + PhBoostProvider(&NetworkProviderRegistration, NULL); + NetworkFirstTime = FALSE; + } + } + else + { + PhSetEnabledProvider(&NetworkProviderRegistration, FALSE); + } + + ShowWindow(ProcessTreeListHandle, selectedIndex == ProcessesTabIndex ? SW_SHOW : SW_HIDE); + ShowWindow(ServiceTreeListHandle, selectedIndex == ServicesTabIndex ? SW_SHOW : SW_HIDE); + ShowWindow(NetworkTreeListHandle, selectedIndex == NetworkTabIndex ? SW_SHOW : SW_HIDE); + + // Additional tabs + + if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (tabPage->SelectionChangedCallback) + { + if (tabPage->Index == OldIndex) + { + tabPage->SelectionChangedCallback((PVOID)FALSE, 0, 0, tabPage->Context); + } + else if (tabPage->Index == selectedIndex) + { + tabPage->SelectionChangedCallback((PVOID)TRUE, 0, 0, tabPage->Context); + } + } + + ShowWindow(tabPage->WindowHandle, selectedIndex == tabPage->Index ? SW_SHOW : SW_HIDE); + } + } + + if (PhPluginsEnabled) + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMainWindowTabChanged), IntToPtr(selectedIndex)); +} + +PPH_ADDITIONAL_TAB_PAGE PhMwpAddTabPage( + _In_ PPH_ADDITIONAL_TAB_PAGE TabPage + ) +{ + PPH_ADDITIONAL_TAB_PAGE newTabPage; + HDWP deferHandle; + + if (!AdditionalTabPageList) + AdditionalTabPageList = PhCreateList(2); + + newTabPage = PhAllocateCopy(TabPage, sizeof(PH_ADDITIONAL_TAB_PAGE)); + PhAddItemList(AdditionalTabPageList, newTabPage); + + newTabPage->Index = PhAddTabControlTab(TabControlHandle, MAXINT, newTabPage->Text); + MaxTabIndex = newTabPage->Index; + + if (newTabPage->WindowHandle) + BringWindowToTop(newTabPage->WindowHandle); + + // The tab control might need multiple lines, so we need to refresh the layout. + deferHandle = BeginDeferWindowPos(1); + PhMwpLayoutTabControl(&deferHandle); + EndDeferWindowPos(deferHandle); + + return newTabPage; +} + +VOID PhMwpSelectTabPage( + _In_ ULONG Index + ) +{ + INT oldIndex; + + oldIndex = TabCtrl_GetCurSel(TabControlHandle); + TabCtrl_SetCurSel(TabControlHandle, Index); + PhMwpSelectionChangedTabControl(oldIndex); +} + +INT PhMwpFindTabPageIndex( + _In_ PWSTR Text + ) +{ + if (PhEqualStringZ(Text, L"Processes", TRUE)) + { + return ProcessesTabIndex; + } + else if (PhEqualStringZ(Text, L"Services", TRUE)) + { + return ServicesTabIndex; + } + else if (PhEqualStringZ(Text, L"Network", TRUE)) + { + return NetworkTabIndex; + } + else if (AdditionalTabPageList) + { + ULONG i; + + for (i = 0; i < AdditionalTabPageList->Count; i++) + { + PPH_ADDITIONAL_TAB_PAGE tabPage = AdditionalTabPageList->Items[i]; + + if (PhEqualStringZ(tabPage->Text, Text, TRUE)) + return tabPage->Index; + } + } + + return -1; +} + +static int __cdecl IconProcessesCpuUsageCompare( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_PROCESS_ITEM processItem1 = *(PPH_PROCESS_ITEM *)elem1; + PPH_PROCESS_ITEM processItem2 = *(PPH_PROCESS_ITEM *)elem2; + + return -singlecmp(processItem1->CpuUsage, processItem2->CpuUsage); +} + +static int __cdecl IconProcessesNameCompare( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_PROCESS_ITEM processItem1 = *(PPH_PROCESS_ITEM *)elem1; + PPH_PROCESS_ITEM processItem2 = *(PPH_PROCESS_ITEM *)elem2; + + return PhCompareString(processItem1->ProcessName, processItem2->ProcessName, TRUE); +} + +VOID PhAddMiniProcessMenuItems( + _Inout_ struct _PH_EMENU_ITEM *Menu, + _In_ HANDLE ProcessId + ) +{ + PPH_EMENU_ITEM priorityMenu; + PPH_EMENU_ITEM ioPriorityMenu = NULL; + PPH_PROCESS_ITEM processItem; + BOOLEAN isSuspended = FALSE; + BOOLEAN isPartiallySuspended = TRUE; + + // Priority + + priorityMenu = PhCreateEMenuItem(0, 0, L"Priority", NULL, ProcessId); + + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_REALTIME, L"Real time", NULL, ProcessId), -1); + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_HIGH, L"High", NULL, ProcessId), -1); + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_ABOVENORMAL, L"Above normal", NULL, ProcessId), -1); + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_NORMAL, L"Normal", NULL, ProcessId), -1); + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_BELOWNORMAL, L"Below normal", NULL, ProcessId), -1); + PhInsertEMenuItem(priorityMenu, PhCreateEMenuItem(0, ID_PRIORITY_IDLE, L"Idle", NULL, ProcessId), -1); + + // I/O priority + + if (WindowsVersion >= WINDOWS_VISTA) + { + ioPriorityMenu = PhCreateEMenuItem(0, 0, L"I/O priority", NULL, ProcessId); + + PhInsertEMenuItem(ioPriorityMenu, PhCreateEMenuItem(0, ID_IOPRIORITY_HIGH, L"High", NULL, ProcessId), -1); + PhInsertEMenuItem(ioPriorityMenu, PhCreateEMenuItem(0, ID_IOPRIORITY_NORMAL, L"Normal", NULL, ProcessId), -1); + PhInsertEMenuItem(ioPriorityMenu, PhCreateEMenuItem(0, ID_IOPRIORITY_LOW, L"Low", NULL, ProcessId), -1); + PhInsertEMenuItem(ioPriorityMenu, PhCreateEMenuItem(0, ID_IOPRIORITY_VERYLOW, L"Very low", NULL, ProcessId), -1); + } + + // Menu + + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_PROCESS_TERMINATE, L"T&erminate", NULL, ProcessId), -1); + + if (processItem = PhReferenceProcessItem(ProcessId)) + { + isSuspended = (BOOLEAN)processItem->IsSuspended; + isPartiallySuspended = (BOOLEAN)processItem->IsPartiallySuspended; + PhDereferenceObject(processItem); + } + + if (!isSuspended) + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_PROCESS_SUSPEND, L"&Suspend", NULL, ProcessId), -1); + if (isPartiallySuspended) + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_PROCESS_RESUME, L"Res&ume", NULL, ProcessId), -1); + + PhInsertEMenuItem(Menu, priorityMenu, -1); + + if (ioPriorityMenu) + PhInsertEMenuItem(Menu, ioPriorityMenu, -1); + + PhMwpSetProcessMenuPriorityChecks(Menu, ProcessId, TRUE, TRUE, FALSE); + + PhInsertEMenuItem(Menu, PhCreateEMenuItem(0, ID_PROCESS_PROPERTIES, L"P&roperties", NULL, ProcessId), -1); +} + +BOOLEAN PhHandleMiniProcessMenuItem( + _Inout_ struct _PH_EMENU_ITEM *MenuItem + ) +{ + switch (MenuItem->Id) + { + case ID_PROCESS_TERMINATE: + case ID_PROCESS_SUSPEND: + case ID_PROCESS_RESUME: + case ID_PROCESS_PROPERTIES: + { + HANDLE processId = MenuItem->Context; + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(processId)) + { + switch (MenuItem->Id) + { + case ID_PROCESS_TERMINATE: + PhUiTerminateProcesses(PhMainWndHandle, &processItem, 1); + break; + case ID_PROCESS_SUSPEND: + PhUiSuspendProcesses(PhMainWndHandle, &processItem, 1); + break; + case ID_PROCESS_RESUME: + PhUiResumeProcesses(PhMainWndHandle, &processItem, 1); + break; + case ID_PROCESS_PROPERTIES: + ProcessHacker_ShowProcessProperties(PhMainWndHandle, processItem); + break; + } + + PhDereferenceObject(processItem); + } + else + { + PhShowError(PhMainWndHandle, L"The process does not exist."); + } + } + break; + case ID_PRIORITY_REALTIME: + case ID_PRIORITY_HIGH: + case ID_PRIORITY_ABOVENORMAL: + case ID_PRIORITY_NORMAL: + case ID_PRIORITY_BELOWNORMAL: + case ID_PRIORITY_IDLE: + { + HANDLE processId = MenuItem->Context; + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(processId)) + { + PhMwpExecuteProcessPriorityCommand(MenuItem->Id, &processItem, 1); + PhDereferenceObject(processItem); + } + else + { + PhShowError(PhMainWndHandle, L"The process does not exist."); + } + } + break; + case ID_IOPRIORITY_HIGH: + case ID_IOPRIORITY_NORMAL: + case ID_IOPRIORITY_LOW: + case ID_IOPRIORITY_VERYLOW: + { + HANDLE processId = MenuItem->Context; + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(processId)) + { + PhMwpExecuteProcessIoPriorityCommand(MenuItem->Id, &processItem, 1); + PhDereferenceObject(processItem); + } + else + { + PhShowError(PhMainWndHandle, L"The process does not exist."); + } + } + break; + } + + return FALSE; +} + +VOID PhMwpAddIconProcesses( + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG NumberOfProcesses + ) +{ + ULONG i; + PPH_PROCESS_ITEM *processItems; + ULONG numberOfProcessItems; + PPH_LIST processList; + PPH_PROCESS_ITEM processItem; + + PhEnumProcessItems(&processItems, &numberOfProcessItems); + processList = PhCreateList(numberOfProcessItems); + PhAddItemsList(processList, processItems, numberOfProcessItems); + + // Remove non-real processes. + for (i = 0; i < processList->Count; i++) + { + processItem = processList->Items[i]; + + if (!PH_IS_REAL_PROCESS_ID(processItem->ProcessId)) + { + PhRemoveItemList(processList, i); + i--; + } + } + + // Remove processes with zero CPU usage and those running as other users. + for (i = 0; i < processList->Count && processList->Count > NumberOfProcesses; i++) + { + processItem = processList->Items[i]; + + if ( + processItem->CpuUsage == 0 || + !processItem->UserName || + (PhCurrentUserName && !PhEqualString(processItem->UserName, PhCurrentUserName, TRUE)) + ) + { + PhRemoveItemList(processList, i); + i--; + } + } + + // Sort the processes by CPU usage and remove the extra processes at the end of the list. + qsort(processList->Items, processList->Count, sizeof(PVOID), IconProcessesCpuUsageCompare); + + if (processList->Count > NumberOfProcesses) + { + PhRemoveItemsList(processList, NumberOfProcesses, processList->Count - NumberOfProcesses); + } + + // Lastly, sort by name. + qsort(processList->Items, processList->Count, sizeof(PVOID), IconProcessesNameCompare); + + // Delete all menu items. + PhRemoveAllEMenuItems(Menu); + + // Add the processes. + + for (i = 0; i < processList->Count; i++) + { + PPH_EMENU_ITEM subMenu; + HBITMAP iconBitmap; + CLIENT_ID clientId; + PPH_STRING clientIdName; + PPH_STRING escapedName; + + processItem = processList->Items[i]; + + // Process + + clientId.UniqueProcess = processItem->ProcessId; + clientId.UniqueThread = NULL; + + clientIdName = PH_AUTO(PhGetClientIdName(&clientId)); + escapedName = PH_AUTO(PhEscapeStringForMenuPrefix(&clientIdName->sr)); + + subMenu = PhCreateEMenuItem( + 0, + 0, + escapedName->Buffer, + NULL, + processItem->ProcessId + ); + + if (processItem->SmallIcon) + { + iconBitmap = PhIconToBitmap(processItem->SmallIcon, PhSmallIconSize.X, PhSmallIconSize.Y); + } + else + { + HICON icon; + + PhGetStockApplicationIcon(&icon, NULL); + iconBitmap = PhIconToBitmap(icon, PhSmallIconSize.X, PhSmallIconSize.Y); + } + + subMenu->Bitmap = iconBitmap; + subMenu->Flags |= PH_EMENU_BITMAP_OWNED; // automatically destroy the bitmap when necessary + + PhAddMiniProcessMenuItems(subMenu, processItem->ProcessId); + PhInsertEMenuItem(Menu, subMenu, -1); + } + + PhDereferenceObject(processList); + PhDereferenceObjects(processItems, numberOfProcessItems); + PhFree(processItems); +} + +VOID PhShowIconContextMenu( + _In_ POINT Location + ) +{ + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_PLUGIN_MENU_INFORMATION menuInfo; + ULONG numberOfProcesses; + ULONG id; + ULONG i; + + // This function seems to be called recursively under some circumstances. + // To reproduce: + // 1. Hold right mouse button on tray icon, then left click. + // 2. Make the menu disappear by clicking on the menu then clicking somewhere else. + // So, don't store any global state or bad things will happen. + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_ICON), 0); + + // Check the Notifications menu items. + for (i = PH_NOTIFY_MINIMUM; i != PH_NOTIFY_MAXIMUM; i <<= 1) + { + if (NotifyIconNotifyMask & i) + { + switch (i) + { + case PH_NOTIFY_PROCESS_CREATE: + id = ID_NOTIFICATIONS_NEWPROCESSES; + break; + case PH_NOTIFY_PROCESS_DELETE: + id = ID_NOTIFICATIONS_TERMINATEDPROCESSES; + break; + case PH_NOTIFY_SERVICE_CREATE: + id = ID_NOTIFICATIONS_NEWSERVICES; + break; + case PH_NOTIFY_SERVICE_DELETE: + id = ID_NOTIFICATIONS_DELETEDSERVICES; + break; + case PH_NOTIFY_SERVICE_START: + id = ID_NOTIFICATIONS_STARTEDSERVICES; + break; + case PH_NOTIFY_SERVICE_STOP: + id = ID_NOTIFICATIONS_STOPPEDSERVICES; + break; + } + + PhSetFlagsEMenuItem(menu, id, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + } + + // Add processes to the menu. + + numberOfProcesses = PhGetIntegerSetting(L"IconProcesses"); + item = PhFindEMenuItem(menu, 0, L"Processes", 0); + + if (item) + PhMwpAddIconProcesses(item, numberOfProcesses); + + // Fix up the Computer menu. + PhMwpSetupComputerMenu(menu); + + // Give plugins a chance to modify the menu. + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, PhMainWndHandle, 0); + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackIconMenuInitializing), &menuInfo); + } + + SetForegroundWindow(PhMainWndHandle); // window must be foregrounded so menu will disappear properly + item = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + Location.x, + Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + if (PhPluginsEnabled && !handled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + handled = PhHandleMiniProcessMenuItem(item); + + if (!handled) + handled = PhMwpExecuteComputerCommand(item->Id); + + if (!handled) + { + switch (item->Id) + { + case ID_ICON_SHOWHIDEPROCESSHACKER: + SendMessage(PhMainWndHandle, WM_PH_TOGGLE_VISIBLE, 0, 0); + break; + case ID_ICON_SYSTEMINFORMATION: + SendMessage(PhMainWndHandle, WM_COMMAND, ID_VIEW_SYSTEMINFORMATION, 0); + break; + case ID_NOTIFICATIONS_ENABLEALL: + NotifyIconNotifyMask |= PH_NOTIFY_VALID_MASK; + break; + case ID_NOTIFICATIONS_DISABLEALL: + NotifyIconNotifyMask &= ~PH_NOTIFY_VALID_MASK; + break; + case ID_NOTIFICATIONS_NEWPROCESSES: + case ID_NOTIFICATIONS_TERMINATEDPROCESSES: + case ID_NOTIFICATIONS_NEWSERVICES: + case ID_NOTIFICATIONS_STARTEDSERVICES: + case ID_NOTIFICATIONS_STOPPEDSERVICES: + case ID_NOTIFICATIONS_DELETEDSERVICES: + { + ULONG bit; + + switch (item->Id) + { + case ID_NOTIFICATIONS_NEWPROCESSES: + bit = PH_NOTIFY_PROCESS_CREATE; + break; + case ID_NOTIFICATIONS_TERMINATEDPROCESSES: + bit = PH_NOTIFY_PROCESS_DELETE; + break; + case ID_NOTIFICATIONS_NEWSERVICES: + bit = PH_NOTIFY_SERVICE_CREATE; + break; + case ID_NOTIFICATIONS_STARTEDSERVICES: + bit = PH_NOTIFY_SERVICE_START; + break; + case ID_NOTIFICATIONS_STOPPEDSERVICES: + bit = PH_NOTIFY_SERVICE_STOP; + break; + case ID_NOTIFICATIONS_DELETEDSERVICES: + bit = PH_NOTIFY_SERVICE_DELETE; + break; + } + + NotifyIconNotifyMask ^= bit; + } + break; + case ID_ICON_EXIT: + SendMessage(PhMainWndHandle, WM_COMMAND, ID_HACKER_EXIT, 0); + break; + } + } + } + + PhDestroyEMenu(menu); +} + +VOID PhShowIconNotification( + _In_ PWSTR Title, + _In_ PWSTR Text, + _In_ ULONG Flags + ) +{ + PhNfShowBalloonTip(0, Title, Text, 10, Flags); +} + +VOID PhShowDetailsForIconNotification( + VOID + ) +{ + switch (LastNotificationType) + { + case PH_NOTIFY_PROCESS_CREATE: + { + PPH_PROCESS_NODE processNode; + + if (processNode = PhFindProcessNode(LastNotificationDetails.ProcessId)) + { + ProcessHacker_SelectTabPage(PhMainWndHandle, ProcessesTabIndex); + ProcessHacker_SelectProcessNode(PhMainWndHandle, processNode); + ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE); + } + } + break; + case PH_NOTIFY_SERVICE_CREATE: + case PH_NOTIFY_SERVICE_START: + case PH_NOTIFY_SERVICE_STOP: + { + PPH_SERVICE_ITEM serviceItem; + + if (LastNotificationDetails.ServiceName && + (serviceItem = PhReferenceServiceItem(LastNotificationDetails.ServiceName->Buffer))) + { + ProcessHacker_SelectTabPage(PhMainWndHandle, ServicesTabIndex); + ProcessHacker_SelectServiceItem(PhMainWndHandle, serviceItem); + ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE); + + PhDereferenceObject(serviceItem); + } + } + break; + } +} + +VOID PhMwpClearLastNotificationDetails( + VOID + ) +{ + if (LastNotificationType & + (PH_NOTIFY_SERVICE_CREATE | PH_NOTIFY_SERVICE_DELETE | PH_NOTIFY_SERVICE_START | PH_NOTIFY_SERVICE_STOP)) + { + PhClearReference(&LastNotificationDetails.ServiceName); + } + + LastNotificationType = 0; + memset(&LastNotificationDetails, 0, sizeof(LastNotificationDetails)); +} + +BOOLEAN PhMwpPluginNotifyEvent( + _In_ ULONG Type, + _In_ PVOID Parameter + ) +{ + PH_PLUGIN_NOTIFY_EVENT notifyEvent; + + notifyEvent.Type = Type; + notifyEvent.Handled = FALSE; + notifyEvent.Parameter = Parameter; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackNotifyEvent), ¬ifyEvent); + + return notifyEvent.Handled; +} + +VOID PhMwpShowProcessProperties( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PPH_PROCESS_PROPCONTEXT propContext; + + propContext = PhCreateProcessPropContext( + PhMainWndHandle, + ProcessItem + ); + + if (propContext) + { + PhShowProcessProperties(propContext); + PhDereferenceObject(propContext); + } +} + +BOOLEAN PhMwpCurrentUserProcessTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_NODE processNode = (PPH_PROCESS_NODE)Node; + + if (!processNode->ProcessItem->UserName) + return FALSE; + + if (!PhCurrentUserName) + return FALSE; + + if (!PhEqualString(processNode->ProcessItem->UserName, PhCurrentUserName, TRUE)) + return FALSE; + + return TRUE; +} + +BOOLEAN PhMwpSignedProcessTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_NODE processNode = (PPH_PROCESS_NODE)Node; + + if (processNode->ProcessItem->VerifyResult == VrTrusted) + return FALSE; + + return TRUE; +} + +BOOLEAN PhMwpExecuteProcessPriorityCommand( + _In_ ULONG Id, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + ULONG priorityClass; + + switch (Id) + { + case ID_PRIORITY_REALTIME: + priorityClass = PROCESS_PRIORITY_CLASS_REALTIME; + break; + case ID_PRIORITY_HIGH: + priorityClass = PROCESS_PRIORITY_CLASS_HIGH; + break; + case ID_PRIORITY_ABOVENORMAL: + priorityClass = PROCESS_PRIORITY_CLASS_ABOVE_NORMAL; + break; + case ID_PRIORITY_NORMAL: + priorityClass = PROCESS_PRIORITY_CLASS_NORMAL; + break; + case ID_PRIORITY_BELOWNORMAL: + priorityClass = PROCESS_PRIORITY_CLASS_BELOW_NORMAL; + break; + case ID_PRIORITY_IDLE: + priorityClass = PROCESS_PRIORITY_CLASS_IDLE; + break; + default: + return FALSE; + } + + PhUiSetPriorityProcesses(PhMainWndHandle, Processes, NumberOfProcesses, priorityClass); + + return TRUE; +} + +BOOLEAN PhMwpExecuteProcessIoPriorityCommand( + _In_ ULONG Id, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + IO_PRIORITY_HINT ioPriority; + + switch (Id) + { + case ID_IOPRIORITY_VERYLOW: + ioPriority = IoPriorityVeryLow; + break; + case ID_IOPRIORITY_LOW: + ioPriority = IoPriorityLow; + break; + case ID_IOPRIORITY_NORMAL: + ioPriority = IoPriorityNormal; + break; + case ID_IOPRIORITY_HIGH: + ioPriority = IoPriorityHigh; + break; + default: + return FALSE; + } + + PhUiSetIoPriorityProcesses(PhMainWndHandle, Processes, NumberOfProcesses, ioPriority); + + return TRUE; +} + +VOID PhMwpSetProcessMenuPriorityChecks( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ BOOLEAN SetPriority, + _In_ BOOLEAN SetIoPriority, + _In_ BOOLEAN SetPagePriority + ) +{ + HANDLE processHandle; + PROCESS_PRIORITY_CLASS priorityClass = { 0 }; + IO_PRIORITY_HINT ioPriority = -1; + ULONG pagePriority = -1; + ULONG id = 0; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + ProcessId + ))) + { + if (SetPriority) + { + NtQueryInformationProcess(processHandle, ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS), NULL); + } + + if (SetIoPriority && WindowsVersion >= WINDOWS_VISTA) + { + if (!NT_SUCCESS(PhGetProcessIoPriority( + processHandle, + &ioPriority + ))) + { + ioPriority = -1; + } + } + + if (SetPagePriority && WindowsVersion >= WINDOWS_VISTA) + { + if (!NT_SUCCESS(PhGetProcessPagePriority( + processHandle, + &pagePriority + ))) + { + pagePriority = -1; + } + } + + NtClose(processHandle); + } + + if (SetPriority) + { + switch (priorityClass.PriorityClass) + { + case PROCESS_PRIORITY_CLASS_REALTIME: + id = ID_PRIORITY_REALTIME; + break; + case PROCESS_PRIORITY_CLASS_HIGH: + id = ID_PRIORITY_HIGH; + break; + case PROCESS_PRIORITY_CLASS_ABOVE_NORMAL: + id = ID_PRIORITY_ABOVENORMAL; + break; + case PROCESS_PRIORITY_CLASS_NORMAL: + id = ID_PRIORITY_NORMAL; + break; + case PROCESS_PRIORITY_CLASS_BELOW_NORMAL: + id = ID_PRIORITY_BELOWNORMAL; + break; + case PROCESS_PRIORITY_CLASS_IDLE: + id = ID_PRIORITY_IDLE; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } + + if (SetIoPriority && ioPriority != -1) + { + id = 0; + + switch (ioPriority) + { + case IoPriorityVeryLow: + id = ID_IOPRIORITY_VERYLOW; + break; + case IoPriorityLow: + id = ID_IOPRIORITY_LOW; + break; + case IoPriorityNormal: + id = ID_IOPRIORITY_NORMAL; + break; + case IoPriorityHigh: + id = ID_IOPRIORITY_HIGH; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } + + if (SetPagePriority && pagePriority != -1) + { + id = 0; + + switch (pagePriority) + { + case MEMORY_PRIORITY_VERY_LOW: + id = ID_PAGEPRIORITY_VERYLOW; + break; + case MEMORY_PRIORITY_LOW: + id = ID_PAGEPRIORITY_LOW; + break; + case MEMORY_PRIORITY_MEDIUM: + id = ID_PAGEPRIORITY_MEDIUM; + break; + case MEMORY_PRIORITY_BELOW_NORMAL: + id = ID_PAGEPRIORITY_BELOWNORMAL; + break; + case MEMORY_PRIORITY_NORMAL: + id = ID_PAGEPRIORITY_NORMAL; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } +} + +VOID PhMwpInitializeProcessMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_PROCESS_ITEM *Processes, + _In_ ULONG NumberOfProcesses + ) +{ + PPH_EMENU_ITEM item; + + if (NumberOfProcesses == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfProcesses == 1) + { + // All menu items are enabled by default. + + // If the user selected a fake process, disable all but + // a few menu items. + if (PH_IS_FAKE_PROCESS_ID(Processes[0]->ProcessId)) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_PROCESS_PROPERTIES, TRUE); + PhEnableEMenuItem(Menu, ID_PROCESS_SEARCHONLINE, TRUE); + } + } + else + { + ULONG menuItemsMultiEnabled[] = + { + ID_PROCESS_TERMINATE, + ID_PROCESS_SUSPEND, + ID_PROCESS_RESUME, + ID_MISCELLANEOUS_REDUCEWORKINGSET, + ID_PROCESS_COPY + }; + ULONG i; + + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + + // Enable the Miscellaneous menu item but disable its children. + if (item = PhFindEMenuItem(Menu, 0, L"Miscellaneous", 0)) + { + item->Flags &= ~PH_EMENU_DISABLED; + PhSetFlagsAllEMenuItems(item, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + + // Enable the Priority menu item. + if (item = PhFindEMenuItem(Menu, 0, L"Priority", 0)) + item->Flags &= ~PH_EMENU_DISABLED; + + // Enable the I/O Priority menu item. + if (item = PhFindEMenuItem(Menu, 0, L"I/O Priority", 0)) + item->Flags &= ~PH_EMENU_DISABLED; + + // These menu items are capable of manipulating + // multiple processes. + for (i = 0; i < sizeof(menuItemsMultiEnabled) / sizeof(ULONG); i++) + { + PhSetFlagsEMenuItem(Menu, menuItemsMultiEnabled[i], PH_EMENU_DISABLED, 0); + } + } + + // Remove irrelevant menu items. + if (WindowsVersion < WINDOWS_VISTA) + { + // Remove I/O priority. + if (item = PhFindEMenuItem(Menu, PH_EMENU_FIND_DESCEND, L"I/O Priority", 0)) + PhDestroyEMenuItem(item); + // Remove page priority. + if (item = PhFindEMenuItem(Menu, PH_EMENU_FIND_DESCEND, L"Page Priority", 0)) + PhDestroyEMenuItem(item); + } + + // Suspend/Resume + if (NumberOfProcesses == 1) + { + if (Processes[0]->IsSuspended) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_PROCESS_SUSPEND)) + PhDestroyEMenuItem(item); + } + + if (!Processes[0]->IsPartiallySuspended) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_PROCESS_RESUME)) + PhDestroyEMenuItem(item); + } + } + + // Virtualization + if (NumberOfProcesses == 1) + { + HANDLE processHandle; + HANDLE tokenHandle; + BOOLEAN allowed = FALSE; + BOOLEAN enabled = FALSE; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Processes[0]->ProcessId + ))) + { + if (NT_SUCCESS(PhOpenProcessToken( + processHandle, + TOKEN_QUERY, + &tokenHandle + ))) + { + PhGetTokenIsVirtualizationAllowed(tokenHandle, &allowed); + PhGetTokenIsVirtualizationEnabled(tokenHandle, &enabled); + SelectedProcessVirtualizationEnabled = enabled; + + NtClose(tokenHandle); + } + + NtClose(processHandle); + } + + if (!allowed) + PhSetFlagsEMenuItem(Menu, ID_PROCESS_VIRTUALIZATION, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + else if (enabled) + PhSetFlagsEMenuItem(Menu, ID_PROCESS_VIRTUALIZATION, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + // Priority + if (NumberOfProcesses == 1) + { + PhMwpSetProcessMenuPriorityChecks(Menu, Processes[0]->ProcessId, TRUE, TRUE, TRUE); + } + + item = PhFindEMenuItem(Menu, 0, L"Window", 0); + + if (item) + { + // Window menu + if (NumberOfProcesses == 1) + { + WINDOWPLACEMENT placement = { sizeof(placement) }; + + // Get a handle to the process' top-level window (if any). + SelectedProcessWindowHandle = PhGetProcessMainWindow(Processes[0]->ProcessId, Processes[0]->QueryHandle); + + if (!SelectedProcessWindowHandle) + item->Flags |= PH_EMENU_DISABLED; + + GetWindowPlacement(SelectedProcessWindowHandle, &placement); + + if (placement.showCmd == SW_MINIMIZE) + PhEnableEMenuItem(item, ID_WINDOW_MINIMIZE, FALSE); + else if (placement.showCmd == SW_MAXIMIZE) + PhEnableEMenuItem(item, ID_WINDOW_MAXIMIZE, FALSE); + else if (placement.showCmd == SW_NORMAL) + PhEnableEMenuItem(item, ID_WINDOW_RESTORE, FALSE); + } + else + { + item->Flags |= PH_EMENU_DISABLED; + } + } + + // Remove irrelevant menu items (continued) + if (!WINDOWS_HAS_UAC) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_PROCESS_VIRTUALIZATION)) + PhDestroyEMenuItem(item); + } +} + +VOID PhShowProcessContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PH_PLUGIN_MENU_INFORMATION menuInfo; + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + + if (numberOfProcesses != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_PROCESS), 0); + PhSetFlagsEMenuItem(menu, ID_PROCESS_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhMwpInitializeProcessMenu(menu, processes, numberOfProcesses); + PhInsertCopyCellEMenuItem(menu, ID_PROCESS_COPY, ProcessTreeListHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, PhMainWndHandle, 0); + menuInfo.u.Process.Processes = processes; + menuInfo.u.Process.NumberOfProcesses = numberOfProcesses; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(PhMainWndHandle, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(processes); +} + +VOID PhMwpOnProcessAdded( + _In_ _Assume_refs_(1) PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG RunId + ) +{ + PPH_PROCESS_NODE processNode; + + if (!ProcessesNeedsRedraw) + { + TreeNew_SetRedraw(ProcessTreeListHandle, FALSE); + ProcessesNeedsRedraw = TRUE; + } + + processNode = PhAddProcessNode(ProcessItem, RunId); + + if (RunId != 1) + { + PPH_PROCESS_ITEM parentProcess; + HANDLE parentProcessId = NULL; + PPH_STRING parentName = NULL; + + if (parentProcess = PhReferenceProcessItemForParent( + ProcessItem->ParentProcessId, + ProcessItem->ProcessId, + &ProcessItem->CreateTime + )) + { + parentProcessId = parentProcess->ProcessId; + parentName = parentProcess->ProcessName; + } + + PhLogProcessEntry( + PH_LOG_ENTRY_PROCESS_CREATE, + ProcessItem->ProcessId, + NULL, + ProcessItem->ProcessName, + parentProcessId, + parentName + ); + + if (NotifyIconNotifyMask & PH_NOTIFY_PROCESS_CREATE) + { + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_PROCESS_CREATE, ProcessItem)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_PROCESS_CREATE; + LastNotificationDetails.ProcessId = ProcessItem->ProcessId; + + PhShowIconNotification(L"Process Created", PhaFormatString( + L"The process %s (%u) was created by %s (%u)", + ProcessItem->ProcessName->Buffer, + HandleToUlong(ProcessItem->ProcessId), + PhGetStringOrDefault(parentName, L"Unknown process"), + HandleToUlong(ProcessItem->ParentProcessId) + )->Buffer, NIIF_INFO); + } + } + + if (parentProcess) + PhDereferenceObject(parentProcess); + + if (PhCsScrollToNewProcesses) + ProcessToScrollTo = processNode; + } + else + { + if (NeedsSelectPid != 0) + { + if (processNode->ProcessId == UlongToHandle(NeedsSelectPid)) + ProcessToScrollTo = processNode; + } + } + + // PhCreateProcessNode has its own reference. + PhDereferenceObject(ProcessItem); +} + +VOID PhMwpOnProcessModified( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PhUpdateProcessNode(PhFindProcessNode(ProcessItem->ProcessId)); + + if (SignedFilterEntry) + PhApplyTreeNewFilters(PhGetFilterSupportProcessTreeList()); +} + +VOID PhMwpOnProcessRemoved( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PPH_PROCESS_NODE processNode; + + if (!ProcessesNeedsRedraw) + { + TreeNew_SetRedraw(ProcessTreeListHandle, FALSE); + ProcessesNeedsRedraw = TRUE; + } + + PhLogProcessEntry(PH_LOG_ENTRY_PROCESS_DELETE, ProcessItem->ProcessId, ProcessItem->QueryHandle, ProcessItem->ProcessName, NULL, NULL); + + if (NotifyIconNotifyMask & PH_NOTIFY_PROCESS_DELETE) + { + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_PROCESS_DELETE, ProcessItem)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_PROCESS_DELETE; + LastNotificationDetails.ProcessId = ProcessItem->ProcessId; + + PhShowIconNotification(L"Process Terminated", PhaFormatString( + L"The process %s (%u) was terminated.", + ProcessItem->ProcessName->Buffer, + HandleToUlong(ProcessItem->ProcessId) + )->Buffer, NIIF_INFO); + } + } + + processNode = PhFindProcessNode(ProcessItem->ProcessId); + PhRemoveProcessNode(processNode); + + if (ProcessToScrollTo == processNode) // shouldn't happen, but just in case + ProcessToScrollTo = NULL; +} + +VOID PhMwpOnProcessesUpdated( + VOID + ) +{ + // The modified notification is only sent for special cases. + // We have to invalidate the text on each update. + PhTickProcessNodes(); + + if (PhPluginsEnabled) + { + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackProcessesUpdated), NULL); + } + + if (ProcessesNeedsRedraw) + { + TreeNew_SetRedraw(ProcessTreeListHandle, TRUE); + ProcessesNeedsRedraw = FALSE; + } + + if (NeedsSelectPid != 0) + { + if (ProcessToScrollTo) + { + PhSelectAndEnsureVisibleProcessNode(ProcessToScrollTo); + ProcessToScrollTo = NULL; + } + + NeedsSelectPid = 0; + } + + if (ProcessToScrollTo) + { + TreeNew_EnsureVisible(ProcessTreeListHandle, &ProcessToScrollTo->Node); + ProcessToScrollTo = NULL; + } +} + +VOID PhMwpNeedServiceTreeList( + VOID + ) +{ + if (!ServiceTreeListLoaded) + { + ServiceTreeListLoaded = TRUE; + + PhLoadSettingsServiceTreeList(); + + if (PhGetIntegerSetting(L"HideDriverServices")) + { + DriverFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportServiceTreeList(), PhMwpDriverServiceTreeFilter, NULL); + } + + if (ServicesPendingList) + { + PPH_SERVICE_ITEM serviceItem; + ULONG enumerationKey = 0; + + while (PhEnumPointerList(ServicesPendingList, &enumerationKey, (PVOID *)&serviceItem)) + { + PhMwpOnServiceAdded(serviceItem, 1); + } + + // Force a re-draw. + PhMwpOnServicesUpdated(); + + PhClearReference(&ServicesPendingList); + } + } +} + +BOOLEAN PhMwpDriverServiceTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_NODE serviceNode = (PPH_SERVICE_NODE)Node; + + if (serviceNode->ServiceItem->Type & SERVICE_DRIVER) + return FALSE; + + return TRUE; +} + +VOID PhMwpInitializeServiceMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_SERVICE_ITEM *Services, + _In_ ULONG NumberOfServices + ) +{ + if (NumberOfServices == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfServices == 1) + { + if (!Services[0]->ProcessId) + PhEnableEMenuItem(Menu, ID_SERVICE_GOTOPROCESS, FALSE); + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_SERVICE_COPY, TRUE); + } + + if (NumberOfServices == 1) + { + switch (Services[0]->State) + { + case SERVICE_RUNNING: + { + PhEnableEMenuItem(Menu, ID_SERVICE_START, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_CONTINUE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_PAUSE, + Services[0]->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + PhEnableEMenuItem(Menu, ID_SERVICE_STOP, + Services[0]->ControlsAccepted & SERVICE_ACCEPT_STOP); + } + break; + case SERVICE_PAUSED: + { + PhEnableEMenuItem(Menu, ID_SERVICE_START, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_CONTINUE, + Services[0]->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + PhEnableEMenuItem(Menu, ID_SERVICE_PAUSE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_STOP, + Services[0]->ControlsAccepted & SERVICE_ACCEPT_STOP); + } + break; + case SERVICE_STOPPED: + { + PhEnableEMenuItem(Menu, ID_SERVICE_CONTINUE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_PAUSE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_STOP, FALSE); + } + break; + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_STOP_PENDING: + { + PhEnableEMenuItem(Menu, ID_SERVICE_START, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_CONTINUE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_PAUSE, FALSE); + PhEnableEMenuItem(Menu, ID_SERVICE_STOP, FALSE); + } + break; + } + + if (!(Services[0]->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE)) + { + PPH_EMENU_ITEM item; + + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_SERVICE_CONTINUE)) + PhDestroyEMenuItem(item); + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_SERVICE_PAUSE)) + PhDestroyEMenuItem(item); + } + } +} + +VOID PhShowServiceContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PH_PLUGIN_MENU_INFORMATION menuInfo; + PPH_SERVICE_ITEM *services; + ULONG numberOfServices; + + PhGetSelectedServiceItems(&services, &numberOfServices); + + if (numberOfServices != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_SERVICE), 0); + PhSetFlagsEMenuItem(menu, ID_SERVICE_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhMwpInitializeServiceMenu(menu, services, numberOfServices); + PhInsertCopyCellEMenuItem(menu, ID_SERVICE_COPY, ServiceTreeListHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, PhMainWndHandle, 0); + menuInfo.u.Service.Services = services; + menuInfo.u.Service.NumberOfServices = numberOfServices; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackServiceMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(PhMainWndHandle, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(services); +} + +VOID PhMwpOnServiceAdded( + _In_ _Assume_refs_(1) PPH_SERVICE_ITEM ServiceItem, + _In_ ULONG RunId + ) +{ + PPH_SERVICE_NODE serviceNode; + + if (ServiceTreeListLoaded) + { + if (!ServicesNeedsRedraw) + { + TreeNew_SetRedraw(ServiceTreeListHandle, FALSE); + ServicesNeedsRedraw = TRUE; + } + + serviceNode = PhAddServiceNode(ServiceItem, RunId); + // ServiceItem dereferenced below + } + else + { + if (!ServicesPendingList) + ServicesPendingList = PhCreatePointerList(100); + + PhAddItemPointerList(ServicesPendingList, ServiceItem); + } + + if (RunId != 1) + { + PhLogServiceEntry(PH_LOG_ENTRY_SERVICE_CREATE, ServiceItem->Name, ServiceItem->DisplayName); + + if (NotifyIconNotifyMask & PH_NOTIFY_SERVICE_CREATE) + { + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_SERVICE_CREATE, ServiceItem)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_SERVICE_CREATE; + PhSwapReference(&LastNotificationDetails.ServiceName, ServiceItem->Name); + + PhShowIconNotification(L"Service Created", PhaFormatString( + L"The service %s (%s) has been created.", + ServiceItem->Name->Buffer, + ServiceItem->DisplayName->Buffer + )->Buffer, NIIF_INFO); + } + } + } + + if (ServiceTreeListLoaded) + PhDereferenceObject(ServiceItem); +} + +VOID PhMwpOnServiceModified( + _In_ struct _PH_SERVICE_MODIFIED_DATA *ServiceModifiedData + ) +{ + PH_SERVICE_CHANGE serviceChange; + UCHAR logEntryType; + + if (ServiceTreeListLoaded) + { + //if (!ServicesNeedsRedraw) + //{ + // TreeNew_SetRedraw(ServiceTreeListHandle, FALSE); + // ServicesNeedsRedraw = TRUE; + //} + + PhUpdateServiceNode(PhFindServiceNode(ServiceModifiedData->Service)); + + if (DriverFilterEntry) + PhApplyTreeNewFilters(PhGetFilterSupportServiceTreeList()); + } + + serviceChange = PhGetServiceChange(ServiceModifiedData); + + switch (serviceChange) + { + case ServiceStarted: + logEntryType = PH_LOG_ENTRY_SERVICE_START; + break; + case ServiceStopped: + logEntryType = PH_LOG_ENTRY_SERVICE_STOP; + break; + case ServiceContinued: + logEntryType = PH_LOG_ENTRY_SERVICE_CONTINUE; + break; + case ServicePaused: + logEntryType = PH_LOG_ENTRY_SERVICE_PAUSE; + break; + default: + logEntryType = 0; + break; + } + + if (logEntryType != 0) + PhLogServiceEntry(logEntryType, ServiceModifiedData->Service->Name, ServiceModifiedData->Service->DisplayName); + + if (NotifyIconNotifyMask & (PH_NOTIFY_SERVICE_START | PH_NOTIFY_SERVICE_STOP)) + { + PPH_SERVICE_ITEM serviceItem; + + serviceItem = ServiceModifiedData->Service; + + if (serviceChange == ServiceStarted && (NotifyIconNotifyMask & PH_NOTIFY_SERVICE_START)) + { + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_SERVICE_START, serviceItem)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_SERVICE_START; + PhSwapReference(&LastNotificationDetails.ServiceName, serviceItem->Name); + + PhShowIconNotification(L"Service Started", PhaFormatString( + L"The service %s (%s) has been started.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + )->Buffer, NIIF_INFO); + } + } + else if (serviceChange == ServiceStopped && (NotifyIconNotifyMask & PH_NOTIFY_SERVICE_STOP)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_SERVICE_STOP; + PhSwapReference(&LastNotificationDetails.ServiceName, serviceItem->Name); + + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_SERVICE_STOP, serviceItem)) + { + PhShowIconNotification(L"Service Stopped", PhaFormatString( + L"The service %s (%s) has been stopped.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + )->Buffer, NIIF_INFO); + } + } + } +} + +VOID PhMwpOnServiceRemoved( + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + if (ServiceTreeListLoaded) + { + if (!ServicesNeedsRedraw) + { + TreeNew_SetRedraw(ServiceTreeListHandle, FALSE); + ServicesNeedsRedraw = TRUE; + } + } + + PhLogServiceEntry(PH_LOG_ENTRY_SERVICE_DELETE, ServiceItem->Name, ServiceItem->DisplayName); + + if (NotifyIconNotifyMask & PH_NOTIFY_SERVICE_CREATE) + { + if (!PhPluginsEnabled || !PhMwpPluginNotifyEvent(PH_NOTIFY_SERVICE_DELETE, ServiceItem)) + { + PhMwpClearLastNotificationDetails(); + LastNotificationType = PH_NOTIFY_SERVICE_DELETE; + PhSwapReference(&LastNotificationDetails.ServiceName, ServiceItem->Name); + + PhShowIconNotification(L"Service Deleted", PhaFormatString( + L"The service %s (%s) has been deleted.", + ServiceItem->Name->Buffer, + ServiceItem->DisplayName->Buffer + )->Buffer, NIIF_INFO); + } + } + + if (ServiceTreeListLoaded) + { + PhRemoveServiceNode(PhFindServiceNode(ServiceItem)); + } + else + { + if (ServicesPendingList) + { + HANDLE pointerHandle; + + // Remove the service from the pending list so we don't try to add it + // later. + + if (pointerHandle = PhFindItemPointerList(ServicesPendingList, ServiceItem)) + PhRemoveItemPointerList(ServicesPendingList, pointerHandle); + + PhDereferenceObject(ServiceItem); + } + } +} + +VOID PhMwpOnServicesUpdated( + VOID + ) +{ + if (ServiceTreeListLoaded) + { + PhTickServiceNodes(); + + if (ServicesNeedsRedraw) + { + TreeNew_SetRedraw(ServiceTreeListHandle, TRUE); + ServicesNeedsRedraw = FALSE; + } + } +} + +VOID PhMwpNeedNetworkTreeList( + VOID + ) +{ + if (!NetworkTreeListLoaded) + { + NetworkTreeListLoaded = TRUE; + + PhLoadSettingsNetworkTreeList(); + } +} + +BOOLEAN PhMwpCurrentUserNetworkTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_NODE networkNode = (PPH_NETWORK_NODE)Node; + PPH_PROCESS_NODE processNode; + + processNode = PhFindProcessNode(networkNode->NetworkItem->ProcessId); + + if (processNode) + return PhMwpCurrentUserProcessTreeFilter(&processNode->Node, NULL); + + return TRUE; +} + +BOOLEAN PhMwpSignedNetworkTreeFilter( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_NODE networkNode = (PPH_NETWORK_NODE)Node; + PPH_PROCESS_NODE processNode; + + processNode = PhFindProcessNode(networkNode->NetworkItem->ProcessId); + + if (processNode) + return PhMwpSignedProcessTreeFilter(&processNode->Node, NULL); + + return TRUE; +} + +VOID PhMwpInitializeNetworkMenu( + _In_ PPH_EMENU Menu, + _In_ PPH_NETWORK_ITEM *NetworkItems, + _In_ ULONG NumberOfNetworkItems + ) +{ + ULONG i; + PPH_EMENU_ITEM item; + + if (NumberOfNetworkItems == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfNetworkItems == 1) + { + if (!NetworkItems[0]->ProcessId) + PhEnableEMenuItem(Menu, ID_NETWORK_GOTOPROCESS, FALSE); + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_NETWORK_CLOSE, TRUE); + PhEnableEMenuItem(Menu, ID_NETWORK_COPY, TRUE); + } + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_NETWORK_VIEWSTACK)) + PhDestroyEMenuItem(item); + } + + // Go to Service + if (NumberOfNetworkItems != 1 || !NetworkItems[0]->OwnerName) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_NETWORK_GOTOSERVICE)) + PhDestroyEMenuItem(item); + } + + // Close + if (NumberOfNetworkItems != 0) + { + BOOLEAN closeOk = TRUE; + + for (i = 0; i < NumberOfNetworkItems; i++) + { + if ( + NetworkItems[i]->ProtocolType != PH_TCP4_NETWORK_PROTOCOL || + NetworkItems[i]->State != MIB_TCP_STATE_ESTAB + ) + { + closeOk = FALSE; + break; + } + } + + if (!closeOk) + PhEnableEMenuItem(Menu, ID_NETWORK_CLOSE, FALSE); + } +} + +VOID PhShowNetworkContextMenu( + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PH_PLUGIN_MENU_INFORMATION menuInfo; + PPH_NETWORK_ITEM *networkItems; + ULONG numberOfNetworkItems; + + PhGetSelectedNetworkItems(&networkItems, &numberOfNetworkItems); + + if (numberOfNetworkItems != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_NETWORK), 0); + PhSetFlagsEMenuItem(menu, ID_NETWORK_GOTOPROCESS, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhMwpInitializeNetworkMenu(menu, networkItems, numberOfNetworkItems); + PhInsertCopyCellEMenuItem(menu, ID_NETWORK_COPY, NetworkTreeListHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, PhMainWndHandle, 0); + menuInfo.u.Network.NetworkItems = networkItems; + menuInfo.u.Network.NumberOfNetworkItems = numberOfNetworkItems; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackNetworkMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(PhMainWndHandle, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(networkItems); +} + +VOID PhMwpOnNetworkItemAdded( + _In_ ULONG RunId, + _In_ _Assume_refs_(1) PPH_NETWORK_ITEM NetworkItem + ) +{ + PPH_NETWORK_NODE networkNode; + + if (!NetworkNeedsRedraw) + { + TreeNew_SetRedraw(NetworkTreeListHandle, FALSE); + NetworkNeedsRedraw = TRUE; + } + + networkNode = PhAddNetworkNode(NetworkItem, RunId); + PhDereferenceObject(NetworkItem); +} + +VOID PhMwpOnNetworkItemModified( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + PhUpdateNetworkNode(PhFindNetworkNode(NetworkItem)); +} + +VOID PhMwpOnNetworkItemRemoved( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + if (!NetworkNeedsRedraw) + { + TreeNew_SetRedraw(NetworkTreeListHandle, FALSE); + NetworkNeedsRedraw = TRUE; + } + + PhRemoveNetworkNode(PhFindNetworkNode(NetworkItem)); +} + +VOID PhMwpOnNetworkItemsUpdated( + VOID + ) +{ + PhTickNetworkNodes(); + + if (NetworkNeedsRedraw) + { + TreeNew_SetRedraw(NetworkTreeListHandle, TRUE); + NetworkNeedsRedraw = FALSE; + } +} + +VOID PhMwpUpdateUsersMenu( + VOID + ) +{ + HMENU menu; + PSESSIONIDW sessions; + ULONG numberOfSessions; + ULONG i; + ULONG j; + MENUITEMINFO menuItemInfo = { sizeof(MENUITEMINFO) }; + + menu = SubMenuHandles[3]; + + // Delete all items in the Users menu. + while (DeleteMenu(menu, 0, MF_BYPOSITION)) ; + + if (WinStationEnumerateW(NULL, &sessions, &numberOfSessions)) + { + for (i = 0; i < numberOfSessions; i++) + { + HMENU userMenu; + PPH_STRING menuText; + PPH_STRING escapedMenuText; + WINSTATIONINFORMATION winStationInfo; + ULONG returnLength; + ULONG numberOfItems; + + if (!WinStationQueryInformationW( + NULL, + sessions[i].SessionId, + WinStationInformation, + &winStationInfo, + sizeof(WINSTATIONINFORMATION), + &returnLength + )) + { + winStationInfo.Domain[0] = 0; + winStationInfo.UserName[0] = 0; + } + + if (winStationInfo.Domain[0] == 0 || winStationInfo.UserName[0] == 0) + { + // Probably the Services or RDP-Tcp session. + continue; + } + + menuText = PhFormatString( + L"%u: %s\\%s", + sessions[i].SessionId, + winStationInfo.Domain, + winStationInfo.UserName + ); + escapedMenuText = PhEscapeStringForMenuPrefix(&menuText->sr); + PhDereferenceObject(menuText); + + userMenu = GetSubMenu(LoadMenu(PhInstanceHandle, MAKEINTRESOURCE(IDR_USER)), 0); + AppendMenu( + menu, + MF_STRING | MF_POPUP, + (UINT_PTR)userMenu, + escapedMenuText->Buffer + ); + + PhDereferenceObject(escapedMenuText); + + menuItemInfo.fMask = MIIM_DATA; + menuItemInfo.dwItemData = sessions[i].SessionId; + + numberOfItems = GetMenuItemCount(userMenu); + + if (numberOfItems != -1) + { + for (j = 0; j < numberOfItems; j++) + SetMenuItemInfo(userMenu, j, TRUE, &menuItemInfo); + } + } + + WinStationFreeMemory(sessions); + } + + DrawMenuBar(PhMainWndHandle); +} diff --git a/ProcessHacker/mdump.c b/ProcessHacker/mdump.c new file mode 100644 index 0000000..cdd2ddd --- /dev/null +++ b/ProcessHacker/mdump.c @@ -0,0 +1,468 @@ +/* + * Process Hacker - + * minidump writer + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4091) // Ignore 'no variable declared on typedef' +#include +#pragma warning(pop) + +#include +#include +#include + +#define WM_PH_MINIDUMP_STATUS_UPDATE (WM_APP + 301) + +#define PH_MINIDUMP_STATUS_UPDATE 1 +#define PH_MINIDUMP_COMPLETED 2 +#define PH_MINIDUMP_ERROR 3 + +typedef struct _PROCESS_MINIDUMP_CONTEXT +{ + HANDLE ProcessId; + PWSTR FileName; + MINIDUMP_TYPE DumpType; + BOOLEAN IsWow64; + + HANDLE ProcessHandle; + HANDLE FileHandle; + + HWND WindowHandle; + HANDLE ThreadHandle; + BOOLEAN Stop; + BOOLEAN Succeeded; + + ULONG LastTickCount; +} PROCESS_MINIDUMP_CONTEXT, *PPROCESS_MINIDUMP_CONTEXT; + +BOOLEAN PhpCreateProcessMiniDumpWithProgress( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PWSTR FileName, + _In_ MINIDUMP_TYPE DumpType + ); + +INT_PTR CALLBACK PhpProcessMiniDumpDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +BOOLEAN PhUiCreateDumpFileProcess( + _In_ HWND hWnd, + _In_ PPH_PROCESS_ITEM Process + ) +{ + static PH_FILETYPE_FILTER filters[] = + { + { L"Dump files (*.dmp)", L"*.dmp" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateSaveFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, PhaConcatStrings2(Process->ProcessName->Buffer, L".dmp")->Buffer); + + if (!PhShowFileDialog(hWnd, fileDialog)) + { + PhFreeFileDialog(fileDialog); + return FALSE; + } + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + PhFreeFileDialog(fileDialog); + + return PhpCreateProcessMiniDumpWithProgress( + hWnd, + Process->ProcessId, + fileName->Buffer, + // task manager uses these flags + MiniDumpWithFullMemory | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo + ); +} + +BOOLEAN PhpCreateProcessMiniDumpWithProgress( + _In_ HWND hWnd, + _In_ HANDLE ProcessId, + _In_ PWSTR FileName, + _In_ MINIDUMP_TYPE DumpType + ) +{ + NTSTATUS status; + PROCESS_MINIDUMP_CONTEXT context; + + memset(&context, 0, sizeof(PROCESS_MINIDUMP_CONTEXT)); + context.ProcessId = ProcessId; + context.FileName = FileName; + context.DumpType = DumpType; + + if (!NT_SUCCESS(status = PhOpenProcess( + &context.ProcessHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ProcessId + ))) + { + PhShowStatus(hWnd, L"Unable to open the process", status, 0); + return FALSE; + } + +#ifdef _WIN64 + PhGetProcessIsWow64(context.ProcessHandle, &context.IsWow64); +#endif + + status = PhCreateFileWin32( + &context.FileHandle, + FileName, + FILE_GENERIC_WRITE | DELETE, + 0, + 0, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + { + PhShowStatus(hWnd, L"Unable to access the dump file", status, 0); + NtClose(context.ProcessHandle); + return FALSE; + } + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PROGRESS), + hWnd, + PhpProcessMiniDumpDlgProc, + (LPARAM)&context + ); + + NtClose(context.FileHandle); + NtClose(context.ProcessHandle); + + return context.Succeeded; +} + +static BOOL CALLBACK PhpProcessMiniDumpCallback( + _In_ PVOID CallbackParam, + _In_ const PMINIDUMP_CALLBACK_INPUT CallbackInput, + _Inout_ PMINIDUMP_CALLBACK_OUTPUT CallbackOutput + ) +{ + PPROCESS_MINIDUMP_CONTEXT context = CallbackParam; + PPH_STRING message = NULL; + + // Don't try to send status updates if we're creating a dump of the current process. + if (context->ProcessId == NtCurrentProcessId()) + return TRUE; + + // MiniDumpWriteDump seems to get bored of calling the callback + // after it begins dumping the process handles. The code is + // still here in case they fix this problem in the future. + + switch (CallbackInput->CallbackType) + { + case CancelCallback: + { + if (context->Stop) + CallbackOutput->Cancel = TRUE; + } + break; + case ModuleCallback: + { + message = PhFormatString(L"Processing module %s...", CallbackInput->Module.FullPath); + } + break; + case ThreadCallback: + { + message = PhFormatString(L"Processing thread %u...", CallbackInput->Thread.ThreadId); + } + break; + } + + if (message) + { + SendMessage( + context->WindowHandle, + WM_PH_MINIDUMP_STATUS_UPDATE, + PH_MINIDUMP_STATUS_UPDATE, + (LPARAM)message->Buffer + ); + PhDereferenceObject(message); + } + + return TRUE; +} + +NTSTATUS PhpProcessMiniDumpThreadStart( + _In_ PVOID Parameter + ) +{ + PPROCESS_MINIDUMP_CONTEXT context = Parameter; + MINIDUMP_CALLBACK_INFORMATION callbackInfo; + + callbackInfo.CallbackRoutine = PhpProcessMiniDumpCallback; + callbackInfo.CallbackParam = context; + +#ifdef _WIN64 + if (context->IsWow64) + { + if (PhUiConnectToPhSvcEx(NULL, Wow64PhSvcMode, FALSE)) + { + NTSTATUS status; + PPH_STRING dbgHelpPath; + + dbgHelpPath = PhGetStringSetting(L"DbgHelpPath"); + PhSvcCallLoadDbgHelp(dbgHelpPath->Buffer); + PhDereferenceObject(dbgHelpPath); + + if (NT_SUCCESS(status = PhSvcCallWriteMiniDumpProcess( + context->ProcessHandle, + context->ProcessId, + context->FileHandle, + context->DumpType + ))) + { + context->Succeeded = TRUE; + } + else + { + // We may have an old version of dbghelp - in that case, try using minimal dump flags. + if (status == STATUS_INVALID_PARAMETER && NT_SUCCESS(status = PhSvcCallWriteMiniDumpProcess( + context->ProcessHandle, + context->ProcessId, + context->FileHandle, + MiniDumpWithFullMemory | MiniDumpWithHandleData + ))) + { + context->Succeeded = TRUE; + } + else + { + SendMessage( + context->WindowHandle, + WM_PH_MINIDUMP_STATUS_UPDATE, + PH_MINIDUMP_ERROR, + (LPARAM)PhNtStatusToDosError(status) + ); + } + } + + PhUiDisconnectFromPhSvc(); + + goto Completed; + } + else + { + if (PhShowMessage( + context->WindowHandle, + MB_YESNO | MB_ICONWARNING, + L"The process is 32-bit, but the 32-bit version of Process Hacker could not be located. " + L"A 64-bit dump will be created instead. Do you want to continue?" + ) == IDNO) + { + FILE_DISPOSITION_INFORMATION dispositionInfo; + IO_STATUS_BLOCK isb; + + dispositionInfo.DeleteFile = TRUE; + NtSetInformationFile( + context->FileHandle, + &isb, + &dispositionInfo, + sizeof(FILE_DISPOSITION_INFORMATION), + FileDispositionInformation + ); + + goto Completed; + } + } + } +#endif + + if (PhWriteMiniDumpProcess( + context->ProcessHandle, + context->ProcessId, + context->FileHandle, + context->DumpType, + NULL, + NULL, + &callbackInfo + )) + { + context->Succeeded = TRUE; + } + else + { + // We may have an old version of dbghelp - in that case, try using minimal dump flags. + if (GetLastError() == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER) && PhWriteMiniDumpProcess( + context->ProcessHandle, + context->ProcessId, + context->FileHandle, + MiniDumpWithFullMemory | MiniDumpWithHandleData, + NULL, + NULL, + &callbackInfo + )) + { + context->Succeeded = TRUE; + } + else + { + SendMessage( + context->WindowHandle, + WM_PH_MINIDUMP_STATUS_UPDATE, + PH_MINIDUMP_ERROR, + (LPARAM)GetLastError() + ); + } + } + +#ifdef _WIN64 +Completed: +#endif + SendMessage( + context->WindowHandle, + WM_PH_MINIDUMP_STATUS_UPDATE, + PH_MINIDUMP_COMPLETED, + 0 + ); + + return STATUS_SUCCESS; +} + +INT_PTR CALLBACK PhpProcessMiniDumpDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPROCESS_MINIDUMP_CONTEXT context = (PPROCESS_MINIDUMP_CONTEXT)lParam; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, L"Creating the dump file..."); + + PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE); + SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75); + + context->WindowHandle = hwndDlg; + context->ThreadHandle = PhCreateThread(0, PhpProcessMiniDumpThreadStart, context); + + if (!context->ThreadHandle) + { + PhShowStatus(hwndDlg, L"Unable to create the minidump thread", 0, GetLastError()); + EndDialog(hwndDlg, IDCANCEL); + } + + SetTimer(hwndDlg, 1, 500, NULL); + } + break; + case WM_DESTROY: + { + PPROCESS_MINIDUMP_CONTEXT context; + + context = (PPROCESS_MINIDUMP_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + NtClose(context->ThreadHandle); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + PPROCESS_MINIDUMP_CONTEXT context = + (PPROCESS_MINIDUMP_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE); + context->Stop = TRUE; + } + break; + } + } + break; + case WM_TIMER: + { + if (wParam == 1) + { + PPROCESS_MINIDUMP_CONTEXT context = + (PPROCESS_MINIDUMP_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + ULONG currentTickCount; + + currentTickCount = GetTickCount(); + + if (currentTickCount - context->LastTickCount >= 2000) + { + // No status message update for 2 seconds. + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, + (PWSTR)L"Creating the dump file..."); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROGRESSTEXT), NULL, FALSE); + + context->LastTickCount = currentTickCount; + } + } + } + break; + case WM_PH_MINIDUMP_STATUS_UPDATE: + { + PPROCESS_MINIDUMP_CONTEXT context; + + context = (PPROCESS_MINIDUMP_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + switch (wParam) + { + case PH_MINIDUMP_STATUS_UPDATE: + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, (PWSTR)lParam); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROGRESSTEXT), NULL, FALSE); + context->LastTickCount = GetTickCount(); + break; + case PH_MINIDUMP_ERROR: + PhShowStatus(hwndDlg, L"Unable to create the minidump", 0, (ULONG)lParam); + break; + case PH_MINIDUMP_COMPLETED: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/memedit.c b/ProcessHacker/memedit.c new file mode 100644 index 0000000..b23b4c5 --- /dev/null +++ b/ProcessHacker/memedit.c @@ -0,0 +1,531 @@ +/* + * Process Hacker - + * memory editor window + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +#define WM_PH_SELECT_OFFSET (WM_APP + 300) + +typedef struct _MEMORY_EDITOR_CONTEXT +{ + PH_AVL_LINKS Links; + union + { + struct + { + HANDLE ProcessId; + PVOID BaseAddress; + SIZE_T RegionSize; + }; + ULONG_PTR Key[3]; + }; + HANDLE ProcessHandle; + HWND WindowHandle; + PH_LAYOUT_MANAGER LayoutManager; + HWND HexEditHandle; + PUCHAR Buffer; + ULONG SelectOffset; + PPH_STRING Title; + ULONG Flags; + + BOOLEAN LoadCompleted; + BOOLEAN WriteAccess; +} MEMORY_EDITOR_CONTEXT, *PMEMORY_EDITOR_CONTEXT; + +INT NTAPI PhpMemoryEditorCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +INT_PTR CALLBACK PhpMemoryEditorDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PH_AVL_TREE PhMemoryEditorSet = PH_AVL_TREE_INIT(PhpMemoryEditorCompareFunction); +static RECT MinimumSize = { -1, -1, -1, -1 }; + +VOID PhShowMemoryEditorDialog( + _In_ HANDLE ProcessId, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize, + _In_ ULONG SelectOffset, + _In_ ULONG SelectLength, + _In_opt_ PPH_STRING Title, + _In_ ULONG Flags + ) +{ + PMEMORY_EDITOR_CONTEXT context; + MEMORY_EDITOR_CONTEXT lookupContext; + PPH_AVL_LINKS links; + + lookupContext.ProcessId = ProcessId; + lookupContext.BaseAddress = BaseAddress; + lookupContext.RegionSize = RegionSize; + + links = PhFindElementAvlTree(&PhMemoryEditorSet, &lookupContext.Links); + + if (!links) + { + context = PhAllocate(sizeof(MEMORY_EDITOR_CONTEXT)); + memset(context, 0, sizeof(MEMORY_EDITOR_CONTEXT)); + + context->ProcessId = ProcessId; + context->BaseAddress = BaseAddress; + context->RegionSize = RegionSize; + context->SelectOffset = SelectOffset; + PhSwapReference(&context->Title, Title); + context->Flags = Flags; + + context->WindowHandle = CreateDialogParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MEMEDIT), + NULL, + PhpMemoryEditorDlgProc, + (LPARAM)context + ); + + if (!context->LoadCompleted) + { + DestroyWindow(context->WindowHandle); + return; + } + + if (SelectOffset != -1) + PostMessage(context->WindowHandle, WM_PH_SELECT_OFFSET, SelectOffset, SelectLength); + + PhRegisterDialog(context->WindowHandle); + PhAddElementAvlTree(&PhMemoryEditorSet, &context->Links); + + ShowWindow(context->WindowHandle, SW_SHOW); + } + else + { + context = CONTAINING_RECORD(links, MEMORY_EDITOR_CONTEXT, Links); + + if (IsIconic(context->WindowHandle)) + ShowWindow(context->WindowHandle, SW_RESTORE); + else + SetForegroundWindow(context->WindowHandle); + + if (SelectOffset != -1) + PostMessage(context->WindowHandle, WM_PH_SELECT_OFFSET, SelectOffset, SelectLength); + + // Just in case. + if ((Flags & PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION) && ProcessId == NtCurrentProcessId()) + NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress); + } +} + +INT NTAPI PhpMemoryEditorCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PMEMORY_EDITOR_CONTEXT context1 = CONTAINING_RECORD(Links1, MEMORY_EDITOR_CONTEXT, Links); + PMEMORY_EDITOR_CONTEXT context2 = CONTAINING_RECORD(Links2, MEMORY_EDITOR_CONTEXT, Links); + + return memcmp(context1->Key, context2->Key, sizeof(context1->Key)); +} + +INT_PTR CALLBACK PhpMemoryEditorDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PMEMORY_EDITOR_CONTEXT context; + + if (uMsg != WM_INITDIALOG) + { + context = GetProp(hwndDlg, PhMakeContextAtom()); + } + else + { + context = (PMEMORY_EDITOR_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + + if (context->Title) + { + SetWindowText(hwndDlg, context->Title->Buffer); + } + else + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(context->ProcessId)) + { + SetWindowText(hwndDlg, PhaFormatString(L"%s (%u) (0x%Ix - 0x%Ix)", + processItem->ProcessName->Buffer, HandleToUlong(context->ProcessId), + context->BaseAddress, (ULONG_PTR)context->BaseAddress + context->RegionSize)->Buffer); + PhDereferenceObject(processItem); + } + } + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + + if (context->RegionSize > 1024 * 1024 * 1024) // 1 GB + { + PhShowError(NULL, L"Unable to edit the memory region because it is too large."); + return TRUE; + } + + if (!NT_SUCCESS(status = PhOpenProcess( + &context->ProcessHandle, + PROCESS_VM_READ, + context->ProcessId + ))) + { + PhShowStatus(NULL, L"Unable to open the process", status, 0); + return TRUE; + } + + context->Buffer = PhAllocatePage(context->RegionSize, NULL); + + if (!context->Buffer) + { + PhShowError(NULL, L"Unable to allocate memory for the buffer."); + return TRUE; + } + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + context->ProcessHandle, + context->BaseAddress, + context->Buffer, + context->RegionSize, + NULL + ))) + { + PhShowStatus(PhMainWndHandle, L"Unable to read memory", status, 0); + return TRUE; + } + + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_SAVE), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_BYTESPERROW), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_GOTO), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_WRITE), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_REREAD), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 290; + rect.bottom = 140; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + context->HexEditHandle = GetDlgItem(hwndDlg, IDC_MEMORY); + PhAddLayoutItem(&context->LayoutManager, context->HexEditHandle, NULL, PH_ANCHOR_ALL); + HexEdit_SetBuffer(context->HexEditHandle, context->Buffer, (ULONG)context->RegionSize); + + { + PH_RECTANGLE windowRectangle; + + windowRectangle.Position = PhGetIntegerPairSetting(L"MemEditPosition"); + windowRectangle.Size = PhGetScalableIntegerPairSetting(L"MemEditSize", TRUE).Pair; + PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); + + MoveWindow(hwndDlg, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + + // Implement cascading by saving an offsetted rectangle. + windowRectangle.Left += 20; + windowRectangle.Top += 20; + + PhSetIntegerPairSetting(L"MemEditPosition", windowRectangle.Position); + PhSetScalableIntegerPairSetting2(L"MemEditSize", windowRectangle.Size); + } + + { + PWSTR bytesPerRowStrings[7]; + ULONG i; + ULONG bytesPerRow; + + for (i = 0; i < sizeof(bytesPerRowStrings) / sizeof(PWSTR); i++) + bytesPerRowStrings[i] = PhaFormatString(L"%u bytes per row", 1 << (2 + i))->Buffer; + + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_BYTESPERROW), + bytesPerRowStrings, sizeof(bytesPerRowStrings) / sizeof(PWSTR)); + + bytesPerRow = PhGetIntegerSetting(L"MemEditBytesPerRow"); + + if (bytesPerRow >= 4) + { + HexEdit_SetBytesPerRow(context->HexEditHandle, bytesPerRow); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_BYTESPERROW), + PhaFormatString(L"%u bytes per row", bytesPerRow)->Buffer, FALSE); + } + } + + context->LoadCompleted = TRUE; + } + break; + case WM_DESTROY: + { + if (context->LoadCompleted) + { + PhSaveWindowPlacementToSetting(L"MemEditPosition", L"MemEditSize", hwndDlg); + PhRemoveElementAvlTree(&PhMemoryEditorSet, &context->Links); + PhUnregisterDialog(hwndDlg); + } + + RemoveProp(hwndDlg, PhMakeContextAtom()); + + PhDeleteLayoutManager(&context->LayoutManager); + + if (context->Buffer) PhFreePage(context->Buffer); + if (context->ProcessHandle) NtClose(context->ProcessHandle); + PhClearReference(&context->Title); + + if ((context->Flags & PH_MEMORY_EDITOR_UNMAP_VIEW_OF_SECTION) && context->ProcessId == NtCurrentProcessId()) + NtUnmapViewOfSection(NtCurrentProcess(), context->BaseAddress); + + PhFree(context); + } + break; + case WM_SHOWWINDOW: + { + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Binary files (*.bin)", L"*.bin" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_PROCESS_ITEM processItem; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + if (!context->Title && (processItem = PhReferenceProcessItem(context->ProcessId))) + { + PhSetFileDialogFileName(fileDialog, + PhaFormatString(L"%s_0x%Ix-0x%Ix.bin", processItem->ProcessName->Buffer, + context->BaseAddress, context->RegionSize)->Buffer); + PhDereferenceObject(processItem); + } + else + { + PhSetFileDialogFileName(fileDialog, L"Memory.bin"); + } + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + status = PhWriteFileStream(fileStream, context->Buffer, (ULONG)context->RegionSize); + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + case IDC_GOTO: + { + PPH_STRING selectedChoice = NULL; + + while (PhaChoiceDialog( + hwndDlg, + L"Go to Offset", + L"Enter an offset:", + NULL, + 0, + NULL, + PH_CHOICE_DIALOG_USER_CHOICE, + &selectedChoice, + NULL, + L"MemEditGotoChoices" + )) + { + ULONG64 offset; + + if (selectedChoice->Length == 0) + continue; + + if (PhStringToInteger64(&selectedChoice->sr, 0, &offset)) + { + if (offset >= context->RegionSize) + { + PhShowError(hwndDlg, L"The offset is too large."); + continue; + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE); + HexEdit_SetSel(context->HexEditHandle, (LONG)offset, (LONG)offset); + break; + } + } + } + break; + case IDC_WRITE: + { + NTSTATUS status; + + if (!context->WriteAccess) + { + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_VM_READ | PROCESS_VM_WRITE, + context->ProcessId + ))) + { + PhShowStatus(hwndDlg, L"Unable to open the process", status, 0); + break; + } + + if (context->ProcessHandle) NtClose(context->ProcessHandle); + context->ProcessHandle = processHandle; + context->WriteAccess = TRUE; + } + + if (!NT_SUCCESS(status = NtWriteVirtualMemory( + context->ProcessHandle, + context->BaseAddress, + context->Buffer, + context->RegionSize, + NULL + ))) + { + PhShowStatus(hwndDlg, L"Unable to write memory", status, 0); + } + } + break; + case IDC_REREAD: + { + NTSTATUS status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + context->ProcessHandle, + context->BaseAddress, + context->Buffer, + context->RegionSize, + NULL + ))) + { + PhShowStatus(hwndDlg, L"Unable to read memory", status, 0); + } + + InvalidateRect(context->HexEditHandle, NULL, TRUE); + } + break; + case IDC_BYTESPERROW: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + PPH_STRING bytesPerRowString = PhaGetDlgItemText(hwndDlg, IDC_BYTESPERROW); + PH_STRINGREF firstPart; + PH_STRINGREF secondPart; + ULONG64 bytesPerRow64; + + if (PhSplitStringRefAtChar(&bytesPerRowString->sr, ' ', &firstPart, &secondPart)) + { + if (PhStringToInteger64(&firstPart, 10, &bytesPerRow64)) + { + PhSetIntegerSetting(L"MemEditBytesPerRow", (ULONG)bytesPerRow64); + HexEdit_SetBytesPerRow(context->HexEditHandle, (ULONG)bytesPerRow64); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->HexEditHandle, TRUE); + } + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_PH_SELECT_OFFSET: + { + HexEdit_SetEditMode(context->HexEditHandle, EDIT_ASCII); + HexEdit_SetSel(context->HexEditHandle, (ULONG)wParam, (ULONG)wParam + (ULONG)lParam); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/memlist.c b/ProcessHacker/memlist.c new file mode 100644 index 0000000..fec1d69 --- /dev/null +++ b/ProcessHacker/memlist.c @@ -0,0 +1,907 @@ +/* + * Process Hacker - + * memory region list + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +VOID PhpClearMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ); + +VOID PhpDestroyMemoryNode( + _In_ PPH_MEMORY_NODE MemoryNode + ); + +LONG PhpMemoryTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpMemoryTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID PhInitializeMemoryList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + HWND hwnd; + + memset(Context, 0, sizeof(PH_MEMORY_LIST_CONTEXT)); + + Context->AllocationBaseNodeList = PhCreateList(100); + Context->RegionNodeList = PhCreateList(400); + + Context->ParentWindowHandle = ParentWindowHandle; + Context->TreeNewHandle = TreeNewHandle; + hwnd = TreeNewHandle; + PhSetControlTheme(hwnd, L"explorer"); + + TreeNew_SetCallback(hwnd, PhpMemoryTreeNewCallback, Context); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHMMTLC_BASEADDRESS, TRUE, L"Base address", 120, PH_ALIGN_LEFT, -2, 0); + PhAddTreeNewColumn(hwnd, PHMMTLC_TYPE, TRUE, L"Type", 90, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_SIZE, TRUE, L"Size", 80, PH_ALIGN_RIGHT, 1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHMMTLC_PROTECTION, TRUE, L"Protection", 60, PH_ALIGN_LEFT, 2, 0); + PhAddTreeNewColumn(hwnd, PHMMTLC_USE, TRUE, L"Use", 200, PH_ALIGN_LEFT, 3, 0); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_TOTALWS, TRUE, L"Total WS", 80, PH_ALIGN_RIGHT, 4, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_PRIVATEWS, TRUE, L"Private WS", 80, PH_ALIGN_RIGHT, 5, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_SHAREABLEWS, TRUE, L"Shareable WS", 80, PH_ALIGN_RIGHT, 6, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_SHAREDWS, TRUE, L"Shared WS", 80, PH_ALIGN_RIGHT, 7, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_LOCKEDWS, TRUE, L"Locked WS", 80, PH_ALIGN_RIGHT, 8, DT_RIGHT, TRUE); + + PhAddTreeNewColumnEx(hwnd, PHMMTLC_COMMITTED, FALSE, L"Committed", 80, PH_ALIGN_RIGHT, 9, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMMTLC_PRIVATE, FALSE, L"Private", 80, PH_ALIGN_RIGHT, 10, DT_RIGHT, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetTriState(hwnd, TRUE); + TreeNew_SetSort(hwnd, 0, NoSortOrder); + + PhCmInitializeManager(&Context->Cm, hwnd, PHMMTLC_MAXIMUM, PhpMemoryTreeNewPostSortFunction); +} + +VOID PhpClearMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + ULONG i; + + for (i = 0; i < Context->AllocationBaseNodeList->Count; i++) + PhpDestroyMemoryNode(Context->AllocationBaseNodeList->Items[i]); + for (i = 0; i < Context->RegionNodeList->Count; i++) + PhpDestroyMemoryNode(Context->RegionNodeList->Items[i]); + + PhClearList(Context->AllocationBaseNodeList); + PhClearList(Context->RegionNodeList); +} + +VOID PhDeleteMemoryList( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + PhCmDeleteManager(&Context->Cm); + + PhpClearMemoryList(Context); + PhDereferenceObject(Context->AllocationBaseNodeList); + PhDereferenceObject(Context->RegionNodeList); +} + +VOID PhLoadSettingsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"MemoryTreeListColumns"); + sortSettings = PhGetStringSetting(L"MemoryTreeListSort"); + PhCmLoadSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSaveSettingsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &sortSettings); + PhSetStringSetting2(L"MemoryTreeListColumns", &settings->sr); + PhSetStringSetting2(L"MemoryTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSetOptionsMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ BOOLEAN HideFreeRegions + ) +{ + ULONG i; + ULONG k; + BOOLEAN modified; + + if (Context->HideFreeRegions != HideFreeRegions) + { + PPH_LIST lists[2]; + + Context->HideFreeRegions = HideFreeRegions; + modified = FALSE; + lists[0] = Context->AllocationBaseNodeList; + lists[1] = Context->RegionNodeList; + + for (k = 0; k < 2; k++) + { + for (i = 0; i < lists[k]->Count; i++) + { + PPH_MEMORY_NODE node = lists[k]->Items[i]; + BOOLEAN visible; + + visible = TRUE; + + if (HideFreeRegions && (node->MemoryItem->State & MEM_FREE)) + visible = FALSE; + + if (node->Node.Visible != visible) + { + node->Node.Visible = visible; + modified = TRUE; + + if (!visible) + node->Node.Selected = FALSE; + } + } + } + + if (modified) + { + TreeNew_NodesStructured(Context->TreeNewHandle); + } + } +} + +VOID PhpDestroyMemoryNode( + _In_ PPH_MEMORY_NODE MemoryNode + ) +{ + PhEmCallObjectOperation(EmMemoryNodeType, MemoryNode, EmObjectDelete); + + PhClearReference(&MemoryNode->SizeText); + PhClearReference(&MemoryNode->UseText); + PhClearReference(&MemoryNode->TotalWsText); + PhClearReference(&MemoryNode->PrivateWsText); + PhClearReference(&MemoryNode->ShareableWsText); + PhClearReference(&MemoryNode->SharedWsText); + PhClearReference(&MemoryNode->LockedWsText); + PhClearReference(&MemoryNode->CommittedText); + PhClearReference(&MemoryNode->PrivateText); + + PhClearReference(&MemoryNode->Children); + PhDereferenceObject(MemoryNode->MemoryItem); + + PhFree(MemoryNode); +} + +PPH_MEMORY_NODE PhpAddAllocationBaseNode( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ PVOID AllocationBase + ) +{ + PPH_MEMORY_NODE memoryNode; + PPH_MEMORY_ITEM memoryItem; + + memoryNode = PhAllocate(PhEmGetObjectSize(EmMemoryNodeType, sizeof(PH_MEMORY_NODE))); + memset(memoryNode, 0, sizeof(PH_MEMORY_NODE)); + PhInitializeTreeNewNode(&memoryNode->Node); + memoryNode->Node.Expanded = FALSE; + + memoryNode->IsAllocationBase = TRUE; + memoryItem = PhCreateMemoryItem(); + memoryNode->MemoryItem = memoryItem; + + memoryItem->BaseAddress = AllocationBase; + memoryItem->AllocationBase = AllocationBase; + + memoryNode->Children = PhCreateList(1); + + memset(memoryNode->TextCache, 0, sizeof(PH_STRINGREF) * PHMMTLC_MAXIMUM); + memoryNode->Node.TextCache = memoryNode->TextCache; + memoryNode->Node.TextCacheSize = PHMMTLC_MAXIMUM; + + PhAddItemList(Context->AllocationBaseNodeList, memoryNode); + + PhEmCallObjectOperation(EmMemoryNodeType, memoryNode, EmObjectCreate); + + return memoryNode; +} + +PPH_MEMORY_NODE PhpAddRegionNode( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ PPH_MEMORY_ITEM MemoryItem + ) +{ + PPH_MEMORY_NODE memoryNode; + + memoryNode = PhAllocate(PhEmGetObjectSize(EmMemoryNodeType, sizeof(PH_MEMORY_NODE))); + memset(memoryNode, 0, sizeof(PH_MEMORY_NODE)); + PhInitializeTreeNewNode(&memoryNode->Node); + + memoryNode->IsAllocationBase = FALSE; + memoryNode->MemoryItem = MemoryItem; + PhReferenceObject(MemoryItem); + + memset(memoryNode->TextCache, 0, sizeof(PH_STRINGREF) * PHMMTLC_MAXIMUM); + memoryNode->Node.TextCache = memoryNode->TextCache; + memoryNode->Node.TextCacheSize = PHMMTLC_MAXIMUM; + + PhAddItemList(Context->RegionNodeList, memoryNode); + + PhEmCallObjectOperation(EmMemoryNodeType, memoryNode, EmObjectCreate); + + return memoryNode; +} + +VOID PhpCopyMemoryRegionTypeInfo( + _In_ PPH_MEMORY_ITEM Source, + _Inout_ PPH_MEMORY_ITEM Destination + ) +{ + if (Destination->RegionType == CustomRegion) + PhClearReference(&Destination->u.Custom.Text); + else if (Destination->RegionType == MappedFileRegion) + PhClearReference(&Destination->u.MappedFile.FileName); + + Destination->RegionType = Source->RegionType; + Destination->u = Source->u; + + if (Destination->RegionType == CustomRegion) + PhReferenceObject(Destination->u.Custom.Text); + else if (Destination->RegionType == MappedFileRegion) + PhReferenceObject(Destination->u.MappedFile.FileName); +} + +VOID PhReplaceMemoryList( + _Inout_ PPH_MEMORY_LIST_CONTEXT Context, + _In_opt_ PPH_MEMORY_ITEM_LIST List + ) +{ + PLIST_ENTRY listEntry; + PPH_MEMORY_NODE allocationBaseNode = NULL; + + PhpClearMemoryList(Context); + + if (!List) + { + TreeNew_NodesStructured(Context->TreeNewHandle); + return; + } + + for (listEntry = List->ListHead.Flink; listEntry != &List->ListHead; listEntry = listEntry->Flink) + { + PPH_MEMORY_ITEM memoryItem = CONTAINING_RECORD(listEntry, PH_MEMORY_ITEM, ListEntry); + PPH_MEMORY_NODE memoryNode; + + if (memoryItem->AllocationBaseItem == memoryItem) + allocationBaseNode = PhpAddAllocationBaseNode(Context, memoryItem->AllocationBase); + + memoryNode = PhpAddRegionNode(Context, memoryItem); + + if (Context->HideFreeRegions && (memoryItem->State & MEM_FREE)) + memoryNode->Node.Visible = FALSE; + + if (allocationBaseNode && memoryItem->AllocationBase == allocationBaseNode->MemoryItem->BaseAddress) + { + if (!(memoryItem->State & MEM_FREE)) + { + memoryNode->Parent = allocationBaseNode; + PhAddItemList(allocationBaseNode->Children, memoryNode); + } + + // Aggregate various statistics. + allocationBaseNode->MemoryItem->RegionSize += memoryItem->RegionSize; + allocationBaseNode->MemoryItem->CommittedSize += memoryItem->CommittedSize; + allocationBaseNode->MemoryItem->PrivateSize += memoryItem->PrivateSize; + allocationBaseNode->MemoryItem->TotalWorkingSetPages += memoryItem->TotalWorkingSetPages; + allocationBaseNode->MemoryItem->PrivateWorkingSetPages += memoryItem->PrivateWorkingSetPages; + allocationBaseNode->MemoryItem->SharedWorkingSetPages += memoryItem->SharedWorkingSetPages; + allocationBaseNode->MemoryItem->ShareableWorkingSetPages += memoryItem->ShareableWorkingSetPages; + allocationBaseNode->MemoryItem->LockedWorkingSetPages += memoryItem->LockedWorkingSetPages; + + if (memoryItem->AllocationBaseItem == memoryItem) + { + if (memoryItem->State & MEM_FREE) + allocationBaseNode->MemoryItem->State = MEM_FREE; + + allocationBaseNode->MemoryItem->Protect = memoryItem->AllocationProtect; + PhGetMemoryProtectionString(allocationBaseNode->MemoryItem->Protect, allocationBaseNode->ProtectionText); + allocationBaseNode->MemoryItem->Type = memoryItem->Type; + + if (memoryItem->RegionType != CustomRegion || memoryItem->u.Custom.PropertyOfAllocationBase) + PhpCopyMemoryRegionTypeInfo(memoryItem, allocationBaseNode->MemoryItem); + + if (Context->HideFreeRegions && (allocationBaseNode->MemoryItem->State & MEM_FREE)) + allocationBaseNode->Node.Visible = FALSE; + } + else + { + if (memoryItem->RegionType == UnknownRegion) + PhpCopyMemoryRegionTypeInfo(allocationBaseNode->MemoryItem, memoryItem); + } + } + + PhGetMemoryProtectionString(memoryItem->Protect, memoryNode->ProtectionText); + } + + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhUpdateMemoryNode( + _In_ PPH_MEMORY_LIST_CONTEXT Context, + _In_ PPH_MEMORY_NODE MemoryNode + ) +{ + PhGetMemoryProtectionString(MemoryNode->MemoryItem->Protect, MemoryNode->ProtectionText); + memset(MemoryNode->TextCache, 0, sizeof(PH_STRINGREF) * PHMMTLC_MAXIMUM); + TreeNew_InvalidateNode(Context->TreeNewHandle, &MemoryNode->Node); +} + +PPH_STRING PhpGetMemoryRegionUseText( + _In_ PPH_MEMORY_ITEM MemoryItem + ) +{ + PH_MEMORY_REGION_TYPE type = MemoryItem->RegionType; + + switch (type) + { + case UnknownRegion: + return PhReferenceEmptyString(); + case CustomRegion: + PhReferenceObject(MemoryItem->u.Custom.Text); + return MemoryItem->u.Custom.Text; + case UnusableRegion: + return PhReferenceEmptyString(); + case MappedFileRegion: + PhReferenceObject(MemoryItem->u.MappedFile.FileName); + return MemoryItem->u.MappedFile.FileName; + case UserSharedDataRegion: + return PhCreateString(L"USER_SHARED_DATA"); + case PebRegion: + case Peb32Region: + return PhFormatString(L"PEB%s", type == Peb32Region ? L" 32-bit" : L""); + case TebRegion: + case Teb32Region: + return PhFormatString(L"TEB%s (thread %u)", + type == Teb32Region ? L" 32-bit" : L"", HandleToUlong(MemoryItem->u.Teb.ThreadId)); + case StackRegion: + case Stack32Region: + return PhFormatString(L"Stack%s (thread %u)", + type == Stack32Region ? L" 32-bit" : L"", HandleToUlong(MemoryItem->u.Stack.ThreadId)); + case HeapRegion: + case Heap32Region: + return PhFormatString(L"Heap%s (ID %u)", + type == Heap32Region ? L" 32-bit" : L"", (ULONG)MemoryItem->u.Heap.Index + 1); + case HeapSegmentRegion: + case HeapSegment32Region: + return PhFormatString(L"Heap segment%s (ID %u)", + type == HeapSegment32Region ? L" 32-bit" : L"", (ULONG)MemoryItem->u.HeapSegment.HeapItem->u.Heap.Index + 1); + default: + return PhReferenceEmptyString(); + } +} + +VOID PhpUpdateMemoryNodeUseText( + _Inout_ PPH_MEMORY_NODE MemoryNode + ) +{ + if (!MemoryNode->UseText) + MemoryNode->UseText = PhpGetMemoryRegionUseText(MemoryNode->MemoryItem); +} + +PPH_STRING PhpFormatSizeIfNonZero( + _In_ ULONG64 Size + ) +{ + if (Size != 0) + return PhFormatSize(Size, 1); + else + return NULL; +} + +#define SORT_FUNCTION(Column) PhpMemoryTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpMemoryTreeNewCompare##Column( \ + _In_ void *_context, \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_MEMORY_NODE node1 = *(PPH_MEMORY_NODE *)_elem1; \ + PPH_MEMORY_NODE node2 = *(PPH_MEMORY_NODE *)_elem2; \ + PPH_MEMORY_ITEM memoryItem1 = node1->MemoryItem; \ + PPH_MEMORY_ITEM memoryItem2 = node2->MemoryItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = uintptrcmp((ULONG_PTR)memoryItem1->BaseAddress, (ULONG_PTR)memoryItem2->BaseAddress); \ + \ + return PhModifySort(sortResult, ((PPH_MEMORY_LIST_CONTEXT)_context)->TreeNewSortOrder); \ +} + +LONG PhpMemoryTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = uintptrcmp((ULONG_PTR)((PPH_MEMORY_NODE)Node1)->MemoryItem->BaseAddress, (ULONG_PTR)((PPH_MEMORY_NODE)Node2)->MemoryItem->BaseAddress); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(BaseAddress) +{ + sortResult = uintptrcmp((ULONG_PTR)memoryItem1->BaseAddress, (ULONG_PTR)memoryItem2->BaseAddress); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Type) +{ + sortResult = uintcmp(memoryItem1->Type | memoryItem1->State, memoryItem2->Type | memoryItem2->State); + + if (sortResult == 0) + sortResult = intcmp(memoryItem1->RegionType, memoryItem2->RegionType); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Size) +{ + sortResult = uintptrcmp(memoryItem1->RegionSize, memoryItem2->RegionSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Protection) +{ + sortResult = PhCompareStringZ(node1->ProtectionText, node2->ProtectionText, FALSE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Use) +{ + PhpUpdateMemoryNodeUseText(node1); + PhpUpdateMemoryNodeUseText(node2); + sortResult = PhCompareStringWithNull(node1->UseText, node2->UseText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TotalWs) +{ + sortResult = uintptrcmp(memoryItem1->TotalWorkingSetPages, memoryItem2->TotalWorkingSetPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PrivateWs) +{ + sortResult = uintptrcmp(memoryItem1->PrivateWorkingSetPages, memoryItem2->PrivateWorkingSetPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ShareableWs) +{ + sortResult = uintptrcmp(memoryItem1->ShareableWorkingSetPages, memoryItem2->ShareableWorkingSetPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(SharedWs) +{ + sortResult = uintptrcmp(memoryItem1->SharedWorkingSetPages, memoryItem2->SharedWorkingSetPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LockedWs) +{ + sortResult = uintptrcmp(memoryItem1->LockedWorkingSetPages, memoryItem2->LockedWorkingSetPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Committed) +{ + sortResult = uintptrcmp(memoryItem1->CommittedSize, memoryItem2->CommittedSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Private) +{ + sortResult = uintptrcmp(memoryItem1->PrivateSize, memoryItem2->PrivateSize); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpMemoryTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_MEMORY_LIST_CONTEXT context; + PPH_MEMORY_NODE node; + + context = Context; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &context->Cm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + node = (PPH_MEMORY_NODE)getChildren->Node; + + if (context->TreeNewSortOrder == NoSortOrder) + { + if (!node) + { + getChildren->Children = (PPH_TREENEW_NODE *)context->AllocationBaseNodeList->Items; + getChildren->NumberOfChildren = context->AllocationBaseNodeList->Count; + } + else + { + getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; + getChildren->NumberOfChildren = node->Children->Count; + } + } + else + { + if (!node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(BaseAddress), + SORT_FUNCTION(Type), + SORT_FUNCTION(Size), + SORT_FUNCTION(Protection), + SORT_FUNCTION(Use), + SORT_FUNCTION(TotalWs), + SORT_FUNCTION(PrivateWs), + SORT_FUNCTION(ShareableWs), + SORT_FUNCTION(SharedWs), + SORT_FUNCTION(LockedWs), + SORT_FUNCTION(Committed), + SORT_FUNCTION(Private) + }; + int (__cdecl *sortFunction)(void *, const void *, const void *); + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)context->RegionNodeList->Items, + context->RegionNodeList->Count, + context->TreeNewSortColumn, + context->TreeNewSortOrder, + &context->Cm + )) + { + if (context->TreeNewSortColumn < PHMMTLC_MAXIMUM) + sortFunction = sortFunctions[context->TreeNewSortColumn]; + else + sortFunction = NULL; + } + else + { + sortFunction = NULL; + } + + if (sortFunction) + { + qsort_s(context->RegionNodeList->Items, context->RegionNodeList->Count, sizeof(PVOID), sortFunction, context); + } + + getChildren->Children = (PPH_TREENEW_NODE *)context->RegionNodeList->Items; + getChildren->NumberOfChildren = context->RegionNodeList->Count; + } + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + node = (PPH_MEMORY_NODE)isLeaf->Node; + + if (context->TreeNewSortOrder == NoSortOrder) + isLeaf->IsLeaf = !node->Children || node->Children->Count == 0; + else + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_MEMORY_ITEM memoryItem; + + node = (PPH_MEMORY_NODE)getCellText->Node; + memoryItem = node->MemoryItem; + + switch (getCellText->Id) + { + case PHMMTLC_BASEADDRESS: + PhPrintPointer(node->BaseAddressText, memoryItem->BaseAddress); + PhInitializeStringRefLongHint(&getCellText->Text, node->BaseAddressText); + break; + case PHMMTLC_TYPE: + if (memoryItem->State & MEM_FREE) + { + if (memoryItem->RegionType == UnusableRegion) + PhInitializeStringRef(&getCellText->Text, L"Free (Unusable)"); + else + PhInitializeStringRef(&getCellText->Text, L"Free"); + } + else if (node->IsAllocationBase) + { + PhInitializeStringRefLongHint(&getCellText->Text, PhGetMemoryTypeString(memoryItem->Type)); + } + else + { + PH_FORMAT format[3]; + SIZE_T returnLength; + + PhInitFormatS(&format[0], PhGetMemoryTypeString(memoryItem->Type)); + PhInitFormatS(&format[1], L": "); + PhInitFormatS(&format[2], PhGetMemoryStateString(memoryItem->State)); + + if (PhFormatToBuffer(format, 3, node->TypeText, sizeof(node->TypeText), &returnLength)) + { + getCellText->Text.Buffer = node->TypeText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); + } + } + break; + case PHMMTLC_SIZE: + PhMoveReference(&node->SizeText, PhFormatSize(memoryItem->RegionSize, 1)); + getCellText->Text = PhGetStringRef(node->SizeText); + break; + case PHMMTLC_PROTECTION: + PhInitializeStringRefLongHint(&getCellText->Text, node->ProtectionText); + break; + case PHMMTLC_USE: + PhpUpdateMemoryNodeUseText(node); + getCellText->Text = PhGetStringRef(node->UseText); + break; + case PHMMTLC_TOTALWS: + PhMoveReference(&node->TotalWsText, PhpFormatSizeIfNonZero((ULONG64)memoryItem->TotalWorkingSetPages * PAGE_SIZE)); + getCellText->Text = PhGetStringRef(node->TotalWsText); + break; + case PHMMTLC_PRIVATEWS: + PhMoveReference(&node->PrivateWsText, PhpFormatSizeIfNonZero((ULONG64)memoryItem->PrivateWorkingSetPages * PAGE_SIZE)); + getCellText->Text = PhGetStringRef(node->PrivateWsText); + break; + case PHMMTLC_SHAREABLEWS: + PhMoveReference(&node->ShareableWsText, PhpFormatSizeIfNonZero((ULONG64)memoryItem->ShareableWorkingSetPages * PAGE_SIZE)); + getCellText->Text = PhGetStringRef(node->ShareableWsText); + break; + case PHMMTLC_SHAREDWS: + PhMoveReference(&node->SharedWsText, PhpFormatSizeIfNonZero((ULONG64)memoryItem->SharedWorkingSetPages * PAGE_SIZE)); + getCellText->Text = PhGetStringRef(node->SharedWsText); + break; + case PHMMTLC_LOCKEDWS: + PhMoveReference(&node->LockedWsText, PhpFormatSizeIfNonZero((ULONG64)memoryItem->LockedWorkingSetPages * PAGE_SIZE)); + getCellText->Text = PhGetStringRef(node->LockedWsText); + break; + case PHMMTLC_COMMITTED: + PhMoveReference(&node->CommittedText, PhpFormatSizeIfNonZero(memoryItem->CommittedSize)); + getCellText->Text = PhGetStringRef(node->CommittedText); + break; + case PHMMTLC_PRIVATE: + PhMoveReference(&node->PrivateText, PhpFormatSizeIfNonZero(memoryItem->PrivateSize)); + getCellText->Text = PhGetStringRef(node->PrivateText); + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + /*PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + PPH_MEMORY_ITEM memoryItem; + + node = (PPH_MEMORY_NODE)getNodeColor->Node; + memoryItem = node->MemoryItem;*/ + + // TODO + + //getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR; + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MEMORY_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(context->TreeNewHandle, 0, -1); + break; + case VK_RETURN: + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MEMORY_READWRITEMEMORY, 0); + break; + case VK_F5: + SendMessage(context->ParentWindowHandle, WM_COMMAND, IDC_REFRESH, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = NoSortOrder; + PhInitializeTreeNewColumnMenuEx(&data, PH_TN_COLUMN_MENU_SHOW_RESET_SORT); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; + + node = (PPH_MEMORY_NODE)mouseEvent->Node; + + if (node && node->IsAllocationBase) + TreeNew_SetNodeExpanded(hwnd, node, !node->Node.Expanded); + else + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MEMORY_READWRITEMEMORY, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_SHOWCONTEXTMENU, (LPARAM)contextMenu); + } + return TRUE; + case TreeNewGetDialogCode: + { + PULONG code = Parameter2; + + if (PtrToUlong(Parameter1) == VK_RETURN) + { + *code = DLGC_WANTMESSAGE; + return TRUE; + } + } + return FALSE; + } + + return FALSE; +} + +PPH_MEMORY_NODE PhGetSelectedMemoryNode( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + ULONG i; + + if (Context->TreeNewSortOrder == NoSortOrder) + { + for (i = 0; i < Context->AllocationBaseNodeList->Count; i++) + { + PPH_MEMORY_NODE node = Context->AllocationBaseNodeList->Items[i]; + + if (node->Node.Selected) + return node; + } + } + + for (i = 0; i < Context->RegionNodeList->Count; i++) + { + PPH_MEMORY_NODE node = Context->RegionNodeList->Items[i]; + + if (node->Node.Selected) + return node; + } + + return NULL; +} + +VOID PhGetSelectedMemoryNodes( + _In_ PPH_MEMORY_LIST_CONTEXT Context, + _Out_ PPH_MEMORY_NODE **MemoryNodes, + _Out_ PULONG NumberOfMemoryNodes + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + if (Context->TreeNewSortOrder == NoSortOrder) + { + for (i = 0; i < Context->AllocationBaseNodeList->Count; i++) + { + PPH_MEMORY_NODE node = Context->AllocationBaseNodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node); + } + } + + for (i = 0; i < Context->RegionNodeList->Count; i++) + { + PPH_MEMORY_NODE node = Context->RegionNodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node); + } + + *NumberOfMemoryNodes = (ULONG)array.Count; + *MemoryNodes = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllMemoryNodes( + _In_ PPH_MEMORY_LIST_CONTEXT Context + ) +{ + TreeNew_DeselectRange(Context->TreeNewHandle, 0, -1); +} diff --git a/ProcessHacker/memlists.c b/ProcessHacker/memlists.c new file mode 100644 index 0000000..6c4e103 --- /dev/null +++ b/ProcessHacker/memlists.c @@ -0,0 +1,286 @@ +/* + * Process Hacker - + * memory list information + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +#define MSG_UPDATE (WM_APP + 1) + +INT_PTR CALLBACK PhpMemoryListsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +HWND PhMemoryListsWindowHandle = NULL; +static VOID (NTAPI *UnregisterDialogFunction)(HWND); +static PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + +VOID PhShowMemoryListsDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ VOID (NTAPI *RegisterDialog)(HWND), + _In_opt_ VOID (NTAPI *UnregisterDialog)(HWND) + ) +{ + if (!PhMemoryListsWindowHandle) + { + PhMemoryListsWindowHandle = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MEMLISTS), + ParentWindowHandle, + PhpMemoryListsDlgProc + ); + RegisterDialog(PhMemoryListsWindowHandle); + UnregisterDialogFunction = UnregisterDialog; + } + + if (!IsWindowVisible(PhMemoryListsWindowHandle)) + ShowWindow(PhMemoryListsWindowHandle, SW_SHOW); + else if (IsIconic(PhMemoryListsWindowHandle)) + ShowWindow(PhMemoryListsWindowHandle, SW_RESTORE); + else + SetForegroundWindow(PhMemoryListsWindowHandle); +} + +static VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhMemoryListsWindowHandle, MSG_UPDATE, 0, 0); +} + +static VOID PhpUpdateMemoryListInfo( + _In_ HWND hwndDlg + ) +{ + SYSTEM_MEMORY_LIST_INFORMATION memoryListInfo; + + if (NT_SUCCESS(NtQuerySystemInformation( + SystemMemoryListInformation, + &memoryListInfo, + sizeof(SYSTEM_MEMORY_LIST_INFORMATION), + NULL + ))) + { + ULONG_PTR standbyPageCount; + ULONG_PTR repurposedPageCount; + ULONG i; + + standbyPageCount = 0; + repurposedPageCount = 0; + + for (i = 0; i < 8; i++) + { + standbyPageCount += memoryListInfo.PageCountByPriority[i]; + repurposedPageCount += memoryListInfo.RepurposedPagesByPriority[i]; + } + + SetDlgItemText(hwndDlg, IDC_ZLISTZEROED_V, PhaFormatSize((ULONG64)memoryListInfo.ZeroPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTFREE_V, PhaFormatSize((ULONG64)memoryListInfo.FreePageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIED_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIEDNOWRITE_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedNoWritePageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTBAD_V, PhaFormatSize((ULONG64)memoryListInfo.BadPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY_V, PhaFormatSize((ULONG64)standbyPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY0_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[0] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY1_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[1] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY2_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[2] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY3_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[3] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY4_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[4] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY5_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[5] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY6_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[6] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY7_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[7] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED_V, PhaFormatSize((ULONG64)repurposedPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED0_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[0] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED1_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[1] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED2_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[2] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED3_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[3] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED4_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[4] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED5_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[5] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED6_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[6] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED7_V, PhaFormatSize((ULONG64)memoryListInfo.RepurposedPagesByPriority[7] * PAGE_SIZE, -1)->Buffer); + + if (WindowsVersion >= WINDOWS_8) + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIEDPAGEFILE_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedPageCountPageFile * PAGE_SIZE, -1)->Buffer); + else + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIEDPAGEFILE_V, L"N/A"); + } + else + { + SetDlgItemText(hwndDlg, IDC_ZLISTZEROED_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTFREE_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIED_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIEDNOWRITE_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTMODIFIEDPAGEFILE_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTBAD_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY0_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY1_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY2_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY3_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY4_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY5_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY6_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTSTANDBY7_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED0_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED1_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED2_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED3_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED4_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED5_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED6_V, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ZLISTREPURPOSED7_V, L"Unknown"); + } +} + +INT_PTR CALLBACK PhpMemoryListsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhRegisterCallback(&PhProcessesUpdatedEvent, ProcessesUpdatedCallback, NULL, &ProcessesUpdatedRegistration); + PhpUpdateMemoryListInfo(hwndDlg); + + PhLoadWindowPlacementFromSetting(L"MemoryListsWindowPosition", NULL, hwndDlg); + PhRegisterDialog(hwndDlg); + } + break; + case WM_DESTROY: + { + PhUnregisterDialog(hwndDlg); + PhSaveWindowPlacementToSetting(L"MemoryListsWindowPosition", NULL, hwndDlg); + + PhUnregisterCallback(&PhProcessesUpdatedEvent, &ProcessesUpdatedRegistration); + + UnregisterDialogFunction(hwndDlg); + PhMemoryListsWindowHandle = NULL; + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_EMPTY: + { + PPH_EMENU menu; + RECT buttonRect; + POINT point; + PPH_EMENU_ITEM selectedItem; + SYSTEM_MEMORY_LIST_COMMAND command = -1; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_EMPTYMEMLISTS), 0); + + GetClientRect(GetDlgItem(hwndDlg, IDC_EMPTY), &buttonRect); + point.x = 0; + point.y = buttonRect.bottom; + + ClientToScreen(GetDlgItem(hwndDlg, IDC_EMPTY), &point); + selectedItem = PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); + + if (selectedItem) + { + switch (selectedItem->Id) + { + case ID_EMPTY_EMPTYWORKINGSETS: + command = MemoryEmptyWorkingSets; + break; + case ID_EMPTY_EMPTYMODIFIEDPAGELIST: + command = MemoryFlushModifiedList; + break; + case ID_EMPTY_EMPTYSTANDBYLIST: + command = MemoryPurgeStandbyList; + break; + case ID_EMPTY_EMPTYPRIORITY0STANDBYLIST: + command = MemoryPurgeLowPriorityStandbyList; + break; + } + } + + if (command != -1) + { + NTSTATUS status; + + SetCursor(LoadCursor(NULL, IDC_WAIT)); + status = NtSetSystemInformation( + SystemMemoryListInformation, + &command, + sizeof(SYSTEM_MEMORY_LIST_COMMAND) + ); + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + if (status == STATUS_PRIVILEGE_NOT_HELD) + { + if (!PhGetOwnTokenAttributes().Elevated) + { + if (PhUiConnectToPhSvc(hwndDlg, FALSE)) + { + SetCursor(LoadCursor(NULL, IDC_WAIT)); + status = PhSvcCallIssueMemoryListCommand(command); + SetCursor(LoadCursor(NULL, IDC_ARROW)); + PhUiDisconnectFromPhSvc(); + } + else + { + // User cancelled eleavtion. + status = STATUS_SUCCESS; + } + } + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus(hwndDlg, L"Unable to execute the memory list command", status, 0); + } + } + + PhDestroyEMenu(menu); + } + break; + } + } + break; + case MSG_UPDATE: + { + PhpUpdateMemoryListInfo(hwndDlg); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/memprot.c b/ProcessHacker/memprot.c new file mode 100644 index 0000000..578bbd9 --- /dev/null +++ b/ProcessHacker/memprot.c @@ -0,0 +1,158 @@ +/* + * Process Hacker - + * memory protection window + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +typedef struct _MEMORY_PROTECT_CONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + PPH_MEMORY_ITEM MemoryItem; +} MEMORY_PROTECT_CONTEXT, *PMEMORY_PROTECT_CONTEXT; + +INT_PTR CALLBACK PhpMemoryProtectDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowMemoryProtectDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_MEMORY_ITEM MemoryItem + ) +{ + MEMORY_PROTECT_CONTEXT context; + + context.ProcessItem = ProcessItem; + context.MemoryItem = MemoryItem; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MEMPROTECT), + ParentWindowHandle, + PhpMemoryProtectDlgProc, + (LPARAM)&context + ); +} + +static INT_PTR CALLBACK PhpMemoryProtectDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)lParam); + + SetDlgItemText(hwndDlg, IDC_INTRO, + L"Possible values:\r\n" + L"\r\n" + L"0x01 - PAGE_NOACCESS\r\n" + L"0x02 - PAGE_READONLY\r\n" + L"0x04 - PAGE_READWRITE\r\n" + L"0x08 - PAGE_WRITECOPY\r\n" + L"0x10 - PAGE_EXECUTE\r\n" + L"0x20 - PAGE_EXECUTE_READ\r\n" + L"0x40 - PAGE_EXECUTE_READWRITE\r\n" + L"0x80 - PAGE_EXECUTE_WRITECOPY\r\n" + L"Modifiers:\r\n" + L"0x100 - PAGE_GUARD\r\n" + L"0x200 - PAGE_NOCACHE\r\n" + L"0x400 - PAGE_WRITECOMBINE\r\n" + ); + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_VALUE), TRUE); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + NTSTATUS status; + PMEMORY_PROTECT_CONTEXT context = (PMEMORY_PROTECT_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + HANDLE processHandle; + ULONG64 protect; + + PhStringToInteger64(&PhaGetDlgItemText(hwndDlg, IDC_VALUE)->sr, 0, &protect); + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_VM_OPERATION, + context->ProcessItem->ProcessId + ))) + { + PVOID baseAddress; + SIZE_T regionSize; + ULONG oldProtect; + + baseAddress = context->MemoryItem->BaseAddress; + regionSize = context->MemoryItem->RegionSize; + + status = NtProtectVirtualMemory( + processHandle, + &baseAddress, + ®ionSize, + (ULONG)protect, + &oldProtect + ); + + if (NT_SUCCESS(status)) + context->MemoryItem->Protect = (ULONG)protect; + } + + if (NT_SUCCESS(status)) + { + EndDialog(hwndDlg, IDOK); + } + else + { + PhShowStatus(hwndDlg, L"Unable to change memory protection", status, 0); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_VALUE), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_VALUE), 0, -1); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/memprv.c b/ProcessHacker/memprv.c new file mode 100644 index 0000000..b97f259 --- /dev/null +++ b/ProcessHacker/memprv.c @@ -0,0 +1,745 @@ +/* + * Process Hacker - + * memory provider + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +#define MAX_HEAPS 1000 +#define WS_REQUEST_COUNT (PAGE_SIZE / sizeof(MEMORY_WORKING_SET_EX_INFORMATION)) + +VOID PhpMemoryItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +PPH_OBJECT_TYPE PhMemoryItemType; + +BOOLEAN PhMemoryProviderInitialization( + VOID + ) +{ + PhMemoryItemType = PhCreateObjectType(L"MemoryItem", 0, PhpMemoryItemDeleteProcedure); + + return TRUE; +} + +VOID PhGetMemoryProtectionString( + _In_ ULONG Protection, + _Out_writes_(17) PWSTR String + ) +{ + PWSTR string; + PH_STRINGREF base; + + if (!Protection) + { + String[0] = 0; + return; + } + + if (Protection & PAGE_NOACCESS) + PhInitializeStringRef(&base, L"NA"); + else if (Protection & PAGE_READONLY) + PhInitializeStringRef(&base, L"R"); + else if (Protection & PAGE_READWRITE) + PhInitializeStringRef(&base, L"RW"); + else if (Protection & PAGE_WRITECOPY) + PhInitializeStringRef(&base, L"WC"); + else if (Protection & PAGE_EXECUTE) + PhInitializeStringRef(&base, L"X"); + else if (Protection & PAGE_EXECUTE_READ) + PhInitializeStringRef(&base, L"RX"); + else if (Protection & PAGE_EXECUTE_READWRITE) + PhInitializeStringRef(&base, L"RWX"); + else if (Protection & PAGE_EXECUTE_WRITECOPY) + PhInitializeStringRef(&base, L"WCX"); + else + PhInitializeStringRef(&base, L"?"); + + string = String; + + memcpy(string, base.Buffer, base.Length); + string += base.Length / sizeof(WCHAR); + + if (Protection & PAGE_GUARD) + { + memcpy(string, L"+G", 2 * 2); + string += 2; + } + + if (Protection & PAGE_NOCACHE) + { + memcpy(string, L"+NC", 3 * 2); + string += 3; + } + + if (Protection & PAGE_WRITECOMBINE) + { + memcpy(string, L"+WCM", 4 * 2); + string += 4; + } + + *string = 0; +} + +PWSTR PhGetMemoryStateString( + _In_ ULONG State + ) +{ + if (State & MEM_COMMIT) + return L"Commit"; + else if (State & MEM_RESERVE) + return L"Reserved"; + else if (State & MEM_FREE) + return L"Free"; + else + return L"Unknown"; +} + +PWSTR PhGetMemoryTypeString( + _In_ ULONG Type + ) +{ + if (Type & MEM_PRIVATE) + return L"Private"; + else if (Type & MEM_MAPPED) + return L"Mapped"; + else if (Type & MEM_IMAGE) + return L"Image"; + else + return L"Unknown"; +} + +PPH_MEMORY_ITEM PhCreateMemoryItem( + VOID + ) +{ + PPH_MEMORY_ITEM memoryItem; + + memoryItem = PhCreateObject(sizeof(PH_MEMORY_ITEM), PhMemoryItemType); + memset(memoryItem, 0, sizeof(PH_MEMORY_ITEM)); + + return memoryItem; +} + +VOID PhpMemoryItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_MEMORY_ITEM memoryItem = Object; + + switch (memoryItem->RegionType) + { + case CustomRegion: + PhClearReference(&memoryItem->u.Custom.Text); + break; + case MappedFileRegion: + PhClearReference(&memoryItem->u.MappedFile.FileName); + break; + } +} + +static LONG NTAPI PhpMemoryItemCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PPH_MEMORY_ITEM memoryItem1 = CONTAINING_RECORD(Links1, PH_MEMORY_ITEM, Links); + PPH_MEMORY_ITEM memoryItem2 = CONTAINING_RECORD(Links2, PH_MEMORY_ITEM, Links); + + return uintptrcmp((ULONG_PTR)memoryItem1->BaseAddress, (ULONG_PTR)memoryItem2->BaseAddress); +} + +VOID PhDeleteMemoryItemList( + _In_ PPH_MEMORY_ITEM_LIST List + ) +{ + PLIST_ENTRY listEntry; + PPH_MEMORY_ITEM memoryItem; + + listEntry = List->ListHead.Flink; + + while (listEntry != &List->ListHead) + { + memoryItem = CONTAINING_RECORD(listEntry, PH_MEMORY_ITEM, ListEntry); + listEntry = listEntry->Flink; + + PhDereferenceObject(memoryItem); + } +} + +PPH_MEMORY_ITEM PhLookupMemoryItemList( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ PVOID Address + ) +{ + PH_MEMORY_ITEM lookupMemoryItem; + PPH_AVL_LINKS links; + PPH_MEMORY_ITEM memoryItem; + + // Do an approximate search on the set to locate the memory item with the largest + // base address that is still smaller than the given address. + lookupMemoryItem.BaseAddress = Address; + links = PhUpperDualBoundElementAvlTree(&List->Set, &lookupMemoryItem.Links); + + if (links) + { + memoryItem = CONTAINING_RECORD(links, PH_MEMORY_ITEM, Links); + + if ((ULONG_PTR)Address < (ULONG_PTR)memoryItem->BaseAddress + memoryItem->RegionSize) + return memoryItem; + } + + return NULL; +} + +PPH_MEMORY_ITEM PhpSetMemoryRegionType( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ PVOID Address, + _In_ BOOLEAN GoToAllocationBase, + _In_ PH_MEMORY_REGION_TYPE RegionType + ) +{ + PPH_MEMORY_ITEM memoryItem; + + memoryItem = PhLookupMemoryItemList(List, Address); + + if (!memoryItem) + return NULL; + + if (GoToAllocationBase && memoryItem->AllocationBaseItem) + memoryItem = memoryItem->AllocationBaseItem; + + if (memoryItem->RegionType != UnknownRegion) + return NULL; + + memoryItem->RegionType = RegionType; + + return memoryItem; +} + +NTSTATUS PhpUpdateMemoryRegionTypes( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ HANDLE ProcessHandle + ) +{ + NTSTATUS status; + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + ULONG i; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; +#endif + PPH_MEMORY_ITEM memoryItem; + PLIST_ENTRY listEntry; + + if (!NT_SUCCESS(status = PhEnumProcessesEx(&processes, SystemExtendedProcessInformation))) + return status; + + process = PhFindProcessInformation(processes, List->ProcessId); + + if (!process) + { + PhFree(processes); + return STATUS_NOT_FOUND; + } + + // USER_SHARED_DATA + PhpSetMemoryRegionType(List, USER_SHARED_DATA, TRUE, UserSharedDataRegion); + + // PEB, heap + { + PROCESS_BASIC_INFORMATION basicInfo; + ULONG numberOfHeaps; + PVOID processHeapsPtr; + PVOID *processHeaps; + ULONG i; +#ifdef _WIN64 + PVOID peb32; + ULONG processHeapsPtr32; + ULONG *processHeaps32; +#endif + + if (NT_SUCCESS(PhGetProcessBasicInformation(ProcessHandle, &basicInfo))) + { + PhpSetMemoryRegionType(List, basicInfo.PebBaseAddress, TRUE, PebRegion); + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, NumberOfHeaps)), + &numberOfHeaps, sizeof(ULONG), NULL)) && numberOfHeaps < MAX_HEAPS) + { + processHeaps = PhAllocate(numberOfHeaps * sizeof(PVOID)); + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessHeaps)), + &processHeapsPtr, sizeof(PVOID), NULL)) && + NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + processHeapsPtr, + processHeaps, numberOfHeaps * sizeof(PVOID), NULL))) + { + for (i = 0; i < numberOfHeaps; i++) + { + if (memoryItem = PhpSetMemoryRegionType(List, processHeaps[i], TRUE, HeapRegion)) + memoryItem->u.Heap.Index = i; + } + } + + PhFree(processHeaps); + } + } +#ifdef _WIN64 + + if (NT_SUCCESS(PhGetProcessPeb32(ProcessHandle, &peb32)) && peb32 != 0) + { + isWow64 = TRUE; + PhpSetMemoryRegionType(List, peb32, TRUE, Peb32Region); + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, NumberOfHeaps)), + &numberOfHeaps, sizeof(ULONG), NULL)) && numberOfHeaps < MAX_HEAPS) + { + processHeaps32 = PhAllocate(numberOfHeaps * sizeof(ULONG)); + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessHeaps)), + &processHeapsPtr32, sizeof(ULONG), NULL)) && + NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, + UlongToPtr(processHeapsPtr32), + processHeaps32, numberOfHeaps * sizeof(ULONG), NULL))) + { + for (i = 0; i < numberOfHeaps; i++) + { + if (memoryItem = PhpSetMemoryRegionType(List, UlongToPtr(processHeaps32[i]), TRUE, Heap32Region)) + memoryItem->u.Heap.Index = i; + } + } + + PhFree(processHeaps32); + } + } +#endif + } + + // TEB, stack + for (i = 0; i < process->NumberOfThreads; i++) + { + PSYSTEM_EXTENDED_THREAD_INFORMATION thread = (PSYSTEM_EXTENDED_THREAD_INFORMATION)process->Threads + i; + + if (WindowsVersion < WINDOWS_VISTA) + { + HANDLE threadHandle; + THREAD_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhOpenThread(&threadHandle, ThreadQueryAccess, thread->ThreadInfo.ClientId.UniqueThread))) + { + if (NT_SUCCESS(PhGetThreadBasicInformation(threadHandle, &basicInfo))) + thread->TebBase = basicInfo.TebBaseAddress; + + NtClose(threadHandle); + } + } + + if (thread->TebBase) + { + NT_TIB ntTib; + SIZE_T bytesRead; + + if (memoryItem = PhpSetMemoryRegionType(List, thread->TebBase, TRUE, TebRegion)) + memoryItem->u.Teb.ThreadId = thread->ThreadInfo.ClientId.UniqueThread; + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, thread->TebBase, &ntTib, sizeof(NT_TIB), &bytesRead)) && + bytesRead == sizeof(NT_TIB)) + { + if ((ULONG_PTR)ntTib.StackLimit < (ULONG_PTR)ntTib.StackBase) + { + if (memoryItem = PhpSetMemoryRegionType(List, ntTib.StackLimit, TRUE, StackRegion)) + memoryItem->u.Stack.ThreadId = thread->ThreadInfo.ClientId.UniqueThread; + } +#ifdef _WIN64 + + if (isWow64 && ntTib.ExceptionList) + { + ULONG teb32 = PtrToUlong(ntTib.ExceptionList); + NT_TIB32 ntTib32; + + // 64-bit and 32-bit TEBs usually share the same memory region, so don't do anything for the 32-bit + // TEB. + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, UlongToPtr(teb32), &ntTib32, sizeof(NT_TIB32), &bytesRead)) && + bytesRead == sizeof(NT_TIB32)) + { + if (ntTib32.StackLimit < ntTib32.StackBase) + { + if (memoryItem = PhpSetMemoryRegionType(List, UlongToPtr(ntTib32.StackLimit), TRUE, Stack32Region)) + memoryItem->u.Stack.ThreadId = thread->ThreadInfo.ClientId.UniqueThread; + } + } + } +#endif + } + } + } + + // Mapped file, heap segment, unusable + for (listEntry = List->ListHead.Flink; listEntry != &List->ListHead; listEntry = listEntry->Flink) + { + memoryItem = CONTAINING_RECORD(listEntry, PH_MEMORY_ITEM, ListEntry); + + if (memoryItem->RegionType != UnknownRegion) + continue; + + if ((memoryItem->Type & (MEM_MAPPED | MEM_IMAGE)) && memoryItem->AllocationBaseItem == memoryItem) + { + PPH_STRING fileName; + + if (NT_SUCCESS(PhGetProcessMappedFileName(ProcessHandle, memoryItem->BaseAddress, &fileName))) + { + PPH_STRING newFileName = PhResolveDevicePrefix(fileName); + + if (newFileName) + PhMoveReference(&fileName, newFileName); + + memoryItem->RegionType = MappedFileRegion; + memoryItem->u.MappedFile.FileName = fileName; + continue; + } + } + + if (memoryItem->State & MEM_COMMIT) + { + UCHAR buffer[HEAP_SEGMENT_MAX_SIZE]; + + if (NT_SUCCESS(NtReadVirtualMemory(ProcessHandle, memoryItem->BaseAddress, + buffer, sizeof(buffer), NULL))) + { + PVOID candidateHeap = NULL; + ULONG candidateHeap32 = 0; + PPH_MEMORY_ITEM heapMemoryItem; + + if (WindowsVersion >= WINDOWS_VISTA) + { + PHEAP_SEGMENT heapSegment = (PHEAP_SEGMENT)buffer; + PHEAP_SEGMENT32 heapSegment32 = (PHEAP_SEGMENT32)buffer; + + if (heapSegment->SegmentSignature == HEAP_SEGMENT_SIGNATURE) + candidateHeap = heapSegment->Heap; + if (heapSegment32->SegmentSignature == HEAP_SEGMENT_SIGNATURE) + candidateHeap32 = heapSegment32->Heap; + } + else + { + PHEAP_SEGMENT_OLD heapSegment = (PHEAP_SEGMENT_OLD)buffer; + PHEAP_SEGMENT_OLD32 heapSegment32 = (PHEAP_SEGMENT_OLD32)buffer; + + if (heapSegment->Signature == HEAP_SEGMENT_SIGNATURE) + candidateHeap = heapSegment->Heap; + if (heapSegment32->Signature == HEAP_SEGMENT_SIGNATURE) + candidateHeap32 = heapSegment32->Heap; + } + + if (candidateHeap) + { + heapMemoryItem = PhLookupMemoryItemList(List, candidateHeap); + + if (heapMemoryItem && heapMemoryItem->BaseAddress == candidateHeap && + heapMemoryItem->RegionType == HeapRegion) + { + memoryItem->RegionType = HeapSegmentRegion; + memoryItem->u.HeapSegment.HeapItem = heapMemoryItem; + continue; + } + } + else if (candidateHeap32) + { + heapMemoryItem = PhLookupMemoryItemList(List, UlongToPtr(candidateHeap32)); + + if (heapMemoryItem && heapMemoryItem->BaseAddress == UlongToPtr(candidateHeap32) && + heapMemoryItem->RegionType == Heap32Region) + { + memoryItem->RegionType = HeapSegment32Region; + memoryItem->u.HeapSegment.HeapItem = heapMemoryItem; + continue; + } + } + } + } + } + + PhFree(processes); + + return STATUS_SUCCESS; +} + +NTSTATUS PhpUpdateMemoryWsCounters( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ HANDLE ProcessHandle + ) +{ + PLIST_ENTRY listEntry; + PMEMORY_WORKING_SET_EX_INFORMATION info; + + info = PhAllocatePage(WS_REQUEST_COUNT * sizeof(MEMORY_WORKING_SET_EX_INFORMATION), NULL); + + if (!info) + return STATUS_NO_MEMORY; + + for (listEntry = List->ListHead.Flink; listEntry != &List->ListHead; listEntry = listEntry->Flink) + { + PPH_MEMORY_ITEM memoryItem = CONTAINING_RECORD(listEntry, PH_MEMORY_ITEM, ListEntry); + ULONG_PTR virtualAddress; + SIZE_T remainingPages; + SIZE_T requestPages; + SIZE_T i; + + if (!(memoryItem->State & MEM_COMMIT)) + continue; + + virtualAddress = (ULONG_PTR)memoryItem->BaseAddress; + remainingPages = memoryItem->RegionSize / PAGE_SIZE; + + while (remainingPages != 0) + { + requestPages = min(remainingPages, WS_REQUEST_COUNT); + + for (i = 0; i < requestPages; i++) + { + info[i].VirtualAddress = (PVOID)virtualAddress; + virtualAddress += PAGE_SIZE; + } + + if (NT_SUCCESS(NtQueryVirtualMemory( + ProcessHandle, + NULL, + MemoryWorkingSetExInformation, + info, + requestPages * sizeof(MEMORY_WORKING_SET_EX_INFORMATION), + NULL + ))) + { + for (i = 0; i < requestPages; i++) + { + PMEMORY_WORKING_SET_EX_BLOCK block = &info[i].u1.VirtualAttributes; + + if (block->Valid) + { + memoryItem->TotalWorkingSetPages++; + + if (block->ShareCount > 1) + memoryItem->SharedWorkingSetPages++; + if (block->ShareCount == 0) + memoryItem->PrivateWorkingSetPages++; + if (block->Shared) + memoryItem->ShareableWorkingSetPages++; + if (block->Locked) + memoryItem->LockedWorkingSetPages++; + } + } + } + + remainingPages -= requestPages; + } + } + + PhFreePage(info); + + return STATUS_SUCCESS; +} + +NTSTATUS PhpUpdateMemoryWsCountersOld( + _In_ PPH_MEMORY_ITEM_LIST List, + _In_ HANDLE ProcessHandle + ) +{ + NTSTATUS status; + PMEMORY_WORKING_SET_INFORMATION info; + PPH_MEMORY_ITEM memoryItem = NULL; + ULONG_PTR i; + + if (!NT_SUCCESS(status = PhGetProcessWorkingSetInformation(ProcessHandle, &info))) + return status; + + for (i = 0; i < info->NumberOfEntries; i++) + { + PMEMORY_WORKING_SET_BLOCK block = &info->WorkingSetInfo[i]; + ULONG_PTR virtualAddress = block->VirtualPage * PAGE_SIZE; + + if (!memoryItem || virtualAddress < (ULONG_PTR)memoryItem->BaseAddress || + virtualAddress >= (ULONG_PTR)memoryItem->BaseAddress + memoryItem->RegionSize) + { + memoryItem = PhLookupMemoryItemList(List, (PVOID)virtualAddress); + } + + if (memoryItem) + { + memoryItem->TotalWorkingSetPages++; + + if (block->ShareCount > 1) + memoryItem->SharedWorkingSetPages++; + if (block->ShareCount == 0) + memoryItem->PrivateWorkingSetPages++; + if (block->Shared) + memoryItem->ShareableWorkingSetPages++; + } + } + + PhFree(info); + + return STATUS_SUCCESS; +} + +NTSTATUS PhQueryMemoryItemList( + _In_ HANDLE ProcessId, + _In_ ULONG Flags, + _Out_ PPH_MEMORY_ITEM_LIST List + ) +{ + NTSTATUS status; + HANDLE processHandle; + ULONG_PTR allocationGranularity; + PVOID baseAddress = (PVOID)0; + MEMORY_BASIC_INFORMATION basicInfo; + PPH_MEMORY_ITEM allocationBaseItem = NULL; + PPH_MEMORY_ITEM previousMemoryItem = NULL; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ProcessId + ))) + { + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION, + ProcessId + ))) + { + return status; + } + } + + List->ProcessId = ProcessId; + PhInitializeAvlTree(&List->Set, PhpMemoryItemCompareFunction); + InitializeListHead(&List->ListHead); + + allocationGranularity = PhSystemBasicInformation.AllocationGranularity; + + while (NT_SUCCESS(NtQueryVirtualMemory( + processHandle, + baseAddress, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + PPH_MEMORY_ITEM memoryItem; + + if (basicInfo.State & MEM_FREE) + { + if (Flags & PH_QUERY_MEMORY_IGNORE_FREE) + goto ContinueLoop; + + basicInfo.AllocationBase = basicInfo.BaseAddress; + } + + memoryItem = PhCreateMemoryItem(); + memoryItem->BasicInfo = basicInfo; + + if (basicInfo.AllocationBase == basicInfo.BaseAddress) + allocationBaseItem = memoryItem; + if (allocationBaseItem && basicInfo.AllocationBase == allocationBaseItem->BaseAddress) + memoryItem->AllocationBaseItem = allocationBaseItem; + + if (basicInfo.State & MEM_COMMIT) + { + memoryItem->CommittedSize = memoryItem->RegionSize; + + if (basicInfo.Type & MEM_PRIVATE) + memoryItem->PrivateSize = memoryItem->RegionSize; + } + + PhAddElementAvlTree(&List->Set, &memoryItem->Links); + InsertTailList(&List->ListHead, &memoryItem->ListEntry); + + if (basicInfo.State & MEM_FREE) + { + if ((ULONG_PTR)basicInfo.BaseAddress & (allocationGranularity - 1)) + { + ULONG_PTR nextAllocationBase; + ULONG_PTR potentialUnusableSize; + + // Split this free region into an unusable and a (possibly empty) usable region. + + nextAllocationBase = ALIGN_UP_BY(basicInfo.BaseAddress, allocationGranularity); + potentialUnusableSize = nextAllocationBase - (ULONG_PTR)basicInfo.BaseAddress; + + memoryItem->RegionType = UnusableRegion; + + // VMMap does this, but is it correct? + //if (previousMemoryItem && (previousMemoryItem->State & MEM_COMMIT)) + // memoryItem->CommittedSize = min(potentialUnusableSize, basicInfo.RegionSize); + + if (nextAllocationBase < (ULONG_PTR)basicInfo.BaseAddress + basicInfo.RegionSize) + { + PPH_MEMORY_ITEM otherMemoryItem; + + memoryItem->RegionSize = potentialUnusableSize; + + otherMemoryItem = PhCreateMemoryItem(); + otherMemoryItem->BasicInfo = basicInfo; + otherMemoryItem->BaseAddress = (PVOID)nextAllocationBase; + otherMemoryItem->AllocationBase = otherMemoryItem->BaseAddress; + otherMemoryItem->RegionSize = basicInfo.RegionSize - potentialUnusableSize; + otherMemoryItem->AllocationBaseItem = otherMemoryItem; + + PhAddElementAvlTree(&List->Set, &otherMemoryItem->Links); + InsertTailList(&List->ListHead, &otherMemoryItem->ListEntry); + + previousMemoryItem = otherMemoryItem; + goto ContinueLoop; + } + } + } + + previousMemoryItem = memoryItem; + +ContinueLoop: + baseAddress = PTR_ADD_OFFSET(baseAddress, basicInfo.RegionSize); + } + + if (Flags & PH_QUERY_MEMORY_REGION_TYPE) + PhpUpdateMemoryRegionTypes(List, processHandle); + + if (Flags & PH_QUERY_MEMORY_WS_COUNTERS) + { + if (WindowsVersion >= WINDOWS_SERVER_2003) + PhpUpdateMemoryWsCounters(List, processHandle); + else + PhpUpdateMemoryWsCountersOld(List, processHandle); + } + + NtClose(processHandle); + + return STATUS_SUCCESS; +} diff --git a/ProcessHacker/memrslt.c b/ProcessHacker/memrslt.c new file mode 100644 index 0000000..a375b59 --- /dev/null +++ b/ProcessHacker/memrslt.c @@ -0,0 +1,617 @@ +/* + * Process Hacker - + * memory search results + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include "pcre/pcre2.h" +#include + +#define FILTER_CONTAINS 1 +#define FILTER_CONTAINS_IGNORECASE 2 +#define FILTER_REGEX 3 +#define FILTER_REGEX_IGNORECASE 4 + +typedef struct _MEMORY_RESULTS_CONTEXT +{ + HANDLE ProcessId; + PPH_LIST Results; + + PH_LAYOUT_MANAGER LayoutManager; +} MEMORY_RESULTS_CONTEXT, *PMEMORY_RESULTS_CONTEXT; + +INT_PTR CALLBACK PhpMemoryResultsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +VOID PhShowMemoryResultsDialog( + _In_ HANDLE ProcessId, + _In_ PPH_LIST Results + ) +{ + HWND windowHandle; + PMEMORY_RESULTS_CONTEXT context; + ULONG i; + + context = PhAllocate(sizeof(MEMORY_RESULTS_CONTEXT)); + context->ProcessId = ProcessId; + context->Results = Results; + + PhReferenceObject(Results); + + for (i = 0; i < Results->Count; i++) + PhReferenceMemoryResult(Results->Items[i]); + + windowHandle = CreateDialogParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MEMRESULTS), + NULL, + PhpMemoryResultsDlgProc, + (LPARAM)context + ); + ShowWindow(windowHandle, SW_SHOW); +} + +static PPH_STRING PhpGetStringForSelectedResults( + _In_ HWND ListViewHandle, + _In_ PPH_LIST Results, + _In_ BOOLEAN All + ) +{ + PH_STRING_BUILDER stringBuilder; + ULONG i; + + PhInitializeStringBuilder(&stringBuilder, 0x100); + + for (i = 0; i < Results->Count; i++) + { + PPH_MEMORY_RESULT result; + + if (!All) + { + if (!(ListView_GetItemState(ListViewHandle, i, LVIS_SELECTED) & LVIS_SELECTED)) + continue; + } + + result = Results->Items[i]; + + PhAppendFormatStringBuilder(&stringBuilder, L"0x%Ix (%u): %s\r\n", result->Address, result->Length, + result->Display.Buffer ? result->Display.Buffer : L""); + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +static VOID FilterResults( + _In_ HWND hwndDlg, + _In_ PMEMORY_RESULTS_CONTEXT Context, + _In_ ULONG Type + ) +{ + PPH_STRING selectedChoice = NULL; + PPH_LIST results; + pcre2_code *compiledExpression; + pcre2_match_data *matchData; + + results = Context->Results; + + SetCursor(LoadCursor(NULL, IDC_WAIT)); + + while (PhaChoiceDialog( + hwndDlg, + L"Filter", + L"Enter the filter pattern:", + NULL, + 0, + NULL, + PH_CHOICE_DIALOG_USER_CHOICE, + &selectedChoice, + NULL, + L"MemFilterChoices" + )) + { + PPH_LIST newResults = NULL; + ULONG i; + + if (Type == FILTER_CONTAINS || Type == FILTER_CONTAINS_IGNORECASE) + { + newResults = PhCreateList(1024); + + if (Type == FILTER_CONTAINS) + { + for (i = 0; i < results->Count; i++) + { + PPH_MEMORY_RESULT result = results->Items[i]; + + if (wcsstr(result->Display.Buffer, selectedChoice->Buffer)) + { + PhReferenceMemoryResult(result); + PhAddItemList(newResults, result); + } + } + } + else + { + PPH_STRING upperChoice; + + upperChoice = PhaUpperString(selectedChoice); + + for (i = 0; i < results->Count; i++) + { + PPH_MEMORY_RESULT result = results->Items[i]; + PWSTR upperDisplay; + + upperDisplay = PhAllocateForMemorySearch(result->Display.Length + sizeof(WCHAR)); + // Copy the null terminator as well. + memcpy(upperDisplay, result->Display.Buffer, result->Display.Length + sizeof(WCHAR)); + + _wcsupr(upperDisplay); + + if (wcsstr(upperDisplay, upperChoice->Buffer)) + { + PhReferenceMemoryResult(result); + PhAddItemList(newResults, result); + } + + PhFreeForMemorySearch(upperDisplay); + } + } + } + else if (Type == FILTER_REGEX || Type == FILTER_REGEX_IGNORECASE) + { + int errorCode; + PCRE2_SIZE errorOffset; + + compiledExpression = pcre2_compile( + selectedChoice->Buffer, + selectedChoice->Length / sizeof(WCHAR), + (Type == FILTER_REGEX_IGNORECASE ? PCRE2_CASELESS : 0) | PCRE2_DOTALL, + &errorCode, + &errorOffset, + NULL + ); + + if (!compiledExpression) + { + PhShowError(hwndDlg, L"Unable to compile the regular expression: \"%s\" at position %zu.", + PhGetStringOrDefault(PH_AUTO(PhPcre2GetErrorMessage(errorCode)), L"Unknown error"), + errorOffset + ); + continue; + } + + matchData = pcre2_match_data_create_from_pattern(compiledExpression, NULL); + + newResults = PhCreateList(1024); + + for (i = 0; i < results->Count; i++) + { + PPH_MEMORY_RESULT result = results->Items[i]; + + if (pcre2_match( + compiledExpression, + result->Display.Buffer, + result->Display.Length / sizeof(WCHAR), + 0, + 0, + matchData, + NULL + ) >= 0) + { + PhReferenceMemoryResult(result); + PhAddItemList(newResults, result); + } + } + + pcre2_match_data_free(matchData); + pcre2_code_free(compiledExpression); + } + + if (newResults) + { + PhShowMemoryResultsDialog(Context->ProcessId, newResults); + PhDereferenceMemoryResults((PPH_MEMORY_RESULT *)newResults->Items, newResults->Count); + PhDereferenceObject(newResults); + break; + } + } + + SetCursor(LoadCursor(NULL, IDC_ARROW)); +} + +INT_PTR CALLBACK PhpMemoryResultsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PMEMORY_RESULTS_CONTEXT context; + + if (uMsg != WM_INITDIALOG) + { + context = GetProp(hwndDlg, PhMakeContextAtom()); + } + else + { + context = (PMEMORY_RESULTS_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhRegisterDialog(hwndDlg); + + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(context->ProcessId)) + { + SetWindowText(hwndDlg, PhaFormatString(L"Results - %s (%u)", + processItem->ProcessName->Buffer, HandleToUlong(processItem->ProcessId))->Buffer); + PhDereferenceObject(processItem); + } + } + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 120, L"Address"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"Length"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 200, L"Result"); + + PhLoadListViewColumnsFromSetting(L"MemResultsListViewColumns", lvHandle); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LIST), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_COPY), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_SAVE), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_FILTER), NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 250; + rect.bottom = 180; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + ListView_SetItemCount(lvHandle, context->Results->Count); + + SetDlgItemText(hwndDlg, IDC_INTRO, PhaFormatString(L"%s results.", + PhaFormatUInt64(context->Results->Count, TRUE)->Buffer)->Buffer); + + { + PH_RECTANGLE windowRectangle; + + windowRectangle.Position = PhGetIntegerPairSetting(L"MemResultsPosition"); + windowRectangle.Size = PhGetScalableIntegerPairSetting(L"MemResultsSize", TRUE).Pair; + PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); + + MoveWindow(hwndDlg, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + + // Implement cascading by saving an offsetted rectangle. + windowRectangle.Left += 20; + windowRectangle.Top += 20; + + PhSetIntegerPairSetting(L"MemResultsPosition", windowRectangle.Position); + PhSetScalableIntegerPairSetting2(L"MemResultsSize", windowRectangle.Size); + } + } + break; + case WM_DESTROY: + { + PhSaveWindowPlacementToSetting(L"MemResultsPosition", L"MemResultsSize", hwndDlg); + PhSaveListViewColumnsToSetting(L"MemResultsListViewColumns", GetDlgItem(hwndDlg, IDC_LIST)); + + PhDeleteLayoutManager(&context->LayoutManager); + PhUnregisterDialog(hwndDlg); + RemoveProp(hwndDlg, PhMakeContextAtom()); + + PhDereferenceMemoryResults((PPH_MEMORY_RESULT *)context->Results->Items, context->Results->Count); + PhDereferenceObject(context->Results); + PhFree(context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_COPY: + { + HWND lvHandle; + PPH_STRING string; + ULONG selectedCount; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + selectedCount = ListView_GetSelectedCount(lvHandle); + + if (selectedCount == 0) + { + // User didn't select anything, so copy all items. + string = PhpGetStringForSelectedResults(lvHandle, context->Results, TRUE); + PhSetStateAllListViewItems(lvHandle, LVIS_SELECTED, LVIS_SELECTED); + } + else + { + string = PhpGetStringForSelectedResults(lvHandle, context->Results, FALSE); + } + + PhSetClipboardString(hwndDlg, &string->sr); + PhDereferenceObject(string); + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)lvHandle, TRUE); + } + break; + case IDC_SAVE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Text files (*.txt)", L"*.txt" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, L"Search results.txt"); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + NTSTATUS status; + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + PPH_STRING string; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + PhWriteStringAsUtf8FileStream(fileStream, &PhUnicodeByteOrderMark); + PhWritePhTextHeader(fileStream); + + string = PhpGetStringForSelectedResults(GetDlgItem(hwndDlg, IDC_LIST), context->Results, TRUE); + PhWriteStringAsUtf8FileStreamEx(fileStream, string->Buffer, string->Length); + PhDereferenceObject(string); + + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + break; + case IDC_FILTER: + { + PPH_EMENU menu; + RECT buttonRect; + POINT point; + PPH_EMENU_ITEM selectedItem; + ULONG filterType = 0; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MEMFILTER), 0); + + GetClientRect(GetDlgItem(hwndDlg, IDC_FILTER), &buttonRect); + point.x = 0; + point.y = buttonRect.bottom; + + ClientToScreen(GetDlgItem(hwndDlg, IDC_FILTER), &point); + selectedItem = PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); + + if (selectedItem) + { + switch (selectedItem->Id) + { + case ID_FILTER_CONTAINS: + filterType = FILTER_CONTAINS; + break; + case ID_FILTER_CONTAINS_CASEINSENSITIVE: + filterType = FILTER_CONTAINS_IGNORECASE; + break; + case ID_FILTER_REGEX: + filterType = FILTER_REGEX; + break; + case ID_FILTER_REGEX_CASEINSENSITIVE: + filterType = FILTER_REGEX_IGNORECASE; + break; + } + } + + if (filterType != 0) + FilterResults(hwndDlg, context, filterType); + + PhDestroyEMenu(menu); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhHandleListViewNotifyForCopy(lParam, lvHandle); + + switch (header->code) + { + case LVN_GETDISPINFO: + { + NMLVDISPINFO *dispInfo = (NMLVDISPINFO *)header; + + if (dispInfo->item.mask & LVIF_TEXT) + { + PPH_MEMORY_RESULT result = context->Results->Items[dispInfo->item.iItem]; + + switch (dispInfo->item.iSubItem) + { + case 0: + { + WCHAR addressString[PH_PTR_STR_LEN_1]; + + PhPrintPointer(addressString, result->Address); + wcsncpy_s( + dispInfo->item.pszText, + dispInfo->item.cchTextMax, + addressString, + _TRUNCATE + ); + } + break; + case 1: + { + WCHAR lengthString[PH_INT32_STR_LEN_1]; + + PhPrintUInt32(lengthString, (ULONG)result->Length); + wcsncpy_s( + dispInfo->item.pszText, + dispInfo->item.cchTextMax, + lengthString, + _TRUNCATE + ); + } + break; + case 2: + wcsncpy_s( + dispInfo->item.pszText, + dispInfo->item.cchTextMax, + result->Display.Buffer, + _TRUNCATE + ); + break; + } + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == lvHandle) + { + INT index; + + if ((index = ListView_GetNextItem( + lvHandle, + -1, + LVNI_SELECTED + )) != -1) + { + NTSTATUS status; + PPH_MEMORY_RESULT result = context->Results->Items[index]; + HANDLE processHandle; + MEMORY_BASIC_INFORMATION basicInfo; + PPH_SHOWMEMORYEDITOR showMemoryEditor; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + context->ProcessId + ))) + { + if (NT_SUCCESS(status = NtQueryVirtualMemory( + processHandle, + result->Address, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + showMemoryEditor = PhAllocate(sizeof(PH_SHOWMEMORYEDITOR)); + memset(showMemoryEditor, 0, sizeof(PH_SHOWMEMORYEDITOR)); + showMemoryEditor->ProcessId = context->ProcessId; + showMemoryEditor->BaseAddress = basicInfo.BaseAddress; + showMemoryEditor->RegionSize = basicInfo.RegionSize; + showMemoryEditor->SelectOffset = (ULONG)((ULONG_PTR)result->Address - (ULONG_PTR)basicInfo.BaseAddress); + showMemoryEditor->SelectLength = (ULONG)result->Length; + ProcessHacker_ShowMemoryEditor(PhMainWndHandle, showMemoryEditor); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to edit memory", status, 0); + } + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/memsrch.c b/ProcessHacker/memsrch.c new file mode 100644 index 0000000..eb6693d --- /dev/null +++ b/ProcessHacker/memsrch.c @@ -0,0 +1,726 @@ +/* + * Process Hacker - + * memory searchers + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define WM_PH_MEMORY_STATUS_UPDATE (WM_APP + 301) + +#define PH_SEARCH_UPDATE 1 +#define PH_SEARCH_COMPLETED 2 + +typedef struct _MEMORY_STRING_CONTEXT +{ + HANDLE ProcessId; + HANDLE ProcessHandle; + ULONG MinimumLength; + BOOLEAN DetectUnicode; + BOOLEAN Private; + BOOLEAN Image; + BOOLEAN Mapped; + + HWND WindowHandle; + HANDLE ThreadHandle; + PH_MEMORY_STRING_OPTIONS Options; + PPH_LIST Results; +} MEMORY_STRING_CONTEXT, *PMEMORY_STRING_CONTEXT; + +INT_PTR CALLBACK PhpMemoryStringDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpMemoryStringProgressDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PVOID PhMemorySearchHeap = NULL; +LONG PhMemorySearchHeapRefCount = 0; +PH_QUEUED_LOCK PhMemorySearchHeapLock = PH_QUEUED_LOCK_INIT; + +PVOID PhAllocateForMemorySearch( + _In_ SIZE_T Size + ) +{ + PVOID memory; + + PhAcquireQueuedLockExclusive(&PhMemorySearchHeapLock); + + if (!PhMemorySearchHeap) + { + assert(PhMemorySearchHeapRefCount == 0); + PhMemorySearchHeap = RtlCreateHeap( + HEAP_GROWABLE | HEAP_CLASS_1, + NULL, + 8192 * 1024, // 8 MB + 2048 * 1024, // 2 MB + NULL, + NULL + ); + } + + if (PhMemorySearchHeap) + { + // Don't use HEAP_NO_SERIALIZE - it's very slow on Vista and above. + memory = RtlAllocateHeap(PhMemorySearchHeap, 0, Size); + + if (memory) + PhMemorySearchHeapRefCount++; + } + else + { + memory = NULL; + } + + PhReleaseQueuedLockExclusive(&PhMemorySearchHeapLock); + + return memory; +} + +VOID PhFreeForMemorySearch( + _In_ _Post_invalid_ PVOID Memory + ) +{ + PhAcquireQueuedLockExclusive(&PhMemorySearchHeapLock); + + RtlFreeHeap(PhMemorySearchHeap, 0, Memory); + + if (--PhMemorySearchHeapRefCount == 0) + { + RtlDestroyHeap(PhMemorySearchHeap); + PhMemorySearchHeap = NULL; + } + + PhReleaseQueuedLockExclusive(&PhMemorySearchHeapLock); +} + +PVOID PhCreateMemoryResult( + _In_ PVOID Address, + _In_ SIZE_T Length + ) +{ + PPH_MEMORY_RESULT result; + + result = PhAllocateForMemorySearch(sizeof(PH_MEMORY_RESULT)); + + if (!result) + return NULL; + + result->RefCount = 1; + result->Address = Address; + result->Length = Length; + result->Display.Length = 0; + result->Display.Buffer = NULL; + + return result; +} + +VOID PhReferenceMemoryResult( + _In_ PPH_MEMORY_RESULT Result + ) +{ + _InterlockedIncrement(&Result->RefCount); +} + +VOID PhDereferenceMemoryResult( + _In_ PPH_MEMORY_RESULT Result + ) +{ + if (_InterlockedDecrement(&Result->RefCount) == 0) + { + if (Result->Display.Buffer) + PhFreeForMemorySearch(Result->Display.Buffer); + + PhFreeForMemorySearch(Result); + } +} + +VOID PhDereferenceMemoryResults( + _In_reads_(NumberOfResults) PPH_MEMORY_RESULT *Results, + _In_ ULONG NumberOfResults + ) +{ + ULONG i; + + for (i = 0; i < NumberOfResults; i++) + PhDereferenceMemoryResult(Results[i]); +} + +VOID PhSearchMemoryString( + _In_ HANDLE ProcessHandle, + _In_ PPH_MEMORY_STRING_OPTIONS Options + ) +{ + ULONG minimumLength; + BOOLEAN detectUnicode; + ULONG memoryTypeMask; + PVOID baseAddress; + MEMORY_BASIC_INFORMATION basicInfo; + PUCHAR buffer; + SIZE_T bufferSize; + PWSTR displayBuffer; + SIZE_T displayBufferCount; + + minimumLength = Options->MinimumLength; + detectUnicode = Options->DetectUnicode; + memoryTypeMask = Options->MemoryTypeMask; + + if (minimumLength < 4) + return; + + baseAddress = (PVOID)0; + + bufferSize = PAGE_SIZE * 64; + buffer = PhAllocatePage(bufferSize, NULL); + + if (!buffer) + return; + + displayBufferCount = PH_DISPLAY_BUFFER_COUNT; + displayBuffer = PhAllocatePage((displayBufferCount + 1) * sizeof(WCHAR), NULL); + + if (!displayBuffer) + { + PhFreePage(buffer); + return; + } + + while (NT_SUCCESS(NtQueryVirtualMemory( + ProcessHandle, + baseAddress, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + ULONG_PTR offset; + SIZE_T readSize; + + if (Options->Header.Cancel) + break; + if (basicInfo.State != MEM_COMMIT) + goto ContinueLoop; + if ((basicInfo.Type & memoryTypeMask) == 0) + goto ContinueLoop; + if (basicInfo.Protect == PAGE_NOACCESS) + goto ContinueLoop; + if (basicInfo.Protect & PAGE_GUARD) + goto ContinueLoop; + + readSize = basicInfo.RegionSize; + + if (basicInfo.RegionSize > bufferSize) + { + // Don't allocate a huge buffer though. + if (basicInfo.RegionSize <= 16 * 1024 * 1024) // 16 MB + { + PhFreePage(buffer); + bufferSize = basicInfo.RegionSize; + buffer = PhAllocatePage(bufferSize, NULL); + + if (!buffer) + break; + } + else + { + readSize = bufferSize; + } + } + + for (offset = 0; offset < basicInfo.RegionSize; offset += readSize) + { + ULONG_PTR i; + UCHAR byte; // current byte + UCHAR byte1; // previous byte + UCHAR byte2; // byte before previous byte + BOOLEAN printable; + BOOLEAN printable1; + BOOLEAN printable2; + ULONG length; + + if (!NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(baseAddress, offset), + buffer, + readSize, + NULL + ))) + continue; + + byte1 = 0; + byte2 = 0; + printable1 = FALSE; + printable2 = FALSE; + length = 0; + + for (i = 0; i < readSize; i++) + { + byte = buffer[i]; + printable = PhCharIsPrintable[byte]; + + // To find strings Process Hacker uses a state table. + // * byte2 - byte before previous byte + // * byte1 - previous byte + // * byte - current byte + // * length - length of current string run + // + // The states are described below. + // + // [byte2] [byte1] [byte] ... + // [char] means printable, [oth] means non-printable. + // + // 1. [char] [char] [char] ... + // (we're in a non-wide sequence) + // -> append char. + // 2. [char] [char] [oth] ... + // (we reached the end of a non-wide sequence, or we need to start a wide sequence) + // -> if current string is big enough, create result (non-wide). + // otherwise if byte = null, reset to new string with byte1 as first character. + // otherwise if byte != null, reset to new string. + // 3. [char] [oth] [char] ... + // (we're in a wide sequence) + // -> (byte1 should = null) append char. + // 4. [char] [oth] [oth] ... + // (we reached the end of a wide sequence) + // -> (byte1 should = null) if the current string is big enough, create result (wide). + // otherwise, reset to new string. + // 5. [oth] [char] [char] ... + // (we reached the end of a wide sequence, or we need to start a non-wide sequence) + // -> (excluding byte1) if the current string is big enough, create result (wide). + // otherwise, reset to new string with byte1 as first character and byte as + // second character. + // 6. [oth] [char] [oth] ... + // (we're in a wide sequence) + // -> (byte2 and byte should = null) do nothing. + // 7. [oth] [oth] [char] ... + // (we're starting a sequence, but we don't know if it's a wide or non-wide sequence) + // -> append char. + // 8. [oth] [oth] [oth] ... + // (nothing) + // -> do nothing. + + if (printable2 && printable1 && printable) + { + if (length < displayBufferCount) + displayBuffer[length] = byte; + + length++; + } + else if (printable2 && printable1 && !printable) + { + if (length >= minimumLength) + { + goto CreateResult; + } + else if (byte == 0) + { + length = 1; + displayBuffer[0] = byte1; + } + else + { + length = 0; + } + } + else if (printable2 && !printable1 && printable) + { + if (byte1 == 0) + { + if (length < displayBufferCount) + displayBuffer[length] = byte; + + length++; + } + } + else if (printable2 && !printable1 && !printable) + { + if (length >= minimumLength) + { + goto CreateResult; + } + else + { + length = 0; + } + } + else if (!printable2 && printable1 && printable) + { + if (length >= minimumLength + 1) // length - 1 >= minimumLength but avoiding underflow + { + length--; // exclude byte1 + goto CreateResult; + } + else + { + length = 2; + displayBuffer[0] = byte1; + displayBuffer[1] = byte; + } + } + else if (!printable2 && printable1 && !printable) + { + // Nothing + } + else if (!printable2 && !printable1 && printable) + { + if (length < displayBufferCount) + displayBuffer[length] = byte; + + length++; + } + else if (!printable2 && !printable1 && !printable) + { + // Nothing + } + + goto AfterCreateResult; + +CreateResult: + { + PPH_MEMORY_RESULT result; + ULONG lengthInBytes; + ULONG bias; + BOOLEAN isWide; + ULONG displayLength; + + lengthInBytes = length; + bias = 0; + isWide = FALSE; + + if (printable1 == printable) // determine if string was wide (refer to state table, 4 and 5) + { + isWide = TRUE; + lengthInBytes *= 2; + } + + if (printable) // byte1 excluded (refer to state table, 5) + { + bias = 1; + } + + if (!(isWide && !detectUnicode) && (result = PhCreateMemoryResult( + PTR_ADD_OFFSET(baseAddress, i - bias - lengthInBytes), + lengthInBytes + ))) + { + displayLength = (ULONG)(min(length, displayBufferCount) * sizeof(WCHAR)); + + if (result->Display.Buffer = PhAllocateForMemorySearch(displayLength + sizeof(WCHAR))) + { + memcpy(result->Display.Buffer, displayBuffer, displayLength); + result->Display.Buffer[displayLength / sizeof(WCHAR)] = 0; + result->Display.Length = displayLength; + } + + Options->Header.Callback( + result, + Options->Header.Context + ); + } + + length = 0; + } +AfterCreateResult: + + byte2 = byte1; + byte1 = byte; + printable2 = printable1; + printable1 = printable; + } + } + +ContinueLoop: + baseAddress = PTR_ADD_OFFSET(baseAddress, basicInfo.RegionSize); + } + + if (buffer) + PhFreePage(buffer); +} + +VOID PhShowMemoryStringDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + NTSTATUS status; + HANDLE processHandle; + MEMORY_STRING_CONTEXT context; + PPH_SHOWMEMORYRESULTS showMemoryResults; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ProcessItem->ProcessId + ))) + { + PhShowStatus(ParentWindowHandle, L"Unable to open the process", status, 0); + return; + } + + memset(&context, 0, sizeof(MEMORY_STRING_CONTEXT)); + context.ProcessId = ProcessItem->ProcessId; + context.ProcessHandle = processHandle; + + if (DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MEMSTRING), + ParentWindowHandle, + PhpMemoryStringDlgProc, + (LPARAM)&context + ) != IDOK) + { + NtClose(processHandle); + return; + } + + context.Results = PhCreateList(1024); + + if (DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PROGRESS), + ParentWindowHandle, + PhpMemoryStringProgressDlgProc, + (LPARAM)&context + ) == IDOK) + { + showMemoryResults = PhAllocate(sizeof(PH_SHOWMEMORYRESULTS)); + showMemoryResults->ProcessId = ProcessItem->ProcessId; + showMemoryResults->Results = context.Results; + + PhReferenceObject(context.Results); + ProcessHacker_ShowMemoryResults( + PhMainWndHandle, + showMemoryResults + ); + } + + PhDereferenceObject(context.Results); + NtClose(processHandle); +} + +INT_PTR CALLBACK PhpMemoryStringDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)lParam); + SetDlgItemText(hwndDlg, IDC_MINIMUMLENGTH, L"10"); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_DETECTUNICODE), BST_CHECKED); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_PRIVATE), BST_CHECKED); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PMEMORY_STRING_CONTEXT context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + ULONG64 minimumLength = 10; + + PhStringToInteger64(&PhaGetDlgItemText(hwndDlg, IDC_MINIMUMLENGTH)->sr, 0, &minimumLength); + + if (minimumLength < 4) + { + PhShowError(hwndDlg, L"The minimum length must be at least 4."); + break; + } + + context->MinimumLength = (ULONG)minimumLength; + context->DetectUnicode = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DETECTUNICODE)) == BST_CHECKED; + context->Private = Button_GetCheck(GetDlgItem(hwndDlg, IDC_PRIVATE)) == BST_CHECKED; + context->Image = Button_GetCheck(GetDlgItem(hwndDlg, IDC_IMAGE)) == BST_CHECKED; + context->Mapped = Button_GetCheck(GetDlgItem(hwndDlg, IDC_MAPPED)) == BST_CHECKED; + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} + +static BOOL NTAPI PhpMemoryStringResultCallback( + _In_ _Assume_refs_(1) PPH_MEMORY_RESULT Result, + _In_opt_ PVOID Context + ) +{ + PMEMORY_STRING_CONTEXT context = Context; + + PhAddItemList(context->Results, Result); + + return TRUE; +} + +NTSTATUS PhpMemoryStringThreadStart( + _In_ PVOID Parameter + ) +{ + PMEMORY_STRING_CONTEXT context = Parameter; + + context->Options.Header.Callback = PhpMemoryStringResultCallback; + context->Options.Header.Context = context; + context->Options.MinimumLength = context->MinimumLength; + context->Options.DetectUnicode = context->DetectUnicode; + + if (context->Private) + context->Options.MemoryTypeMask |= MEM_PRIVATE; + if (context->Image) + context->Options.MemoryTypeMask |= MEM_IMAGE; + if (context->Mapped) + context->Options.MemoryTypeMask |= MEM_MAPPED; + + PhSearchMemoryString(context->ProcessHandle, &context->Options); + + SendMessage( + context->WindowHandle, + WM_PH_MEMORY_STATUS_UPDATE, + PH_SEARCH_COMPLETED, + 0 + ); + + return STATUS_SUCCESS; +} + +INT_PTR CALLBACK PhpMemoryStringProgressDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PMEMORY_STRING_CONTEXT context = (PMEMORY_STRING_CONTEXT)lParam; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, L"Searching..."); + + PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE); + SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75); + + context->WindowHandle = hwndDlg; + context->ThreadHandle = PhCreateThread(0, PhpMemoryStringThreadStart, context); + + if (!context->ThreadHandle) + { + PhShowStatus(hwndDlg, L"Unable to create the search thread", 0, GetLastError()); + EndDialog(hwndDlg, IDCANCEL); + return FALSE; + } + + SetTimer(hwndDlg, 1, 500, NULL); + } + break; + case WM_DESTROY: + { + PMEMORY_STRING_CONTEXT context; + + context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (context->ThreadHandle) + NtClose(context->ThreadHandle); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + PMEMORY_STRING_CONTEXT context = + (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE); + context->Options.Header.Cancel = TRUE; + } + break; + } + } + break; + case WM_TIMER: + { + if (wParam == 1) + { + PMEMORY_STRING_CONTEXT context = + (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + PPH_STRING progressText; + PPH_STRING numberText; + + numberText = PhFormatUInt64(context->Results->Count, TRUE); + progressText = PhFormatString(L"%s strings found...", numberText->Buffer); + PhDereferenceObject(numberText); + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, progressText->Buffer); + PhDereferenceObject(progressText); + InvalidateRect(GetDlgItem(hwndDlg, IDC_PROGRESSTEXT), NULL, FALSE); + } + } + break; + case WM_PH_MEMORY_STATUS_UPDATE: + { + PMEMORY_STRING_CONTEXT context; + + context = (PMEMORY_STRING_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + switch (wParam) + { + case PH_SEARCH_COMPLETED: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/miniinfo.c b/ProcessHacker/miniinfo.c new file mode 100644 index 0000000..2878bfe --- /dev/null +++ b/ProcessHacker/miniinfo.c @@ -0,0 +1,2266 @@ +/* + * Process Hacker - + * mini information window + * + * Copyright (C) 2015-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static HWND PhMipContainerWindow = NULL; +static POINT PhMipSourcePoint; +static LONG PhMipPinCounts[MaxMiniInfoPinType]; +static LONG PhMipMaxPinCounts[] = +{ + 1, // MiniInfoManualPinType + 1, // MiniInfoIconPinType + 1, // MiniInfoActivePinType + 1, // MiniInfoHoverPinType + 1, // MiniInfoChildControlPinType +}; +C_ASSERT(sizeof(PhMipMaxPinCounts) / sizeof(LONG) == MaxMiniInfoPinType); +static LONG PhMipDelayedPinAdjustments[MaxMiniInfoPinType]; +static PPH_MESSAGE_LOOP_FILTER_ENTRY PhMipMessageLoopFilterEntry; +static HWND PhMipLastTrackedWindow; +static HWND PhMipLastNcTrackedWindow; +static ULONG PhMipRefreshAutomatically; +static BOOLEAN PhMipPinned; + +static HWND PhMipWindow = NULL; +static PH_LAYOUT_MANAGER PhMipLayoutManager; +static RECT MinimumSize; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; +static PH_STRINGREF DownArrowPrefix = PH_STRINGREF_INIT(L"\u25be "); +static WNDPROC SectionControlOldWndProc; + +static PPH_LIST SectionList; +static PH_MINIINFO_PARAMETERS CurrentParameters; +static PPH_MINIINFO_SECTION CurrentSection; + +VOID PhPinMiniInformation( + _In_ PH_MINIINFO_PIN_TYPE PinType, + _In_ LONG PinCount, + _In_opt_ ULONG PinDelayMs, + _In_ ULONG Flags, + _In_opt_ PWSTR SectionName, + _In_opt_ PPOINT SourcePoint + ) +{ + PH_MIP_ADJUST_PIN_RESULT adjustPinResult; + + if (PinDelayMs && PinCount < 0) + { + PhMipDelayedPinAdjustments[PinType] = PinCount; + SetTimer(PhMipContainerWindow, MIP_TIMER_PIN_FIRST + PinType, PinDelayMs, NULL); + return; + } + else + { + PhMipDelayedPinAdjustments[PinType] = 0; + KillTimer(PhMipContainerWindow, MIP_TIMER_PIN_FIRST + PinType); + } + + adjustPinResult = PhMipAdjustPin(PinType, PinCount); + + if (adjustPinResult == ShowAdjustPinResult) + { + PH_RECTANGLE windowRectangle; + ULONG opacity; + + if (SourcePoint) + PhMipSourcePoint = *SourcePoint; + + if (!PhMipContainerWindow) + { + WNDCLASSEX wcex; + + memset(&wcex, 0, sizeof(WNDCLASSEX)); + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = 0; + wcex.lpfnWndProc = PhMipContainerWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = PhInstanceHandle; + wcex.hIcon = LoadIcon(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wcex.lpszClassName = MIP_CONTAINER_CLASSNAME; + wcex.hIconSm = (HICON)LoadImage(PhInstanceHandle, MAKEINTRESOURCE(IDI_PROCESSHACKER), IMAGE_ICON, 16, 16, 0); + RegisterClassEx(&wcex); + + PhMipContainerWindow = CreateWindow( + MIP_CONTAINER_CLASSNAME, + L"Process Hacker", + WS_BORDER | WS_THICKFRAME | WS_POPUP, + 0, + 0, + 400, + 400, + NULL, + NULL, + PhInstanceHandle, + NULL + ); + PhSetWindowExStyle(PhMipContainerWindow, WS_EX_TOOLWINDOW, WS_EX_TOOLWINDOW); + PhMipWindow = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MINIINFO), + PhMipContainerWindow, + PhMipMiniInfoDialogProc + ); + ShowWindow(PhMipWindow, SW_SHOW); + + if (PhGetIntegerSetting(L"MiniInfoWindowPinned")) + PhMipSetPinned(TRUE); + + PhMipRefreshAutomatically = PhGetIntegerSetting(L"MiniInfoWindowRefreshAutomatically"); + + opacity = PhGetIntegerSetting(L"MiniInfoWindowOpacity"); + + if (opacity != 0) + PhSetWindowOpacity(PhMipContainerWindow, opacity); + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = 210; + MinimumSize.bottom = 60; + MapDialogRect(PhMipWindow, &MinimumSize); + } + + if (!(Flags & PH_MINIINFO_LOAD_POSITION)) + { + PhMipCalculateWindowRectangle(&PhMipSourcePoint, &windowRectangle); + SetWindowPos( + PhMipContainerWindow, + HWND_TOPMOST, + windowRectangle.Left, + windowRectangle.Top, + windowRectangle.Width, + windowRectangle.Height, + SWP_NOACTIVATE + ); + } + else + { + PhLoadWindowPlacementFromSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow); + SetWindowPos(PhMipContainerWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + } + + ShowWindow(PhMipContainerWindow, (Flags & PH_MINIINFO_ACTIVATE_WINDOW) ? SW_SHOW : SW_SHOWNOACTIVATE); + } + else if (adjustPinResult == HideAdjustPinResult) + { + if (PhMipContainerWindow) + ShowWindow(PhMipContainerWindow, SW_HIDE); + } + else + { + if ((Flags & PH_MINIINFO_ACTIVATE_WINDOW) && IsWindowVisible(PhMipContainerWindow)) + SetActiveWindow(PhMipContainerWindow); + } + + if (Flags & PH_MINIINFO_ACTIVATE_WINDOW) + SetForegroundWindow(PhMipContainerWindow); + + if (SectionName && (!PhMipPinned || !(Flags & PH_MINIINFO_DONT_CHANGE_SECTION_IF_PINNED))) + { + PH_STRINGREF sectionName; + PPH_MINIINFO_SECTION section; + + PhInitializeStringRefLongHint(§ionName, SectionName); + + if (section = PhMipFindSection(§ionName)) + PhMipChangeSection(section); + } +} + +LRESULT CALLBACK PhMipContainerWndProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_SHOWWINDOW: + { + PhMipContainerOnShowWindow(!!wParam, (ULONG)lParam); + } + break; + case WM_ACTIVATE: + { + PhMipContainerOnActivate(LOWORD(wParam), !!HIWORD(wParam)); + } + break; + case WM_SIZE: + { + PhMipContainerOnSize(); + } + break; + case WM_SIZING: + { + PhMipContainerOnSizing((ULONG)wParam, (PRECT)lParam); + } + break; + case WM_EXITSIZEMOVE: + { + PhMipContainerOnExitSizeMove(); + } + break; + case WM_CLOSE: + { + // Hide, don't close. + ShowWindow(hWnd, SW_HIDE); + SetWindowLongPtr(hWnd, DWLP_MSGRESULT, 0); + } + return TRUE; + case WM_ERASEBKGND: + { + if (PhMipContainerOnEraseBkgnd((HDC)wParam)) + return TRUE; + } + break; + case WM_TIMER: + { + PhMipContainerOnTimer((ULONG)wParam); + } + break; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +INT_PTR CALLBACK PhMipMiniInfoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhMipWindow = hwndDlg; + PhMipOnInitDialog(); + } + break; + case WM_SHOWWINDOW: + { + PhMipOnShowWindow(!!wParam, (ULONG)lParam); + } + break; + case WM_COMMAND: + { + PhMipOnCommand(LOWORD(wParam), HIWORD(wParam)); + } + break; + case WM_NOTIFY: + { + LRESULT result; + + if (PhMipOnNotify((NMHDR *)lParam, &result)) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, result); + return TRUE; + } + } + break; + case WM_CTLCOLORBTN: + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + HBRUSH brush; + + if (PhMipOnCtlColorXxx(uMsg, (HWND)lParam, (HDC)wParam, &brush)) + return (INT_PTR)brush; + } + break; + case WM_DRAWITEM: + { + if (PhMipOnDrawItem(wParam, (DRAWITEMSTRUCT *)lParam)) + return TRUE; + } + break; + } + + if (uMsg >= MIP_MSG_FIRST && uMsg <= MIP_MSG_LAST) + { + PhMipOnUserMessage(uMsg, wParam, lParam); + } + + return FALSE; +} + +VOID PhMipContainerOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ) +{ + ULONG i; + PPH_MINIINFO_SECTION section; + + if (Showing) + { + PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0); + + PhMipMessageLoopFilterEntry = PhRegisterMessageLoopFilter(PhMipMessageLoopFilter, NULL); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + PhMipUpdateHandler, + NULL, + &ProcessesUpdatedRegistration + ); + + PhMipContainerOnSize(); + } + else + { + ULONG i; + + for (i = 0; i < MaxMiniInfoPinType; i++) + PhMipPinCounts[i] = 0; + + Button_SetCheck(GetDlgItem(PhMipWindow, IDC_PIN), BST_UNCHECKED); + PhMipSetPinned(FALSE); + PhSetIntegerSetting(L"MiniInfoWindowPinned", FALSE); + + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &ProcessesUpdatedRegistration + ); + + if (PhMipMessageLoopFilterEntry) + { + PhUnregisterMessageLoopFilter(PhMipMessageLoopFilterEntry); + PhMipMessageLoopFilterEntry = NULL; + } + + PhSaveWindowPlacementToSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow); + } + + if (SectionList) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + section->Callback(section, MiniInfoShowing, (PVOID)Showing, NULL); + } + } +} + +VOID PhMipContainerOnActivate( + _In_ ULONG Type, + _In_ BOOLEAN Minimized + ) +{ + if (Type == WA_ACTIVE || Type == WA_CLICKACTIVE) + { + PhPinMiniInformation(MiniInfoActivePinType, 1, 0, 0, NULL, NULL); + } + else if (Type == WA_INACTIVE) + { + PhPinMiniInformation(MiniInfoActivePinType, -1, 0, 0, NULL, NULL); + } +} + +VOID PhMipContainerOnSize( + VOID + ) +{ + if (PhMipWindow) + { + InvalidateRect(PhMipContainerWindow, NULL, FALSE); + PhMipLayout(); + } +} + +VOID PhMipContainerOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ) +{ + PhResizingMinimumSize(DragRectangle, Edge, MinimumSize.right, MinimumSize.bottom); +} + +VOID PhMipContainerOnExitSizeMove( + VOID + ) +{ + PhSaveWindowPlacementToSetting(L"MiniInfoWindowPosition", L"MiniInfoWindowSize", PhMipContainerWindow); +} + +BOOLEAN PhMipContainerOnEraseBkgnd( + _In_ HDC hdc + ) +{ + return FALSE; +} + +VOID PhMipContainerOnTimer( + _In_ ULONG Id + ) +{ + if (Id >= MIP_TIMER_PIN_FIRST && Id <= MIP_TIMER_PIN_LAST) + { + PH_MINIINFO_PIN_TYPE pinType = Id - MIP_TIMER_PIN_FIRST; + + // PhPinMiniInformation kills the timer for us. + PhPinMiniInformation(pinType, PhMipDelayedPinAdjustments[pinType], 0, 0, NULL, NULL); + } +} + +VOID PhMipOnInitDialog( + VOID + ) +{ + HICON cog; + HICON pin; + + cog = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_COG)); + SET_BUTTON_ICON(PhMipWindow, IDC_OPTIONS, cog); + + pin = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_PIN)); + SET_BUTTON_ICON(PhMipWindow, IDC_PIN, pin); + + PhInitializeLayoutManager(&PhMipLayoutManager, PhMipWindow); + PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_LAYOUT), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_SECTION), NULL, + PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM | PH_LAYOUT_FORCE_INVALIDATE); + PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_OPTIONS), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&PhMipLayoutManager, GetDlgItem(PhMipWindow, IDC_PIN), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SectionControlOldWndProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(PhMipWindow, IDC_SECTION), GWLP_WNDPROC); + SetWindowLongPtr(GetDlgItem(PhMipWindow, IDC_SECTION), GWLP_WNDPROC, (LONG_PTR)PhMipSectionControlHookWndProc); + + Button_SetCheck(GetDlgItem(PhMipWindow, IDC_PIN), !!PhGetIntegerSetting(L"MiniInfoWindowPinned")); +} + +VOID PhMipOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ) +{ + if (SectionList) + return; + + SectionList = PhCreateList(8); + PhMipInitializeParameters(); + + SendMessage(GetDlgItem(PhMipWindow, IDC_SECTION), WM_SETFONT, (WPARAM)CurrentParameters.MediumFont, FALSE); + + PhMipCreateInternalListSection(L"CPU", 0, PhMipCpuListSectionCallback); + PhMipCreateInternalListSection(L"Commit charge", 0, PhMipCommitListSectionCallback); + PhMipCreateInternalListSection(L"Physical memory", 0, PhMipPhysicalListSectionCallback); + PhMipCreateInternalListSection(L"I/O", 0, PhMipIoListSectionCallback); + + if (PhPluginsEnabled) + { + PH_PLUGIN_MINIINFO_POINTERS pointers; + + pointers.WindowHandle = PhMipContainerWindow; + pointers.CreateSection = PhMipCreateSection; + pointers.FindSection = PhMipFindSection; + pointers.CreateListSection = PhMipCreateListSection; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMiniInformationInitializing), &pointers); + } + + PhMipChangeSection(SectionList->Items[0]); +} + +VOID PhMipOnCommand( + _In_ ULONG Id, + _In_ ULONG Code + ) +{ + switch (Id) + { + case IDC_SECTION: + switch (Code) + { + case STN_CLICKED: + PhMipShowSectionMenu(); + break; + } + break; + case IDC_OPTIONS: + PhMipShowOptionsMenu(); + break; + case IDC_PIN: + { + BOOLEAN pinned; + + pinned = Button_GetCheck(GetDlgItem(PhMipWindow, IDC_PIN)) == BST_CHECKED; + PhPinMiniInformation(MiniInfoManualPinType, pinned ? 1 : -1, 0, 0, NULL, NULL); + PhMipSetPinned(pinned); + PhSetIntegerSetting(L"MiniInfoWindowPinned", pinned); + } + break; + } +} + +BOOLEAN PhMipOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ) +{ + return FALSE; +} + +BOOLEAN PhMipOnCtlColorXxx( + _In_ ULONG Message, + _In_ HWND hwnd, + _In_ HDC hdc, + _Out_ HBRUSH *Brush + ) +{ + return FALSE; +} + +BOOLEAN PhMipOnDrawItem( + _In_ ULONG_PTR Id, + _In_ DRAWITEMSTRUCT *DrawItemStruct + ) +{ + return FALSE; +} + +VOID PhMipOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ) +{ + switch (Message) + { + case MIP_MSG_UPDATE: + { + ULONG i; + PPH_MINIINFO_SECTION section; + + if (SectionList) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + section->Callback(section, MiniInfoTick, NULL, NULL); + } + } + } + break; + } +} + +BOOLEAN PhMipMessageLoopFilter( + _In_ PMSG Message, + _In_ PVOID Context + ) +{ + if (Message->hwnd == PhMipContainerWindow || IsChild(PhMipContainerWindow, Message->hwnd)) + { + if (Message->message == WM_MOUSEMOVE || Message->message == WM_NCMOUSEMOVE) + { + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE | (Message->message == WM_NCMOUSEMOVE ? TME_NONCLIENT : 0); + trackMouseEvent.hwndTrack = Message->hwnd; + trackMouseEvent.dwHoverTime = 0; + TrackMouseEvent(&trackMouseEvent); + + if (Message->message == WM_MOUSEMOVE) + PhMipLastTrackedWindow = Message->hwnd; + else + PhMipLastNcTrackedWindow = Message->hwnd; + + PhPinMiniInformation(MiniInfoHoverPinType, 1, 0, 0, NULL, NULL); + } + else if (Message->message == WM_MOUSELEAVE && Message->hwnd == PhMipLastTrackedWindow) + { + PhPinMiniInformation(MiniInfoHoverPinType, -1, MIP_UNPIN_HOVER_DELAY, 0, NULL, NULL); + } + else if (Message->message == WM_NCMOUSELEAVE && Message->hwnd == PhMipLastNcTrackedWindow) + { + PhPinMiniInformation(MiniInfoHoverPinType, -1, MIP_UNPIN_HOVER_DELAY, 0, NULL, NULL); + } + else if (Message->message == WM_KEYDOWN) + { + switch (Message->wParam) + { + case VK_F5: + PhMipRefresh(); + break; + case VK_F6: + case VK_PAUSE: + PhMipToggleRefreshAutomatically(); + break; + } + } + } + + return FALSE; +} + +VOID NTAPI PhMipUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (PhMipRefreshAutomatically & MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned)) + PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0); +} + +PH_MIP_ADJUST_PIN_RESULT PhMipAdjustPin( + _In_ PH_MINIINFO_PIN_TYPE PinType, + _In_ LONG PinCount + ) +{ + LONG oldTotalPinCount; + LONG oldPinCount; + LONG newPinCount; + ULONG i; + + oldTotalPinCount = 0; + + for (i = 0; i < MaxMiniInfoPinType; i++) + oldTotalPinCount += PhMipPinCounts[i]; + + oldPinCount = PhMipPinCounts[PinType]; + newPinCount = max(oldPinCount + PinCount, 0); + newPinCount = min(newPinCount, PhMipMaxPinCounts[PinType]); + PhMipPinCounts[PinType] = newPinCount; + + if (oldTotalPinCount == 0 && newPinCount > oldPinCount) + return ShowAdjustPinResult; + else if (oldTotalPinCount > 0 && oldTotalPinCount - oldPinCount + newPinCount == 0) + return HideAdjustPinResult; + else + return NoAdjustPinResult; +} + +VOID PhMipCalculateWindowRectangle( + _In_ PPOINT SourcePoint, + _Out_ PPH_RECTANGLE WindowRectangle + ) +{ + RECT windowRect; + PH_RECTANGLE windowRectangle; + PH_RECTANGLE point; + MONITORINFO monitorInfo = { sizeof(monitorInfo) }; + + PhLoadWindowPlacementFromSetting(NULL, L"MiniInfoWindowSize", PhMipContainerWindow); + GetWindowRect(PhMipContainerWindow, &windowRect); + SendMessage(PhMipContainerWindow, WM_SIZING, WMSZ_BOTTOMRIGHT, (LPARAM)&windowRect); // Adjust for the minimum size. + windowRectangle = PhRectToRectangle(windowRect); + + point.Left = SourcePoint->x; + point.Top = SourcePoint->y; + point.Width = 0; + point.Height = 0; + PhCenterRectangle(&windowRectangle, &point); + + if (GetMonitorInfo( + MonitorFromPoint(*SourcePoint, MONITOR_DEFAULTTOPRIMARY), + &monitorInfo + )) + { + PH_RECTANGLE bounds; + + if (memcmp(&monitorInfo.rcWork, &monitorInfo.rcMonitor, sizeof(RECT)) == 0) + { + HWND trayWindow; + RECT taskbarRect; + + // The taskbar probably has auto-hide enabled. We need to adjust for that. + if ((trayWindow = FindWindow(L"Shell_TrayWnd", NULL)) && + GetMonitorInfo(MonitorFromWindow(trayWindow, MONITOR_DEFAULTTOPRIMARY), &monitorInfo) && // Just in case + GetWindowRect(trayWindow, &taskbarRect)) + { + LONG monitorMidX = (monitorInfo.rcMonitor.left + monitorInfo.rcMonitor.right) / 2; + LONG monitorMidY = (monitorInfo.rcMonitor.top + monitorInfo.rcMonitor.bottom) / 2; + + if (taskbarRect.right < monitorMidX) + { + // Left + monitorInfo.rcWork.left += taskbarRect.right - taskbarRect.left; + } + else if (taskbarRect.bottom < monitorMidY) + { + // Top + monitorInfo.rcWork.top += taskbarRect.bottom - taskbarRect.top; + } + else if (taskbarRect.left > monitorMidX) + { + // Right + monitorInfo.rcWork.right -= taskbarRect.right - taskbarRect.left; + } + else if (taskbarRect.top > monitorMidY) + { + // Bottom + monitorInfo.rcWork.bottom -= taskbarRect.bottom - taskbarRect.top; + } + } + } + + bounds = PhRectToRectangle(monitorInfo.rcWork); + + PhAdjustRectangleToBounds(&windowRectangle, &bounds); + } + + *WindowRectangle = windowRectangle; +} + +VOID PhMipInitializeParameters( + VOID + ) +{ + LOGFONT logFont; + HDC hdc; + TEXTMETRIC textMetrics; + HFONT originalFont; + + memset(&CurrentParameters, 0, sizeof(PH_MINIINFO_PARAMETERS)); + + CurrentParameters.ContainerWindowHandle = PhMipContainerWindow; + CurrentParameters.MiniInfoWindowHandle = PhMipWindow; + + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + CurrentParameters.Font = CreateFontIndirect(&logFont); + } + else + { + CurrentParameters.Font = PhApplicationFont; + GetObject(PhApplicationFont, sizeof(LOGFONT), &logFont); + } + + hdc = GetDC(PhMipWindow); + + logFont.lfHeight -= PhMultiplyDivide(2, GetDeviceCaps(hdc, LOGPIXELSY), 72); + CurrentParameters.MediumFont = CreateFontIndirect(&logFont); + + originalFont = SelectObject(hdc, CurrentParameters.Font); + GetTextMetrics(hdc, &textMetrics); + CurrentParameters.FontHeight = textMetrics.tmHeight; + CurrentParameters.FontAverageWidth = textMetrics.tmAveCharWidth; + + SelectObject(hdc, CurrentParameters.MediumFont); + GetTextMetrics(hdc, &textMetrics); + CurrentParameters.MediumFontHeight = textMetrics.tmHeight; + CurrentParameters.MediumFontAverageWidth = textMetrics.tmAveCharWidth; + + CurrentParameters.SetSectionText = PhMipSetSectionText; + + SelectObject(hdc, originalFont); + ReleaseDC(PhMipWindow, hdc); +} + +PPH_MINIINFO_SECTION PhMipCreateSection( + _In_ PPH_MINIINFO_SECTION Template + ) +{ + PPH_MINIINFO_SECTION section; + + section = PhAllocate(sizeof(PH_MINIINFO_SECTION)); + memset(section, 0, sizeof(PH_MINIINFO_SECTION)); + + section->Name = Template->Name; + section->Flags = Template->Flags; + section->Callback = Template->Callback; + section->Context = Template->Context; + section->Parameters = &CurrentParameters; + + PhAddItemList(SectionList, section); + + section->Callback(section, MiniInfoCreate, NULL, NULL); + + return section; +} + +VOID PhMipDestroySection( + _In_ PPH_MINIINFO_SECTION Section + ) +{ + Section->Callback(Section, MiniInfoDestroy, NULL, NULL); + + PhClearReference(&Section->Text); + PhFree(Section); +} + +PPH_MINIINFO_SECTION PhMipFindSection( + _In_ PPH_STRINGREF Name + ) +{ + ULONG i; + PPH_MINIINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (PhEqualStringRef(§ion->Name, Name, TRUE)) + return section; + } + + return NULL; +} + +PPH_MINIINFO_SECTION PhMipCreateInternalSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_SECTION_CALLBACK Callback + ) +{ + PH_MINIINFO_SECTION section; + + memset(§ion, 0, sizeof(PH_MINIINFO_SECTION)); + PhInitializeStringRef(§ion.Name, Name); + section.Flags = Flags; + section.Callback = Callback; + + return PhMipCreateSection(§ion); +} + +VOID PhMipCreateSectionDialog( + _In_ PPH_MINIINFO_SECTION Section + ) +{ + PH_MINIINFO_CREATE_DIALOG createDialog; + + memset(&createDialog, 0, sizeof(PH_MINIINFO_CREATE_DIALOG)); + + if (Section->Callback(Section, MiniInfoCreateDialog, &createDialog, NULL)) + { + if (!createDialog.CustomCreate) + { + Section->DialogHandle = PhCreateDialogFromTemplate( + PhMipWindow, + DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD, + createDialog.Instance, + createDialog.Template, + createDialog.DialogProc, + createDialog.Parameter + ); + } + } +} + +VOID PhMipChangeSection( + _In_ PPH_MINIINFO_SECTION NewSection + ) +{ + PPH_MINIINFO_SECTION oldSection; + + if (NewSection == CurrentSection) + return; + + oldSection = CurrentSection; + CurrentSection = NewSection; + + if (oldSection) + { + oldSection->Callback(oldSection, MiniInfoSectionChanging, CurrentSection, NULL); + + if (oldSection->DialogHandle) + ShowWindow(oldSection->DialogHandle, SW_HIDE); + } + + if (!NewSection->DialogHandle) + PhMipCreateSectionDialog(NewSection); + if (NewSection->DialogHandle) + ShowWindow(NewSection->DialogHandle, SW_SHOW); + + PhMipUpdateSectionText(NewSection); + PhMipLayout(); + + NewSection->Callback(NewSection, MiniInfoTick, NULL, NULL); +} + +VOID PhMipSetSectionText( + _In_ struct _PH_MINIINFO_SECTION *Section, + _In_opt_ PPH_STRING Text + ) +{ + PhSwapReference(&Section->Text, Text); + + if (Section == CurrentSection) + PhMipUpdateSectionText(Section); +} + +VOID PhMipUpdateSectionText( + _In_ PPH_MINIINFO_SECTION Section + ) +{ + if (Section->Text) + { + SetDlgItemText(PhMipWindow, IDC_SECTION, + PH_AUTO_T(PH_STRING, PhConcatStringRef2(&DownArrowPrefix, &Section->Text->sr))->Buffer); + } + else + { + SetDlgItemText(PhMipWindow, IDC_SECTION, + PH_AUTO_T(PH_STRING, PhConcatStringRef2(&DownArrowPrefix, &Section->Name))->Buffer); + } +} + +VOID PhMipLayout( + VOID + ) +{ + RECT clientRect; + RECT rect; + + GetClientRect(PhMipContainerWindow, &clientRect); + MoveWindow( + PhMipWindow, + clientRect.left, clientRect.top, + clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, + FALSE + ); + + PhLayoutManagerLayout(&PhMipLayoutManager); + + GetWindowRect(GetDlgItem(PhMipWindow, IDC_LAYOUT), &rect); + MapWindowPoints(NULL, PhMipWindow, (POINT *)&rect, 2); + + if (CurrentSection && CurrentSection->DialogHandle) + { + if (CurrentSection->Flags & PH_MINIINFO_SECTION_NO_UPPER_MARGINS) + { + rect.left = 0; + rect.top = 0; + rect.right = clientRect.right; + } + else + { + LONG leftDistance = rect.left - clientRect.left; + LONG rightDistance = clientRect.right - rect.right; + LONG minDistance; + + if (leftDistance != rightDistance) + { + // HACK: Enforce symmetry. Sometimes these are off by a pixel. + minDistance = min(leftDistance, rightDistance); + rect.left = clientRect.left + minDistance; + rect.right = clientRect.right - minDistance; + } + } + + MoveWindow( + CurrentSection->DialogHandle, + rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + TRUE + ); + } + + GetWindowRect(GetDlgItem(PhMipWindow, IDC_PIN), &rect); + MapWindowPoints(NULL, PhMipWindow, (POINT *)&rect, 2); +} + +VOID PhMipBeginChildControlPin( + VOID + ) +{ + PhPinMiniInformation(MiniInfoChildControlPinType, 1, 0, 0, NULL, NULL); +} + +VOID PhMipEndChildControlPin( + VOID + ) +{ + PhPinMiniInformation(MiniInfoChildControlPinType, -1, MIP_UNPIN_CHILD_CONTROL_DELAY, 0, NULL, NULL); + PostMessage(PhMipWindow, WM_MOUSEMOVE, 0, 0); // Re-evaluate hover pin +} + +VOID PhMipRefresh( + VOID + ) +{ + if (PhMipPinned) + ProcessHacker_Refresh(PhMainWndHandle); + + PostMessage(PhMipWindow, MIP_MSG_UPDATE, 0, 0); +} + +VOID PhMipToggleRefreshAutomatically( + VOID + ) +{ + PhMipRefreshAutomatically ^= MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned); + PhSetIntegerSetting(L"MiniInfoWindowRefreshAutomatically", PhMipRefreshAutomatically); +} + +VOID PhMipSetPinned( + _In_ BOOLEAN Pinned + ) +{ + PhSetWindowStyle(PhMipContainerWindow, WS_DLGFRAME | WS_SYSMENU, Pinned ? (WS_DLGFRAME | WS_SYSMENU) : 0); + SetWindowPos(PhMipContainerWindow, NULL, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); + PhMipPinned = Pinned; + + PhNfNotifyMiniInfoPinned(Pinned); +} + +VOID PhMipShowSectionMenu( + VOID + ) +{ + PPH_EMENU menu; + ULONG i; + PPH_MINIINFO_SECTION section; + PPH_EMENU_ITEM menuItem; + POINT point; + + PhMipBeginChildControlPin(); + menu = PhCreateEMenu(); + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + menuItem = PhCreateEMenuItem( + (section == CurrentSection ? (PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK) : 0), + 0, + PH_AUTO_T(PH_STRING, PhCreateString2(§ion->Name))->Buffer, + NULL, + section + ); + PhInsertEMenuItem(menu, menuItem, -1); + } + + GetCursorPos(&point); + menuItem = PhShowEMenu(menu, PhMipWindow, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); + + if (menuItem) + { + PhMipChangeSection(menuItem->Context); + } + + PhDestroyEMenu(menu); + PhMipEndChildControlPin(); +} + +VOID PhMipShowOptionsMenu( + VOID + ) +{ + PPH_EMENU menu; + PPH_EMENU_ITEM menuItem; + ULONG id; + RECT rect; + + PhMipBeginChildControlPin(); + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MINIINFO), 0); + + // Opacity + + id = PH_OPACITY_TO_ID(PhGetIntegerSetting(L"MiniInfoWindowOpacity")); + + if (menuItem = PhFindEMenuItem(menu, PH_EMENU_FIND_DESCEND, NULL, id)) + menuItem->Flags |= PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK; + + // Refresh Automatically + + if (PhMipRefreshAutomatically & MIP_REFRESH_AUTOMATICALLY_FLAG(PhMipPinned)) + PhSetFlagsEMenuItem(menu, ID_MINIINFO_REFRESHAUTOMATICALLY, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + + // Show the menu. + + GetWindowRect(GetDlgItem(PhMipWindow, IDC_OPTIONS), &rect); + menuItem = PhShowEMenu(menu, PhMipWindow, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_BOTTOM, rect.left, rect.top); + + if (menuItem) + { + switch (menuItem->Id) + { + case ID_OPACITY_10: + case ID_OPACITY_20: + case ID_OPACITY_30: + case ID_OPACITY_40: + case ID_OPACITY_50: + case ID_OPACITY_60: + case ID_OPACITY_70: + case ID_OPACITY_80: + case ID_OPACITY_90: + case ID_OPACITY_OPAQUE: + { + ULONG opacity; + + opacity = PH_ID_TO_OPACITY(menuItem->Id); + PhSetIntegerSetting(L"MiniInfoWindowOpacity", opacity); + PhSetWindowOpacity(PhMipContainerWindow, opacity); + } + break; + case ID_MINIINFO_REFRESH: + PhMipRefresh(); + break; + case ID_MINIINFO_REFRESHAUTOMATICALLY: + PhMipToggleRefreshAutomatically(); + break; + } + } + + PhDestroyEMenu(menu); + PhMipEndChildControlPin(); +} + +LRESULT CALLBACK PhMipSectionControlHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_SETCURSOR: + { + SetCursor(LoadCursor(NULL, IDC_HAND)); + } + return TRUE; + } + + return CallWindowProc(SectionControlOldWndProc, hwnd, uMsg, wParam, lParam); +} + +PPH_MINIINFO_LIST_SECTION PhMipCreateListSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_LIST_SECTION Template + ) +{ + PPH_MINIINFO_LIST_SECTION listSection; + PH_MINIINFO_SECTION section; + + listSection = PhAllocate(sizeof(PH_MINIINFO_LIST_SECTION)); + memset(listSection, 0, sizeof(PH_MINIINFO_LIST_SECTION)); + + listSection->Context = Template->Context; + listSection->Callback = Template->Callback; + + memset(§ion, 0, sizeof(PH_MINIINFO_SECTION)); + PhInitializeStringRef(§ion.Name, Name); + section.Flags = PH_MINIINFO_SECTION_NO_UPPER_MARGINS; + section.Callback = PhMipListSectionCallback; + section.Context = listSection; + listSection->Section = PhMipCreateSection(§ion); + + return listSection; +} + +PPH_MINIINFO_LIST_SECTION PhMipCreateInternalListSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_MINIINFO_LIST_SECTION_CALLBACK Callback + ) +{ + PH_MINIINFO_LIST_SECTION listSection; + + memset(&listSection, 0, sizeof(PH_MINIINFO_LIST_SECTION)); + listSection.Callback = Callback; + + return PhMipCreateListSection(Name, Flags, &listSection); +} + +BOOLEAN PhMipListSectionCallback( + _In_ PPH_MINIINFO_SECTION Section, + _In_ PH_MINIINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + PPH_MINIINFO_LIST_SECTION listSection = Section->Context; + + switch (Message) + { + case MiniInfoCreate: + { + listSection->NodeList = PhCreateList(2); + listSection->Callback(listSection, MiListSectionCreate, NULL, NULL); + } + break; + case MiniInfoDestroy: + { + listSection->Callback(listSection, MiListSectionDestroy, NULL, NULL); + + PhMipClearListSection(listSection); + PhDereferenceObject(listSection->NodeList); + PhFree(listSection); + } + break; + case MiniInfoTick: + if (listSection->SuspendUpdate == 0) + PhMipTickListSection(listSection); + break; + case MiniInfoShowing: + { + listSection->Callback(listSection, MiListSectionShowing, Parameter1, Parameter2); + + if (!Parameter1) // Showing + { + // We don't want to hold process item references while the mini info window + // is hidden. + PhMipClearListSection(listSection); + TreeNew_NodesStructured(listSection->TreeNewHandle); + } + } + break; + case MiniInfoCreateDialog: + { + PPH_MINIINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PhInstanceHandle; + createDialog->Template = MAKEINTRESOURCE(IDD_MINIINFO_LIST); + createDialog->DialogProc = PhMipListSectionDialogProc; + createDialog->Parameter = listSection; + } + return TRUE; + } + + return FALSE; +} + +INT_PTR CALLBACK PhMipListSectionDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_MINIINFO_LIST_SECTION listSection = (PPH_MINIINFO_LIST_SECTION)GetProp(hwndDlg, PhMakeContextAtom()); + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM layoutItem; + + listSection = (PPH_MINIINFO_LIST_SECTION)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)listSection); + + listSection->DialogHandle = hwndDlg; + listSection->TreeNewHandle = GetDlgItem(hwndDlg, IDC_LIST); + + PhInitializeLayoutManager(&listSection->LayoutManager, hwndDlg); + layoutItem = PhAddLayoutItem(&listSection->LayoutManager, listSection->TreeNewHandle, NULL, PH_ANCHOR_ALL); + + // Use negative margins to maximize our use of the window area. + layoutItem->Margin.left = -1; + layoutItem->Margin.top = -1; + layoutItem->Margin.right = -1; + + PhSetControlTheme(listSection->TreeNewHandle, L"explorer"); + TreeNew_SetCallback(listSection->TreeNewHandle, PhMipListSectionTreeNewCallback, listSection); + TreeNew_SetRowHeight(listSection->TreeNewHandle, PhMipCalculateRowHeight()); + PhAddTreeNewColumnEx2(listSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TRUE, L"Process", 1, + PH_ALIGN_LEFT, 0, 0, TN_COLUMN_FLAG_CUSTOMDRAW); + + listSection->Callback(listSection, MiListSectionDialogCreated, hwndDlg, NULL); + PhMipTickListSection(listSection); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&listSection->LayoutManager); + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&listSection->LayoutManager); + TreeNew_AutoSizeColumn(listSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TN_AUTOSIZE_REMAINING_SPACE); + } + break; + } + + return FALSE; +} + +VOID PhMipListSectionSortFunction( + _In_ PPH_LIST List, + _In_opt_ PVOID Context + ) +{ + PPH_MINIINFO_LIST_SECTION listSection = Context; + PH_MINIINFO_LIST_SECTION_SORT_LIST sortList; + + sortList.List = List; + listSection->Callback(listSection, MiListSectionSortProcessList, &sortList, NULL); +} + +VOID PhMipTickListSection( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ) +{ + ULONG i; + PPH_MIP_GROUP_NODE node; + PH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData; + + PhMipClearListSection(ListSection); + + ListSection->ProcessGroupList = PhCreateProcessGroupList( + PhMipListSectionSortFunction, + ListSection, + MIP_MAX_PROCESS_GROUPS, + 0 + ); + + if (!ListSection->ProcessGroupList) + return; + + for (i = 0; i < ListSection->ProcessGroupList->Count; i++) + { + node = PhMipAddGroupNode(ListSection, ListSection->ProcessGroupList->Items[i]); + + if (node->RepresentativeProcessId == ListSection->SelectedRepresentativeProcessId && + node->RepresentativeCreateTime.QuadPart == ListSection->SelectedRepresentativeCreateTime.QuadPart) + { + node->Node.Selected = TRUE; + } + + assignSortData.ProcessGroup = node->ProcessGroup; + assignSortData.SortData = &node->SortData; + ListSection->Callback(ListSection, MiListSectionAssignSortData, &assignSortData, NULL); + } + + TreeNew_NodesStructured(ListSection->TreeNewHandle); + TreeNew_AutoSizeColumn(ListSection->TreeNewHandle, MIP_SINGLE_COLUMN_ID, TN_AUTOSIZE_REMAINING_SPACE); + + ListSection->Callback(ListSection, MiListSectionTick, NULL, NULL); +} + +VOID PhMipClearListSection( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ) +{ + ULONG i; + + if (ListSection->ProcessGroupList) + { + PhFreeProcessGroupList(ListSection->ProcessGroupList); + ListSection->ProcessGroupList = NULL; + } + + for (i = 0; i < ListSection->NodeList->Count; i++) + PhMipDestroyGroupNode(ListSection->NodeList->Items[i]); + + PhClearList(ListSection->NodeList); +} + +LONG PhMipCalculateRowHeight( + VOID + ) +{ + LONG iconHeight; + LONG titleAndSubtitleHeight; + + iconHeight = MIP_ICON_PADDING + PhLargeIconSize.Y + MIP_CELL_PADDING; + titleAndSubtitleHeight = + MIP_CELL_PADDING * 2 + CurrentParameters.FontHeight + MIP_INNER_PADDING + CurrentParameters.FontHeight; + + return max(iconHeight, titleAndSubtitleHeight); +} + +PPH_MIP_GROUP_NODE PhMipAddGroupNode( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_PROCESS_GROUP ProcessGroup + ) +{ + // This is an undocumented function exported by user32.dll that + // retrieves the hung window represented by a ghost window. + static HWND (WINAPI *HungWindowFromGhostWindow_I)( + _In_ HWND hWnd + ); + + PPH_MIP_GROUP_NODE node; + + node = PhAllocate(sizeof(PH_MIP_GROUP_NODE)); + memset(node, 0, sizeof(PH_MIP_GROUP_NODE)); + + PhInitializeTreeNewNode(&node->Node); + node->ProcessGroup = ProcessGroup; + node->RepresentativeProcessId = ProcessGroup->Representative->ProcessId; + node->RepresentativeCreateTime = ProcessGroup->Representative->CreateTime; + node->RepresentativeIsHung = ProcessGroup->WindowHandle && IsHungAppWindow(ProcessGroup->WindowHandle); + + if (node->RepresentativeIsHung) + { + if (!HungWindowFromGhostWindow_I) + HungWindowFromGhostWindow_I = PhGetModuleProcAddress(L"user32.dll", "HungWindowFromGhostWindow"); + + // Make sure this is a real hung window, not a ghost window. + if (HungWindowFromGhostWindow_I && HungWindowFromGhostWindow_I(ProcessGroup->WindowHandle)) + node->RepresentativeIsHung = FALSE; + } + + PhAddItemList(ListSection->NodeList, node); + + return node; +} + +VOID PhMipDestroyGroupNode( + _In_ PPH_MIP_GROUP_NODE Node + ) +{ + PhClearReference(&Node->TooltipText); + PhFree(Node); +} + +BOOLEAN PhMipListSectionTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_MINIINFO_LIST_SECTION listSection = Context; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + PH_MINIINFO_LIST_SECTION_SORT_LIST sortList; + + if (!getChildren->Node) + { + getChildren->Children = (PPH_TREENEW_NODE *)listSection->NodeList->Items; + getChildren->NumberOfChildren = listSection->NodeList->Count; + + sortList.List = listSection->NodeList; + listSection->Callback(listSection, MiListSectionSortGroupList, &sortList, NULL); + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewCustomDraw: + { + PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1; + PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)customDraw->Node; + PPH_PROCESS_ITEM processItem = node->ProcessGroup->Representative; + HDC hdc = customDraw->Dc; + RECT rect = customDraw->CellRect; + ULONG baseTextFlags = DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE; + HICON icon; + COLORREF originalTextColor; + RECT topRect; + RECT bottomRect; + PH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText; + ULONG usageTextTopWidth = 0; + ULONG usageTextBottomWidth = 0; + PH_MINIINFO_LIST_SECTION_GET_TITLE_TEXT getTitleText; + + rect.left += MIP_ICON_PADDING; + rect.top += MIP_ICON_PADDING; + rect.right -= MIP_CELL_PADDING; + rect.bottom -= MIP_CELL_PADDING; + + if (processItem->LargeIcon) + icon = processItem->LargeIcon; + else + PhGetStockApplicationIcon(NULL, &icon); + + DrawIconEx(hdc, rect.left, rect.top, icon, PhLargeIconSize.X, PhLargeIconSize.Y, + 0, NULL, DI_NORMAL); + rect.left += (MIP_CELL_PADDING - MIP_ICON_PADDING) + PhLargeIconSize.X + MIP_CELL_PADDING; + rect.top += MIP_CELL_PADDING - MIP_ICON_PADDING; + SelectObject(hdc, CurrentParameters.Font); + + // This color changes depending on whether the node is selected, etc. + originalTextColor = GetTextColor(hdc); + + // Usage text + + topRect = rect; + topRect.bottom = topRect.top + CurrentParameters.FontHeight; + bottomRect = rect; + bottomRect.top = bottomRect.bottom - CurrentParameters.FontHeight; + + getUsageText.ProcessGroup = node->ProcessGroup; + getUsageText.SortData = &node->SortData; + getUsageText.Line1 = NULL; + getUsageText.Line2 = NULL; + getUsageText.Line1Color = originalTextColor; + getUsageText.Line2Color = originalTextColor; + + if (listSection->Callback(listSection, MiListSectionGetUsageText, &getUsageText, NULL)) + { + PH_STRINGREF text; + RECT textRect; + SIZE textSize; + + // Top + text = PhGetStringRef(getUsageText.Line1); + GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / 2, &textSize); + usageTextTopWidth = textSize.cx; + textRect = topRect; + textRect.left = textRect.right - textSize.cx; + SetTextColor(hdc, getUsageText.Line1Color); + DrawText(hdc, text.Buffer, (ULONG)text.Length / 2, &textRect, baseTextFlags | DT_RIGHT); + PhClearReference(&getUsageText.Line1); + + // Bottom + text = PhGetStringRef(getUsageText.Line2); + GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / 2, &textSize); + usageTextBottomWidth = textSize.cx; + textRect = bottomRect; + textRect.left = textRect.right - textSize.cx; + SetTextColor(hdc, getUsageText.Line2Color); + DrawText(hdc, text.Buffer, (ULONG)text.Length / 2, &textRect, baseTextFlags | DT_RIGHT); + PhClearReference(&getUsageText.Line2); + } + + // Title, subtitle + + getTitleText.ProcessGroup = node->ProcessGroup; + getTitleText.SortData = &node->SortData; + + if (!PhIsNullOrEmptyString(processItem->VersionInfo.FileDescription)) + PhSetReference(&getTitleText.Title, processItem->VersionInfo.FileDescription); + else + PhSetReference(&getTitleText.Title, processItem->ProcessName); + + if (node->ProcessGroup->Processes->Count == 1) + { + PhSetReference(&getTitleText.Subtitle, processItem->ProcessName); + } + else + { + getTitleText.Subtitle = PhFormatString( + L"%s (%u processes)", + processItem->ProcessName->Buffer, + node->ProcessGroup->Processes->Count + ); + } + + getTitleText.TitleColor = originalTextColor; + getTitleText.SubtitleColor = GetSysColor(COLOR_GRAYTEXT); + + // Special text for hung windows + if (node->RepresentativeIsHung) + { + static PH_STRINGREF hungPrefix = PH_STRINGREF_INIT(L"(Not responding) "); + + PhMoveReference(&getTitleText.Title, PhConcatStringRef2(&hungPrefix, &getTitleText.Title->sr)); + getTitleText.TitleColor = RGB(0xff, 0x00, 0x00); + } + + listSection->Callback(listSection, MiListSectionGetTitleText, &getTitleText, NULL); + + if (!PhIsNullOrEmptyString(getTitleText.Title)) + { + RECT textRect; + + textRect = topRect; + textRect.right -= usageTextTopWidth + MIP_INNER_PADDING; + SetTextColor(hdc, getTitleText.TitleColor); + DrawText( + hdc, + getTitleText.Title->Buffer, + (ULONG)getTitleText.Title->Length / 2, + &textRect, + baseTextFlags | DT_END_ELLIPSIS + ); + } + + if (!PhIsNullOrEmptyString(getTitleText.Subtitle)) + { + RECT textRect; + + textRect = bottomRect; + textRect.right -= usageTextBottomWidth + MIP_INNER_PADDING; + SetTextColor(hdc, getTitleText.SubtitleColor); + DrawText( + hdc, + getTitleText.Subtitle->Buffer, + (ULONG)getTitleText.Subtitle->Length / 2, + &textRect, + baseTextFlags | DT_END_ELLIPSIS + ); + } + + PhClearReference(&getTitleText.Title); + PhClearReference(&getTitleText.Subtitle); + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)getCellTooltip->Node; + ULONG tickCount; + + tickCount = GetTickCount(); + + // This is useless most of the time because the tooltip doesn't display unless the window is active. + // TODO: Find a way to make the tooltip display all the time. + + if (!node->TooltipText) + node->TooltipText = PhMipGetGroupNodeTooltip(listSection, node); + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->MaximumWidth = -1; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewSelectionChanged: + { + ULONG i; + PPH_MIP_GROUP_NODE node; + + listSection->SelectedRepresentativeProcessId = NULL; + listSection->SelectedRepresentativeCreateTime.QuadPart = 0; + + for (i = 0; i < listSection->NodeList->Count; i++) + { + node = listSection->NodeList->Items[i]; + + if (node->Node.Selected) + { + listSection->SelectedRepresentativeProcessId = node->RepresentativeProcessId; + listSection->SelectedRepresentativeCreateTime = node->RepresentativeCreateTime; + break; + } + } + } + break; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + PPH_MIP_GROUP_NODE node; + + listSection->SuspendUpdate++; + + switch (keyEvent->VirtualKey) + { + case VK_DELETE: + if (node = PhMipGetSelectedGroupNode(listSection)) + PhUiTerminateProcesses(listSection->DialogHandle, &node->ProcessGroup->Representative, 1); + break; + case VK_RETURN: + if (node = PhMipGetSelectedGroupNode(listSection)) + { + if (GetKeyState(VK_CONTROL) >= 0) + { + PhMipHandleListSectionCommand(listSection, node->ProcessGroup, ID_PROCESS_GOTOPROCESS); + } + else + { + if (node->ProcessGroup->Representative->FileName) + PhShellExploreFile(listSection->DialogHandle, node->ProcessGroup->Representative->FileName->Buffer); + } + } + break; + } + + listSection->SuspendUpdate--; + } + return TRUE; + case TreeNewLeftDoubleClick: + { + PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; + PPH_MIP_GROUP_NODE node = (PPH_MIP_GROUP_NODE)mouseEvent->Node; + + if (node) + { + listSection->SuspendUpdate++; + PhMipHandleListSectionCommand(listSection, node->ProcessGroup, ID_PROCESS_GOTOPROCESS); + listSection->SuspendUpdate--; + } + } + break; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + // Prevent the node list from being updated (otherwise any nodes we're using might be destroyed while we're + // in a modal message loop). + listSection->SuspendUpdate++; + PhMipBeginChildControlPin(); + PhMipShowListSectionContextMenu(listSection, contextMenu); + PhMipEndChildControlPin(); + listSection->SuspendUpdate--; + } + break; + } + + return FALSE; +} + +PPH_STRING PhMipGetGroupNodeTooltip( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_MIP_GROUP_NODE Node + ) +{ + PH_STRING_BUILDER sb; + + PhInitializeStringBuilder(&sb, 100); + + // TODO + + return PhFinalStringBuilderString(&sb); +} + +PPH_MIP_GROUP_NODE PhMipGetSelectedGroupNode( + _In_ PPH_MINIINFO_LIST_SECTION ListSection + ) +{ + ULONG i; + PPH_MIP_GROUP_NODE node; + + for (i = 0; i < ListSection->NodeList->Count; i++) + { + node = ListSection->NodeList->Items[i]; + + if (node->Node.Selected) + return node; + } + + return NULL; +} + +VOID PhMipShowListSectionContextMenu( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PPH_MIP_GROUP_NODE selectedNode; + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_MINIINFO_LIST_SECTION_MENU_INFORMATION menuInfo; + PH_PLUGIN_MENU_INFORMATION pluginMenuInfo; + + selectedNode = PhMipGetSelectedGroupNode(ListSection); + + if (!selectedNode) + return; + + menu = PhCreateEMenu(); + // TODO: If there are multiple processes, then create submenus for each process. + PhAddMiniProcessMenuItems(menu, ListSection->SelectedRepresentativeProcessId); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MINIINFO_PROCESS), 0); + PhSetFlagsEMenuItem(menu, ID_PROCESS_GOTOPROCESS, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + if (selectedNode->ProcessGroup->Processes->Count != 1) + { + if (item = PhFindEMenuItem(menu, 0, NULL, ID_PROCESS_GOTOPROCESS)) + PhModifyEMenuItem(item, PH_EMENU_MODIFY_TEXT, 0, L"&Go to processes", NULL); + } + + memset(&menuInfo, 0, sizeof(PH_MINIINFO_LIST_SECTION_MENU_INFORMATION)); + menuInfo.ProcessGroup = selectedNode->ProcessGroup; + menuInfo.SortData = &selectedNode->SortData; + menuInfo.ContextMenu = ContextMenu; + ListSection->Callback(ListSection, MiListSectionInitializeContextMenu, &menuInfo, NULL); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&pluginMenuInfo, menu, ListSection->DialogHandle, 0); + pluginMenuInfo.Menu = menu; + pluginMenuInfo.OwnerWindow = PhMipWindow; + pluginMenuInfo.u.MiListSection.SectionName = &ListSection->Section->Name; + pluginMenuInfo.u.MiListSection.ProcessGroup = selectedNode->ProcessGroup; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMiListSectionMenuInitializing), &pluginMenuInfo); + } + + item = PhShowEMenu( + menu, + PhMipWindow, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&pluginMenuInfo, item); + + if (!handled) + { + menuInfo.SelectedItem = item; + handled = ListSection->Callback(ListSection, MiListSectionHandleContextMenu, &menuInfo, NULL); + } + + if (!handled) + PhHandleMiniProcessMenuItem(item); + + if (!handled) + PhMipHandleListSectionCommand(ListSection, selectedNode->ProcessGroup, item->Id); + } + + PhDestroyEMenu(menu); +} + +VOID PhMipHandleListSectionCommand( + _In_ PPH_MINIINFO_LIST_SECTION ListSection, + _In_ PPH_PROCESS_GROUP ProcessGroup, + _In_ ULONG Id + ) +{ + switch (Id) + { + case ID_PROCESS_GOTOPROCESS: + { + PPH_LIST nodes; + ULONG i; + + nodes = PhCreateList(ProcessGroup->Processes->Count); + + for (i = 0; i < ProcessGroup->Processes->Count; i++) + { + PPH_PROCESS_NODE node; + + if (node = PhFindProcessNode(((PPH_PROCESS_ITEM)ProcessGroup->Processes->Items[i])->ProcessId)) + PhAddItemList(nodes, node); + } + + PhPinMiniInformation(MiniInfoIconPinType, -1, 0, 0, NULL, NULL); + PhPinMiniInformation(MiniInfoActivePinType, -1, 0, 0, NULL, NULL); + PhPinMiniInformation(MiniInfoHoverPinType, -1, 0, 0, NULL, NULL); + + ProcessHacker_ToggleVisible(PhMainWndHandle, TRUE); + ProcessHacker_SelectTabPage(PhMainWndHandle, 0); + PhSelectAndEnsureVisibleProcessNodes((PPH_PROCESS_NODE *)nodes->Items, nodes->Count); + PhDereferenceObject(nodes); + } + break; + } +} + +BOOLEAN PhMipCpuListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PhaFormatString(L"CPU %.2f%%", (PhCpuUserUsage + PhCpuKernelUsage) * 100)); + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), PhMipCpuListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + FLOAT cpuUsage = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + cpuUsage += ((PPH_PROCESS_ITEM)processes->Items[i])->CpuUsage; + + *(PFLOAT)assignSortData->SortData->UserData = cpuUsage; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipCpuListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + FLOAT cpuUsage = *(PFLOAT)getUsageText->SortData->UserData * 100; + PPH_STRING cpuUsageText; + + if (cpuUsage >= 0.01) + cpuUsageText = PhFormatString(L"%.2f%%", cpuUsage); + else if (cpuUsage != 0) + cpuUsageText = PhCreateString(L"< 0.01%"); + else + cpuUsageText = NULL; + + PhMoveReference(&getUsageText->Line1, cpuUsageText); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl PhMipCpuListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + int result; + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + + result = singlecmp(node2->ProcessItem->CpuUsage, node1->ProcessItem->CpuUsage); + + if (result == 0) + result = uint64cmp(node2->ProcessItem->UserTime.QuadPart, node1->ProcessItem->UserTime.QuadPart); + + return result; +} + +int __cdecl PhMipCpuListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return singlecmp(*(PFLOAT)data2->UserData, *(PFLOAT)data1->UserData); +} + +BOOLEAN PhMipCommitListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + PH_FORMAT format[5]; + DOUBLE commitFraction = (DOUBLE)PhPerfInformation.CommittedPages / PhPerfInformation.CommitLimit; + + PhInitFormatS(&format[0], L"Commit "); + PhInitFormatSize(&format[1], UInt32x32To64(PhPerfInformation.CommittedPages, PAGE_SIZE)); + PhInitFormatS(&format[2], L" ("); + PhInitFormatF(&format[3], commitFraction * 100, 2); + PhInitFormatS(&format[4], L"%)"); + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PH_AUTO(PhFormat(format, 5, 96))); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), PhMipCommitListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + ULONG64 privateBytes = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + privateBytes += ((PPH_PROCESS_ITEM)processes->Items[i])->VmCounters.PagefileUsage; + + *(PULONG64)assignSortData->SortData->UserData = privateBytes; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipCommitListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + ULONG64 privateBytes = *(PULONG64)getUsageText->SortData->UserData; + + PhMoveReference(&getUsageText->Line1, PhFormatSize(privateBytes, -1)); + PhMoveReference(&getUsageText->Line2, PhCreateString(L"Private bytes")); + getUsageText->Line2Color = GetSysColor(COLOR_GRAYTEXT); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl PhMipCommitListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + + return uintptrcmp(node2->ProcessItem->VmCounters.PagefileUsage, node1->ProcessItem->VmCounters.PagefileUsage); +} + +int __cdecl PhMipCommitListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return uint64cmp(*(PULONG64)data2->UserData, *(PULONG64)data1->UserData); +} + +BOOLEAN PhMipPhysicalListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + PH_FORMAT format[5]; + ULONG physicalUsage = PhSystemBasicInformation.NumberOfPhysicalPages - PhPerfInformation.AvailablePages; + FLOAT physicalFraction = (FLOAT)physicalUsage / PhSystemBasicInformation.NumberOfPhysicalPages; + + PhInitFormatS(&format[0], L"Physical "); + PhInitFormatSize(&format[1], UInt32x32To64(physicalUsage, PAGE_SIZE)); + PhInitFormatS(&format[2], L" ("); + PhInitFormatF(&format[3], physicalFraction * 100, 2); + PhInitFormatS(&format[4], L"%)"); + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PH_AUTO(PhFormat(format, 5, 96))); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), PhMipPhysicalListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + ULONG64 workingSet = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + workingSet += ((PPH_PROCESS_ITEM)processes->Items[i])->VmCounters.WorkingSetSize; + + *(PULONG64)assignSortData->SortData->UserData = workingSet; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipPhysicalListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + ULONG64 privateBytes = *(PULONG64)getUsageText->SortData->UserData; + + PhMoveReference(&getUsageText->Line1, PhFormatSize(privateBytes, -1)); + PhMoveReference(&getUsageText->Line2, PhCreateString(L"Working set")); + getUsageText->Line2Color = GetSysColor(COLOR_GRAYTEXT); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl PhMipPhysicalListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + + return uintptrcmp(node2->ProcessItem->VmCounters.WorkingSetSize, node1->ProcessItem->VmCounters.WorkingSetSize); +} + +int __cdecl PhMipPhysicalListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return uint64cmp(*(PULONG64)data2->UserData, *(PULONG64)data1->UserData); +} + +BOOLEAN PhMipIoListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + PH_FORMAT format[6]; + + PhInitFormatS(&format[0], L"I/O R: "); + PhInitFormatSize(&format[1], PhIoReadDelta.Delta); + format[1].Type |= FormatUsePrecision; + format[1].Precision = 0; + PhInitFormatS(&format[2], L" W: "); + PhInitFormatSize(&format[3], PhIoWriteDelta.Delta); + format[3].Type |= FormatUsePrecision; + format[3].Precision = 0; + PhInitFormatS(&format[4], L" O: "); + PhInitFormatSize(&format[5], PhIoOtherDelta.Delta); + format[5].Type |= FormatUsePrecision; + format[5].Precision = 0; + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PH_AUTO(PhFormat(format, 6, 80))); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), PhMipIoListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + ULONG64 ioReadOtherDelta = 0; + ULONG64 ioWriteDelta = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + { + PPH_PROCESS_ITEM processItem = processes->Items[i]; + ioReadOtherDelta += processItem->IoReadDelta.Delta; + ioWriteDelta += processItem->IoWriteDelta.Delta; + ioReadOtherDelta += processItem->IoOtherDelta.Delta; + } + + assignSortData->SortData->UserData[0] = ioReadOtherDelta; + assignSortData->SortData->UserData[1] = ioWriteDelta; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), PhMipIoListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + ULONG64 ioReadOtherDelta = getUsageText->SortData->UserData[0]; + ULONG64 ioWriteDelta = getUsageText->SortData->UserData[1]; + PH_FORMAT format[1]; + + PhInitFormatSize(&format[0], ioReadOtherDelta + ioWriteDelta); + PhMoveReference(&getUsageText->Line1, PhFormat(format, 1, 16)); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl PhMipIoListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + int result; + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + ULONG64 delta1 = node1->ProcessItem->IoReadDelta.Delta + node1->ProcessItem->IoWriteDelta.Delta + node1->ProcessItem->IoOtherDelta.Delta; + ULONG64 delta2 = node2->ProcessItem->IoReadDelta.Delta + node2->ProcessItem->IoWriteDelta.Delta + node2->ProcessItem->IoOtherDelta.Delta; + ULONG64 value1 = node1->ProcessItem->IoReadDelta.Value + node1->ProcessItem->IoWriteDelta.Value + node1->ProcessItem->IoOtherDelta.Value; + ULONG64 value2 = node2->ProcessItem->IoReadDelta.Value + node2->ProcessItem->IoWriteDelta.Value + node2->ProcessItem->IoOtherDelta.Value; + + result = uint64cmp(delta2, delta1); + + if (result == 0) + result = uint64cmp(value2, value1); + + return result; +} + +int __cdecl PhMipIoListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return uint64cmp(data2->UserData[0] + data2->UserData[1], data1->UserData[0] + data1->UserData[1]); +} diff --git a/ProcessHacker/modlist.c b/ProcessHacker/modlist.c new file mode 100644 index 0000000..1f5f002 --- /dev/null +++ b/ProcessHacker/modlist.c @@ -0,0 +1,1010 @@ +/* + * Process Hacker - + * module list + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhpModuleNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG PhpModuleNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpDestroyModuleNode( + _In_ PPH_MODULE_NODE ModuleNode + ); + +VOID PhpRemoveModuleNode( + _In_ PPH_MODULE_NODE ModuleNode, + _In_ PPH_MODULE_LIST_CONTEXT Context + ); + +LONG PhpModuleTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpModuleTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID PhInitializeModuleList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + HWND hwnd; + + memset(Context, 0, sizeof(PH_MODULE_LIST_CONTEXT)); + Context->EnableStateHighlighting = TRUE; + + Context->NodeHashtable = PhCreateHashtable( + sizeof(PPH_MODULE_NODE), + PhpModuleNodeHashtableEqualFunction, + PhpModuleNodeHashtableHashFunction, + 100 + ); + Context->NodeList = PhCreateList(100); + + Context->ParentWindowHandle = ParentWindowHandle; + Context->TreeNewHandle = TreeNewHandle; + hwnd = TreeNewHandle; + PhSetControlTheme(hwnd, L"explorer"); + + TreeNew_SetCallback(hwnd, PhpModuleTreeNewCallback, Context); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHMOTLC_NAME, TRUE, L"Name", 100, PH_ALIGN_LEFT, -2, 0); + PhAddTreeNewColumn(hwnd, PHMOTLC_BASEADDRESS, TRUE, L"Base address", 80, PH_ALIGN_RIGHT, 0, DT_RIGHT); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_SIZE, TRUE, L"Size", 60, PH_ALIGN_RIGHT, 1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHMOTLC_DESCRIPTION, TRUE, L"Description", 160, PH_ALIGN_LEFT, 2, 0); + + PhAddTreeNewColumn(hwnd, PHMOTLC_COMPANYNAME, FALSE, L"Company name", 180, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHMOTLC_VERSION, FALSE, L"Version", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHMOTLC_FILENAME, FALSE, L"File name", 180, PH_ALIGN_LEFT, -1, DT_PATH_ELLIPSIS); + + PhAddTreeNewColumn(hwnd, PHMOTLC_TYPE, FALSE, L"Type", 80, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_LOADCOUNT, FALSE, L"Load count", 40, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHMOTLC_VERIFICATIONSTATUS, FALSE, L"Verification status", 70, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHMOTLC_VERIFIEDSIGNER, FALSE, L"Verified signer", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_ASLR, FALSE, L"ASLR", 50, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_TIMESTAMP, FALSE, L"Time stamp", 100, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_CFGUARD, FALSE, L"CF Guard", 70, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_LOADTIME, FALSE, L"Load time", 100, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumn(hwnd, PHMOTLC_LOADREASON, FALSE, L"Load reason", 80, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_FILEMODIFIEDTIME, FALSE, L"File modified time", 140, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHMOTLC_FILESIZE, FALSE, L"File size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetTriState(hwnd, TRUE); + TreeNew_SetSort(hwnd, 0, NoSortOrder); + + PhCmInitializeManager(&Context->Cm, hwnd, PHMOTLC_MAXIMUM, PhpModuleTreeNewPostSortFunction); +} + +VOID PhDeleteModuleList( + _In_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + ULONG i; + + if (Context->BoldFont) + DeleteObject(Context->BoldFont); + + PhCmDeleteManager(&Context->Cm); + + for (i = 0; i < Context->NodeList->Count; i++) + PhpDestroyModuleNode(Context->NodeList->Items[i]); + + PhDereferenceObject(Context->NodeHashtable); + PhDereferenceObject(Context->NodeList); +} + +BOOLEAN PhpModuleNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_MODULE_NODE moduleNode1 = *(PPH_MODULE_NODE *)Entry1; + PPH_MODULE_NODE moduleNode2 = *(PPH_MODULE_NODE *)Entry2; + + return moduleNode1->ModuleItem == moduleNode2->ModuleItem; +} + +ULONG PhpModuleNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)(*(PPH_MODULE_NODE *)Entry)->ModuleItem); +} + +VOID PhLoadSettingsModuleList( + _Inout_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"ModuleTreeListColumns"); + sortSettings = PhGetStringSetting(L"ModuleTreeListSort"); + PhCmLoadSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSaveSettingsModuleList( + _Inout_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(Context->TreeNewHandle, &Context->Cm, 0, &sortSettings); + PhSetStringSetting2(L"ModuleTreeListColumns", &settings->sr); + PhSetStringSetting2(L"ModuleTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +PPH_MODULE_NODE PhAddModuleNode( + _Inout_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_ITEM ModuleItem, + _In_ ULONG RunId + ) +{ + PPH_MODULE_NODE moduleNode; + + moduleNode = PhAllocate(PhEmGetObjectSize(EmModuleNodeType, sizeof(PH_MODULE_NODE))); + memset(moduleNode, 0, sizeof(PH_MODULE_NODE)); + PhInitializeTreeNewNode(&moduleNode->Node); + + if (Context->EnableStateHighlighting && RunId != 1) + { + PhChangeShStateTn( + &moduleNode->Node, + &moduleNode->ShState, + &Context->NodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + moduleNode->ModuleItem = ModuleItem; + PhReferenceObject(ModuleItem); + + memset(moduleNode->TextCache, 0, sizeof(PH_STRINGREF) * PHMOTLC_MAXIMUM); + moduleNode->Node.TextCache = moduleNode->TextCache; + moduleNode->Node.TextCacheSize = PHMOTLC_MAXIMUM; + + PhAddEntryHashtable(Context->NodeHashtable, &moduleNode); + PhAddItemList(Context->NodeList, moduleNode); + + PhEmCallObjectOperation(EmModuleNodeType, moduleNode, EmObjectCreate); + + TreeNew_NodesStructured(Context->TreeNewHandle); + + return moduleNode; +} + +PPH_MODULE_NODE PhFindModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_ITEM ModuleItem + ) +{ + PH_MODULE_NODE lookupModuleNode; + PPH_MODULE_NODE lookupModuleNodePtr = &lookupModuleNode; + PPH_MODULE_NODE *moduleNode; + + lookupModuleNode.ModuleItem = ModuleItem; + + moduleNode = (PPH_MODULE_NODE *)PhFindEntryHashtable( + Context->NodeHashtable, + &lookupModuleNodePtr + ); + + if (moduleNode) + return *moduleNode; + else + return NULL; +} + +VOID PhRemoveModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_NODE ModuleNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashtable(Context->NodeHashtable, &ModuleNode); + + if (Context->EnableStateHighlighting) + { + PhChangeShStateTn( + &ModuleNode->Node, + &ModuleNode->ShState, + &Context->NodeStateList, + RemovingItemState, + PhCsColorRemoved, + Context->TreeNewHandle + ); + } + else + { + PhpRemoveModuleNode(ModuleNode, Context); + } +} + +VOID PhpDestroyModuleNode( + _In_ PPH_MODULE_NODE ModuleNode + ) +{ + PhEmCallObjectOperation(EmModuleNodeType, ModuleNode, EmObjectDelete); + + PhClearReference(&ModuleNode->TooltipText); + + PhClearReference(&ModuleNode->SizeText); + PhClearReference(&ModuleNode->TimeStampText); + PhClearReference(&ModuleNode->LoadTimeText); + PhClearReference(&ModuleNode->FileModifiedTimeText); + PhClearReference(&ModuleNode->FileSizeText); + + PhDereferenceObject(ModuleNode->ModuleItem); + + PhFree(ModuleNode); +} + +VOID PhpRemoveModuleNode( + _In_ PPH_MODULE_NODE ModuleNode, + _In_ PPH_MODULE_LIST_CONTEXT Context // PH_TICK_SH_STATE requires this parameter to be after ModuleNode + ) +{ + ULONG index; + + // Remove from list and cleanup. + + if ((index = PhFindItemList(Context->NodeList, ModuleNode)) != -1) + PhRemoveItemList(Context->NodeList, index); + + PhpDestroyModuleNode(ModuleNode); + + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhUpdateModuleNode( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _In_ PPH_MODULE_NODE ModuleNode + ) +{ + memset(ModuleNode->TextCache, 0, sizeof(PH_STRINGREF) * PHMOTLC_MAXIMUM); + PhClearReference(&ModuleNode->TooltipText); + + ModuleNode->ValidMask = 0; + PhInvalidateTreeNewNode(&ModuleNode->Node, TN_CACHE_COLOR); + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhTickModuleNodes( + _In_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + PH_TICK_SH_STATE_TN(PH_MODULE_NODE, ShState, Context->NodeStateList, PhpRemoveModuleNode, PhCsHighlightingDuration, Context->TreeNewHandle, TRUE, NULL, Context); +} + +#define SORT_FUNCTION(Column) PhpModuleTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpModuleTreeNewCompare##Column( \ + _In_ void *_context, \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_MODULE_NODE node1 = *(PPH_MODULE_NODE *)_elem1; \ + PPH_MODULE_NODE node2 = *(PPH_MODULE_NODE *)_elem2; \ + PPH_MODULE_ITEM moduleItem1 = node1->ModuleItem; \ + PPH_MODULE_ITEM moduleItem2 = node2->ModuleItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = uintptrcmp((ULONG_PTR)moduleItem1->BaseAddress, (ULONG_PTR)moduleItem2->BaseAddress); \ + \ + return PhModifySort(sortResult, ((PPH_MODULE_LIST_CONTEXT)_context)->TreeNewSortOrder); \ +} + +LONG PhpModuleTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = uintptrcmp((ULONG_PTR)((PPH_MODULE_NODE)Node1)->ModuleItem->BaseAddress, (ULONG_PTR)((PPH_MODULE_NODE)Node2)->ModuleItem->BaseAddress); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(TriState) +{ + if (moduleItem1->IsFirst) + { + sortResult = -1; + } + else if (moduleItem2->IsFirst) + { + sortResult = 1; + } + else + { + sortResult = PhCompareString(moduleItem1->Name, moduleItem2->Name, TRUE); // fall back to sorting by name + } +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Name) +{ + sortResult = PhCompareString(moduleItem1->Name, moduleItem2->Name, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(BaseAddress) +{ + sortResult = uintptrcmp((ULONG_PTR)moduleItem1->BaseAddress, (ULONG_PTR)moduleItem2->BaseAddress); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Size) +{ + sortResult = uintcmp(moduleItem1->Size, moduleItem2->Size); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Description) +{ + sortResult = PhCompareStringWithNull(moduleItem1->VersionInfo.FileDescription, moduleItem2->VersionInfo.FileDescription, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CompanyName) +{ + sortResult = PhCompareStringWithNull(moduleItem1->VersionInfo.CompanyName, moduleItem2->VersionInfo.CompanyName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Version) +{ + sortResult = PhCompareStringWithNull(moduleItem1->VersionInfo.FileVersion, moduleItem2->VersionInfo.FileVersion, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileName) +{ + sortResult = PhCompareStringWithNull(moduleItem1->FileName, moduleItem2->FileName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Type) +{ + sortResult = uintcmp(moduleItem1->Type, moduleItem2->Type); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LoadCount) +{ + sortResult = uintcmp(moduleItem1->LoadCount, moduleItem2->LoadCount); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(VerificationStatus) +{ + sortResult = intcmp(moduleItem1->VerifyResult, moduleItem2->VerifyResult); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(VerifiedSigner) +{ + sortResult = PhCompareStringWithNull( + moduleItem1->VerifySignerName, + moduleItem2->VerifySignerName, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Aslr) +{ + sortResult = intcmp( + moduleItem1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, + moduleItem2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TimeStamp) +{ + sortResult = uintcmp(moduleItem1->ImageTimeDateStamp, moduleItem2->ImageTimeDateStamp); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CfGuard) +{ + sortResult = intcmp( + moduleItem1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF, + moduleItem2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LoadTime) +{ + sortResult = uint64cmp(moduleItem1->LoadTime.QuadPart, moduleItem2->LoadTime.QuadPart); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LoadReason) +{ + sortResult = uintcmp(moduleItem1->LoadReason, moduleItem2->LoadReason); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileModifiedTime) +{ + sortResult = int64cmp(moduleItem1->FileLastWriteTime.QuadPart, moduleItem2->FileLastWriteTime.QuadPart); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileSize) +{ + sortResult = int64cmp(moduleItem1->FileEndOfFile.QuadPart, moduleItem2->FileEndOfFile.QuadPart); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpModuleTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_MODULE_LIST_CONTEXT context; + PPH_MODULE_NODE node; + + context = Context; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &context->Cm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Name), + SORT_FUNCTION(BaseAddress), + SORT_FUNCTION(Size), + SORT_FUNCTION(Description), + SORT_FUNCTION(CompanyName), + SORT_FUNCTION(Version), + SORT_FUNCTION(FileName), + SORT_FUNCTION(Type), + SORT_FUNCTION(LoadCount), + SORT_FUNCTION(VerificationStatus), + SORT_FUNCTION(VerifiedSigner), + SORT_FUNCTION(Aslr), + SORT_FUNCTION(TimeStamp), + SORT_FUNCTION(CfGuard), + SORT_FUNCTION(LoadTime), + SORT_FUNCTION(LoadReason), + SORT_FUNCTION(FileModifiedTime), + SORT_FUNCTION(FileSize) + }; + int (__cdecl *sortFunction)(void *, const void *, const void *); + + if (context->TreeNewSortOrder == NoSortOrder) + { + sortFunction = SORT_FUNCTION(TriState); + } + else + { + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)context->NodeList->Items, + context->NodeList->Count, + context->TreeNewSortColumn, + context->TreeNewSortOrder, + &context->Cm + )) + { + if (context->TreeNewSortColumn < PHMOTLC_MAXIMUM) + sortFunction = sortFunctions[context->TreeNewSortColumn]; + else + sortFunction = NULL; + } + else + { + sortFunction = NULL; + } + } + + if (sortFunction) + { + qsort_s(context->NodeList->Items, context->NodeList->Count, sizeof(PVOID), sortFunction, context); + } + + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeList->Items; + getChildren->NumberOfChildren = context->NodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_MODULE_ITEM moduleItem; + + node = (PPH_MODULE_NODE)getCellText->Node; + moduleItem = node->ModuleItem; + + switch (getCellText->Id) + { + case PHMOTLC_NAME: + getCellText->Text = moduleItem->Name->sr; + break; + case PHMOTLC_BASEADDRESS: + PhInitializeStringRefLongHint(&getCellText->Text, moduleItem->BaseAddressString); + break; + case PHMOTLC_SIZE: + if (!node->SizeText) + node->SizeText = PhFormatSize(moduleItem->Size, -1); + getCellText->Text = PhGetStringRef(node->SizeText); + break; + case PHMOTLC_DESCRIPTION: + getCellText->Text = PhGetStringRef(moduleItem->VersionInfo.FileDescription); + break; + case PHMOTLC_COMPANYNAME: + getCellText->Text = PhGetStringRef(moduleItem->VersionInfo.CompanyName); + break; + case PHMOTLC_VERSION: + getCellText->Text = PhGetStringRef(moduleItem->VersionInfo.FileVersion); + break; + case PHMOTLC_FILENAME: + getCellText->Text = PhGetStringRef(moduleItem->FileName); + break; + case PHMOTLC_TYPE: + { + PWSTR typeString; + + switch (moduleItem->Type) + { + case PH_MODULE_TYPE_MODULE: + typeString = L"DLL"; + break; + case PH_MODULE_TYPE_MAPPED_FILE: + typeString = L"Mapped file"; + break; + case PH_MODULE_TYPE_MAPPED_IMAGE: + typeString = L"Mapped image"; + break; + case PH_MODULE_TYPE_WOW64_MODULE: + typeString = L"WOW64 DLL"; + break; + case PH_MODULE_TYPE_KERNEL_MODULE: + typeString = L"Kernel module"; + break; + default: + typeString = L"Unknown"; + break; + } + + PhInitializeStringRefLongHint(&getCellText->Text, typeString); + } + break; + case PHMOTLC_LOADCOUNT: + if (moduleItem->Type == PH_MODULE_TYPE_MODULE || moduleItem->Type == PH_MODULE_TYPE_KERNEL_MODULE || + moduleItem->Type == PH_MODULE_TYPE_WOW64_MODULE) + { + if (moduleItem->LoadCount != (USHORT)-1) + { + PhPrintInt32(node->LoadCountText, moduleItem->LoadCount); + PhInitializeStringRefLongHint(&getCellText->Text, node->LoadCountText); + } + else + { + PhInitializeStringRef(&getCellText->Text, L"Static"); + } + } + else + { + PhInitializeEmptyStringRef(&getCellText->Text); + } + break; + case PHMOTLC_VERIFICATIONSTATUS: + if (moduleItem->Type == PH_MODULE_TYPE_MODULE || moduleItem->Type == PH_MODULE_TYPE_KERNEL_MODULE || + moduleItem->Type == PH_MODULE_TYPE_WOW64_MODULE || moduleItem->Type == PH_MODULE_TYPE_MAPPED_IMAGE) + { + PhInitializeStringRef(&getCellText->Text, + moduleItem->VerifyResult == VrTrusted ? L"Trusted" : L"Not trusted"); + } + else + { + PhInitializeEmptyStringRef(&getCellText->Text); + } + break; + case PHMOTLC_VERIFIEDSIGNER: + getCellText->Text = PhGetStringRef(moduleItem->VerifySignerName); + break; + case PHMOTLC_ASLR: + if (WindowsVersion >= WINDOWS_VISTA) + { + if (moduleItem->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) + PhInitializeStringRef(&getCellText->Text, L"ASLR"); + } + else + { + PhInitializeStringRef(&getCellText->Text, L"N/A"); + } + break; + case PHMOTLC_TIMESTAMP: + { + LARGE_INTEGER time; + SYSTEMTIME systemTime; + + if (moduleItem->ImageTimeDateStamp != 0) + { + RtlSecondsSince1970ToTime(moduleItem->ImageTimeDateStamp, &time); + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + PhMoveReference(&node->TimeStampText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->TimeStampText->sr; + } + else + { + PhInitializeEmptyStringRef(&getCellText->Text); + } + } + break; + case PHMOTLC_CFGUARD: + if (WindowsVersion >= WINDOWS_8_1) + { + if (moduleItem->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF) + PhInitializeStringRef(&getCellText->Text, L"CF Guard"); + } + else + { + PhInitializeStringRef(&getCellText->Text, L"N/A"); + } + break; + case PHMOTLC_LOADTIME: + { + SYSTEMTIME systemTime; + + if (moduleItem->LoadTime.QuadPart != 0) + { + PhLargeIntegerToLocalSystemTime(&systemTime, &moduleItem->LoadTime); + PhMoveReference(&node->LoadTimeText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->LoadTimeText->sr; + } + else + { + PhInitializeEmptyStringRef(&getCellText->Text); + } + } + break; + case PHMOTLC_LOADREASON: + { + PWSTR string = L""; + + if (moduleItem->Type == PH_MODULE_TYPE_MODULE || moduleItem->Type == PH_MODULE_TYPE_WOW64_MODULE) + { + switch (moduleItem->LoadReason) + { + case LoadReasonStaticDependency: + string = L"Static dependency"; + break; + case LoadReasonStaticForwarderDependency: + string = L"Static forwarder dependency"; + break; + case LoadReasonDynamicForwarderDependency: + string = L"Dynamic forwarder dependency"; + break; + case LoadReasonDelayloadDependency: + string = L"Delay load dependency"; + break; + case LoadReasonDynamicLoad: + string = L"Dynamic"; + break; + case LoadReasonAsImageLoad: + string = L"As image"; + break; + case LoadReasonAsDataLoad: + string = L"As data"; + break; + default: + if (WindowsVersion >= WINDOWS_8) + string = L"Unknown"; + else + string = L"N/A"; + break; + } + } + + PhInitializeStringRefLongHint(&getCellText->Text, string); + } + break; + case PHMOTLC_FILEMODIFIEDTIME: + if (moduleItem->FileLastWriteTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &moduleItem->FileLastWriteTime); + PhMoveReference(&node->FileModifiedTimeText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->FileModifiedTimeText->sr; + } + break; + case PHMOTLC_FILESIZE: + if (moduleItem->FileEndOfFile.QuadPart != -1) + { + PhMoveReference(&node->FileSizeText, PhFormatSize(moduleItem->FileEndOfFile.QuadPart, -1)); + getCellText->Text = node->FileSizeText->sr; + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + PPH_MODULE_ITEM moduleItem; + + node = (PPH_MODULE_NODE)getNodeColor->Node; + moduleItem = node->ModuleItem; + + if (!moduleItem) + ; // Dummy + else if (PhCsUseColorDotNet && (moduleItem->Flags & LDRP_COR_IMAGE)) + getNodeColor->BackColor = PhCsColorDotNet; + else if (PhCsUseColorImmersiveProcesses && (moduleItem->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_APPCONTAINER)) + getNodeColor->BackColor = PhCsColorImmersiveProcesses; + else if (PhCsUseColorRelocatedModules && (moduleItem->Flags & LDRP_IMAGE_NOT_AT_BASE)) + getNodeColor->BackColor = PhCsColorRelocatedModules; + + getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR; + } + return TRUE; + case TreeNewGetNodeFont: + { + PPH_TREENEW_GET_NODE_FONT getNodeFont = Parameter1; + + node = (PPH_MODULE_NODE)getNodeFont->Node; + + // Make the executable file module item bold. + if (node->ModuleItem->IsFirst) + { + if (!context->BoldFont) + context->BoldFont = PhDuplicateFontWithNewWeight((HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0), FW_BOLD); + + getNodeFont->Font = context->BoldFont ? context->BoldFont : NULL; + getNodeFont->Flags = TN_CACHE; + return TRUE; + } + } + break; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + + node = (PPH_MODULE_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0) + return FALSE; + + if (!node->TooltipText) + { + node->TooltipText = PhFormatImageVersionInfo( + node->ModuleItem->FileName, + &node->ModuleItem->VersionInfo, + NULL, + 0 + ); + + // Make sure we don't try to create the tooltip text again. + if (!node->TooltipText) + node->TooltipText = PhReferenceEmptyString(); + } + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->Font = NULL; // use default font + getCellTooltip->MaximumWidth = 550; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(context->TreeNewHandle, 0, -1); + break; + case 'M': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_SEARCHONLINE, 0); + break; + case VK_DELETE: + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_UNLOAD, 0); + break; + case VK_RETURN: + if (GetKeyState(VK_CONTROL) >= 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_INSPECT, 0); + else + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_OPENFILELOCATION, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = NoSortOrder; + PhInitializeTreeNewColumnMenuEx(&data, PH_TN_COLUMN_MENU_SHOW_RESET_SORT); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_MODULE_INSPECT, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_SHOWCONTEXTMENU, (LPARAM)contextMenu); + } + return TRUE; + case TreeNewGetDialogCode: + { + PULONG code = Parameter2; + + if (PtrToUlong(Parameter1) == VK_RETURN) + { + *code = DLGC_WANTMESSAGE; + return TRUE; + } + } + return FALSE; + } + + return FALSE; +} + +PPH_MODULE_ITEM PhGetSelectedModuleItem( + _In_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + PPH_MODULE_ITEM moduleItem = NULL; + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_MODULE_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + { + moduleItem = node->ModuleItem; + break; + } + } + + return moduleItem; +} + +VOID PhGetSelectedModuleItems( + _In_ PPH_MODULE_LIST_CONTEXT Context, + _Out_ PPH_MODULE_ITEM **Modules, + _Out_ PULONG NumberOfModules + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_MODULE_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->ModuleItem); + } + + *NumberOfModules = (ULONG)array.Count; + *Modules = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllModuleNodes( + _In_ PPH_MODULE_LIST_CONTEXT Context + ) +{ + TreeNew_DeselectRange(Context->TreeNewHandle, 0, -1); +} diff --git a/ProcessHacker/modprv.c b/ProcessHacker/modprv.c new file mode 100644 index 0000000..9d8dea2 --- /dev/null +++ b/ProcessHacker/modprv.c @@ -0,0 +1,587 @@ +/* + * Process Hacker - + * module provider + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PH_MODULE_QUERY_DATA +{ + SLIST_ENTRY ListEntry; + PPH_MODULE_PROVIDER ModuleProvider; + PPH_MODULE_ITEM ModuleItem; + + VERIFY_RESULT VerifyResult; + PPH_STRING VerifySignerName; +} PH_MODULE_QUERY_DATA, *PPH_MODULE_QUERY_DATA; + +VOID NTAPI PhpModuleProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpModuleItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +BOOLEAN NTAPI PhpModuleHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpModuleHashtableHashFunction( + _In_ PVOID Entry + ); + +PPH_OBJECT_TYPE PhModuleProviderType; +PPH_OBJECT_TYPE PhModuleItemType; + +BOOLEAN PhModuleProviderInitialization( + VOID + ) +{ + PhModuleProviderType = PhCreateObjectType(L"ModuleProvider", 0, PhpModuleProviderDeleteProcedure); + PhModuleItemType = PhCreateObjectType(L"ModuleItem", 0, PhpModuleItemDeleteProcedure); + + return TRUE; +} + +PPH_MODULE_PROVIDER PhCreateModuleProvider( + _In_ HANDLE ProcessId + ) +{ + NTSTATUS status; + PPH_MODULE_PROVIDER moduleProvider; + + moduleProvider = PhCreateObject( + PhEmGetObjectSize(EmModuleProviderType, sizeof(PH_MODULE_PROVIDER)), + PhModuleProviderType + ); + + moduleProvider->ModuleHashtable = PhCreateHashtable( + sizeof(PPH_MODULE_ITEM), + PhpModuleHashtableEqualFunction, + PhpModuleHashtableHashFunction, + 20 + ); + PhInitializeFastLock(&moduleProvider->ModuleHashtableLock); + + PhInitializeCallback(&moduleProvider->ModuleAddedEvent); + PhInitializeCallback(&moduleProvider->ModuleModifiedEvent); + PhInitializeCallback(&moduleProvider->ModuleRemovedEvent); + PhInitializeCallback(&moduleProvider->UpdatedEvent); + + moduleProvider->ProcessId = ProcessId; + moduleProvider->ProcessHandle = NULL; + moduleProvider->PackageFullName = NULL; + moduleProvider->RunStatus = STATUS_SUCCESS; + + // It doesn't matter if we can't get a process handle. + + // Try to get a handle with query information + vm read access. + if (!NT_SUCCESS(status = PhOpenProcess( + &moduleProvider->ProcessHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ProcessId + ))) + { + if (WINDOWS_HAS_LIMITED_ACCESS) + { + // Try to get a handle with query limited information + vm read access. + status = PhOpenProcess( + &moduleProvider->ProcessHandle, + PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, + ProcessId + ); + } + + moduleProvider->RunStatus = status; + } + + if (moduleProvider->ProcessHandle) + moduleProvider->PackageFullName = PhGetProcessPackageFullName(moduleProvider->ProcessHandle); + + RtlInitializeSListHead(&moduleProvider->QueryListHead); + + PhEmCallObjectOperation(EmModuleProviderType, moduleProvider, EmObjectCreate); + + return moduleProvider; +} + +VOID PhpModuleProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_MODULE_PROVIDER moduleProvider = (PPH_MODULE_PROVIDER)Object; + + PhEmCallObjectOperation(EmModuleProviderType, moduleProvider, EmObjectDelete); + + // Dereference all module items (we referenced them + // when we added them to the hashtable). + PhDereferenceAllModuleItems(moduleProvider); + + PhDereferenceObject(moduleProvider->ModuleHashtable); + PhDeleteFastLock(&moduleProvider->ModuleHashtableLock); + PhDeleteCallback(&moduleProvider->ModuleAddedEvent); + PhDeleteCallback(&moduleProvider->ModuleModifiedEvent); + PhDeleteCallback(&moduleProvider->ModuleRemovedEvent); + PhDeleteCallback(&moduleProvider->UpdatedEvent); + + // Destroy all queue items. + { + PSLIST_ENTRY entry; + PPH_MODULE_QUERY_DATA data; + + entry = RtlInterlockedFlushSList(&moduleProvider->QueryListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_MODULE_QUERY_DATA, ListEntry); + entry = entry->Next; + + if (data->VerifySignerName) PhDereferenceObject(data->VerifySignerName); + PhDereferenceObject(data->ModuleItem); + PhFree(data); + } + } + + if (moduleProvider->PackageFullName) PhDereferenceObject(moduleProvider->PackageFullName); + if (moduleProvider->ProcessHandle) NtClose(moduleProvider->ProcessHandle); +} + +PPH_MODULE_ITEM PhCreateModuleItem( + VOID + ) +{ + PPH_MODULE_ITEM moduleItem; + + moduleItem = PhCreateObject( + PhEmGetObjectSize(EmModuleItemType, sizeof(PH_MODULE_ITEM)), + PhModuleItemType + ); + memset(moduleItem, 0, sizeof(PH_MODULE_ITEM)); + PhEmCallObjectOperation(EmModuleItemType, moduleItem, EmObjectCreate); + + return moduleItem; +} + +VOID PhpModuleItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_MODULE_ITEM moduleItem = (PPH_MODULE_ITEM)Object; + + PhEmCallObjectOperation(EmModuleItemType, moduleItem, EmObjectDelete); + + if (moduleItem->Name) PhDereferenceObject(moduleItem->Name); + if (moduleItem->FileName) PhDereferenceObject(moduleItem->FileName); + if (moduleItem->VerifySignerName) PhDereferenceObject(moduleItem->VerifySignerName); + PhDeleteImageVersionInfo(&moduleItem->VersionInfo); +} + +BOOLEAN NTAPI PhpModuleHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + return + (*(PPH_MODULE_ITEM *)Entry1)->BaseAddress == + (*(PPH_MODULE_ITEM *)Entry2)->BaseAddress; +} + +ULONG NTAPI PhpModuleHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PVOID baseAddress = (*(PPH_MODULE_ITEM *)Entry)->BaseAddress; + + return PhHashIntPtr((ULONG_PTR)baseAddress); +} + +PPH_MODULE_ITEM PhReferenceModuleItem( + _In_ PPH_MODULE_PROVIDER ModuleProvider, + _In_ PVOID BaseAddress + ) +{ + PH_MODULE_ITEM lookupModuleItem; + PPH_MODULE_ITEM lookupModuleItemPtr = &lookupModuleItem; + PPH_MODULE_ITEM *moduleItemPtr; + PPH_MODULE_ITEM moduleItem; + + lookupModuleItem.BaseAddress = BaseAddress; + + PhAcquireFastLockShared(&ModuleProvider->ModuleHashtableLock); + + moduleItemPtr = (PPH_MODULE_ITEM *)PhFindEntryHashtable( + ModuleProvider->ModuleHashtable, + &lookupModuleItemPtr + ); + + if (moduleItemPtr) + { + moduleItem = *moduleItemPtr; + PhReferenceObject(moduleItem); + } + else + { + moduleItem = NULL; + } + + PhReleaseFastLockShared(&ModuleProvider->ModuleHashtableLock); + + return moduleItem; +} + +VOID PhDereferenceAllModuleItems( + _In_ PPH_MODULE_PROVIDER ModuleProvider + ) +{ + ULONG enumerationKey = 0; + PPH_MODULE_ITEM *moduleItem; + + PhAcquireFastLockExclusive(&ModuleProvider->ModuleHashtableLock); + + while (PhEnumHashtable(ModuleProvider->ModuleHashtable, (PVOID *)&moduleItem, &enumerationKey)) + { + PhDereferenceObject(*moduleItem); + } + + PhReleaseFastLockExclusive(&ModuleProvider->ModuleHashtableLock); +} + +VOID PhpRemoveModuleItem( + _In_ PPH_MODULE_PROVIDER ModuleProvider, + _In_ PPH_MODULE_ITEM ModuleItem + ) +{ + PhRemoveEntryHashtable(ModuleProvider->ModuleHashtable, &ModuleItem); + PhDereferenceObject(ModuleItem); +} + +NTSTATUS PhpModuleQueryWorker( + _In_ PVOID Parameter + ) +{ + PPH_MODULE_QUERY_DATA data = (PPH_MODULE_QUERY_DATA)Parameter; + + data->VerifyResult = PhVerifyFileCached( + data->ModuleItem->FileName, + PhGetString(data->ModuleProvider->PackageFullName), + &data->VerifySignerName, + FALSE + ); + + RtlInterlockedPushEntrySList(&data->ModuleProvider->QueryListHead, &data->ListEntry); + + PhDereferenceObject(data->ModuleProvider); + + return STATUS_SUCCESS; +} + +VOID PhpQueueModuleQuery( + _In_ PPH_MODULE_PROVIDER ModuleProvider, + _In_ PPH_MODULE_ITEM ModuleItem + ) +{ + PPH_MODULE_QUERY_DATA data; + PH_WORK_QUEUE_ENVIRONMENT environment; + + if (!PhEnableProcessQueryStage2) + return; + + data = PhAllocate(sizeof(PH_MODULE_QUERY_DATA)); + memset(data, 0, sizeof(PH_MODULE_QUERY_DATA)); + data->ModuleProvider = ModuleProvider; + data->ModuleItem = ModuleItem; + + PhReferenceObject(ModuleProvider); + PhReferenceObject(ModuleItem); + + PhInitializeWorkQueueEnvironment(&environment); + environment.BasePriority = THREAD_PRIORITY_BELOW_NORMAL; + environment.IoPriority = IoPriorityLow; + environment.PagePriority = MEMORY_PRIORITY_LOW; + + PhQueueItemWorkQueueEx(PhGetGlobalWorkQueue(), PhpModuleQueryWorker, data, NULL, &environment); +} + +static BOOLEAN NTAPI EnumModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PPH_MODULE_INFO copy; + + copy = PhAllocateCopy(Module, sizeof(PH_MODULE_INFO)); + PhReferenceObject(copy->Name); + PhReferenceObject(copy->FileName); + + PhAddItemList((PPH_LIST)Context, copy); + + return TRUE; +} + +VOID PhModuleProviderUpdate( + _In_ PVOID Object + ) +{ + PPH_MODULE_PROVIDER moduleProvider = (PPH_MODULE_PROVIDER)Object; + PPH_LIST modules; + ULONG i; + + // If we didn't get a handle when we created the provider, + // abort (unless this is the System process - in that case + // we don't need a handle). + if (!moduleProvider->ProcessHandle && moduleProvider->ProcessId != SYSTEM_PROCESS_ID) + goto UpdateExit; + + modules = PhCreateList(20); + + moduleProvider->RunStatus = PhEnumGenericModules( + moduleProvider->ProcessId, + moduleProvider->ProcessHandle, + PH_ENUM_GENERIC_MAPPED_FILES | PH_ENUM_GENERIC_MAPPED_IMAGES, + EnumModulesCallback, + modules + ); + + // Look for removed modules. + { + PPH_LIST modulesToRemove = NULL; + ULONG enumerationKey = 0; + PPH_MODULE_ITEM *moduleItem; + + while (PhEnumHashtable(moduleProvider->ModuleHashtable, (PVOID *)&moduleItem, &enumerationKey)) + { + BOOLEAN found = FALSE; + + // Check if the module still exists. + for (i = 0; i < modules->Count; i++) + { + PPH_MODULE_INFO module = modules->Items[i]; + + if ((*moduleItem)->BaseAddress == module->BaseAddress && + PhEqualString((*moduleItem)->FileName, module->FileName, TRUE)) + { + found = TRUE; + break; + } + } + + if (!found) + { + // Raise the module removed event. + PhInvokeCallback(&moduleProvider->ModuleRemovedEvent, *moduleItem); + + if (!modulesToRemove) + modulesToRemove = PhCreateList(2); + + PhAddItemList(modulesToRemove, *moduleItem); + } + } + + if (modulesToRemove) + { + PhAcquireFastLockExclusive(&moduleProvider->ModuleHashtableLock); + + for (i = 0; i < modulesToRemove->Count; i++) + { + PhpRemoveModuleItem( + moduleProvider, + (PPH_MODULE_ITEM)modulesToRemove->Items[i] + ); + } + + PhReleaseFastLockExclusive(&moduleProvider->ModuleHashtableLock); + PhDereferenceObject(modulesToRemove); + } + } + + // Go through the queued thread query data. + { + PSLIST_ENTRY entry; + PPH_MODULE_QUERY_DATA data; + + entry = RtlInterlockedFlushSList(&moduleProvider->QueryListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_MODULE_QUERY_DATA, ListEntry); + entry = entry->Next; + + data->ModuleItem->VerifyResult = data->VerifyResult; + data->ModuleItem->VerifySignerName = data->VerifySignerName; + data->ModuleItem->JustProcessed = TRUE; + + PhDereferenceObject(data->ModuleItem); + PhFree(data); + } + } + + // Look for new modules. + for (i = 0; i < modules->Count; i++) + { + PPH_MODULE_INFO module = modules->Items[i]; + PPH_MODULE_ITEM moduleItem; + + moduleItem = PhReferenceModuleItem(moduleProvider, module->BaseAddress); + + if (!moduleItem) + { + FILE_NETWORK_OPEN_INFORMATION networkOpenInfo; + + moduleItem = PhCreateModuleItem(); + + moduleItem->BaseAddress = module->BaseAddress; + PhPrintPointer(moduleItem->BaseAddressString, moduleItem->BaseAddress); + moduleItem->Size = module->Size; + moduleItem->Flags = module->Flags; + moduleItem->Type = module->Type; + moduleItem->LoadReason = module->LoadReason; + moduleItem->LoadCount = module->LoadCount; + moduleItem->LoadTime = module->LoadTime; + + moduleItem->Name = module->Name; + PhReferenceObject(moduleItem->Name); + moduleItem->FileName = module->FileName; + PhReferenceObject(moduleItem->FileName); + + PhInitializeImageVersionInfo(&moduleItem->VersionInfo, moduleItem->FileName->Buffer); + + moduleItem->IsFirst = i == 0; + + // Fix up the load count. If this is not an ordinary DLL or kernel module, set the load count to 0. + if (moduleItem->Type != PH_MODULE_TYPE_MODULE && + moduleItem->Type != PH_MODULE_TYPE_WOW64_MODULE && + moduleItem->Type != PH_MODULE_TYPE_KERNEL_MODULE) + { + moduleItem->LoadCount = 0; + } + + if (moduleItem->Type == PH_MODULE_TYPE_MODULE || + moduleItem->Type == PH_MODULE_TYPE_WOW64_MODULE || + moduleItem->Type == PH_MODULE_TYPE_MAPPED_IMAGE) + { + PH_REMOTE_MAPPED_IMAGE remoteMappedImage; + + // Note: + // On Windows 7 the LDRP_IMAGE_NOT_AT_BASE flag doesn't appear to be used + // anymore. Instead we'll check ImageBase in the image headers. We read this in + // from the process' memory because: + // + // 1. It (should be) faster than opening the file and mapping it in, and + // 2. It contains the correct original image base relocated by ASLR, if present. + + moduleItem->Flags &= ~LDRP_IMAGE_NOT_AT_BASE; + + if (NT_SUCCESS(PhLoadRemoteMappedImage(moduleProvider->ProcessHandle, moduleItem->BaseAddress, &remoteMappedImage))) + { + moduleItem->ImageTimeDateStamp = remoteMappedImage.NtHeaders->FileHeader.TimeDateStamp; + moduleItem->ImageCharacteristics = remoteMappedImage.NtHeaders->FileHeader.Characteristics; + + if (remoteMappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + if ((ULONG_PTR)((PIMAGE_OPTIONAL_HEADER32)&remoteMappedImage.NtHeaders->OptionalHeader)->ImageBase != (ULONG_PTR)moduleItem->BaseAddress) + moduleItem->Flags |= LDRP_IMAGE_NOT_AT_BASE; + + moduleItem->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER32)&remoteMappedImage.NtHeaders->OptionalHeader)->DllCharacteristics; + } + else if (remoteMappedImage.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + { + if ((ULONG_PTR)((PIMAGE_OPTIONAL_HEADER64)&remoteMappedImage.NtHeaders->OptionalHeader)->ImageBase != (ULONG_PTR)moduleItem->BaseAddress) + moduleItem->Flags |= LDRP_IMAGE_NOT_AT_BASE; + + moduleItem->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER64)&remoteMappedImage.NtHeaders->OptionalHeader)->DllCharacteristics; + } + + PhUnloadRemoteMappedImage(&remoteMappedImage); + } + } + + if (NT_SUCCESS(PhQueryFullAttributesFileWin32(moduleItem->FileName->Buffer, &networkOpenInfo))) + { + moduleItem->FileLastWriteTime = networkOpenInfo.LastWriteTime; + moduleItem->FileEndOfFile = networkOpenInfo.EndOfFile; + } + else + { + moduleItem->FileEndOfFile.QuadPart = -1; + } + + if (moduleItem->Type == PH_MODULE_TYPE_MODULE || moduleItem->Type == PH_MODULE_TYPE_KERNEL_MODULE || + moduleItem->Type == PH_MODULE_TYPE_WOW64_MODULE || moduleItem->Type == PH_MODULE_TYPE_MAPPED_IMAGE) + { + // See if the file has already been verified; if not, queue for verification. + + moduleItem->VerifyResult = PhVerifyFileCached(moduleItem->FileName, NULL, &moduleItem->VerifySignerName, TRUE); + + if (moduleItem->VerifyResult == VrUnknown) + PhpQueueModuleQuery(moduleProvider, moduleItem); + } + + // Add the module item to the hashtable. + PhAcquireFastLockExclusive(&moduleProvider->ModuleHashtableLock); + PhAddEntryHashtable(moduleProvider->ModuleHashtable, &moduleItem); + PhReleaseFastLockExclusive(&moduleProvider->ModuleHashtableLock); + + // Raise the module added event. + PhInvokeCallback(&moduleProvider->ModuleAddedEvent, moduleItem); + } + else + { + BOOLEAN modified = FALSE; + + if (moduleItem->JustProcessed) + modified = TRUE; + + moduleItem->JustProcessed = FALSE; + + if (modified) + PhInvokeCallback(&moduleProvider->ModuleModifiedEvent, moduleItem); + + PhDereferenceObject(moduleItem); + } + } + + // Free the modules list. + + for (i = 0; i < modules->Count; i++) + { + PPH_MODULE_INFO module = modules->Items[i]; + + PhDereferenceObject(module->Name); + PhDereferenceObject(module->FileName); + PhFree(module); + } + + PhDereferenceObject(modules); + +UpdateExit: + PhInvokeCallback(&moduleProvider->UpdatedEvent, NULL); +} diff --git a/ProcessHacker/mtgndlg.c b/ProcessHacker/mtgndlg.c new file mode 100644 index 0000000..54d128d --- /dev/null +++ b/ProcessHacker/mtgndlg.c @@ -0,0 +1,168 @@ +/* + * Process Hacker - + * process mitigation policy details + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +typedef struct _MITIGATION_POLICY_ENTRY +{ + PPH_STRING ShortDescription; + PPH_STRING LongDescription; +} MITIGATION_POLICY_ENTRY, *PMITIGATION_POLICY_ENTRY; + +typedef struct _MITIGATION_POLICY_CONTEXT +{ + MITIGATION_POLICY_ENTRY Entries[MaxProcessMitigationPolicy]; +} MITIGATION_POLICY_CONTEXT, *PMITIGATION_POLICY_CONTEXT; + +INT_PTR CALLBACK PhpProcessMitigationPolicyDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowProcessMitigationPolicyDialog( + _In_ HWND ParentWindowHandle, + _In_ struct _PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION *Information + ) +{ + MITIGATION_POLICY_CONTEXT context; + PROCESS_MITIGATION_POLICY policy; + PPH_STRING shortDescription; + PPH_STRING longDescription; + + memset(&context.Entries, 0, sizeof(context.Entries)); + + for (policy = 0; policy < MaxProcessMitigationPolicy; policy++) + { + if (Information->Pointers[policy] && PhDescribeProcessMitigationPolicy( + policy, + Information->Pointers[policy], + &shortDescription, + &longDescription + )) + { + context.Entries[policy].ShortDescription = shortDescription; + context.Entries[policy].LongDescription = longDescription; + } + } + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_MITIGATION), + ParentWindowHandle, + PhpProcessMitigationPolicyDlgProc, + (LPARAM)&context + ); + + for (policy = 0; policy < MaxProcessMitigationPolicy; policy++) + { + PhClearReference(&context.Entries[policy].ShortDescription); + PhClearReference(&context.Entries[policy].LongDescription); + } +} + +INT_PTR CALLBACK PhpProcessMitigationPolicyDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PMITIGATION_POLICY_CONTEXT context = (PMITIGATION_POLICY_CONTEXT)lParam; + HWND lvHandle; + PROCESS_MITIGATION_POLICY policy; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 350, L"Policy"); + PhSetExtendedListView(lvHandle); + + for (policy = 0; policy < MaxProcessMitigationPolicy; policy++) + { + PMITIGATION_POLICY_ENTRY entry = &context->Entries[policy]; + + if (!entry->ShortDescription) + continue; + + PhAddListViewItem(lvHandle, MAXINT, entry->ShortDescription->Buffer, entry); + } + + ExtendedListView_SortItems(lvHandle); + ListView_SetItemState(lvHandle, 0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)lvHandle, TRUE); + } + break; + case WM_DESTROY: + { + // Nothing + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + HWND lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == lvHandle) + { + PWSTR description; + + if (ListView_GetSelectedCount(lvHandle) == 1) + description = ((PMITIGATION_POLICY_ENTRY)PhGetSelectedListViewItemParam(lvHandle))->LongDescription->Buffer; + else + description = L""; + + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, description); + } + } + break; + } + + PhHandleListViewNotifyForCopy(lParam, lvHandle); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/mxml/COPYING b/ProcessHacker/mxml/COPYING new file mode 100644 index 0000000..4d0aa78 --- /dev/null +++ b/ProcessHacker/mxml/COPYING @@ -0,0 +1,507 @@ + Mini-XML License + September 18, 2010 + + +The Mini-XML library and included programs are provided under the +terms of the GNU Library General Public License version 2 (LGPL2) +with the following exceptions: + + 1. Static linking of applications to the Mini-XML library +does not constitute a derivative work and does not require +the author to provide source code for the application, use +the shared Mini-XML libraries, or link their applications +against a user-supplied version of Mini-XML. + +If you link the application to a modified version of +Mini-XML, then the changes to Mini-XML must be provided +under the terms of the LGPL2 in sections 1, 2, and 4. + + 2. You do not have to provide a copy of the Mini-XML license +with programs that are linked to the Mini-XML library, nor +do you have to identify the Mini-XML license in your +program or documentation as required by section 6 of the +LGPL2. + + + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ProcessHacker/mxml/config.h b/ProcessHacker/mxml/config.h new file mode 100644 index 0000000..1723d41 --- /dev/null +++ b/ProcessHacker/mxml/config.h @@ -0,0 +1,123 @@ +/* + * "$Id: config.h 451 2014-01-04 21:50:06Z msweet $" + * + * Configuration file for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Beginning with VC2005, Microsoft breaks ISO C and POSIX conformance + * by deprecating a number of functions in the name of security, even + * when many of the affected functions are otherwise completely secure. + * The _CRT_SECURE_NO_DEPRECATE definition ensures that we won't get + * warnings from their use... + * + * Then Microsoft decided that they should ignore this in VC2008 and use + * yet another define (_CRT_SECURE_NO_WARNINGS) instead. Bastards. + */ + +#define _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_WARNINGS + + +/* + * Include necessary headers... + */ + +#include +#include +#include +#include +#include +#include + + +/* + * Microsoft also renames the POSIX functions to _name, and introduces + * a broken compatibility layer using the original names. As a result, + * random crashes can occur when, for example, strdup() allocates memory + * from a different heap than used by malloc() and free(). + * + * To avoid moronic problems like this, we #define the POSIX function + * names to the corresponding non-standard Microsoft names. + */ + +#define close _close +#define open _open +#define read _read +#define snprintf _snprintf +#define strdup _strdup +#define vsnprintf _vsnprintf +#define write _write + + +/* + * Version number... + */ + +#define MXML_VERSION "Mini-XML v2.8" + + +/* + * Inline function support... + */ + +#define inline _inline + + +/* + * Long long support... + */ + +#define HAVE_LONG_LONG 1 + + +/* + * Do we have the snprintf() and vsnprintf() functions? + */ + +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 + + +/* + * Do we have the strXXX() functions? + */ + +#define HAVE_STRDUP 1 + + +/* + * Define prototypes for string functions as needed... + */ + +# ifndef HAVE_STRDUP +extern char *_mxml_strdup(const char *); +# define strdup _mxml_strdup +# endif /* !HAVE_STRDUP */ + +extern char *_mxml_strdupf(const char *, ...); +extern char *_mxml_vstrdupf(const char *, va_list); + +# ifndef HAVE_SNPRINTF +extern int _mxml_snprintf(char *, size_t, const char *, ...); +# define snprintf _mxml_snprintf +# endif /* !HAVE_SNPRINTF */ + +# ifndef HAVE_VSNPRINTF +extern int _mxml_vsnprintf(char *, size_t, const char *, va_list); +# define vsnprintf _mxml_vsnprintf +# endif /* !HAVE_VSNPRINTF */ + +/* + * End of "$Id: config.h 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-attr.c b/ProcessHacker/mxml/mxml-attr.c new file mode 100644 index 0000000..c01fa3e --- /dev/null +++ b/ProcessHacker/mxml/mxml-attr.c @@ -0,0 +1,315 @@ +/* + * "$Id: mxml-attr.c 451 2014-01-04 21:50:06Z msweet $" + * + * Attribute support code for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include +#include "config.h" +#include "mxml.h" + + +/* + * Local functions... + */ + +static int mxml_set_attr(mxml_node_t *node, const char *name, + char *value); + + +/* + * 'mxmlElementDeleteAttr()' - Delete an attribute. + * + * @since Mini-XML 2.4@ + */ + +void +mxmlElementDeleteAttr(mxml_node_t *node,/* I - Element */ + const char *name)/* I - Attribute name */ +{ + int i; /* Looping var */ + mxml_attr_t *attr; /* Cirrent attribute */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlElementDeleteAttr(node=%p, name=\"%s\")\n", + node, name ? name : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || !name) + return; + + /* + * Look for the attribute... + */ + + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; + i > 0; + i --, attr ++) + { +#ifdef DEBUG + fprintf(stderr, " %s=\"%s\"\n", attr->name, attr->value); +#endif /* DEBUG */ + + if (!strcmp(attr->name, name)) + { + /* + * Delete this attribute... + */ + + PhFree(attr->name); + PhFree(attr->value); + + i --; + if (i > 0) + memmove(attr, attr + 1, i * sizeof(mxml_attr_t)); + + node->value.element.num_attrs --; + + if (node->value.element.num_attrs == 0) + PhFree(node->value.element.attrs); + return; + } + } +} + + +/* + * 'mxmlElementGetAttr()' - Get an attribute. + * + * This function returns NULL if the node is not an element or the + * named attribute does not exist. + */ + +const char * /* O - Attribute value or NULL */ +mxmlElementGetAttr(mxml_node_t *node, /* I - Element node */ + const char *name) /* I - Name of attribute */ +{ + int i; /* Looping var */ + mxml_attr_t *attr; /* Cirrent attribute */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlElementGetAttr(node=%p, name=\"%s\")\n", + node, name ? name : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || !name) + return (NULL); + + /* + * Look for the attribute... + */ + + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; + i > 0; + i --, attr ++) + { +#ifdef DEBUG + fprintf(stderr, " %s=\"%s\"\n", attr->name, attr->value); +#endif /* DEBUG */ + + if (!strcmp(attr->name, name)) + { +#ifdef DEBUG + fprintf(stderr, " Returning \"%s\"!\n", attr->value); +#endif /* DEBUG */ + return (attr->value); + } + } + + /* + * Didn't find attribute, so return NULL... + */ + +#ifdef DEBUG + fputs(" Returning NULL!\n", stderr); +#endif /* DEBUG */ + + return (NULL); +} + + +/* + * 'mxmlElementSetAttr()' - Set an attribute. + * + * If the named attribute already exists, the value of the attribute + * is replaced by the new string value. The string value is copied + * into the element node. This function does nothing if the node is + * not an element. + */ + +void +mxmlElementSetAttr(mxml_node_t *node, /* I - Element node */ + const char *name, /* I - Name of attribute */ + const char *value) /* I - Attribute value */ +{ + char *valuec; /* Copy of value */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlElementSetAttr(node=%p, name=\"%s\", value=\"%s\")\n", + node, name ? name : "(null)", value ? value : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || !name) + return; + + if (value) + valuec = PhDuplicateBytesZSafe((char *)value); + else + valuec = NULL; + + if (mxml_set_attr(node, name, valuec)) + PhFree(valuec); +} + + +/* + * 'mxmlElementSetAttrf()' - Set an attribute with a formatted value. + * + * If the named attribute already exists, the value of the attribute + * is replaced by the new formatted string. The formatted string value is + * copied into the element node. This function does nothing if the node + * is not an element. + * + * @since Mini-XML 2.3@ + */ + +void +mxmlElementSetAttrf(mxml_node_t *node, /* I - Element node */ + const char *name, /* I - Name of attribute */ + const char *format,/* I - Printf-style attribute value */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Argument pointer */ + char *value; /* Value */ + + +#ifdef DEBUG + fprintf(stderr, + "mxmlElementSetAttrf(node=%p, name=\"%s\", format=\"%s\", ...)\n", + node, name ? name : "(null)", format ? format : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || !name || !format) + return; + + /* + * Format the value... + */ + + va_start(ap, format); + value = _mxml_vstrdupf(format, ap); + va_end(ap); + + if (!value) + mxml_error("Unable to allocate memory for attribute '%s' in element %s!", + name, node->value.element.name); + else if (mxml_set_attr(node, name, value)) + PhFree(value); +} + + +/* + * 'mxml_set_attr()' - Set or add an attribute name/value pair. + */ + +static int /* O - 0 on success, -1 on failure */ +mxml_set_attr(mxml_node_t *node, /* I - Element node */ + const char *name, /* I - Attribute name */ + char *value) /* I - Attribute value */ +{ + int i; /* Looping var */ + mxml_attr_t *attr; /* New attribute */ + + + /* + * Look for the attribute... + */ + + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; + i > 0; + i --, attr ++) + if (!strcmp(attr->name, name)) + { + /* + * Free the old value as needed... + */ + + if (attr->value) + PhFree(attr->value); + + attr->value = value; + + return (0); + } + + /* + * Add a new attribute... + */ + + if (node->value.element.num_attrs == 0) + attr = PhAllocateSafe(sizeof(mxml_attr_t)); + else + attr = PhReAllocateSafe(node->value.element.attrs, + (node->value.element.num_attrs + 1) * sizeof(mxml_attr_t)); + + if (!attr) + { + mxml_error("Unable to allocate memory for attribute '%s' in element %s!", + name, node->value.element.name); + return (-1); + } + + node->value.element.attrs = attr; + attr += node->value.element.num_attrs; + + if ((attr->name = PhDuplicateBytesZSafe((char *)name)) == NULL) + { + mxml_error("Unable to allocate memory for attribute '%s' in element %s!", + name, node->value.element.name); + return (-1); + } + + attr->value = value; + + node->value.element.num_attrs ++; + + return (0); +} + + +/* + * End of "$Id: mxml-attr.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-entity.c b/ProcessHacker/mxml/mxml-entity.c new file mode 100644 index 0000000..b32e8a9 --- /dev/null +++ b/ProcessHacker/mxml/mxml-entity.c @@ -0,0 +1,449 @@ +/* + * "$Id: mxml-entity.c 451 2014-01-04 21:50:06Z msweet $" + * + * Character entity support code for Mini-XML, a small XML-like + * file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include "mxml-private.h" + + +/* + * 'mxmlEntityAddCallback()' - Add a callback to convert entities to Unicode. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlEntityAddCallback( + mxml_entity_cb_t cb) /* I - Callback function to add */ +{ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + if (global->num_entity_cbs < (int)(sizeof(global->entity_cbs) / sizeof(global->entity_cbs[0]))) + { + global->entity_cbs[global->num_entity_cbs] = cb; + global->num_entity_cbs ++; + + return (0); + } + else + { + mxml_error("Unable to add entity callback!"); + + return (-1); + } +} + + +/* + * 'mxmlEntityGetName()' - Get the name that corresponds to the character value. + * + * If val does not need to be represented by a named entity, NULL is returned. + */ + +const char * /* O - Entity name or NULL */ +mxmlEntityGetName(int val) /* I - Character value */ +{ + switch (val) + { + case '&' : + return ("amp"); + + case '<' : + return ("lt"); + + case '>' : + return ("gt"); + + case '\"' : + return ("quot"); + + default : + return (NULL); + } +} + + +/* + * 'mxmlEntityGetValue()' - Get the character corresponding to a named entity. + * + * The entity name can also be a numeric constant. -1 is returned if the + * name is not known. + */ + +int /* O - Character value or -1 on error */ +mxmlEntityGetValue(const char *name) /* I - Entity name */ +{ + int i; /* Looping var */ + int ch; /* Character value */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + for (i = 0; i < global->num_entity_cbs; i ++) + if ((ch = (global->entity_cbs[i])(name)) >= 0) + return (ch); + + return (-1); +} + + +/* + * 'mxmlEntityRemoveCallback()' - Remove a callback. + */ + +void +mxmlEntityRemoveCallback( + mxml_entity_cb_t cb) /* I - Callback function to remove */ +{ + int i; /* Looping var */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + for (i = 0; i < global->num_entity_cbs; i ++) + if (cb == global->entity_cbs[i]) + { + /* + * Remove the callback... + */ + + global->num_entity_cbs --; + + if (i < global->num_entity_cbs) + memmove(global->entity_cbs + i, global->entity_cbs + i + 1, + (global->num_entity_cbs - i) * sizeof(global->entity_cbs[0])); + + return; + } +} + + +/* + * '_mxml_entity_cb()' - Lookup standard (X)HTML entities. + */ + +int /* O - Unicode value or -1 */ +_mxml_entity_cb(const char *name) /* I - Entity name */ +{ + int diff, /* Difference between names */ + current, /* Current entity in search */ + first, /* First entity in search */ + last; /* Last entity in search */ + static const struct + { + const char *name; /* Entity name */ + int val; /* Character value */ + } entities[] = + { + { "AElig", 198 }, + { "Aacute", 193 }, + { "Acirc", 194 }, + { "Agrave", 192 }, + { "Alpha", 913 }, + { "Aring", 197 }, + { "Atilde", 195 }, + { "Auml", 196 }, + { "Beta", 914 }, + { "Ccedil", 199 }, + { "Chi", 935 }, + { "Dagger", 8225 }, + { "Delta", 916 }, + { "Dstrok", 208 }, + { "ETH", 208 }, + { "Eacute", 201 }, + { "Ecirc", 202 }, + { "Egrave", 200 }, + { "Epsilon", 917 }, + { "Eta", 919 }, + { "Euml", 203 }, + { "Gamma", 915 }, + { "Iacute", 205 }, + { "Icirc", 206 }, + { "Igrave", 204 }, + { "Iota", 921 }, + { "Iuml", 207 }, + { "Kappa", 922 }, + { "Lambda", 923 }, + { "Mu", 924 }, + { "Ntilde", 209 }, + { "Nu", 925 }, + { "OElig", 338 }, + { "Oacute", 211 }, + { "Ocirc", 212 }, + { "Ograve", 210 }, + { "Omega", 937 }, + { "Omicron", 927 }, + { "Oslash", 216 }, + { "Otilde", 213 }, + { "Ouml", 214 }, + { "Phi", 934 }, + { "Pi", 928 }, + { "Prime", 8243 }, + { "Psi", 936 }, + { "Rho", 929 }, + { "Scaron", 352 }, + { "Sigma", 931 }, + { "THORN", 222 }, + { "Tau", 932 }, + { "Theta", 920 }, + { "Uacute", 218 }, + { "Ucirc", 219 }, + { "Ugrave", 217 }, + { "Upsilon", 933 }, + { "Uuml", 220 }, + { "Xi", 926 }, + { "Yacute", 221 }, + { "Yuml", 376 }, + { "Zeta", 918 }, + { "aacute", 225 }, + { "acirc", 226 }, + { "acute", 180 }, + { "aelig", 230 }, + { "agrave", 224 }, + { "alefsym", 8501 }, + { "alpha", 945 }, + { "amp", '&' }, + { "and", 8743 }, + { "ang", 8736 }, + { "apos", '\'' }, + { "aring", 229 }, + { "asymp", 8776 }, + { "atilde", 227 }, + { "auml", 228 }, + { "bdquo", 8222 }, + { "beta", 946 }, + { "brkbar", 166 }, + { "brvbar", 166 }, + { "bull", 8226 }, + { "cap", 8745 }, + { "ccedil", 231 }, + { "cedil", 184 }, + { "cent", 162 }, + { "chi", 967 }, + { "circ", 710 }, + { "clubs", 9827 }, + { "cong", 8773 }, + { "copy", 169 }, + { "crarr", 8629 }, + { "cup", 8746 }, + { "curren", 164 }, + { "dArr", 8659 }, + { "dagger", 8224 }, + { "darr", 8595 }, + { "deg", 176 }, + { "delta", 948 }, + { "diams", 9830 }, + { "die", 168 }, + { "divide", 247 }, + { "eacute", 233 }, + { "ecirc", 234 }, + { "egrave", 232 }, + { "empty", 8709 }, + { "emsp", 8195 }, + { "ensp", 8194 }, + { "epsilon", 949 }, + { "equiv", 8801 }, + { "eta", 951 }, + { "eth", 240 }, + { "euml", 235 }, + { "euro", 8364 }, + { "exist", 8707 }, + { "fnof", 402 }, + { "forall", 8704 }, + { "frac12", 189 }, + { "frac14", 188 }, + { "frac34", 190 }, + { "frasl", 8260 }, + { "gamma", 947 }, + { "ge", 8805 }, + { "gt", '>' }, + { "hArr", 8660 }, + { "harr", 8596 }, + { "hearts", 9829 }, + { "hellip", 8230 }, + { "hibar", 175 }, + { "iacute", 237 }, + { "icirc", 238 }, + { "iexcl", 161 }, + { "igrave", 236 }, + { "image", 8465 }, + { "infin", 8734 }, + { "int", 8747 }, + { "iota", 953 }, + { "iquest", 191 }, + { "isin", 8712 }, + { "iuml", 239 }, + { "kappa", 954 }, + { "lArr", 8656 }, + { "lambda", 955 }, + { "lang", 9001 }, + { "laquo", 171 }, + { "larr", 8592 }, + { "lceil", 8968 }, + { "ldquo", 8220 }, + { "le", 8804 }, + { "lfloor", 8970 }, + { "lowast", 8727 }, + { "loz", 9674 }, + { "lrm", 8206 }, + { "lsaquo", 8249 }, + { "lsquo", 8216 }, + { "lt", '<' }, + { "macr", 175 }, + { "mdash", 8212 }, + { "micro", 181 }, + { "middot", 183 }, + { "minus", 8722 }, + { "mu", 956 }, + { "nabla", 8711 }, + { "nbsp", 160 }, + { "ndash", 8211 }, + { "ne", 8800 }, + { "ni", 8715 }, + { "not", 172 }, + { "notin", 8713 }, + { "nsub", 8836 }, + { "ntilde", 241 }, + { "nu", 957 }, + { "oacute", 243 }, + { "ocirc", 244 }, + { "oelig", 339 }, + { "ograve", 242 }, + { "oline", 8254 }, + { "omega", 969 }, + { "omicron", 959 }, + { "oplus", 8853 }, + { "or", 8744 }, + { "ordf", 170 }, + { "ordm", 186 }, + { "oslash", 248 }, + { "otilde", 245 }, + { "otimes", 8855 }, + { "ouml", 246 }, + { "para", 182 }, + { "part", 8706 }, + { "permil", 8240 }, + { "perp", 8869 }, + { "phi", 966 }, + { "pi", 960 }, + { "piv", 982 }, + { "plusmn", 177 }, + { "pound", 163 }, + { "prime", 8242 }, + { "prod", 8719 }, + { "prop", 8733 }, + { "psi", 968 }, + { "quot", '\"' }, + { "rArr", 8658 }, + { "radic", 8730 }, + { "rang", 9002 }, + { "raquo", 187 }, + { "rarr", 8594 }, + { "rceil", 8969 }, + { "rdquo", 8221 }, + { "real", 8476 }, + { "reg", 174 }, + { "rfloor", 8971 }, + { "rho", 961 }, + { "rlm", 8207 }, + { "rsaquo", 8250 }, + { "rsquo", 8217 }, + { "sbquo", 8218 }, + { "scaron", 353 }, + { "sdot", 8901 }, + { "sect", 167 }, + { "shy", 173 }, + { "sigma", 963 }, + { "sigmaf", 962 }, + { "sim", 8764 }, + { "spades", 9824 }, + { "sub", 8834 }, + { "sube", 8838 }, + { "sum", 8721 }, + { "sup", 8835 }, + { "sup1", 185 }, + { "sup2", 178 }, + { "sup3", 179 }, + { "supe", 8839 }, + { "szlig", 223 }, + { "tau", 964 }, + { "there4", 8756 }, + { "theta", 952 }, + { "thetasym", 977 }, + { "thinsp", 8201 }, + { "thorn", 254 }, + { "tilde", 732 }, + { "times", 215 }, + { "trade", 8482 }, + { "uArr", 8657 }, + { "uacute", 250 }, + { "uarr", 8593 }, + { "ucirc", 251 }, + { "ugrave", 249 }, + { "uml", 168 }, + { "upsih", 978 }, + { "upsilon", 965 }, + { "uuml", 252 }, + { "weierp", 8472 }, + { "xi", 958 }, + { "yacute", 253 }, + { "yen", 165 }, + { "yuml", 255 }, + { "zeta", 950 }, + { "zwj", 8205 }, + { "zwnj", 8204 } + }; + + + /* + * Do a binary search for the named entity... + */ + + first = 0; + last = (int)(sizeof(entities) / sizeof(entities[0]) - 1); + + while ((last - first) > 1) + { + current = (first + last) / 2; + + if ((diff = strcmp(name, entities[current].name)) == 0) + return (entities[current].val); + else if (diff < 0) + last = current; + else + first = current; + } + + /* + * If we get here, there is a small chance that there is still + * a match; check first and last... + */ + + if (!strcmp(name, entities[first].name)) + return (entities[first].val); + else if (!strcmp(name, entities[last].name)) + return (entities[last].val); + else + return (-1); +} + + +/* + * End of "$Id: mxml-entity.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-file.c b/ProcessHacker/mxml/mxml-file.c new file mode 100644 index 0000000..87f40f4 --- /dev/null +++ b/ProcessHacker/mxml/mxml-file.c @@ -0,0 +1,3017 @@ +/* + * "$Id: mxml-file.c 459 2014-10-19 17:21:48Z msweet $" + * + * File loading code for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include +#ifndef WIN32 +# include +#endif /* !WIN32 */ +#include "mxml-private.h" + + +/* + * Character encoding... + */ + +#define ENCODE_UTF8 0 /* UTF-8 */ +#define ENCODE_UTF16BE 1 /* UTF-16 Big-Endian */ +#define ENCODE_UTF16LE 2 /* UTF-16 Little-Endian */ + + +/* + * Macro to test for a bad XML character... + */ + +#define mxml_bad_char(ch) ((ch) < ' ' && (ch) != '\n' && (ch) != '\r' && (ch) != '\t') + + +/* + * Types and structures... + */ + +typedef int (*_mxml_getc_cb_t)(void *, int *); +typedef int (*_mxml_putc_cb_t)(int, void *); + +typedef struct _mxml_fdbuf_s /**** File descriptor buffer ****/ +{ + HANDLE fd; /* File descriptor */ + unsigned char *current, /* Current position in buffer */ + *end, /* End of buffer */ + buffer[8192]; /* Character buffer */ +} _mxml_fdbuf_t; + + +/* + * Local functions... + */ + +static int mxml_add_char(int ch, char **ptr, char **buffer, + int *bufsize); +static int mxml_fd_getc(void *p, int *encoding); +static int mxml_fd_putc(int ch, void *p); +static int mxml_fd_read(_mxml_fdbuf_t *buf); +static int mxml_fd_write(_mxml_fdbuf_t *buf); +static int mxml_file_getc(void *p, int *encoding); +static int mxml_file_putc(int ch, void *p); +static int mxml_get_entity(mxml_node_t *parent, void *p, + int *encoding, + _mxml_getc_cb_t getc_cb); +static inline int mxml_isspace(int ch) + { + return (ch == ' ' || ch == '\t' || ch == '\r' || + ch == '\n'); + } +static mxml_node_t *mxml_load_data(mxml_node_t *top, void *p, + mxml_load_cb_t cb, + _mxml_getc_cb_t getc_cb, + mxml_sax_cb_t sax_cb, void *sax_data); +static int mxml_parse_element(mxml_node_t *node, void *p, + int *encoding, + _mxml_getc_cb_t getc_cb); +static int mxml_string_getc(void *p, int *encoding); +static int mxml_string_putc(int ch, void *p); +static int mxml_write_name(const char *s, void *p, + _mxml_putc_cb_t putc_cb); +static int mxml_write_node(mxml_node_t *node, void *p, + mxml_save_cb_t cb, int col, + _mxml_putc_cb_t putc_cb, + _mxml_global_t *global); +static int mxml_write_string(const char *s, void *p, + _mxml_putc_cb_t putc_cb); +static int mxml_write_ws(mxml_node_t *node, void *p, + mxml_save_cb_t cb, int ws, + int col, _mxml_putc_cb_t putc_cb); + + +/* + * 'mxmlLoadFd()' - Load a file descriptor into an XML node tree. + * + * The nodes in the specified file are added to the specified top node. + * If no top node is provided, the XML file MUST be well-formed with a + * single parent node like for the entire file. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + */ + +mxml_node_t * /* O - First node or NULL if the file could not be read. */ +mxmlLoadFd(mxml_node_t *top, /* I - Top node */ + HANDLE fd, /* I - File descriptor to read from */ + mxml_load_cb_t cb) /* I - Callback function or MXML_NO_CALLBACK */ +{ + _mxml_fdbuf_t buf; /* File descriptor buffer */ + + + /* + * Initialize the file descriptor buffer... + */ + + buf.fd = fd; + buf.current = buf.buffer; + buf.end = buf.buffer; + + /* + * Read the XML data... + */ + + return (mxml_load_data(top, &buf, cb, mxml_fd_getc, MXML_NO_CALLBACK, NULL)); +} + + +/* + * 'mxmlLoadFile()' - Load a file into an XML node tree. + * + * The nodes in the specified file are added to the specified top node. + * If no top node is provided, the XML file MUST be well-formed with a + * single parent node like for the entire file. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + */ + +mxml_node_t * /* O - First node or NULL if the file could not be read. */ +mxmlLoadFile(mxml_node_t *top, /* I - Top node */ + FILE *fp, /* I - File to read from */ + mxml_load_cb_t cb) /* I - Callback function or MXML_NO_CALLBACK */ +{ + /* + * Read the XML data... + */ + + return (mxml_load_data(top, fp, cb, mxml_file_getc, MXML_NO_CALLBACK, NULL)); +} + + +/* + * 'mxmlLoadString()' - Load a string into an XML node tree. + * + * The nodes in the specified string are added to the specified top node. + * If no top node is provided, the XML string MUST be well-formed with a + * single parent node like for the entire string. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + */ + +mxml_node_t * /* O - First node or NULL if the string has errors. */ +mxmlLoadString(mxml_node_t *top, /* I - Top node */ + const char *s, /* I - String to load */ + mxml_load_cb_t cb) /* I - Callback function or MXML_NO_CALLBACK */ +{ + /* + * Read the XML data... + */ + + return (mxml_load_data(top, (void *)&s, cb, mxml_string_getc, MXML_NO_CALLBACK, + NULL)); +} + + +/* + * 'mxmlSaveAllocString()' - Save an XML tree to an allocated string. + * + * This function returns a pointer to a string containing the textual + * representation of the XML node tree. The string should be freed + * using the free() function when you are done with it. NULL is returned + * if the node would produce an empty string or if the string cannot be + * allocated. + * + * The callback argument specifies a function that returns a whitespace + * string or NULL before and after each element. If MXML_NO_CALLBACK + * is specified, whitespace will only be added before MXML_TEXT nodes + * with leading whitespace and before attribute names inside opening + * element tags. + */ + +char * /* O - Allocated string or NULL */ +mxmlSaveAllocString( + mxml_node_t *node, /* I - Node to write */ + mxml_save_cb_t cb) /* I - Whitespace callback or MXML_NO_CALLBACK */ +{ + int bytes; /* Required bytes */ + char buffer[8192]; /* Temporary buffer */ + char *s; /* Allocated string */ + + + /* + * Write the node to the temporary buffer... + */ + + bytes = mxmlSaveString(node, buffer, sizeof(buffer), cb); + + if (bytes <= 0) + return (NULL); + + if (bytes < (int)(sizeof(buffer) - 1)) + { + /* + * Node fit inside the buffer, so just duplicate that string and + * return... + */ + + return (PhDuplicateBytesZSafe(buffer)); + } + + /* + * Allocate a buffer of the required size and save the node to the + * new buffer... + */ + + if ((s = PhAllocateSafe(bytes + 1)) == NULL) + return (NULL); + + mxmlSaveString(node, s, bytes + 1, cb); + + /* + * Return the allocated string... + */ + + return (s); +} + + +/* + * 'mxmlSaveFd()' - Save an XML tree to a file descriptor. + * + * The callback argument specifies a function that returns a whitespace + * string or NULL before and after each element. If MXML_NO_CALLBACK + * is specified, whitespace will only be added before MXML_TEXT nodes + * with leading whitespace and before attribute names inside opening + * element tags. + */ + +int /* O - 0 on success, -1 on error. */ +mxmlSaveFd(mxml_node_t *node, /* I - Node to write */ + HANDLE fd, /* I - File descriptor to write to */ + mxml_save_cb_t cb) /* I - Whitespace callback or MXML_NO_CALLBACK */ +{ + int col; /* Final column */ + _mxml_fdbuf_t buf; /* File descriptor buffer */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + /* + * Initialize the file descriptor buffer... + */ + + buf.fd = fd; + buf.current = buf.buffer; + buf.end = buf.buffer + sizeof(buf.buffer); + + /* + * Write the node... + */ + + if ((col = mxml_write_node(node, &buf, cb, 0, mxml_fd_putc, global)) < 0) + return (-1); + + if (col > 0) + if (mxml_fd_putc('\n', &buf) < 0) + return (-1); + + /* + * Flush and return... + */ + + return (mxml_fd_write(&buf)); +} + + +/* + * 'mxmlSaveFile()' - Save an XML tree to a file. + * + * The callback argument specifies a function that returns a whitespace + * string or NULL before and after each element. If MXML_NO_CALLBACK + * is specified, whitespace will only be added before MXML_TEXT nodes + * with leading whitespace and before attribute names inside opening + * element tags. + */ + +int /* O - 0 on success, -1 on error. */ +mxmlSaveFile(mxml_node_t *node, /* I - Node to write */ + FILE *fp, /* I - File to write to */ + mxml_save_cb_t cb) /* I - Whitespace callback or MXML_NO_CALLBACK */ +{ + int col; /* Final column */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + /* + * Write the node... + */ + + if ((col = mxml_write_node(node, fp, cb, 0, mxml_file_putc, global)) < 0) + return (-1); + + if (col > 0) + if (putc('\n', fp) < 0) + return (-1); + + /* + * Return 0 (success)... + */ + + return (0); +} + + +/* + * 'mxmlSaveString()' - Save an XML node tree to a string. + * + * This function returns the total number of bytes that would be + * required for the string but only copies (bufsize - 1) characters + * into the specified buffer. + * + * The callback argument specifies a function that returns a whitespace + * string or NULL before and after each element. If MXML_NO_CALLBACK + * is specified, whitespace will only be added before MXML_TEXT nodes + * with leading whitespace and before attribute names inside opening + * element tags. + */ + +int /* O - Size of string */ +mxmlSaveString(mxml_node_t *node, /* I - Node to write */ + char *buffer, /* I - String buffer */ + int bufsize, /* I - Size of string buffer */ + mxml_save_cb_t cb) /* I - Whitespace callback or MXML_NO_CALLBACK */ +{ + int col; /* Final column */ + char *ptr[2]; /* Pointers for putc_cb */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + /* + * Write the node... + */ + + ptr[0] = buffer; + ptr[1] = buffer + bufsize; + + if ((col = mxml_write_node(node, ptr, cb, 0, mxml_string_putc, global)) < 0) + return (-1); + + if (col > 0) + mxml_string_putc('\n', ptr); + + /* + * Nul-terminate the buffer... + */ + + if (ptr[0] >= ptr[1]) + buffer[bufsize - 1] = '\0'; + else + ptr[0][0] = '\0'; + + /* + * Return the number of characters... + */ + + return (int)(ptr[0] - buffer); +} + + +/* + * 'mxmlSAXLoadFd()' - Load a file descriptor into an XML node tree + * using a SAX callback. + * + * The nodes in the specified file are added to the specified top node. + * If no top node is provided, the XML file MUST be well-formed with a + * single parent node like for the entire file. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + * + * The SAX callback must call mxmlRetain() for any nodes that need to + * be kept for later use. Otherwise, nodes are deleted when the parent + * node is closed or after each data, comment, CDATA, or directive node. + * + * @since Mini-XML 2.3@ + */ + +mxml_node_t * /* O - First node or NULL if the file could not be read. */ +mxmlSAXLoadFd(mxml_node_t *top, /* I - Top node */ + HANDLE fd, /* I - File descriptor to read from */ + mxml_load_cb_t cb, /* I - Callback function or MXML_NO_CALLBACK */ + mxml_sax_cb_t sax_cb, /* I - SAX callback or MXML_NO_CALLBACK */ + void *sax_data) /* I - SAX user data */ +{ + _mxml_fdbuf_t buf; /* File descriptor buffer */ + + + /* + * Initialize the file descriptor buffer... + */ + + buf.fd = fd; + buf.current = buf.buffer; + buf.end = buf.buffer; + + /* + * Read the XML data... + */ + + return (mxml_load_data(top, &buf, cb, mxml_fd_getc, sax_cb, sax_data)); +} + + +/* + * 'mxmlSAXLoadFile()' - Load a file into an XML node tree + * using a SAX callback. + * + * The nodes in the specified file are added to the specified top node. + * If no top node is provided, the XML file MUST be well-formed with a + * single parent node like for the entire file. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + * + * The SAX callback must call mxmlRetain() for any nodes that need to + * be kept for later use. Otherwise, nodes are deleted when the parent + * node is closed or after each data, comment, CDATA, or directive node. + * + * @since Mini-XML 2.3@ + */ + +mxml_node_t * /* O - First node or NULL if the file could not be read. */ +mxmlSAXLoadFile( + mxml_node_t *top, /* I - Top node */ + FILE *fp, /* I - File to read from */ + mxml_load_cb_t cb, /* I - Callback function or MXML_NO_CALLBACK */ + mxml_sax_cb_t sax_cb, /* I - SAX callback or MXML_NO_CALLBACK */ + void *sax_data) /* I - SAX user data */ +{ + /* + * Read the XML data... + */ + + return (mxml_load_data(top, fp, cb, mxml_file_getc, sax_cb, sax_data)); +} + + +/* + * 'mxmlSAXLoadString()' - Load a string into an XML node tree + * using a SAX callback. + * + * The nodes in the specified string are added to the specified top node. + * If no top node is provided, the XML string MUST be well-formed with a + * single parent node like for the entire string. The callback + * function returns the value type that should be used for child nodes. + * If MXML_NO_CALLBACK is specified then all child nodes will be either + * MXML_ELEMENT or MXML_TEXT nodes. + * + * The constants MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, + * MXML_REAL_CALLBACK, and MXML_TEXT_CALLBACK are defined for loading + * child nodes of the specified type. + * + * The SAX callback must call mxmlRetain() for any nodes that need to + * be kept for later use. Otherwise, nodes are deleted when the parent + * node is closed or after each data, comment, CDATA, or directive node. + * + * @since Mini-XML 2.3@ + */ + +mxml_node_t * /* O - First node or NULL if the string has errors. */ +mxmlSAXLoadString( + mxml_node_t *top, /* I - Top node */ + const char *s, /* I - String to load */ + mxml_load_cb_t cb, /* I - Callback function or MXML_NO_CALLBACK */ + mxml_sax_cb_t sax_cb, /* I - SAX callback or MXML_NO_CALLBACK */ + void *sax_data) /* I - SAX user data */ +{ + /* + * Read the XML data... + */ + + return (mxml_load_data(top, (void *)&s, cb, mxml_string_getc, sax_cb, sax_data)); +} + + +/* + * 'mxmlSetCustomHandlers()' - Set the handling functions for custom data. + * + * The load function accepts a node pointer and a data string and must + * return 0 on success and non-zero on error. + * + * The save function accepts a node pointer and must return a malloc'd + * string on success and NULL on error. + * + */ + +void +mxmlSetCustomHandlers( + mxml_custom_load_cb_t load, /* I - Load function */ + mxml_custom_save_cb_t save) /* I - Save function */ +{ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + global->custom_load_cb = load; + global->custom_save_cb = save; +} + + +/* + * 'mxmlSetErrorCallback()' - Set the error message callback. + */ + +void +mxmlSetErrorCallback(mxml_error_cb_t cb)/* I - Error callback function */ +{ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + global->error_cb = cb; +} + + +/* + * 'mxmlSetWrapMargin()' - Set the wrap margin when saving XML data. + * + * Wrapping is disabled when "column" is 0. + * + * @since Mini-XML 2.3@ + */ + +void +mxmlSetWrapMargin(int column) /* I - Column for wrapping, 0 to disable wrapping */ +{ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + global->wrap = column; +} + + +/* + * 'mxml_add_char()' - Add a character to a buffer, expanding as needed. + */ + +static int /* O - 0 on success, -1 on error */ +mxml_add_char(int ch, /* I - Character to add */ + char **bufptr, /* IO - Current position in buffer */ + char **buffer, /* IO - Current buffer */ + int *bufsize) /* IO - Current buffer size */ +{ + char *newbuffer; /* New buffer value */ + + + if (*bufptr >= (*buffer + *bufsize - 4)) + { + /* + * Increase the size of the buffer... + */ + + if (*bufsize < 1024) + (*bufsize) *= 2; + else + (*bufsize) += 1024; + + if ((newbuffer = PhReAllocateSafe(*buffer, *bufsize)) == NULL) + { + PhFree(*buffer); + + mxml_error("Unable to expand string buffer to %d bytes!", *bufsize); + + return (-1); + } + + *bufptr = newbuffer + (*bufptr - *buffer); + *buffer = newbuffer; + } + + if (ch < 0x80) + { + /* + * Single byte ASCII... + */ + + *(*bufptr)++ = ch; + } + else if (ch < 0x800) + { + /* + * Two-byte UTF-8... + */ + + *(*bufptr)++ = 0xc0 | (ch >> 6); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + else if (ch < 0x10000) + { + /* + * Three-byte UTF-8... + */ + + *(*bufptr)++ = 0xe0 | (ch >> 12); + *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + else + { + /* + * Four-byte UTF-8... + */ + + *(*bufptr)++ = 0xf0 | (ch >> 18); + *(*bufptr)++ = 0x80 | ((ch >> 12) & 0x3f); + *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f); + *(*bufptr)++ = 0x80 | (ch & 0x3f); + } + + return (0); +} + + +/* + * 'mxml_fd_getc()' - Read a character from a file descriptor. + */ + +static int /* O - Character or EOF */ +mxml_fd_getc(void *p, /* I - File descriptor buffer */ + int *encoding) /* IO - Encoding */ +{ + _mxml_fdbuf_t *buf; /* File descriptor buffer */ + int ch, /* Current character */ + temp; /* Temporary character */ + + + /* + * Grab the next character in the buffer... + */ + + buf = (_mxml_fdbuf_t *)p; + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + ch = *(buf->current)++; + + switch (*encoding) + { + case ENCODE_UTF8 : + /* + * Got a UTF-8 character; convert UTF-8 to Unicode and return... + */ + + if (!(ch & 0x80)) + { +#if DEBUG > 1 + printf("mxml_fd_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + + return (ch); + } + else if (ch == 0xfe) + { + /* + * UTF-16 big-endian BOM? + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + ch = *(buf->current)++; + + if (ch != 0xff) + return (EOF); + + *encoding = ENCODE_UTF16BE; + + return (mxml_fd_getc(p, encoding)); + } + else if (ch == 0xff) + { + /* + * UTF-16 little-endian BOM? + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + ch = *(buf->current)++; + + if (ch != 0xfe) + return (EOF); + + *encoding = ENCODE_UTF16LE; + + return (mxml_fd_getc(p, encoding)); + } + else if ((ch & 0xe0) == 0xc0) + { + /* + * Two-byte value... + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x1f) << 6) | (temp & 0x3f); + + if (ch < 0x80) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + } + else if ((ch & 0xf0) == 0xe0) + { + /* + * Three-byte value... + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x0f) << 6) | (temp & 0x3f); + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if (ch < 0x800) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + + /* + * Ignore (strip) Byte Order Mark (BOM)... + */ + + if (ch == 0xfeff) + return (mxml_fd_getc(p, encoding)); + } + else if ((ch & 0xf8) == 0xf0) + { + /* + * Four-byte value... + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x07) << 6) | (temp & 0x3f); + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + if ((temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if (ch < 0x10000) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + } + else + return (EOF); + break; + + case ENCODE_UTF16BE : + /* + * Read UTF-16 big-endian char... + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + ch = (ch << 8) | temp; + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch; + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + lch = *(buf->current)++; + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + lch = (lch << 8) | temp; + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + + case ENCODE_UTF16LE : + /* + * Read UTF-16 little-endian char... + */ + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + ch |= (temp << 8); + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch; + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + lch = *(buf->current)++; + + if (buf->current >= buf->end) + if (mxml_fd_read(buf) < 0) + return (EOF); + + temp = *(buf->current)++; + + lch |= (temp << 8); + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + } + +#if DEBUG > 1 + printf("mxml_fd_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); +} + + +/* + * 'mxml_fd_putc()' - Write a character to a file descriptor. + */ + +static int /* O - 0 on success, -1 on error */ +mxml_fd_putc(int ch, /* I - Character */ + void *p) /* I - File descriptor buffer */ +{ + _mxml_fdbuf_t *buf; /* File descriptor buffer */ + + + /* + * Flush the write buffer as needed... + */ + + buf = (_mxml_fdbuf_t *)p; + + if (buf->current >= buf->end) + if (mxml_fd_write(buf) < 0) + return (-1); + + *(buf->current)++ = ch; + + /* + * Return successfully... + */ + + return (0); +} + + +/* + * 'mxml_fd_read()' - Read a buffer of data from a file descriptor. + */ + /* wj32: modified to use file handles */ + +static int /* O - 0 on success, -1 on error */ +mxml_fd_read(_mxml_fdbuf_t *buf) /* I - File descriptor buffer */ +{ + IO_STATUS_BLOCK isb; + + if (!buf) + return -1; + + if (!NT_SUCCESS(NtReadFile(buf->fd, NULL, NULL, NULL, &isb, buf->buffer, sizeof(buf->buffer), NULL, NULL))) + return -1; + + if (isb.Information == 0) // check bytes read + return -1; + + buf->current = buf->buffer; + buf->end = buf->buffer + isb.Information; + + return 0; +} + + +/* + * 'mxml_fd_write()' - Write a buffer of data to a file descriptor. + */ + /* wj32: modified to use file handles */ + +static int /* O - 0 on success, -1 on error */ +mxml_fd_write(_mxml_fdbuf_t *buf) /* I - File descriptor buffer */ +{ + IO_STATUS_BLOCK isb; + + if (!buf) + return 1; + + if (buf->current == buf->buffer) + return 0; + + if (!NT_SUCCESS(NtWriteFile(buf->fd, NULL, NULL, NULL, &isb, buf->buffer, (ULONG)(buf->current - buf->buffer), NULL, NULL))) + return -1; + + buf->current = buf->buffer; + + return 0; +} + + +/* + * 'mxml_file_getc()' - Get a character from a file. + */ + +static int /* O - Character or EOF */ +mxml_file_getc(void *p, /* I - Pointer to file */ + int *encoding) /* IO - Encoding */ +{ + int ch, /* Character from file */ + temp; /* Temporary character */ + FILE *fp; /* Pointer to file */ + + + /* + * Read a character from the file and see if it is EOF or ASCII... + */ + + fp = (FILE *)p; + ch = getc(fp); + + if (ch == EOF) + return (EOF); + + switch (*encoding) + { + case ENCODE_UTF8 : + /* + * Got a UTF-8 character; convert UTF-8 to Unicode and return... + */ + + if (!(ch & 0x80)) + { + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + +#if DEBUG > 1 + printf("mxml_file_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + } + else if (ch == 0xfe) + { + /* + * UTF-16 big-endian BOM? + */ + + ch = getc(fp); + if (ch != 0xff) + return (EOF); + + *encoding = ENCODE_UTF16BE; + + return (mxml_file_getc(p, encoding)); + } + else if (ch == 0xff) + { + /* + * UTF-16 little-endian BOM? + */ + + ch = getc(fp); + if (ch != 0xfe) + return (EOF); + + *encoding = ENCODE_UTF16LE; + + return (mxml_file_getc(p, encoding)); + } + else if ((ch & 0xe0) == 0xc0) + { + /* + * Two-byte value... + */ + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x1f) << 6) | (temp & 0x3f); + + if (ch < 0x80) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + } + else if ((ch & 0xf0) == 0xe0) + { + /* + * Three-byte value... + */ + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x0f) << 6) | (temp & 0x3f); + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if (ch < 0x800) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + + /* + * Ignore (strip) Byte Order Mark (BOM)... + */ + + if (ch == 0xfeff) + return (mxml_file_getc(p, encoding)); + } + else if ((ch & 0xf8) == 0xf0) + { + /* + * Four-byte value... + */ + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x07) << 6) | (temp & 0x3f); + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if ((temp = getc(fp)) == EOF || (temp & 0xc0) != 0x80) + return (EOF); + + ch = (ch << 6) | (temp & 0x3f); + + if (ch < 0x10000) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + } + else + return (EOF); + break; + + case ENCODE_UTF16BE : + /* + * Read UTF-16 big-endian char... + */ + + ch = (ch << 8) | getc(fp); + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch = (getc(fp) << 8) | getc(fp); + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + + case ENCODE_UTF16LE : + /* + * Read UTF-16 little-endian char... + */ + + ch |= (getc(fp) << 8); + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch = getc(fp) | (getc(fp) << 8); + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + break; + } + +#if DEBUG > 1 + printf("mxml_file_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); +} + + +/* + * 'mxml_file_putc()' - Write a character to a file. + */ + +static int /* O - 0 on success, -1 on failure */ +mxml_file_putc(int ch, /* I - Character to write */ + void *p) /* I - Pointer to file */ +{ + return (putc(ch, (FILE *)p) == EOF ? -1 : 0); +} + + +/* + * 'mxml_get_entity()' - Get the character corresponding to an entity... + */ + +static int /* O - Character value or EOF on error */ +mxml_get_entity(mxml_node_t *parent, /* I - Parent node */ + void *p, /* I - Pointer to source */ + int *encoding, /* IO - Character encoding */ + int (*getc_cb)(void *, int *)) + /* I - Get character function */ +{ + int ch; /* Current character */ + char entity[64], /* Entity string */ + *entptr; /* Pointer into entity */ + + + entptr = entity; + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + if (ch > 126 || (!isalnum(ch) && ch != '#')) + break; + else if (entptr < (entity + sizeof(entity) - 1)) + *entptr++ = ch; + else + { + mxml_error("Entity name too long under parent <%s>!", + parent ? parent->value.element.name : "null"); + break; + } + + *entptr = '\0'; + + if (ch != ';') + { + mxml_error("Character entity \"%s\" not terminated under parent <%s>!", + entity, parent ? parent->value.element.name : "null"); + return (EOF); + } + + if (entity[0] == '#') + { + if (entity[1] == 'x') + ch = strtol(entity + 2, NULL, 16); + else + ch = strtol(entity + 1, NULL, 10); + } + else if ((ch = mxmlEntityGetValue(entity)) < 0) + mxml_error("Entity name \"%s;\" not supported under parent <%s>!", + entity, parent ? parent->value.element.name : "null"); + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x under parent <%s> not allowed by XML standard!", + ch, parent ? parent->value.element.name : "null"); + return (EOF); + } + + return (ch); +} + + +/* + * 'mxml_load_data()' - Load data into an XML node tree. + */ + +static mxml_node_t * /* O - First node or NULL if the file could not be read. */ +mxml_load_data( + mxml_node_t *top, /* I - Top node */ + void *p, /* I - Pointer to data */ + mxml_load_cb_t cb, /* I - Callback function or MXML_NO_CALLBACK */ + _mxml_getc_cb_t getc_cb, /* I - Read function */ + mxml_sax_cb_t sax_cb, /* I - SAX callback or MXML_NO_CALLBACK */ + void *sax_data) /* I - SAX user data */ +{ + mxml_node_t *node, /* Current node */ + *first, /* First node added */ + *parent; /* Current parent node */ + int ch, /* Character from file */ + whitespace; /* Non-zero if whitespace seen */ + char *buffer, /* String buffer */ + *bufptr; /* Pointer into buffer */ + int bufsize; /* Size of buffer */ + mxml_type_t type; /* Current node type */ + int encoding; /* Character encoding */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + static const char * const types[] = /* Type strings... */ + { + "MXML_ELEMENT", /* XML element with attributes */ + "MXML_INTEGER", /* Integer value */ + "MXML_OPAQUE", /* Opaque string */ + "MXML_REAL", /* Real value */ + "MXML_TEXT", /* Text fragment */ + "MXML_CUSTOM" /* Custom data */ + }; + + + /* + * Read elements and other nodes from the file... + */ + + if ((buffer = PhAllocateSafe(64)) == NULL) + { + mxml_error("Unable to allocate string buffer!"); + return (NULL); + } + + bufsize = 64; + bufptr = buffer; + parent = top; + first = NULL; + whitespace = 0; + encoding = ENCODE_UTF8; + + if (cb && parent) + type = (*cb)(parent); + else if (parent) + type = MXML_TEXT; + else + type = MXML_IGNORE; + + while ((ch = (*getc_cb)(p, &encoding)) != EOF) + { + if ((ch == '<' || + (mxml_isspace(ch) && type != MXML_OPAQUE && type != MXML_CUSTOM)) && + bufptr > buffer) + { + /* + * Add a new value node... + */ + + *bufptr = '\0'; + + switch (type) + { + case MXML_INTEGER : + node = mxmlNewInteger(parent, strtol(buffer, &bufptr, 0)); + break; + + case MXML_OPAQUE : + node = mxmlNewOpaque(parent, buffer); + break; + + case MXML_REAL : + node = mxmlNewReal(parent, strtod(buffer, &bufptr)); + break; + + case MXML_TEXT : + node = mxmlNewText(parent, whitespace, buffer); + break; + + case MXML_CUSTOM : + if (global->custom_load_cb) + { + /* + * Use the callback to fill in the custom data... + */ + + node = mxmlNewCustom(parent, NULL, NULL); + + if ((*global->custom_load_cb)(node, buffer)) + { + mxml_error("Bad custom value '%s' in parent <%s>!", + buffer, parent ? parent->value.element.name : "null"); + mxmlDelete(node); + node = NULL; + } + break; + } + + default : /* Ignore... */ + node = NULL; + break; + } + + if (*bufptr) + { + /* + * Bad integer/real number value... + */ + + mxml_error("Bad %s value '%s' in parent <%s>!", + type == MXML_INTEGER ? "integer" : "real", buffer, + parent ? parent->value.element.name : "null"); + break; + } + + bufptr = buffer; + whitespace = mxml_isspace(ch) && type == MXML_TEXT; + + if (!node && type != MXML_IGNORE) + { + /* + * Print error and return... + */ + + mxml_error("Unable to add value node of type %s to parent <%s>!", + types[type], parent ? parent->value.element.name : "null"); + goto error; + } + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_DATA, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (!first && node) + first = node; + } + else if (mxml_isspace(ch) && type == MXML_TEXT) + whitespace = 1; + + /* + * Add lone whitespace node if we have an element and existing + * whitespace... + */ + + if (ch == '<' && whitespace && type == MXML_TEXT) + { + if (parent) + { + node = mxmlNewText(parent, whitespace, ""); + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_DATA, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (!first && node) + first = node; + } + + whitespace = 0; + } + + if (ch == '<') + { + /* + * Start of open/close tag... + */ + + bufptr = buffer; + + while ((ch = (*getc_cb)(p, &encoding)) != EOF) + if (mxml_isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer)) + break; + else if (ch == '<') + { + mxml_error("Bare < in element!"); + goto error; + } + else if (ch == '&') + { + if ((ch = mxml_get_entity(parent, p, &encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + else if (((bufptr - buffer) == 1 && buffer[0] == '?') || + ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3)) || + ((bufptr - buffer) == 8 && !strncmp(buffer, "![CDATA[", 8))) + break; + + *bufptr = '\0'; + + if (!strcmp(buffer, "!--")) + { + /* + * Gather rest of comment... + */ + + while ((ch = (*getc_cb)(p, &encoding)) != EOF) + { + if (ch == '>' && bufptr > (buffer + 4) && + bufptr[-3] != '-' && bufptr[-2] == '-' && bufptr[-1] == '-') + break; + else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + + /* + * Error out if we didn't get the whole comment... + */ + + if (ch != '>') + { + /* + * Print error and return... + */ + + mxml_error("Early EOF in comment node!"); + goto error; + } + + + /* + * Otherwise add this as an element under the current parent... + */ + + *bufptr = '\0'; + + if (!parent && first) + { + /* + * There can only be one root element! + */ + + mxml_error("<%s> cannot be a second root node after <%s>", + buffer, first->value.element.name); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + /* + * Just print error for now... + */ + + mxml_error("Unable to add comment node to parent <%s>!", + parent ? parent->value.element.name : "null"); + break; + } + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_COMMENT, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node && !first) + first = node; + } + else if (!strcmp(buffer, "![CDATA[")) + { + /* + * Gather CDATA section... + */ + + while ((ch = (*getc_cb)(p, &encoding)) != EOF) + { + if (ch == '>' && !strncmp(bufptr - 2, "]]", 2)) + break; + else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + + /* + * Error out if we didn't get the whole comment... + */ + + if (ch != '>') + { + /* + * Print error and return... + */ + + mxml_error("Early EOF in CDATA node!"); + goto error; + } + + + /* + * Otherwise add this as an element under the current parent... + */ + + *bufptr = '\0'; + + if (!parent && first) + { + /* + * There can only be one root element! + */ + + mxml_error("<%s> cannot be a second root node after <%s>", + buffer, first->value.element.name); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + /* + * Print error and return... + */ + + mxml_error("Unable to add CDATA node to parent <%s>!", + parent ? parent->value.element.name : "null"); + goto error; + } + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_CDATA, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node && !first) + first = node; + } + else if (buffer[0] == '?') + { + /* + * Gather rest of processing instruction... + */ + + while ((ch = (*getc_cb)(p, &encoding)) != EOF) + { + if (ch == '>' && bufptr > buffer && bufptr[-1] == '?') + break; + else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + + /* + * Error out if we didn't get the whole processing instruction... + */ + + if (ch != '>') + { + /* + * Print error and return... + */ + + mxml_error("Early EOF in processing instruction node!"); + goto error; + } + + /* + * Otherwise add this as an element under the current parent... + */ + + *bufptr = '\0'; + + if (!parent && first) + { + /* + * There can only be one root element! + */ + + mxml_error("<%s> cannot be a second root node after <%s>", + buffer, first->value.element.name); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + /* + * Print error and return... + */ + + mxml_error("Unable to add processing instruction node to parent <%s>!", + parent ? parent->value.element.name : "null"); + goto error; + } + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_DIRECTIVE, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node) + { + if (!first) + first = node; + + if (!parent) + { + parent = node; + + if (cb) + type = (*cb)(parent); + else + type = MXML_TEXT; + } + } + } + else if (buffer[0] == '!') + { + /* + * Gather rest of declaration... + */ + + do + { + if (ch == '>') + break; + else + { + if (ch == '&') + if ((ch = mxml_get_entity(parent, p, &encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + } + while ((ch = (*getc_cb)(p, &encoding)) != EOF); + + /* + * Error out if we didn't get the whole declaration... + */ + + if (ch != '>') + { + /* + * Print error and return... + */ + + mxml_error("Early EOF in declaration node!"); + goto error; + } + + /* + * Otherwise add this as an element under the current parent... + */ + + *bufptr = '\0'; + + if (!parent && first) + { + /* + * There can only be one root element! + */ + + mxml_error("<%s> cannot be a second root node after <%s>", + buffer, first->value.element.name); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + /* + * Print error and return... + */ + + mxml_error("Unable to add declaration node to parent <%s>!", + parent ? parent->value.element.name : "null"); + goto error; + } + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_DIRECTIVE, sax_data); + + if (!mxmlRelease(node)) + node = NULL; + } + + if (node) + { + if (!first) + first = node; + + if (!parent) + { + parent = node; + + if (cb) + type = (*cb)(parent); + else + type = MXML_TEXT; + } + } + } + else if (buffer[0] == '/') + { + /* + * Handle close tag... + */ + + if (!parent || strcmp(buffer + 1, parent->value.element.name)) + { + /* + * Close tag doesn't match tree; print an error for now... + */ + + mxml_error("Mismatched close tag <%s> under parent <%s>!", + buffer, parent ? parent->value.element.name : "(null)"); + goto error; + } + + /* + * Keep reading until we see >... + */ + + while (ch != '>' && ch != EOF) + ch = (*getc_cb)(p, &encoding); + + node = parent; + parent = parent->parent; + + if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_ELEMENT_CLOSE, sax_data); + + if (!mxmlRelease(node) && first == node) + first = NULL; + } + + /* + * Ascend into the parent and set the value type as needed... + */ + + if (cb && parent) + type = (*cb)(parent); + } + else + { + /* + * Handle open tag... + */ + + if (!parent && first) + { + /* + * There can only be one root element! + */ + + mxml_error("<%s> cannot be a second root node after <%s>", + buffer, first->value.element.name); + goto error; + } + + if ((node = mxmlNewElement(parent, buffer)) == NULL) + { + /* + * Just print error for now... + */ + + mxml_error("Unable to add element node to parent <%s>!", + parent ? parent->value.element.name : "null"); + goto error; + } + + if (mxml_isspace(ch)) + { + if ((ch = mxml_parse_element(node, p, &encoding, getc_cb)) == EOF) + goto error; + } + else if (ch == '/') + { + if ((ch = (*getc_cb)(p, &encoding)) != '>') + { + mxml_error("Expected > but got '%c' instead for element <%s/>!", + ch, buffer); + mxmlDelete(node); + goto error; + } + + ch = '/'; + } + + if (sax_cb) + (*sax_cb)(node, MXML_SAX_ELEMENT_OPEN, sax_data); + + if (!first) + first = node; + + if (ch == EOF) + break; + + if (ch != '/') + { + /* + * Descend into this node, setting the value type as needed... + */ + + parent = node; + + if (cb && parent) + type = (*cb)(parent); + } + else if (sax_cb) + { + (*sax_cb)(node, MXML_SAX_ELEMENT_CLOSE, sax_data); + + if (!mxmlRelease(node) && first == node) + first = NULL; + } + } + + bufptr = buffer; + } + else if (ch == '&') + { + /* + * Add character entity to current buffer... + */ + + if ((ch = mxml_get_entity(parent, p, &encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + else if (type == MXML_OPAQUE || type == MXML_CUSTOM || !mxml_isspace(ch)) + { + /* + * Add character to current buffer... + */ + + if (mxml_add_char(ch, &bufptr, &buffer, &bufsize)) + goto error; + } + } + + /* + * Free the string buffer - we don't need it anymore... + */ + + PhFree(buffer); + + /* + * Find the top element and return it... + */ + + if (parent) + { + node = parent; + + while (parent != top && parent->parent) + parent = parent->parent; + + if (node != parent) + { + mxml_error("Missing close tag under parent <%s>!", + node->value.element.name, + node->parent ? node->parent->value.element.name : "(null)"); + + mxmlDelete(first); + + return (NULL); + } + } + + if (parent) + return (parent); + else + return (first); + + /* + * Common error return... + */ + +error: + + mxmlDelete(first); + + PhFree(buffer); + + return (NULL); +} + + +/* + * 'mxml_parse_element()' - Parse an element for any attributes... + */ + +static int /* O - Terminating character */ +mxml_parse_element( + mxml_node_t *node, /* I - Element node */ + void *p, /* I - Data to read from */ + int *encoding, /* IO - Encoding */ + _mxml_getc_cb_t getc_cb) /* I - Data callback */ +{ + int ch, /* Current character in file */ + quote; /* Quoting character */ + char *name, /* Attribute name */ + *value, /* Attribute value */ + *ptr; /* Pointer into name/value */ + int namesize, /* Size of name string */ + valsize; /* Size of value string */ + + + /* + * Initialize the name and value buffers... + */ + + if ((name = PhAllocateSafe(64)) == NULL) + { + mxml_error("Unable to allocate memory for name!"); + return (EOF); + } + + namesize = 64; + + if ((value = PhAllocateSafe(64)) == NULL) + { + PhFree(name); + mxml_error("Unable to allocate memory for value!"); + return (EOF); + } + + valsize = 64; + + /* + * Loop until we hit a >, /, ?, or EOF... + */ + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + { +#if DEBUG > 1 + fprintf(stderr, "parse_element: ch='%c'\n", ch); +#endif /* DEBUG > 1 */ + + /* + * Skip leading whitespace... + */ + + if (mxml_isspace(ch)) + continue; + + /* + * Stop at /, ?, or >... + */ + + if (ch == '/' || ch == '?') + { + /* + * Grab the > character and print an error if it isn't there... + */ + + quote = (*getc_cb)(p, encoding); + + if (quote != '>') + { + mxml_error("Expected '>' after '%c' for element %s, but got '%c'!", + ch, node->value.element.name, quote); + goto error; + } + + break; + } + else if (ch == '<') + { + mxml_error("Bare < in element %s!", node->value.element.name); + goto error; + } + else if (ch == '>') + break; + + /* + * Read the attribute name... + */ + + name[0] = ch; + ptr = name + 1; + + if (ch == '\"' || ch == '\'') + { + /* + * Name is in quotes, so get a quoted string... + */ + + quote = ch; + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + { + if (ch == '&') + if ((ch = mxml_get_entity(node, p, encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &ptr, &name, &namesize)) + goto error; + + if (ch == quote) + break; + } + } + else + { + /* + * Grab an normal, non-quoted name... + */ + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + if (mxml_isspace(ch) || ch == '=' || ch == '/' || ch == '>' || + ch == '?') + break; + else + { + if (ch == '&') + if ((ch = mxml_get_entity(node, p, encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &ptr, &name, &namesize)) + goto error; + } + } + + *ptr = '\0'; + + if (mxmlElementGetAttr(node, name)) + goto error; + + while (ch != EOF && mxml_isspace(ch)) + ch = (*getc_cb)(p, encoding); + + if (ch == '=') + { + /* + * Read the attribute value... + */ + + while ((ch = (*getc_cb)(p, encoding)) != EOF && mxml_isspace(ch)); + + if (ch == EOF) + { + mxml_error("Missing value for attribute '%s' in element %s!", + name, node->value.element.name); + goto error; + } + + if (ch == '\'' || ch == '\"') + { + /* + * Read quoted value... + */ + + quote = ch; + ptr = value; + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + if (ch == quote) + break; + else + { + if (ch == '&') + if ((ch = mxml_get_entity(node, p, encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &ptr, &value, &valsize)) + goto error; + } + + *ptr = '\0'; + } + else + { + /* + * Read unquoted value... + */ + + value[0] = ch; + ptr = value + 1; + + while ((ch = (*getc_cb)(p, encoding)) != EOF) + if (mxml_isspace(ch) || ch == '=' || ch == '/' || ch == '>') + break; + else + { + if (ch == '&') + if ((ch = mxml_get_entity(node, p, encoding, getc_cb)) == EOF) + goto error; + + if (mxml_add_char(ch, &ptr, &value, &valsize)) + goto error; + } + + *ptr = '\0'; + } + + /* + * Set the attribute with the given string value... + */ + + mxmlElementSetAttr(node, name, value); + } + else + { + mxml_error("Missing value for attribute '%s' in element %s!", + name, node->value.element.name); + goto error; + } + + /* + * Check the end character... + */ + + if (ch == '/' || ch == '?') + { + /* + * Grab the > character and print an error if it isn't there... + */ + + quote = (*getc_cb)(p, encoding); + + if (quote != '>') + { + mxml_error("Expected '>' after '%c' for element %s, but got '%c'!", + ch, node->value.element.name, quote); + ch = EOF; + } + + break; + } + else if (ch == '>') + break; + } + + /* + * Free the name and value buffers and return... + */ + + PhFree(name); + PhFree(value); + + return (ch); + + /* + * Common error return point... + */ + +error: + + PhFree(name); + PhFree(value); + + return (EOF); +} + + +/* + * 'mxml_string_getc()' - Get a character from a string. + */ + +static int /* O - Character or EOF */ +mxml_string_getc(void *p, /* I - Pointer to file */ + int *encoding) /* IO - Encoding */ +{ + int ch; /* Character */ + const char **s; /* Pointer to string pointer */ + + + s = (const char **)p; + + if ((ch = (*s)[0] & 255) != 0 || *encoding == ENCODE_UTF16LE) + { + /* + * Got character; convert UTF-8 to integer and return... + */ + + (*s)++; + + switch (*encoding) + { + case ENCODE_UTF8 : + if (!(ch & 0x80)) + { +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + + return (ch); + } + else if (ch == 0xfe) + { + /* + * UTF-16 big-endian BOM? + */ + + if (((*s)[0] & 255) != 0xff) + return (EOF); + + *encoding = ENCODE_UTF16BE; + (*s)++; + + return (mxml_string_getc(p, encoding)); + } + else if (ch == 0xff) + { + /* + * UTF-16 little-endian BOM? + */ + + if (((*s)[0] & 255) != 0xfe) + return (EOF); + + *encoding = ENCODE_UTF16LE; + (*s)++; + + return (mxml_string_getc(p, encoding)); + } + else if ((ch & 0xe0) == 0xc0) + { + /* + * Two-byte value... + */ + + if (((*s)[0] & 0xc0) != 0x80) + return (EOF); + + ch = ((ch & 0x1f) << 6) | ((*s)[0] & 0x3f); + + (*s)++; + + if (ch < 0x80) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + } + else if ((ch & 0xf0) == 0xe0) + { + /* + * Three-byte value... + */ + + if (((*s)[0] & 0xc0) != 0x80 || + ((*s)[1] & 0xc0) != 0x80) + return (EOF); + + ch = ((((ch & 0x0f) << 6) | ((*s)[0] & 0x3f)) << 6) | ((*s)[1] & 0x3f); + + (*s) += 2; + + if (ch < 0x800) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + + /* + * Ignore (strip) Byte Order Mark (BOM)... + */ + + if (ch == 0xfeff) + return (mxml_string_getc(p, encoding)); + +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + } + else if ((ch & 0xf8) == 0xf0) + { + /* + * Four-byte value... + */ + + if (((*s)[0] & 0xc0) != 0x80 || + ((*s)[1] & 0xc0) != 0x80 || + ((*s)[2] & 0xc0) != 0x80) + return (EOF); + + ch = ((((((ch & 0x07) << 6) | ((*s)[0] & 0x3f)) << 6) | + ((*s)[1] & 0x3f)) << 6) | ((*s)[2] & 0x3f); + + (*s) += 3; + + if (ch < 0x10000) + { + mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch); + return (EOF); + } + +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + } + else + return (EOF); + + case ENCODE_UTF16BE : + /* + * Read UTF-16 big-endian char... + */ + + ch = (ch << 8) | ((*s)[0] & 255); + (*s) ++; + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch; /* Lower word */ + + + if (!(*s)[0]) + return (EOF); + + lch = (((*s)[0] & 255) << 8) | ((*s)[1] & 255); + (*s) += 2; + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + + case ENCODE_UTF16LE : + /* + * Read UTF-16 little-endian char... + */ + + ch = ch | (((*s)[0] & 255) << 8); + + if (!ch) + { + (*s) --; + return (EOF); + } + + (*s) ++; + + if (mxml_bad_char(ch)) + { + mxml_error("Bad control character 0x%02x not allowed by XML standard!", + ch); + return (EOF); + } + else if (ch >= 0xd800 && ch <= 0xdbff) + { + /* + * Multi-word UTF-16 char... + */ + + int lch; /* Lower word */ + + + if (!(*s)[1]) + return (EOF); + + lch = (((*s)[1] & 255) << 8) | ((*s)[0] & 255); + (*s) += 2; + + if (lch < 0xdc00 || lch >= 0xdfff) + return (EOF); + + ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; + } + +#if DEBUG > 1 + printf("mxml_string_getc: %c (0x%04x)\n", ch < ' ' ? '.' : ch, ch); +#endif /* DEBUG > 1 */ + + return (ch); + } + } + + return (EOF); +} + + +/* + * 'mxml_string_putc()' - Write a character to a string. + */ + +static int /* O - 0 on success, -1 on failure */ +mxml_string_putc(int ch, /* I - Character to write */ + void *p) /* I - Pointer to string pointers */ +{ + char **pp; /* Pointer to string pointers */ + + + pp = (char **)p; + + if (pp[0] < pp[1]) + pp[0][0] = ch; + + pp[0] ++; + + return (0); +} + + +/* + * 'mxml_write_name()' - Write a name string. + */ + +static int /* O - 0 on success, -1 on failure */ +mxml_write_name(const char *s, /* I - Name to write */ + void *p, /* I - Write pointer */ + int (*putc_cb)(int, void *)) + /* I - Write callback */ +{ + char quote; /* Quote character */ + const char *name; /* Entity name */ + + + if (*s == '\"' || *s == '\'') + { + /* + * Write a quoted name string... + */ + + if ((*putc_cb)(*s, p) < 0) + return (-1); + + quote = *s++; + + while (*s && *s != quote) + { + if ((name = mxmlEntityGetName(*s)) != NULL) + { + if ((*putc_cb)('&', p) < 0) + return (-1); + + while (*name) + { + if ((*putc_cb)(*name, p) < 0) + return (-1); + + name ++; + } + + if ((*putc_cb)(';', p) < 0) + return (-1); + } + else if ((*putc_cb)(*s, p) < 0) + return (-1); + + s ++; + } + + /* + * Write the end quote... + */ + + if ((*putc_cb)(quote, p) < 0) + return (-1); + } + else + { + /* + * Write a non-quoted name string... + */ + + while (*s) + { + if ((*putc_cb)(*s, p) < 0) + return (-1); + + s ++; + } + } + + return (0); +} + + +/* + * 'mxml_write_node()' - Save an XML node to a file. + */ + +static int /* O - Column or -1 on error */ +mxml_write_node(mxml_node_t *node, /* I - Node to write */ + void *p, /* I - File to write to */ + mxml_save_cb_t cb, /* I - Whitespace callback */ + int col, /* I - Current column */ + _mxml_putc_cb_t putc_cb,/* I - Output callback */ + _mxml_global_t *global)/* I - Global data */ +{ + int i, /* Looping var */ + width; /* Width of attr + value */ + mxml_attr_t *attr; /* Current attribute */ + char s[255]; /* Temporary string */ + + + /* + * Print the node value... + */ + + switch (node->type) + { + case MXML_ELEMENT : + col = mxml_write_ws(node, p, cb, MXML_WS_BEFORE_OPEN, col, putc_cb); + + if ((*putc_cb)('<', p) < 0) + return (-1); + if (node->value.element.name[0] == '?' || + !strncmp(node->value.element.name, "!--", 3) || + !strncmp(node->value.element.name, "![CDATA[", 8)) + { + /* + * Comments, CDATA, and processing instructions do not + * use character entities. + */ + + const char *ptr; /* Pointer into name */ + + + for (ptr = node->value.element.name; *ptr; ptr ++) + if ((*putc_cb)(*ptr, p) < 0) + return (-1); + } + else if (mxml_write_name(node->value.element.name, p, putc_cb) < 0) + return (-1); + + col += (int)strlen(node->value.element.name) + 1; + + for (i = node->value.element.num_attrs, attr = node->value.element.attrs; + i > 0; + i --, attr ++) + { + width = (int)strlen(attr->name); + + if (attr->value) + width += (int)strlen(attr->value) + 3; + + if (global->wrap > 0 && (col + width) > global->wrap) + { + if ((*putc_cb)('\n', p) < 0) + return (-1); + + col = 0; + } + else + { + if ((*putc_cb)(' ', p) < 0) + return (-1); + + col ++; + } + + if (mxml_write_name(attr->name, p, putc_cb) < 0) + return (-1); + + if (attr->value) + { + if ((*putc_cb)('=', p) < 0) + return (-1); + if ((*putc_cb)('\"', p) < 0) + return (-1); + if (mxml_write_string(attr->value, p, putc_cb) < 0) + return (-1); + if ((*putc_cb)('\"', p) < 0) + return (-1); + } + + col += width; + } + + if (node->child) + { + /* + * Write children... + */ + + mxml_node_t *child; /* Current child */ + + + if ((*putc_cb)('>', p) < 0) + return (-1); + else + col ++; + + col = mxml_write_ws(node, p, cb, MXML_WS_AFTER_OPEN, col, putc_cb); + + for (child = node->child; child; child = child->next) + { + if ((col = mxml_write_node(child, p, cb, col, putc_cb, global)) < 0) + return (-1); + } + + /* + * The ? and ! elements are special-cases and have no end tags... + */ + + if (node->value.element.name[0] != '!' && + node->value.element.name[0] != '?') + { + col = mxml_write_ws(node, p, cb, MXML_WS_BEFORE_CLOSE, col, putc_cb); + + if ((*putc_cb)('<', p) < 0) + return (-1); + if ((*putc_cb)('/', p) < 0) + return (-1); + if (mxml_write_string(node->value.element.name, p, putc_cb) < 0) + return (-1); + if ((*putc_cb)('>', p) < 0) + return (-1); + + col += (int)strlen(node->value.element.name) + 3; + + col = mxml_write_ws(node, p, cb, MXML_WS_AFTER_CLOSE, col, putc_cb); + } + } + else if (node->value.element.name[0] == '!' || + node->value.element.name[0] == '?') + { + /* + * The ? and ! elements are special-cases... + */ + + if ((*putc_cb)('>', p) < 0) + return (-1); + else + col ++; + + col = mxml_write_ws(node, p, cb, MXML_WS_AFTER_OPEN, col, putc_cb); + } + else + { + if ((*putc_cb)(' ', p) < 0) + return (-1); + if ((*putc_cb)('/', p) < 0) + return (-1); + if ((*putc_cb)('>', p) < 0) + return (-1); + + col += 3; + + col = mxml_write_ws(node, p, cb, MXML_WS_AFTER_OPEN, col, putc_cb); + } + break; + + case MXML_INTEGER : + if (node->prev) + { + if (global->wrap > 0 && col > global->wrap) + { + if ((*putc_cb)('\n', p) < 0) + return (-1); + + col = 0; + } + else if ((*putc_cb)(' ', p) < 0) + return (-1); + else + col ++; + } + + sprintf(s, "%d", node->value.integer); + if (mxml_write_string(s, p, putc_cb) < 0) + return (-1); + + col += (int)strlen(s); + break; + + case MXML_OPAQUE : + if (mxml_write_string(node->value.opaque, p, putc_cb) < 0) + return (-1); + + col += (int)strlen(node->value.opaque); + break; + + case MXML_REAL : + if (node->prev) + { + if (global->wrap > 0 && col > global->wrap) + { + if ((*putc_cb)('\n', p) < 0) + return (-1); + + col = 0; + } + else if ((*putc_cb)(' ', p) < 0) + return (-1); + else + col ++; + } + + sprintf(s, "%f", node->value.real); + if (mxml_write_string(s, p, putc_cb) < 0) + return (-1); + + col += (int)strlen(s); + break; + + case MXML_TEXT : + if (node->value.text.whitespace && col > 0) + { + if (global->wrap > 0 && col > global->wrap) + { + if ((*putc_cb)('\n', p) < 0) + return (-1); + + col = 0; + } + else if ((*putc_cb)(' ', p) < 0) + return (-1); + else + col ++; + } + + if (mxml_write_string(node->value.text.string, p, putc_cb) < 0) + return (-1); + + col += (int)strlen(node->value.text.string); + break; + + case MXML_CUSTOM : + if (global->custom_save_cb) + { + char *data; /* Custom data string */ + const char *newline; /* Last newline in string */ + + + if ((data = (*global->custom_save_cb)(node)) == NULL) + return (-1); + + if (mxml_write_string(data, p, putc_cb) < 0) + return (-1); + + if ((newline = strrchr(data, '\n')) == NULL) + col += (int)strlen(data); + else + col = (int)strlen(newline); + + PhFree(data); + break; + } + + default : /* Should never happen */ + return (-1); + } + + return (col); +} + + +/* + * 'mxml_write_string()' - Write a string, escaping & and < as needed. + */ + +static int /* O - 0 on success, -1 on failure */ +mxml_write_string( + const char *s, /* I - String to write */ + void *p, /* I - Write pointer */ + _mxml_putc_cb_t putc_cb) /* I - Write callback */ +{ + const char *name; /* Entity name, if any */ + + + while (*s) + { + if ((name = mxmlEntityGetName(*s)) != NULL) + { + if ((*putc_cb)('&', p) < 0) + return (-1); + + while (*name) + { + if ((*putc_cb)(*name, p) < 0) + return (-1); + name ++; + } + + if ((*putc_cb)(';', p) < 0) + return (-1); + } + else if ((*putc_cb)(*s, p) < 0) + return (-1); + + s ++; + } + + return (0); +} + + +/* + * 'mxml_write_ws()' - Do whitespace callback... + */ + +static int /* O - New column */ +mxml_write_ws(mxml_node_t *node, /* I - Current node */ + void *p, /* I - Write pointer */ + mxml_save_cb_t cb, /* I - Callback function */ + int ws, /* I - Where value */ + int col, /* I - Current column */ + _mxml_putc_cb_t putc_cb) /* I - Write callback */ +{ + const char *s; /* Whitespace string */ + + + if (cb && (s = (*cb)(node, ws)) != NULL) + { + while (*s) + { + if ((*putc_cb)(*s, p) < 0) + return (-1); + else if (*s == '\n') + col = 0; + else if (*s == '\t') + { + col += MXML_TAB; + col = col - (col % MXML_TAB); + } + else + col ++; + + s ++; + } + } + + return (col); +} + + +/* + * End of "$Id: mxml-file.c 459 2014-10-19 17:21:48Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-get.c b/ProcessHacker/mxml/mxml-get.c new file mode 100644 index 0000000..40ed3d0 --- /dev/null +++ b/ProcessHacker/mxml/mxml-get.c @@ -0,0 +1,452 @@ +/* + * "$Id: mxml-get.c 451 2014-01-04 21:50:06Z msweet $" + * + * Node get functions for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include "config.h" +#include "mxml.h" + + +/* + * 'mxmlGetCDATA()' - Get the value for a CDATA node. + * + * @code NULL@ is returned if the node is not a CDATA element. + * + * @since Mini-XML 2.7@ + */ + +const char * /* O - CDATA value or NULL */ +mxmlGetCDATA(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || + strncmp(node->value.element.name, "![CDATA[", 8)) + return (NULL); + + /* + * Return the text following the CDATA declaration... + */ + + return (node->value.element.name + 8); +} + + +/* + * 'mxmlGetCustom()' - Get the value for a custom node. + * + * @code NULL@ is returned if the node (or its first child) is not a custom + * value node. + * + * @since Mini-XML 2.7@ + */ + +const void * /* O - Custom value or NULL */ +mxmlGetCustom(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the integer value... + */ + + if (node->type == MXML_CUSTOM) + return (node->value.custom.data); + else if (node->type == MXML_ELEMENT && + node->child && + node->child->type == MXML_CUSTOM) + return (node->child->value.custom.data); + else + return (NULL); +} + + +/* + * 'mxmlGetElement()' - Get the name for an element node. + * + * @code NULL@ is returned if the node is not an element node. + * + * @since Mini-XML 2.7@ + */ + +const char * /* O - Element name or NULL */ +mxmlGetElement(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT) + return (NULL); + + /* + * Return the element name... + */ + + return (node->value.element.name); +} + + +/* + * 'mxmlGetFirstChild()' - Get the first child of an element node. + * + * @code NULL@ is returned if the node is not an element node or if the node + * has no children. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * /* O - First child or NULL */ +mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT) + return (NULL); + + /* + * Return the first child node... + */ + + return (node->child); +} + + +/* + * 'mxmlGetInteger()' - Get the integer value from the specified node or its + * first child. + * + * 0 is returned if the node (or its first child) is not an integer value node. + * + * @since Mini-XML 2.7@ + */ + +int /* O - Integer value or 0 */ +mxmlGetInteger(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (0); + + /* + * Return the integer value... + */ + + if (node->type == MXML_INTEGER) + return (node->value.integer); + else if (node->type == MXML_ELEMENT && + node->child && + node->child->type == MXML_INTEGER) + return (node->child->value.integer); + else + return (0); +} + + +/* + * 'mxmlGetLastChild()' - Get the last child of an element node. + * + * @code NULL@ is returned if the node is not an element node or if the node + * has no children. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * /* O - Last child or NULL */ +mxmlGetLastChild(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT) + return (NULL); + + /* + * Return the node type... + */ + + return (node->last_child); +} + + +/* + * 'mxmlGetNextSibling()' - Get the next node for the current parent. + * + * @code NULL@ is returned if this is the last child for the current parent. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * +mxmlGetNextSibling(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the node type... + */ + + return (node->next); +} + + +/* + * 'mxmlGetOpaque()' - Get an opaque string value for a node or its first child. + * + * @code NULL@ is returned if the node (or its first child) is not an opaque + * value node. + * + * @since Mini-XML 2.7@ + */ + +const char * /* O - Opaque string or NULL */ +mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the integer value... + */ + + if (node->type == MXML_OPAQUE) + return (node->value.opaque); + else if (node->type == MXML_ELEMENT && + node->child && + node->child->type == MXML_OPAQUE) + return (node->child->value.opaque); + else + return (NULL); +} + + +/* + * 'mxmlGetParent()' - Get the parent node. + * + * @code NULL@ is returned for a root node. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * /* O - Parent node or NULL */ +mxmlGetParent(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the node type... + */ + + return (node->parent); +} + + +/* + * 'mxmlGetPrevSibling()' - Get the previous node for the current parent. + * + * @code NULL@ is returned if this is the first child for the current parent. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * /* O - Previous node or NULL */ +mxmlGetPrevSibling(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the node type... + */ + + return (node->prev); +} + + +/* + * 'mxmlGetReal()' - Get the real value for a node or its first child. + * + * 0.0 is returned if the node (or its first child) is not a real value node. + * + * @since Mini-XML 2.7@ + */ + +double /* O - Real value or 0.0 */ +mxmlGetReal(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (0.0); + + /* + * Return the integer value... + */ + + if (node->type == MXML_REAL) + return (node->value.real); + else if (node->type == MXML_ELEMENT && + node->child && + node->child->type == MXML_REAL) + return (node->child->value.real); + else + return (0.0); +} + + +/* + * 'mxmlGetText()' - Get the text value for a node or its first child. + * + * @code NULL@ is returned if the node (or its first child) is not a text node. + * The "whitespace" argument can be NULL. + * + * @since Mini-XML 2.7@ + */ + +const char * /* O - Text string or NULL */ +mxmlGetText(mxml_node_t *node, /* I - Node to get */ + int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */ +{ + /* + * Range check input... + */ + + if (!node) + { + if (whitespace) + *whitespace = 0; + + return (NULL); + } + + /* + * Return the integer value... + */ + + if (node->type == MXML_TEXT) + { + if (whitespace) + *whitespace = node->value.text.whitespace; + + return (node->value.text.string); + } + else if (node->type == MXML_ELEMENT && + node->child && + node->child->type == MXML_TEXT) + { + if (whitespace) + *whitespace = node->child->value.text.whitespace; + + return (node->child->value.text.string); + } + else + { + if (whitespace) + *whitespace = 0; + + return (NULL); + } +} + + +/* + * 'mxmlGetType()' - Get the node type. + * + * @code MXML_IGNORE@ is returned if "node" is @code NULL@. + * + * @since Mini-XML 2.7@ + */ + +mxml_type_t /* O - Type of node */ +mxmlGetType(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (MXML_IGNORE); + + /* + * Return the node type... + */ + + return (node->type); +} + + +/* + * 'mxmlGetUserData()' - Get the user data pointer for a node. + * + * @since Mini-XML 2.7@ + */ + +void * /* O - User data pointer */ +mxmlGetUserData(mxml_node_t *node) /* I - Node to get */ +{ + /* + * Range check input... + */ + + if (!node) + return (NULL); + + /* + * Return the user data pointer... + */ + + return (node->user_data); +} + + +/* + * End of "$Id: mxml-get.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-index.c b/ProcessHacker/mxml/mxml-index.c new file mode 100644 index 0000000..86fcd2d --- /dev/null +++ b/ProcessHacker/mxml/mxml-index.c @@ -0,0 +1,660 @@ +/* + * "$Id: mxml-index.c 451 2014-01-04 21:50:06Z msweet $" + * + * Index support code for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include +#include "config.h" +#include "mxml.h" + + +/* + * Sort functions... + */ + +static int index_compare(mxml_index_t *ind, mxml_node_t *first, + mxml_node_t *second); +static int index_find(mxml_index_t *ind, const char *element, + const char *value, mxml_node_t *node); +static void index_sort(mxml_index_t *ind, int left, int right); + + +/* + * 'mxmlIndexDelete()' - Delete an index. + */ + +void +mxmlIndexDelete(mxml_index_t *ind) /* I - Index to delete */ +{ + /* + * Range check input.. + */ + + if (!ind) + return; + + /* + * Free memory... + */ + + if (ind->attr) + PhFree(ind->attr); + + if (ind->alloc_nodes) + PhFree(ind->nodes); + + PhFree(ind); +} + + +/* + * 'mxmlIndexEnum()' - Return the next node in the index. + * + * Nodes are returned in the sorted order of the index. + */ + +mxml_node_t * /* O - Next node or NULL if there is none */ +mxmlIndexEnum(mxml_index_t *ind) /* I - Index to enumerate */ +{ + /* + * Range check input... + */ + + if (!ind) + return (NULL); + + /* + * Return the next node... + */ + + if (ind->cur_node < ind->num_nodes) + return (ind->nodes[ind->cur_node ++]); + else + return (NULL); +} + + +/* + * 'mxmlIndexFind()' - Find the next matching node. + * + * You should call mxmlIndexReset() prior to using this function for + * the first time with a particular set of "element" and "value" + * strings. Passing NULL for both "element" and "value" is equivalent + * to calling mxmlIndexEnum(). + */ + +mxml_node_t * /* O - Node or NULL if none found */ +mxmlIndexFind(mxml_index_t *ind, /* I - Index to search */ + const char *element, /* I - Element name to find, if any */ + const char *value) /* I - Attribute value, if any */ +{ + int diff, /* Difference between names */ + current, /* Current entity in search */ + first, /* First entity in search */ + last; /* Last entity in search */ + + +#ifdef DEBUG + printf("mxmlIndexFind(ind=%p, element=\"%s\", value=\"%s\")\n", + ind, element ? element : "(null)", value ? value : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!ind || (!ind->attr && value)) + { +#ifdef DEBUG + puts(" returning NULL..."); + printf(" ind->attr=\"%s\"\n", ind->attr ? ind->attr : "(null)"); +#endif /* DEBUG */ + + return (NULL); + } + + /* + * If both element and value are NULL, just enumerate the nodes in the + * index... + */ + + if (!element && !value) + return (mxmlIndexEnum(ind)); + + /* + * If there are no nodes in the index, return NULL... + */ + + if (!ind->num_nodes) + { +#ifdef DEBUG + puts(" returning NULL..."); + puts(" no nodes!"); +#endif /* DEBUG */ + + return (NULL); + } + + /* + * If cur_node == 0, then find the first matching node... + */ + + if (ind->cur_node == 0) + { + /* + * Find the first node using a modified binary search algorithm... + */ + + first = 0; + last = ind->num_nodes - 1; + +#ifdef DEBUG + printf(" find first time, num_nodes=%d...\n", ind->num_nodes); +#endif /* DEBUG */ + + while ((last - first) > 1) + { + current = (first + last) / 2; + +#ifdef DEBUG + printf(" first=%d, last=%d, current=%d\n", first, last, current); +#endif /* DEBUG */ + + if ((diff = index_find(ind, element, value, ind->nodes[current])) == 0) + { + /* + * Found a match, move back to find the first... + */ + +#ifdef DEBUG + puts(" match!"); +#endif /* DEBUG */ + + while (current > 0 && + !index_find(ind, element, value, ind->nodes[current - 1])) + current --; + +#ifdef DEBUG + printf(" returning first match=%d\n", current); +#endif /* DEBUG */ + + /* + * Return the first match and save the index to the next... + */ + + ind->cur_node = current + 1; + + return (ind->nodes[current]); + } + else if (diff < 0) + last = current; + else + first = current; + +#ifdef DEBUG + printf(" diff=%d\n", diff); +#endif /* DEBUG */ + } + + /* + * If we get this far, then we found exactly 0 or 1 matches... + */ + + for (current = first; current <= last; current ++) + if (!index_find(ind, element, value, ind->nodes[current])) + { + /* + * Found exactly one (or possibly two) match... + */ + +#ifdef DEBUG + printf(" returning only match %d...\n", current); +#endif /* DEBUG */ + + ind->cur_node = current + 1; + + return (ind->nodes[current]); + } + + /* + * No matches... + */ + + ind->cur_node = ind->num_nodes; + +#ifdef DEBUG + puts(" returning NULL..."); +#endif /* DEBUG */ + + return (NULL); + } + else if (ind->cur_node < ind->num_nodes && + !index_find(ind, element, value, ind->nodes[ind->cur_node])) + { + /* + * Return the next matching node... + */ + +#ifdef DEBUG + printf(" returning next match %d...\n", ind->cur_node); +#endif /* DEBUG */ + + return (ind->nodes[ind->cur_node ++]); + } + + /* + * If we get this far, then we have no matches... + */ + + ind->cur_node = ind->num_nodes; + +#ifdef DEBUG + puts(" returning NULL..."); +#endif /* DEBUG */ + + return (NULL); +} + + +/* + * 'mxmlIndexGetCount()' - Get the number of nodes in an index. + * + * @since Mini-XML 2.7@ + */ + +int /* I - Number of nodes in index */ +mxmlIndexGetCount(mxml_index_t *ind) /* I - Index of nodes */ +{ + /* + * Range check input... + */ + + if (!ind) + return (0); + + /* + * Return the number of nodes in the index... + */ + + return (ind->num_nodes); +} + + +/* + * 'mxmlIndexNew()' - Create a new index. + * + * The index will contain all nodes that contain the named element and/or + * attribute. If both "element" and "attr" are NULL, then the index will + * contain a sorted list of the elements in the node tree. Nodes are + * sorted by element name and optionally by attribute value if the "attr" + * argument is not NULL. + */ + +mxml_index_t * /* O - New index */ +mxmlIndexNew(mxml_node_t *node, /* I - XML node tree */ + const char *element, /* I - Element to index or NULL for all */ + const char *attr) /* I - Attribute to index or NULL for none */ +{ + mxml_index_t *ind; /* New index */ + mxml_node_t *current, /* Current node in index */ + **temp; /* Temporary node pointer array */ + + + /* + * Range check input... + */ + +#ifdef DEBUG + printf("mxmlIndexNew(node=%p, element=\"%s\", attr=\"%s\")\n", + node, element ? element : "(null)", attr ? attr : "(null)"); +#endif /* DEBUG */ + + if (!node) + return (NULL); + + /* + * Create a new index... + */ + + if ((ind = PhAllocateExSafe(sizeof(mxml_index_t), HEAP_ZERO_MEMORY)) == NULL) + { + mxml_error("Unable to allocate %d bytes for index - %s", + sizeof(mxml_index_t), strerror(errno)); + return (NULL); + } + + if (attr) + ind->attr = PhDuplicateBytesZSafe((char *)attr); + + if (!element && !attr) + current = node; + else + current = mxmlFindElement(node, node, element, attr, NULL, MXML_DESCEND); + + while (current) + { + if (ind->num_nodes >= ind->alloc_nodes) + { + if (!ind->alloc_nodes) + temp = PhAllocateSafe(64 * sizeof(mxml_node_t *)); + else + temp = PhReAllocateSafe(ind->nodes, (ind->alloc_nodes + 64) * sizeof(mxml_node_t *)); + + if (!temp) + { + /* + * Unable to allocate memory for the index, so abort... + */ + + mxml_error("Unable to allocate %d bytes for index: %s", + (ind->alloc_nodes + 64) * sizeof(mxml_node_t *), + strerror(errno)); + + mxmlIndexDelete(ind); + return (NULL); + } + + ind->nodes = temp; + ind->alloc_nodes += 64; + } + + ind->nodes[ind->num_nodes ++] = current; + + current = mxmlFindElement(current, node, element, attr, NULL, MXML_DESCEND); + } + + /* + * Sort nodes based upon the search criteria... + */ + +#ifdef DEBUG + { + int i; /* Looping var */ + + + printf("%d node(s) in index.\n\n", ind->num_nodes); + + if (attr) + { + printf("Node Address Element %s\n", attr); + puts("-------- -------- -------------- ------------------------------"); + + for (i = 0; i < ind->num_nodes; i ++) + printf("%8d %-8p %-14.14s %s\n", i, ind->nodes[i], + ind->nodes[i]->value.element.name, + mxmlElementGetAttr(ind->nodes[i], attr)); + } + else + { + puts("Node Address Element"); + puts("-------- -------- --------------"); + + for (i = 0; i < ind->num_nodes; i ++) + printf("%8d %-8p %s\n", i, ind->nodes[i], + ind->nodes[i]->value.element.name); + } + + putchar('\n'); + } +#endif /* DEBUG */ + + if (ind->num_nodes > 1) + index_sort(ind, 0, ind->num_nodes - 1); + +#ifdef DEBUG + { + int i; /* Looping var */ + + + puts("After sorting:\n"); + + if (attr) + { + printf("Node Address Element %s\n", attr); + puts("-------- -------- -------------- ------------------------------"); + + for (i = 0; i < ind->num_nodes; i ++) + printf("%8d %-8p %-14.14s %s\n", i, ind->nodes[i], + ind->nodes[i]->value.element.name, + mxmlElementGetAttr(ind->nodes[i], attr)); + } + else + { + puts("Node Address Element"); + puts("-------- -------- --------------"); + + for (i = 0; i < ind->num_nodes; i ++) + printf("%8d %-8p %s\n", i, ind->nodes[i], + ind->nodes[i]->value.element.name); + } + + putchar('\n'); + } +#endif /* DEBUG */ + + /* + * Return the new index... + */ + + return (ind); +} + + +/* + * 'mxmlIndexReset()' - Reset the enumeration/find pointer in the index and + * return the first node in the index. + * + * This function should be called prior to using mxmlIndexEnum() or + * mxmlIndexFind() for the first time. + */ + +mxml_node_t * /* O - First node or NULL if there is none */ +mxmlIndexReset(mxml_index_t *ind) /* I - Index to reset */ +{ +#ifdef DEBUG + printf("mxmlIndexReset(ind=%p)\n", ind); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!ind) + return (NULL); + + /* + * Set the index to the first element... + */ + + ind->cur_node = 0; + + /* + * Return the first node... + */ + + if (ind->num_nodes) + return (ind->nodes[0]); + else + return (NULL); +} + + +/* + * 'index_compare()' - Compare two nodes. + */ + +static int /* O - Result of comparison */ +index_compare(mxml_index_t *ind, /* I - Index */ + mxml_node_t *first, /* I - First node */ + mxml_node_t *second) /* I - Second node */ +{ + int diff; /* Difference */ + + + /* + * Check the element name... + */ + + if ((diff = strcmp(first->value.element.name, + second->value.element.name)) != 0) + return (diff); + + /* + * Check the attribute value... + */ + + if (ind->attr) + { + if ((diff = strcmp(mxmlElementGetAttr(first, ind->attr), + mxmlElementGetAttr(second, ind->attr))) != 0) + return (diff); + } + + /* + * No difference, return 0... + */ + + return (0); +} + + +/* + * 'index_find()' - Compare a node with index values. + */ + +static int /* O - Result of comparison */ +index_find(mxml_index_t *ind, /* I - Index */ + const char *element, /* I - Element name or NULL */ + const char *value, /* I - Attribute value or NULL */ + mxml_node_t *node) /* I - Node */ +{ + int diff; /* Difference */ + + + /* + * Check the element name... + */ + + if (element) + { + if ((diff = strcmp(element, node->value.element.name)) != 0) + return (diff); + } + + /* + * Check the attribute value... + */ + + if (value) + { + if ((diff = strcmp(value, mxmlElementGetAttr(node, ind->attr))) != 0) + return (diff); + } + + /* + * No difference, return 0... + */ + + return (0); +} + + +/* + * 'index_sort()' - Sort the nodes in the index... + * + * This function implements the classic quicksort algorithm... + */ + +static void +index_sort(mxml_index_t *ind, /* I - Index to sort */ + int left, /* I - Left node in partition */ + int right) /* I - Right node in partition */ +{ + mxml_node_t *pivot, /* Pivot node */ + *temp; /* Swap node */ + int templ, /* Temporary left node */ + tempr; /* Temporary right node */ + + + /* + * Loop until we have sorted all the way to the right... + */ + + do + { + /* + * Sort the pivot in the current partition... + */ + + pivot = ind->nodes[left]; + + for (templ = left, tempr = right; templ < tempr;) + { + /* + * Move left while left node <= pivot node... + */ + + while ((templ < right) && + index_compare(ind, ind->nodes[templ], pivot) <= 0) + templ ++; + + /* + * Move right while right node > pivot node... + */ + + while ((tempr > left) && + index_compare(ind, ind->nodes[tempr], pivot) > 0) + tempr --; + + /* + * Swap nodes if needed... + */ + + if (templ < tempr) + { + temp = ind->nodes[templ]; + ind->nodes[templ] = ind->nodes[tempr]; + ind->nodes[tempr] = temp; + } + } + + /* + * When we get here, the right (tempr) node is the new position for the + * pivot node... + */ + + if (index_compare(ind, pivot, ind->nodes[tempr]) > 0) + { + ind->nodes[left] = ind->nodes[tempr]; + ind->nodes[tempr] = pivot; + } + + /* + * Recursively sort the left partition as needed... + */ + + if (left < (tempr - 1)) + index_sort(ind, left, tempr - 1); + } + while (right > (left = tempr + 1)); +} + + +/* + * End of "$Id: mxml-index.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-node.c b/ProcessHacker/mxml/mxml-node.c new file mode 100644 index 0000000..f175e19 --- /dev/null +++ b/ProcessHacker/mxml/mxml-node.c @@ -0,0 +1,788 @@ +/* + * "$Id: mxml-node.c 451 2014-01-04 21:50:06Z msweet $" + * + * Node support code for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ +#include +#include "config.h" +#include "mxml.h" + + +/* + * Local functions... + */ + +static mxml_node_t *mxml_new(mxml_node_t *parent, mxml_type_t type); + + +/* + * 'mxmlAdd()' - Add a node to a tree. + * + * Adds the specified node to the parent. If the child argument is not + * NULL, puts the new node before or after the specified child depending + * on the value of the where argument. If the child argument is NULL, + * puts the new node at the beginning of the child list (MXML_ADD_BEFORE) + * or at the end of the child list (MXML_ADD_AFTER). The constant + * MXML_ADD_TO_PARENT can be used to specify a NULL child pointer. + */ + +void +mxmlAdd(mxml_node_t *parent, /* I - Parent node */ + int where, /* I - Where to add, MXML_ADD_BEFORE or MXML_ADD_AFTER */ + mxml_node_t *child, /* I - Child node for where or MXML_ADD_TO_PARENT */ + mxml_node_t *node) /* I - Node to add */ +{ +#ifdef DEBUG + fprintf(stderr, "mxmlAdd(parent=%p, where=%d, child=%p, node=%p)\n", parent, + where, child, node); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!parent || !node) + return; + +#if DEBUG > 1 + fprintf(stderr, " BEFORE: node->parent=%p\n", node->parent); + if (parent) + { + fprintf(stderr, " BEFORE: parent->child=%p\n", parent->child); + fprintf(stderr, " BEFORE: parent->last_child=%p\n", parent->last_child); + fprintf(stderr, " BEFORE: parent->prev=%p\n", parent->prev); + fprintf(stderr, " BEFORE: parent->next=%p\n", parent->next); + } +#endif /* DEBUG > 1 */ + + /* + * Remove the node from any existing parent... + */ + + if (node->parent) + mxmlRemove(node); + + /* + * Reset pointers... + */ + + node->parent = parent; + + switch (where) + { + case MXML_ADD_BEFORE : + if (!child || child == parent->child || child->parent != parent) + { + /* + * Insert as first node under parent... + */ + + node->next = parent->child; + + if (parent->child) + parent->child->prev = node; + else + parent->last_child = node; + + parent->child = node; + } + else + { + /* + * Insert node before this child... + */ + + node->next = child; + node->prev = child->prev; + + if (child->prev) + child->prev->next = node; + else + parent->child = node; + + child->prev = node; + } + break; + + case MXML_ADD_AFTER : + if (!child || child == parent->last_child || child->parent != parent) + { + /* + * Insert as last node under parent... + */ + + node->parent = parent; + node->prev = parent->last_child; + + if (parent->last_child) + parent->last_child->next = node; + else + parent->child = node; + + parent->last_child = node; + } + else + { + /* + * Insert node after this child... + */ + + node->prev = child; + node->next = child->next; + + if (child->next) + child->next->prev = node; + else + parent->last_child = node; + + child->next = node; + } + break; + } + +#if DEBUG > 1 + fprintf(stderr, " AFTER: node->parent=%p\n", node->parent); + if (parent) + { + fprintf(stderr, " AFTER: parent->child=%p\n", parent->child); + fprintf(stderr, " AFTER: parent->last_child=%p\n", parent->last_child); + fprintf(stderr, " AFTER: parent->prev=%p\n", parent->prev); + fprintf(stderr, " AFTER: parent->next=%p\n", parent->next); + } +#endif /* DEBUG > 1 */ +} + + +/* + * 'mxmlDelete()' - Delete a node and all of its children. + * + * If the specified node has a parent, this function first removes the + * node from its parent using the mxmlRemove() function. + */ + +void +mxmlDelete(mxml_node_t *node) /* I - Node to delete */ +{ + int i; /* Looping var */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlDelete(node=%p)\n", node); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node) + return; + + /* + * Remove the node from its parent, if any... + */ + + mxmlRemove(node); + + /* + * Delete children... + */ + + while (node->child) + mxmlDelete(node->child); + + /* + * Now delete any node data... + */ + + switch (node->type) + { + case MXML_ELEMENT : + if (node->value.element.name) + PhFree(node->value.element.name); + + if (node->value.element.num_attrs) + { + for (i = 0; i < node->value.element.num_attrs; i ++) + { + if (node->value.element.attrs[i].name) + PhFree(node->value.element.attrs[i].name); + if (node->value.element.attrs[i].value) + PhFree(node->value.element.attrs[i].value); + } + + PhFree(node->value.element.attrs); + } + break; + case MXML_INTEGER : + /* Nothing to do */ + break; + case MXML_OPAQUE : + if (node->value.opaque) + PhFree(node->value.opaque); + break; + case MXML_REAL : + /* Nothing to do */ + break; + case MXML_TEXT : + if (node->value.text.string) + PhFree(node->value.text.string); + break; + case MXML_CUSTOM : + if (node->value.custom.data && + node->value.custom.destroy) + (*(node->value.custom.destroy))(node->value.custom.data); + break; + default : + break; + } + + /* + * Free this node... + */ + + PhFree(node); +} + + +/* + * 'mxmlGetRefCount()' - Get the current reference (use) count for a node. + * + * The initial reference count of new nodes is 1. Use the @link mxmlRetain@ + * and @link mxmlRelease@ functions to increment and decrement a node's + * reference count. + * + * @since Mini-XML 2.7@. + */ + +int /* O - Reference count */ +mxmlGetRefCount(mxml_node_t *node) /* I - Node */ +{ + /* + * Range check input... + */ + + if (!node) + return (0); + + /* + * Return the reference count... + */ + + return (node->ref_count); +} + + +/* + * 'mxmlNewCDATA()' - Create a new CDATA node. + * + * The new CDATA node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * CDATA node has no parent. The data string must be nul-terminated and + * is copied into the new node. CDATA nodes use the MXML_ELEMENT type. + * + * @since Mini-XML 2.3@ + */ + +mxml_node_t * /* O - New node */ +mxmlNewCDATA(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + const char *data) /* I - Data string */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewCDATA(parent=%p, data=\"%s\")\n", + parent, data ? data : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!data) + return (NULL); + + /* + * Create the node and set the name value... + */ + + if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL) + node->value.element.name = _mxml_strdupf("![CDATA[%s]]", data); + + return (node); +} + + +/* + * 'mxmlNewCustom()' - Create a new custom data node. + * + * The new custom node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * element node has no parent. NULL can be passed when the data in the + * node is not dynamically allocated or is separately managed. + * + * @since Mini-XML 2.1@ + */ + +mxml_node_t * /* O - New node */ +mxmlNewCustom( + mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + void *data, /* I - Pointer to data */ + mxml_custom_destroy_cb_t destroy) /* I - Function to destroy data */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewCustom(parent=%p, data=%p, destroy=%p)\n", parent, + data, destroy); +#endif /* DEBUG */ + + /* + * Create the node and set the value... + */ + + if ((node = mxml_new(parent, MXML_CUSTOM)) != NULL) + { + node->value.custom.data = data; + node->value.custom.destroy = destroy; + } + + return (node); +} + + +/* + * 'mxmlNewElement()' - Create a new element node. + * + * The new element node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * element node has no parent. + */ + +mxml_node_t * /* O - New node */ +mxmlNewElement(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + const char *name) /* I - Name of element */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewElement(parent=%p, name=\"%s\")\n", parent, + name ? name : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!name) + return (NULL); + + /* + * Create the node and set the element name... + */ + + if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL) + node->value.element.name = PhDuplicateBytesZSafe((char *)name); + + return (node); +} + + +/* + * 'mxmlNewInteger()' - Create a new integer node. + * + * The new integer node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * integer node has no parent. + */ + +mxml_node_t * /* O - New node */ +mxmlNewInteger(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + int integer) /* I - Integer value */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewInteger(parent=%p, integer=%d)\n", parent, integer); +#endif /* DEBUG */ + + /* + * Create the node and set the element name... + */ + + if ((node = mxml_new(parent, MXML_INTEGER)) != NULL) + node->value.integer = integer; + + return (node); +} + + +/* + * 'mxmlNewOpaque()' - Create a new opaque string. + * + * The new opaque node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * opaque node has no parent. The opaque string must be nul-terminated and + * is copied into the new node. + */ + +mxml_node_t * /* O - New node */ +mxmlNewOpaque(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + const char *opaque) /* I - Opaque string */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewOpaque(parent=%p, opaque=\"%s\")\n", parent, + opaque ? opaque : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!opaque) + return (NULL); + + /* + * Create the node and set the element name... + */ + + if ((node = mxml_new(parent, MXML_OPAQUE)) != NULL) + node->value.opaque = PhDuplicateBytesZSafe((char *)opaque); + + return (node); +} + + +/* + * 'mxmlNewReal()' - Create a new real number node. + * + * The new real number node is added to the end of the specified parent's + * child list. The constant MXML_NO_PARENT can be used to specify that + * the new real number node has no parent. + */ + +mxml_node_t * /* O - New node */ +mxmlNewReal(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + double real) /* I - Real number value */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewReal(parent=%p, real=%g)\n", parent, real); +#endif /* DEBUG */ + + /* + * Create the node and set the element name... + */ + + if ((node = mxml_new(parent, MXML_REAL)) != NULL) + node->value.real = real; + + return (node); +} + + +/* + * 'mxmlNewText()' - Create a new text fragment node. + * + * The new text node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * text node has no parent. The whitespace parameter is used to specify + * whether leading whitespace is present before the node. The text + * string must be nul-terminated and is copied into the new node. + */ + +mxml_node_t * /* O - New node */ +mxmlNewText(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ + const char *string) /* I - String */ +{ + mxml_node_t *node; /* New node */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewText(parent=%p, whitespace=%d, string=\"%s\")\n", + parent, whitespace, string ? string : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!string) + return (NULL); + + /* + * Create the node and set the text value... + */ + + if ((node = mxml_new(parent, MXML_TEXT)) != NULL) + { + node->value.text.whitespace = whitespace; + node->value.text.string = PhDuplicateBytesZSafe((char *)string); + } + + return (node); +} + + +/* + * 'mxmlNewTextf()' - Create a new formatted text fragment node. + * + * The new text node is added to the end of the specified parent's child + * list. The constant MXML_NO_PARENT can be used to specify that the new + * text node has no parent. The whitespace parameter is used to specify + * whether leading whitespace is present before the node. The format + * string must be nul-terminated and is formatted into the new node. + */ + +mxml_node_t * /* O - New node */ +mxmlNewTextf(mxml_node_t *parent, /* I - Parent node or MXML_NO_PARENT */ + int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ + const char *format, /* I - Printf-style frmat string */ + ...) /* I - Additional args as needed */ +{ + mxml_node_t *node; /* New node */ + va_list ap; /* Pointer to arguments */ + + +#ifdef DEBUG + fprintf(stderr, "mxmlNewTextf(parent=%p, whitespace=%d, format=\"%s\", ...)\n", + parent, whitespace, format ? format : "(null)"); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!format) + return (NULL); + + /* + * Create the node and set the text value... + */ + + if ((node = mxml_new(parent, MXML_TEXT)) != NULL) + { + va_start(ap, format); + + node->value.text.whitespace = whitespace; + node->value.text.string = _mxml_vstrdupf(format, ap); + + va_end(ap); + } + + return (node); +} + + +/* + * 'mxmlRemove()' - Remove a node from its parent. + * + * Does not free memory used by the node - use mxmlDelete() for that. + * This function does nothing if the node has no parent. + */ + +void +mxmlRemove(mxml_node_t *node) /* I - Node to remove */ +{ +#ifdef DEBUG + fprintf(stderr, "mxmlRemove(node=%p)\n", node); +#endif /* DEBUG */ + + /* + * Range check input... + */ + + if (!node || !node->parent) + return; + + /* + * Remove from parent... + */ + +#if DEBUG > 1 + fprintf(stderr, " BEFORE: node->parent=%p\n", node->parent); + if (node->parent) + { + fprintf(stderr, " BEFORE: node->parent->child=%p\n", node->parent->child); + fprintf(stderr, " BEFORE: node->parent->last_child=%p\n", node->parent->last_child); + } + fprintf(stderr, " BEFORE: node->child=%p\n", node->child); + fprintf(stderr, " BEFORE: node->last_child=%p\n", node->last_child); + fprintf(stderr, " BEFORE: node->prev=%p\n", node->prev); + fprintf(stderr, " BEFORE: node->next=%p\n", node->next); +#endif /* DEBUG > 1 */ + + if (node->prev) + node->prev->next = node->next; + else + node->parent->child = node->next; + + if (node->next) + node->next->prev = node->prev; + else + node->parent->last_child = node->prev; + + node->parent = NULL; + node->prev = NULL; + node->next = NULL; + +#if DEBUG > 1 + fprintf(stderr, " AFTER: node->parent=%p\n", node->parent); + if (node->parent) + { + fprintf(stderr, " AFTER: node->parent->child=%p\n", node->parent->child); + fprintf(stderr, " AFTER: node->parent->last_child=%p\n", node->parent->last_child); + } + fprintf(stderr, " AFTER: node->child=%p\n", node->child); + fprintf(stderr, " AFTER: node->last_child=%p\n", node->last_child); + fprintf(stderr, " AFTER: node->prev=%p\n", node->prev); + fprintf(stderr, " AFTER: node->next=%p\n", node->next); +#endif /* DEBUG > 1 */ +} + + +/* + * 'mxmlNewXML()' - Create a new XML document tree. + * + * The "version" argument specifies the version number to put in the + * ?xml element node. If NULL, version 1.0 is assumed. + * + * @since Mini-XML 2.3@ + */ + +mxml_node_t * /* O - New ?xml node */ +mxmlNewXML(const char *version) /* I - Version number to use */ +{ + char element[1024]; /* Element text */ + + + _snprintf(element, sizeof(element), "?xml version=\"%s\" encoding=\"utf-8\"?", + version ? version : "1.0"); + + return (mxmlNewElement(NULL, element)); +} + + +/* + * 'mxmlRelease()' - Release a node. + * + * When the reference count reaches zero, the node (and any children) + * is deleted via mxmlDelete(). + * + * @since Mini-XML 2.3@ + */ + +int /* O - New reference count */ +mxmlRelease(mxml_node_t *node) /* I - Node */ +{ + if (node) + { + if ((-- node->ref_count) <= 0) + { + mxmlDelete(node); + return (0); + } + else + return (node->ref_count); + } + else + return (-1); +} + + +/* + * 'mxmlRetain()' - Retain a node. + * + * @since Mini-XML 2.3@ + */ + +int /* O - New reference count */ +mxmlRetain(mxml_node_t *node) /* I - Node */ +{ + if (node) + return (++ node->ref_count); + else + return (-1); +} + + +/* + * 'mxml_new()' - Create a new node. + */ + +static mxml_node_t * /* O - New node */ +mxml_new(mxml_node_t *parent, /* I - Parent node */ + mxml_type_t type) /* I - Node type */ +{ + mxml_node_t *node; /* New node */ + + +#if DEBUG > 1 + fprintf(stderr, "mxml_new(parent=%p, type=%d)\n", parent, type); +#endif /* DEBUG > 1 */ + + /* + * Allocate memory for the node... + */ + + if ((node = PhAllocateExSafe(sizeof(mxml_node_t), HEAP_ZERO_MEMORY)) == NULL) + { +#if DEBUG > 1 + fputs(" returning NULL\n", stderr); +#endif /* DEBUG > 1 */ + + return (NULL); + } + +#if DEBUG > 1 + fprintf(stderr, " returning %p\n", node); +#endif /* DEBUG > 1 */ + + /* + * Set the node type... + */ + + node->type = type; + node->ref_count = 1; + + /* + * Add to the parent if present... + */ + + if (parent) + mxmlAdd(parent, MXML_ADD_AFTER, MXML_ADD_TO_PARENT, node); + + /* + * Return the new node... + */ + + return (node); +} + + +/* + * End of "$Id: mxml-node.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-private.c b/ProcessHacker/mxml/mxml-private.c new file mode 100644 index 0000000..6f6e480 --- /dev/null +++ b/ProcessHacker/mxml/mxml-private.c @@ -0,0 +1,323 @@ +/* + * "$Id: mxml-private.c 451 2014-01-04 21:50:06Z msweet $" + * + * Private functions for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include "mxml-private.h" + + +/* + * Some crazy people think that unloading a shared object is a good or safe + * thing to do. Unfortunately, most objects are simply *not* safe to unload + * and bad things *will* happen. + * + * The following mess of conditional code allows us to provide a destructor + * function in Mini-XML for our thread-global storage so that it can possibly + * be unloaded safely, although since there is no standard way to do so I + * can't even provide any guarantees that you can do it safely on all platforms. + * + * This code currently supports AIX, HP-UX, Linux, Mac OS X, Solaris, and + * Windows. It might work on the BSDs and IRIX, but I haven't tested that. + */ + +#if defined(__sun) || defined(_AIX) +# pragma fini(_mxml_fini) +# define _MXML_FINI _mxml_fini +#elif defined(__hpux) +# pragma FINI _mxml_fini +# define _MXML_FINI _mxml_fini +#elif defined(__GNUC__) /* Linux and Mac OS X */ +# define _MXML_FINI __attribute((destructor)) _mxml_fini +#else +# define _MXML_FINI _fini +#endif /* __sun */ + + +/* + * 'mxml_error()' - Display an error message. + */ + +void +mxml_error(const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Pointer to arguments */ + char s[1024]; /* Message string */ + _mxml_global_t *global = _mxml_global(); + /* Global data */ + + + /* + * Range check input... + */ + + if (!format) + return; + + /* + * Format the error message string... + */ + + va_start(ap, format); + + vsnprintf(s, sizeof(s), format, ap); + + va_end(ap); + + /* + * And then display the error message... + */ + + if (global->error_cb) + (*global->error_cb)(s); + else + fprintf(stderr, "mxml: %s\n", s); +} + + +/* + * 'mxml_ignore_cb()' - Default callback for ignored values. + */ + +mxml_type_t /* O - Node type */ +mxml_ignore_cb(mxml_node_t *node) /* I - Current node */ +{ + (void)node; + + return (MXML_IGNORE); +} + + +/* + * 'mxml_integer_cb()' - Default callback for integer values. + */ + +mxml_type_t /* O - Node type */ +mxml_integer_cb(mxml_node_t *node) /* I - Current node */ +{ + (void)node; + + return (MXML_INTEGER); +} + + +/* + * 'mxml_opaque_cb()' - Default callback for opaque values. + */ + +mxml_type_t /* O - Node type */ +mxml_opaque_cb(mxml_node_t *node) /* I - Current node */ +{ + (void)node; + + return (MXML_OPAQUE); +} + + +/* + * 'mxml_real_cb()' - Default callback for real number values. + */ + +mxml_type_t /* O - Node type */ +mxml_real_cb(mxml_node_t *node) /* I - Current node */ +{ + (void)node; + + return (MXML_REAL); +} + + +#ifdef HAVE_PTHREAD_H /**** POSIX threading ****/ +# include + +static pthread_key_t _mxml_key = -1; /* Thread local storage key */ +static pthread_once_t _mxml_key_once = PTHREAD_ONCE_INIT; + /* One-time initialization object */ +static void _mxml_init(void); +static void _mxml_destructor(void *g); + + +/* + * '_mxml_destructor()' - Free memory used for globals... + */ + +static void +_mxml_destructor(void *g) /* I - Global data */ +{ + free(g); +} + + +/* + * '_mxml_fini()' - Clean up when unloaded. + */ + +static void +_MXML_FINI(void) +{ + _mxml_global_t *global; /* Global data */ + + + if (_mxml_key != -1) + { + if ((global = (_mxml_global_t *)pthread_getspecific(_mxml_key)) != NULL) + _mxml_destructor(global); + + pthread_key_delete(_mxml_key); + _mxml_key = -1; + } +} + + +/* + * '_mxml_global()' - Get global data. + */ + +_mxml_global_t * /* O - Global data */ +_mxml_global(void) +{ + _mxml_global_t *global; /* Global data */ + + + pthread_once(&_mxml_key_once, _mxml_init); + + if ((global = (_mxml_global_t *)pthread_getspecific(_mxml_key)) == NULL) + { + global = (_mxml_global_t *)calloc(1, sizeof(_mxml_global_t)); + pthread_setspecific(_mxml_key, global); + + global->num_entity_cbs = 1; + global->entity_cbs[0] = _mxml_entity_cb; + global->wrap = 72; + } + + return (global); +} + + +/* + * '_mxml_init()' - Initialize global data... + */ + +static void +_mxml_init(void) +{ + pthread_key_create(&_mxml_key, _mxml_destructor); +} + + +#elif defined(WIN32) && defined(MXML1_EXPORTS) /**** WIN32 threading ****/ +# include + +static DWORD _mxml_tls_index; /* Index for global storage */ + + +/* + * 'DllMain()' - Main entry for library. + */ + +BOOL WINAPI /* O - Success/failure */ +DllMain(HINSTANCE hinst, /* I - DLL module handle */ + DWORD reason, /* I - Reason */ + LPVOID reserved) /* I - Unused */ +{ + _mxml_global_t *global; /* Global data */ + + + (void)hinst; + (void)reserved; + + switch (reason) + { + case DLL_PROCESS_ATTACH : /* Called on library initialization */ + if ((_mxml_tls_index = TlsAlloc()) == TLS_OUT_OF_INDEXES) + return (FALSE); + break; + + case DLL_THREAD_DETACH : /* Called when a thread terminates */ + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) != NULL) + free(global); + break; + + case DLL_PROCESS_DETACH : /* Called when library is unloaded */ + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) != NULL) + free(global); + + TlsFree(_mxml_tls_index); + break; + + default: + break; + } + + return (TRUE); +} + + +/* + * '_mxml_global()' - Get global data. + */ + +_mxml_global_t * /* O - Global data */ +_mxml_global(void) +{ + _mxml_global_t *global; /* Global data */ + + + if ((global = (_mxml_global_t *)TlsGetValue(_mxml_tls_index)) == NULL) + { + global = (_mxml_global_t *)calloc(1, sizeof(_mxml_global_t)); + + global->num_entity_cbs = 1; + global->entity_cbs[0] = _mxml_entity_cb; + global->wrap = 72; + + TlsSetValue(_mxml_tls_index, (LPVOID)global); + } + + return (global); +} + + +#else /**** No threading ****/ +/* + * '_mxml_global()' - Get global data. + */ + +_mxml_global_t * /* O - Global data */ +_mxml_global(void) +{ + static _mxml_global_t global = /* Global data */ + { + NULL, /* error_cb */ + 1, /* num_entity_cbs */ + { _mxml_entity_cb }, /* entity_cbs */ + 72, /* wrap */ + NULL, /* custom_load_cb */ + NULL /* custom_save_cb */ + }; + + + return (&global); +} +#endif /* HAVE_PTHREAD_H */ + + +/* + * End of "$Id: mxml-private.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-private.h b/ProcessHacker/mxml/mxml-private.h new file mode 100644 index 0000000..c5e4e6b --- /dev/null +++ b/ProcessHacker/mxml/mxml-private.h @@ -0,0 +1,50 @@ +/* + * "$Id: mxml-private.h 451 2014-01-04 21:50:06Z msweet $" + * + * Private definitions for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include "config.h" +#include "mxml.h" + + +/* + * Global, per-thread data... + */ + +typedef struct _mxml_global_s +{ + void (*error_cb)(const char *); + int num_entity_cbs; + int (*entity_cbs[100])(const char *name); + int wrap; + mxml_custom_load_cb_t custom_load_cb; + mxml_custom_save_cb_t custom_save_cb; +} _mxml_global_t; + + +/* + * Functions... + */ + +extern _mxml_global_t *_mxml_global(void); +extern int _mxml_entity_cb(const char *name); + + +/* + * End of "$Id: mxml-private.h 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-search.c b/ProcessHacker/mxml/mxml-search.c new file mode 100644 index 0000000..ec7ef3f --- /dev/null +++ b/ProcessHacker/mxml/mxml-search.c @@ -0,0 +1,280 @@ +/* + * "$Id: mxml-search.c 451 2014-01-04 21:50:06Z msweet $" + * + * Search/navigation functions for Mini-XML, a small XML-like file + * parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include "config.h" +#include "mxml.h" + + +/* + * 'mxmlFindElement()' - Find the named element. + * + * The search is constrained by the name, attribute name, and value; any + * NULL names or values are treated as wildcards, so different kinds of + * searches can be implemented by looking for all elements of a given name + * or all elements with a specific attribute. The descend argument determines + * whether the search descends into child nodes; normally you will use + * MXML_DESCEND_FIRST for the initial search and MXML_NO_DESCEND to find + * additional direct descendents of the node. The top node argument + * constrains the search to a particular node's children. + */ + +mxml_node_t * /* O - Element node or NULL */ +mxmlFindElement(mxml_node_t *node, /* I - Current node */ + mxml_node_t *top, /* I - Top node */ + const char *name, /* I - Element name or NULL for any */ + const char *attr, /* I - Attribute name, or NULL for none */ + const char *value, /* I - Attribute value, or NULL for any */ + int descend) /* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */ +{ + const char *temp; /* Current attribute value */ + + + /* + * Range check input... + */ + + if (!node || !top || (!attr && value)) + return (NULL); + + /* + * Start with the next node... + */ + + node = mxmlWalkNext(node, top, descend); + + /* + * Loop until we find a matching element... + */ + + while (node != NULL) + { + /* + * See if this node matches... + */ + + if (node->type == MXML_ELEMENT && + node->value.element.name && + (!name || !strcmp(node->value.element.name, name))) + { + /* + * See if we need to check for an attribute... + */ + + if (!attr) + return (node); /* No attribute search, return it... */ + + /* + * Check for the attribute... + */ + + if ((temp = mxmlElementGetAttr(node, attr)) != NULL) + { + /* + * OK, we have the attribute, does it match? + */ + + if (!value || !strcmp(value, temp)) + return (node); /* Yes, return it... */ + } + } + + /* + * No match, move on to the next node... + */ + + if (descend == MXML_DESCEND) + node = mxmlWalkNext(node, top, MXML_DESCEND); + else + node = node->next; + } + + return (NULL); +} + + +/* + * 'mxmlFindPath()' - Find a node with the given path. + * + * The "path" is a slash-separated list of element names. The name "*" is + * considered a wildcard for one or more levels of elements. For example, + * "foo/one/two", "bar/two/one", "*\/one", and so forth. + * + * The first child node of the found node is returned if the given node has + * children and the first child is a value node. + * + * @since Mini-XML 2.7@ + */ + +mxml_node_t * /* O - Found node or NULL */ +mxmlFindPath(mxml_node_t *top, /* I - Top node */ + const char *path) /* I - Path to element */ +{ + mxml_node_t *node; /* Current node */ + char element[256]; /* Current element name */ + const char *pathsep; /* Separator in path */ + int descend; /* mxmlFindElement option */ + + + /* + * Range check input... + */ + + if (!top || !path || !*path) + return (NULL); + + /* + * Search each element in the path... + */ + + node = top; + while (*path) + { + /* + * Handle wildcards... + */ + + if (!strncmp(path, "*/", 2)) + { + path += 2; + descend = MXML_DESCEND; + } + else + descend = MXML_DESCEND_FIRST; + + /* + * Get the next element in the path... + */ + + if ((pathsep = strchr(path, '/')) == NULL) + pathsep = path + strlen(path); + + if (pathsep == path || (pathsep - path) >= sizeof(element)) + return (NULL); + + memcpy(element, path, pathsep - path); + element[pathsep - path] = '\0'; + + if (*pathsep) + path = pathsep + 1; + else + path = pathsep; + + /* + * Search for the element... + */ + + if ((node = mxmlFindElement(node, node, element, NULL, NULL, + descend)) == NULL) + return (NULL); + } + + /* + * If we get this far, return the node or its first child... + */ + + if (node->child && node->child->type != MXML_ELEMENT) + return (node->child); + else + return (node); +} + + +/* + * 'mxmlWalkNext()' - Walk to the next logical node in the tree. + * + * The descend argument controls whether the first child is considered + * to be the next node. The top node argument constrains the walk to + * the node's children. + */ + +mxml_node_t * /* O - Next node or NULL */ +mxmlWalkNext(mxml_node_t *node, /* I - Current node */ + mxml_node_t *top, /* I - Top node */ + int descend) /* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */ +{ + if (!node) + return (NULL); + else if (node->child && descend) + return (node->child); + else if (node == top) + return (NULL); + else if (node->next) + return (node->next); + else if (node->parent && node->parent != top) + { + node = node->parent; + + while (!node->next) + if (node->parent == top || !node->parent) + return (NULL); + else + node = node->parent; + + return (node->next); + } + else + return (NULL); +} + + +/* + * 'mxmlWalkPrev()' - Walk to the previous logical node in the tree. + * + * The descend argument controls whether the previous node's last child + * is considered to be the previous node. The top node argument constrains + * the walk to the node's children. + */ + +mxml_node_t * /* O - Previous node or NULL */ +mxmlWalkPrev(mxml_node_t *node, /* I - Current node */ + mxml_node_t *top, /* I - Top node */ + int descend) /* I - Descend into tree - MXML_DESCEND, MXML_NO_DESCEND, or MXML_DESCEND_FIRST */ +{ + if (!node || node == top) + return (NULL); + else if (node->prev) + { + if (node->prev->last_child && descend) + { + /* + * Find the last child under the previous node... + */ + + node = node->prev->last_child; + + while (node->last_child) + node = node->last_child; + + return (node); + } + else + return (node->prev); + } + else if (node->parent != top) + return (node->parent); + else + return (NULL); +} + + +/* + * End of "$Id: mxml-search.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-set.c b/ProcessHacker/mxml/mxml-set.c new file mode 100644 index 0000000..be7593f --- /dev/null +++ b/ProcessHacker/mxml/mxml-set.c @@ -0,0 +1,338 @@ +/* + * "$Id: mxml-set.c 451 2014-01-04 21:50:06Z msweet $" + * + * Node set functions for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include +#include "config.h" +#include "mxml.h" + + +/* + * 'mxmlSetCDATA()' - Set the element name of a CDATA node. + * + * The node is not changed if it (or its first child) is not a CDATA element node. + * + * @since Mini-XML 2.3@ + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetCDATA(mxml_node_t *node, /* I - Node to set */ + const char *data) /* I - New data string */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + strncmp(node->value.element.name, "![CDATA[", 8) && + node->child && node->child->type == MXML_ELEMENT && + !strncmp(node->child->value.element.name, "![CDATA[", 8)) + node = node->child; + + if (!node || node->type != MXML_ELEMENT || !data || + strncmp(node->value.element.name, "![CDATA[", 8)) + return (-1); + + /* + * Free any old element value and set the new value... + */ + + if (node->value.element.name) + PhFree(node->value.element.name); + + node->value.element.name = _mxml_strdupf("![CDATA[%s]]", data); + + return (0); +} + + +/* + * 'mxmlSetCustom()' - Set the data and destructor of a custom data node. + * + * The node is not changed if it (or its first child) is not a custom node. + * + * @since Mini-XML 2.1@ + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetCustom( + mxml_node_t *node, /* I - Node to set */ + void *data, /* I - New data pointer */ + mxml_custom_destroy_cb_t destroy) /* I - New destructor function */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_CUSTOM) + node = node->child; + + if (!node || node->type != MXML_CUSTOM) + return (-1); + + /* + * Free any old element value and set the new value... + */ + + if (node->value.custom.data && node->value.custom.destroy) + (*(node->value.custom.destroy))(node->value.custom.data); + + node->value.custom.data = data; + node->value.custom.destroy = destroy; + + return (0); +} + + +/* + * 'mxmlSetElement()' - Set the name of an element node. + * + * The node is not changed if it is not an element node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetElement(mxml_node_t *node, /* I - Node to set */ + const char *name) /* I - New name string */ +{ + /* + * Range check input... + */ + + if (!node || node->type != MXML_ELEMENT || !name) + return (-1); + + /* + * Free any old element value and set the new value... + */ + + if (node->value.element.name) + PhFree(node->value.element.name); + + node->value.element.name = PhDuplicateBytesZSafe((char *)name); + + return (0); +} + + +/* + * 'mxmlSetInteger()' - Set the value of an integer node. + * + * The node is not changed if it (or its first child) is not an integer node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetInteger(mxml_node_t *node, /* I - Node to set */ + int integer) /* I - Integer value */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_INTEGER) + node = node->child; + + if (!node || node->type != MXML_INTEGER) + return (-1); + + /* + * Set the new value and return... + */ + + node->value.integer = integer; + + return (0); +} + + +/* + * 'mxmlSetOpaque()' - Set the value of an opaque node. + * + * The node is not changed if it (or its first child) is not an opaque node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetOpaque(mxml_node_t *node, /* I - Node to set */ + const char *opaque) /* I - Opaque string */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_OPAQUE) + node = node->child; + + if (!node || node->type != MXML_OPAQUE || !opaque) + return (-1); + + /* + * Free any old opaque value and set the new value... + */ + + if (node->value.opaque) + PhFree(node->value.opaque); + + node->value.opaque = PhDuplicateBytesZSafe((char *)opaque); + + return (0); +} + + +/* + * 'mxmlSetReal()' - Set the value of a real number node. + * + * The node is not changed if it (or its first child) is not a real number node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetReal(mxml_node_t *node, /* I - Node to set */ + double real) /* I - Real number value */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_REAL) + node = node->child; + + if (!node || node->type != MXML_REAL) + return (-1); + + /* + * Set the new value and return... + */ + + node->value.real = real; + + return (0); +} + + +/* + * 'mxmlSetText()' - Set the value of a text node. + * + * The node is not changed if it (or its first child) is not a text node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetText(mxml_node_t *node, /* I - Node to set */ + int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ + const char *string) /* I - String */ +{ + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_TEXT) + node = node->child; + + if (!node || node->type != MXML_TEXT || !string) + return (-1); + + /* + * Free any old string value and set the new value... + */ + + if (node->value.text.string) + PhFree(node->value.text.string); + + node->value.text.whitespace = whitespace; + node->value.text.string = PhDuplicateBytesZSafe((char *)string); + + return (0); +} + + +/* + * 'mxmlSetTextf()' - Set the value of a text node to a formatted string. + * + * The node is not changed if it (or its first child) is not a text node. + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetTextf(mxml_node_t *node, /* I - Node to set */ + int whitespace, /* I - 1 = leading whitespace, 0 = no whitespace */ + const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Pointer to arguments */ + + + /* + * Range check input... + */ + + if (node && node->type == MXML_ELEMENT && + node->child && node->child->type == MXML_TEXT) + node = node->child; + + if (!node || node->type != MXML_TEXT || !format) + return (-1); + + /* + * Free any old string value and set the new value... + */ + + if (node->value.text.string) + PhFree(node->value.text.string); + + va_start(ap, format); + + node->value.text.whitespace = whitespace; + node->value.text.string = _mxml_strdupf(format, ap); + + va_end(ap); + + return (0); +} + + +/* + * 'mxmlSetUserData()' - Set the user data pointer for a node. + * + * @since Mini-XML 2.7@ + */ + +int /* O - 0 on success, -1 on failure */ +mxmlSetUserData(mxml_node_t *node, /* I - Node to set */ + void *data) /* I - User data pointer */ +{ + /* + * Range check input... + */ + + if (!node) + return (-1); + + /* + * Set the user data pointer and return... + */ + + node->user_data = data; + return (0); +} + + +/* + * End of "$Id: mxml-set.c 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml-string.c b/ProcessHacker/mxml/mxml-string.c new file mode 100644 index 0000000..ee4efc2 --- /dev/null +++ b/ProcessHacker/mxml/mxml-string.c @@ -0,0 +1,470 @@ +/* + * "$Id: mxml-string.c 454 2014-01-05 03:25:07Z msweet $" + * + * String functions for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Include necessary headers... + */ + +#include +#include "config.h" + + +/* + * The va_copy macro is part of C99, but many compilers don't implement it. + * Provide a "direct assignment" implmentation when va_copy isn't defined... + */ + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst,src) __va_copy(dst,src) +# else +# define va_copy(dst,src) memcpy(&dst, src, sizeof(va_list)) +# endif /* __va_copy */ +#endif /* va_copy */ + + +#ifndef HAVE_SNPRINTF +/* + * '_mxml_snprintf()' - Format a string. + */ + +int /* O - Number of bytes formatted */ +_mxml_snprintf(char *buffer, /* I - Output buffer */ + size_t bufsize, /* I - Size of output buffer */ + const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Argument list */ + int bytes; /* Number of bytes formatted */ + + + va_start(ap, format); + bytes = vsnprintf(buffer, bufsize, format, ap); + va_end(ap); + + return (bytes); +} +#endif /* !HAVE_SNPRINTF */ + + +/* + * '_mxml_strdup()' - Duplicate a string. + */ + +#ifndef HAVE_STRDUP +char * /* O - New string pointer */ +_mxml_strdup(const char *s) /* I - String to duplicate */ +{ + char *t; /* New string pointer */ + + + if (s == NULL) + return (NULL); + + if ((t = malloc(strlen(s) + 1)) == NULL) + return (NULL); + + return (strcpy(t, s)); +} +#endif /* !HAVE_STRDUP */ + + +/* + * '_mxml_strdupf()' - Format and duplicate a string. + */ + +char * /* O - New string pointer */ +_mxml_strdupf(const char *format, /* I - Printf-style format string */ + ...) /* I - Additional arguments as needed */ +{ + va_list ap; /* Pointer to additional arguments */ + char *s; /* Pointer to formatted string */ + + + /* + * Get a pointer to the additional arguments, format the string, + * and return it... + */ + + va_start(ap, format); + s = _mxml_vstrdupf(format, ap); + va_end(ap); + + return (s); +} + + +#ifndef HAVE_VSNPRINTF +/* + * '_mxml_vsnprintf()' - Format a string into a fixed size buffer. + */ + +int /* O - Number of bytes formatted */ +_mxml_vsnprintf(char *buffer, /* O - Output buffer */ + size_t bufsize, /* O - Size of output buffer */ + const char *format, /* I - Printf-style format string */ + va_list ap) /* I - Pointer to additional arguments */ +{ + char *bufptr, /* Pointer to position in buffer */ + *bufend, /* Pointer to end of buffer */ + sign, /* Sign of format width */ + size, /* Size character (h, l, L) */ + type; /* Format type character */ + int width, /* Width of field */ + prec; /* Number of characters of precision */ + char tformat[100], /* Temporary format string for sprintf() */ + *tptr, /* Pointer into temporary format */ + temp[1024]; /* Buffer for formatted numbers */ + char *s; /* Pointer to string */ + int slen; /* Length of string */ + int bytes; /* Total number of bytes needed */ + + + /* + * Loop through the format string, formatting as needed... + */ + + bufptr = buffer; + bufend = buffer + bufsize - 1; + bytes = 0; + + while (*format) + { + if (*format == '%') + { + tptr = tformat; + *tptr++ = *format++; + + if (*format == '%') + { + if (bufptr && bufptr < bufend) + *bufptr++ = *format; + bytes ++; + format ++; + continue; + } + else if (strchr(" -+#\'", *format)) + { + *tptr++ = *format; + sign = *format++; + } + else + sign = 0; + + if (*format == '*') + { + /* + * Get width from argument... + */ + + format ++; + width = va_arg(ap, int); + + snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width); + tptr += strlen(tptr); + } + else + { + width = 0; + + while (isdigit(*format & 255)) + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + width = width * 10 + *format++ - '0'; + } + } + + if (*format == '.') + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + format ++; + + if (*format == '*') + { + /* + * Get precision from argument... + */ + + format ++; + prec = va_arg(ap, int); + + snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec); + tptr += strlen(tptr); + } + else + { + prec = 0; + + while (isdigit(*format & 255)) + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + prec = prec * 10 + *format++ - '0'; + } + } + } + else + prec = -1; + + if (*format == 'l' && format[1] == 'l') + { + size = 'L'; + + if (tptr < (tformat + sizeof(tformat) - 2)) + { + *tptr++ = 'l'; + *tptr++ = 'l'; + } + + format += 2; + } + else if (*format == 'h' || *format == 'l' || *format == 'L') + { + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + size = *format++; + } + + if (!*format) + break; + + if (tptr < (tformat + sizeof(tformat) - 1)) + *tptr++ = *format; + + type = *format++; + *tptr = '\0'; + + switch (type) + { + case 'E' : /* Floating point formats */ + case 'G' : + case 'e' : + case 'f' : + case 'g' : + if ((width + 2) > sizeof(temp)) + break; + + sprintf(temp, tformat, va_arg(ap, double)); + + bytes += strlen(temp); + + if (bufptr) + { + if ((bufptr + strlen(temp)) > bufend) + { + strncpy(bufptr, temp, (size_t)(bufend - bufptr)); + bufptr = bufend; + } + else + { + strcpy(bufptr, temp); + bufptr += strlen(temp); + } + } + break; + + case 'B' : /* Integer formats */ + case 'X' : + case 'b' : + case 'd' : + case 'i' : + case 'o' : + case 'u' : + case 'x' : + if ((width + 2) > sizeof(temp)) + break; + +#ifdef HAVE_LONG_LONG + if (size == 'L') + sprintf(temp, tformat, va_arg(ap, long long)); + else +#endif /* HAVE_LONG_LONG */ + sprintf(temp, tformat, va_arg(ap, int)); + + bytes += strlen(temp); + + if (bufptr) + { + if ((bufptr + strlen(temp)) > bufend) + { + strncpy(bufptr, temp, (size_t)(bufend - bufptr)); + bufptr = bufend; + } + else + { + strcpy(bufptr, temp); + bufptr += strlen(temp); + } + } + break; + + case 'p' : /* Pointer value */ + if ((width + 2) > sizeof(temp)) + break; + + sprintf(temp, tformat, va_arg(ap, void *)); + + bytes += strlen(temp); + + if (bufptr) + { + if ((bufptr + strlen(temp)) > bufend) + { + strncpy(bufptr, temp, (size_t)(bufend - bufptr)); + bufptr = bufend; + } + else + { + strcpy(bufptr, temp); + bufptr += strlen(temp); + } + } + break; + + case 'c' : /* Character or character array */ + bytes += width; + + if (bufptr) + { + if (width <= 1) + *bufptr++ = va_arg(ap, int); + else + { + if ((bufptr + width) > bufend) + width = bufend - bufptr; + + memcpy(bufptr, va_arg(ap, char *), (size_t)width); + bufptr += width; + } + } + break; + + case 's' : /* String */ + if ((s = va_arg(ap, char *)) == NULL) + s = "(null)"; + + slen = strlen(s); + if (slen > width && prec != width) + width = slen; + + bytes += width; + + if (bufptr) + { + if ((bufptr + width) > bufend) + width = bufend - bufptr; + + if (slen > width) + slen = width; + + if (sign == '-') + { + strncpy(bufptr, s, (size_t)slen); + memset(bufptr + slen, ' ', (size_t)(width - slen)); + } + else + { + memset(bufptr, ' ', (size_t)(width - slen)); + strncpy(bufptr + width - slen, s, (size_t)slen); + } + + bufptr += width; + } + break; + + case 'n' : /* Output number of chars so far */ + *(va_arg(ap, int *)) = bytes; + break; + } + } + else + { + bytes ++; + + if (bufptr && bufptr < bufend) + *bufptr++ = *format; + + format ++; + } + } + + /* + * Nul-terminate the string and return the number of characters needed. + */ + + *bufptr = '\0'; + + return (bytes); +} +#endif /* !HAVE_VSNPRINTF */ + + +/* + * '_mxml_vstrdupf()' - Format and duplicate a string. + */ + +char * /* O - New string pointer */ +_mxml_vstrdupf(const char *format, /* I - Printf-style format string */ + va_list ap) /* I - Pointer to additional arguments */ +{ + int bytes; /* Number of bytes required */ + char *buffer, /* String buffer */ + temp[256]; /* Small buffer for first vsnprintf */ + va_list apcopy; /* Copy of argument list */ + + + /* + * First format with a tiny buffer; this will tell us how many bytes are + * needed... + */ + + va_copy(apcopy, ap); + bytes = vsnprintf(temp, sizeof(temp), format, apcopy); + + if (bytes < sizeof(temp)) + { + /* + * Hey, the formatted string fits in the tiny buffer, so just dup that... + */ + + return (PhDuplicateBytesZSafe(temp)); + } + + /* + * Allocate memory for the whole thing and reformat to the new, larger + * buffer... + */ + + if ((buffer = PhAllocateExSafe(bytes + 1, HEAP_ZERO_MEMORY)) != NULL) + vsnprintf(buffer, bytes + 1, format, ap); + + /* + * Return the new string... + */ + + return (buffer); +} + + +/* + * End of "$Id: mxml-string.c 454 2014-01-05 03:25:07Z msweet $". + */ diff --git a/ProcessHacker/mxml/mxml.h b/ProcessHacker/mxml/mxml.h new file mode 100644 index 0000000..5d79656 --- /dev/null +++ b/ProcessHacker/mxml/mxml.h @@ -0,0 +1,338 @@ +/* + * "$Id: mxml.h 451 2014-01-04 21:50:06Z msweet $" + * + * Header file for Mini-XML, a small XML-like file parsing library. + * + * Copyright 2003-2014 by Michael R Sweet. + * + * These coded instructions, statements, and computer programs are the + * property of Michael R Sweet and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "COPYING" + * which should have been included with this file. If this file is + * missing or damaged, see the license at: + * + * http://www.msweet.org/projects.php/Mini-XML + */ + +/* + * Prevent multiple inclusion... + */ + +#ifndef _mxml_h_ +# define _mxml_h_ + +/* + * Include necessary headers... + */ + +# include +# include +# include +# include +# include +# include + +#ifdef _PHAPP_ +#define PHMXMLAPI __declspec(dllexport) +#else +#define PHMXMLAPI +#endif + +/* + * Constants... + */ + +# define MXML_MAJOR_VERSION 2 /* Major version number */ +# define MXML_MINOR_VERSION 8 /* Minor version number */ + +# define MXML_TAB 8 /* Tabs every N columns */ + +# define MXML_NO_CALLBACK 0 /* Don't use a type callback */ +# define MXML_INTEGER_CALLBACK mxml_integer_cb + /* Treat all data as integers */ +# define MXML_OPAQUE_CALLBACK mxml_opaque_cb + /* Treat all data as opaque */ +# define MXML_REAL_CALLBACK mxml_real_cb + /* Treat all data as real numbers */ +# define MXML_TEXT_CALLBACK 0 /* Treat all data as text */ +# define MXML_IGNORE_CALLBACK mxml_ignore_cb + /* Ignore all non-element content */ + +# define MXML_NO_PARENT 0 /* No parent for the node */ + +# define MXML_DESCEND 1 /* Descend when finding/walking */ +# define MXML_NO_DESCEND 0 /* Don't descend when finding/walking */ +# define MXML_DESCEND_FIRST -1 /* Descend for first find */ + +# define MXML_WS_BEFORE_OPEN 0 /* Callback for before open tag */ +# define MXML_WS_AFTER_OPEN 1 /* Callback for after open tag */ +# define MXML_WS_BEFORE_CLOSE 2 /* Callback for before close tag */ +# define MXML_WS_AFTER_CLOSE 3 /* Callback for after close tag */ + +# define MXML_ADD_BEFORE 0 /* Add node before specified node */ +# define MXML_ADD_AFTER 1 /* Add node after specified node */ +# define MXML_ADD_TO_PARENT NULL /* Add node relative to parent */ + + +/* + * Data types... + */ + +typedef enum mxml_sax_event_e /**** SAX event type. ****/ +{ + MXML_SAX_CDATA, /* CDATA node */ + MXML_SAX_COMMENT, /* Comment node */ + MXML_SAX_DATA, /* Data node */ + MXML_SAX_DIRECTIVE, /* Processing directive node */ + MXML_SAX_ELEMENT_CLOSE, /* Element closed */ + MXML_SAX_ELEMENT_OPEN /* Element opened */ +} mxml_sax_event_t; + +typedef enum mxml_type_e /**** The XML node type. ****/ +{ + MXML_IGNORE = -1, /* Ignore/throw away node @since Mini-XML 2.3@ */ + MXML_ELEMENT, /* XML element with attributes */ + MXML_INTEGER, /* Integer value */ + MXML_OPAQUE, /* Opaque string */ + MXML_REAL, /* Real value */ + MXML_TEXT, /* Text fragment */ + MXML_CUSTOM /* Custom data @since Mini-XML 2.1@ */ +} mxml_type_t; + +typedef void (*mxml_custom_destroy_cb_t)(void *); + /**** Custom data destructor ****/ + +typedef void (*mxml_error_cb_t)(const char *); + /**** Error callback function ****/ + +typedef struct mxml_attr_s /**** An XML element attribute value. @private@ ****/ +{ + char *name; /* Attribute name */ + char *value; /* Attribute value */ +} mxml_attr_t; + +typedef struct mxml_element_s /**** An XML element value. @private@ ****/ +{ + char *name; /* Name of element */ + int num_attrs; /* Number of attributes */ + mxml_attr_t *attrs; /* Attributes */ +} mxml_element_t; + +typedef struct mxml_text_s /**** An XML text value. @private@ ****/ +{ + int whitespace; /* Leading whitespace? */ + char *string; /* Fragment string */ +} mxml_text_t; + +typedef struct mxml_custom_s /**** An XML custom value. @private@ ****/ +{ + void *data; /* Pointer to (allocated) custom data */ + mxml_custom_destroy_cb_t destroy; /* Pointer to destructor function */ +} mxml_custom_t; + +typedef union mxml_value_u /**** An XML node value. @private@ ****/ +{ + mxml_element_t element; /* Element */ + int integer; /* Integer number */ + char *opaque; /* Opaque string */ + double real; /* Real number */ + mxml_text_t text; /* Text fragment */ + mxml_custom_t custom; /* Custom data @since Mini-XML 2.1@ */ +} mxml_value_t; + +struct mxml_node_s /**** An XML node. @private@ ****/ +{ + mxml_type_t type; /* Node type */ + struct mxml_node_s *next; /* Next node under same parent */ + struct mxml_node_s *prev; /* Previous node under same parent */ + struct mxml_node_s *parent; /* Parent node */ + struct mxml_node_s *child; /* First child node */ + struct mxml_node_s *last_child; /* Last child node */ + mxml_value_t value; /* Node value */ + int ref_count; /* Use count */ + void *user_data; /* User data */ +}; + +typedef struct mxml_node_s mxml_node_t; /**** An XML node. ****/ + +struct mxml_index_s /**** An XML node index. @private@ ****/ +{ + char *attr; /* Attribute used for indexing or NULL */ + int num_nodes; /* Number of nodes in index */ + int alloc_nodes; /* Allocated nodes in index */ + int cur_node; /* Current node */ + mxml_node_t **nodes; /* Node array */ +}; + +typedef struct mxml_index_s mxml_index_t; + /**** An XML node index. ****/ + +typedef int (*mxml_custom_load_cb_t)(mxml_node_t *, const char *); + /**** Custom data load callback function ****/ + +typedef char *(*mxml_custom_save_cb_t)(mxml_node_t *); + /**** Custom data save callback function ****/ + +typedef int (*mxml_entity_cb_t)(const char *); + /**** Entity callback function */ + +typedef mxml_type_t (*mxml_load_cb_t)(mxml_node_t *); + /**** Load callback function ****/ + +typedef const char *(*mxml_save_cb_t)(mxml_node_t *, int); + /**** Save callback function ****/ + +typedef void (*mxml_sax_cb_t)(mxml_node_t *, mxml_sax_event_t, void *); + /**** SAX callback function ****/ + + +/* + * C++ support... + */ + +# ifdef __cplusplus +extern "C" { +# endif /* __cplusplus */ + +/* + * Prototypes... + */ + +PHMXMLAPI extern void mxmlAdd(mxml_node_t *parent, int where, + mxml_node_t *child, mxml_node_t *node); +PHMXMLAPI extern void mxmlDelete(mxml_node_t *node); +PHMXMLAPI extern void mxmlElementDeleteAttr(mxml_node_t *node, + const char *name); +PHMXMLAPI extern const char *mxmlElementGetAttr(mxml_node_t *node, const char *name); +PHMXMLAPI extern void mxmlElementSetAttr(mxml_node_t *node, const char *name, + const char *value); +extern void mxmlElementSetAttrf(mxml_node_t *node, const char *name, + const char *format, ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +# endif /* __GNUC__ */ +; +extern int mxmlEntityAddCallback(mxml_entity_cb_t cb); +extern const char *mxmlEntityGetName(int val); +extern int mxmlEntityGetValue(const char *name); +extern void mxmlEntityRemoveCallback(mxml_entity_cb_t cb); +PHMXMLAPI extern mxml_node_t *mxmlFindElement(mxml_node_t *node, mxml_node_t *top, + const char *name, const char *attr, + const char *value, int descend); +extern mxml_node_t *mxmlFindPath(mxml_node_t *node, const char *path); +extern const char *mxmlGetCDATA(mxml_node_t *node); +extern const void *mxmlGetCustom(mxml_node_t *node); +extern const char *mxmlGetElement(mxml_node_t *node); +extern mxml_node_t *mxmlGetFirstChild(mxml_node_t *node); +extern int mxmlGetInteger(mxml_node_t *node); +extern mxml_node_t *mxmlGetLastChild(mxml_node_t *node); +extern mxml_node_t *mxmlGetNextSibling(mxml_node_t *node); +extern const char *mxmlGetOpaque(mxml_node_t *node); +extern mxml_node_t *mxmlGetParent(mxml_node_t *node); +extern mxml_node_t *mxmlGetPrevSibling(mxml_node_t *node); +extern double mxmlGetReal(mxml_node_t *node); +extern int mxmlGetRefCount(mxml_node_t *node); +extern const char *mxmlGetText(mxml_node_t *node, int *whitespace); +extern mxml_type_t mxmlGetType(mxml_node_t *node); +extern void *mxmlGetUserData(mxml_node_t *node); +extern void mxmlIndexDelete(mxml_index_t *ind); +extern mxml_node_t *mxmlIndexEnum(mxml_index_t *ind); +extern mxml_node_t *mxmlIndexFind(mxml_index_t *ind, + const char *element, + const char *value); +extern int mxmlIndexGetCount(mxml_index_t *ind); +extern mxml_index_t *mxmlIndexNew(mxml_node_t *node, const char *element, + const char *attr); +extern mxml_node_t *mxmlIndexReset(mxml_index_t *ind); +PHMXMLAPI extern mxml_node_t *mxmlLoadFd(mxml_node_t *top, HANDLE fd, + mxml_type_t (*cb)(mxml_node_t *)); +extern mxml_node_t *mxmlLoadFile(mxml_node_t *top, FILE *fp, + mxml_type_t (*cb)(mxml_node_t *)); +PHMXMLAPI extern mxml_node_t *mxmlLoadString(mxml_node_t *top, const char *s, + mxml_type_t (*cb)(mxml_node_t *)); +PHMXMLAPI extern mxml_node_t *mxmlNewCDATA(mxml_node_t *parent, const char *string); +PHMXMLAPI extern mxml_node_t *mxmlNewCustom(mxml_node_t *parent, void *data, + mxml_custom_destroy_cb_t destroy); +PHMXMLAPI extern mxml_node_t *mxmlNewElement(mxml_node_t *parent, const char *name); +PHMXMLAPI extern mxml_node_t *mxmlNewInteger(mxml_node_t *parent, int integer); +PHMXMLAPI extern mxml_node_t *mxmlNewOpaque(mxml_node_t *parent, const char *opaque); +PHMXMLAPI extern mxml_node_t *mxmlNewReal(mxml_node_t *parent, double real); +PHMXMLAPI extern mxml_node_t *mxmlNewText(mxml_node_t *parent, int whitespace, + const char *string); +extern mxml_node_t *mxmlNewTextf(mxml_node_t *parent, int whitespace, + const char *format, ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +# endif /* __GNUC__ */ +; +PHMXMLAPI extern mxml_node_t *mxmlNewXML(const char *version); +PHMXMLAPI extern int mxmlRelease(mxml_node_t *node); +PHMXMLAPI extern void mxmlRemove(mxml_node_t *node); +PHMXMLAPI extern int mxmlRetain(mxml_node_t *node); +PHMXMLAPI extern char *mxmlSaveAllocString(mxml_node_t *node, + mxml_save_cb_t cb); +PHMXMLAPI extern int mxmlSaveFd(mxml_node_t *node, HANDLE fd, + mxml_save_cb_t cb); +extern int mxmlSaveFile(mxml_node_t *node, FILE *fp, + mxml_save_cb_t cb); +PHMXMLAPI extern int mxmlSaveString(mxml_node_t *node, char *buffer, + int bufsize, mxml_save_cb_t cb); +extern mxml_node_t *mxmlSAXLoadFd(mxml_node_t *top, HANDLE fd, + mxml_type_t (*cb)(mxml_node_t *), + mxml_sax_cb_t sax, void *sax_data); +extern mxml_node_t *mxmlSAXLoadFile(mxml_node_t *top, FILE *fp, + mxml_type_t (*cb)(mxml_node_t *), + mxml_sax_cb_t sax, void *sax_data); +extern mxml_node_t *mxmlSAXLoadString(mxml_node_t *top, const char *s, + mxml_type_t (*cb)(mxml_node_t *), + mxml_sax_cb_t sax, void *sax_data); +PHMXMLAPI extern int mxmlSetCDATA(mxml_node_t *node, const char *data); +PHMXMLAPI extern int mxmlSetCustom(mxml_node_t *node, void *data, + mxml_custom_destroy_cb_t destroy); +PHMXMLAPI extern void mxmlSetCustomHandlers(mxml_custom_load_cb_t load, + mxml_custom_save_cb_t save); +PHMXMLAPI extern int mxmlSetElement(mxml_node_t *node, const char *name); +PHMXMLAPI extern void mxmlSetErrorCallback(mxml_error_cb_t cb); +extern int mxmlSetInteger(mxml_node_t *node, int integer); +PHMXMLAPI extern int mxmlSetOpaque(mxml_node_t *node, const char *opaque); +extern int mxmlSetReal(mxml_node_t *node, double real); +PHMXMLAPI extern int mxmlSetText(mxml_node_t *node, int whitespace, + const char *string); +extern int mxmlSetTextf(mxml_node_t *node, int whitespace, + const char *format, ...) +# ifdef __GNUC__ +__attribute__ ((__format__ (__printf__, 3, 4))) +# endif /* __GNUC__ */ +; +PHMXMLAPI extern int mxmlSetUserData(mxml_node_t *node, void *data); +extern void mxmlSetWrapMargin(int column); +PHMXMLAPI extern mxml_node_t *mxmlWalkNext(mxml_node_t *node, mxml_node_t *top, + int descend); +PHMXMLAPI extern mxml_node_t *mxmlWalkPrev(mxml_node_t *node, mxml_node_t *top, + int descend); + + +/* + * Semi-private functions... + */ + +extern void mxml_error(const char *format, ...); +extern mxml_type_t mxml_ignore_cb(mxml_node_t *node); +extern mxml_type_t mxml_integer_cb(mxml_node_t *node); +extern mxml_type_t mxml_opaque_cb(mxml_node_t *node); +extern mxml_type_t mxml_real_cb(mxml_node_t *node); + + +/* + * C++ support... + */ + +# ifdef __cplusplus +} +# endif /* __cplusplus */ +#endif /* !_mxml_h_ */ + + +/* + * End of "$Id: mxml.h 451 2014-01-04 21:50:06Z msweet $". + */ diff --git a/ProcessHacker/netlist.c b/ProcessHacker/netlist.c new file mode 100644 index 0000000..3142d71 --- /dev/null +++ b/ProcessHacker/netlist.c @@ -0,0 +1,793 @@ +/* + * Process Hacker - + * network list + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhpNetworkNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG PhpNetworkNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpRemoveNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ); + +LONG PhpNetworkTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpNetworkTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +PPH_STRING PhpGetNetworkItemProcessName( + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +VOID PhpUpdateNetworkNodeAddressStrings( + _In_ PPH_NETWORK_NODE NetworkNode + ); + +static HWND NetworkTreeListHandle; +static ULONG NetworkTreeListSortColumn; +static PH_SORT_ORDER NetworkTreeListSortOrder; +static PH_CM_MANAGER NetworkTreeListCm; + +static PPH_HASHTABLE NetworkNodeHashtable; // hashtable of all nodes +static PPH_LIST NetworkNodeList; // list of all nodes +static LONG NextUniqueId = 1; + +BOOLEAN PhNetworkTreeListStateHighlighting = TRUE; +static PPH_POINTER_LIST NetworkNodeStateList = NULL; // list of nodes which need to be processed + +static PH_TN_FILTER_SUPPORT FilterSupport; + +VOID PhNetworkTreeListInitialization( + VOID + ) +{ + NetworkNodeHashtable = PhCreateHashtable( + sizeof(PPH_NETWORK_NODE), + PhpNetworkNodeHashtableEqualFunction, + PhpNetworkNodeHashtableHashFunction, + 100 + ); + NetworkNodeList = PhCreateList(100); +} + +BOOLEAN PhpNetworkNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_NETWORK_NODE networkNode1 = *(PPH_NETWORK_NODE *)Entry1; + PPH_NETWORK_NODE networkNode2 = *(PPH_NETWORK_NODE *)Entry2; + + return networkNode1->NetworkItem == networkNode2->NetworkItem; +} + +ULONG PhpNetworkNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)(*(PPH_NETWORK_NODE *)Entry)->NetworkItem); +} + +VOID PhInitializeNetworkTreeList( + _In_ HWND hwnd + ) +{ + NetworkTreeListHandle = hwnd; + PhSetControlTheme(NetworkTreeListHandle, L"explorer"); + SendMessage(TreeNew_GetTooltips(NetworkTreeListHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT); + + TreeNew_SetCallback(hwnd, PhpNetworkTreeNewCallback, NULL); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHNETLC_PROCESS, TRUE, L"Name", 100, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumn(hwnd, PHNETLC_LOCALADDRESS, TRUE, L"Local address", 120, PH_ALIGN_LEFT, 1, 0); + PhAddTreeNewColumn(hwnd, PHNETLC_LOCALPORT, TRUE, L"Local port", 50, PH_ALIGN_RIGHT, 2, DT_RIGHT); + PhAddTreeNewColumn(hwnd, PHNETLC_REMOTEADDRESS, TRUE, L"Remote address", 120, PH_ALIGN_LEFT, 3, 0); + PhAddTreeNewColumn(hwnd, PHNETLC_REMOTEPORT, TRUE, L"Remote port", 50, PH_ALIGN_RIGHT, 4, DT_RIGHT); + PhAddTreeNewColumn(hwnd, PHNETLC_PROTOCOL, TRUE, L"Protocol", 45, PH_ALIGN_LEFT, 5, 0); + PhAddTreeNewColumn(hwnd, PHNETLC_STATE, TRUE, L"State", 70, PH_ALIGN_LEFT, 6, 0); + PhAddTreeNewColumn(hwnd, PHNETLC_OWNER, WINDOWS_HAS_SERVICE_TAGS, L"Owner", 80, PH_ALIGN_LEFT, 7, 0); + PhAddTreeNewColumnEx(hwnd, PHNETLC_TIMESTAMP, FALSE, L"Time stamp", 100, PH_ALIGN_LEFT, -1, 0, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetSort(hwnd, 0, AscendingSortOrder); + + PhCmInitializeManager(&NetworkTreeListCm, hwnd, PHNETLC_MAXIMUM, PhpNetworkTreeNewPostSortFunction); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = hwnd; + treeNewInfo.CmData = &NetworkTreeListCm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackNetworkTreeNewInitializing), &treeNewInfo); + } + + PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, NetworkNodeList); +} + +VOID PhLoadSettingsNetworkTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"NetworkTreeListColumns"); + sortSettings = PhGetStringSetting(L"NetworkTreeListSort"); + PhCmLoadSettingsEx(NetworkTreeListHandle, &NetworkTreeListCm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSaveSettingsNetworkTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(NetworkTreeListHandle, &NetworkTreeListCm, 0, &sortSettings); + PhSetStringSetting2(L"NetworkTreeListColumns", &settings->sr); + PhSetStringSetting2(L"NetworkTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +struct _PH_TN_FILTER_SUPPORT *PhGetFilterSupportNetworkTreeList( + VOID + ) +{ + return &FilterSupport; +} + +PPH_NETWORK_NODE PhAddNetworkNode( + _In_ PPH_NETWORK_ITEM NetworkItem, + _In_ ULONG RunId + ) +{ + PPH_NETWORK_NODE networkNode; + + networkNode = PhAllocate(PhEmGetObjectSize(EmNetworkNodeType, sizeof(PH_NETWORK_NODE))); + memset(networkNode, 0, sizeof(PH_NETWORK_NODE)); + PhInitializeTreeNewNode(&networkNode->Node); + + if (PhNetworkTreeListStateHighlighting && RunId != 1) + { + PhChangeShStateTn( + &networkNode->Node, + &networkNode->ShState, + &NetworkNodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + networkNode->NetworkItem = NetworkItem; + PhReferenceObject(NetworkItem); + networkNode->UniqueId = NextUniqueId++; // used to stabilize sorting + + memset(networkNode->TextCache, 0, sizeof(PH_STRINGREF) * PHNETLC_MAXIMUM); + networkNode->Node.TextCache = networkNode->TextCache; + networkNode->Node.TextCacheSize = PHNETLC_MAXIMUM; + + networkNode->ProcessNameText = PhpGetNetworkItemProcessName(NetworkItem); + PhpUpdateNetworkNodeAddressStrings(networkNode); + + PhAddEntryHashtable(NetworkNodeHashtable, &networkNode); + PhAddItemList(NetworkNodeList, networkNode); + + if (FilterSupport.NodeList) + networkNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &networkNode->Node); + + PhEmCallObjectOperation(EmNetworkNodeType, networkNode, EmObjectCreate); + + TreeNew_NodesStructured(NetworkTreeListHandle); + + return networkNode; +} + +PPH_NETWORK_NODE PhFindNetworkNode( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + PH_NETWORK_NODE lookupNetworkNode; + PPH_NETWORK_NODE lookupNetworkNodePtr = &lookupNetworkNode; + PPH_NETWORK_NODE *networkNode; + + lookupNetworkNode.NetworkItem = NetworkItem; + + networkNode = (PPH_NETWORK_NODE *)PhFindEntryHashtable( + NetworkNodeHashtable, + &lookupNetworkNodePtr + ); + + if (networkNode) + return *networkNode; + else + return NULL; +} + +VOID PhRemoveNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashtable(NetworkNodeHashtable, &NetworkNode); + + if (PhNetworkTreeListStateHighlighting) + { + PhChangeShStateTn( + &NetworkNode->Node, + &NetworkNode->ShState, + &NetworkNodeStateList, + RemovingItemState, + PhCsColorRemoved, + NetworkTreeListHandle + ); + } + else + { + PhpRemoveNetworkNode(NetworkNode); + } +} + +VOID PhpRemoveNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ) +{ + ULONG index; + + PhEmCallObjectOperation(EmNetworkNodeType, NetworkNode, EmObjectDelete); + + // Remove from list and cleanup. + + if ((index = PhFindItemList(NetworkNodeList, NetworkNode)) != -1) + PhRemoveItemList(NetworkNodeList, index); + + if (NetworkNode->ProcessNameText) PhDereferenceObject(NetworkNode->ProcessNameText); + if (NetworkNode->TimeStampText) PhDereferenceObject(NetworkNode->TimeStampText); + if (NetworkNode->TooltipText) PhDereferenceObject(NetworkNode->TooltipText); + + PhDereferenceObject(NetworkNode->NetworkItem); + + PhFree(NetworkNode); + + TreeNew_NodesStructured(NetworkTreeListHandle); +} + +VOID PhUpdateNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ) +{ + memset(NetworkNode->TextCache, 0, sizeof(PH_STRINGREF) * PHNETLC_MAXIMUM); + PhpUpdateNetworkNodeAddressStrings(NetworkNode); + PhClearReference(&NetworkNode->TooltipText); + + PhInvalidateTreeNewNode(&NetworkNode->Node, TN_CACHE_ICON); + TreeNew_NodesStructured(NetworkTreeListHandle); +} + +VOID PhTickNetworkNodes( + VOID + ) +{ + if (NetworkTreeListSortOrder != NoSortOrder && NetworkTreeListSortColumn >= PHNETLC_MAXIMUM) + { + // Sorting is on, but it's not one of our columns. Force a rebuild. (If it was one of our + // columns, the restructure would have been handled in PhUpdateNetworkNode.) + TreeNew_NodesStructured(NetworkTreeListHandle); + } + + PH_TICK_SH_STATE_TN(PH_NETWORK_NODE, ShState, NetworkNodeStateList, PhpRemoveNetworkNode, PhCsHighlightingDuration, NetworkTreeListHandle, TRUE, NULL); +} + +#define SORT_FUNCTION(Column) PhpNetworkTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpNetworkTreeNewCompare##Column( \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_NETWORK_NODE node1 = *(PPH_NETWORK_NODE *)_elem1; \ + PPH_NETWORK_NODE node2 = *(PPH_NETWORK_NODE *)_elem2; \ + PPH_NETWORK_ITEM networkItem1 = node1->NetworkItem; \ + PPH_NETWORK_ITEM networkItem2 = node2->NetworkItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = intcmp(node1->UniqueId, node2->UniqueId); \ + \ + return PhModifySort(sortResult, NetworkTreeListSortOrder); \ +} + +LONG PhpNetworkTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = intcmp(((PPH_NETWORK_NODE)Node1)->UniqueId, ((PPH_NETWORK_NODE)Node2)->UniqueId); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(Process) +{ + sortResult = PhCompareString(node1->ProcessNameText, node2->ProcessNameText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LocalAddress) +{ + sortResult = PhCompareStringRef(&node1->LocalAddressText, &node2->LocalAddressText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(LocalPort) +{ + sortResult = uintcmp(networkItem1->LocalEndpoint.Port, networkItem2->LocalEndpoint.Port); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(RemoteAddress) +{ + sortResult = PhCompareStringRef(&node1->RemoteAddressText, &node2->RemoteAddressText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(RemotePort) +{ + sortResult = uintcmp(networkItem1->RemoteEndpoint.Port, networkItem2->RemoteEndpoint.Port); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Protocol) +{ + sortResult = uintcmp(networkItem1->ProtocolType, networkItem2->ProtocolType); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(State) +{ + sortResult = uintcmp(networkItem1->State, networkItem2->State); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Owner) +{ + sortResult = PhCompareStringWithNull(networkItem1->OwnerName, networkItem2->OwnerName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TimeStamp) +{ + sortResult = uint64cmp(networkItem1->CreateTime.QuadPart, networkItem2->CreateTime.QuadPart); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpNetworkTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_NODE node; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &NetworkTreeListCm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Process), + SORT_FUNCTION(LocalAddress), + SORT_FUNCTION(LocalPort), + SORT_FUNCTION(RemoteAddress), + SORT_FUNCTION(RemotePort), + SORT_FUNCTION(Protocol), + SORT_FUNCTION(State), + SORT_FUNCTION(Owner), + SORT_FUNCTION(TimeStamp) + }; + int (__cdecl *sortFunction)(const void *, const void *); + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)NetworkNodeList->Items, + NetworkNodeList->Count, + NetworkTreeListSortColumn, + NetworkTreeListSortOrder, + &NetworkTreeListCm + )) + { + if (NetworkTreeListSortColumn < PHNETLC_MAXIMUM) + sortFunction = sortFunctions[NetworkTreeListSortColumn]; + else + sortFunction = NULL; + + if (sortFunction) + { + qsort(NetworkNodeList->Items, NetworkNodeList->Count, sizeof(PVOID), sortFunction); + } + } + + getChildren->Children = (PPH_TREENEW_NODE *)NetworkNodeList->Items; + getChildren->NumberOfChildren = NetworkNodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_NETWORK_ITEM networkItem; + + node = (PPH_NETWORK_NODE)getCellText->Node; + networkItem = node->NetworkItem; + + switch (getCellText->Id) + { + case PHNETLC_PROCESS: + getCellText->Text = node->ProcessNameText->sr; + break; + case PHNETLC_LOCALADDRESS: + getCellText->Text = node->LocalAddressText; + break; + case PHNETLC_LOCALPORT: + PhInitializeStringRefLongHint(&getCellText->Text, networkItem->LocalPortString); + break; + case PHNETLC_REMOTEADDRESS: + getCellText->Text = node->RemoteAddressText; + break; + case PHNETLC_REMOTEPORT: + PhInitializeStringRefLongHint(&getCellText->Text, networkItem->RemotePortString); + break; + case PHNETLC_PROTOCOL: + PhInitializeStringRefLongHint(&getCellText->Text, PhGetProtocolTypeName(networkItem->ProtocolType)); + break; + case PHNETLC_STATE: + if (networkItem->ProtocolType & PH_TCP_PROTOCOL_TYPE) + PhInitializeStringRefLongHint(&getCellText->Text, PhGetTcpStateName(networkItem->State)); + else + PhInitializeEmptyStringRef(&getCellText->Text); + break; + case PHNETLC_OWNER: + if (WINDOWS_HAS_SERVICE_TAGS) + getCellText->Text = PhGetStringRef(networkItem->OwnerName); + else + PhInitializeStringRef(&getCellText->Text, L"N/A"); // make sure the user knows this column doesn't work on XP + break; + case PHNETLC_TIMESTAMP: + { + SYSTEMTIME systemTime; + + if (networkItem->CreateTime.QuadPart != 0) + { + PhLargeIntegerToLocalSystemTime(&systemTime, &networkItem->CreateTime); + PhMoveReference(&node->TimeStampText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->TimeStampText->sr; + } + else + { + PhInitializeEmptyStringRef(&getCellText->Text); + } + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeIcon: + { + PPH_TREENEW_GET_NODE_ICON getNodeIcon = Parameter1; + + node = (PPH_NETWORK_NODE)getNodeIcon->Node; + + if (node->NetworkItem->ProcessIconValid) + { + // TODO: Check if the icon handle is actually valid, since the process item + // might get destroyed while the network node is still valid. + getNodeIcon->Icon = node->NetworkItem->ProcessIcon; + } + else + { + PhGetStockApplicationIcon(&getNodeIcon->Icon, NULL); + } + + getNodeIcon->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + PPH_PROCESS_ITEM processItem; + + node = (PPH_NETWORK_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0) + return FALSE; + + if (!node->TooltipText) + { + if (processItem = PhReferenceProcessItem(node->NetworkItem->ProcessId)) + { + node->TooltipText = PhGetProcessTooltipText(processItem, NULL); + PhDereferenceObject(processItem); + } + } + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->MaximumWidth = -1; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &NetworkTreeListSortColumn, &NetworkTreeListSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_NETWORK_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(NetworkTreeListHandle, 0, -1); + break; + case VK_RETURN: + SendMessage(PhMainWndHandle, WM_COMMAND, ID_NETWORK_GOTOPROCESS, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = AscendingSortOrder; + PhInitializeTreeNewColumnMenu(&data); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(PhMainWndHandle, WM_COMMAND, ID_NETWORK_GOTOPROCESS, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + PhShowNetworkContextMenu(contextMenu); + } + return TRUE; + } + + return FALSE; +} + +PPH_STRING PhpGetNetworkItemProcessName( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + PH_FORMAT format[4]; + + if (!NetworkItem->ProcessId) + return PhCreateString(L"Waiting connections"); + + PhInitFormatS(&format[1], L" ("); + PhInitFormatU(&format[2], HandleToUlong(NetworkItem->ProcessId)); + PhInitFormatC(&format[3], ')'); + + if (NetworkItem->ProcessName) + PhInitFormatSR(&format[0], NetworkItem->ProcessName->sr); + else + PhInitFormatS(&format[0], L"Unknown process"); + + return PhFormat(format, 4, 96); +} + +VOID PhpUpdateNetworkNodeAddressStrings( + _In_ PPH_NETWORK_NODE NetworkNode + ) +{ + if (NetworkNode->NetworkItem->LocalHostString) + NetworkNode->LocalAddressText = NetworkNode->NetworkItem->LocalHostString->sr; + else + PhInitializeStringRefLongHint(&NetworkNode->LocalAddressText, NetworkNode->NetworkItem->LocalAddressString); + + if (NetworkNode->NetworkItem->RemoteHostString) + NetworkNode->RemoteAddressText = NetworkNode->NetworkItem->RemoteHostString->sr; + else + PhInitializeStringRefLongHint(&NetworkNode->RemoteAddressText, NetworkNode->NetworkItem->RemoteAddressString); +} + +PPH_NETWORK_ITEM PhGetSelectedNetworkItem( + VOID + ) +{ + PPH_NETWORK_ITEM networkItem = NULL; + ULONG i; + + for (i = 0; i < NetworkNodeList->Count; i++) + { + PPH_NETWORK_NODE node = NetworkNodeList->Items[i]; + + if (node->Node.Selected) + { + networkItem = node->NetworkItem; + break; + } + } + + return networkItem; +} + +VOID PhGetSelectedNetworkItems( + _Out_ PPH_NETWORK_ITEM **NetworkItems, + _Out_ PULONG NumberOfNetworkItems + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < NetworkNodeList->Count; i++) + { + PPH_NETWORK_NODE node = NetworkNodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->NetworkItem); + } + + *NumberOfNetworkItems = (ULONG)array.Count; + *NetworkItems = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllNetworkNodes( + VOID + ) +{ + TreeNew_DeselectRange(NetworkTreeListHandle, 0, -1); +} + +VOID PhSelectAndEnsureVisibleNetworkNode( + _In_ PPH_NETWORK_NODE NetworkNode + ) +{ + PhDeselectAllNetworkNodes(); + + if (!NetworkNode->Node.Visible) + return; + + TreeNew_SetFocusNode(NetworkTreeListHandle, &NetworkNode->Node); + TreeNew_SetMarkNode(NetworkTreeListHandle, &NetworkNode->Node); + TreeNew_SelectRange(NetworkTreeListHandle, NetworkNode->Node.Index, NetworkNode->Node.Index); + TreeNew_EnsureVisible(NetworkTreeListHandle, &NetworkNode->Node); +} + +VOID PhCopyNetworkList( + VOID + ) +{ + PPH_STRING text; + + text = PhGetTreeNewText(NetworkTreeListHandle, 0); + PhSetClipboardString(NetworkTreeListHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID PhWriteNetworkList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ) +{ + PPH_LIST lines; + ULONG i; + + lines = PhGetGenericTreeNewLines(NetworkTreeListHandle, Mode); + + for (i = 0; i < lines->Count; i++) + { + PPH_STRING line; + + line = lines->Items[i]; + PhWriteStringAsUtf8FileStream(FileStream, &line->sr); + PhDereferenceObject(line); + PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n"); + } + + PhDereferenceObject(lines); +} diff --git a/ProcessHacker/netprv.c b/ProcessHacker/netprv.c new file mode 100644 index 0000000..038d65a --- /dev/null +++ b/ProcessHacker/netprv.c @@ -0,0 +1,1067 @@ +/* + * Process Hacker - + * network provider + * + * Copyright (C) 2010 wj32 + * Copyright (C) 2010 evilpie + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PH_NETWORK_CONNECTION +{ + ULONG ProtocolType; + PH_IP_ENDPOINT LocalEndpoint; + PH_IP_ENDPOINT RemoteEndpoint; + ULONG State; + HANDLE ProcessId; + LARGE_INTEGER CreateTime; + ULONGLONG OwnerInfo[PH_NETWORK_OWNER_INFO_SIZE]; +} PH_NETWORK_CONNECTION, *PPH_NETWORK_CONNECTION; + +typedef struct _PH_NETWORK_ITEM_QUERY_DATA +{ + SLIST_ENTRY ListEntry; + PPH_NETWORK_ITEM NetworkItem; + + PH_IP_ADDRESS Address; + BOOLEAN Remote; + PPH_STRING HostString; +} PH_NETWORK_ITEM_QUERY_DATA, *PPH_NETWORK_ITEM_QUERY_DATA; + +typedef struct _PHP_RESOLVE_CACHE_ITEM +{ + PH_IP_ADDRESS Address; + PPH_STRING HostString; +} PHP_RESOLVE_CACHE_ITEM, *PPHP_RESOLVE_CACHE_ITEM; + +typedef DWORD (WINAPI *_GetExtendedTcpTable)( + _Out_writes_bytes_opt_(*pdwSize) PVOID pTcpTable, + _Inout_ PDWORD pdwSize, + _In_ BOOL bOrder, + _In_ ULONG ulAf, + _In_ TCP_TABLE_CLASS TableClass, + _In_ ULONG Reserved + ); + +typedef DWORD (WINAPI *_GetExtendedUdpTable)( + _Out_writes_bytes_opt_(*pdwSize) PVOID pUdpTable, + _Inout_ PDWORD pdwSize, + _In_ BOOL bOrder, + _In_ ULONG ulAf, + _In_ UDP_TABLE_CLASS TableClass, + _In_ ULONG Reserved + ); + +typedef int (WSAAPI *_WSAStartup)( + _In_ WORD wVersionRequested, + _Out_ LPWSADATA lpWSAData + ); + +typedef int (WSAAPI *_WSAGetLastError)(); + +typedef INT (WSAAPI *_GetNameInfoW)( + _In_reads_bytes_(SockaddrLength) const SOCKADDR *pSockaddr, + _In_ socklen_t SockaddrLength, + _Out_writes_opt_(NodeBufferSize) PWCHAR pNodeBuffer, + _In_ DWORD NodeBufferSize, + _Out_writes_opt_(ServiceBufferSize) PWCHAR pServiceBuffer, + _In_ DWORD ServiceBufferSize, + _In_ INT Flags + ); + +typedef struct hostent *(WSAAPI *_gethostbyaddr)( + _In_reads_bytes_(len) const char *addr, + _In_ int len, + _In_ int type + ); + +VOID NTAPI PhpNetworkItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +BOOLEAN PhpNetworkHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpNetworkHashtableHashFunction( + _In_ PVOID Entry + ); + +BOOLEAN PhpResolveCacheHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpResolveCacheHashtableHashFunction( + _In_ PVOID Entry + ); + +BOOLEAN PhGetNetworkConnections( + _Out_ PPH_NETWORK_CONNECTION *Connections, + _Out_ PULONG NumberOfConnections + ); + +PPH_OBJECT_TYPE PhNetworkItemType; + +PPH_HASHTABLE PhNetworkHashtable; +PH_QUEUED_LOCK PhNetworkHashtableLock = PH_QUEUED_LOCK_INIT; + +PHAPPAPI PH_CALLBACK_DECLARE(PhNetworkItemAddedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhNetworkItemModifiedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhNetworkItemRemovedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhNetworkItemsUpdatedEvent); + +BOOLEAN PhEnableNetworkProviderResolve = TRUE; + +PH_INITONCE PhNetworkProviderWorkQueueInitOnce = PH_INITONCE_INIT; +PH_WORK_QUEUE PhNetworkProviderWorkQueue; +SLIST_HEADER PhNetworkItemQueryListHead; + +static PPH_HASHTABLE PhpResolveCacheHashtable; +static PH_QUEUED_LOCK PhpResolveCacheHashtableLock = PH_QUEUED_LOCK_INIT; + +static BOOLEAN NetworkImportDone = FALSE; +static _GetExtendedTcpTable GetExtendedTcpTable_I; +static _GetExtendedUdpTable GetExtendedUdpTable_I; +static _WSAStartup WSAStartup_I; +static _WSAGetLastError WSAGetLastError_I; +static _GetNameInfoW GetNameInfoW_I; +static _gethostbyaddr gethostbyaddr_I; + +BOOLEAN PhNetworkProviderInitialization( + VOID + ) +{ + PhNetworkItemType = PhCreateObjectType(L"NetworkItem", 0, PhpNetworkItemDeleteProcedure); + PhNetworkHashtable = PhCreateHashtable( + sizeof(PPH_NETWORK_ITEM), + PhpNetworkHashtableEqualFunction, + PhpNetworkHashtableHashFunction, + 40 + ); + + RtlInitializeSListHead(&PhNetworkItemQueryListHead); + + PhpResolveCacheHashtable = PhCreateHashtable( + sizeof(PHP_RESOLVE_CACHE_ITEM), + PhpResolveCacheHashtableEqualFunction, + PhpResolveCacheHashtableHashFunction, + 20 + ); + + return TRUE; +} + +PPH_NETWORK_ITEM PhCreateNetworkItem( + VOID + ) +{ + PPH_NETWORK_ITEM networkItem; + + networkItem = PhCreateObject( + PhEmGetObjectSize(EmNetworkItemType, sizeof(PH_NETWORK_ITEM)), + PhNetworkItemType + ); + memset(networkItem, 0, sizeof(PH_NETWORK_ITEM)); + PhEmCallObjectOperation(EmNetworkItemType, networkItem, EmObjectCreate); + + return networkItem; +} + +VOID NTAPI PhpNetworkItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)Object; + + PhEmCallObjectOperation(EmNetworkItemType, networkItem, EmObjectDelete); + + if (networkItem->ProcessName) + PhDereferenceObject(networkItem->ProcessName); + if (networkItem->OwnerName) + PhDereferenceObject(networkItem->OwnerName); + if (networkItem->LocalHostString) + PhDereferenceObject(networkItem->LocalHostString); + if (networkItem->RemoteHostString) + PhDereferenceObject(networkItem->RemoteHostString); +} + +BOOLEAN PhpNetworkHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_NETWORK_ITEM networkItem1 = *(PPH_NETWORK_ITEM *)Entry1; + PPH_NETWORK_ITEM networkItem2 = *(PPH_NETWORK_ITEM *)Entry2; + + return + networkItem1->ProtocolType == networkItem2->ProtocolType && + PhEqualIpEndpoint(&networkItem1->LocalEndpoint, &networkItem2->LocalEndpoint) && + PhEqualIpEndpoint(&networkItem1->RemoteEndpoint, &networkItem2->RemoteEndpoint) && + networkItem1->ProcessId == networkItem2->ProcessId; +} + +ULONG NTAPI PhpNetworkHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPH_NETWORK_ITEM networkItem = *(PPH_NETWORK_ITEM *)Entry; + + return + networkItem->ProtocolType ^ + PhHashIpEndpoint(&networkItem->LocalEndpoint) ^ + PhHashIpEndpoint(&networkItem->RemoteEndpoint) ^ + HandleToUlong(networkItem->ProcessId); +} + +PPH_NETWORK_ITEM PhReferenceNetworkItem( + _In_ ULONG ProtocolType, + _In_ PPH_IP_ENDPOINT LocalEndpoint, + _In_ PPH_IP_ENDPOINT RemoteEndpoint, + _In_ HANDLE ProcessId + ) +{ + PH_NETWORK_ITEM lookupNetworkItem; + PPH_NETWORK_ITEM lookupNetworkItemPtr = &lookupNetworkItem; + PPH_NETWORK_ITEM *networkItemPtr; + PPH_NETWORK_ITEM networkItem; + + lookupNetworkItem.ProtocolType = ProtocolType; + lookupNetworkItem.LocalEndpoint = *LocalEndpoint; + lookupNetworkItem.RemoteEndpoint = *RemoteEndpoint; + lookupNetworkItem.ProcessId = ProcessId; + + PhAcquireQueuedLockShared(&PhNetworkHashtableLock); + + networkItemPtr = (PPH_NETWORK_ITEM *)PhFindEntryHashtable( + PhNetworkHashtable, + &lookupNetworkItemPtr + ); + + if (networkItemPtr) + { + networkItem = *networkItemPtr; + PhReferenceObject(networkItem); + } + else + { + networkItem = NULL; + } + + PhReleaseQueuedLockShared(&PhNetworkHashtableLock); + + return networkItem; +} + +VOID PhpRemoveNetworkItem( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + PhRemoveEntryHashtable(PhNetworkHashtable, &NetworkItem); + PhDereferenceObject(NetworkItem); +} + +BOOLEAN PhpResolveCacheHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPHP_RESOLVE_CACHE_ITEM cacheItem1 = *(PPHP_RESOLVE_CACHE_ITEM *)Entry1; + PPHP_RESOLVE_CACHE_ITEM cacheItem2 = *(PPHP_RESOLVE_CACHE_ITEM *)Entry2; + + return PhEqualIpAddress(&cacheItem1->Address, &cacheItem2->Address); +} + +ULONG NTAPI PhpResolveCacheHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPHP_RESOLVE_CACHE_ITEM cacheItem = *(PPHP_RESOLVE_CACHE_ITEM *)Entry; + + return PhHashIpAddress(&cacheItem->Address); +} + +PPHP_RESOLVE_CACHE_ITEM PhpLookupResolveCacheItem( + _In_ PPH_IP_ADDRESS Address + ) +{ + PHP_RESOLVE_CACHE_ITEM lookupCacheItem; + PPHP_RESOLVE_CACHE_ITEM lookupCacheItemPtr = &lookupCacheItem; + PPHP_RESOLVE_CACHE_ITEM *cacheItemPtr; + + // Construct a temporary cache item for the lookup. + lookupCacheItem.Address = *Address; + + cacheItemPtr = (PPHP_RESOLVE_CACHE_ITEM *)PhFindEntryHashtable( + PhpResolveCacheHashtable, + &lookupCacheItemPtr + ); + + if (cacheItemPtr) + return *cacheItemPtr; + else + return NULL; +} + +PPH_STRING PhGetHostNameFromAddress( + _In_ PPH_IP_ADDRESS Address + ) +{ + struct sockaddr_in ipv4Address; + struct sockaddr_in6 ipv6Address; + struct sockaddr *address; + socklen_t length; + PPH_STRING hostName; + + if (!GetNameInfoW_I) + return NULL; + + if (Address->Type == PH_IPV4_NETWORK_TYPE) + { + ipv4Address.sin_family = AF_INET; + ipv4Address.sin_port = 0; + ipv4Address.sin_addr = Address->InAddr; + address = (struct sockaddr *)&ipv4Address; + length = sizeof(ipv4Address); + } + else if (Address->Type == PH_IPV6_NETWORK_TYPE) + { + ipv6Address.sin6_family = AF_INET6; + ipv6Address.sin6_port = 0; + ipv6Address.sin6_flowinfo = 0; + ipv6Address.sin6_addr = Address->In6Addr; + ipv6Address.sin6_scope_id = 0; + address = (struct sockaddr *)&ipv6Address; + length = sizeof(ipv6Address); + } + else + { + return NULL; + } + + hostName = PhCreateStringEx(NULL, 128); + + if (GetNameInfoW_I( + address, + length, + hostName->Buffer, + (ULONG)hostName->Length / 2 + 1, + NULL, + 0, + NI_NAMEREQD + ) != 0) + { + // Try with the maximum host name size. + PhDereferenceObject(hostName); + hostName = PhCreateStringEx(NULL, NI_MAXHOST * 2); + + if (GetNameInfoW_I( + address, + length, + hostName->Buffer, + (ULONG)hostName->Length / 2 + 1, + NULL, + 0, + NI_NAMEREQD + ) != 0) + { + PhDereferenceObject(hostName); + + return NULL; + } + } + + PhTrimToNullTerminatorString(hostName); + + return hostName; +} + +NTSTATUS PhpNetworkItemQueryWorker( + _In_ PVOID Parameter + ) +{ + PPH_NETWORK_ITEM_QUERY_DATA data = (PPH_NETWORK_ITEM_QUERY_DATA)Parameter; + PPH_STRING hostString; + PPHP_RESOLVE_CACHE_ITEM cacheItem; + + // Last minute check of the cache. + + PhAcquireQueuedLockShared(&PhpResolveCacheHashtableLock); + cacheItem = PhpLookupResolveCacheItem(&data->Address); + PhReleaseQueuedLockShared(&PhpResolveCacheHashtableLock); + + if (!cacheItem) + { + hostString = PhGetHostNameFromAddress(&data->Address); + + if (hostString) + { + data->HostString = hostString; + + // Update the cache. + + PhAcquireQueuedLockExclusive(&PhpResolveCacheHashtableLock); + + cacheItem = PhpLookupResolveCacheItem(&data->Address); + + if (!cacheItem) + { + cacheItem = PhAllocate(sizeof(PHP_RESOLVE_CACHE_ITEM)); + cacheItem->Address = data->Address; + cacheItem->HostString = hostString; + PhReferenceObject(hostString); + + PhAddEntryHashtable(PhpResolveCacheHashtable, &cacheItem); + } + + PhReleaseQueuedLockExclusive(&PhpResolveCacheHashtableLock); + } + else + { + dprintf("resolve failed, error %u\n", WSAGetLastError_I()); + } + } + else + { + PhReferenceObject(cacheItem->HostString); + data->HostString = cacheItem->HostString; + } + + RtlInterlockedPushEntrySList(&PhNetworkItemQueryListHead, &data->ListEntry); + + return STATUS_SUCCESS; +} + +VOID PhpQueueNetworkItemQuery( + _In_ PPH_NETWORK_ITEM NetworkItem, + _In_ BOOLEAN Remote + ) +{ + PPH_NETWORK_ITEM_QUERY_DATA data; + + if (!PhEnableNetworkProviderResolve) + return; + + data = PhAllocate(sizeof(PH_NETWORK_ITEM_QUERY_DATA)); + memset(data, 0, sizeof(PH_NETWORK_ITEM_QUERY_DATA)); + data->NetworkItem = NetworkItem; + data->Remote = Remote; + + if (Remote) + data->Address = NetworkItem->RemoteEndpoint.Address; + else + data->Address = NetworkItem->LocalEndpoint.Address; + + PhReferenceObject(NetworkItem); + + if (PhBeginInitOnce(&PhNetworkProviderWorkQueueInitOnce)) + { + PhInitializeWorkQueue(&PhNetworkProviderWorkQueue, 0, 3, 500); + PhEndInitOnce(&PhNetworkProviderWorkQueueInitOnce); + } + + PhQueueItemWorkQueue(&PhNetworkProviderWorkQueue, PhpNetworkItemQueryWorker, data); +} + +VOID PhpUpdateNetworkItemOwner( + _In_ PPH_NETWORK_ITEM NetworkItem, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + if (*(PULONG64)NetworkItem->OwnerInfo) + { + PVOID serviceTag; + PPH_STRING serviceName; + + // May change in the future... + serviceTag = UlongToPtr(*(PULONG)NetworkItem->OwnerInfo); + serviceName = PhGetServiceNameFromTag(NetworkItem->ProcessId, serviceTag); + + if (serviceName) + PhMoveReference(&NetworkItem->OwnerName, serviceName); + } +} + +VOID PhNetworkProviderUpdate( + _In_ PVOID Object + ) +{ + PPH_NETWORK_CONNECTION connections; + ULONG numberOfConnections; + ULONG i; + + if (!NetworkImportDone) + { + WSADATA wsaData; + HMODULE iphlpapi; + HMODULE ws2_32; + + iphlpapi = LoadLibrary(L"iphlpapi.dll"); + GetExtendedTcpTable_I = (PVOID)GetProcAddress(iphlpapi, "GetExtendedTcpTable"); + GetExtendedUdpTable_I = (PVOID)GetProcAddress(iphlpapi, "GetExtendedUdpTable"); + ws2_32 = LoadLibrary(L"ws2_32.dll"); + WSAStartup_I = (PVOID)GetProcAddress(ws2_32, "WSAStartup"); + WSAGetLastError_I = (PVOID)GetProcAddress(ws2_32, "WSAGetLastError"); + GetNameInfoW_I = (PVOID)GetProcAddress(ws2_32, "GetNameInfoW"); + gethostbyaddr_I = (PVOID)GetProcAddress(ws2_32, "gethostbyaddr"); + + // Make sure WSA is initialized. + if (WSAStartup_I) + { + WSAStartup_I(MAKEWORD(2, 2), &wsaData); + } + + NetworkImportDone = TRUE; + } + + if (!PhGetNetworkConnections(&connections, &numberOfConnections)) + return; + + { + PPH_LIST connectionsToRemove = NULL; + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_NETWORK_ITEM *networkItem; + + PhBeginEnumHashtable(PhNetworkHashtable, &enumContext); + + while (networkItem = PhNextEnumHashtable(&enumContext)) + { + BOOLEAN found = FALSE; + + for (i = 0; i < numberOfConnections; i++) + { + if ( + (*networkItem)->ProtocolType == connections[i].ProtocolType && + PhEqualIpEndpoint(&(*networkItem)->LocalEndpoint, &connections[i].LocalEndpoint) && + PhEqualIpEndpoint(&(*networkItem)->RemoteEndpoint, &connections[i].RemoteEndpoint) && + (*networkItem)->ProcessId == connections[i].ProcessId + ) + { + found = TRUE; + break; + } + } + + if (!found) + { + PhInvokeCallback(&PhNetworkItemRemovedEvent, *networkItem); + + if (!connectionsToRemove) + connectionsToRemove = PhCreateList(2); + + PhAddItemList(connectionsToRemove, *networkItem); + } + } + + if (connectionsToRemove) + { + PhAcquireQueuedLockExclusive(&PhNetworkHashtableLock); + + for (i = 0; i < connectionsToRemove->Count; i++) + { + PhpRemoveNetworkItem(connectionsToRemove->Items[i]); + } + + PhReleaseQueuedLockExclusive(&PhNetworkHashtableLock); + PhDereferenceObject(connectionsToRemove); + } + } + + // Go through the queued network item query data. + { + PSLIST_ENTRY entry; + PPH_NETWORK_ITEM_QUERY_DATA data; + + entry = RtlInterlockedFlushSList(&PhNetworkItemQueryListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_NETWORK_ITEM_QUERY_DATA, ListEntry); + entry = entry->Next; + + if (data->Remote) + PhMoveReference(&data->NetworkItem->RemoteHostString, data->HostString); + else + PhMoveReference(&data->NetworkItem->LocalHostString, data->HostString); + + data->NetworkItem->JustResolved = TRUE; + + PhDereferenceObject(data->NetworkItem); + PhFree(data); + } + } + + for (i = 0; i < numberOfConnections; i++) + { + PPH_NETWORK_ITEM networkItem; + + // Try to find the connection in our hashtable. + networkItem = PhReferenceNetworkItem( + connections[i].ProtocolType, + &connections[i].LocalEndpoint, + &connections[i].RemoteEndpoint, + connections[i].ProcessId + ); + + if (!networkItem) + { + PPHP_RESOLVE_CACHE_ITEM cacheItem; + PPH_PROCESS_ITEM processItem; + + // Network item not found, create it. + + networkItem = PhCreateNetworkItem(); + + // Fill in basic information. + networkItem->ProtocolType = connections[i].ProtocolType; + networkItem->LocalEndpoint = connections[i].LocalEndpoint; + networkItem->RemoteEndpoint = connections[i].RemoteEndpoint; + networkItem->State = connections[i].State; + networkItem->ProcessId = connections[i].ProcessId; + networkItem->CreateTime = connections[i].CreateTime; + memcpy(networkItem->OwnerInfo, connections[i].OwnerInfo, sizeof(ULONGLONG) * PH_NETWORK_OWNER_INFO_SIZE); + + // Format various strings. + + if (networkItem->LocalEndpoint.Address.Type == PH_IPV4_NETWORK_TYPE) + RtlIpv4AddressToString(&networkItem->LocalEndpoint.Address.InAddr, networkItem->LocalAddressString); + else + RtlIpv6AddressToString(&networkItem->LocalEndpoint.Address.In6Addr, networkItem->LocalAddressString); + + PhPrintUInt32(networkItem->LocalPortString, networkItem->LocalEndpoint.Port); + + if ( + networkItem->RemoteEndpoint.Address.Type == PH_IPV4_NETWORK_TYPE && + networkItem->RemoteEndpoint.Address.Ipv4 != 0 + ) + { + RtlIpv4AddressToString(&networkItem->RemoteEndpoint.Address.InAddr, networkItem->RemoteAddressString); + } + else if ( + networkItem->RemoteEndpoint.Address.Type == PH_IPV6_NETWORK_TYPE && + !PhIsNullIpAddress(&networkItem->RemoteEndpoint.Address) + ) + { + RtlIpv6AddressToString(&networkItem->RemoteEndpoint.Address.In6Addr, networkItem->RemoteAddressString); + } + + if (networkItem->RemoteEndpoint.Address.Type != 0 && networkItem->RemoteEndpoint.Port != 0) + PhPrintUInt32(networkItem->RemotePortString, networkItem->RemoteEndpoint.Port); + + // Get host names. + + // Local + { + PhAcquireQueuedLockShared(&PhpResolveCacheHashtableLock); + cacheItem = PhpLookupResolveCacheItem(&networkItem->LocalEndpoint.Address); + PhReleaseQueuedLockShared(&PhpResolveCacheHashtableLock); + + if (cacheItem) + { + PhReferenceObject(cacheItem->HostString); + networkItem->LocalHostString = cacheItem->HostString; + } + else + { + PhpQueueNetworkItemQuery(networkItem, FALSE); + } + } + + // Remote + if (!PhIsNullIpAddress(&networkItem->RemoteEndpoint.Address)) + { + PhAcquireQueuedLockShared(&PhpResolveCacheHashtableLock); + cacheItem = PhpLookupResolveCacheItem(&networkItem->RemoteEndpoint.Address); + PhReleaseQueuedLockShared(&PhpResolveCacheHashtableLock); + + if (cacheItem) + { + PhReferenceObject(cacheItem->HostString); + networkItem->RemoteHostString = cacheItem->HostString; + } + else + { + PhpQueueNetworkItemQuery(networkItem, TRUE); + } + } + + // Get process information. + if (processItem = PhReferenceProcessItem(networkItem->ProcessId)) + { + networkItem->ProcessName = processItem->ProcessName; + PhReferenceObject(processItem->ProcessName); + PhpUpdateNetworkItemOwner(networkItem, processItem); + + if (PhTestEvent(&processItem->Stage1Event)) + { + networkItem->ProcessIcon = processItem->SmallIcon; + networkItem->ProcessIconValid = TRUE; + } + + PhDereferenceObject(processItem); + } + + // Add the network item to the hashtable. + PhAcquireQueuedLockExclusive(&PhNetworkHashtableLock); + PhAddEntryHashtable(PhNetworkHashtable, &networkItem); + PhReleaseQueuedLockExclusive(&PhNetworkHashtableLock); + + // Raise the network item added event. + PhInvokeCallback(&PhNetworkItemAddedEvent, networkItem); + } + else + { + BOOLEAN modified = FALSE; + PPH_PROCESS_ITEM processItem; + + if (networkItem->JustResolved) + modified = TRUE; + + if (networkItem->State != connections[i].State) + { + networkItem->State = connections[i].State; + modified = TRUE; + } + + if (!networkItem->ProcessName || !networkItem->ProcessIconValid) + { + if (processItem = PhReferenceProcessItem(networkItem->ProcessId)) + { + if (!networkItem->ProcessName) + { + networkItem->ProcessName = processItem->ProcessName; + PhReferenceObject(processItem->ProcessName); + PhpUpdateNetworkItemOwner(networkItem, processItem); + modified = TRUE; + } + + if (!networkItem->ProcessIconValid && PhTestEvent(&processItem->Stage1Event)) + { + networkItem->ProcessIcon = processItem->SmallIcon; + networkItem->ProcessIconValid = TRUE; + modified = TRUE; + } + + PhDereferenceObject(processItem); + } + } + + networkItem->JustResolved = FALSE; + + if (modified) + { + // Raise the network item modified event. + PhInvokeCallback(&PhNetworkItemModifiedEvent, networkItem); + } + + PhDereferenceObject(networkItem); + } + } + + PhFree(connections); + + PhInvokeCallback(&PhNetworkItemsUpdatedEvent, NULL); +} + +PWSTR PhGetProtocolTypeName( + _In_ ULONG ProtocolType + ) +{ + switch (ProtocolType) + { + case PH_TCP4_NETWORK_PROTOCOL: + return L"TCP"; + case PH_TCP6_NETWORK_PROTOCOL: + return L"TCP6"; + case PH_UDP4_NETWORK_PROTOCOL: + return L"UDP"; + case PH_UDP6_NETWORK_PROTOCOL: + return L"UDP6"; + default: + return L"Unknown"; + } +} + +PWSTR PhGetTcpStateName( + _In_ ULONG State + ) +{ + switch (State) + { + case MIB_TCP_STATE_CLOSED: + return L"Closed"; + case MIB_TCP_STATE_LISTEN: + return L"Listen"; + case MIB_TCP_STATE_SYN_SENT: + return L"SYN sent"; + case MIB_TCP_STATE_SYN_RCVD: + return L"SYN received"; + case MIB_TCP_STATE_ESTAB: + return L"Established"; + case MIB_TCP_STATE_FIN_WAIT1: + return L"FIN wait 1"; + case MIB_TCP_STATE_FIN_WAIT2: + return L"FIN wait 2"; + case MIB_TCP_STATE_CLOSE_WAIT: + return L"Close wait"; + case MIB_TCP_STATE_CLOSING: + return L"Closing"; + case MIB_TCP_STATE_LAST_ACK: + return L"Last ACK"; + case MIB_TCP_STATE_TIME_WAIT: + return L"Time wait"; + case MIB_TCP_STATE_DELETE_TCB: + return L"Delete TCB"; + default: + return L"Unknown"; + } +} + +BOOLEAN PhGetNetworkConnections( + _Out_ PPH_NETWORK_CONNECTION *Connections, + _Out_ PULONG NumberOfConnections + ) +{ + PVOID table; + DWORD tableSize; + PMIB_TCPTABLE_OWNER_MODULE tcp4Table; + PMIB_UDPTABLE_OWNER_MODULE udp4Table; + PMIB_TCP6TABLE_OWNER_MODULE tcp6Table; + PMIB_UDP6TABLE_OWNER_MODULE udp6Table; + ULONG count = 0; + ULONG i; + ULONG index = 0; + PPH_NETWORK_CONNECTION connections; + + if (!GetExtendedTcpTable_I || !GetExtendedUdpTable_I) + return FALSE; + + // TCP IPv4 + + tableSize = 0; + GetExtendedTcpTable_I(NULL, &tableSize, FALSE, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0); + table = PhAllocate(tableSize); + + if (GetExtendedTcpTable_I(table, &tableSize, FALSE, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0) == 0) + { + tcp4Table = table; + count += tcp4Table->dwNumEntries; + } + else + { + PhFree(table); + tcp4Table = NULL; + } + + // TCP IPv6 + + tableSize = 0; + GetExtendedTcpTable_I(NULL, &tableSize, FALSE, AF_INET6, TCP_TABLE_OWNER_MODULE_ALL, 0); + + // Note: On Windows XP, GetExtendedTcpTable had a bug where it calculated the required buffer size + // for IPv6 TCP_TABLE_OWNER_MODULE_ALL requests incorrectly, causing it to return the wrong size + // and overrun the provided buffer instead of returning an error. The size should be: + // = FIELD_OFFSET(MIB_TCP6TABLE_OWNER_MODULE, table) + sizeof(MIB_TCP6ROW_OWNER_MODULE) * (number of entries) + // However, the function calculated it as: + // = FIELD_OFFSET(MIB_TCP6TABLE_OWNER_MODULE, table) + sizeof(MIB_TCP6ROW_OWNER_PID) * (number of entries) + // A workaround is implemented below. + if (WindowsVersion <= WINDOWS_XP && tableSize >= (ULONG)FIELD_OFFSET(MIB_TCP6TABLE_OWNER_MODULE, table)) // make sure we don't wrap around + { + tableSize = FIELD_OFFSET(MIB_TCP6TABLE_OWNER_MODULE, table) + + (tableSize - FIELD_OFFSET(MIB_TCP6TABLE_OWNER_MODULE, table)) / sizeof(MIB_TCP6ROW_OWNER_PID) * sizeof(MIB_TCP6ROW_OWNER_MODULE); + } + + table = PhAllocate(tableSize); + + if (GetExtendedTcpTable_I(table, &tableSize, FALSE, AF_INET6, TCP_TABLE_OWNER_MODULE_ALL, 0) == 0) + { + tcp6Table = table; + count += tcp6Table->dwNumEntries; + } + else + { + PhFree(table); + tcp6Table = NULL; + } + + // UDP IPv4 + + tableSize = 0; + GetExtendedUdpTable_I(NULL, &tableSize, FALSE, AF_INET, UDP_TABLE_OWNER_MODULE, 0); + table = PhAllocate(tableSize); + + if (GetExtendedUdpTable_I(table, &tableSize, FALSE, AF_INET, UDP_TABLE_OWNER_MODULE, 0) == 0) + { + udp4Table = table; + count += udp4Table->dwNumEntries; + } + else + { + PhFree(table); + udp4Table = NULL; + } + + // UDP IPv6 + + tableSize = 0; + GetExtendedUdpTable_I(NULL, &tableSize, FALSE, AF_INET6, UDP_TABLE_OWNER_MODULE, 0); + table = PhAllocate(tableSize); + + if (GetExtendedUdpTable_I(table, &tableSize, FALSE, AF_INET6, UDP_TABLE_OWNER_MODULE, 0) == 0) + { + udp6Table = table; + count += udp6Table->dwNumEntries; + } + else + { + PhFree(table); + udp6Table = NULL; + } + + connections = PhAllocate(sizeof(PH_NETWORK_CONNECTION) * count); + memset(connections, 0, sizeof(PH_NETWORK_CONNECTION) * count); + + if (tcp4Table) + { + for (i = 0; i < tcp4Table->dwNumEntries; i++) + { + connections[index].ProtocolType = PH_TCP4_NETWORK_PROTOCOL; + + connections[index].LocalEndpoint.Address.Type = PH_IPV4_NETWORK_TYPE; + connections[index].LocalEndpoint.Address.Ipv4 = tcp4Table->table[i].dwLocalAddr; + connections[index].LocalEndpoint.Port = _byteswap_ushort((USHORT)tcp4Table->table[i].dwLocalPort); + + connections[index].RemoteEndpoint.Address.Type = PH_IPV4_NETWORK_TYPE; + connections[index].RemoteEndpoint.Address.Ipv4 = tcp4Table->table[i].dwRemoteAddr; + connections[index].RemoteEndpoint.Port = _byteswap_ushort((USHORT)tcp4Table->table[i].dwRemotePort); + + connections[index].State = tcp4Table->table[i].dwState; + connections[index].ProcessId = UlongToHandle(tcp4Table->table[i].dwOwningPid); + connections[index].CreateTime = tcp4Table->table[i].liCreateTimestamp; + memcpy( + connections[index].OwnerInfo, + tcp4Table->table[i].OwningModuleInfo, + sizeof(ULONGLONG) * min(PH_NETWORK_OWNER_INFO_SIZE, TCPIP_OWNING_MODULE_SIZE) + ); + + index++; + } + + PhFree(tcp4Table); + } + + if (tcp6Table) + { + for (i = 0; i < tcp6Table->dwNumEntries; i++) + { + connections[index].ProtocolType = PH_TCP6_NETWORK_PROTOCOL; + + connections[index].LocalEndpoint.Address.Type = PH_IPV6_NETWORK_TYPE; + memcpy(connections[index].LocalEndpoint.Address.Ipv6, tcp6Table->table[i].ucLocalAddr, 16); + connections[index].LocalEndpoint.Port = _byteswap_ushort((USHORT)tcp6Table->table[i].dwLocalPort); + + connections[index].RemoteEndpoint.Address.Type = PH_IPV6_NETWORK_TYPE; + memcpy(connections[index].RemoteEndpoint.Address.Ipv6, tcp6Table->table[i].ucRemoteAddr, 16); + connections[index].RemoteEndpoint.Port = _byteswap_ushort((USHORT)tcp6Table->table[i].dwRemotePort); + + connections[index].State = tcp6Table->table[i].dwState; + connections[index].ProcessId = UlongToHandle(tcp6Table->table[i].dwOwningPid); + connections[index].CreateTime = tcp6Table->table[i].liCreateTimestamp; + memcpy( + connections[index].OwnerInfo, + tcp6Table->table[i].OwningModuleInfo, + sizeof(ULONGLONG) * min(PH_NETWORK_OWNER_INFO_SIZE, TCPIP_OWNING_MODULE_SIZE) + ); + + index++; + } + + PhFree(tcp6Table); + } + + if (udp4Table) + { + for (i = 0; i < udp4Table->dwNumEntries; i++) + { + connections[index].ProtocolType = PH_UDP4_NETWORK_PROTOCOL; + + connections[index].LocalEndpoint.Address.Type = PH_IPV4_NETWORK_TYPE; + connections[index].LocalEndpoint.Address.Ipv4 = udp4Table->table[i].dwLocalAddr; + connections[index].LocalEndpoint.Port = _byteswap_ushort((USHORT)udp4Table->table[i].dwLocalPort); + + connections[index].RemoteEndpoint.Address.Type = 0; + + connections[index].State = 0; + connections[index].ProcessId = UlongToHandle(udp4Table->table[i].dwOwningPid); + connections[index].CreateTime = udp4Table->table[i].liCreateTimestamp; + memcpy( + connections[index].OwnerInfo, + udp4Table->table[i].OwningModuleInfo, + sizeof(ULONGLONG) * min(PH_NETWORK_OWNER_INFO_SIZE, TCPIP_OWNING_MODULE_SIZE) + ); + + index++; + } + + PhFree(udp4Table); + } + + if (udp6Table) + { + for (i = 0; i < udp6Table->dwNumEntries; i++) + { + connections[index].ProtocolType = PH_UDP6_NETWORK_PROTOCOL; + + connections[index].LocalEndpoint.Address.Type = PH_IPV6_NETWORK_TYPE; + memcpy(connections[index].LocalEndpoint.Address.Ipv6, udp6Table->table[i].ucLocalAddr, 16); + connections[index].LocalEndpoint.Port = _byteswap_ushort((USHORT)udp6Table->table[i].dwLocalPort); + + connections[index].RemoteEndpoint.Address.Type = 0; + + connections[index].State = 0; + connections[index].ProcessId = UlongToHandle(udp6Table->table[i].dwOwningPid); + connections[index].CreateTime = udp6Table->table[i].liCreateTimestamp; + memcpy( + connections[index].OwnerInfo, + udp6Table->table[i].OwningModuleInfo, + sizeof(ULONGLONG) * min(PH_NETWORK_OWNER_INFO_SIZE, TCPIP_OWNING_MODULE_SIZE) + ); + + index++; + } + + PhFree(udp6Table); + } + + *NumberOfConnections = count; + *Connections = connections; + + return TRUE; +} diff --git a/ProcessHacker/netstk.c b/ProcessHacker/netstk.c new file mode 100644 index 0000000..4a055f3 --- /dev/null +++ b/ProcessHacker/netstk.c @@ -0,0 +1,238 @@ +/* + * Process Hacker - + * network stack viewer + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct NETWORK_STACK_CONTEXT +{ + HWND ListViewHandle; + PPH_NETWORK_ITEM NetworkItem; + PPH_SYMBOL_PROVIDER SymbolProvider; + HANDLE LoadingProcessId; +} NETWORK_STACK_CONTEXT, *PNETWORK_STACK_CONTEXT; + +INT_PTR CALLBACK PhpNetworkStackDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +static BOOLEAN LoadSymbolsEnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PNETWORK_STACK_CONTEXT context = Context; + PPH_SYMBOL_PROVIDER symbolProvider = context->SymbolProvider; + + // If we're loading kernel module symbols for a process other than + // System, ignore modules which are in user space. This may happen + // in Windows 7. + if ( + context->LoadingProcessId == SYSTEM_PROCESS_ID && + context->NetworkItem->ProcessId != SYSTEM_PROCESS_ID && + (ULONG_PTR)Module->BaseAddress <= PhSystemBasicInformation.MaximumUserModeAddress + ) + return TRUE; + + PhLoadModuleSymbolProvider( + symbolProvider, + Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, + Module->Size + ); + + return TRUE; +} + +VOID PhShowNetworkStackDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + NETWORK_STACK_CONTEXT networkStackContext; + + networkStackContext.NetworkItem = NetworkItem; + networkStackContext.SymbolProvider = PhCreateSymbolProvider(NetworkItem->ProcessId); + + if (networkStackContext.SymbolProvider->IsRealHandle) + { + // Load symbols for the process. + networkStackContext.LoadingProcessId = NetworkItem->ProcessId; + PhEnumGenericModules( + NetworkItem->ProcessId, + networkStackContext.SymbolProvider->ProcessHandle, + 0, + LoadSymbolsEnumGenericModulesCallback, + &networkStackContext + ); + // Load symbols for kernel-mode. + networkStackContext.LoadingProcessId = SYSTEM_PROCESS_ID; + PhEnumGenericModules( + SYSTEM_PROCESS_ID, + NULL, + 0, + LoadSymbolsEnumGenericModulesCallback, + &networkStackContext + ); + } + else + { + PhDereferenceObject(networkStackContext.SymbolProvider); + PhShowError(ParentWindowHandle, L"Unable to open the process."); + return; + } + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_NETSTACK), + ParentWindowHandle, + PhpNetworkStackDlgProc, + (LPARAM)&networkStackContext + ); + + PhDereferenceObject(networkStackContext.SymbolProvider); +} + +static INT_PTR CALLBACK PhpNetworkStackDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PNETWORK_STACK_CONTEXT networkStackContext; + HWND lvHandle; + PPH_LAYOUT_MANAGER layoutManager; + PVOID address; + ULONG i; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + networkStackContext = (PNETWORK_STACK_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)networkStackContext); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 350, L"Name"); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + + networkStackContext->ListViewHandle = lvHandle; + + layoutManager = PhAllocate(sizeof(PH_LAYOUT_MANAGER)); + PhInitializeLayoutManager(layoutManager, hwndDlg); + SetProp(hwndDlg, L"LayoutManager", (HANDLE)layoutManager); + + PhAddLayoutItem(layoutManager, lvHandle, NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDOK), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 190; + rect.bottom = 120; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + for (i = 0; i < PH_NETWORK_OWNER_INFO_SIZE; i++) + { + PPH_STRING name; + + address = *(PVOID *)&networkStackContext->NetworkItem->OwnerInfo[i]; + + if ((ULONG_PTR)address < PAGE_SIZE) // stop at an invalid address + break; + + name = PhGetSymbolFromAddress( + networkStackContext->SymbolProvider, + (ULONG64)address, + NULL, + NULL, + NULL, + NULL + ); + PhAddListViewItem(lvHandle, MAXINT, name->Buffer, NULL); + PhDereferenceObject(name); + } + } + break; + case WM_DESTROY: + { + PPH_LAYOUT_MANAGER layoutManager; + PNETWORK_STACK_CONTEXT networkStackContext; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhDeleteLayoutManager(layoutManager); + PhFree(layoutManager); + + networkStackContext = (PNETWORK_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + RemoveProp(hwndDlg, L"LayoutManager"); + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case IDCANCEL: // Esc and X button to close + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + case WM_SIZE: + { + PPH_LAYOUT_MANAGER layoutManager; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhLayoutManagerLayout(layoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/notifico.c b/ProcessHacker/notifico.c new file mode 100644 index 0000000..bf6c792 --- /dev/null +++ b/ProcessHacker/notifico.c @@ -0,0 +1,1341 @@ +/* + * Process Hacker - + * notification icon manager + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhNfTerminating = FALSE; +ULONG PhNfIconMask; +ULONG PhNfIconNotifyMask; +ULONG PhNfMaximumIconId = PH_ICON_DEFAULT_MAXIMUM; +PPH_NF_ICON PhNfRegisteredIcons[32] = { 0 }; +PPH_STRING PhNfIconTextCache[32] = { 0 }; +BOOLEAN PhNfMiniInfoEnabled; +BOOLEAN PhNfMiniInfoPinned; + +PH_NF_POINTERS PhNfpPointers; +PH_CALLBACK_REGISTRATION PhNfpProcessesUpdatedRegistration; +PH_NF_BITMAP PhNfpDefaultBitmapContext = { 0 }; +PH_NF_BITMAP PhNfpBlackBitmapContext = { 0 }; +HBITMAP PhNfpBlackBitmap = NULL; +HICON PhNfpBlackIcon = NULL; + +static POINT IconClickLocation; +static PH_NF_MSG_SHOWMINIINFOSECTION_DATA IconClickShowMiniInfoSectionData; +static BOOLEAN IconClickUpDueToDown; +static BOOLEAN IconDisableHover; + +VOID PhNfLoadStage1( + VOID + ) +{ + PPH_STRING iconList; + PH_STRINGREF part; + PH_STRINGREF remainingPart; + + PhNfpPointers.UpdateRegisteredIcon = PhNfpUpdateRegisteredIcon; + PhNfpPointers.BeginBitmap = PhNfpBeginBitmap; + + // Load settings for default icons. + PhNfIconMask = PhGetIntegerSetting(L"IconMask"); + + // Load settings for registered icons. + + iconList = PhGetStringSetting(L"IconMaskList"); + remainingPart = iconList->sr; + + while (remainingPart.Length != 0) + { + PhSplitStringRefAtChar(&remainingPart, '|', &part, &remainingPart); + + if (part.Length != 0) + { + PH_STRINGREF pluginName; + ULONG subId; + PPH_NF_ICON icon; + + if (PhEmParseCompoundId(&part, &pluginName, &subId) && + (icon = PhNfFindIcon(&pluginName, subId))) + { + PhNfIconMask |= icon->IconId; + } + } + } + + PhDereferenceObject(iconList); +} + +VOID PhNfLoadStage2( + VOID + ) +{ + ULONG i; + + PhNfMiniInfoEnabled = WindowsVersion >= WINDOWS_VISTA && !!PhGetIntegerSetting(L"MiniInfoWindowEnabled"); + + for (i = PH_ICON_MINIMUM; i != PhNfMaximumIconId; i <<= 1) + { + if (PhNfIconMask & i) + PhNfpAddNotifyIcon(i); + } + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + PhNfpProcessesUpdatedHandler, + NULL, + &PhNfpProcessesUpdatedRegistration + ); +} + +VOID PhNfSaveSettings( + VOID + ) +{ + ULONG registeredIconMask; + + PhSetIntegerSetting(L"IconMask", PhNfIconMask & PH_ICON_DEFAULT_ALL); + + registeredIconMask = PhNfIconMask & ~PH_ICON_DEFAULT_ALL; + + if (registeredIconMask != 0) + { + PH_STRING_BUILDER iconListBuilder; + ULONG i; + + PhInitializeStringBuilder(&iconListBuilder, 60); + + for (i = 0; i < sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON); i++) + { + if (PhNfRegisteredIcons[i]) + { + if (registeredIconMask & PhNfRegisteredIcons[i]->IconId) + { + PhAppendFormatStringBuilder( + &iconListBuilder, + L"+%s+%u|", + PhNfRegisteredIcons[i]->Plugin->Name.Buffer, + PhNfRegisteredIcons[i]->SubId + ); + } + } + } + + if (iconListBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&iconListBuilder, 1); + + PhSetStringSetting2(L"IconMaskList", &iconListBuilder.String->sr); + PhDeleteStringBuilder(&iconListBuilder); + } + else + { + PhSetStringSetting(L"IconMaskList", L""); + } +} + +VOID PhNfUninitialization( + VOID + ) +{ + ULONG i; + + // Remove all icons to prevent them hanging around after we exit. + + PhNfTerminating = TRUE; // prevent further icon updating + + for (i = PH_ICON_MINIMUM; i != PhNfMaximumIconId; i <<= 1) + { + if (PhNfIconMask & i) + PhNfpRemoveNotifyIcon(i); + } +} + +VOID PhNfForwardMessage( + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ) +{ + ULONG iconIndex = HIWORD(LParam); + PPH_NF_ICON registeredIcon = NULL; + + if (iconIndex < sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON) && PhNfRegisteredIcons[iconIndex]) + { + registeredIcon = PhNfRegisteredIcons[iconIndex]; + + if (registeredIcon->MessageCallback) + { + if (registeredIcon->MessageCallback( + registeredIcon, + WParam, + LParam, + registeredIcon->Context + )) + { + return; + } + } + } + + switch (LOWORD(LParam)) + { + case WM_LBUTTONDOWN: + { + if (PhGetIntegerSetting(L"IconSingleClick")) + { + ProcessHacker_IconClick(PhMainWndHandle); + PhNfpDisableHover(); + } + else + { + IconClickUpDueToDown = TRUE; + } + } + break; + case WM_LBUTTONUP: + { + if (!PhGetIntegerSetting(L"IconSingleClick") && PhNfMiniInfoEnabled && IconClickUpDueToDown) + { + PH_NF_MSG_SHOWMINIINFOSECTION_DATA showMiniInfoSectionData; + + if (PhNfpGetShowMiniInfoSectionData(iconIndex, registeredIcon, &showMiniInfoSectionData)) + { + GetCursorPos(&IconClickLocation); + + if (IconClickShowMiniInfoSectionData.SectionName) + { + PhFree(IconClickShowMiniInfoSectionData.SectionName); + IconClickShowMiniInfoSectionData.SectionName = NULL; + } + + if (showMiniInfoSectionData.SectionName) + { + IconClickShowMiniInfoSectionData.SectionName = PhDuplicateStringZ(showMiniInfoSectionData.SectionName); + } + + SetTimer(PhMainWndHandle, TIMER_ICON_CLICK_ACTIVATE, GetDoubleClickTime() + NFP_ICON_CLICK_ACTIVATE_DELAY, PhNfpIconClickActivateTimerProc); + } + else + { + KillTimer(PhMainWndHandle, TIMER_ICON_CLICK_ACTIVATE); + } + } + } + break; + case WM_LBUTTONDBLCLK: + { + if (!PhGetIntegerSetting(L"IconSingleClick")) + { + if (PhNfMiniInfoEnabled) + { + // We will get another WM_LBUTTONUP message corresponding to the double-click, + // and we need to make sure that it doesn't start the activation timer again. + KillTimer(PhMainWndHandle, TIMER_ICON_CLICK_ACTIVATE); + IconClickUpDueToDown = FALSE; + PhNfpDisableHover(); + } + + ProcessHacker_IconClick(PhMainWndHandle); + } + } + break; + case WM_RBUTTONUP: + case WM_CONTEXTMENU: + { + POINT location; + + if (!PhGetIntegerSetting(L"IconSingleClick") && PhNfMiniInfoEnabled) + KillTimer(PhMainWndHandle, TIMER_ICON_CLICK_ACTIVATE); + + PhPinMiniInformation(MiniInfoIconPinType, -1, 0, 0, NULL, NULL); + GetCursorPos(&location); + PhShowIconContextMenu(location); + } + break; + case NIN_KEYSELECT: + // HACK: explorer seems to send two NIN_KEYSELECT messages when the user selects the icon and presses ENTER. + if (GetForegroundWindow() != PhMainWndHandle) + ProcessHacker_IconClick(PhMainWndHandle); + break; + case NIN_BALLOONUSERCLICK: + PhShowDetailsForIconNotification(); + break; + case NIN_POPUPOPEN: + { + PH_NF_MSG_SHOWMINIINFOSECTION_DATA showMiniInfoSectionData; + POINT location; + + if (PhNfMiniInfoEnabled && !IconDisableHover && PhNfpGetShowMiniInfoSectionData(iconIndex, registeredIcon, &showMiniInfoSectionData)) + { + GetCursorPos(&location); + PhPinMiniInformation(MiniInfoIconPinType, 1, 0, PH_MINIINFO_DONT_CHANGE_SECTION_IF_PINNED, + showMiniInfoSectionData.SectionName, &location); + } + } + break; + case NIN_POPUPCLOSE: + PhPinMiniInformation(MiniInfoIconPinType, -1, 350, 0, NULL, NULL); + break; + } +} + +ULONG PhNfGetMaximumIconId( + VOID + ) +{ + return PhNfMaximumIconId; +} + +ULONG PhNfTestIconMask( + _In_ ULONG Id + ) +{ + return PhNfIconMask & Id; +} + +VOID PhNfSetVisibleIcon( + _In_ ULONG Id, + _In_ BOOLEAN Visible + ) +{ + if (Visible) + { + PhNfIconMask |= Id; + PhNfpAddNotifyIcon(Id); + } + else + { + PhNfIconMask &= ~Id; + PhNfpRemoveNotifyIcon(Id); + } +} + +BOOLEAN PhNfShowBalloonTip( + _In_opt_ ULONG Id, + _In_ PWSTR Title, + _In_ PWSTR Text, + _In_ ULONG Timeout, + _In_ ULONG Flags + ) +{ + NOTIFYICONDATA notifyIcon = { NOTIFYICONDATA_V3_SIZE }; + + if (Id == 0) + { + // Choose the first visible icon. + Id = PhNfIconMask; + } + + if (!_BitScanForward(&Id, Id)) + return FALSE; + + notifyIcon.hWnd = PhMainWndHandle; + notifyIcon.uID = Id; + notifyIcon.uFlags = NIF_INFO; + wcsncpy_s(notifyIcon.szInfoTitle, sizeof(notifyIcon.szInfoTitle) / sizeof(WCHAR), Title, _TRUNCATE); + wcsncpy_s(notifyIcon.szInfo, sizeof(notifyIcon.szInfo) / sizeof(WCHAR), Text, _TRUNCATE); + notifyIcon.uTimeout = Timeout; + notifyIcon.dwInfoFlags = Flags; + + Shell_NotifyIcon(NIM_MODIFY, ¬ifyIcon); + + return TRUE; +} + +HICON PhNfBitmapToIcon( + _In_ HBITMAP Bitmap + ) +{ + ICONINFO iconInfo; + + PhNfpGetBlackIcon(); + + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = PhNfpBlackBitmap; + iconInfo.hbmColor = Bitmap; + + return CreateIconIndirect(&iconInfo); +} + +PPH_NF_ICON PhNfRegisterIcon( + _In_ struct _PH_PLUGIN *Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PWSTR Text, + _In_ ULONG Flags, + _In_opt_ PPH_NF_ICON_UPDATE_CALLBACK UpdateCallback, + _In_opt_ PPH_NF_ICON_MESSAGE_CALLBACK MessageCallback + ) +{ + PPH_NF_ICON icon; + ULONG iconId; + ULONG iconIndex; + + if (PhNfMaximumIconId == PH_ICON_LIMIT) + { + // No room for any more icons. + return NULL; + } + + iconId = PhNfMaximumIconId; + + if (!_BitScanReverse(&iconIndex, iconId) || + iconIndex >= sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON)) + { + // Should never happen. + return NULL; + } + + PhNfMaximumIconId <<= 1; + + icon = PhAllocate(sizeof(PH_NF_ICON)); + icon->Plugin = Plugin; + icon->SubId = SubId; + icon->Context = Context; + icon->Pointers = &PhNfpPointers; + icon->Text = Text; + icon->Flags = Flags; + icon->IconId = iconId; + icon->UpdateCallback = UpdateCallback; + icon->MessageCallback = MessageCallback; + + PhNfRegisteredIcons[iconIndex] = icon; + + return icon; +} + +PPH_NF_ICON PhNfGetIconById( + _In_ ULONG Id + ) +{ + ULONG iconIndex; + + if (!_BitScanReverse(&iconIndex, Id)) + return NULL; + + return PhNfRegisteredIcons[iconIndex]; +} + +PPH_NF_ICON PhNfFindIcon( + _In_ PPH_STRINGREF PluginName, + _In_ ULONG SubId + ) +{ + ULONG i; + + for (i = 0; i < sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON); i++) + { + if (PhNfRegisteredIcons[i]) + { + if (PhNfRegisteredIcons[i]->SubId == SubId && + PhEqualStringRef(PluginName, &PhNfRegisteredIcons[i]->Plugin->AppContext.AppName, FALSE)) + { + return PhNfRegisteredIcons[i]; + } + } + } + + return NULL; +} + +VOID PhNfNotifyMiniInfoPinned( + _In_ BOOLEAN Pinned + ) +{ + ULONG i; + ULONG id; + + if (PhNfMiniInfoPinned != Pinned) + { + PhNfMiniInfoPinned = Pinned; + + // Go through every icon and set/clear the NIF_SHOWTIP flag depending on whether the mini info window is + // pinned. If it's pinned then we want to show normal tooltips, because the section doesn't change + // automatically when the cursor hovers over an icon. + for (i = 0; i < sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON); i++) + { + id = 1 << i; + + if (PhNfIconMask & id) + { + PhNfpModifyNotifyIcon(id, NIF_TIP, PhNfIconTextCache[i], NULL); + } + } + } +} + +HICON PhNfpGetBlackIcon( + VOID + ) +{ + if (!PhNfpBlackIcon) + { + ULONG width; + ULONG height; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + ICONINFO iconInfo; + + PhNfpBeginBitmap2(&PhNfpBlackBitmapContext, &width, &height, &PhNfpBlackBitmap, &bits, &hdc, &oldBitmap); + memset(bits, 0, width * height * sizeof(ULONG)); + + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = PhNfpBlackBitmap; + iconInfo.hbmColor = PhNfpBlackBitmap; + PhNfpBlackIcon = CreateIconIndirect(&iconInfo); + + SelectObject(hdc, oldBitmap); + } + + return PhNfpBlackIcon; +} + +BOOLEAN PhNfpAddNotifyIcon( + _In_ ULONG Id + ) +{ + NOTIFYICONDATA notifyIcon = { sizeof(NOTIFYICONDATA) }; + PPH_NF_ICON icon; + + if (PhNfTerminating) + return FALSE; + if ((icon = PhNfGetIconById(Id)) && (icon->Flags & PH_NF_ICON_UNAVAILABLE)) + return FALSE; + + // The IDs we pass to explorer are bit indicies, not the normal mask values. + + if (!_BitScanForward(&Id, Id)) + return FALSE; + + notifyIcon.hWnd = PhMainWndHandle; + notifyIcon.uID = Id; + notifyIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + notifyIcon.uCallbackMessage = WM_PH_NOTIFY_ICON_MESSAGE; + wcsncpy_s( + notifyIcon.szTip, sizeof(notifyIcon.szTip) / sizeof(WCHAR), + PhGetStringOrDefault(PhNfIconTextCache[Id], PhApplicationName), + _TRUNCATE + ); + notifyIcon.hIcon = PhNfpGetBlackIcon(); + + if (!PhNfMiniInfoEnabled || PhNfMiniInfoPinned || (icon && !(icon->Flags & PH_NF_ICON_SHOW_MINIINFO))) + notifyIcon.uFlags |= NIF_SHOWTIP; + + Shell_NotifyIcon(NIM_ADD, ¬ifyIcon); + + if (WindowsVersion >= WINDOWS_VISTA) + { + notifyIcon.uVersion = NOTIFYICON_VERSION_4; + Shell_NotifyIcon(NIM_SETVERSION, ¬ifyIcon); + } + + return TRUE; +} + +BOOLEAN PhNfpRemoveNotifyIcon( + _In_ ULONG Id + ) +{ + NOTIFYICONDATA notifyIcon = { sizeof(NOTIFYICONDATA) }; + PPH_NF_ICON icon; + + if ((icon = PhNfGetIconById(Id)) && (icon->Flags & PH_NF_ICON_UNAVAILABLE)) + return FALSE; + + if (!_BitScanForward(&Id, Id)) + return FALSE; + + notifyIcon.hWnd = PhMainWndHandle; + notifyIcon.uID = Id; + + Shell_NotifyIcon(NIM_DELETE, ¬ifyIcon); + + return TRUE; +} + +BOOLEAN PhNfpModifyNotifyIcon( + _In_ ULONG Id, + _In_ ULONG Flags, + _In_opt_ PPH_STRING Text, + _In_opt_ HICON Icon + ) +{ + NOTIFYICONDATA notifyIcon = { sizeof(NOTIFYICONDATA) }; + PPH_NF_ICON icon; + ULONG notifyId; + + if (PhNfTerminating) + return FALSE; + if ((icon = PhNfGetIconById(Id)) && (icon->Flags & PH_NF_ICON_UNAVAILABLE)) + return FALSE; + + if (!_BitScanForward(¬ifyId, Id)) + return FALSE; + + notifyIcon.hWnd = PhMainWndHandle; + notifyIcon.uID = notifyId; + notifyIcon.uFlags = Flags; + + if (Flags & NIF_TIP) + { + PhSwapReference(&PhNfIconTextCache[notifyId], Text); + wcsncpy_s( + notifyIcon.szTip, + sizeof(notifyIcon.szTip) / sizeof(WCHAR), + PhGetStringOrDefault(Text, PhApplicationName), + _TRUNCATE + ); + } + + notifyIcon.hIcon = Icon; + + if (!PhNfMiniInfoEnabled || PhNfMiniInfoPinned || (icon && !(icon->Flags & PH_NF_ICON_SHOW_MINIINFO))) + notifyIcon.uFlags |= NIF_SHOWTIP; + + if (!Shell_NotifyIcon(NIM_MODIFY, ¬ifyIcon)) + { + // Explorer probably died and we lost our icon. Try to add the icon, and try again. + PhNfpAddNotifyIcon(Id); + Shell_NotifyIcon(NIM_MODIFY, ¬ifyIcon); + } + + return TRUE; +} + +VOID PhNfpProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ULONG registeredIconMask; + + // We do icon updating on the provider thread so we don't block the main GUI when + // explorer is not responding. + + if (PhNfIconMask & PH_ICON_CPU_HISTORY) + PhNfpUpdateIconCpuHistory(); + if (PhNfIconMask & PH_ICON_IO_HISTORY) + PhNfpUpdateIconIoHistory(); + if (PhNfIconMask & PH_ICON_COMMIT_HISTORY) + PhNfpUpdateIconCommitHistory(); + if (PhNfIconMask & PH_ICON_PHYSICAL_HISTORY) + PhNfpUpdateIconPhysicalHistory(); + if (PhNfIconMask & PH_ICON_CPU_USAGE) + PhNfpUpdateIconCpuUsage(); + + registeredIconMask = PhNfIconMask & ~PH_ICON_DEFAULT_ALL; + + if (registeredIconMask != 0) + { + ULONG i; + + for (i = 0; i < sizeof(PhNfRegisteredIcons) / sizeof(PPH_NF_ICON); i++) + { + if (PhNfRegisteredIcons[i] && (registeredIconMask & PhNfRegisteredIcons[i]->IconId)) + { + PhNfpUpdateRegisteredIcon(PhNfRegisteredIcons[i]); + } + } + } +} + +VOID PhNfpUpdateRegisteredIcon( + _In_ PPH_NF_ICON Icon + ) +{ + PVOID newIconOrBitmap; + ULONG updateFlags; + PPH_STRING newText; + HICON newIcon; + ULONG flags; + + if (!Icon->UpdateCallback) + return; + + newIconOrBitmap = NULL; + newText = NULL; + newIcon = NULL; + flags = 0; + + Icon->UpdateCallback( + Icon, + &newIconOrBitmap, + &updateFlags, + &newText, + Icon->Context + ); + + if (newIconOrBitmap) + { + if (updateFlags & PH_NF_UPDATE_IS_BITMAP) + newIcon = PhNfBitmapToIcon(newIconOrBitmap); + else + newIcon = newIconOrBitmap; + + flags |= NIF_ICON; + } + + if (newText) + flags |= NIF_TIP; + + if (flags != 0) + PhNfpModifyNotifyIcon(Icon->IconId, flags, newText, newIcon); + + if (newIcon && (updateFlags & PH_NF_UPDATE_IS_BITMAP)) + DestroyIcon(newIcon); + + if (newIconOrBitmap && (updateFlags & PH_NF_UPDATE_DESTROY_RESOURCE)) + { + if (updateFlags & PH_NF_UPDATE_IS_BITMAP) + DeleteObject(newIconOrBitmap); + else + DestroyIcon(newIconOrBitmap); + } + + if (newText) + PhDereferenceObject(newText); +} + +VOID PhNfpBeginBitmap( + _Out_ PULONG Width, + _Out_ PULONG Height, + _Out_ HBITMAP *Bitmap, + _Out_opt_ PVOID *Bits, + _Out_ HDC *Hdc, + _Out_ HBITMAP *OldBitmap + ) +{ + PhNfpBeginBitmap2(&PhNfpDefaultBitmapContext, Width, Height, Bitmap, Bits, Hdc, OldBitmap); +} + +VOID PhNfpBeginBitmap2( + _Inout_ PPH_NF_BITMAP Context, + _Out_ PULONG Width, + _Out_ PULONG Height, + _Out_ HBITMAP *Bitmap, + _Out_opt_ PVOID *Bits, + _Out_ HDC *Hdc, + _Out_ HBITMAP *OldBitmap + ) +{ + if (!Context->Initialized) + { + HDC screenHdc; + + screenHdc = GetDC(NULL); + Context->Hdc = CreateCompatibleDC(screenHdc); + + memset(&Context->Header, 0, sizeof(BITMAPINFOHEADER)); + Context->Header.biSize = sizeof(BITMAPINFOHEADER); + Context->Header.biWidth = PhSmallIconSize.X; + Context->Header.biHeight = PhSmallIconSize.Y; + Context->Header.biPlanes = 1; + Context->Header.biBitCount = 32; + Context->Bitmap = CreateDIBSection(screenHdc, (BITMAPINFO *)&Context->Header, DIB_RGB_COLORS, &Context->Bits, NULL, 0); + + ReleaseDC(NULL, screenHdc); + + Context->Initialized = TRUE; + } + + *Width = PhSmallIconSize.X; + *Height = PhSmallIconSize.Y; + *Bitmap = Context->Bitmap; + if (Bits) *Bits = Context->Bits; + *Hdc = Context->Hdc; + *OldBitmap = SelectObject(Context->Hdc, Context->Bitmap); +} + +VOID PhNfpUpdateIconCpuHistory( + VOID + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + PH_GRAPH_USE_LINE_2, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + PFLOAT lineData2; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HICON icon; + HANDLE maxCpuProcessId; + PPH_PROCESS_ITEM maxCpuProcessItem; + PH_FORMAT format[8]; + PPH_STRING text; + + // Icon + + PhNfpBeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + lineData2 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, PhCpuKernelHistory.Count); + PhCopyCircularBuffer_FLOAT(&PhCpuKernelHistory, lineData1, lineDataCount); + PhCopyCircularBuffer_FLOAT(&PhCpuUserHistory, lineData2, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineData2 = lineData2; + drawInfo.LineColor1 = PhCsColorCpuKernel; + drawInfo.LineColor2 = PhCsColorCpuUser; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorCpuKernel); + drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorCpuUser); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + icon = PhNfBitmapToIcon(bitmap); + + // Text + + if (PhMaxCpuHistory.Count != 0) + maxCpuProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&PhMaxCpuHistory, 0)); + else + maxCpuProcessId = NULL; + + if (maxCpuProcessId) + maxCpuProcessItem = PhReferenceProcessItem(maxCpuProcessId); + else + maxCpuProcessItem = NULL; + + PhInitFormatS(&format[0], L"CPU Usage: "); + PhInitFormatF(&format[1], (PhCpuKernelUsage + PhCpuUserUsage) * 100, 2); + PhInitFormatC(&format[2], '%'); + + if (maxCpuProcessItem) + { + PhInitFormatC(&format[3], '\n'); + PhInitFormatSR(&format[4], maxCpuProcessItem->ProcessName->sr); + PhInitFormatS(&format[5], L": "); + PhInitFormatF(&format[6], maxCpuProcessItem->CpuUsage * 100, 2); + PhInitFormatC(&format[7], '%'); + } + + text = PhFormat(format, maxCpuProcessItem ? 8 : 3, 128); + if (maxCpuProcessItem) PhDereferenceObject(maxCpuProcessItem); + + PhNfpModifyNotifyIcon(PH_ICON_CPU_HISTORY, NIF_TIP | NIF_ICON, text, icon); + + DestroyIcon(icon); + PhDereferenceObject(text); +} + +VOID PhNfpUpdateIconIoHistory( + VOID + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + PH_GRAPH_USE_LINE_2, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + PFLOAT lineData2; + FLOAT max; + ULONG i; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HICON icon; + HANDLE maxIoProcessId; + PPH_PROCESS_ITEM maxIoProcessItem; + PH_FORMAT format[8]; + PPH_STRING text; + + // Icon + + PhNfpBeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + lineData2 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, PhIoReadHistory.Count); + max = 1024 * 1024; // minimum scaling of 1 MB. + + for (i = 0; i < lineDataCount; i++) + { + lineData1[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoOtherHistory, i); + lineData2[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoWriteHistory, i); + + if (max < lineData1[i] + lineData2[i]) + max = lineData1[i] + lineData2[i]; + } + + PhDivideSinglesBySingle(lineData1, max, lineDataCount); + PhDivideSinglesBySingle(lineData2, max, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineData2 = lineData2; + drawInfo.LineColor1 = PhCsColorIoReadOther; + drawInfo.LineColor2 = PhCsColorIoWrite; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorIoReadOther); + drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorIoWrite); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + icon = PhNfBitmapToIcon(bitmap); + + // Text + + if (PhMaxIoHistory.Count != 0) + maxIoProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&PhMaxIoHistory, 0)); + else + maxIoProcessId = NULL; + + if (maxIoProcessId) + maxIoProcessItem = PhReferenceProcessItem(maxIoProcessId); + else + maxIoProcessItem = NULL; + + PhInitFormatS(&format[0], L"I/O\nR: "); + PhInitFormatSize(&format[1], PhIoReadDelta.Delta); + PhInitFormatS(&format[2], L"\nW: "); + PhInitFormatSize(&format[3], PhIoWriteDelta.Delta); + PhInitFormatS(&format[4], L"\nO: "); + PhInitFormatSize(&format[5], PhIoOtherDelta.Delta); + + if (maxIoProcessItem) + { + PhInitFormatC(&format[6], '\n'); + PhInitFormatSR(&format[7], maxIoProcessItem->ProcessName->sr); + } + + text = PhFormat(format, maxIoProcessItem ? 8 : 6, 128); + if (maxIoProcessItem) PhDereferenceObject(maxIoProcessItem); + + PhNfpModifyNotifyIcon(PH_ICON_IO_HISTORY, NIF_TIP | NIF_ICON, text, icon); + + DestroyIcon(icon); + PhDereferenceObject(text); +} + +VOID PhNfpUpdateIconCommitHistory( + VOID + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + 0, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + ULONG i; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HICON icon; + DOUBLE commitFraction; + PH_FORMAT format[5]; + PPH_STRING text; + + // Icon + + PhNfpBeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, PhCommitHistory.Count); + + for (i = 0; i < lineDataCount; i++) + lineData1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhCommitHistory, i); + + PhDivideSinglesBySingle(lineData1, (FLOAT)PhPerfInformation.CommitLimit, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineColor1 = PhCsColorPrivate; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorPrivate); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + icon = PhNfBitmapToIcon(bitmap); + + // Text + + commitFraction = (DOUBLE)PhPerfInformation.CommittedPages / PhPerfInformation.CommitLimit; + + PhInitFormatS(&format[0], L"Commit: "); + PhInitFormatSize(&format[1], UInt32x32To64(PhPerfInformation.CommittedPages, PAGE_SIZE)); + PhInitFormatS(&format[2], L" ("); + PhInitFormatF(&format[3], commitFraction * 100, 2); + PhInitFormatS(&format[4], L"%)"); + + text = PhFormat(format, 5, 96); + + PhNfpModifyNotifyIcon(PH_ICON_COMMIT_HISTORY, NIF_TIP | NIF_ICON, text, icon); + + DestroyIcon(icon); + PhDereferenceObject(text); +} + +VOID PhNfpUpdateIconPhysicalHistory( + VOID + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + 0, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + ULONG i; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HICON icon; + ULONG physicalUsage; + FLOAT physicalFraction; + PH_FORMAT format[5]; + PPH_STRING text; + + // Icon + + PhNfpBeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, PhCommitHistory.Count); + + for (i = 0; i < lineDataCount; i++) + lineData1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, i); + + PhDivideSinglesBySingle(lineData1, (FLOAT)PhSystemBasicInformation.NumberOfPhysicalPages, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineColor1 = PhCsColorPhysical; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorPhysical); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + icon = PhNfBitmapToIcon(bitmap); + + // Text + + physicalUsage = PhSystemBasicInformation.NumberOfPhysicalPages - PhPerfInformation.AvailablePages; + physicalFraction = (FLOAT)physicalUsage / PhSystemBasicInformation.NumberOfPhysicalPages; + + PhInitFormatS(&format[0], L"Physical memory: "); + PhInitFormatSize(&format[1], UInt32x32To64(physicalUsage, PAGE_SIZE)); + PhInitFormatS(&format[2], L" ("); + PhInitFormatF(&format[3], physicalFraction * 100, 2); + PhInitFormatS(&format[4], L"%)"); + + text = PhFormat(format, 5, 96); + + PhNfpModifyNotifyIcon(PH_ICON_PHYSICAL_HISTORY, NIF_TIP | NIF_ICON, text, icon); + + DestroyIcon(icon); + PhDereferenceObject(text); +} + +VOID PhNfpUpdateIconCpuUsage( + VOID + ) +{ + ULONG width; + ULONG height; + HBITMAP bitmap; + HDC hdc; + HBITMAP oldBitmap; + HICON icon; + HANDLE maxCpuProcessId; + PPH_PROCESS_ITEM maxCpuProcessItem; + PPH_STRING maxCpuText = NULL; + PPH_STRING text; + + // Icon + + PhNfpBeginBitmap(&width, &height, &bitmap, NULL, &hdc, &oldBitmap); + + // This stuff is copied from CpuUsageIcon.cs (PH 1.x). + { + COLORREF kColor = PhCsColorCpuKernel; + COLORREF uColor = PhCsColorCpuUser; + COLORREF kbColor = PhHalveColorBrightness(PhCsColorCpuKernel); + COLORREF ubColor = PhHalveColorBrightness(PhCsColorCpuUser); + FLOAT k = PhCpuKernelUsage; + FLOAT u = PhCpuUserUsage; + LONG kl = (LONG)(k * height); + LONG ul = (LONG)(u * height); + RECT rect; + HBRUSH dcBrush; + HBRUSH dcPen; + POINT points[2]; + + dcBrush = GetStockObject(DC_BRUSH); + dcPen = GetStockObject(DC_PEN); + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + SetDCBrushColor(hdc, RGB(0x00, 0x00, 0x00)); + FillRect(hdc, &rect, dcBrush); + + // Draw the base line. + if (kl + ul == 0) + { + SelectObject(hdc, dcPen); + SetDCPenColor(hdc, uColor); + points[0].x = 0; + points[0].y = height - 1; + points[1].x = width; + points[1].y = height - 1; + Polyline(hdc, points, 2); + } + else + { + rect.left = 0; + rect.top = height - ul - kl; + rect.right = width; + rect.bottom = height - kl; + SetDCBrushColor(hdc, ubColor); + FillRect(hdc, &rect, dcBrush); + + points[0].x = 0; + points[0].y = height - 1 - ul - kl; + if (points[0].y < 0) points[0].y = 0; + points[1].x = width; + points[1].y = points[0].y; + SelectObject(hdc, dcPen); + SetDCPenColor(hdc, uColor); + Polyline(hdc, points, 2); + + if (kl != 0) + { + rect.left = 0; + rect.top = height - kl; + rect.right = width; + rect.bottom = height; + SetDCBrushColor(hdc, kbColor); + FillRect(hdc, &rect, dcBrush); + + points[0].x = 0; + points[0].y = height - 1 - kl; + if (points[0].y < 0) points[0].y = 0; + points[1].x = width; + points[1].y = points[0].y; + SelectObject(hdc, dcPen); + SetDCPenColor(hdc, kColor); + Polyline(hdc, points, 2); + } + } + } + + SelectObject(hdc, oldBitmap); + icon = PhNfBitmapToIcon(bitmap); + + // Text + + if (PhMaxCpuHistory.Count != 0) + maxCpuProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&PhMaxCpuHistory, 0)); + else + maxCpuProcessId = NULL; + + if (maxCpuProcessId) + { + if (maxCpuProcessItem = PhReferenceProcessItem(maxCpuProcessId)) + { + maxCpuText = PhFormatString( + L"\n%s: %.2f%%", + maxCpuProcessItem->ProcessName->Buffer, + maxCpuProcessItem->CpuUsage * 100 + ); + PhDereferenceObject(maxCpuProcessItem); + } + } + + text = PhFormatString(L"CPU usage: %.2f%%%s", (PhCpuKernelUsage + PhCpuUserUsage) * 100, PhGetStringOrEmpty(maxCpuText)); + if (maxCpuText) PhDereferenceObject(maxCpuText); + + PhNfpModifyNotifyIcon(PH_ICON_CPU_USAGE, NIF_TIP | NIF_ICON, text, icon); + + DestroyIcon(icon); + PhDereferenceObject(text); +} + +BOOLEAN PhNfpGetShowMiniInfoSectionData( + _In_ ULONG IconIndex, + _In_ PPH_NF_ICON RegisteredIcon, + _Out_ PPH_NF_MSG_SHOWMINIINFOSECTION_DATA Data + ) +{ + BOOLEAN showMiniInfo = FALSE; + + if (RegisteredIcon) + { + Data->SectionName = NULL; + + if (RegisteredIcon->Flags & PH_NF_ICON_SHOW_MINIINFO) + { + if (RegisteredIcon->MessageCallback) + { + RegisteredIcon->MessageCallback( + RegisteredIcon, + (ULONG_PTR)Data, + MAKELPARAM(PH_NF_MSG_SHOWMINIINFOSECTION, 0), + RegisteredIcon->Context + ); + } + + showMiniInfo = TRUE; + } + } + else + { + switch (1 << IconIndex) + { + case PH_ICON_CPU_HISTORY: + case PH_ICON_CPU_USAGE: + Data->SectionName = L"CPU"; + break; + case PH_ICON_IO_HISTORY: + Data->SectionName = L"I/O"; + break; + case PH_ICON_COMMIT_HISTORY: + Data->SectionName = L"Commit charge"; + break; + case PH_ICON_PHYSICAL_HISTORY: + Data->SectionName = L"Physical memory"; + break; + } + + showMiniInfo = TRUE; + } + + return showMiniInfo; +} + +VOID PhNfpIconClickActivateTimerProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ UINT_PTR idEvent, + _In_ DWORD dwTime + ) +{ + PhPinMiniInformation(MiniInfoActivePinType, 1, 0, + PH_MINIINFO_ACTIVATE_WINDOW | PH_MINIINFO_DONT_CHANGE_SECTION_IF_PINNED, + IconClickShowMiniInfoSectionData.SectionName, &IconClickLocation); + KillTimer(PhMainWndHandle, TIMER_ICON_CLICK_ACTIVATE); +} + +VOID PhNfpDisableHover( + VOID + ) +{ + IconDisableHover = TRUE; + SetTimer(PhMainWndHandle, TIMER_ICON_RESTORE_HOVER, NFP_ICON_RESTORE_HOVER_DELAY, PhNfpIconRestoreHoverTimerProc); +} + +VOID PhNfpIconRestoreHoverTimerProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ UINT_PTR idEvent, + _In_ DWORD dwTime + ) +{ + IconDisableHover = FALSE; + KillTimer(PhMainWndHandle, TIMER_ICON_RESTORE_HOVER); +} diff --git a/ProcessHacker/ntobjprp.c b/ProcessHacker/ntobjprp.c new file mode 100644 index 0000000..920d1f4 --- /dev/null +++ b/ProcessHacker/ntobjprp.c @@ -0,0 +1,755 @@ +/* + * Process Hacker - + * properties for NT objects + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +typedef struct _COMMON_PAGE_CONTEXT +{ + PPH_OPEN_OBJECT OpenObject; + PVOID Context; +} COMMON_PAGE_CONTEXT, *PCOMMON_PAGE_CONTEXT; + +HPROPSHEETPAGE PhpCommonCreatePage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ); + +INT CALLBACK PhpCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +INT_PTR CALLBACK PhpEventPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpEventPairPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpMutantPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpSectionPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpSemaphorePageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpTimerPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static HPROPSHEETPAGE PhpCommonCreatePage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + PROPSHEETPAGE propSheetPage; + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhCreateAlloc(sizeof(COMMON_PAGE_CONTEXT)); + memset(pageContext, 0, sizeof(COMMON_PAGE_CONTEXT)); + pageContext->OpenObject = OpenObject; + pageContext->Context = Context; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USECALLBACK; + propSheetPage.pszTemplate = Template; + propSheetPage.pfnDlgProc = DlgProc; + propSheetPage.lParam = (LPARAM)pageContext; + propSheetPage.pfnCallback = PhpCommonPropPageProc; + + propSheetPageHandle = CreatePropertySheetPage(&propSheetPage); + // CreatePropertySheetPage would have sent PSPCB_ADDREF (below), + // which would have added a reference. + PhDereferenceObject(pageContext); + + return propSheetPageHandle; +} + +INT CALLBACK PhpCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = (PCOMMON_PAGE_CONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + { + PhReferenceObject(pageContext); + } + else if (uMsg == PSPCB_RELEASE) + { + PhDereferenceObject(pageContext); + } + + return 1; +} + +FORCEINLINE PCOMMON_PAGE_CONTEXT PhpCommonPageHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return (PCOMMON_PAGE_CONTEXT)PhpGenericPropertyPageHeader( + hwndDlg, uMsg, wParam, lParam, L"PageContext"); +} + +HPROPSHEETPAGE PhCreateEventPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJEVENT), + PhpEventPageProc + ); +} + +static VOID PhpRefreshEventPageInfo( + _In_ HWND hwndDlg, + _In_ PCOMMON_PAGE_CONTEXT PageContext + ) +{ + HANDLE eventHandle; + + if (NT_SUCCESS(PageContext->OpenObject( + &eventHandle, + EVENT_QUERY_STATE, + PageContext->Context + ))) + { + EVENT_BASIC_INFORMATION basicInfo; + PWSTR eventType = L"Unknown"; + PWSTR eventState = L"Unknown"; + + if (NT_SUCCESS(PhGetEventBasicInformation(eventHandle, &basicInfo))) + { + switch (basicInfo.EventType) + { + case NotificationEvent: + eventType = L"Notification"; + break; + case SynchronizationEvent: + eventType = L"Synchronization"; + break; + } + + eventState = basicInfo.EventState > 0 ? L"True" : L"False"; + } + + SetDlgItemText(hwndDlg, IDC_TYPE, eventType); + SetDlgItemText(hwndDlg, IDC_SIGNALED, eventState); + + NtClose(eventHandle); + } +} + +INT_PTR CALLBACK PhpEventPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpRefreshEventPageInfo(hwndDlg, pageContext); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_SET: + case IDC_RESET: + case IDC_PULSE: + { + NTSTATUS status; + HANDLE eventHandle; + + if (NT_SUCCESS(status = pageContext->OpenObject( + &eventHandle, + EVENT_MODIFY_STATE, + pageContext->Context + ))) + { + switch (LOWORD(wParam)) + { + case IDC_SET: + NtSetEvent(eventHandle, NULL); + break; + case IDC_RESET: + NtResetEvent(eventHandle, NULL); + break; + case IDC_PULSE: + NtPulseEvent(eventHandle, NULL); + break; + } + + NtClose(eventHandle); + } + + PhpRefreshEventPageInfo(hwndDlg, pageContext); + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to open the event", status, 0); + } + break; + } + } + break; + } + + return FALSE; +} + +HPROPSHEETPAGE PhCreateEventPairPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJEVENTPAIR), + PhpEventPairPageProc + ); +} + +INT_PTR CALLBACK PhpEventPairPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + // Nothing + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_SETLOW: + case IDC_SETHIGH: + { + NTSTATUS status; + HANDLE eventPairHandle; + + if (NT_SUCCESS(status = pageContext->OpenObject( + &eventPairHandle, + EVENT_PAIR_ALL_ACCESS, + pageContext->Context + ))) + { + switch (LOWORD(wParam)) + { + case IDC_SETLOW: + NtSetLowEventPair(eventPairHandle); + break; + case IDC_SETHIGH: + NtSetHighEventPair(eventPairHandle); + break; + } + + NtClose(eventPairHandle); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to open the event pair", status, 0); + } + break; + } + } + break; + } + + return FALSE; +} + +HPROPSHEETPAGE PhCreateMutantPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJMUTANT), + PhpMutantPageProc + ); +} + +static VOID PhpRefreshMutantPageInfo( + _In_ HWND hwndDlg, + _In_ PCOMMON_PAGE_CONTEXT PageContext + ) +{ + HANDLE mutantHandle; + + if (NT_SUCCESS(PageContext->OpenObject( + &mutantHandle, + SEMAPHORE_QUERY_STATE, + PageContext->Context + ))) + { + MUTANT_BASIC_INFORMATION basicInfo; + MUTANT_OWNER_INFORMATION ownerInfo; + + if (NT_SUCCESS(PhGetMutantBasicInformation(mutantHandle, &basicInfo))) + { + SetDlgItemInt(hwndDlg, IDC_COUNT, basicInfo.CurrentCount, TRUE); + SetDlgItemText(hwndDlg, IDC_ABANDONED, basicInfo.AbandonedState ? L"True" : L"False"); + } + else + { + SetDlgItemText(hwndDlg, IDC_COUNT, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_ABANDONED, L"Unknown"); + } + + if ( + WindowsVersion >= WINDOWS_VISTA && + NT_SUCCESS(PhGetMutantOwnerInformation(mutantHandle, &ownerInfo)) + ) + { + PPH_STRING name; + + if (ownerInfo.ClientId.UniqueProcess != NULL) + { + name = PhGetClientIdName(&ownerInfo.ClientId); + SetDlgItemText(hwndDlg, IDC_OWNER, name->Buffer); + PhDereferenceObject(name); + } + else + { + SetDlgItemText(hwndDlg, IDC_OWNER, L"N/A"); + } + } + else + { + SetDlgItemText(hwndDlg, IDC_OWNER, L"Unknown"); + } + + NtClose(mutantHandle); + } +} + +INT_PTR CALLBACK PhpMutantPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + if (WindowsVersion < WINDOWS_VISTA) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_OWNERLABEL), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_OWNER), FALSE); + } + + PhpRefreshMutantPageInfo(hwndDlg, pageContext); + } + break; + } + + return FALSE; +} + +HPROPSHEETPAGE PhCreateSectionPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJSECTION), + PhpSectionPageProc + ); +} + +static VOID PhpRefreshSectionPageInfo( + _In_ HWND hwndDlg, + _In_ PCOMMON_PAGE_CONTEXT PageContext + ) +{ + HANDLE sectionHandle; + SECTION_BASIC_INFORMATION basicInfo; + PWSTR sectionType = L"Unknown"; + PPH_STRING sectionSize = NULL; + PPH_STRING fileName = NULL; + + if (!NT_SUCCESS(PageContext->OpenObject( + §ionHandle, + SECTION_QUERY | SECTION_MAP_READ, + PageContext->Context + ))) + { + if (!NT_SUCCESS(PageContext->OpenObject( + §ionHandle, + SECTION_QUERY | SECTION_MAP_READ, + PageContext->Context + ))) + { + return; + } + } + + if (NT_SUCCESS(PhGetSectionBasicInformation(sectionHandle, &basicInfo))) + { + if (basicInfo.AllocationAttributes & SEC_COMMIT) + sectionType = L"Commit"; + else if (basicInfo.AllocationAttributes & SEC_FILE) + sectionType = L"File"; + else if (basicInfo.AllocationAttributes & SEC_IMAGE) + sectionType = L"Image"; + else if (basicInfo.AllocationAttributes & SEC_RESERVE) + sectionType = L"Reserve"; + + sectionSize = PhaFormatSize(basicInfo.MaximumSize.QuadPart, -1); + } + + if (NT_SUCCESS(PhGetSectionFileName(sectionHandle, &fileName))) + { + PPH_STRING newFileName; + + PH_AUTO(fileName); + + if (newFileName = PhResolveDevicePrefix(fileName)) + fileName = PH_AUTO(newFileName); + } + + SetDlgItemText(hwndDlg, IDC_TYPE, sectionType); + SetDlgItemText(hwndDlg, IDC_SIZE_, PhGetStringOrDefault(sectionSize, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_FILE, PhGetStringOrDefault(fileName, L"N/A")); + + NtClose(sectionHandle); +} + +INT_PTR CALLBACK PhpSectionPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpRefreshSectionPageInfo(hwndDlg, pageContext); + } + break; + } + + return FALSE; +} + +HPROPSHEETPAGE PhCreateSemaphorePage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJSEMAPHORE), + PhpSemaphorePageProc + ); +} + +static VOID PhpRefreshSemaphorePageInfo( + _In_ HWND hwndDlg, + _In_ PCOMMON_PAGE_CONTEXT PageContext + ) +{ + HANDLE semaphoreHandle; + + if (NT_SUCCESS(PageContext->OpenObject( + &semaphoreHandle, + SEMAPHORE_QUERY_STATE, + PageContext->Context + ))) + { + SEMAPHORE_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhGetSemaphoreBasicInformation(semaphoreHandle, &basicInfo))) + { + SetDlgItemInt(hwndDlg, IDC_CURRENTCOUNT, basicInfo.CurrentCount, TRUE); + SetDlgItemInt(hwndDlg, IDC_MAXIMUMCOUNT, basicInfo.MaximumCount, TRUE); + } + else + { + SetDlgItemText(hwndDlg, IDC_CURRENTCOUNT, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_MAXIMUMCOUNT, L"Unknown"); + } + + NtClose(semaphoreHandle); + } +} + +INT_PTR CALLBACK PhpSemaphorePageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpRefreshSemaphorePageInfo(hwndDlg, pageContext); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_ACQUIRE: + case IDC_RELEASE: + { + NTSTATUS status; + HANDLE semaphoreHandle; + + if (NT_SUCCESS(status = pageContext->OpenObject( + &semaphoreHandle, + LOWORD(wParam) == IDC_ACQUIRE ? SYNCHRONIZE : SEMAPHORE_MODIFY_STATE, + pageContext->Context + ))) + { + switch (LOWORD(wParam)) + { + case IDC_ACQUIRE: + { + LARGE_INTEGER timeout; + + timeout.QuadPart = 0; + NtWaitForSingleObject(semaphoreHandle, FALSE, &timeout); + } + break; + case IDC_RELEASE: + NtReleaseSemaphore(semaphoreHandle, 1, NULL); + break; + } + + NtClose(semaphoreHandle); + } + + PhpRefreshSemaphorePageInfo(hwndDlg, pageContext); + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to open the semaphore", status, 0); + } + break; + } + } + break; + } + + return FALSE; +} + +HPROPSHEETPAGE PhCreateTimerPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context + ) +{ + return PhpCommonCreatePage( + OpenObject, + Context, + MAKEINTRESOURCE(IDD_OBJTIMER), + PhpTimerPageProc + ); +} + +static VOID PhpRefreshTimerPageInfo( + _In_ HWND hwndDlg, + _In_ PCOMMON_PAGE_CONTEXT PageContext + ) +{ + HANDLE timerHandle; + + if (NT_SUCCESS(PageContext->OpenObject( + &timerHandle, + TIMER_QUERY_STATE, + PageContext->Context + ))) + { + TIMER_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhGetTimerBasicInformation(timerHandle, &basicInfo))) + { + SetDlgItemText(hwndDlg, IDC_SIGNALED, basicInfo.TimerState ? L"True" : L"False"); + } + else + { + SetDlgItemText(hwndDlg, IDC_SIGNALED, L"Unknown"); + } + + NtClose(timerHandle); + } +} + +INT_PTR CALLBACK PhpTimerPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhpCommonPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!pageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpRefreshTimerPageInfo(hwndDlg, pageContext); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_CANCEL: + { + NTSTATUS status; + HANDLE timerHandle; + + if (NT_SUCCESS(status = pageContext->OpenObject( + &timerHandle, + TIMER_MODIFY_STATE, + pageContext->Context + ))) + { + switch (LOWORD(wParam)) + { + case IDC_CANCEL: + NtCancelTimer(timerHandle, NULL); + break; + } + + NtClose(timerHandle); + } + + PhpRefreshTimerPageInfo(hwndDlg, pageContext); + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to open the timer", status, 0); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/options.c b/ProcessHacker/options.c new file mode 100644 index 0000000..865253e --- /dev/null +++ b/ProcessHacker/options.c @@ -0,0 +1,1254 @@ +/* + * Process Hacker - + * options window + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#define WM_PH_CHILD_EXIT (WM_APP + 301) + +INT CALLBACK PhpOptionsPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ); + +LRESULT CALLBACK PhpOptionsWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpOptionsGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpOptionsAdvancedDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpOptionsSymbolsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpOptionsHighlightingDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpOptionsGraphsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// All +static BOOLEAN PageInit; +static BOOLEAN PressedOk; +static BOOLEAN RestartRequired; +static POINT StartLocation; +static WNDPROC OldWndProc; + +// General +static PH_STRINGREF CurrentUserRunKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"); +static BOOLEAN CurrentUserRunPresent; +static BOOLEAN CurrentUserRunStartHidden; +static HFONT CurrentFontInstance; +static PPH_STRING NewFontSelection; + +// Advanced +static PH_STRINGREF TaskMgrImageOptionsKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\taskmgr.exe"); +static PPH_STRING OldTaskMgrDebugger; +static BOOLEAN OldReplaceTaskMgr; +static HWND WindowHandleForElevate; + +// Highlighting +static HWND HighlightingListViewHandle; + +VOID PhShowOptionsDialog( + _In_ HWND ParentWindowHandle + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[5]; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_USECALLBACK | + PSH_USEPSTARTPAGE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = L"Options"; + propSheetHeader.nPages = 0; + propSheetHeader.pStartPage = !PhStartupParameters.ShowOptions ? L"General" : L"Advanced"; + propSheetHeader.phpage = pages; + propSheetHeader.pfnCallback = PhpOptionsPropSheetProc; + + if (!PhStartupParameters.ShowOptions) + { + // Disable all pages other than Advanced. + // General page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OPTGENERAL); + propSheetPage.pfnDlgProc = PhpOptionsGeneralDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Advanced page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OPTADVANCED); + propSheetPage.pfnDlgProc = PhpOptionsAdvancedDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + if (!PhStartupParameters.ShowOptions) + { + // Symbols page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OPTSYMBOLS); + propSheetPage.pfnDlgProc = PhpOptionsSymbolsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + if (!PhStartupParameters.ShowOptions) + { + // Highlighting page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OPTHIGHLIGHTING); + propSheetPage.pfnDlgProc = PhpOptionsHighlightingDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + if (!PhStartupParameters.ShowOptions) + { + // Graphs page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OPTGRAPHS); + propSheetPage.pfnDlgProc = PhpOptionsGraphsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + PageInit = FALSE; + PressedOk = FALSE; + RestartRequired = FALSE; + + if (PhStartupParameters.ShowOptions) + StartLocation = PhStartupParameters.Point; + else + StartLocation.x = MINLONG; + + OldTaskMgrDebugger = NULL; + + PhModalPropertySheet(&propSheetHeader); + + if (PressedOk) + { + if (!PhStartupParameters.ShowOptions) + { + PhUpdateCachedSettings(); + ProcessHacker_SaveAllSettings(PhMainWndHandle); + PhInvalidateAllProcessNodes(); + PhReloadSettingsProcessTreeList(); + PhSiNotifyChangeSettings(); + + if (RestartRequired) + { + if (PhShowMessage( + PhMainWndHandle, + MB_ICONQUESTION | MB_YESNO, + L"One or more options you have changed requires a restart of Process Hacker. " + L"Do you want to restart Process Hacker now?" + ) == IDYES) + { + ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); + PhShellProcessHacker( + PhMainWndHandle, + L"-v", + SW_SHOW, + 0, + PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY, + 0, + NULL + ); + ProcessHacker_Destroy(PhMainWndHandle); + } + } + } + else + { + // Main window not available. + if (PhSettingsFileName) + PhSaveSettings(PhSettingsFileName->Buffer); + } + } +} + +INT CALLBACK PhpOptionsPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case PSCB_BUTTONPRESSED: + { + if (lParam == PSBTN_OK) + { + PressedOk = TRUE; + } + } + break; + } + + return 0; +} + +static VOID PhpPageInit( + _In_ HWND hwndDlg + ) +{ + if (!PageInit) + { + HWND optionsWindow; + HWND resetButton; + RECT clientRect; + RECT rect; + + optionsWindow = GetParent(hwndDlg); + OldWndProc = (WNDPROC)GetWindowLongPtr(optionsWindow, GWLP_WNDPROC); + SetWindowLongPtr(optionsWindow, GWLP_WNDPROC, (LONG_PTR)PhpOptionsWndProc); + + // Create the Reset button. + GetClientRect(optionsWindow, &clientRect); + GetWindowRect(GetDlgItem(optionsWindow, IDCANCEL), &rect); + MapWindowPoints(NULL, optionsWindow, (POINT *)&rect, 2); + resetButton = CreateWindowEx( + WS_EX_NOPARENTNOTIFY, + L"BUTTON", + L"Reset", + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + clientRect.right - rect.right, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + optionsWindow, + (HMENU)IDC_RESET, + PhInstanceHandle, + NULL + ); + SendMessage(resetButton, WM_SETFONT, SendMessage(GetDlgItem(optionsWindow, IDCANCEL), WM_GETFONT, 0, 0), TRUE); + + if (PhStartupParameters.ShowOptions) + ShowWindow(resetButton, SW_HIDE); + + // Set the location of the options window. + if (StartLocation.x == MINLONG) + { + PhCenterWindow(optionsWindow, GetParent(optionsWindow)); + } + else + { + SetWindowPos(optionsWindow, NULL, StartLocation.x, StartLocation.y, 0, 0, + SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER); + } + + PageInit = TRUE; + } +} + +LRESULT CALLBACK PhpOptionsWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_RESET: + { + if (PhShowMessage( + hwnd, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2, + L"Do you want to reset all settings and restart Process Hacker?" + ) == IDYES) + { + ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); + + PhResetSettings(); + + if (PhSettingsFileName) + PhSaveSettings(PhSettingsFileName->Buffer); + + PhShellProcessHacker( + PhMainWndHandle, + L"-v", + SW_SHOW, + 0, + PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY, + 0, + NULL + ); + ProcessHacker_Destroy(PhMainWndHandle); + } + } + break; + } + } + break; + } + + return CallWindowProc(OldWndProc, hwnd, uMsg, wParam, lParam); +} + +#define SetDlgItemCheckForSetting(hwndDlg, Id, Name) \ + Button_SetCheck(GetDlgItem(hwndDlg, Id), PhGetIntegerSetting(Name) ? BST_CHECKED : BST_UNCHECKED) +#define SetSettingForDlgItemCheck(hwndDlg, Id, Name) \ + PhSetIntegerSetting(Name, Button_GetCheck(GetDlgItem(hwndDlg, Id)) == BST_CHECKED) +#define SetSettingForDlgItemCheckRestartRequired(hwndDlg, Id, Name) \ + do { \ + BOOLEAN __oldValue = !!PhGetIntegerSetting(Name); \ + BOOLEAN __newValue = Button_GetCheck(GetDlgItem(hwndDlg, Id)) == BST_CHECKED; \ + if (__newValue != __oldValue) \ + RestartRequired = TRUE; \ + PhSetIntegerSetting(Name, __newValue); \ + } while (0) +#define DialogChanged PropSheet_Changed(GetParent(hwndDlg), hwndDlg) + +static BOOLEAN GetCurrentFont( + _Out_ PLOGFONT Font + ) +{ + BOOLEAN result; + PPH_STRING fontHexString; + + if (NewFontSelection) + fontHexString = NewFontSelection; + else + fontHexString = PhaGetStringSetting(L"Font"); + + if (fontHexString->Length / 2 / 2 == sizeof(LOGFONT)) + result = PhHexStringToBuffer(&fontHexString->sr, (PUCHAR)Font); + else + result = FALSE; + + return result; +} + +static VOID ReadCurrentUserRun( + VOID + ) +{ + HANDLE keyHandle; + PPH_STRING value; + + CurrentUserRunPresent = FALSE; + CurrentUserRunStartHidden = FALSE; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_CURRENT_USER, + &CurrentUserRunKeyName, + 0 + ))) + { + if (value = PhQueryRegistryString(keyHandle, L"Process Hacker 2")) + { + PH_STRINGREF fileName; + PH_STRINGREF arguments; + PPH_STRING fullFileName; + + PH_AUTO(value); + + if (PhParseCommandLineFuzzy(&value->sr, &fileName, &arguments, &fullFileName)) + { + PH_AUTO(fullFileName); + + if (fullFileName && PhEqualString(fullFileName, PhApplicationFileName, TRUE)) + { + CurrentUserRunPresent = TRUE; + CurrentUserRunStartHidden = PhEqualStringRef2(&arguments, L"-hide", FALSE); + } + } + } + + NtClose(keyHandle); + } +} + +static VOID WriteCurrentUserRun( + _In_ BOOLEAN Present, + _In_ BOOLEAN StartHidden + ) +{ + HANDLE keyHandle; + + if (CurrentUserRunPresent == Present && (!Present || CurrentUserRunStartHidden == StartHidden)) + return; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_WRITE, + PH_KEY_CURRENT_USER, + &CurrentUserRunKeyName, + 0 + ))) + { + UNICODE_STRING valueName; + + RtlInitUnicodeString(&valueName, L"Process Hacker 2"); + + if (Present) + { + PPH_STRING value; + + value = PH_AUTO(PhConcatStrings(3, L"\"", PhApplicationFileName->Buffer, L"\"")); + + if (StartHidden) + value = PhaConcatStrings2(value->Buffer, L" -hide"); + + NtSetValueKey(keyHandle, &valueName, 0, REG_SZ, value->Buffer, (ULONG)value->Length + 2); + } + else + { + NtDeleteValueKey(keyHandle, &valueName); + } + + NtClose(keyHandle); + } +} + + +INT_PTR CALLBACK PhpOptionsGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND comboBoxHandle; + ULONG i; + LOGFONT font; + + PhpPageInit(hwndDlg); + + comboBoxHandle = GetDlgItem(hwndDlg, IDC_MAXSIZEUNIT); + + for (i = 0; i < sizeof(PhSizeUnitNames) / sizeof(PWSTR); i++) + ComboBox_AddString(comboBoxHandle, PhSizeUnitNames[i]); + + SetDlgItemText(hwndDlg, IDC_SEARCHENGINE, PhaGetStringSetting(L"SearchEngine")->Buffer); + SetDlgItemText(hwndDlg, IDC_PEVIEWER, PhaGetStringSetting(L"ProgramInspectExecutables")->Buffer); + + if (PhMaxSizeUnit != -1) + ComboBox_SetCurSel(comboBoxHandle, PhMaxSizeUnit); + else + ComboBox_SetCurSel(comboBoxHandle, sizeof(PhSizeUnitNames) / sizeof(PWSTR) - 1); + + SetDlgItemInt(hwndDlg, IDC_ICONPROCESSES, PhGetIntegerSetting(L"IconProcesses"), FALSE); + + SetDlgItemCheckForSetting(hwndDlg, IDC_ALLOWONLYONEINSTANCE, L"AllowOnlyOneInstance"); + SetDlgItemCheckForSetting(hwndDlg, IDC_HIDEONCLOSE, L"HideOnClose"); + SetDlgItemCheckForSetting(hwndDlg, IDC_HIDEONMINIMIZE, L"HideOnMinimize"); + SetDlgItemCheckForSetting(hwndDlg, IDC_COLLAPSESERVICES, L"CollapseServicesOnStart"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ICONSINGLECLICK, L"IconSingleClick"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ICONTOGGLESVISIBILITY, L"IconTogglesVisibility"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLEPLUGINS, L"EnablePlugins"); + + ReadCurrentUserRun(); + + if (CurrentUserRunPresent) + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_STARTATLOGON), BST_CHECKED); + + if (CurrentUserRunStartHidden) + Button_SetCheck(GetDlgItem(hwndDlg, IDC_STARTHIDDEN), BST_CHECKED); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_STARTHIDDEN), FALSE); + } + + // Set the font of the button for a nice preview. + if (GetCurrentFont(&font)) + { + CurrentFontInstance = CreateFontIndirect(&font); + + if (CurrentFontInstance) + SendMessage(GetDlgItem(hwndDlg, IDC_FONT), WM_SETFONT, (WPARAM)CurrentFontInstance, TRUE); + } + } + break; + case WM_DESTROY: + { + if (CurrentFontInstance) + DeleteObject(CurrentFontInstance); + + PhClearReference(&NewFontSelection); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_STARTATLOGON: + { + EnableWindow(GetDlgItem(hwndDlg, IDC_STARTHIDDEN), Button_GetCheck(GetDlgItem(hwndDlg, IDC_STARTATLOGON)) == BST_CHECKED); + } + break; + case IDC_FONT: + { + LOGFONT font; + CHOOSEFONT chooseFont; + + if (!GetCurrentFont(&font)) + { + // Can't get LOGFONT from the existing setting, probably + // because the user hasn't ever chosen a font before. + // Set the font to something familiar. + GetObject((HFONT)SendMessage(PhMainWndHandle, WM_PH_GET_FONT, 0, 0), sizeof(LOGFONT), &font); + } + + memset(&chooseFont, 0, sizeof(CHOOSEFONT)); + chooseFont.lStructSize = sizeof(CHOOSEFONT); + chooseFont.hwndOwner = hwndDlg; + chooseFont.lpLogFont = &font; + chooseFont.Flags = CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; + + if (ChooseFont(&chooseFont)) + { + PhMoveReference(&NewFontSelection, PhBufferToHexString((PUCHAR)&font, sizeof(LOGFONT))); + + // Update the button's font. + + if (CurrentFontInstance) + DeleteObject(CurrentFontInstance); + + CurrentFontInstance = CreateFontIndirect(&font); + SendMessage(GetDlgItem(hwndDlg, IDC_FONT), WM_SETFONT, (WPARAM)CurrentFontInstance, TRUE); + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + BOOLEAN startAtLogon; + BOOLEAN startHidden; + + PhSetStringSetting2(L"SearchEngine", &(PhaGetDlgItemText(hwndDlg, IDC_SEARCHENGINE)->sr)); + PhSetStringSetting2(L"ProgramInspectExecutables", &(PhaGetDlgItemText(hwndDlg, IDC_PEVIEWER)->sr)); + PhSetIntegerSetting(L"MaxSizeUnit", PhMaxSizeUnit = ComboBox_GetCurSel(GetDlgItem(hwndDlg, IDC_MAXSIZEUNIT))); + PhSetIntegerSetting(L"IconProcesses", GetDlgItemInt(hwndDlg, IDC_ICONPROCESSES, NULL, FALSE)); + SetSettingForDlgItemCheck(hwndDlg, IDC_ALLOWONLYONEINSTANCE, L"AllowOnlyOneInstance"); + SetSettingForDlgItemCheck(hwndDlg, IDC_HIDEONCLOSE, L"HideOnClose"); + SetSettingForDlgItemCheck(hwndDlg, IDC_HIDEONMINIMIZE, L"HideOnMinimize"); + SetSettingForDlgItemCheck(hwndDlg, IDC_COLLAPSESERVICES, L"CollapseServicesOnStart"); + SetSettingForDlgItemCheck(hwndDlg, IDC_ICONSINGLECLICK, L"IconSingleClick"); + SetSettingForDlgItemCheck(hwndDlg, IDC_ICONTOGGLESVISIBILITY, L"IconTogglesVisibility"); + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_ENABLEPLUGINS, L"EnablePlugins"); + + startAtLogon = Button_GetCheck(GetDlgItem(hwndDlg, IDC_STARTATLOGON)) == BST_CHECKED; + startHidden = Button_GetCheck(GetDlgItem(hwndDlg, IDC_STARTHIDDEN)) == BST_CHECKED; + WriteCurrentUserRun(startAtLogon, startHidden); + + if (NewFontSelection) + { + PhSetStringSetting2(L"Font", &NewFontSelection->sr); + PostMessage(PhMainWndHandle, WM_PH_UPDATE_FONT, 0, 0); + } + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +static BOOLEAN PathMatchesPh( + _In_ PPH_STRING Path + ) +{ + BOOLEAN match = FALSE; + + if (PhEqualString(OldTaskMgrDebugger, PhApplicationFileName, TRUE)) + { + match = TRUE; + } + // Allow for a quoted value. + else if ( + OldTaskMgrDebugger->Length == PhApplicationFileName->Length + sizeof(WCHAR) * 2 && + OldTaskMgrDebugger->Buffer[0] == '"' && + OldTaskMgrDebugger->Buffer[OldTaskMgrDebugger->Length / sizeof(WCHAR) - 1] == '"' + ) + { + PH_STRINGREF partInside; + + partInside.Buffer = &OldTaskMgrDebugger->Buffer[1]; + partInside.Length = OldTaskMgrDebugger->Length - sizeof(WCHAR) * 2; + + if (PhEqualStringRef(&partInside, &PhApplicationFileName->sr, TRUE)) + match = TRUE; + } + + return match; +} + +VOID PhpAdvancedPageLoad( + _In_ HWND hwndDlg + ) +{ + HWND changeButton; + + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLEWARNINGS, L"EnableWarnings"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLEKERNELMODEDRIVER, L"EnableKph"); + SetDlgItemCheckForSetting(hwndDlg, IDC_HIDEUNNAMEDHANDLES, L"HideUnnamedHandles"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLESTAGE2, L"EnableStage2"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLENETWORKRESOLVE, L"EnableNetworkResolve"); + SetDlgItemCheckForSetting(hwndDlg, IDC_PROPAGATECPUUSAGE, L"PropagateCpuUsage"); + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLEINSTANTTOOLTIPS, L"EnableInstantTooltips"); + + if (WindowsVersion >= WINDOWS_7) + SetDlgItemCheckForSetting(hwndDlg, IDC_ENABLECYCLECPUUSAGE, L"EnableCycleCpuUsage"); + + SetDlgItemInt(hwndDlg, IDC_SAMPLECOUNT, PhGetIntegerSetting(L"SampleCount"), FALSE); + SetDlgItemCheckForSetting(hwndDlg, IDC_SAMPLECOUNTAUTOMATIC, L"SampleCountAutomatic"); + + if (PhGetIntegerSetting(L"SampleCountAutomatic")) + EnableWindow(GetDlgItem(hwndDlg, IDC_SAMPLECOUNT), FALSE); + + // Replace Task Manager + + changeButton = GetDlgItem(hwndDlg, IDC_CHANGE); + + if (PhGetOwnTokenAttributes().Elevated) + { + ShowWindow(changeButton, SW_HIDE); + } + else + { + SendMessage(changeButton, BCM_SETSHIELD, 0, TRUE); + } + + { + HANDLE taskmgrKeyHandle = NULL; + ULONG disposition; + BOOLEAN success = FALSE; + BOOLEAN alreadyReplaced = FALSE; + + // See if we can write to the key. + if (NT_SUCCESS(PhCreateKey( + &taskmgrKeyHandle, + KEY_READ | KEY_WRITE, + PH_KEY_LOCAL_MACHINE, + &TaskMgrImageOptionsKeyName, + 0, + 0, + &disposition + ))) + { + success = TRUE; + } + + if (taskmgrKeyHandle || NT_SUCCESS(PhOpenKey( + &taskmgrKeyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &TaskMgrImageOptionsKeyName, + 0 + ))) + { + PhClearReference(&OldTaskMgrDebugger); + + if (OldTaskMgrDebugger = PhQueryRegistryString(taskmgrKeyHandle, L"Debugger")) + { + alreadyReplaced = PathMatchesPh(OldTaskMgrDebugger); + } + + NtClose(taskmgrKeyHandle); + } + + if (!success) + EnableWindow(GetDlgItem(hwndDlg, IDC_REPLACETASKMANAGER), FALSE); + + OldReplaceTaskMgr = alreadyReplaced; + Button_SetCheck(GetDlgItem(hwndDlg, IDC_REPLACETASKMANAGER), alreadyReplaced ? BST_CHECKED : BST_UNCHECKED); + } +} + +VOID PhpAdvancedPageSave( + _In_ HWND hwndDlg + ) +{ + ULONG sampleCount; + + SetSettingForDlgItemCheck(hwndDlg, IDC_ENABLEWARNINGS, L"EnableWarnings"); + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_ENABLEKERNELMODEDRIVER, L"EnableKph"); + SetSettingForDlgItemCheck(hwndDlg, IDC_HIDEUNNAMEDHANDLES, L"HideUnnamedHandles"); + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_ENABLESTAGE2, L"EnableStage2"); + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_ENABLENETWORKRESOLVE, L"EnableNetworkResolve"); + SetSettingForDlgItemCheck(hwndDlg, IDC_PROPAGATECPUUSAGE, L"PropagateCpuUsage"); + SetSettingForDlgItemCheck(hwndDlg, IDC_ENABLEINSTANTTOOLTIPS, L"EnableInstantTooltips"); + + if (WindowsVersion >= WINDOWS_7) + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_ENABLECYCLECPUUSAGE, L"EnableCycleCpuUsage"); + + sampleCount = GetDlgItemInt(hwndDlg, IDC_SAMPLECOUNT, NULL, FALSE); + SetSettingForDlgItemCheckRestartRequired(hwndDlg, IDC_SAMPLECOUNTAUTOMATIC, L"SampleCountAutomatic"); + + if (sampleCount == 0) + sampleCount = 1; + + if (sampleCount != PhGetIntegerSetting(L"SampleCount")) + RestartRequired = TRUE; + + PhSetIntegerSetting(L"SampleCount", sampleCount); + + // Replace Task Manager + if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_REPLACETASKMANAGER))) + { + NTSTATUS status; + HANDLE taskmgrKeyHandle; + BOOLEAN replaceTaskMgr; + UNICODE_STRING valueName; + + replaceTaskMgr = Button_GetCheck(GetDlgItem(hwndDlg, IDC_REPLACETASKMANAGER)) == BST_CHECKED; + + if (OldReplaceTaskMgr != replaceTaskMgr) + { + // We should have created the key back in PhpAdvancedPageLoad, which is why + // we're opening the key here. + if (NT_SUCCESS(PhOpenKey( + &taskmgrKeyHandle, + KEY_WRITE, + PH_KEY_LOCAL_MACHINE, + &TaskMgrImageOptionsKeyName, + 0 + ))) + { + RtlInitUnicodeString(&valueName, L"Debugger"); + + if (replaceTaskMgr) + { + PPH_STRING quotedFileName; + + quotedFileName = PH_AUTO(PhConcatStrings(3, L"\"", PhApplicationFileName->Buffer, L"\"")); + status = NtSetValueKey(taskmgrKeyHandle, &valueName, 0, REG_SZ, quotedFileName->Buffer, (ULONG)quotedFileName->Length + 2); + } + else + { + status = NtDeleteValueKey(taskmgrKeyHandle, &valueName); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to replace Task Manager", status, 0); + + NtClose(taskmgrKeyHandle); + } + } + } +} + +NTSTATUS PhpElevateAdvancedThreadStart( + _In_ PVOID Parameter + ) +{ + PPH_STRING arguments; + + arguments = Parameter; + PhShellProcessHacker( + WindowHandleForElevate, + arguments->Buffer, + SW_SHOW, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS, + INFINITE, + NULL + ); + PhDereferenceObject(arguments); + + PostMessage(WindowHandleForElevate, WM_PH_CHILD_EXIT, 0, 0); + + return STATUS_SUCCESS; +} + +INT_PTR CALLBACK PhpOptionsAdvancedDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpPageInit(hwndDlg); + PhpAdvancedPageLoad(hwndDlg); + + if (PhStartupParameters.ShowOptions) + { + // Disable all controls except for Replace Task Manager. + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEWARNINGS), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEKERNELMODEDRIVER), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_HIDEUNNAMEDHANDLES), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLESTAGE2), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLENETWORKRESOLVE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_PROPAGATECPUUSAGE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEINSTANTTOOLTIPS), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLECYCLECPUUSAGE), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_SAMPLECOUNTLABEL), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_SAMPLECOUNT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_SAMPLECOUNTAUTOMATIC), FALSE); + } + else + { + if (WindowsVersion < WINDOWS_7) + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLECYCLECPUUSAGE), FALSE); // cycle-based CPU usage not available before Windows 7 + } + } + break; + case WM_DESTROY: + { + PhClearReference(&OldTaskMgrDebugger); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_CHANGE: + { + HANDLE threadHandle; + RECT windowRect; + + // Save the options so they don't get "overwritten" when + // WM_PH_CHILD_EXIT gets sent. + PhpAdvancedPageSave(hwndDlg); + + GetWindowRect(GetParent(hwndDlg), &windowRect); + WindowHandleForElevate = hwndDlg; + threadHandle = PhCreateThread(0, PhpElevateAdvancedThreadStart, PhFormatString( + L"-showoptions -hwnd %Ix -point %u,%u", + (ULONG_PTR)GetParent(hwndDlg), + windowRect.left + 20, + windowRect.top + 20 + )); + + if (threadHandle) + NtClose(threadHandle); + } + break; + case IDC_SAMPLECOUNTAUTOMATIC: + { + EnableWindow(GetDlgItem(hwndDlg, IDC_SAMPLECOUNT), Button_GetCheck(GetDlgItem(hwndDlg, IDC_SAMPLECOUNTAUTOMATIC)) != BST_CHECKED); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + PhpAdvancedPageSave(hwndDlg); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + case WM_PH_CHILD_EXIT: + { + PhpAdvancedPageLoad(hwndDlg); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhpOptionsSymbolsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpPageInit(hwndDlg); + + SetDlgItemText(hwndDlg, IDC_DBGHELPPATH, PhaGetStringSetting(L"DbgHelpPath")->Buffer); + SetDlgItemText(hwndDlg, IDC_DBGHELPSEARCHPATH, PhaGetStringSetting(L"DbgHelpSearchPath")->Buffer); + + SetDlgItemCheckForSetting(hwndDlg, IDC_UNDECORATESYMBOLS, L"DbgHelpUndecorate"); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"dbghelp.dll", L"dbghelp.dll" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PH_AUTO(PhGetFileName(PhaGetDlgItemText(hwndDlg, IDC_DBGHELPPATH))); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + SetDlgItemText(hwndDlg, IDC_DBGHELPPATH, fileName->Buffer); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + PPH_STRING dbgHelpPath = PhaGetDlgItemText(hwndDlg, IDC_DBGHELPPATH); + + if (!PhEqualString(dbgHelpPath, PhaGetStringSetting(L"DbgHelpPath"), TRUE)) + RestartRequired = TRUE; + + PhSetStringSetting2(L"DbgHelpPath", &dbgHelpPath->sr); + PhSetStringSetting2(L"DbgHelpSearchPath", &(PhaGetDlgItemText(hwndDlg, IDC_DBGHELPSEARCHPATH)->sr)); + SetSettingForDlgItemCheck(hwndDlg, IDC_UNDECORATESYMBOLS, L"DbgHelpUndecorate"); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +#define CROSS_INDEX 0 +#define TICK_INDEX 1 + +typedef struct _COLOR_ITEM +{ + PWSTR SettingName; + PWSTR UseSettingName; + PWSTR Name; + PWSTR Description; + + BOOLEAN CurrentUse; + COLORREF CurrentColor; +} COLOR_ITEM, *PCOLOR_ITEM; + +#define COLOR_ITEM(SettingName, Name, Description) { SettingName, L"Use" SettingName, Name, Description } + +static COLOR_ITEM ColorItems[] = +{ + COLOR_ITEM(L"ColorOwnProcesses", L"Own processes", L"Processes running under the same user account as Process Hacker."), + COLOR_ITEM(L"ColorSystemProcesses", L"System processes", L"Processes running under the NT AUTHORITY\\SYSTEM user account."), + COLOR_ITEM(L"ColorServiceProcesses", L"Service processes", L"Processes which host one or more services."), + COLOR_ITEM(L"ColorJobProcesses", L"Job processes", L"Processes associated with a job."), +#ifdef _WIN64 + COLOR_ITEM(L"ColorWow64Processes", L"32-bit processes", L"Processes running under WOW64, i.e. 32-bit."), +#endif + COLOR_ITEM(L"ColorDebuggedProcesses", L"Debugged processes", L"Processes that are currently being debugged."), + COLOR_ITEM(L"ColorElevatedProcesses", L"Elevated processes", L"Processes with full privileges on a system with UAC enabled."), + COLOR_ITEM(L"ColorImmersiveProcesses", L"Immersive processes and DLLs", L"Processes and DLLs that belong to a Modern UI app."), + COLOR_ITEM(L"ColorSuspended", L"Suspended processes and threads", L"Processes and threads that are suspended from execution."), + COLOR_ITEM(L"ColorDotNet", L".NET processes and DLLs", L".NET (i.e. managed) processes and DLLs."), + COLOR_ITEM(L"ColorPacked", L"Packed processes", L"Executables are sometimes \"packed\" to reduce their size."), + COLOR_ITEM(L"ColorGuiThreads", L"GUI threads", L"Threads that have made at least one GUI-related system call."), + COLOR_ITEM(L"ColorRelocatedModules", L"Relocated DLLs", L"DLLs that were not loaded at their preferred image bases."), + COLOR_ITEM(L"ColorProtectedHandles", L"Protected handles", L"Handles that are protected from being closed."), + COLOR_ITEM(L"ColorInheritHandles", L"Inheritable handles", L"Handles that can be inherited by child processes.") +}; + +COLORREF NTAPI PhpColorItemColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PCOLOR_ITEM item = Param; + + return item->CurrentColor; +} + +INT_PTR CALLBACK PhpOptionsHighlightingDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG i; + + PhpPageInit(hwndDlg); + + // Highlighting Duration + SetDlgItemInt(hwndDlg, IDC_HIGHLIGHTINGDURATION, PhCsHighlightingDuration, FALSE); + + // New Objects + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_NEWOBJECTS), PhCsColorNew); + + // Removed Objects + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_REMOVEDOBJECTS), PhCsColorRemoved); + + // Highlighting + HighlightingListViewHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(HighlightingListViewHandle, FALSE, TRUE); + ListView_SetExtendedListViewStyleEx(HighlightingListViewHandle, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); + PhAddListViewColumn(HighlightingListViewHandle, 0, 0, 0, LVCFMT_LEFT, 240, L"Name"); + PhSetExtendedListView(HighlightingListViewHandle); + ExtendedListView_SetItemColorFunction(HighlightingListViewHandle, PhpColorItemColorFunction); + + for (i = 0; i < sizeof(ColorItems) / sizeof(COLOR_ITEM); i++) + { + INT lvItemIndex; + + lvItemIndex = PhAddListViewItem(HighlightingListViewHandle, MAXINT, ColorItems[i].Name, &ColorItems[i]); + ColorItems[i].CurrentColor = PhGetIntegerSetting(ColorItems[i].SettingName); + ColorItems[i].CurrentUse = !!PhGetIntegerSetting(ColorItems[i].UseSettingName); + ListView_SetCheckState(HighlightingListViewHandle, lvItemIndex, ColorItems[i].CurrentUse); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_ENABLEALL: + { + ULONG i; + + for (i = 0; i < sizeof(ColorItems) / sizeof(COLOR_ITEM); i++) + ListView_SetCheckState(HighlightingListViewHandle, i, TRUE); + } + break; + case IDC_DISABLEALL: + { + ULONG i; + + for (i = 0; i < sizeof(ColorItems) / sizeof(COLOR_ITEM); i++) + ListView_SetCheckState(HighlightingListViewHandle, i, FALSE); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + ULONG i; + + PH_SET_INTEGER_CACHED_SETTING(HighlightingDuration, GetDlgItemInt(hwndDlg, IDC_HIGHLIGHTINGDURATION, NULL, FALSE)); + PH_SET_INTEGER_CACHED_SETTING(ColorNew, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_NEWOBJECTS))); + PH_SET_INTEGER_CACHED_SETTING(ColorRemoved, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_REMOVEDOBJECTS))); + + for (i = 0; i < sizeof(ColorItems) / sizeof(COLOR_ITEM); i++) + { + ColorItems[i].CurrentUse = !!ListView_GetCheckState(HighlightingListViewHandle, i); + PhSetIntegerSetting(ColorItems[i].SettingName, ColorItems[i].CurrentColor); + PhSetIntegerSetting(ColorItems[i].UseSettingName, ColorItems[i].CurrentUse); + } + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + case NM_DBLCLK: + { + if (header->hwndFrom == HighlightingListViewHandle) + { + PCOLOR_ITEM item; + + if (item = PhGetSelectedListViewItemParam(HighlightingListViewHandle)) + { + CHOOSECOLOR chooseColor = { sizeof(chooseColor) }; + COLORREF customColors[16] = { 0 }; + + chooseColor.hwndOwner = hwndDlg; + chooseColor.rgbResult = item->CurrentColor; + chooseColor.lpCustColors = customColors; + chooseColor.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT; + + if (ChooseColor(&chooseColor)) + { + item->CurrentColor = chooseColor.rgbResult; + InvalidateRect(HighlightingListViewHandle, NULL, TRUE); + } + } + } + } + break; + case LVN_GETINFOTIP: + { + if (header->hwndFrom == HighlightingListViewHandle) + { + NMLVGETINFOTIP *getInfoTip = (NMLVGETINFOTIP *)lParam; + PH_STRINGREF tip; + + PhInitializeStringRefLongHint(&tip, ColorItems[getInfoTip->iItem].Description); + PhCopyListViewInfoTip(getInfoTip, &tip); + } + } + break; + } + } + break; + } + + REFLECT_MESSAGE_DLG(hwndDlg, HighlightingListViewHandle, uMsg, wParam, lParam); + + return FALSE; +} + +INT_PTR CALLBACK PhpOptionsGraphsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpPageInit(hwndDlg); + + // Show Text + SetDlgItemCheckForSetting(hwndDlg, IDC_SHOWTEXT, L"GraphShowText"); + SetDlgItemCheckForSetting(hwndDlg, IDC_USEOLDCOLORS, L"GraphColorMode"); + SetDlgItemCheckForSetting(hwndDlg, IDC_SHOWCOMMITINSUMMARY, L"ShowCommitInSummary"); + + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_CPUUSER), PhCsColorCpuUser); + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_CPUKERNEL), PhCsColorCpuKernel); + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_IORO), PhCsColorIoReadOther); + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_IOW), PhCsColorIoWrite); + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_PRIVATE), PhCsColorPrivate); + ColorBox_SetColor(GetDlgItem(hwndDlg, IDC_PHYSICAL), PhCsColorPhysical); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + SetSettingForDlgItemCheck(hwndDlg, IDC_SHOWTEXT, L"GraphShowText"); + SetSettingForDlgItemCheck(hwndDlg, IDC_USEOLDCOLORS, L"GraphColorMode"); + SetSettingForDlgItemCheck(hwndDlg, IDC_SHOWCOMMITINSUMMARY, L"ShowCommitInSummary"); + PH_SET_INTEGER_CACHED_SETTING(ColorCpuUser, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_CPUUSER))); + PH_SET_INTEGER_CACHED_SETTING(ColorCpuKernel, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_CPUKERNEL))); + PH_SET_INTEGER_CACHED_SETTING(ColorIoReadOther, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_IORO))); + PH_SET_INTEGER_CACHED_SETTING(ColorIoWrite, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_IOW))); + PH_SET_INTEGER_CACHED_SETTING(ColorPrivate, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_PRIVATE))); + PH_SET_INTEGER_CACHED_SETTING(ColorPhysical, ColorBox_GetColor(GetDlgItem(hwndDlg, IDC_PHYSICAL))); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/pagfiles.c b/ProcessHacker/pagfiles.c new file mode 100644 index 0000000..43fa8df --- /dev/null +++ b/ProcessHacker/pagfiles.c @@ -0,0 +1,159 @@ +/* + * Process Hacker - + * pagefiles viewer + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +INT_PTR CALLBACK PhpPagefilesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowPagefilesDialog( + _In_ HWND ParentWindowHandle + ) +{ + DialogBox( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PAGEFILES), + ParentWindowHandle, + PhpPagefilesDlgProc + ); +} + +static VOID PhpAddPagefileItems( + _In_ HWND ListViewHandle, + _In_ PVOID Pagefiles + ) +{ + PSYSTEM_PAGEFILE_INFORMATION pagefile; + + pagefile = PH_FIRST_PAGEFILE(Pagefiles); + + while (pagefile) + { + INT lvItemIndex; + PPH_STRING fileName; + PPH_STRING newFileName; + PPH_STRING usage; + + fileName = PhCreateStringFromUnicodeString(&pagefile->PageFileName); + newFileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + + lvItemIndex = PhAddListViewItem(ListViewHandle, MAXINT, + newFileName->Buffer, NULL); + + PhDereferenceObject(newFileName); + + // Usage + usage = PhFormatSize(UInt32x32To64(pagefile->TotalInUse, PAGE_SIZE), -1); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 1, usage->Buffer); + PhDereferenceObject(usage); + + // Peak usage + usage = PhFormatSize(UInt32x32To64(pagefile->PeakUsage, PAGE_SIZE), -1); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 2, usage->Buffer); + PhDereferenceObject(usage); + + // Total + usage = PhFormatSize(UInt32x32To64(pagefile->TotalSize, PAGE_SIZE), -1); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 3, usage->Buffer); + PhDereferenceObject(usage); + + pagefile = PH_NEXT_PAGEFILE(pagefile); + } +} + +INT_PTR CALLBACK PhpPagefilesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + HWND lvHandle; + PVOID pagefiles; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 120, L"File name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Usage"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 100, L"Peak usage"); + PhAddListViewColumn(lvHandle, 3, 3, 3, LVCFMT_LEFT, 100, L"Total"); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + + if (NT_SUCCESS(status = PhEnumPagefiles(&pagefiles))) + { + PhpAddPagefileItems(lvHandle, pagefiles); + PhFree(pagefiles); + } + else + { + PhShowStatus(hwndDlg, L"Unable to get pagefile information", status, 0); + EndDialog(hwndDlg, IDCANCEL); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDOK), TRUE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_REFRESH: + { + NTSTATUS status; + PVOID pagefiles; + + if (NT_SUCCESS(status = PhEnumPagefiles(&pagefiles))) + { + ListView_DeleteAllItems(GetDlgItem(hwndDlg, IDC_LIST)); + PhpAddPagefileItems(GetDlgItem(hwndDlg, IDC_LIST), pagefiles); + PhFree(pagefiles); + } + else + { + PhShowStatus(hwndDlg, L"Unable to get pagefile information", status, 0); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/pcre/LICENCE b/ProcessHacker/pcre/LICENCE new file mode 100644 index 0000000..6600a65 --- /dev/null +++ b/ProcessHacker/pcre/LICENCE @@ -0,0 +1,83 @@ +PCRE2 LICENCE +------------- + +PCRE2 is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + +Release 10 of PCRE2 is distributed under the terms of the "BSD" licence, as +specified below. The documentation for PCRE2, supplied in the "doc" +directory, is distributed under the same terms as the software itself. The data +in the testdata directory is not copyrighted and is in the public domain. + +The basic library functions are written in C and are freestanding. Also +included in the distribution is a just-in-time compiler that can be used to +optimize pattern matching. This is an optional feature that can be omitted when +the library is built. + + +THE BASIC LIBRARY FUNCTIONS +--------------------------- + +Written by: Philip Hazel +Email local part: ph10 +Email domain: cam.ac.uk + +University of Cambridge Computing Service, +Cambridge, England. + +Copyright (c) 1997-2016 University of Cambridge +All rights reserved. + + +PCRE2 JUST-IN-TIME COMPILATION SUPPORT +-------------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2010-2016 Zoltan Herczeg +All rights reserved. + + +STACK-LESS JUST-IN-TIME COMPILER +-------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2009-2016 Zoltan Herczeg +All rights reserved. + + +THE "BSD" LICENCE +----------------- + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of any + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +End diff --git a/ProcessHacker/pcre/README b/ProcessHacker/pcre/README new file mode 100644 index 0000000..48d2ffd --- /dev/null +++ b/ProcessHacker/pcre/README @@ -0,0 +1,843 @@ +README file for PCRE2 (Perl-compatible regular expression library) +------------------------------------------------------------------ + +PCRE2 is a re-working of the original PCRE library to provide an entirely new +API. The latest release of PCRE2 is always available in three alternative +formats from: + + ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre2-xxx.tar.gz + ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre2-xxx.tar.bz2 + ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre2-xxx.zip + +There is a mailing list for discussion about the development of PCRE (both the +original and new APIs) at pcre-dev@exim.org. You can access the archives and +subscribe or manage your subscription here: + + https://lists.exim.org/mailman/listinfo/pcre-dev + +Please read the NEWS file if you are upgrading from a previous release. +The contents of this README file are: + + The PCRE2 APIs + Documentation for PCRE2 + Contributions by users of PCRE2 + Building PCRE2 on non-Unix-like systems + Building PCRE2 without using autotools + Building PCRE2 using autotools + Retrieving configuration information + Shared libraries + Cross-compiling using autotools + Making new tarballs + Testing PCRE2 + Character tables + File manifest + + +The PCRE2 APIs +-------------- + +PCRE2 is written in C, and it has its own API. There are three sets of +functions, one for the 8-bit library, which processes strings of bytes, one for +the 16-bit library, which processes strings of 16-bit values, and one for the +32-bit library, which processes strings of 32-bit values. There are no C++ +wrappers. + +The distribution does contain a set of C wrapper functions for the 8-bit +library that are based on the POSIX regular expression API (see the pcre2posix +man page). These can be found in a library called libpcre2posix. Note that this +just provides a POSIX calling interface to PCRE2; the regular expressions +themselves still follow Perl syntax and semantics. The POSIX API is restricted, +and does not give full access to all of PCRE2's facilities. + +The header file for the POSIX-style functions is called pcre2posix.h. The +official POSIX name is regex.h, but I did not want to risk possible problems +with existing files of that name by distributing it that way. To use PCRE2 with +an existing program that uses the POSIX API, pcre2posix.h will have to be +renamed or pointed at by a link. + +If you are using the POSIX interface to PCRE2 and there is already a POSIX +regex library installed on your system, as well as worrying about the regex.h +header file (as mentioned above), you must also take care when linking programs +to ensure that they link with PCRE2's libpcre2posix library. Otherwise they may +pick up the POSIX functions of the same name from the other library. + +One way of avoiding this confusion is to compile PCRE2 with the addition of +-Dregcomp=PCRE2regcomp (and similarly for the other POSIX functions) to the +compiler flags (CFLAGS if you are using "configure" -- see below). This has the +effect of renaming the functions so that the names no longer clash. Of course, +you have to do the same thing for your applications, or write them using the +new names. + + +Documentation for PCRE2 +----------------------- + +If you install PCRE2 in the normal way on a Unix-like system, you will end up +with a set of man pages whose names all start with "pcre2". The one that is +just called "pcre2" lists all the others. In addition to these man pages, the +PCRE2 documentation is supplied in two other forms: + + 1. There are files called doc/pcre2.txt, doc/pcre2grep.txt, and + doc/pcre2test.txt in the source distribution. The first of these is a + concatenation of the text forms of all the section 3 man pages except the + listing of pcre2demo.c and those that summarize individual functions. The + other two are the text forms of the section 1 man pages for the pcre2grep + and pcre2test commands. These text forms are provided for ease of scanning + with text editors or similar tools. They are installed in + /share/doc/pcre2, where is the installation prefix + (defaulting to /usr/local). + + 2. A set of files containing all the documentation in HTML form, hyperlinked + in various ways, and rooted in a file called index.html, is distributed in + doc/html and installed in /share/doc/pcre2/html. + + +Building PCRE2 on non-Unix-like systems +--------------------------------------- + +For a non-Unix-like system, please read the comments in the file +NON-AUTOTOOLS-BUILD, though if your system supports the use of "configure" and +"make" you may be able to build PCRE2 using autotools in the same way as for +many Unix-like systems. + +PCRE2 can also be configured using CMake, which can be run in various ways +(command line, GUI, etc). This creates Makefiles, solution files, etc. The file +NON-AUTOTOOLS-BUILD has information about CMake. + +PCRE2 has been compiled on many different operating systems. It should be +straightforward to build PCRE2 on any system that has a Standard C compiler and +library, because it uses only Standard C functions. + + +Building PCRE2 without using autotools +-------------------------------------- + +The use of autotools (in particular, libtool) is problematic in some +environments, even some that are Unix or Unix-like. See the NON-AUTOTOOLS-BUILD +file for ways of building PCRE2 without using autotools. + + +Building PCRE2 using autotools +------------------------------ + +The following instructions assume the use of the widely used "configure; make; +make install" (autotools) process. + +To build PCRE2 on system that supports autotools, first run the "configure" +command from the PCRE2 distribution directory, with your current directory set +to the directory where you want the files to be created. This command is a +standard GNU "autoconf" configuration script, for which generic instructions +are supplied in the file INSTALL. + +Most commonly, people build PCRE2 within its own distribution directory, and in +this case, on many systems, just running "./configure" is sufficient. However, +the usual methods of changing standard defaults are available. For example: + +CFLAGS='-O2 -Wall' ./configure --prefix=/opt/local + +This command specifies that the C compiler should be run with the flags '-O2 +-Wall' instead of the default, and that "make install" should install PCRE2 +under /opt/local instead of the default /usr/local. + +If you want to build in a different directory, just run "configure" with that +directory as current. For example, suppose you have unpacked the PCRE2 source +into /source/pcre2/pcre2-xxx, but you want to build it in +/build/pcre2/pcre2-xxx: + +cd /build/pcre2/pcre2-xxx +/source/pcre2/pcre2-xxx/configure + +PCRE2 is written in C and is normally compiled as a C library. However, it is +possible to build it as a C++ library, though the provided building apparatus +does not have any features to support this. + +There are some optional features that can be included or omitted from the PCRE2 +library. They are also documented in the pcre2build man page. + +. By default, both shared and static libraries are built. You can change this + by adding one of these options to the "configure" command: + + --disable-shared + --disable-static + + (See also "Shared libraries on Unix-like systems" below.) + +. By default, only the 8-bit library is built. If you add --enable-pcre2-16 to + the "configure" command, the 16-bit library is also built. If you add + --enable-pcre2-32 to the "configure" command, the 32-bit library is also + built. If you want only the 16-bit or 32-bit library, use --disable-pcre2-8 + to disable building the 8-bit library. + +. If you want to include support for just-in-time compiling, which can give + large performance improvements on certain platforms, add --enable-jit to the + "configure" command. This support is available only for certain hardware + architectures. If you try to enable it on an unsupported architecture, there + will be a compile time error. + +. When JIT support is enabled, pcre2grep automatically makes use of it, unless + you add --disable-pcre2grep-jit to the "configure" command. + +. If you do not want to make use of the support for UTF-8 Unicode character + strings in the 8-bit library, UTF-16 Unicode character strings in the 16-bit + library, or UTF-32 Unicode character strings in the 32-bit library, you can + add --disable-unicode to the "configure" command. This reduces the size of + the libraries. It is not possible to configure one library with Unicode + support, and another without, in the same configuration. + + When Unicode support is available, the use of a UTF encoding still has to be + enabled by setting the PCRE2_UTF option at run time or starting a pattern + with (*UTF). When PCRE2 is compiled with Unicode support, its input can only + either be ASCII or UTF-8/16/32, even when running on EBCDIC platforms. It is + not possible to use both --enable-unicode and --enable-ebcdic at the same + time. + + As well as supporting UTF strings, Unicode support includes support for the + \P, \p, and \X sequences that recognize Unicode character properties. + However, only the basic two-letter properties such as Lu are supported. + Escape sequences such as \d and \w in patterns do not by default make use of + Unicode properties, but can be made to do so by setting the PCRE2_UCP option + or starting a pattern with (*UCP). + +. You can build PCRE2 to recognize either CR or LF or the sequence CRLF, or any + of the preceding, or any of the Unicode newline sequences, as indicating the + end of a line. Whatever you specify at build time is the default; the caller + of PCRE2 can change the selection at run time. The default newline indicator + is a single LF character (the Unix standard). You can specify the default + newline indicator by adding --enable-newline-is-cr, --enable-newline-is-lf, + --enable-newline-is-crlf, --enable-newline-is-anycrlf, or + --enable-newline-is-any to the "configure" command, respectively. + + If you specify --enable-newline-is-cr or --enable-newline-is-crlf, some of + the standard tests will fail, because the lines in the test files end with + LF. Even if the files are edited to change the line endings, there are likely + to be some failures. With --enable-newline-is-anycrlf or + --enable-newline-is-any, many tests should succeed, but there may be some + failures. + +. By default, the sequence \R in a pattern matches any Unicode line ending + sequence. This is independent of the option specifying what PCRE2 considers + to be the end of a line (see above). However, the caller of PCRE2 can + restrict \R to match only CR, LF, or CRLF. You can make this the default by + adding --enable-bsr-anycrlf to the "configure" command (bsr = "backslash R"). + +. In a pattern, the escape sequence \C matches a single code unit, even in a + UTF mode. This can be dangerous because it breaks up multi-code-unit + characters. You can build PCRE2 with the use of \C permanently locked out by + adding --enable-never-backslash-C (note the upper case C) to the "configure" + command. When \C is allowed by the library, individual applications can lock + it out by calling pcre2_compile() with the PCRE2_NEVER_BACKSLASH_C option. + +. PCRE2 has a counter that limits the depth of nesting of parentheses in a + pattern. This limits the amount of system stack that a pattern uses when it + is compiled. The default is 250, but you can change it by setting, for + example, + + --with-parens-nest-limit=500 + +. PCRE2 has a counter that can be set to limit the amount of resources it uses + when matching a pattern. If the limit is exceeded during a match, the match + fails. The default is ten million. You can change the default by setting, for + example, + + --with-match-limit=500000 + + on the "configure" command. This is just the default; individual calls to + pcre2_match() can supply their own value. There is more discussion on the + pcre2api man page. + +. There is a separate counter that limits the depth of recursive function calls + during a matching process. This also has a default of ten million, which is + essentially "unlimited". You can change the default by setting, for example, + + --with-match-limit-recursion=500000 + + Recursive function calls use up the runtime stack; running out of stack can + cause programs to crash in strange ways. There is a discussion about stack + sizes in the pcre2stack man page. + +. In the 8-bit library, the default maximum compiled pattern size is around + 64K. You can increase this by adding --with-link-size=3 to the "configure" + command. PCRE2 then uses three bytes instead of two for offsets to different + parts of the compiled pattern. In the 16-bit library, --with-link-size=3 is + the same as --with-link-size=4, which (in both libraries) uses four-byte + offsets. Increasing the internal link size reduces performance in the 8-bit + and 16-bit libraries. In the 32-bit library, the link size setting is + ignored, as 4-byte offsets are always used. + +. You can build PCRE2 so that its internal match() function that is called from + pcre2_match() does not call itself recursively. Instead, it uses memory + blocks obtained from the heap to save data that would otherwise be saved on + the stack. To build PCRE2 like this, use + + --disable-stack-for-recursion + + on the "configure" command. PCRE2 runs more slowly in this mode, but it may + be necessary in environments with limited stack sizes. This applies only to + the normal execution of the pcre2_match() function; if JIT support is being + successfully used, it is not relevant. Equally, it does not apply to + pcre2_dfa_match(), which does not use deeply nested recursion. There is a + discussion about stack sizes in the pcre2stack man page. + +. For speed, PCRE2 uses four tables for manipulating and identifying characters + whose code point values are less than 256. By default, it uses a set of + tables for ASCII encoding that is part of the distribution. If you specify + + --enable-rebuild-chartables + + a program called dftables is compiled and run in the default C locale when + you obey "make". It builds a source file called pcre2_chartables.c. If you do + not specify this option, pcre2_chartables.c is created as a copy of + pcre2_chartables.c.dist. See "Character tables" below for further + information. + +. It is possible to compile PCRE2 for use on systems that use EBCDIC as their + character code (as opposed to ASCII/Unicode) by specifying + + --enable-ebcdic --disable-unicode + + This automatically implies --enable-rebuild-chartables (see above). However, + when PCRE2 is built this way, it always operates in EBCDIC. It cannot support + both EBCDIC and UTF-8/16/32. There is a second option, --enable-ebcdic-nl25, + which specifies that the code value for the EBCDIC NL character is 0x25 + instead of the default 0x15. + +. If you specify --enable-debug, additional debugging code is included in the + build. This option is intended for use by the PCRE2 maintainers. + +. In environments where valgrind is installed, if you specify + + --enable-valgrind + + PCRE2 will use valgrind annotations to mark certain memory regions as + unaddressable. This allows it to detect invalid memory accesses, and is + mostly useful for debugging PCRE2 itself. + +. In environments where the gcc compiler is used and lcov version 1.6 or above + is installed, if you specify + + --enable-coverage + + the build process implements a code coverage report for the test suite. The + report is generated by running "make coverage". If ccache is installed on + your system, it must be disabled when building PCRE2 for coverage reporting. + You can do this by setting the environment variable CCACHE_DISABLE=1 before + running "make" to build PCRE2. There is more information about coverage + reporting in the "pcre2build" documentation. + +. The pcre2grep program currently supports only 8-bit data files, and so + requires the 8-bit PCRE2 library. It is possible to compile pcre2grep to use + libz and/or libbz2, in order to read .gz and .bz2 files (respectively), by + specifying one or both of + + --enable-pcre2grep-libz + --enable-pcre2grep-libbz2 + + Of course, the relevant libraries must be installed on your system. + +. The default size (in bytes) of the internal buffer used by pcre2grep can be + set by, for example: + + --with-pcre2grep-bufsize=51200 + + The value must be a plain integer. The default is 20480. + +. It is possible to compile pcre2test so that it links with the libreadline + or libedit libraries, by specifying, respectively, + + --enable-pcre2test-libreadline or --enable-pcre2test-libedit + + If this is done, when pcre2test's input is from a terminal, it reads it using + the readline() function. This provides line-editing and history facilities. + Note that libreadline is GPL-licenced, so if you distribute a binary of + pcre2test linked in this way, there may be licensing issues. These can be + avoided by linking with libedit (which has a BSD licence) instead. + + Enabling libreadline causes the -lreadline option to be added to the + pcre2test build. In many operating environments with a sytem-installed + readline library this is sufficient. However, in some environments (e.g. if + an unmodified distribution version of readline is in use), it may be + necessary to specify something like LIBS="-lncurses" as well. This is + because, to quote the readline INSTALL, "Readline uses the termcap functions, + but does not link with the termcap or curses library itself, allowing + applications which link with readline the to choose an appropriate library." + If you get error messages about missing functions tgetstr, tgetent, tputs, + tgetflag, or tgoto, this is the problem, and linking with the ncurses library + should fix it. + +The "configure" script builds the following files for the basic C library: + +. Makefile the makefile that builds the library +. src/config.h build-time configuration options for the library +. src/pcre2.h the public PCRE2 header file +. pcre2-config script that shows the building settings such as CFLAGS + that were set for "configure" +. libpcre2-8.pc ) +. libpcre2-16.pc ) data for the pkg-config command +. libpcre2-32.pc ) +. libpcre2-posix.pc ) +. libtool script that builds shared and/or static libraries + +Versions of config.h and pcre2.h are distributed in the src directory of PCRE2 +tarballs under the names config.h.generic and pcre2.h.generic. These are +provided for those who have to build PCRE2 without using "configure" or CMake. +If you use "configure" or CMake, the .generic versions are not used. + +The "configure" script also creates config.status, which is an executable +script that can be run to recreate the configuration, and config.log, which +contains compiler output from tests that "configure" runs. + +Once "configure" has run, you can run "make". This builds whichever of the +libraries libpcre2-8, libpcre2-16 and libpcre2-32 are configured, and a test +program called pcre2test. If you enabled JIT support with --enable-jit, another +test program called pcre2_jit_test is built as well. If the 8-bit library is +built, libpcre2-posix and the pcre2grep command are also built. Running +"make" with the -j option may speed up compilation on multiprocessor systems. + +The command "make check" runs all the appropriate tests. Details of the PCRE2 +tests are given below in a separate section of this document. The -j option of +"make" can also be used when running the tests. + +You can use "make install" to install PCRE2 into live directories on your +system. The following are installed (file names are all relative to the + that is set when "configure" is run): + + Commands (bin): + pcre2test + pcre2grep (if 8-bit support is enabled) + pcre2-config + + Libraries (lib): + libpcre2-8 (if 8-bit support is enabled) + libpcre2-16 (if 16-bit support is enabled) + libpcre2-32 (if 32-bit support is enabled) + libpcre2-posix (if 8-bit support is enabled) + + Configuration information (lib/pkgconfig): + libpcre2-8.pc + libpcre2-16.pc + libpcre2-32.pc + libpcre2-posix.pc + + Header files (include): + pcre2.h + pcre2posix.h + + Man pages (share/man/man{1,3}): + pcre2grep.1 + pcre2test.1 + pcre2-config.1 + pcre2.3 + pcre2*.3 (lots more pages, all starting "pcre2") + + HTML documentation (share/doc/pcre2/html): + index.html + *.html (lots more pages, hyperlinked from index.html) + + Text file documentation (share/doc/pcre2): + AUTHORS + COPYING + ChangeLog + LICENCE + NEWS + README + pcre2.txt (a concatenation of the man(3) pages) + pcre2test.txt the pcre2test man page + pcre2grep.txt the pcre2grep man page + pcre2-config.txt the pcre2-config man page + +If you want to remove PCRE2 from your system, you can run "make uninstall". +This removes all the files that "make install" installed. However, it does not +remove any directories, because these are often shared with other programs. + + +Retrieving configuration information +------------------------------------ + +Running "make install" installs the command pcre2-config, which can be used to +recall information about the PCRE2 configuration and installation. For example: + + pcre2-config --version + +prints the version number, and + + pcre2-config --libs8 + +outputs information about where the 8-bit library is installed. This command +can be included in makefiles for programs that use PCRE2, saving the programmer +from having to remember too many details. Run pcre2-config with no arguments to +obtain a list of possible arguments. + +The pkg-config command is another system for saving and retrieving information +about installed libraries. Instead of separate commands for each library, a +single command is used. For example: + + pkg-config --libs libpcre2-16 + +The data is held in *.pc files that are installed in a directory called +/lib/pkgconfig. + + +Shared libraries +---------------- + +The default distribution builds PCRE2 as shared libraries and static libraries, +as long as the operating system supports shared libraries. Shared library +support relies on the "libtool" script which is built as part of the +"configure" process. + +The libtool script is used to compile and link both shared and static +libraries. They are placed in a subdirectory called .libs when they are newly +built. The programs pcre2test and pcre2grep are built to use these uninstalled +libraries (by means of wrapper scripts in the case of shared libraries). When +you use "make install" to install shared libraries, pcre2grep and pcre2test are +automatically re-built to use the newly installed shared libraries before being +installed themselves. However, the versions left in the build directory still +use the uninstalled libraries. + +To build PCRE2 using static libraries only you must use --disable-shared when +configuring it. For example: + +./configure --prefix=/usr/gnu --disable-shared + +Then run "make" in the usual way. Similarly, you can use --disable-static to +build only shared libraries. + + +Cross-compiling using autotools +------------------------------- + +You can specify CC and CFLAGS in the normal way to the "configure" command, in +order to cross-compile PCRE2 for some other host. However, you should NOT +specify --enable-rebuild-chartables, because if you do, the dftables.c source +file is compiled and run on the local host, in order to generate the inbuilt +character tables (the pcre2_chartables.c file). This will probably not work, +because dftables.c needs to be compiled with the local compiler, not the cross +compiler. + +When --enable-rebuild-chartables is not specified, pcre2_chartables.c is +created by making a copy of pcre2_chartables.c.dist, which is a default set of +tables that assumes ASCII code. Cross-compiling with the default tables should +not be a problem. + +If you need to modify the character tables when cross-compiling, you should +move pcre2_chartables.c.dist out of the way, then compile dftables.c by hand +and run it on the local host to make a new version of pcre2_chartables.c.dist. +Then when you cross-compile PCRE2 this new version of the tables will be used. + + +Making new tarballs +------------------- + +The command "make dist" creates three PCRE2 tarballs, in tar.gz, tar.bz2, and +zip formats. The command "make distcheck" does the same, but then does a trial +build of the new distribution to ensure that it works. + +If you have modified any of the man page sources in the doc directory, you +should first run the PrepareRelease script before making a distribution. This +script creates the .txt and HTML forms of the documentation from the man pages. + + +Testing PCRE2 +------------ + +To test the basic PCRE2 library on a Unix-like system, run the RunTest script. +There is another script called RunGrepTest that tests the pcre2grep command. +When JIT support is enabled, a third test program called pcre2_jit_test is +built. Both the scripts and all the program tests are run if you obey "make +check". For other environments, see the instructions in NON-AUTOTOOLS-BUILD. + +The RunTest script runs the pcre2test test program (which is documented in its +own man page) on each of the relevant testinput files in the testdata +directory, and compares the output with the contents of the corresponding +testoutput files. RunTest uses a file called testtry to hold the main output +from pcre2test. Other files whose names begin with "test" are used as working +files in some tests. + +Some tests are relevant only when certain build-time options were selected. For +example, the tests for UTF-8/16/32 features are run only when Unicode support +is available. RunTest outputs a comment when it skips a test. + +Many (but not all) of the tests that are not skipped are run twice if JIT +support is available. On the second run, JIT compilation is forced. This +testing can be suppressed by putting "nojit" on the RunTest command line. + +The entire set of tests is run once for each of the 8-bit, 16-bit and 32-bit +libraries that are enabled. If you want to run just one set of tests, call +RunTest with either the -8, -16 or -32 option. + +If valgrind is installed, you can run the tests under it by putting "valgrind" +on the RunTest command line. To run pcre2test on just one or more specific test +files, give their numbers as arguments to RunTest, for example: + + RunTest 2 7 11 + +You can also specify ranges of tests such as 3-6 or 3- (meaning 3 to the +end), or a number preceded by ~ to exclude a test. For example: + + Runtest 3-15 ~10 + +This runs tests 3 to 15, excluding test 10, and just ~13 runs all the tests +except test 13. Whatever order the arguments are in, the tests are always run +in numerical order. + +You can also call RunTest with the single argument "list" to cause it to output +a list of tests. + +The test sequence starts with "test 0", which is a special test that has no +input file, and whose output is not checked. This is because it will be +different on different hardware and with different configurations. The test +exists in order to exercise some of pcre2test's code that would not otherwise +be run. + +Tests 1 and 2 can always be run, as they expect only plain text strings (not +UTF) and make no use of Unicode properties. The first test file can be fed +directly into the perltest.sh script to check that Perl gives the same results. +The only difference you should see is in the first few lines, where the Perl +version is given instead of the PCRE2 version. The second set of tests check +auxiliary functions, error detection, and run-time flags that are specific to +PCRE2. It also uses the debugging flags to check some of the internals of +pcre2_compile(). + +If you build PCRE2 with a locale setting that is not the standard C locale, the +character tables may be different (see next paragraph). In some cases, this may +cause failures in the second set of tests. For example, in a locale where the +isprint() function yields TRUE for characters in the range 128-255, the use of +[:isascii:] inside a character class defines a different set of characters, and +this shows up in this test as a difference in the compiled code, which is being +listed for checking. For example, where the comparison test output contains +[\x00-\x7f] the test might contain [\x00-\xff], and similarly in some other +cases. This is not a bug in PCRE2. + +Test 3 checks pcre2_maketables(), the facility for building a set of character +tables for a specific locale and using them instead of the default tables. The +script uses the "locale" command to check for the availability of the "fr_FR", +"french", or "fr" locale, and uses the first one that it finds. If the "locale" +command fails, or if its output doesn't include "fr_FR", "french", or "fr" in +the list of available locales, the third test cannot be run, and a comment is +output to say why. If running this test produces an error like this: + + ** Failed to set locale "fr_FR" + +it means that the given locale is not available on your system, despite being +listed by "locale". This does not mean that PCRE2 is broken. There are three +alternative output files for the third test, because three different versions +of the French locale have been encountered. The test passes if its output +matches any one of them. + +Tests 4 and 5 check UTF and Unicode property support, test 4 being compatible +with the perltest.sh script, and test 5 checking PCRE2-specific things. + +Tests 6 and 7 check the pcre2_dfa_match() alternative matching function, in +non-UTF mode and UTF-mode with Unicode property support, respectively. + +Test 8 checks some internal offsets and code size features; it is run only when +the default "link size" of 2 is set (in other cases the sizes change) and when +Unicode support is enabled. + +Tests 9 and 10 are run only in 8-bit mode, and tests 11 and 12 are run only in +16-bit and 32-bit modes. These are tests that generate different output in +8-bit mode. Each pair are for general cases and Unicode support, respectively. +Test 13 checks the handling of non-UTF characters greater than 255 by +pcre2_dfa_match() in 16-bit and 32-bit modes. + +Test 14 contains a number of tests that must not be run with JIT. They check, +among other non-JIT things, the match-limiting features of the intepretive +matcher. + +Test 15 is run only when JIT support is not available. It checks that an +attempt to use JIT has the expected behaviour. + +Test 16 is run only when JIT support is available. It checks JIT complete and +partial modes, match-limiting under JIT, and other JIT-specific features. + +Tests 17 and 18 are run only in 8-bit mode. They check the POSIX interface to +the 8-bit library, without and with Unicode support, respectively. + +Test 19 checks the serialization functions by writing a set of compiled +patterns to a file, and then reloading and checking them. + + +Character tables +---------------- + +For speed, PCRE2 uses four tables for manipulating and identifying characters +whose code point values are less than 256. By default, a set of tables that is +built into the library is used. The pcre2_maketables() function can be called +by an application to create a new set of tables in the current locale. This are +passed to PCRE2 by calling pcre2_set_character_tables() to put a pointer into a +compile context. + +The source file called pcre2_chartables.c contains the default set of tables. +By default, this is created as a copy of pcre2_chartables.c.dist, which +contains tables for ASCII coding. However, if --enable-rebuild-chartables is +specified for ./configure, a different version of pcre2_chartables.c is built +by the program dftables (compiled from dftables.c), which uses the ANSI C +character handling functions such as isalnum(), isalpha(), isupper(), +islower(), etc. to build the table sources. This means that the default C +locale which is set for your system will control the contents of these default +tables. You can change the default tables by editing pcre2_chartables.c and +then re-building PCRE2. If you do this, you should take care to ensure that the +file does not get automatically re-generated. The best way to do this is to +move pcre2_chartables.c.dist out of the way and replace it with your customized +tables. + +When the dftables program is run as a result of --enable-rebuild-chartables, +it uses the default C locale that is set on your system. It does not pay +attention to the LC_xxx environment variables. In other words, it uses the +system's default locale rather than whatever the compiling user happens to have +set. If you really do want to build a source set of character tables in a +locale that is specified by the LC_xxx variables, you can run the dftables +program by hand with the -L option. For example: + + ./dftables -L pcre2_chartables.c.special + +The first two 256-byte tables provide lower casing and case flipping functions, +respectively. The next table consists of three 32-byte bit maps which identify +digits, "word" characters, and white space, respectively. These are used when +building 32-byte bit maps that represent character classes for code points less +than 256. The final 256-byte table has bits indicating various character types, +as follows: + + 1 white space character + 2 letter + 4 decimal digit + 8 hexadecimal digit + 16 alphanumeric or '_' + 128 regular expression metacharacter or binary zero + +You should not alter the set of characters that contain the 128 bit, as that +will cause PCRE2 to malfunction. + + +File manifest +------------- + +The distribution should contain the files listed below. + +(A) Source files for the PCRE2 library functions and their headers are found in + the src directory: + + src/dftables.c auxiliary program for building pcre2_chartables.c + when --enable-rebuild-chartables is specified + + src/pcre2_chartables.c.dist a default set of character tables that assume + ASCII coding; unless --enable-rebuild-chartables is + specified, used by copying to pcre2_chartables.c + + src/pcre2posix.c ) + src/pcre2_auto_possess.c ) + src/pcre2_compile.c ) + src/pcre2_config.c ) + src/pcre2_context.c ) + src/pcre2_dfa_match.c ) + src/pcre2_error.c ) + src/pcre2_find_bracket.c ) + src/pcre2_jit_compile.c ) + src/pcre2_jit_match.c ) sources for the functions in the library, + src/pcre2_jit_misc.c ) and some internal functions that they use + src/pcre2_maketables.c ) + src/pcre2_match.c ) + src/pcre2_match_data.c ) + src/pcre2_newline.c ) + src/pcre2_ord2utf.c ) + src/pcre2_pattern_info.c ) + src/pcre2_serialize.c ) + src/pcre2_string_utils.c ) + src/pcre2_study.c ) + src/pcre2_substitute.c ) + src/pcre2_substring.c ) + src/pcre2_tables.c ) + src/pcre2_ucd.c ) + src/pcre2_valid_utf.c ) + src/pcre2_xclass.c ) + + src/pcre2_printint.c debugging function that is used by pcre2test, + + src/config.h.in template for config.h, when built by "configure" + src/pcre2.h.in template for pcre2.h when built by "configure" + src/pcre2posix.h header for the external POSIX wrapper API + src/pcre2_internal.h header for internal use + src/pcre2_intmodedep.h a mode-specific internal header + src/pcre2_ucp.h header for Unicode property handling + + sljit/* source files for the JIT compiler + +(B) Source files for programs that use PCRE2: + + src/pcre2demo.c simple demonstration of coding calls to PCRE2 + src/pcre2grep.c source of a grep utility that uses PCRE2 + src/pcre2test.c comprehensive test program + src/pcre2_printint.c part of pcre2test + src/pcre2_jit_test.c JIT test program + +(C) Auxiliary files: + + 132html script to turn "man" pages into HTML + AUTHORS information about the author of PCRE2 + ChangeLog log of changes to the code + CleanTxt script to clean nroff output for txt man pages + Detrail script to remove trailing spaces + HACKING some notes about the internals of PCRE2 + INSTALL generic installation instructions + LICENCE conditions for the use of PCRE2 + COPYING the same, using GNU's standard name + Makefile.in ) template for Unix Makefile, which is built by + ) "configure" + Makefile.am ) the automake input that was used to create + ) Makefile.in + NEWS important changes in this release + NON-AUTOTOOLS-BUILD notes on building PCRE2 without using autotools + PrepareRelease script to make preparations for "make dist" + README this file + RunTest a Unix shell script for running tests + RunGrepTest a Unix shell script for pcre2grep tests + aclocal.m4 m4 macros (generated by "aclocal") + config.guess ) files used by libtool, + config.sub ) used only when building a shared library + configure a configuring shell script (built by autoconf) + configure.ac ) the autoconf input that was used to build + ) "configure" and config.h + depcomp ) script to find program dependencies, generated by + ) automake + doc/*.3 man page sources for PCRE2 + doc/*.1 man page sources for pcre2grep and pcre2test + doc/index.html.src the base HTML page + doc/html/* HTML documentation + doc/pcre2.txt plain text version of the man pages + doc/pcre2test.txt plain text documentation of test program + install-sh a shell script for installing files + libpcre2-8.pc.in template for libpcre2-8.pc for pkg-config + libpcre2-16.pc.in template for libpcre2-16.pc for pkg-config + libpcre2-32.pc.in template for libpcre2-32.pc for pkg-config + libpcre2posix.pc.in template for libpcre2posix.pc for pkg-config + ltmain.sh file used to build a libtool script + missing ) common stub for a few missing GNU programs while + ) installing, generated by automake + mkinstalldirs script for making install directories + perltest.sh Script for running a Perl test program + pcre2-config.in source of script which retains PCRE2 information + testdata/testinput* test data for main library tests + testdata/testoutput* expected test results + testdata/grep* input and output for pcre2grep tests + testdata/* other supporting test files + +(D) Auxiliary files for cmake support + + cmake/COPYING-CMAKE-SCRIPTS + cmake/FindPackageHandleStandardArgs.cmake + cmake/FindEditline.cmake + cmake/FindReadline.cmake + CMakeLists.txt + config-cmake.h.in + +(E) Auxiliary files for building PCRE2 "by hand" + + pcre2.h.generic ) a version of the public PCRE2 header file + ) for use in non-"configure" environments + config.h.generic ) a version of config.h for use in non-"configure" + ) environments + +Philip Hazel +Email local part: ph10 +Email domain: cam.ac.uk +Last updated: 16 October 2015 diff --git a/ProcessHacker/pcre/config.h b/ProcessHacker/pcre/config.h new file mode 100644 index 0000000..9b3b7aa --- /dev/null +++ b/ProcessHacker/pcre/config.h @@ -0,0 +1,336 @@ +/* src/config.h. Generated from config.h.in by configure. */ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* PCRE2 is written in Standard C, but there are a few non-standard things it +can cope with, allowing it to run on SunOS4 and other "close to standard" +systems. + +In environments that support the GNU autotools, config.h.in is converted into +config.h by the "configure" script. In environments that use CMake, +config-cmake.in is converted into config.h. If you are going to build PCRE2 "by +hand" without using "configure" or CMake, you should copy the distributed +config.h.generic to config.h, and edit the macro definitions to be the way you +need them. You must then add -DHAVE_CONFIG_H to all of your compile commands, +so that config.h is included at the start of every source. + +Alternatively, you can avoid editing by using -D on the compiler command line +to set the macro values. In this case, you do not have to set -DHAVE_CONFIG_H, +but if you do, default values will be taken from config.h for non-boolean +macros that are not defined on the command line. + +Boolean macros such as HAVE_STDLIB_H and SUPPORT_PCRE2_8 should either be defined +(conventionally to 1) for TRUE, and not defined at all for FALSE. All such +macros are listed as a commented #undef in config.h.generic. Macros such as +MATCH_LIMIT, whose actual value is relevant, have defaults defined, but are +surrounded by #ifndef/#endif lines so that the value can be overridden by -D. + +PCRE2 uses memmove() if HAVE_MEMMOVE is defined; otherwise it uses bcopy() if +HAVE_BCOPY is defined. If your system has neither bcopy() nor memmove(), make +sure both macros are undefined; an emulation function will then be used. */ + +/* By default, the \R escape sequence matches any Unicode line ending + character or sequence of characters. If BSR_ANYCRLF is defined (to any + value), this is changed so that backslash-R matches only CR, LF, or CRLF. + The build-time default can be overridden by the user of PCRE2 at runtime. + */ +/* #undef BSR_ANYCRLF */ + +/* If you are compiling for a system that uses EBCDIC instead of ASCII + character codes, define this macro to any value. When EBCDIC is set, PCRE2 + assumes that all input strings are in EBCDIC. If you do not define this + macro, PCRE2 will assume input strings are ASCII or UTF-8/16/32 Unicode. It + is not possible to build a version of PCRE2 that supports both EBCDIC and + UTF-8/16/32. */ +/* #undef EBCDIC */ + +/* In an EBCDIC environment, define this macro to any value to arrange for the + NL character to be 0x25 instead of the default 0x15. NL plays the role that + LF does in an ASCII/Unicode environment. */ +/* #undef EBCDIC_NL25 */ + +/* Define to 1 if you have the `bcopy' function. */ +/* #undef HAVE_BCOPY */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_BZLIB_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DIRENT_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLFCN_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_EDITLINE_READLINE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_EDIT_READLINE_READLINE_H */ + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_INTTYPES_H +#define HAVE_INTTYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_LIMITS_H +#define HAVE_LIMITS_H 1 +#endif + +/* Define to 1 if you have the `memmove' function. */ +#ifndef HAVE_MEMMOVE +#define HAVE_MEMMOVE 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_MEMORY_H +#define HAVE_MEMORY_H 1 +#endif + +/* Define if you have POSIX threads libraries and header files. */ +/* #undef HAVE_PTHREAD */ + +/* Have PTHREAD_PRIO_INHERIT. */ +/* #undef HAVE_PTHREAD_PRIO_INHERIT */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_READLINE_HISTORY_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_READLINE_READLINE_H */ + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_STDLIB_H +#define HAVE_STDLIB_H 1 +#endif + +/* Define to 1 if you have the `strerror' function. */ +#ifndef HAVE_STRERROR +#define HAVE_STRERROR 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STRINGS_H */ + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_STRING_H +#define HAVE_STRING_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_SYS_STAT_H +#define HAVE_SYS_STAT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_SYS_TYPES_H +#define HAVE_SYS_TYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UNISTD_H */ + +/* Define to 1 if the compiler supports simple visibility declarations. */ +/* #undef HAVE_VISIBILITY */ + +/* Define to 1 if you have the header file. */ +#ifndef HAVE_WINDOWS_H +#define HAVE_WINDOWS_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ZLIB_H */ + +/* PCRE2 uses recursive function calls to handle backtracking while matching. + This can sometimes be a problem on systems that have stacks of limited + size. Define HEAP_MATCH_RECURSE to any value to get a version that doesn't + use recursion in the match() function; instead it creates its own stack by + steam using memory from the heap. For more detail, see the comments and + other stuff just above the match() function. */ +/* #undef HEAP_MATCH_RECURSE */ + +/* The value of LINK_SIZE determines the number of bytes used to store links + as offsets within the compiled regex. The default is 2, which allows for + compiled patterns up to 64K long. This covers the vast majority of cases. + However, PCRE2 can also be compiled to use 3 or 4 bytes instead. This + allows for longer patterns in extreme cases. */ +#ifndef LINK_SIZE +#define LINK_SIZE 2 +#endif + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +/* This is ignored unless you are using libtool. */ +#ifndef LT_OBJDIR +#define LT_OBJDIR ".libs/" +#endif + +/* The value of MATCH_LIMIT determines the default number of times the + internal match() function can be called during a single execution of + pcre2_match(). There is a runtime interface for setting a different limit. + The limit exists in order to catch runaway regular expressions that take + for ever to determine that they do not match. The default is set very large + so that it does not accidentally catch legitimate cases. */ +#ifndef MATCH_LIMIT +#define MATCH_LIMIT 10000000 +#endif + +/* The above limit applies to all calls of match(), whether or not they + increase the recursion depth. In some environments it is desirable to limit + the depth of recursive calls of match() more strictly, in order to restrict + the maximum amount of stack (or heap, if HEAP_MATCH_RECURSE is defined) + that is used. The value of MATCH_LIMIT_RECURSION applies only to recursive + calls of match(). To have any useful effect, it must be less than the value + of MATCH_LIMIT. The default is to use the same value as MATCH_LIMIT. There + is a runtime method for setting a different limit. */ +#ifndef MATCH_LIMIT_RECURSION +#define MATCH_LIMIT_RECURSION MATCH_LIMIT +#endif + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#ifndef MAX_NAME_COUNT +#define MAX_NAME_COUNT 10000 +#endif + +/* This limit is parameterized just in case anybody ever wants to change it. + Care must be taken if it is increased, because it guards against integer + overflow caused by enormously large patterns. */ +#ifndef MAX_NAME_SIZE +#define MAX_NAME_SIZE 32 +#endif + +/* Defining NEVER_BACKSLASH_C locks out the use of \C in all patterns. */ +/* #undef NEVER_BACKSLASH_C */ + +/* The value of NEWLINE_DEFAULT determines the default newline character + sequence. PCRE2 client programs can override this by selecting other values + at run time. The valid values are 1 (CR), 2 (LF), 3 (CRLF), 4 (ANY), and 5 + (ANYCRLF). */ +#ifndef NEWLINE_DEFAULT +#define NEWLINE_DEFAULT 2 +#endif + +/* Name of package */ +#define PACKAGE "pcre2" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "PCRE2" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "PCRE2 10.21" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "pcre2" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "10.21" + +/* The value of PARENS_NEST_LIMIT specifies the maximum depth of nested + parentheses (of any kind) in a pattern. This limits the amount of system + stack that is used while compiling a pattern. */ +#ifndef PARENS_NEST_LIMIT +#define PARENS_NEST_LIMIT 250 +#endif + +/* The value of PCRE2GREP_BUFSIZE determines the size of buffer used by + pcre2grep to hold parts of the file it is searching. This is also the + minimum value. The actual amount of memory used by pcre2grep is three times + this number, because it allows for the buffering of "before" and "after" + lines. */ +#ifndef PCRE2GREP_BUFSIZE +#define PCRE2GREP_BUFSIZE 20480 +#endif + +/* Define to any value to include debugging code. */ +/* #undef PCRE2_DEBUG */ + +/* If you are compiling for a system other than a Unix-like system or + Win32, and it needs some magic to be inserted before the definition + of a function that is exported by the library, define this macro to + contain the relevant magic. If you do not define this macro, a suitable + __declspec value is used for Windows systems; in other environments + "extern" is used for a C compiler and "extern C" for a C++ compiler. + This macro apears at the start of every exported function that is part + of the external API. It does not appear on functions that are "external" + in the C sense, but which are internal to the library. */ +/* #undef PCRE2_EXP_DEFN */ + +/* Define to any value if linking statically (TODO: make nice with Libtool) */ +#ifndef PCRE2_STATIC +#define PCRE2_STATIC 1 +#endif + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* Define to 1 if you have the ANSI C header files. */ +#ifndef STDC_HEADERS +#define STDC_HEADERS 1 +#endif + +/* Define to any value to enable support for Just-In-Time compiling. */ +/* #undef SUPPORT_JIT */ + +/* Define to any value to allow pcre2grep to be linked with libbz2, so that it + is able to handle .bz2 files. */ +/* #undef SUPPORT_LIBBZ2 */ + +/* Define to any value to allow pcre2test to be linked with libedit. */ +/* #undef SUPPORT_LIBEDIT */ + +/* Define to any value to allow pcre2test to be linked with libreadline. */ +/* #undef SUPPORT_LIBREADLINE */ + +/* Define to any value to allow pcre2grep to be linked with libz, so that it + is able to handle .gz files. */ +/* #undef SUPPORT_LIBZ */ + +/* Define to any value to enable JIT support in pcre2grep. */ +/* #undef SUPPORT_PCRE2GREP_JIT */ + +/* Define to any value to enable the 16 bit PCRE2 library. */ +#ifndef SUPPORT_PCRE2_16 +#define SUPPORT_PCRE2_16 1 +#endif + +/* Define to any value to enable the 32 bit PCRE2 library. */ +/* #undef SUPPORT_PCRE2_32 */ + +/* Define to any value to enable the 8 bit PCRE2 library. */ +/* #undef SUPPORT_PCRE2_8 */ + +/* Define to any value to enable support for Unicode and UTF encoding. This + will work even in an EBCDIC environment, but it is incompatible with the + EBCDIC macro. That is, PCRE2 can support *either* EBCDIC code *or* + ASCII/Unicode, but not both at once. */ +#ifndef SUPPORT_UNICODE +#define SUPPORT_UNICODE 1 +#endif + +/* Define to any value for valgrind support to find invalid memory reads. */ +/* #undef SUPPORT_VALGRIND */ + +/* Version number of package */ +#define VERSION "10.21" + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to the type of a signed integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +/* #undef int64_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ diff --git a/ProcessHacker/pcre/pcre2.h b/ProcessHacker/pcre/pcre2.h new file mode 100644 index 0000000..af0a3f5 --- /dev/null +++ b/ProcessHacker/pcre/pcre2.h @@ -0,0 +1,730 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This is the public header file for the PCRE library, second API, to be +#included by applications that call PCRE2 functions. + + Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifndef _PCRE2_H +#define _PCRE2_H + +// dmex: This must match PCRE2_STATIC in config.h +#define PCRE2_STATIC 1 + +// dmex: This must match SUPPORT_PCRE2_16 or SUPPORT_PCRE2_32 in config.h +#undef PCRE2_CODE_UNIT_WIDTH +#define PCRE2_CODE_UNIT_WIDTH 16 + + +/* The current PCRE version information. */ + +#define PCRE2_MAJOR 10 +#define PCRE2_MINOR 21 +#define PCRE2_PRERELEASE +#define PCRE2_DATE 2016-01-12 + +/* When an application links to a PCRE DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE2, the appropriate +export setting is defined in pcre2_internal.h, which includes this file. So we +don't change existing definitions of PCRE2_EXP_DECL. */ + +#if defined(_WIN32) && !defined(PCRE2_STATIC) +# ifndef PCRE2_EXP_DECL +# define PCRE2_EXP_DECL extern __declspec(dllimport) +# endif +#endif + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCRE2_EXP_DECL +# ifdef __cplusplus +# define PCRE2_EXP_DECL extern "C" +# else +# define PCRE2_EXP_DECL extern +# endif +#endif + +/* Have to include limits.h, stdlib.h and stdint.h to ensure that size_t and +uint8_t, UCHAR_MAX, etc are defined. */ + +#include +#include +#include + +/* Allow for C++ users compiling this directly. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* The following option bits can be passed to pcre2_compile(), pcre2_match(), +or pcre2_dfa_match(). PCRE2_NO_UTF_CHECK affects only the function to which it +is passed. Put these bits at the most significant end of the options word so +others can be added next to them */ + +#define PCRE2_ANCHORED 0x80000000u +#define PCRE2_NO_UTF_CHECK 0x40000000u + +/* The following option bits can be passed only to pcre2_compile(). However, +they may affect compilation, JIT compilation, and/or interpretive execution. +The following tags indicate which: + +C alters what is compiled by pcre2_compile() +J alters what is compiled by pcre2_jit_compile() +M is inspected during pcre2_match() execution +D is inspected during pcre2_dfa_match() execution +*/ + +#define PCRE2_ALLOW_EMPTY_CLASS 0x00000001u /* C */ +#define PCRE2_ALT_BSUX 0x00000002u /* C */ +#define PCRE2_AUTO_CALLOUT 0x00000004u /* C */ +#define PCRE2_CASELESS 0x00000008u /* C */ +#define PCRE2_DOLLAR_ENDONLY 0x00000010u /* J M D */ +#define PCRE2_DOTALL 0x00000020u /* C */ +#define PCRE2_DUPNAMES 0x00000040u /* C */ +#define PCRE2_EXTENDED 0x00000080u /* C */ +#define PCRE2_FIRSTLINE 0x00000100u /* J M D */ +#define PCRE2_MATCH_UNSET_BACKREF 0x00000200u /* C J M */ +#define PCRE2_MULTILINE 0x00000400u /* C */ +#define PCRE2_NEVER_UCP 0x00000800u /* C */ +#define PCRE2_NEVER_UTF 0x00001000u /* C */ +#define PCRE2_NO_AUTO_CAPTURE 0x00002000u /* C */ +#define PCRE2_NO_AUTO_POSSESS 0x00004000u /* C */ +#define PCRE2_NO_DOTSTAR_ANCHOR 0x00008000u /* C */ +#define PCRE2_NO_START_OPTIMIZE 0x00010000u /* J M D */ +#define PCRE2_UCP 0x00020000u /* C J M D */ +#define PCRE2_UNGREEDY 0x00040000u /* C */ +#define PCRE2_UTF 0x00080000u /* C J M D */ +#define PCRE2_NEVER_BACKSLASH_C 0x00100000u /* C */ +#define PCRE2_ALT_CIRCUMFLEX 0x00200000u /* J M D */ +#define PCRE2_ALT_VERBNAMES 0x00400000u /* C */ +#define PCRE2_USE_OFFSET_LIMIT 0x00800000u /* J M D */ + +/* These are for pcre2_jit_compile(). */ + +#define PCRE2_JIT_COMPLETE 0x00000001u /* For full matching */ +#define PCRE2_JIT_PARTIAL_SOFT 0x00000002u +#define PCRE2_JIT_PARTIAL_HARD 0x00000004u + +/* These are for pcre2_match(), pcre2_dfa_match(), and pcre2_jit_match(). Note +that PCRE2_ANCHORED and PCRE2_NO_UTF_CHECK can also be passed to these +functions (though pcre2_jit_match() ignores the latter since it bypasses all +sanity checks). */ + +#define PCRE2_NOTBOL 0x00000001u +#define PCRE2_NOTEOL 0x00000002u +#define PCRE2_NOTEMPTY 0x00000004u /* ) These two must be kept */ +#define PCRE2_NOTEMPTY_ATSTART 0x00000008u /* ) adjacent to each other. */ +#define PCRE2_PARTIAL_SOFT 0x00000010u +#define PCRE2_PARTIAL_HARD 0x00000020u + +/* These are additional options for pcre2_dfa_match(). */ + +#define PCRE2_DFA_RESTART 0x00000040u +#define PCRE2_DFA_SHORTEST 0x00000080u + +/* These are additional options for pcre2_substitute(). */ + +#define PCRE2_SUBSTITUTE_GLOBAL 0x00000100u +#define PCRE2_SUBSTITUTE_EXTENDED 0x00000200u +#define PCRE2_SUBSTITUTE_UNSET_EMPTY 0x00000400u +#define PCRE2_SUBSTITUTE_UNKNOWN_UNSET 0x00000800u +#define PCRE2_SUBSTITUTE_OVERFLOW_LENGTH 0x00001000u + +/* Newline and \R settings, for use in compile contexts. The newline values +must be kept in step with values set in config.h and both sets must all be +greater than zero. */ + +#define PCRE2_NEWLINE_CR 1 +#define PCRE2_NEWLINE_LF 2 +#define PCRE2_NEWLINE_CRLF 3 +#define PCRE2_NEWLINE_ANY 4 +#define PCRE2_NEWLINE_ANYCRLF 5 + +#define PCRE2_BSR_UNICODE 1 +#define PCRE2_BSR_ANYCRLF 2 + +/* Error codes: no match and partial match are "expected" errors. */ + +#define PCRE2_ERROR_NOMATCH (-1) +#define PCRE2_ERROR_PARTIAL (-2) + +/* Error codes for UTF-8 validity checks */ + +#define PCRE2_ERROR_UTF8_ERR1 (-3) +#define PCRE2_ERROR_UTF8_ERR2 (-4) +#define PCRE2_ERROR_UTF8_ERR3 (-5) +#define PCRE2_ERROR_UTF8_ERR4 (-6) +#define PCRE2_ERROR_UTF8_ERR5 (-7) +#define PCRE2_ERROR_UTF8_ERR6 (-8) +#define PCRE2_ERROR_UTF8_ERR7 (-9) +#define PCRE2_ERROR_UTF8_ERR8 (-10) +#define PCRE2_ERROR_UTF8_ERR9 (-11) +#define PCRE2_ERROR_UTF8_ERR10 (-12) +#define PCRE2_ERROR_UTF8_ERR11 (-13) +#define PCRE2_ERROR_UTF8_ERR12 (-14) +#define PCRE2_ERROR_UTF8_ERR13 (-15) +#define PCRE2_ERROR_UTF8_ERR14 (-16) +#define PCRE2_ERROR_UTF8_ERR15 (-17) +#define PCRE2_ERROR_UTF8_ERR16 (-18) +#define PCRE2_ERROR_UTF8_ERR17 (-19) +#define PCRE2_ERROR_UTF8_ERR18 (-20) +#define PCRE2_ERROR_UTF8_ERR19 (-21) +#define PCRE2_ERROR_UTF8_ERR20 (-22) +#define PCRE2_ERROR_UTF8_ERR21 (-23) + +/* Error codes for UTF-16 validity checks */ + +#define PCRE2_ERROR_UTF16_ERR1 (-24) +#define PCRE2_ERROR_UTF16_ERR2 (-25) +#define PCRE2_ERROR_UTF16_ERR3 (-26) + +/* Error codes for UTF-32 validity checks */ + +#define PCRE2_ERROR_UTF32_ERR1 (-27) +#define PCRE2_ERROR_UTF32_ERR2 (-28) + +/* Error codes for pcre2[_dfa]_match(), substring extraction functions, context +functions, and serializing functions. They are in numerical order. Originally +they were in alphabetical order too, but now that PCRE2 is released, the +numbers must not be changed. */ + +#define PCRE2_ERROR_BADDATA (-29) +#define PCRE2_ERROR_MIXEDTABLES (-30) /* Name was changed */ +#define PCRE2_ERROR_BADMAGIC (-31) +#define PCRE2_ERROR_BADMODE (-32) +#define PCRE2_ERROR_BADOFFSET (-33) +#define PCRE2_ERROR_BADOPTION (-34) +#define PCRE2_ERROR_BADREPLACEMENT (-35) +#define PCRE2_ERROR_BADUTFOFFSET (-36) +#define PCRE2_ERROR_CALLOUT (-37) /* Never used by PCRE2 itself */ +#define PCRE2_ERROR_DFA_BADRESTART (-38) +#define PCRE2_ERROR_DFA_RECURSE (-39) +#define PCRE2_ERROR_DFA_UCOND (-40) +#define PCRE2_ERROR_DFA_UFUNC (-41) +#define PCRE2_ERROR_DFA_UITEM (-42) +#define PCRE2_ERROR_DFA_WSSIZE (-43) +#define PCRE2_ERROR_INTERNAL (-44) +#define PCRE2_ERROR_JIT_BADOPTION (-45) +#define PCRE2_ERROR_JIT_STACKLIMIT (-46) +#define PCRE2_ERROR_MATCHLIMIT (-47) +#define PCRE2_ERROR_NOMEMORY (-48) +#define PCRE2_ERROR_NOSUBSTRING (-49) +#define PCRE2_ERROR_NOUNIQUESUBSTRING (-50) +#define PCRE2_ERROR_NULL (-51) +#define PCRE2_ERROR_RECURSELOOP (-52) +#define PCRE2_ERROR_RECURSIONLIMIT (-53) +#define PCRE2_ERROR_UNAVAILABLE (-54) +#define PCRE2_ERROR_UNSET (-55) +#define PCRE2_ERROR_BADOFFSETLIMIT (-56) +#define PCRE2_ERROR_BADREPESCAPE (-57) +#define PCRE2_ERROR_REPMISSINGBRACE (-58) +#define PCRE2_ERROR_BADSUBSTITUTION (-59) +#define PCRE2_ERROR_BADSUBSPATTERN (-60) +#define PCRE2_ERROR_TOOMANYREPLACE (-61) + +/* Request types for pcre2_pattern_info() */ + +#define PCRE2_INFO_ALLOPTIONS 0 +#define PCRE2_INFO_ARGOPTIONS 1 +#define PCRE2_INFO_BACKREFMAX 2 +#define PCRE2_INFO_BSR 3 +#define PCRE2_INFO_CAPTURECOUNT 4 +#define PCRE2_INFO_FIRSTCODEUNIT 5 +#define PCRE2_INFO_FIRSTCODETYPE 6 +#define PCRE2_INFO_FIRSTBITMAP 7 +#define PCRE2_INFO_HASCRORLF 8 +#define PCRE2_INFO_JCHANGED 9 +#define PCRE2_INFO_JITSIZE 10 +#define PCRE2_INFO_LASTCODEUNIT 11 +#define PCRE2_INFO_LASTCODETYPE 12 +#define PCRE2_INFO_MATCHEMPTY 13 +#define PCRE2_INFO_MATCHLIMIT 14 +#define PCRE2_INFO_MAXLOOKBEHIND 15 +#define PCRE2_INFO_MINLENGTH 16 +#define PCRE2_INFO_NAMECOUNT 17 +#define PCRE2_INFO_NAMEENTRYSIZE 18 +#define PCRE2_INFO_NAMETABLE 19 +#define PCRE2_INFO_NEWLINE 20 +#define PCRE2_INFO_RECURSIONLIMIT 21 +#define PCRE2_INFO_SIZE 22 +#define PCRE2_INFO_HASBACKSLASHC 23 + +/* Request types for pcre2_config(). */ + +#define PCRE2_CONFIG_BSR 0 +#define PCRE2_CONFIG_JIT 1 +#define PCRE2_CONFIG_JITTARGET 2 +#define PCRE2_CONFIG_LINKSIZE 3 +#define PCRE2_CONFIG_MATCHLIMIT 4 +#define PCRE2_CONFIG_NEWLINE 5 +#define PCRE2_CONFIG_PARENSLIMIT 6 +#define PCRE2_CONFIG_RECURSIONLIMIT 7 +#define PCRE2_CONFIG_STACKRECURSE 8 +#define PCRE2_CONFIG_UNICODE 9 +#define PCRE2_CONFIG_UNICODE_VERSION 10 +#define PCRE2_CONFIG_VERSION 11 + +/* Types for code units in patterns and subject strings. */ + +typedef uint8_t PCRE2_UCHAR8; +typedef uint16_t PCRE2_UCHAR16; +typedef uint32_t PCRE2_UCHAR32; + +typedef const PCRE2_UCHAR8 *PCRE2_SPTR8; +typedef const PCRE2_UCHAR16 *PCRE2_SPTR16; +typedef const PCRE2_UCHAR32 *PCRE2_SPTR32; + +/* The PCRE2_SIZE type is used for all string lengths and offsets in PCRE2, +including pattern offsets for errors and subject offsets after a match. We +define special values to indicate zero-terminated strings and unset offsets in +the offset vector (ovector). */ + +#define PCRE2_SIZE size_t +#define PCRE2_SIZE_MAX SIZE_MAX +#define PCRE2_ZERO_TERMINATED (~(PCRE2_SIZE)0) +#define PCRE2_UNSET (~(PCRE2_SIZE)0) + +/* Generic types for opaque structures and JIT callback functions. These +declarations are defined in a macro that is expanded for each width later. */ + +#define PCRE2_TYPES_LIST \ +struct pcre2_real_general_context; \ +typedef struct pcre2_real_general_context pcre2_general_context; \ +\ +struct pcre2_real_compile_context; \ +typedef struct pcre2_real_compile_context pcre2_compile_context; \ +\ +struct pcre2_real_match_context; \ +typedef struct pcre2_real_match_context pcre2_match_context; \ +\ +struct pcre2_real_code; \ +typedef struct pcre2_real_code pcre2_code; \ +\ +struct pcre2_real_match_data; \ +typedef struct pcre2_real_match_data pcre2_match_data; \ +\ +struct pcre2_real_jit_stack; \ +typedef struct pcre2_real_jit_stack pcre2_jit_stack; \ +\ +typedef pcre2_jit_stack *(*pcre2_jit_callback)(void *); + + +/* The structure for passing out data via the pcre_callout_function. We use a +structure so that new fields can be added on the end in future versions, +without changing the API of the function, thereby allowing old clients to work +without modification. Define the generic version in a macro; the width-specific +versions are generated from this macro below. */ + +#define PCRE2_STRUCTURE_LIST \ +typedef struct pcre2_callout_block { \ + uint32_t version; /* Identifies version of block */ \ + /* ------------------------ Version 0 ------------------------------- */ \ + uint32_t callout_number; /* Number compiled into pattern */ \ + uint32_t capture_top; /* Max current capture */ \ + uint32_t capture_last; /* Most recently closed capture */ \ + PCRE2_SIZE *offset_vector; /* The offset vector */ \ + PCRE2_SPTR mark; /* Pointer to current mark or NULL */ \ + PCRE2_SPTR subject; /* The subject being matched */ \ + PCRE2_SIZE subject_length; /* The length of the subject */ \ + PCRE2_SIZE start_match; /* Offset to start of this match attempt */ \ + PCRE2_SIZE current_position; /* Where we currently are in the subject */ \ + PCRE2_SIZE pattern_position; /* Offset to next item in the pattern */ \ + PCRE2_SIZE next_item_length; /* Length of next item in the pattern */ \ + /* ------------------- Added for Version 1 -------------------------- */ \ + PCRE2_SIZE callout_string_offset; /* Offset to string within pattern */ \ + PCRE2_SIZE callout_string_length; /* Length of string compiled into pattern */ \ + PCRE2_SPTR callout_string; /* String compiled into pattern */ \ + /* ------------------------------------------------------------------ */ \ +} pcre2_callout_block; \ +\ +typedef struct pcre2_callout_enumerate_block { \ + uint32_t version; /* Identifies version of block */ \ + /* ------------------------ Version 0 ------------------------------- */ \ + PCRE2_SIZE pattern_position; /* Offset to next item in the pattern */ \ + PCRE2_SIZE next_item_length; /* Length of next item in the pattern */ \ + uint32_t callout_number; /* Number compiled into pattern */ \ + PCRE2_SIZE callout_string_offset; /* Offset to string within pattern */ \ + PCRE2_SIZE callout_string_length; /* Length of string compiled into pattern */ \ + PCRE2_SPTR callout_string; /* String compiled into pattern */ \ + /* ------------------------------------------------------------------ */ \ +} pcre2_callout_enumerate_block; + + +/* List the generic forms of all other functions in macros, which will be +expanded for each width below. Start with functions that give general +information. */ + +#define PCRE2_GENERAL_INFO_FUNCTIONS \ +PCRE2_EXP_DECL int pcre2_config(uint32_t, void *); + + +/* Functions for manipulating contexts. */ + +#define PCRE2_GENERAL_CONTEXT_FUNCTIONS \ +PCRE2_EXP_DECL \ + pcre2_general_context *pcre2_general_context_copy(pcre2_general_context *); \ +PCRE2_EXP_DECL \ + pcre2_general_context *pcre2_general_context_create( \ + void *(*)(PCRE2_SIZE, void *), \ + void (*)(void *, void *), void *); \ +PCRE2_EXP_DECL void pcre2_general_context_free(pcre2_general_context *); + +#define PCRE2_COMPILE_CONTEXT_FUNCTIONS \ +PCRE2_EXP_DECL \ + pcre2_compile_context *pcre2_compile_context_copy(pcre2_compile_context *); \ +PCRE2_EXP_DECL \ + pcre2_compile_context *pcre2_compile_context_create(pcre2_general_context *);\ +PCRE2_EXP_DECL void pcre2_compile_context_free(pcre2_compile_context *); \ +PCRE2_EXP_DECL int pcre2_set_bsr(pcre2_compile_context *, uint32_t); \ +PCRE2_EXP_DECL int pcre2_set_character_tables(pcre2_compile_context *, \ + const unsigned char *); \ +PCRE2_EXP_DECL int pcre2_set_max_pattern_length(pcre2_compile_context *, \ + PCRE2_SIZE); \ +PCRE2_EXP_DECL int pcre2_set_newline(pcre2_compile_context *, uint32_t); \ +PCRE2_EXP_DECL int pcre2_set_parens_nest_limit(pcre2_compile_context *, \ + uint32_t); \ +PCRE2_EXP_DECL int pcre2_set_compile_recursion_guard(\ + pcre2_compile_context *, int (*)(uint32_t, void *), \ + void *); + +#define PCRE2_MATCH_CONTEXT_FUNCTIONS \ +PCRE2_EXP_DECL \ + pcre2_match_context *pcre2_match_context_copy(pcre2_match_context *); \ +PCRE2_EXP_DECL \ + pcre2_match_context *pcre2_match_context_create(pcre2_general_context *); \ +PCRE2_EXP_DECL void pcre2_match_context_free(pcre2_match_context *); \ +PCRE2_EXP_DECL int pcre2_set_callout(pcre2_match_context *, \ + int (*)(pcre2_callout_block *, void *), void *); \ +PCRE2_EXP_DECL int pcre2_set_match_limit(pcre2_match_context *, \ + uint32_t); \ +PCRE2_EXP_DECL int pcre2_set_offset_limit(pcre2_match_context *, \ + PCRE2_SIZE); \ +PCRE2_EXP_DECL int pcre2_set_recursion_limit(pcre2_match_context *, \ + uint32_t); \ +PCRE2_EXP_DECL int pcre2_set_recursion_memory_management( \ + pcre2_match_context *, void *(*)(PCRE2_SIZE, void *), \ + void (*)(void *, void *), void *); + + +/* Functions concerned with compiling a pattern to PCRE internal code. */ + +#define PCRE2_COMPILE_FUNCTIONS \ +PCRE2_EXP_DECL \ + pcre2_code *pcre2_compile(PCRE2_SPTR, PCRE2_SIZE, uint32_t, \ + int *, PCRE2_SIZE *, pcre2_compile_context *); \ +PCRE2_EXP_DECL void pcre2_code_free(pcre2_code *); + + +/* Functions that give information about a compiled pattern. */ + +#define PCRE2_PATTERN_INFO_FUNCTIONS \ +PCRE2_EXP_DECL int pcre2_pattern_info(const pcre2_code *, uint32_t, \ + void *); \ +PCRE2_EXP_DECL int pcre2_callout_enumerate(const pcre2_code *, \ + int (*)(pcre2_callout_enumerate_block *, void *), \ + void *); + + +/* Functions for running a match and inspecting the result. */ + +#define PCRE2_MATCH_FUNCTIONS \ +PCRE2_EXP_DECL \ + pcre2_match_data *pcre2_match_data_create(uint32_t, \ + pcre2_general_context *); \ +PCRE2_EXP_DECL \ + pcre2_match_data *pcre2_match_data_create_from_pattern(\ + const pcre2_code *, \ + pcre2_general_context *); \ +PCRE2_EXP_DECL int pcre2_dfa_match(const pcre2_code *, PCRE2_SPTR, \ + PCRE2_SIZE, PCRE2_SIZE, uint32_t, \ + pcre2_match_data *, pcre2_match_context *, int *, \ + PCRE2_SIZE); \ +PCRE2_EXP_DECL int pcre2_match(const pcre2_code *, \ + PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \ + pcre2_match_data *, pcre2_match_context *); \ +PCRE2_EXP_DECL void pcre2_match_data_free(pcre2_match_data *); \ +PCRE2_EXP_DECL PCRE2_SPTR pcre2_get_mark(pcre2_match_data *); \ +PCRE2_EXP_DECL uint32_t pcre2_get_ovector_count(pcre2_match_data *); \ +PCRE2_EXP_DECL PCRE2_SIZE *pcre2_get_ovector_pointer(pcre2_match_data *); \ +PCRE2_EXP_DECL PCRE2_SIZE pcre2_get_startchar(pcre2_match_data *); + + +/* Convenience functions for handling matched substrings. */ + +#define PCRE2_SUBSTRING_FUNCTIONS \ +PCRE2_EXP_DECL int pcre2_substring_copy_byname(pcre2_match_data *, \ + PCRE2_SPTR, PCRE2_UCHAR *, PCRE2_SIZE *); \ +PCRE2_EXP_DECL int pcre2_substring_copy_bynumber(pcre2_match_data *, \ + uint32_t, PCRE2_UCHAR *, PCRE2_SIZE *); \ +PCRE2_EXP_DECL void pcre2_substring_free(PCRE2_UCHAR *); \ +PCRE2_EXP_DECL int pcre2_substring_get_byname(pcre2_match_data *, \ + PCRE2_SPTR, PCRE2_UCHAR **, PCRE2_SIZE *); \ +PCRE2_EXP_DECL int pcre2_substring_get_bynumber(pcre2_match_data *, \ + uint32_t, PCRE2_UCHAR **, PCRE2_SIZE *); \ +PCRE2_EXP_DECL int pcre2_substring_length_byname(pcre2_match_data *, \ + PCRE2_SPTR, PCRE2_SIZE *); \ +PCRE2_EXP_DECL int pcre2_substring_length_bynumber(pcre2_match_data *, \ + uint32_t, PCRE2_SIZE *); \ +PCRE2_EXP_DECL int pcre2_substring_nametable_scan(const pcre2_code *, \ + PCRE2_SPTR, PCRE2_SPTR *, PCRE2_SPTR *); \ +PCRE2_EXP_DECL int pcre2_substring_number_from_name(\ + const pcre2_code *, PCRE2_SPTR); \ +PCRE2_EXP_DECL void pcre2_substring_list_free(PCRE2_SPTR *); \ +PCRE2_EXP_DECL int pcre2_substring_list_get(pcre2_match_data *, \ + PCRE2_UCHAR ***, PCRE2_SIZE **); + +/* Functions for serializing / deserializing compiled patterns. */ + +#define PCRE2_SERIALIZE_FUNCTIONS \ +PCRE2_EXP_DECL int32_t pcre2_serialize_encode(const pcre2_code **, \ + int32_t, uint8_t **, PCRE2_SIZE *, \ + pcre2_general_context *); \ +PCRE2_EXP_DECL int32_t pcre2_serialize_decode(pcre2_code **, int32_t, \ + const uint8_t *, pcre2_general_context *); \ +PCRE2_EXP_DECL int32_t pcre2_serialize_get_number_of_codes(const uint8_t *); \ +PCRE2_EXP_DECL void pcre2_serialize_free(uint8_t *); + + +/* Convenience function for match + substitute. */ + +#define PCRE2_SUBSTITUTE_FUNCTION \ +PCRE2_EXP_DECL int pcre2_substitute(const pcre2_code *, \ + PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \ + pcre2_match_data *, pcre2_match_context *, \ + PCRE2_SPTR, PCRE2_SIZE, PCRE2_UCHAR *, \ + PCRE2_SIZE *); + + +/* Functions for JIT processing */ + +#define PCRE2_JIT_FUNCTIONS \ +PCRE2_EXP_DECL int pcre2_jit_compile(pcre2_code *, uint32_t); \ +PCRE2_EXP_DECL int pcre2_jit_match(const pcre2_code *, \ + PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \ + pcre2_match_data *, pcre2_match_context *); \ +PCRE2_EXP_DECL void pcre2_jit_free_unused_memory(pcre2_general_context *); \ +PCRE2_EXP_DECL \ + pcre2_jit_stack *pcre2_jit_stack_create(PCRE2_SIZE, PCRE2_SIZE, \ + pcre2_general_context *); \ +PCRE2_EXP_DECL void pcre2_jit_stack_assign(pcre2_match_context *, \ + pcre2_jit_callback, void *); \ +PCRE2_EXP_DECL void pcre2_jit_stack_free(pcre2_jit_stack *); + + +/* Other miscellaneous functions. */ + +#define PCRE2_OTHER_FUNCTIONS \ +PCRE2_EXP_DECL int pcre2_get_error_message(int, PCRE2_UCHAR *, PCRE2_SIZE); \ +PCRE2_EXP_DECL \ + const uint8_t *pcre2_maketables(pcre2_general_context *); \ + + +/* Define macros that generate width-specific names from generic versions. The +three-level macro scheme is necessary to get the macros expanded when we want +them to be. First we get the width from PCRE2_LOCAL_WIDTH, which is used for +generating three versions of everything below. After that, PCRE2_SUFFIX will be +re-defined to use PCRE2_CODE_UNIT_WIDTH, for use when macros such as +pcre2_compile are called by application code. */ + +#define PCRE2_JOIN(a,b) a ## b +#define PCRE2_GLUE(a,b) PCRE2_JOIN(a,b) +#define PCRE2_SUFFIX(a) PCRE2_GLUE(a,PCRE2_LOCAL_WIDTH) + + +/* Data types */ + +#define PCRE2_UCHAR PCRE2_SUFFIX(PCRE2_UCHAR) +#define PCRE2_SPTR PCRE2_SUFFIX(PCRE2_SPTR) + +#define pcre2_code PCRE2_SUFFIX(pcre2_code_) +#define pcre2_jit_callback PCRE2_SUFFIX(pcre2_jit_callback_) +#define pcre2_jit_stack PCRE2_SUFFIX(pcre2_jit_stack_) + +#define pcre2_real_code PCRE2_SUFFIX(pcre2_real_code_) +#define pcre2_real_general_context PCRE2_SUFFIX(pcre2_real_general_context_) +#define pcre2_real_compile_context PCRE2_SUFFIX(pcre2_real_compile_context_) +#define pcre2_real_match_context PCRE2_SUFFIX(pcre2_real_match_context_) +#define pcre2_real_jit_stack PCRE2_SUFFIX(pcre2_real_jit_stack_) +#define pcre2_real_match_data PCRE2_SUFFIX(pcre2_real_match_data_) + + +/* Data blocks */ + +#define pcre2_callout_block PCRE2_SUFFIX(pcre2_callout_block_) +#define pcre2_callout_enumerate_block PCRE2_SUFFIX(pcre2_callout_enumerate_block_) +#define pcre2_general_context PCRE2_SUFFIX(pcre2_general_context_) +#define pcre2_compile_context PCRE2_SUFFIX(pcre2_compile_context_) +#define pcre2_match_context PCRE2_SUFFIX(pcre2_match_context_) +#define pcre2_match_data PCRE2_SUFFIX(pcre2_match_data_) + + +/* Functions: the complete list in alphabetical order */ + +#define pcre2_callout_enumerate PCRE2_SUFFIX(pcre2_callout_enumerate_) +#define pcre2_code_free PCRE2_SUFFIX(pcre2_code_free_) +#define pcre2_compile PCRE2_SUFFIX(pcre2_compile_) +#define pcre2_compile_context_copy PCRE2_SUFFIX(pcre2_compile_context_copy_) +#define pcre2_compile_context_create PCRE2_SUFFIX(pcre2_compile_context_create_) +#define pcre2_compile_context_free PCRE2_SUFFIX(pcre2_compile_context_free_) +#define pcre2_config PCRE2_SUFFIX(pcre2_config_) +#define pcre2_dfa_match PCRE2_SUFFIX(pcre2_dfa_match_) +#define pcre2_general_context_copy PCRE2_SUFFIX(pcre2_general_context_copy_) +#define pcre2_general_context_create PCRE2_SUFFIX(pcre2_general_context_create_) +#define pcre2_general_context_free PCRE2_SUFFIX(pcre2_general_context_free_) +#define pcre2_get_error_message PCRE2_SUFFIX(pcre2_get_error_message_) +#define pcre2_get_mark PCRE2_SUFFIX(pcre2_get_mark_) +#define pcre2_get_ovector_pointer PCRE2_SUFFIX(pcre2_get_ovector_pointer_) +#define pcre2_get_ovector_count PCRE2_SUFFIX(pcre2_get_ovector_count_) +#define pcre2_get_startchar PCRE2_SUFFIX(pcre2_get_startchar_) +#define pcre2_jit_compile PCRE2_SUFFIX(pcre2_jit_compile_) +#define pcre2_jit_match PCRE2_SUFFIX(pcre2_jit_match_) +#define pcre2_jit_free_unused_memory PCRE2_SUFFIX(pcre2_jit_free_unused_memory_) +#define pcre2_jit_stack_assign PCRE2_SUFFIX(pcre2_jit_stack_assign_) +#define pcre2_jit_stack_create PCRE2_SUFFIX(pcre2_jit_stack_create_) +#define pcre2_jit_stack_free PCRE2_SUFFIX(pcre2_jit_stack_free_) +#define pcre2_maketables PCRE2_SUFFIX(pcre2_maketables_) +#define pcre2_match PCRE2_SUFFIX(pcre2_match_) +#define pcre2_match_context_copy PCRE2_SUFFIX(pcre2_match_context_copy_) +#define pcre2_match_context_create PCRE2_SUFFIX(pcre2_match_context_create_) +#define pcre2_match_context_free PCRE2_SUFFIX(pcre2_match_context_free_) +#define pcre2_match_data_create PCRE2_SUFFIX(pcre2_match_data_create_) +#define pcre2_match_data_create_from_pattern PCRE2_SUFFIX(pcre2_match_data_create_from_pattern_) +#define pcre2_match_data_free PCRE2_SUFFIX(pcre2_match_data_free_) +#define pcre2_pattern_info PCRE2_SUFFIX(pcre2_pattern_info_) +#define pcre2_serialize_decode PCRE2_SUFFIX(pcre2_serialize_decode_) +#define pcre2_serialize_encode PCRE2_SUFFIX(pcre2_serialize_encode_) +#define pcre2_serialize_free PCRE2_SUFFIX(pcre2_serialize_free_) +#define pcre2_serialize_get_number_of_codes PCRE2_SUFFIX(pcre2_serialize_get_number_of_codes_) +#define pcre2_set_bsr PCRE2_SUFFIX(pcre2_set_bsr_) +#define pcre2_set_callout PCRE2_SUFFIX(pcre2_set_callout_) +#define pcre2_set_character_tables PCRE2_SUFFIX(pcre2_set_character_tables_) +#define pcre2_set_compile_recursion_guard PCRE2_SUFFIX(pcre2_set_compile_recursion_guard_) +#define pcre2_set_match_limit PCRE2_SUFFIX(pcre2_set_match_limit_) +#define pcre2_set_max_pattern_length PCRE2_SUFFIX(pcre2_set_max_pattern_length_) +#define pcre2_set_newline PCRE2_SUFFIX(pcre2_set_newline_) +#define pcre2_set_parens_nest_limit PCRE2_SUFFIX(pcre2_set_parens_nest_limit_) +#define pcre2_set_offset_limit PCRE2_SUFFIX(pcre2_set_offset_limit_) +#define pcre2_set_recursion_limit PCRE2_SUFFIX(pcre2_set_recursion_limit_) +#define pcre2_set_recursion_memory_management PCRE2_SUFFIX(pcre2_set_recursion_memory_management_) +#define pcre2_substitute PCRE2_SUFFIX(pcre2_substitute_) +#define pcre2_substring_copy_byname PCRE2_SUFFIX(pcre2_substring_copy_byname_) +#define pcre2_substring_copy_bynumber PCRE2_SUFFIX(pcre2_substring_copy_bynumber_) +#define pcre2_substring_free PCRE2_SUFFIX(pcre2_substring_free_) +#define pcre2_substring_get_byname PCRE2_SUFFIX(pcre2_substring_get_byname_) +#define pcre2_substring_get_bynumber PCRE2_SUFFIX(pcre2_substring_get_bynumber_) +#define pcre2_substring_length_byname PCRE2_SUFFIX(pcre2_substring_length_byname_) +#define pcre2_substring_length_bynumber PCRE2_SUFFIX(pcre2_substring_length_bynumber_) +#define pcre2_substring_list_get PCRE2_SUFFIX(pcre2_substring_list_get_) +#define pcre2_substring_list_free PCRE2_SUFFIX(pcre2_substring_list_free_) +#define pcre2_substring_nametable_scan PCRE2_SUFFIX(pcre2_substring_nametable_scan_) +#define pcre2_substring_number_from_name PCRE2_SUFFIX(pcre2_substring_number_from_name_) + + +/* Now generate all three sets of width-specific structures and function +prototypes. */ + +#define PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS \ +PCRE2_TYPES_LIST \ +PCRE2_STRUCTURE_LIST \ +PCRE2_GENERAL_INFO_FUNCTIONS \ +PCRE2_GENERAL_CONTEXT_FUNCTIONS \ +PCRE2_COMPILE_CONTEXT_FUNCTIONS \ +PCRE2_MATCH_CONTEXT_FUNCTIONS \ +PCRE2_COMPILE_FUNCTIONS \ +PCRE2_PATTERN_INFO_FUNCTIONS \ +PCRE2_MATCH_FUNCTIONS \ +PCRE2_SUBSTRING_FUNCTIONS \ +PCRE2_SERIALIZE_FUNCTIONS \ +PCRE2_SUBSTITUTE_FUNCTION \ +PCRE2_JIT_FUNCTIONS \ +PCRE2_OTHER_FUNCTIONS + +#define PCRE2_LOCAL_WIDTH 8 +PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS +#undef PCRE2_LOCAL_WIDTH + +#define PCRE2_LOCAL_WIDTH 16 +PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS +#undef PCRE2_LOCAL_WIDTH + +#define PCRE2_LOCAL_WIDTH 32 +PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS +#undef PCRE2_LOCAL_WIDTH + +/* Undefine the list macros; they are no longer needed. */ + +#undef PCRE2_TYPES_LIST +#undef PCRE2_STRUCTURE_LIST +#undef PCRE2_GENERAL_INFO_FUNCTIONS +#undef PCRE2_GENERAL_CONTEXT_FUNCTIONS +#undef PCRE2_COMPILE_CONTEXT_FUNCTIONS +#undef PCRE2_MATCH_CONTEXT_FUNCTIONS +#undef PCRE2_COMPILE_FUNCTIONS +#undef PCRE2_PATTERN_INFO_FUNCTIONS +#undef PCRE2_MATCH_FUNCTIONS +#undef PCRE2_SUBSTRING_FUNCTIONS +#undef PCRE2_SERIALIZE_FUNCTIONS +#undef PCRE2_SUBSTITUTE_FUNCTION +#undef PCRE2_JIT_FUNCTIONS +#undef PCRE2_OTHER_FUNCTIONS +#undef PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS + +/* PCRE2_CODE_UNIT_WIDTH must be defined. If it is 8, 16, or 32, redefine +PCRE2_SUFFIX to use it. If it is 0, undefine the other macros and make +PCRE2_SUFFIX a no-op. Otherwise, generate an error. */ + +#undef PCRE2_SUFFIX +#ifndef PCRE2_CODE_UNIT_WIDTH +#error PCRE2_CODE_UNIT_WIDTH must be defined before including pcre2.h. +#error Use 8, 16, or 32; or 0 for a multi-width application. +#else /* PCRE2_CODE_UNIT_WIDTH is defined */ +#if PCRE2_CODE_UNIT_WIDTH == 8 || \ + PCRE2_CODE_UNIT_WIDTH == 16 || \ + PCRE2_CODE_UNIT_WIDTH == 32 +#define PCRE2_SUFFIX(a) PCRE2_GLUE(a, PCRE2_CODE_UNIT_WIDTH) +#elif PCRE2_CODE_UNIT_WIDTH == 0 +#undef PCRE2_JOIN +#undef PCRE2_GLUE +#define PCRE2_SUFFIX(a) a +#else +#error PCRE2_CODE_UNIT_WIDTH must be 0, 8, 16, or 32. +#endif +#endif /* PCRE2_CODE_UNIT_WIDTH is defined */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* End of pcre2.h */ diff --git a/ProcessHacker/pcre/pcre2_auto_possess.c b/ProcessHacker/pcre/pcre2_auto_possess.c new file mode 100644 index 0000000..4662cf4 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_auto_possess.c @@ -0,0 +1,1287 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains functions that scan a compiled pattern and change +repeats into possessive repeats where possible. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "pcre2_internal.h" + + +/************************************************* +* Tables for auto-possessification * +*************************************************/ + +/* This table is used to check whether auto-possessification is possible +between adjacent character-type opcodes. The left-hand (repeated) opcode is +used to select the row, and the right-hand opcode is use to select the column. +A value of 1 means that auto-possessification is OK. For example, the second +value in the first row means that \D+\d can be turned into \D++\d. + +The Unicode property types (\P and \p) have to be present to fill out the table +because of what their opcode values are, but the table values should always be +zero because property types are handled separately in the code. The last four +columns apply to items that cannot be repeated, so there is no need to have +rows for them. Note that OP_DIGIT etc. are generated only when PCRE_UCP is +*not* set. When it is set, \d etc. are converted into OP_(NOT_)PROP codes. */ + +#define APTROWS (LAST_AUTOTAB_LEFT_OP - FIRST_AUTOTAB_OP + 1) +#define APTCOLS (LAST_AUTOTAB_RIGHT_OP - FIRST_AUTOTAB_OP + 1) + +static const uint8_t autoposstab[APTROWS][APTCOLS] = { +/* \D \d \S \s \W \w . .+ \C \P \p \R \H \h \V \v \X \Z \z $ $M */ + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* \D */ + { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 }, /* \d */ + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 }, /* \S */ + { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* \s */ + { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* \W */ + { 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1 }, /* \w */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* . */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* .+ */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* \C */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* \P */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* \p */ + { 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, /* \R */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, /* \H */ + { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0 }, /* \h */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, /* \V */ + { 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0 }, /* \v */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 } /* \X */ +}; + +/* This table is used to check whether auto-possessification is possible +between adjacent Unicode property opcodes (OP_PROP and OP_NOTPROP). The +left-hand (repeated) opcode is used to select the row, and the right-hand +opcode is used to select the column. The values are as follows: + + 0 Always return FALSE (never auto-possessify) + 1 Character groups are distinct (possessify if both are OP_PROP) + 2 Check character categories in the same group (general or particular) + 3 TRUE if the two opcodes are not the same (PROP vs NOTPROP) + + 4 Check left general category vs right particular category + 5 Check right general category vs left particular category + + 6 Left alphanum vs right general category + 7 Left space vs right general category + 8 Left word vs right general category + + 9 Right alphanum vs left general category + 10 Right space vs left general category + 11 Right word vs left general category + + 12 Left alphanum vs right particular category + 13 Left space vs right particular category + 14 Left word vs right particular category + + 15 Right alphanum vs left particular category + 16 Right space vs left particular category + 17 Right word vs left particular category +*/ + +static const uint8_t propposstab[PT_TABSIZE][PT_TABSIZE] = { +/* ANY LAMP GC PC SC ALNUM SPACE PXSPACE WORD CLIST UCNC */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* PT_ANY */ + { 0, 3, 0, 0, 0, 3, 1, 1, 0, 0, 0 }, /* PT_LAMP */ + { 0, 0, 2, 4, 0, 9, 10, 10, 11, 0, 0 }, /* PT_GC */ + { 0, 0, 5, 2, 0, 15, 16, 16, 17, 0, 0 }, /* PT_PC */ + { 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 }, /* PT_SC */ + { 0, 3, 6, 12, 0, 3, 1, 1, 0, 0, 0 }, /* PT_ALNUM */ + { 0, 1, 7, 13, 0, 1, 3, 3, 1, 0, 0 }, /* PT_SPACE */ + { 0, 1, 7, 13, 0, 1, 3, 3, 1, 0, 0 }, /* PT_PXSPACE */ + { 0, 0, 8, 14, 0, 0, 1, 1, 3, 0, 0 }, /* PT_WORD */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* PT_CLIST */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3 } /* PT_UCNC */ +}; + +/* This table is used to check whether auto-possessification is possible +between adjacent Unicode property opcodes (OP_PROP and OP_NOTPROP) when one +specifies a general category and the other specifies a particular category. The +row is selected by the general category and the column by the particular +category. The value is 1 if the particular category is not part of the general +category. */ + +static const uint8_t catposstab[7][30] = { +/* Cc Cf Cn Co Cs Ll Lm Lo Lt Lu Mc Me Mn Nd Nl No Pc Pd Pe Pf Pi Po Ps Sc Sk Sm So Zl Zp Zs */ + { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* C */ + { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* L */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* M */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, /* N */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1 }, /* P */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 }, /* S */ + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 } /* Z */ +}; + +/* This table is used when checking ALNUM, (PX)SPACE, SPACE, and WORD against +a general or particular category. The properties in each row are those +that apply to the character set in question. Duplication means that a little +unnecessary work is done when checking, but this keeps things much simpler +because they can all use the same code. For more details see the comment where +this table is used. + +Note: SPACE and PXSPACE used to be different because Perl excluded VT from +"space", but from Perl 5.18 it's included, so both categories are treated the +same here. */ + +static const uint8_t posspropstab[3][4] = { + { ucp_L, ucp_N, ucp_N, ucp_Nl }, /* ALNUM, 3rd and 4th values redundant */ + { ucp_Z, ucp_Z, ucp_C, ucp_Cc }, /* SPACE and PXSPACE, 2nd value redundant */ + { ucp_L, ucp_N, ucp_P, ucp_Po } /* WORD */ +}; + + + +#ifdef SUPPORT_UNICODE +/************************************************* +* Check a character and a property * +*************************************************/ + +/* This function is called by compare_opcodes() when a property item is +adjacent to a fixed character. + +Arguments: + c the character + ptype the property type + pdata the data for the type + negated TRUE if it's a negated property (\P or \p{^) + +Returns: TRUE if auto-possessifying is OK +*/ + +static BOOL +check_char_prop(uint32_t c, unsigned int ptype, unsigned int pdata, + BOOL negated) +{ +const uint32_t *p; +const ucd_record *prop = GET_UCD(c); + +switch(ptype) + { + case PT_LAMP: + return (prop->chartype == ucp_Lu || + prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt) == negated; + + case PT_GC: + return (pdata == PRIV(ucp_gentype)[prop->chartype]) == negated; + + case PT_PC: + return (pdata == prop->chartype) == negated; + + case PT_SC: + return (pdata == prop->script) == negated; + + /* These are specials */ + + case PT_ALNUM: + return (PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N) == negated; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, which + means that Perl space and POSIX space are now identical. PCRE was changed + at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + return negated; + + default: + return (PRIV(ucp_gentype)[prop->chartype] == ucp_Z) == negated; + } + break; /* Control never reaches here */ + + case PT_WORD: + return (PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE) == negated; + + case PT_CLIST: + p = PRIV(ucd_caseless_sets) + prop->caseset; + for (;;) + { + if (c < *p) return !negated; + if (c == *p++) return negated; + } + break; /* Control never reaches here */ + } + +return FALSE; +} +#endif /* SUPPORT_UNICODE */ + + + +/************************************************* +* Base opcode of repeated opcodes * +*************************************************/ + +/* Returns the base opcode for repeated single character type opcodes. If the +opcode is not a repeated character type, it returns with the original value. + +Arguments: c opcode +Returns: base opcode for the type +*/ + +static PCRE2_UCHAR +get_repeat_base(PCRE2_UCHAR c) +{ +return (c > OP_TYPEPOSUPTO)? c : + (c >= OP_TYPESTAR)? OP_TYPESTAR : + (c >= OP_NOTSTARI)? OP_NOTSTARI : + (c >= OP_NOTSTAR)? OP_NOTSTAR : + (c >= OP_STARI)? OP_STARI : + OP_STAR; +} + + +/************************************************* +* Fill the character property list * +*************************************************/ + +/* Checks whether the code points to an opcode that can take part in auto- +possessification, and if so, fills a list with its properties. + +Arguments: + code points to start of expression + utf TRUE if in UTF mode + fcc points to the case-flipping table + list points to output list + list[0] will be filled with the opcode + list[1] will be non-zero if this opcode + can match an empty character string + list[2..7] depends on the opcode + +Returns: points to the start of the next opcode if *code is accepted + NULL if *code is not accepted +*/ + +static PCRE2_SPTR +get_chr_property_list(PCRE2_SPTR code, BOOL utf, const uint8_t *fcc, + uint32_t *list) +{ +PCRE2_UCHAR c = *code; +PCRE2_UCHAR base; +PCRE2_SPTR end; +uint32_t chr; + +#ifdef SUPPORT_UNICODE +uint32_t *clist_dest; +const uint32_t *clist_src; +#else +(void)utf; /* Suppress "unused parameter" compiler warning */ +#endif + +list[0] = c; +list[1] = FALSE; +code++; + +if (c >= OP_STAR && c <= OP_TYPEPOSUPTO) + { + base = get_repeat_base(c); + c -= (base - OP_STAR); + + if (c == OP_UPTO || c == OP_MINUPTO || c == OP_EXACT || c == OP_POSUPTO) + code += IMM2_SIZE; + + list[1] = (c != OP_PLUS && c != OP_MINPLUS && c != OP_EXACT && + c != OP_POSPLUS); + + switch(base) + { + case OP_STAR: + list[0] = OP_CHAR; + break; + + case OP_STARI: + list[0] = OP_CHARI; + break; + + case OP_NOTSTAR: + list[0] = OP_NOT; + break; + + case OP_NOTSTARI: + list[0] = OP_NOTI; + break; + + case OP_TYPESTAR: + list[0] = *code; + code++; + break; + } + c = list[0]; + } + +switch(c) + { + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + case OP_ANYNL: + case OP_NOT_HSPACE: + case OP_HSPACE: + case OP_NOT_VSPACE: + case OP_VSPACE: + case OP_EXTUNI: + case OP_EODN: + case OP_EOD: + case OP_DOLL: + case OP_DOLLM: + return code; + + case OP_CHAR: + case OP_NOT: + GETCHARINCTEST(chr, code); + list[2] = chr; + list[3] = NOTACHAR; + return code; + + case OP_CHARI: + case OP_NOTI: + list[0] = (c == OP_CHARI) ? OP_CHAR : OP_NOT; + GETCHARINCTEST(chr, code); + list[2] = chr; + +#ifdef SUPPORT_UNICODE + if (chr < 128 || (chr < 256 && !utf)) + list[3] = fcc[chr]; + else + list[3] = UCD_OTHERCASE(chr); +#elif defined SUPPORT_WIDE_CHARS + list[3] = (chr < 256) ? fcc[chr] : chr; +#else + list[3] = fcc[chr]; +#endif + + /* The othercase might be the same value. */ + + if (chr == list[3]) + list[3] = NOTACHAR; + else + list[4] = NOTACHAR; + return code; + +#ifdef SUPPORT_UNICODE + case OP_PROP: + case OP_NOTPROP: + if (code[0] != PT_CLIST) + { + list[2] = code[0]; + list[3] = code[1]; + return code + 2; + } + + /* Convert only if we have enough space. */ + + clist_src = PRIV(ucd_caseless_sets) + code[1]; + clist_dest = list + 2; + code += 2; + + do { + if (clist_dest >= list + 8) + { + /* Early return if there is not enough space. This should never + happen, since all clists are shorter than 5 character now. */ + list[2] = code[0]; + list[3] = code[1]; + return code; + } + *clist_dest++ = *clist_src; + } + while(*clist_src++ != NOTACHAR); + + /* All characters are stored. The terminating NOTACHAR is copied from the + clist itself. */ + + list[0] = (c == OP_PROP) ? OP_CHAR : OP_NOT; + return code; +#endif + + case OP_NCLASS: + case OP_CLASS: +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + if (c == OP_XCLASS) + end = code + GET(code, 0) - 1; + else +#endif + end = code + 32 / sizeof(PCRE2_UCHAR); + + switch(*end) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSQUERY: + list[1] = TRUE; + end++; + break; + + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + end++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + list[1] = (GET2(end, 1) == 0); + end += 1 + 2 * IMM2_SIZE; + break; + } + list[2] = (uint32_t)(end - code); + return end; + } +return NULL; /* Opcode not accepted */ +} + + + +/************************************************* +* Scan further character sets for match * +*************************************************/ + +/* Checks whether the base and the current opcode have a common character, in +which case the base cannot be possessified. + +Arguments: + code points to the byte code + utf TRUE in UTF mode + cb compile data block + base_list the data list of the base opcode + base_end the end of the data list + rec_limit points to recursion depth counter + +Returns: TRUE if the auto-possessification is possible +*/ + +static BOOL +compare_opcodes(PCRE2_SPTR code, BOOL utf, const compile_block *cb, + const uint32_t *base_list, PCRE2_SPTR base_end, int *rec_limit) +{ +PCRE2_UCHAR c; +uint32_t list[8]; +const uint32_t *chr_ptr; +const uint32_t *ochr_ptr; +const uint32_t *list_ptr; +PCRE2_SPTR next_code; +#ifdef SUPPORT_WIDE_CHARS +PCRE2_SPTR xclass_flags; +#endif +const uint8_t *class_bitset; +const uint8_t *set1, *set2, *set_end; +uint32_t chr; +BOOL accepted, invert_bits; +BOOL entered_a_group = FALSE; + +if (--(*rec_limit) <= 0) return FALSE; /* Recursion has gone too deep */ + +/* Note: the base_list[1] contains whether the current opcode has a greedy +(represented by a non-zero value) quantifier. This is a different from +other character type lists, which store here that the character iterator +matches to an empty string (also represented by a non-zero value). */ + +for(;;) + { + /* All operations move the code pointer forward. + Therefore infinite recursions are not possible. */ + + c = *code; + + /* Skip over callouts */ + + if (c == OP_CALLOUT) + { + code += PRIV(OP_lengths)[c]; + continue; + } + + if (c == OP_CALLOUT_STR) + { + code += GET(code, 1 + 2*LINK_SIZE); + continue; + } + + if (c == OP_ALT) + { + do code += GET(code, 1); while (*code == OP_ALT); + c = *code; + } + + switch(c) + { + case OP_END: + case OP_KETRPOS: + /* TRUE only in greedy case. The non-greedy case could be replaced by + an OP_EXACT, but it is probably not worth it. (And note that OP_EXACT + uses more memory, which we cannot get at this stage.) */ + + return base_list[1] != 0; + + case OP_KET: + /* If the bracket is capturing, and referenced by an OP_RECURSE, or + it is an atomic sub-pattern (assert, once, etc.) the non-greedy case + cannot be converted to a possessive form. */ + + if (base_list[1] == 0) return FALSE; + + switch(*(code - GET(code, 1))) + { + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + case OP_ONCE: + case OP_ONCE_NC: + /* Atomic sub-patterns and assertions can always auto-possessify their + last iterator. However, if the group was entered as a result of checking + a previous iterator, this is not possible. */ + + return !entered_a_group; + } + + code += PRIV(OP_lengths)[c]; + continue; + + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRA: + case OP_CBRA: + next_code = code + GET(code, 1); + code += PRIV(OP_lengths)[c]; + + while (*next_code == OP_ALT) + { + if (!compare_opcodes(code, utf, cb, base_list, base_end, rec_limit)) + return FALSE; + code = next_code + 1 + LINK_SIZE; + next_code += GET(next_code, 1); + } + + entered_a_group = TRUE; + continue; + + case OP_BRAZERO: + case OP_BRAMINZERO: + + next_code = code + 1; + if (*next_code != OP_BRA && *next_code != OP_CBRA + && *next_code != OP_ONCE && *next_code != OP_ONCE_NC) return FALSE; + + do next_code += GET(next_code, 1); while (*next_code == OP_ALT); + + /* The bracket content will be checked by the OP_BRA/OP_CBRA case above. */ + + next_code += 1 + LINK_SIZE; + if (!compare_opcodes(next_code, utf, cb, base_list, base_end, rec_limit)) + return FALSE; + + code += PRIV(OP_lengths)[c]; + continue; + + default: + break; + } + + /* Check for a supported opcode, and load its properties. */ + + code = get_chr_property_list(code, utf, cb->fcc, list); + if (code == NULL) return FALSE; /* Unsupported */ + + /* If either opcode is a small character list, set pointers for comparing + characters from that list with another list, or with a property. */ + + if (base_list[0] == OP_CHAR) + { + chr_ptr = base_list + 2; + list_ptr = list; + } + else if (list[0] == OP_CHAR) + { + chr_ptr = list + 2; + list_ptr = base_list; + } + + /* Character bitsets can also be compared to certain opcodes. */ + + else if (base_list[0] == OP_CLASS || list[0] == OP_CLASS +#if PCRE2_CODE_UNIT_WIDTH == 8 + /* In 8 bit, non-UTF mode, OP_CLASS and OP_NCLASS are the same. */ + || (!utf && (base_list[0] == OP_NCLASS || list[0] == OP_NCLASS)) +#endif + ) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (base_list[0] == OP_CLASS || (!utf && base_list[0] == OP_NCLASS)) +#else + if (base_list[0] == OP_CLASS) +#endif + { + set1 = (uint8_t *)(base_end - base_list[2]); + list_ptr = list; + } + else + { + set1 = (uint8_t *)(code - list[2]); + list_ptr = base_list; + } + + invert_bits = FALSE; + switch(list_ptr[0]) + { + case OP_CLASS: + case OP_NCLASS: + set2 = (uint8_t *) + ((list_ptr == list ? code : base_end) - list_ptr[2]); + break; + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + xclass_flags = (list_ptr == list ? code : base_end) - list_ptr[2] + LINK_SIZE; + if ((*xclass_flags & XCL_HASPROP) != 0) return FALSE; + if ((*xclass_flags & XCL_MAP) == 0) + { + /* No bits are set for characters < 256. */ + if (list[1] == 0) return TRUE; + /* Might be an empty repeat. */ + continue; + } + set2 = (uint8_t *)(xclass_flags + 1); + break; +#endif + + case OP_NOT_DIGIT: + invert_bits = TRUE; + /* Fall through */ + case OP_DIGIT: + set2 = (uint8_t *)(cb->cbits + cbit_digit); + break; + + case OP_NOT_WHITESPACE: + invert_bits = TRUE; + /* Fall through */ + case OP_WHITESPACE: + set2 = (uint8_t *)(cb->cbits + cbit_space); + break; + + case OP_NOT_WORDCHAR: + invert_bits = TRUE; + /* Fall through */ + case OP_WORDCHAR: + set2 = (uint8_t *)(cb->cbits + cbit_word); + break; + + default: + return FALSE; + } + + /* Because the bit sets are unaligned bytes, we need to perform byte + comparison here. */ + + set_end = set1 + 32; + if (invert_bits) + { + do + { + if ((*set1++ & ~(*set2++)) != 0) return FALSE; + } + while (set1 < set_end); + } + else + { + do + { + if ((*set1++ & *set2++) != 0) return FALSE; + } + while (set1 < set_end); + } + + if (list[1] == 0) return TRUE; + /* Might be an empty repeat. */ + continue; + } + + /* Some property combinations also acceptable. Unicode property opcodes are + processed specially; the rest can be handled with a lookup table. */ + + else + { + uint32_t leftop, rightop; + + leftop = base_list[0]; + rightop = list[0]; + +#ifdef SUPPORT_UNICODE + accepted = FALSE; /* Always set in non-unicode case. */ + if (leftop == OP_PROP || leftop == OP_NOTPROP) + { + if (rightop == OP_EOD) + accepted = TRUE; + else if (rightop == OP_PROP || rightop == OP_NOTPROP) + { + int n; + const uint8_t *p; + BOOL same = leftop == rightop; + BOOL lisprop = leftop == OP_PROP; + BOOL risprop = rightop == OP_PROP; + BOOL bothprop = lisprop && risprop; + + /* There's a table that specifies how each combination is to be + processed: + 0 Always return FALSE (never auto-possessify) + 1 Character groups are distinct (possessify if both are OP_PROP) + 2 Check character categories in the same group (general or particular) + 3 Return TRUE if the two opcodes are not the same + ... see comments below + */ + + n = propposstab[base_list[2]][list[2]]; + switch(n) + { + case 0: break; + case 1: accepted = bothprop; break; + case 2: accepted = (base_list[3] == list[3]) != same; break; + case 3: accepted = !same; break; + + case 4: /* Left general category, right particular category */ + accepted = risprop && catposstab[base_list[3]][list[3]] == same; + break; + + case 5: /* Right general category, left particular category */ + accepted = lisprop && catposstab[list[3]][base_list[3]] == same; + break; + + /* This code is logically tricky. Think hard before fiddling with it. + The posspropstab table has four entries per row. Each row relates to + one of PCRE's special properties such as ALNUM or SPACE or WORD. + Only WORD actually needs all four entries, but using repeats for the + others means they can all use the same code below. + + The first two entries in each row are Unicode general categories, and + apply always, because all the characters they include are part of the + PCRE character set. The third and fourth entries are a general and a + particular category, respectively, that include one or more relevant + characters. One or the other is used, depending on whether the check + is for a general or a particular category. However, in both cases the + category contains more characters than the specials that are defined + for the property being tested against. Therefore, it cannot be used + in a NOTPROP case. + + Example: the row for WORD contains ucp_L, ucp_N, ucp_P, ucp_Po. + Underscore is covered by ucp_P or ucp_Po. */ + + case 6: /* Left alphanum vs right general category */ + case 7: /* Left space vs right general category */ + case 8: /* Left word vs right general category */ + p = posspropstab[n-6]; + accepted = risprop && lisprop == + (list[3] != p[0] && + list[3] != p[1] && + (list[3] != p[2] || !lisprop)); + break; + + case 9: /* Right alphanum vs left general category */ + case 10: /* Right space vs left general category */ + case 11: /* Right word vs left general category */ + p = posspropstab[n-9]; + accepted = lisprop && risprop == + (base_list[3] != p[0] && + base_list[3] != p[1] && + (base_list[3] != p[2] || !risprop)); + break; + + case 12: /* Left alphanum vs right particular category */ + case 13: /* Left space vs right particular category */ + case 14: /* Left word vs right particular category */ + p = posspropstab[n-12]; + accepted = risprop && lisprop == + (catposstab[p[0]][list[3]] && + catposstab[p[1]][list[3]] && + (list[3] != p[3] || !lisprop)); + break; + + case 15: /* Right alphanum vs left particular category */ + case 16: /* Right space vs left particular category */ + case 17: /* Right word vs left particular category */ + p = posspropstab[n-15]; + accepted = lisprop && risprop == + (catposstab[p[0]][base_list[3]] && + catposstab[p[1]][base_list[3]] && + (base_list[3] != p[3] || !risprop)); + break; + } + } + } + + else +#endif /* SUPPORT_UNICODE */ + + accepted = leftop >= FIRST_AUTOTAB_OP && leftop <= LAST_AUTOTAB_LEFT_OP && + rightop >= FIRST_AUTOTAB_OP && rightop <= LAST_AUTOTAB_RIGHT_OP && + autoposstab[leftop - FIRST_AUTOTAB_OP][rightop - FIRST_AUTOTAB_OP]; + + if (!accepted) return FALSE; + + if (list[1] == 0) return TRUE; + /* Might be an empty repeat. */ + continue; + } + + /* Control reaches here only if one of the items is a small character list. + All characters are checked against the other side. */ + + do + { + chr = *chr_ptr; + + switch(list_ptr[0]) + { + case OP_CHAR: + ochr_ptr = list_ptr + 2; + do + { + if (chr == *ochr_ptr) return FALSE; + ochr_ptr++; + } + while(*ochr_ptr != NOTACHAR); + break; + + case OP_NOT: + ochr_ptr = list_ptr + 2; + do + { + if (chr == *ochr_ptr) + break; + ochr_ptr++; + } + while(*ochr_ptr != NOTACHAR); + if (*ochr_ptr == NOTACHAR) return FALSE; /* Not found */ + break; + + /* Note that OP_DIGIT etc. are generated only when PCRE2_UCP is *not* + set. When it is set, \d etc. are converted into OP_(NOT_)PROP codes. */ + + case OP_DIGIT: + if (chr < 256 && (cb->ctypes[chr] & ctype_digit) != 0) return FALSE; + break; + + case OP_NOT_DIGIT: + if (chr > 255 || (cb->ctypes[chr] & ctype_digit) == 0) return FALSE; + break; + + case OP_WHITESPACE: + if (chr < 256 && (cb->ctypes[chr] & ctype_space) != 0) return FALSE; + break; + + case OP_NOT_WHITESPACE: + if (chr > 255 || (cb->ctypes[chr] & ctype_space) == 0) return FALSE; + break; + + case OP_WORDCHAR: + if (chr < 255 && (cb->ctypes[chr] & ctype_word) != 0) return FALSE; + break; + + case OP_NOT_WORDCHAR: + if (chr > 255 || (cb->ctypes[chr] & ctype_word) == 0) return FALSE; + break; + + case OP_HSPACE: + switch(chr) + { + HSPACE_CASES: return FALSE; + default: break; + } + break; + + case OP_NOT_HSPACE: + switch(chr) + { + HSPACE_CASES: break; + default: return FALSE; + } + break; + + case OP_ANYNL: + case OP_VSPACE: + switch(chr) + { + VSPACE_CASES: return FALSE; + default: break; + } + break; + + case OP_NOT_VSPACE: + switch(chr) + { + VSPACE_CASES: break; + default: return FALSE; + } + break; + + case OP_DOLL: + case OP_EODN: + switch (chr) + { + case CHAR_CR: + case CHAR_LF: + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + return FALSE; + } + break; + + case OP_EOD: /* Can always possessify before \z */ + break; + +#ifdef SUPPORT_UNICODE + case OP_PROP: + case OP_NOTPROP: + if (!check_char_prop(chr, list_ptr[2], list_ptr[3], + list_ptr[0] == OP_NOTPROP)) + return FALSE; + break; +#endif + + case OP_NCLASS: + if (chr > 255) return FALSE; + /* Fall through */ + + case OP_CLASS: + if (chr > 255) break; + class_bitset = (uint8_t *) + ((list_ptr == list ? code : base_end) - list_ptr[2]); + if ((class_bitset[chr >> 3] & (1 << (chr & 7))) != 0) return FALSE; + break; + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + if (PRIV(xclass)(chr, (list_ptr == list ? code : base_end) - + list_ptr[2] + LINK_SIZE, utf)) return FALSE; + break; +#endif + + default: + return FALSE; + } + + chr_ptr++; + } + while(*chr_ptr != NOTACHAR); + + /* At least one character must be matched from this opcode. */ + + if (list[1] == 0) return TRUE; + } + +/* Control never reaches here. There used to be a fail-save return FALSE; here, +but some compilers complain about an unreachable statement. */ +} + + + +/************************************************* +* Scan compiled regex for auto-possession * +*************************************************/ + +/* Replaces single character iterations with their possessive alternatives +if appropriate. This function modifies the compiled opcode! Hitting a +non-existant opcode may indicate a bug in PCRE2, but it can also be caused if a +bad UTF string was compiled with PCRE2_NO_UTF_CHECK. + +Arguments: + code points to start of the byte code + utf TRUE in UTF mode + cb compile data block + +Returns: 0 for success + -1 if a non-existant opcode is encountered +*/ + +int +PRIV(auto_possessify)(PCRE2_UCHAR *code, BOOL utf, const compile_block *cb) +{ +register PCRE2_UCHAR c; +PCRE2_SPTR end; +PCRE2_UCHAR *repeat_opcode; +uint32_t list[8]; +int rec_limit; + +for (;;) + { + c = *code; + + if (c > OP_TABLE_LENGTH) return -1; /* Something gone wrong */ + + if (c >= OP_STAR && c <= OP_TYPEPOSUPTO) + { + c -= get_repeat_base(c) - OP_STAR; + end = (c <= OP_MINUPTO) ? + get_chr_property_list(code, utf, cb->fcc, list) : NULL; + list[1] = c == OP_STAR || c == OP_PLUS || c == OP_QUERY || c == OP_UPTO; + + rec_limit = 1000; + if (end != NULL && compare_opcodes(end, utf, cb, list, end, &rec_limit)) + { + switch(c) + { + case OP_STAR: + *code += OP_POSSTAR - OP_STAR; + break; + + case OP_MINSTAR: + *code += OP_POSSTAR - OP_MINSTAR; + break; + + case OP_PLUS: + *code += OP_POSPLUS - OP_PLUS; + break; + + case OP_MINPLUS: + *code += OP_POSPLUS - OP_MINPLUS; + break; + + case OP_QUERY: + *code += OP_POSQUERY - OP_QUERY; + break; + + case OP_MINQUERY: + *code += OP_POSQUERY - OP_MINQUERY; + break; + + case OP_UPTO: + *code += OP_POSUPTO - OP_UPTO; + break; + + case OP_MINUPTO: + *code += OP_POSUPTO - OP_MINUPTO; + break; + } + } + c = *code; + } + else if (c == OP_CLASS || c == OP_NCLASS || c == OP_XCLASS) + { +#ifdef SUPPORT_WIDE_CHARS + if (c == OP_XCLASS) + repeat_opcode = code + GET(code, 1); + else +#endif + repeat_opcode = code + 1 + (32 / sizeof(PCRE2_UCHAR)); + + c = *repeat_opcode; + if (c >= OP_CRSTAR && c <= OP_CRMINRANGE) + { + /* end must not be NULL. */ + end = get_chr_property_list(code, utf, cb->fcc, list); + + list[1] = (c & 1) == 0; + + rec_limit = 1000; + if (compare_opcodes(end, utf, cb, list, end, &rec_limit)) + { + switch (c) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + *repeat_opcode = OP_CRPOSSTAR; + break; + + case OP_CRPLUS: + case OP_CRMINPLUS: + *repeat_opcode = OP_CRPOSPLUS; + break; + + case OP_CRQUERY: + case OP_CRMINQUERY: + *repeat_opcode = OP_CRPOSQUERY; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + *repeat_opcode = OP_CRPOSRANGE; + break; + } + } + } + c = *code; + } + + switch(c) + { + case OP_END: + return 0; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSUPTO: + if (code[1 + IMM2_SIZE] == OP_PROP || code[1 + IMM2_SIZE] == OP_NOTPROP) + code += 2; + break; + + case OP_CALLOUT_STR: + code += GET(code, 1 + 2*LINK_SIZE); + break; + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + code += GET(code, 1); + break; +#endif + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + code += code[1]; + break; + } + + /* Add in the fixed length from the table */ + + code += PRIV(OP_lengths)[c]; + + /* In UTF-8 and UTF-16 modes, opcodes that are followed by a character may be + followed by a multi-byte character. The length in the table is a minimum, so + we have to arrange to skip the extra code units. */ + +#ifdef MAYBE_UTF_MULTI + if (utf) switch(c) + { + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_UPTOI: + case OP_MINUPTOI: + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + if (HAS_EXTRALEN(code[-1])) code += GET_EXTRALEN(code[-1]); + break; + } +#else + (void)(utf); /* Keep compiler happy by referencing function argument */ +#endif /* SUPPORT_WIDE_CHARS */ + } +} + +/* End of pcre2_auto_possess.c */ diff --git a/ProcessHacker/pcre/pcre2_chartables.c b/ProcessHacker/pcre/pcre2_chartables.c new file mode 100644 index 0000000..23578d8 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_chartables.c @@ -0,0 +1,199 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* This file contains character tables that are used when no external tables +are passed to PCRE2 by the application that calls it. The tables are used only +for characters whose code values are less than 256. + +This is a default version of the tables that assumes ASCII encoding. A program +called dftables (which is distributed with PCRE2) can be used to build +alternative versions of this file. This is necessary if you are running in an +EBCDIC environment, or if you want to default to a different encoding, for +example ISO-8859-1. When dftables is run, it creates these tables in the +current locale. If PCRE2 is configured with --enable-rebuild-chartables, this +happens automatically. + +The following #includes are present because without them gcc 4.x may remove the +array definition from the final binary if PCRE2 is built into a static library +and dead code stripping is activated. This leads to link errors. Pulling in the +header ensures that the array gets flagged as "someone outside this compilation +unit might reference this" and so it will always be supplied to the linker. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + +const uint8_t PRIV(default_tables)[] = { + +/* This table is a lower casing table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table is a case flipping table. */ + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 97, 98, 99,100,101,102,103, + 104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119, + 120,121,122, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90,123,124,125,126,127, + 128,129,130,131,132,133,134,135, + 136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151, + 152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167, + 168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183, + 184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199, + 200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215, + 216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231, + 232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247, + 248,249,250,251,252,253,254,255, + +/* This table contains bit maps for various character classes. Each map is 32 +bytes long and the bits run from the least significant end of each byte. The +classes that have their own maps are: space, xdigit, digit, upper, lower, word, +graph, print, punct, and cntrl. Other classes are built from combinations. */ + + 0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03, + 0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc, + 0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + +/* This table identifies various classes of character by individual bits: + 0x01 white space character + 0x02 letter + 0x04 decimal digit + 0x08 hexadecimal digit + 0x10 alphanumeric or '_' + 0x80 regular expression metacharacter or binary zero +*/ + + 0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */ + 0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00, /* 8- 15 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */ + 0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */ + 0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */ + 0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */ + 0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x80,0x10, /* X - _ */ + 0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */ + 0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */ + 0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */ + +/* End of pcre2_chartables.c */ diff --git a/ProcessHacker/pcre/pcre2_compile.c b/ProcessHacker/pcre/pcre2_compile.c new file mode 100644 index 0000000..24475fb --- /dev/null +++ b/ProcessHacker/pcre/pcre2_compile.c @@ -0,0 +1,9015 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4267) +#pragma warning(disable : 4244) +#pragma warning(disable : 4146) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK cb /* Block containing newline information */ +#define PSSTART start_pattern /* Field containing processed string start */ +#define PSEND end_pattern /* Field containing processed string end */ + +#include "pcre2_internal.h" + +/* In rare error cases debugging might require calling pcre2_printint(). */ + +#if 0 +#ifdef EBCDIC +#define PRINTABLE(c) ((c) >= 64 && (c) < 255) +#else +#define PRINTABLE(c) ((c) >= 32 && (c) < 127) +#endif +#include "pcre2_printint.c" +#define CALL_PRINTINT +#endif + +/* There are a few things that vary with different code unit sizes. Handle them +by defining macros in order to minimize #if usage. */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define STRING_UTFn_RIGHTPAR STRING_UTF8_RIGHTPAR, 5 +#define XDIGIT(c) xdigitab[c] + +#else /* Either 16-bit or 32-bit */ +#define XDIGIT(c) (MAX_255(c)? xdigitab[c] : 0xff) + +#if PCRE2_CODE_UNIT_WIDTH == 16 +#define STRING_UTFn_RIGHTPAR STRING_UTF16_RIGHTPAR, 6 + +#else /* 32-bit */ +#define STRING_UTFn_RIGHTPAR STRING_UTF32_RIGHTPAR, 6 +#endif +#endif + +/* Function definitions to allow mutual recursion */ + +static int + add_list_to_class(uint8_t *, PCRE2_UCHAR **, uint32_t, compile_block *, + const uint32_t *, unsigned int); + +static BOOL + compile_regex(uint32_t, PCRE2_UCHAR **, PCRE2_SPTR *, int *, BOOL, BOOL, + uint32_t, int, uint32_t *, int32_t *, uint32_t *, int32_t *, + branch_chain *, compile_block *, size_t *); + + + +/************************************************* +* Code parameters and static tables * +*************************************************/ + +/* This value specifies the size of stack workspace, which is used in different +ways in the different pattern scans. The group-identifying pre-scan uses it to +handle nesting, and needs it to be 16-bit aligned. + +During the first compiling phase, when determining how much memory is required, +the regex is partly compiled into this space, but the compiled parts are +discarded as soon as they can be, so that hopefully there will never be an +overrun. The code does, however, check for an overrun, which can occur for +pathological patterns. The size of the workspace depends on LINK_SIZE because +the length of compiled items varies with this. + +In the real compile phase, the workspace is used for remembering data about +numbered groups, provided there are not too many of them (if there are, extra +memory is acquired). For this phase the memory must be 32-bit aligned. Having +defined the size in code units, we set up C32_WORK_SIZE as the number of +elements in the 32-bit vector. */ + +#define COMPILE_WORK_SIZE (2048*LINK_SIZE) /* Size in code units */ + +#define C32_WORK_SIZE \ + ((COMPILE_WORK_SIZE * sizeof(PCRE2_UCHAR))/sizeof(uint32_t)) + +/* The overrun tests check for a slightly smaller size so that they detect the +overrun before it actually does run off the end of the data block. */ + +#define WORK_SIZE_SAFETY_MARGIN (100) + +/* This value determines the size of the initial vector that is used for +remembering named groups during the pre-compile. It is allocated on the stack, +but if it is too small, it is expanded, in a similar way to the workspace. The +value is the number of slots in the list. */ + +#define NAMED_GROUP_LIST_SIZE 20 + +/* The original PCRE required patterns to be zero-terminated, and it simplifies +the compiling code if it is guaranteed that there is a zero code unit at the +end of the pattern, because this means that tests for coding sequences such as +(*SKIP) or even just (?<= can check a sequence of code units without having to +keep checking for the end of the pattern. The new PCRE2 API allows zero code +units within patterns if a positive length is given, but in order to keep most +of the compiling code as it was, we copy such patterns and add a zero on the +end. This value determines the size of space on the stack that is used if the +pattern fits; if not, heap memory is used. */ + +#define COPIED_PATTERN_SIZE 1024 + +/* Maximum length value to check against when making sure that the variable +that holds the compiled pattern length does not overflow. We make it a bit less +than INT_MAX to allow for adding in group terminating bytes, so that we don't +have to check them every time. */ + +#define OFLOW_MAX (INT_MAX - 20) + +/* Macro for setting individual bits in class bitmaps. */ + +#define SETBIT(a,b) a[(b)/8] |= (1 << ((b)&7)) + +/* Private flags added to firstcu and reqcu. */ + +#define REQ_CASELESS (1 << 0) /* Indicates caselessness */ +#define REQ_VARY (1 << 1) /* reqcu followed non-literal item */ +/* Negative values for the firstcu and reqcu flags */ +#define REQ_UNSET (-2) /* Not yet found anything */ +#define REQ_NONE (-1) /* Found not fixed char */ + +/* These flags are used in the groupinfo vector. */ + +#define GI_SET_COULD_BE_EMPTY 0x80000000u +#define GI_COULD_BE_EMPTY 0x40000000u +#define GI_NOT_FIXED_LENGTH 0x20000000u +#define GI_SET_FIXED_LENGTH 0x10000000u +#define GI_FIXED_LENGTH_MASK 0x0000ffffu + +/* This bit (which is greater than any UTF value) is used to indicate that a +variable contains a number of code units instead of an actual code point. */ + +#define UTF_LENGTH 0x10000000l + +/* This simple test for a decimal digit works for both ASCII/Unicode and EBCDIC +and is fast (a good compiler can turn it into a subtraction and unsigned +comparison). */ + +#define IS_DIGIT(x) ((x) >= CHAR_0 && (x) <= CHAR_9) + +/* Table to identify hex digits. The tables in chartables are dependent on the +locale, and may mark arbitrary characters as digits. We want to recognize only +0-9, a-z, and A-Z as hex digits, which is why we have a private table here. It +costs 256 bytes, but it is a lot faster than doing character value tests (at +least in some simple cases I timed), and in some applications one wants PCRE to +compile efficiently as well as match efficiently. The value in the table is +the binary hex digit value, or 0xff for non-hex digits. */ + +/* This is the "normal" case, for ASCII systems, and EBCDIC systems running in +UTF-8 mode. */ + +#ifndef EBCDIC +static const uint8_t xdigitab[] = + { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 0- 7 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 8- 15 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 16- 23 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 24- 31 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* - ' */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* ( - / */ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, /* 0 - 7 */ + 0x08,0x09,0xff,0xff,0xff,0xff,0xff,0xff, /* 8 - ? */ + 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0xff, /* @ - G */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* H - O */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* P - W */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* X - _ */ + 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0xff, /* ` - g */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* h - o */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* p - w */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* x -127 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 128-135 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 136-143 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 144-151 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 152-159 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 160-167 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 168-175 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 176-183 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 184-191 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 192-199 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 2ff-207 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 208-215 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 216-223 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 224-231 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 232-239 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 240-247 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};/* 248-255 */ + +#else + +/* This is the "abnormal" case, for EBCDIC systems not running in UTF-8 mode. */ + +static const uint8_t xdigitab[] = + { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 0- 7 0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 8- 15 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 16- 23 10 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 24- 31 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 32- 39 20 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 40- 47 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 48- 55 30 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 56- 63 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* - 71 40 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 72- | */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* & - 87 50 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 88- 95 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* - -103 60 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 104- ? */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 112-119 70 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 120- " */ + 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0xff, /* 128- g 80 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* h -143 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 144- p 90 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* q -159 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 160- x A0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* y -175 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* ^ -183 B0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* 184-191 */ + 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0xff, /* { - G C0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* H -207 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* } - P D0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* Q -223 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* \ - X E0 */ + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, /* Y -239 */ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, /* 0 - 7 F0 */ + 0x08,0x09,0xff,0xff,0xff,0xff,0xff,0xff};/* 8 -255 */ +#endif /* EBCDIC */ + + +/* Table for handling alphanumeric escaped characters. Positive returns are +simple data values; negative values are for special things like \d and so on. +Zero means further processing is needed (for things like \x), or the escape is +invalid. */ + +/* This is the "normal" table for ASCII systems or for EBCDIC systems running +in UTF-8 mode. It runs from '0' to 'z'. */ + +#ifndef EBCDIC +#define ESCAPES_FIRST CHAR_0 +#define ESCAPES_LAST CHAR_z +#define UPPER_CASE(c) (c-32) + +static const short int escapes[] = { + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + CHAR_COLON, CHAR_SEMICOLON, + CHAR_LESS_THAN_SIGN, CHAR_EQUALS_SIGN, + CHAR_GREATER_THAN_SIGN, CHAR_QUESTION_MARK, + CHAR_COMMERCIAL_AT, -ESC_A, + -ESC_B, -ESC_C, + -ESC_D, -ESC_E, + 0, -ESC_G, + -ESC_H, 0, + 0, -ESC_K, + 0, 0, + -ESC_N, 0, + -ESC_P, -ESC_Q, + -ESC_R, -ESC_S, + 0, 0, + -ESC_V, -ESC_W, + -ESC_X, 0, + -ESC_Z, CHAR_LEFT_SQUARE_BRACKET, + CHAR_BACKSLASH, CHAR_RIGHT_SQUARE_BRACKET, + CHAR_CIRCUMFLEX_ACCENT, CHAR_UNDERSCORE, + CHAR_GRAVE_ACCENT, ESC_a, + -ESC_b, 0, + -ESC_d, ESC_e, + ESC_f, 0, + -ESC_h, 0, + 0, -ESC_k, + 0, 0, + ESC_n, 0, + -ESC_p, 0, + ESC_r, -ESC_s, + ESC_tee, 0, + -ESC_v, -ESC_w, + 0, 0, + -ESC_z +}; + +#else + +/* This is the "abnormal" table for EBCDIC systems without UTF-8 support. +It runs from 'a' to '9'. For some minimal testing of EBCDIC features, the code +is sometimes compiled on an ASCII system. In this case, we must not use CHAR_a +because it is defined as 'a', which of course picks up the ASCII value. */ + +#if 'a' == 0x81 /* Check for a real EBCDIC environment */ +#define ESCAPES_FIRST CHAR_a +#define ESCAPES_LAST CHAR_9 +#define UPPER_CASE(c) (c+64) +#else /* Testing in an ASCII environment */ +#define ESCAPES_FIRST ((unsigned char)'\x81') /* EBCDIC 'a' */ +#define ESCAPES_LAST ((unsigned char)'\xf9') /* EBCDIC '9' */ +#define UPPER_CASE(c) (c-32) +#endif + +static const short int escapes[] = { +/* 80 */ ESC_a, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0, +/* 88 */-ESC_h, 0, 0, '{', 0, 0, 0, 0, +/* 90 */ 0, 0, -ESC_k, 0, 0, ESC_n, 0, -ESC_p, +/* 98 */ 0, ESC_r, 0, '}', 0, 0, 0, 0, +/* A0 */ 0, '~', -ESC_s, ESC_tee, 0,-ESC_v, -ESC_w, 0, +/* A8 */ 0,-ESC_z, 0, 0, 0, '[', 0, 0, +/* B0 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* B8 */ 0, 0, 0, 0, 0, ']', '=', '-', +/* C0 */ '{',-ESC_A, -ESC_B, -ESC_C, -ESC_D,-ESC_E, 0, -ESC_G, +/* C8 */-ESC_H, 0, 0, 0, 0, 0, 0, 0, +/* D0 */ '}', 0, -ESC_K, 0, 0,-ESC_N, 0, -ESC_P, +/* D8 */-ESC_Q,-ESC_R, 0, 0, 0, 0, 0, 0, +/* E0 */ '\\', 0, -ESC_S, 0, 0,-ESC_V, -ESC_W, -ESC_X, +/* E8 */ 0,-ESC_Z, 0, 0, 0, 0, 0, 0, +/* F0 */ 0, 0, 0, 0, 0, 0, 0, 0, +/* F8 */ 0, 0 +}; + +/* We also need a table of characters that may follow \c in an EBCDIC +environment for characters 0-31. */ + +static unsigned char ebcdic_escape_c[] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; + +#endif /* EBCDIC */ + + +/* Table of special "verbs" like (*PRUNE). This is a short table, so it is +searched linearly. Put all the names into a single string, in order to reduce +the number of relocations when a shared library is dynamically linked. The +string is built from string macros so that it works in UTF-8 mode on EBCDIC +platforms. */ + +typedef struct verbitem { + int len; /* Length of verb name */ + int op; /* Op when no arg, or -1 if arg mandatory */ + int op_arg; /* Op when arg present, or -1 if not allowed */ +} verbitem; + +static const char verbnames[] = + "\0" /* Empty name is a shorthand for MARK */ + STRING_MARK0 + STRING_ACCEPT0 + STRING_COMMIT0 + STRING_F0 + STRING_FAIL0 + STRING_PRUNE0 + STRING_SKIP0 + STRING_THEN; + +static const verbitem verbs[] = { + { 0, -1, OP_MARK }, + { 4, -1, OP_MARK }, + { 6, OP_ACCEPT, -1 }, + { 6, OP_COMMIT, -1 }, + { 1, OP_FAIL, -1 }, + { 4, OP_FAIL, -1 }, + { 5, OP_PRUNE, OP_PRUNE_ARG }, + { 4, OP_SKIP, OP_SKIP_ARG }, + { 4, OP_THEN, OP_THEN_ARG } +}; + +static const int verbcount = sizeof(verbs)/sizeof(verbitem); + + +/* Substitutes for [[:<:]] and [[:>:]], which mean start and end of word in +another regex library. */ + +static const PCRE2_UCHAR sub_start_of_word[] = { + CHAR_BACKSLASH, CHAR_b, CHAR_LEFT_PARENTHESIS, CHAR_QUESTION_MARK, + CHAR_EQUALS_SIGN, CHAR_BACKSLASH, CHAR_w, CHAR_RIGHT_PARENTHESIS, '\0' }; + +static const PCRE2_UCHAR sub_end_of_word[] = { + CHAR_BACKSLASH, CHAR_b, CHAR_LEFT_PARENTHESIS, CHAR_QUESTION_MARK, + CHAR_LESS_THAN_SIGN, CHAR_EQUALS_SIGN, CHAR_BACKSLASH, CHAR_w, + CHAR_RIGHT_PARENTHESIS, '\0' }; + + +/* Tables of names of POSIX character classes and their lengths. The names are +now all in a single string, to reduce the number of relocations when a shared +library is dynamically loaded. The list of lengths is terminated by a zero +length entry. The first three must be alpha, lower, upper, as this is assumed +for handling case independence. The indices for graph, print, and punct are +needed, so identify them. */ + +static const char posix_names[] = + STRING_alpha0 STRING_lower0 STRING_upper0 STRING_alnum0 + STRING_ascii0 STRING_blank0 STRING_cntrl0 STRING_digit0 + STRING_graph0 STRING_print0 STRING_punct0 STRING_space0 + STRING_word0 STRING_xdigit; + +static const uint8_t posix_name_lengths[] = { + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 6, 0 }; + +#define PC_GRAPH 8 +#define PC_PRINT 9 +#define PC_PUNCT 10 + + +/* Table of class bit maps for each POSIX class. Each class is formed from a +base map, with an optional addition or removal of another map. Then, for some +classes, there is some additional tweaking: for [:blank:] the vertical space +characters are removed, and for [:alpha:] and [:alnum:] the underscore +character is removed. The triples in the table consist of the base map offset, +second map offset or -1 if no second map, and a non-negative value for map +addition or a negative value for map subtraction (if there are two maps). The +absolute value of the third field has these meanings: 0 => no tweaking, 1 => +remove vertical space characters, 2 => remove underscore. */ + +static const int posix_class_maps[] = { + cbit_word, cbit_digit, -2, /* alpha */ + cbit_lower, -1, 0, /* lower */ + cbit_upper, -1, 0, /* upper */ + cbit_word, -1, 2, /* alnum - word without underscore */ + cbit_print, cbit_cntrl, 0, /* ascii */ + cbit_space, -1, 1, /* blank - a GNU extension */ + cbit_cntrl, -1, 0, /* cntrl */ + cbit_digit, -1, 0, /* digit */ + cbit_graph, -1, 0, /* graph */ + cbit_print, -1, 0, /* print */ + cbit_punct, -1, 0, /* punct */ + cbit_space, -1, 0, /* space */ + cbit_word, -1, 0, /* word - a Perl extension */ + cbit_xdigit,-1, 0 /* xdigit */ +}; + +/* Table of substitutes for \d etc when PCRE2_UCP is set. They are replaced by +Unicode property escapes. */ + +#ifdef SUPPORT_UNICODE +static const PCRE2_UCHAR string_PNd[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_N, CHAR_d, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pNd[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_N, CHAR_d, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PXsp[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_s, CHAR_p, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pXsp[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_s, CHAR_p, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PXwd[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_w, CHAR_d, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pXwd[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_w, CHAR_d, CHAR_RIGHT_CURLY_BRACKET, '\0' }; + +static PCRE2_SPTR substitutes[] = { + string_PNd, /* \D */ + string_pNd, /* \d */ + string_PXsp, /* \S */ /* Xsp is Perl space, but from 8.34, Perl */ + string_pXsp, /* \s */ /* space and POSIX space are the same. */ + string_PXwd, /* \W */ + string_pXwd /* \w */ +}; + +/* The POSIX class substitutes must be in the order of the POSIX class names, +defined above, and there are both positive and negative cases. NULL means no +general substitute of a Unicode property escape (\p or \P). However, for some +POSIX classes (e.g. graph, print, punct) a special property code is compiled +directly. */ + +static const PCRE2_UCHAR string_pCc[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_C, CHAR_c, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pL[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pLl[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_l, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pLu[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_u, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_pXan[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_a, CHAR_n, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_h[] = { + CHAR_BACKSLASH, CHAR_h, '\0' }; +static const PCRE2_UCHAR string_pXps[] = { + CHAR_BACKSLASH, CHAR_p, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_p, CHAR_s, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PCc[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_C, CHAR_c, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PL[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PLl[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_l, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PLu[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_L, CHAR_u, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_PXan[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_a, CHAR_n, CHAR_RIGHT_CURLY_BRACKET, '\0' }; +static const PCRE2_UCHAR string_H[] = { + CHAR_BACKSLASH, CHAR_H, '\0' }; +static const PCRE2_UCHAR string_PXps[] = { + CHAR_BACKSLASH, CHAR_P, CHAR_LEFT_CURLY_BRACKET, + CHAR_X, CHAR_p, CHAR_s, CHAR_RIGHT_CURLY_BRACKET, '\0' }; + +static PCRE2_SPTR posix_substitutes[] = { + string_pL, /* alpha */ + string_pLl, /* lower */ + string_pLu, /* upper */ + string_pXan, /* alnum */ + NULL, /* ascii */ + string_h, /* blank */ + string_pCc, /* cntrl */ + string_pNd, /* digit */ + NULL, /* graph */ + NULL, /* print */ + NULL, /* punct */ + string_pXps, /* space */ /* Xps is POSIX space, but from 8.34 */ + string_pXwd, /* word */ /* Perl and POSIX space are the same */ + NULL, /* xdigit */ + /* Negated cases */ + string_PL, /* ^alpha */ + string_PLl, /* ^lower */ + string_PLu, /* ^upper */ + string_PXan, /* ^alnum */ + NULL, /* ^ascii */ + string_H, /* ^blank */ + string_PCc, /* ^cntrl */ + string_PNd, /* ^digit */ + NULL, /* ^graph */ + NULL, /* ^print */ + NULL, /* ^punct */ + string_PXps, /* ^space */ /* Xps is POSIX space, but from 8.34 */ + string_PXwd, /* ^word */ /* Perl and POSIX space are the same */ + NULL /* ^xdigit */ +}; +#define POSIX_SUBSIZE (sizeof(posix_substitutes) / sizeof(PCRE2_UCHAR *)) +#endif /* SUPPORT_UNICODE */ + +/* Masks for checking option settings. */ + +#define PUBLIC_COMPILE_OPTIONS \ + (PCRE2_ANCHORED|PCRE2_ALLOW_EMPTY_CLASS|PCRE2_ALT_BSUX|PCRE2_ALT_CIRCUMFLEX| \ + PCRE2_ALT_VERBNAMES|PCRE2_AUTO_CALLOUT|PCRE2_CASELESS|PCRE2_DOLLAR_ENDONLY| \ + PCRE2_DOTALL|PCRE2_DUPNAMES|PCRE2_EXTENDED|PCRE2_FIRSTLINE| \ + PCRE2_MATCH_UNSET_BACKREF|PCRE2_MULTILINE|PCRE2_NEVER_BACKSLASH_C| \ + PCRE2_NEVER_UCP|PCRE2_NEVER_UTF|PCRE2_NO_AUTO_CAPTURE| \ + PCRE2_NO_AUTO_POSSESS|PCRE2_NO_DOTSTAR_ANCHOR|PCRE2_NO_START_OPTIMIZE| \ + PCRE2_NO_UTF_CHECK|PCRE2_UCP|PCRE2_UNGREEDY|PCRE2_USE_OFFSET_LIMIT| \ + PCRE2_UTF) + +/* Compile time error code numbers. They are given names so that they can more +easily be tracked. When a new number is added, the tables called eint1 and +eint2 in pcre2posix.c may need to be updated, and a new error text must be +added to compile_error_texts in pcre2_error.c. */ + +enum { ERR0 = COMPILE_ERROR_BASE, + ERR1, ERR2, ERR3, ERR4, ERR5, ERR6, ERR7, ERR8, ERR9, ERR10, + ERR11, ERR12, ERR13, ERR14, ERR15, ERR16, ERR17, ERR18, ERR19, ERR20, + ERR21, ERR22, ERR23, ERR24, ERR25, ERR26, ERR27, ERR28, ERR29, ERR30, + ERR31, ERR32, ERR33, ERR34, ERR35, ERR36, ERR37, ERR38, ERR39, ERR40, + ERR41, ERR42, ERR43, ERR44, ERR45, ERR46, ERR47, ERR48, ERR49, ERR50, + ERR51, ERR52, ERR53, ERR54, ERR55, ERR56, ERR57, ERR58, ERR59, ERR60, + ERR61, ERR62, ERR63, ERR64, ERR65, ERR66, ERR67, ERR68, ERR69, ERR70, + ERR71, ERR72, ERR73, ERR74, ERR75, ERR76, ERR77, ERR78, ERR79, ERR80, + ERR81, ERR82, ERR83, ERR84, ERR85, ERR86, ERR87, ERR88 }; + +/* Error codes that correspond to negative error codes returned by +find_fixedlength(). */ + +static int fixed_length_errors[] = + { + ERR0, /* Not an error */ + ERR0, /* Not an error; -1 is used for "process later" */ + ERR25, /* Lookbehind is not fixed length */ + ERR36, /* \C in lookbehind is not allowed */ + ERR87, /* Lookbehind is too long */ + ERR86, /* Pattern too complicated */ + ERR70 /* Internal error: unknown opcode encountered */ + }; + +/* This is a table of start-of-pattern options such as (*UTF) and settings such +as (*LIMIT_MATCH=nnnn) and (*CRLF). For completeness and backward +compatibility, (*UTFn) is supported in the relevant libraries, but (*UTF) is +generic and always supported. */ + +enum { PSO_OPT, /* Value is an option bit */ + PSO_FLG, /* Value is a flag bit */ + PSO_NL, /* Value is a newline type */ + PSO_BSR, /* Value is a \R type */ + PSO_LIMM, /* Read integer value for match limit */ + PSO_LIMR }; /* Read integer value for recursion limit */ + +typedef struct pso { + const uint8_t *name; + uint16_t length; + uint16_t type; + uint32_t value; +} pso; + +/* NB: STRING_UTFn_RIGHTPAR contains the length as well */ + +static pso pso_list[] = { + { (uint8_t *)STRING_UTFn_RIGHTPAR, PSO_OPT, PCRE2_UTF }, + { (uint8_t *)STRING_UTF_RIGHTPAR, 4, PSO_OPT, PCRE2_UTF }, + { (uint8_t *)STRING_UCP_RIGHTPAR, 4, PSO_OPT, PCRE2_UCP }, + { (uint8_t *)STRING_NOTEMPTY_RIGHTPAR, 9, PSO_FLG, PCRE2_NOTEMPTY_SET }, + { (uint8_t *)STRING_NOTEMPTY_ATSTART_RIGHTPAR, 17, PSO_FLG, PCRE2_NE_ATST_SET }, + { (uint8_t *)STRING_NO_AUTO_POSSESS_RIGHTPAR, 16, PSO_OPT, PCRE2_NO_AUTO_POSSESS }, + { (uint8_t *)STRING_NO_DOTSTAR_ANCHOR_RIGHTPAR, 18, PSO_OPT, PCRE2_NO_DOTSTAR_ANCHOR }, + { (uint8_t *)STRING_NO_JIT_RIGHTPAR, 7, PSO_FLG, PCRE2_NOJIT }, + { (uint8_t *)STRING_NO_START_OPT_RIGHTPAR, 13, PSO_OPT, PCRE2_NO_START_OPTIMIZE }, + { (uint8_t *)STRING_LIMIT_MATCH_EQ, 12, PSO_LIMM, 0 }, + { (uint8_t *)STRING_LIMIT_RECURSION_EQ, 16, PSO_LIMR, 0 }, + { (uint8_t *)STRING_CR_RIGHTPAR, 3, PSO_NL, PCRE2_NEWLINE_CR }, + { (uint8_t *)STRING_LF_RIGHTPAR, 3, PSO_NL, PCRE2_NEWLINE_LF }, + { (uint8_t *)STRING_CRLF_RIGHTPAR, 5, PSO_NL, PCRE2_NEWLINE_CRLF }, + { (uint8_t *)STRING_ANY_RIGHTPAR, 4, PSO_NL, PCRE2_NEWLINE_ANY }, + { (uint8_t *)STRING_ANYCRLF_RIGHTPAR, 8, PSO_NL, PCRE2_NEWLINE_ANYCRLF }, + { (uint8_t *)STRING_BSR_ANYCRLF_RIGHTPAR, 12, PSO_BSR, PCRE2_BSR_ANYCRLF }, + { (uint8_t *)STRING_BSR_UNICODE_RIGHTPAR, 12, PSO_BSR, PCRE2_BSR_UNICODE } +}; + +/* This table is used when converting repeating opcodes into possessified +versions as a result of an explicit possessive quantifier such as ++. A zero +value means there is no possessified version - in those cases the item in +question must be wrapped in ONCE brackets. The table is truncated at OP_CALLOUT +because all relevant opcodes are less than that. */ + +static const uint8_t opcode_possessify[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - 15 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16 - 31 */ + + 0, /* NOTI */ + OP_POSSTAR, 0, /* STAR, MINSTAR */ + OP_POSPLUS, 0, /* PLUS, MINPLUS */ + OP_POSQUERY, 0, /* QUERY, MINQUERY */ + OP_POSUPTO, 0, /* UPTO, MINUPTO */ + 0, /* EXACT */ + 0, 0, 0, 0, /* POS{STAR,PLUS,QUERY,UPTO} */ + + OP_POSSTARI, 0, /* STARI, MINSTARI */ + OP_POSPLUSI, 0, /* PLUSI, MINPLUSI */ + OP_POSQUERYI, 0, /* QUERYI, MINQUERYI */ + OP_POSUPTOI, 0, /* UPTOI, MINUPTOI */ + 0, /* EXACTI */ + 0, 0, 0, 0, /* POS{STARI,PLUSI,QUERYI,UPTOI} */ + + OP_NOTPOSSTAR, 0, /* NOTSTAR, NOTMINSTAR */ + OP_NOTPOSPLUS, 0, /* NOTPLUS, NOTMINPLUS */ + OP_NOTPOSQUERY, 0, /* NOTQUERY, NOTMINQUERY */ + OP_NOTPOSUPTO, 0, /* NOTUPTO, NOTMINUPTO */ + 0, /* NOTEXACT */ + 0, 0, 0, 0, /* NOTPOS{STAR,PLUS,QUERY,UPTO} */ + + OP_NOTPOSSTARI, 0, /* NOTSTARI, NOTMINSTARI */ + OP_NOTPOSPLUSI, 0, /* NOTPLUSI, NOTMINPLUSI */ + OP_NOTPOSQUERYI, 0, /* NOTQUERYI, NOTMINQUERYI */ + OP_NOTPOSUPTOI, 0, /* NOTUPTOI, NOTMINUPTOI */ + 0, /* NOTEXACTI */ + 0, 0, 0, 0, /* NOTPOS{STARI,PLUSI,QUERYI,UPTOI} */ + + OP_TYPEPOSSTAR, 0, /* TYPESTAR, TYPEMINSTAR */ + OP_TYPEPOSPLUS, 0, /* TYPEPLUS, TYPEMINPLUS */ + OP_TYPEPOSQUERY, 0, /* TYPEQUERY, TYPEMINQUERY */ + OP_TYPEPOSUPTO, 0, /* TYPEUPTO, TYPEMINUPTO */ + 0, /* TYPEEXACT */ + 0, 0, 0, 0, /* TYPEPOS{STAR,PLUS,QUERY,UPTO} */ + + OP_CRPOSSTAR, 0, /* CRSTAR, CRMINSTAR */ + OP_CRPOSPLUS, 0, /* CRPLUS, CRMINPLUS */ + OP_CRPOSQUERY, 0, /* CRQUERY, CRMINQUERY */ + OP_CRPOSRANGE, 0, /* CRRANGE, CRMINRANGE */ + 0, 0, 0, 0, /* CRPOS{STAR,PLUS,QUERY,RANGE} */ + + 0, 0, 0, /* CLASS, NCLASS, XCLASS */ + 0, 0, /* REF, REFI */ + 0, 0, /* DNREF, DNREFI */ + 0, 0 /* RECURSE, CALLOUT */ +}; + + + +/************************************************* +* Free compiled code * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_code_free(pcre2_code *code) +{ +PCRE2_SIZE* ref_count; + +if (code != NULL) + { + if (code->executable_jit != NULL) + PRIV(jit_free)(code->executable_jit, &code->memctl); + + if ((code->flags & PCRE2_DEREF_TABLES) != 0) + { + /* Decoded tables belong to the codes after deserialization, and they must + be freed when there are no more reference to them. The *ref_count should + always be > 0. */ + + ref_count = (PCRE2_SIZE *)(code->tables + tables_length); + if (*ref_count > 0) + { + (*ref_count)--; + if (*ref_count == 0) + code->memctl.free((void *)code->tables, code->memctl.memory_data); + } + } + + code->memctl.free(code, code->memctl.memory_data); + } +} + + + +/************************************************* +* Insert an automatic callout point * +*************************************************/ + +/* This function is called when the PCRE2_AUTO_CALLOUT option is set, to insert +callout points before each pattern item. + +Arguments: + code current code pointer + ptr current pattern pointer + cb general compile-time data + +Returns: new code pointer +*/ + +static PCRE2_UCHAR * +auto_callout(PCRE2_UCHAR *code, PCRE2_SPTR ptr, compile_block *cb) +{ +code[0] = OP_CALLOUT; +PUT(code, 1, ptr - cb->start_pattern); /* Pattern offset */ +PUT(code, 1 + LINK_SIZE, 0); /* Default length */ +code[1 + 2*LINK_SIZE] = 255; +return code + PRIV(OP_lengths)[OP_CALLOUT]; +} + + + +/************************************************* +* Complete a callout item * +*************************************************/ + +/* A callout item contains the length of the next item in the pattern, which +we can't fill in till after we have reached the relevant point. This is used +for both automatic and manual callouts. + +Arguments: + previous_callout points to previous callout item + ptr current pattern pointer + cb general compile-time data + +Returns: nothing +*/ + +static void +complete_callout(PCRE2_UCHAR *previous_callout, PCRE2_SPTR ptr, + compile_block *cb) +{ +size_t length = ptr - cb->start_pattern - GET(previous_callout, 1); +PUT(previous_callout, 1 + LINK_SIZE, length); +} + + + +/************************************************* +* Find the fixed length of a branch * +*************************************************/ + +/* Scan a branch and compute the fixed length of subject that will match it, if +the length is fixed. This is needed for dealing with lookbehind assertions. In +UTF mode, the result is in code units rather than bytes. The branch is +temporarily terminated with OP_END when this function is called. + +This function is called when a lookbehind assertion is encountered, so that if +it fails, the error message can point to the correct place in the pattern. +However, we cannot do this when the assertion contains subroutine calls, +because they can be forward references. We solve this by remembering this case +and doing the check at the end; a flag specifies which mode we are running in. + +Lookbehind lengths are held in 16-bit fields and the maximum value is defined +as LOOKBEHIND_MAX. + +Arguments: + code points to the start of the pattern (the bracket) + utf TRUE in UTF mode + atend TRUE if called when the pattern is complete + cb the "compile data" structure + recurses chain of recurse_check to catch mutual recursion + countptr pointer to counter, to catch over-complexity + +Returns: if non-negative, the fixed length, + or -1 if an OP_RECURSE item was encountered and atend is FALSE + or -2 if there is no fixed length, + or -3 if \C was encountered (in UTF-8 mode only) + or -4 length is too long + or -5 if an unknown opcode was encountered (internal error) +*/ + +#define FFL_LATER (-1) +#define FFL_NOTFIXED (-2) +#define FFL_BACKSLASHC (-3) +#define FFL_TOOLONG (-4) +#define FFL_TOOCOMPLICATED (-5) +#define FFL_UNKNOWNOP (-6) + +static int +find_fixedlength(PCRE2_UCHAR *code, BOOL utf, BOOL atend, compile_block *cb, + recurse_check *recurses, int *countptr) +{ +int length = -1; +uint32_t group = 0; +uint32_t groupinfo = 0; +recurse_check this_recurse; +register int branchlength = 0; +register PCRE2_UCHAR *cc = code + 1 + LINK_SIZE; + +/* If this is a capturing group, we may have the answer cached, but we can only +use this information if there are no (?| groups in the pattern, because +otherwise group numbers are not unique. */ + +if (*code == OP_CBRA || *code == OP_CBRAPOS || *code == OP_SCBRA || + *code == OP_SCBRAPOS) + { + group = GET2(cc, 0); + cc += IMM2_SIZE; + groupinfo = cb->groupinfo[group]; + if ((cb->external_flags & PCRE2_DUPCAPUSED) == 0) + { + if ((groupinfo & GI_NOT_FIXED_LENGTH) != 0) return FFL_NOTFIXED; + if ((groupinfo & GI_SET_FIXED_LENGTH) != 0) + return groupinfo & GI_FIXED_LENGTH_MASK; + } + } + +/* A large and/or complex regex can take too long to process. This can happen +more often when (?| groups are present in the pattern. */ + +if ((*countptr)++ > 2000) return FFL_TOOCOMPLICATED; + +/* Scan along the opcodes for this branch. If we get to the end of the +branch, check the length against that of the other branches. */ + +for (;;) + { + int d; + PCRE2_UCHAR *ce, *cs; + register PCRE2_UCHAR op = *cc; + + if (branchlength > LOOKBEHIND_MAX) return FFL_TOOLONG; + + switch (op) + { + /* We only need to continue for OP_CBRA (normal capturing bracket) and + OP_BRA (normal non-capturing bracket) because the other variants of these + opcodes are all concerned with unlimited repeated groups, which of course + are not of fixed length. */ + + case OP_CBRA: + case OP_BRA: + case OP_ONCE: + case OP_ONCE_NC: + case OP_COND: + d = find_fixedlength(cc, utf, atend, cb, recurses, countptr); + if (d < 0) return d; + branchlength += d; + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* Reached end of a branch; if it's a ket it is the end of a nested call. + If it's ALT it is an alternation in a nested call. An ACCEPT is effectively + an ALT. If it is END it's the end of the outer call. All can be handled by + the same code. Note that we must not include the OP_KETRxxx opcodes here, + because they all imply an unlimited repeat. */ + + case OP_ALT: + case OP_KET: + case OP_END: + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + if (length < 0) length = branchlength; + else if (length != branchlength) goto ISNOTFIXED; + if (*cc != OP_ALT) + { + if (group > 0) + { + groupinfo |= (GI_SET_FIXED_LENGTH | length); + cb->groupinfo[group] = groupinfo; + } + return length; + } + cc += 1 + LINK_SIZE; + branchlength = 0; + break; + + /* A true recursion implies not fixed length, but a subroutine call may + be OK. If the subroutine is a forward reference, we can't deal with + it until the end of the pattern, so return FFL_LATER. */ + + case OP_RECURSE: + if (!atend) return FFL_LATER; + cs = ce = (PCRE2_UCHAR *)cb->start_code + GET(cc, 1); /* Start subpattern */ + do ce += GET(ce, 1); while (*ce == OP_ALT); /* End subpattern */ + if (cc > cs && cc < ce) goto ISNOTFIXED; /* Recursion */ + else /* Check for mutual recursion */ + { + recurse_check *r = recurses; + for (r = recurses; r != NULL; r = r->prev) if (r->group == cs) break; + if (r != NULL) goto ISNOTFIXED; /* Mutual recursion */ + } + this_recurse.prev = recurses; + this_recurse.group = cs; + d = find_fixedlength(cs, utf, atend, cb, &this_recurse, countptr); + if (d < 0) return d; + branchlength += d; + cc += 1 + LINK_SIZE; + break; + + /* Skip over assertive subpatterns. Note that we must increment cc by + 1 + LINK_SIZE at the end, not by OP_length[*cc] because in a recursive + situation this assertion may be the one that is ultimately being checked + for having a fixed length, in which case its terminating OP_KET will have + been temporarily replaced by OP_END. */ + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* Skip over things that don't match chars */ + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + cc += cc[1] + PRIV(OP_lengths)[*cc]; + break; + + case OP_CALLOUT: + case OP_CIRC: + case OP_CIRCM: + case OP_CLOSE: + case OP_COMMIT: + case OP_CREF: + case OP_FALSE: + case OP_TRUE: + case OP_DNCREF: + case OP_DNRREF: + case OP_DOLL: + case OP_DOLLM: + case OP_EOD: + case OP_EODN: + case OP_FAIL: + case OP_NOT_WORD_BOUNDARY: + case OP_PRUNE: + case OP_REVERSE: + case OP_RREF: + case OP_SET_SOM: + case OP_SKIP: + case OP_SOD: + case OP_SOM: + case OP_THEN: + case OP_WORD_BOUNDARY: + cc += PRIV(OP_lengths)[*cc]; + break; + + case OP_CALLOUT_STR: + cc += GET(cc, 1 + 2*LINK_SIZE); + break; + + /* Handle literal characters */ + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + branchlength++; + cc += 2; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + /* Handle exact repetitions. The count is already in characters, but we + need to skip over a multibyte character in UTF8 mode. */ + + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + branchlength += (int)GET2(cc,1); + cc += 2 + IMM2_SIZE; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + case OP_TYPEEXACT: + branchlength += GET2(cc,1); + if (cc[1 + IMM2_SIZE] == OP_PROP || cc[1 + IMM2_SIZE] == OP_NOTPROP) + cc += 2; + cc += 1 + IMM2_SIZE + 1; + break; + + /* Handle single-char matchers */ + + case OP_PROP: + case OP_NOTPROP: + cc += 2; + /* Fall through */ + + case OP_HSPACE: + case OP_VSPACE: + case OP_NOT_HSPACE: + case OP_NOT_VSPACE: + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + branchlength++; + cc++; + break; + + /* The single-byte matcher isn't allowed. This only happens in UTF-8 mode; + otherwise \C is coded as OP_ALLANY. */ + + case OP_ANYBYTE: + return FFL_BACKSLASHC; + + /* Check a class for variable quantification */ + + case OP_CLASS: + case OP_NCLASS: +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + /* The original code caused an unsigned overflow in 64 bit systems, + so now we use a conditional statement. */ + if (op == OP_XCLASS) + cc += GET(cc, 1); + else + cc += PRIV(OP_lengths)[OP_CLASS]; +#else + cc += PRIV(OP_lengths)[OP_CLASS]; +#endif + + switch (*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSPLUS: + case OP_CRPOSQUERY: + goto ISNOTFIXED; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + if (GET2(cc,1) != GET2(cc,1+IMM2_SIZE)) goto ISNOTFIXED; + branchlength += (int)GET2(cc,1); + cc += 1 + 2 * IMM2_SIZE; + break; + + default: + branchlength++; + } + break; + + /* Anything else is variable length */ + + case OP_ANYNL: + case OP_BRAMINZERO: + case OP_BRAPOS: + case OP_BRAPOSZERO: + case OP_BRAZERO: + case OP_CBRAPOS: + case OP_EXTUNI: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_KETRPOS: + case OP_MINPLUS: + case OP_MINPLUSI: + case OP_MINQUERY: + case OP_MINQUERYI: + case OP_MINSTAR: + case OP_MINSTARI: + case OP_MINUPTO: + case OP_MINUPTOI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_PLUS: + case OP_PLUSI: + case OP_POSPLUS: + case OP_POSPLUSI: + case OP_POSQUERY: + case OP_POSQUERYI: + case OP_POSSTAR: + case OP_POSSTARI: + case OP_POSUPTO: + case OP_POSUPTOI: + case OP_QUERY: + case OP_QUERYI: + case OP_REF: + case OP_REFI: + case OP_DNREF: + case OP_DNREFI: + case OP_SBRA: + case OP_SBRAPOS: + case OP_SCBRA: + case OP_SCBRAPOS: + case OP_SCOND: + case OP_SKIPZERO: + case OP_STAR: + case OP_STARI: + case OP_TYPEMINPLUS: + case OP_TYPEMINQUERY: + case OP_TYPEMINSTAR: + case OP_TYPEMINUPTO: + case OP_TYPEPLUS: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSUPTO: + case OP_TYPEQUERY: + case OP_TYPESTAR: + case OP_TYPEUPTO: + case OP_UPTO: + case OP_UPTOI: + goto ISNOTFIXED; + + /* Catch unrecognized opcodes so that when new ones are added they + are not forgotten, as has happened in the past. */ + + default: + return FFL_UNKNOWNOP; + } + } +/* Control never gets here except by goto. */ + +ISNOTFIXED: +if (group > 0) + { + groupinfo |= GI_NOT_FIXED_LENGTH; + cb->groupinfo[group] = groupinfo; + } +return FFL_NOTFIXED; +} + + + +/************************************************* +* Find first significant op code * +*************************************************/ + +/* This is called by several functions that scan a compiled expression looking +for a fixed first character, or an anchoring op code etc. It skips over things +that do not influence this. For some calls, it makes sense to skip negative +forward and all backward assertions, and also the \b assertion; for others it +does not. + +Arguments: + code pointer to the start of the group + skipassert TRUE if certain assertions are to be skipped + +Returns: pointer to the first significant opcode +*/ + +static const PCRE2_UCHAR* +first_significant_code(PCRE2_SPTR code, BOOL skipassert) +{ +for (;;) + { + switch ((int)*code) + { + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + if (!skipassert) return code; + do code += GET(code, 1); while (*code == OP_ALT); + code += PRIV(OP_lengths)[*code]; + break; + + case OP_WORD_BOUNDARY: + case OP_NOT_WORD_BOUNDARY: + if (!skipassert) return code; + /* Fall through */ + + case OP_CALLOUT: + case OP_CREF: + case OP_DNCREF: + case OP_RREF: + case OP_DNRREF: + case OP_FALSE: + case OP_TRUE: + code += PRIV(OP_lengths)[*code]; + break; + + case OP_CALLOUT_STR: + code += GET(code, 1 + 2*LINK_SIZE); + break; + + default: + return code; + } + } +/* Control never reaches here */ +} + + + +/************************************************* +* Scan compiled branch for non-emptiness * +*************************************************/ + +/* This function scans through a branch of a compiled pattern to see whether it +can match the empty string. It is called at the end of compiling to check the +entire pattern, and from compile_branch() when checking for an unlimited repeat +of a group that can match nothing. In the latter case it is called only when +doing the real compile, not during the pre-compile that measures the size of +the compiled pattern. + +Note that first_significant_code() skips over backward and negative forward +assertions when its final argument is TRUE. If we hit an unclosed bracket, we +return "empty" - this means we've struck an inner bracket whose current branch +will already have been scanned. + +Arguments: + code points to start of search + endcode points to where to stop + utf TRUE if in UTF mode + cb compile data + atend TRUE if being called to check an entire pattern + recurses chain of recurse_check to catch mutual recursion + countptr pointer to count to catch over-complicated pattern + +Returns: 0 if what is matched cannot be empty + 1 if what is matched could be empty + -1 if the pattern is too complicated +*/ + +#define CBE_NOTEMPTY 0 +#define CBE_EMPTY 1 +#define CBE_TOOCOMPLICATED (-1) + + +static int +could_be_empty_branch(PCRE2_SPTR code, PCRE2_SPTR endcode, BOOL utf, + compile_block *cb, BOOL atend, recurse_check *recurses, int *countptr) +{ +uint32_t group = 0; +uint32_t groupinfo = 0; +register PCRE2_UCHAR c; +recurse_check this_recurse; + +/* If what we are checking has already been set as "could be empty", we know +the answer. */ + +if (*code >= OP_SBRA && *code <= OP_SCOND) return CBE_EMPTY; + +/* If this is a capturing group, we may have the answer cached, but we can only +use this information if there are no (?| groups in the pattern, because +otherwise group numbers are not unique. */ + +if ((cb->external_flags & PCRE2_DUPCAPUSED) == 0 && + (*code == OP_CBRA || *code == OP_CBRAPOS)) + { + group = GET2(code, 1 + LINK_SIZE); + groupinfo = cb->groupinfo[group]; + if ((groupinfo & GI_SET_COULD_BE_EMPTY) != 0) + return ((groupinfo & GI_COULD_BE_EMPTY) != 0)? CBE_EMPTY : CBE_NOTEMPTY; + } + +/* A large and/or complex regex can take too long to process. We have to assume +it can match an empty string. This can happen more often when (?| groups are +present in the pattern and the caching is disabled. Setting the cap at 1100 +allows the test for more than 1023 capturing patterns to work. */ + +if ((*countptr)++ > 1100) return CBE_TOOCOMPLICATED; + +/* Scan the opcodes for this branch. */ + +for (code = first_significant_code(code + PRIV(OP_lengths)[*code], TRUE); + code < endcode; + code = first_significant_code(code + PRIV(OP_lengths)[c], TRUE)) + { + PCRE2_SPTR ccode; + + c = *code; + + /* Skip over forward assertions; the other assertions are skipped by + first_significant_code() with a TRUE final argument. */ + + if (c == OP_ASSERT) + { + do code += GET(code, 1); while (*code == OP_ALT); + c = *code; + continue; + } + + /* For a recursion/subroutine call we can scan the recursion when this + function is called at the end, to check a complete pattern. Before then, + recursions just have the group number as their argument and in any case may + be forward references. In that situation, we return CBE_EMPTY, just in case. + It means that unlimited repeats of groups that contain recursions are always + treated as "could be empty" - which just adds a bit more processing time + because of the runtime check. */ + + if (c == OP_RECURSE) + { + PCRE2_SPTR scode, endgroup; + BOOL empty_branch; + + if (!atend) goto ISTRUE; + scode = cb->start_code + GET(code, 1); + endgroup = scode; + + /* We need to detect whether this is a recursive call, as otherwise there + will be an infinite loop. If it is a recursion, just skip over it. Simple + recursions are easily detected. For mutual recursions we keep a chain on + the stack. */ + + do endgroup += GET(endgroup, 1); while (*endgroup == OP_ALT); + if (code >= scode && code <= endgroup) continue; /* Simple recursion */ + else + { + recurse_check *r = recurses; + for (r = recurses; r != NULL; r = r->prev) + if (r->group == scode) break; + if (r != NULL) continue; /* Mutual recursion */ + } + + /* Scan the referenced group, remembering it on the stack chain to detect + mutual recursions. */ + + empty_branch = FALSE; + this_recurse.prev = recurses; + this_recurse.group = scode; + + do + { + int rc = could_be_empty_branch(scode, endcode, utf, cb, atend, + &this_recurse, countptr); + if (rc < 0) return rc; + if (rc > 0) + { + empty_branch = TRUE; + break; + } + scode += GET(scode, 1); + } + while (*scode == OP_ALT); + + if (!empty_branch) goto ISFALSE; /* All branches are non-empty */ + continue; + } + + /* Groups with zero repeats can of course be empty; skip them. */ + + if (c == OP_BRAZERO || c == OP_BRAMINZERO || c == OP_SKIPZERO || + c == OP_BRAPOSZERO) + { + code += PRIV(OP_lengths)[c]; + do code += GET(code, 1); while (*code == OP_ALT); + c = *code; + continue; + } + + /* A nested group that is already marked as "could be empty" can just be + skipped. */ + + if (c == OP_SBRA || c == OP_SBRAPOS || + c == OP_SCBRA || c == OP_SCBRAPOS) + { + do code += GET(code, 1); while (*code == OP_ALT); + c = *code; + continue; + } + + /* For other groups, scan the branches. */ + + if (c == OP_BRA || c == OP_BRAPOS || + c == OP_CBRA || c == OP_CBRAPOS || + c == OP_ONCE || c == OP_ONCE_NC || + c == OP_COND || c == OP_SCOND) + { + BOOL empty_branch; + if (GET(code, 1) == 0) goto ISTRUE; /* Hit unclosed bracket */ + + /* If a conditional group has only one branch, there is a second, implied, + empty branch, so just skip over the conditional, because it could be empty. + Otherwise, scan the individual branches of the group. */ + + if (c == OP_COND && code[GET(code, 1)] != OP_ALT) + code += GET(code, 1); + else + { + empty_branch = FALSE; + do + { + if (!empty_branch) + { + int rc = could_be_empty_branch(code, endcode, utf, cb, atend, + recurses, countptr); + if (rc < 0) return rc; + if (rc > 0) empty_branch = TRUE; + } + code += GET(code, 1); + } + while (*code == OP_ALT); + if (!empty_branch) goto ISFALSE; /* All branches are non-empty */ + } + + c = *code; + continue; + } + + /* Handle the other opcodes */ + + switch (c) + { + /* Check for quantifiers after a class. XCLASS is used for classes that + cannot be represented just by a bit map. This includes negated single + high-valued characters. The length in PRIV(OP_lengths)[] is zero; the + actual length is stored in the compiled code, so we must update "code" + here. */ + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + ccode = code += GET(code, 1); + goto CHECK_CLASS_REPEAT; +#endif + + case OP_CLASS: + case OP_NCLASS: + ccode = code + PRIV(OP_lengths)[OP_CLASS]; + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + CHECK_CLASS_REPEAT: +#endif + + switch (*ccode) + { + case OP_CRSTAR: /* These could be empty; continue */ + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSQUERY: + break; + + default: /* Non-repeat => class must match */ + case OP_CRPLUS: /* These repeats aren't empty */ + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + goto ISFALSE; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + if (GET2(ccode, 1) > 0) goto ISFALSE; /* Minimum > 0 */ + break; + } + break; + + /* Opcodes that must match a character */ + + case OP_ANY: + case OP_ALLANY: + case OP_ANYBYTE: + + case OP_PROP: + case OP_NOTPROP: + case OP_ANYNL: + + case OP_NOT_HSPACE: + case OP_HSPACE: + case OP_NOT_VSPACE: + case OP_VSPACE: + case OP_EXTUNI: + + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + + case OP_PLUS: + case OP_PLUSI: + case OP_MINPLUS: + case OP_MINPLUSI: + + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + + case OP_POSPLUS: + case OP_POSPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + case OP_TYPEEXACT: + goto ISFALSE; + + /* These are going to continue, as they may be empty, but we have to + fudge the length for the \p and \P cases. */ + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + /* Same for these */ + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + if (code[1 + IMM2_SIZE] == OP_PROP || code[1 + IMM2_SIZE] == OP_NOTPROP) + code += 2; + break; + + /* End of branch */ + + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_KETRPOS: + case OP_ALT: + goto ISTRUE; + + /* In UTF-8 or UTF-16 mode, STAR, MINSTAR, POSSTAR, QUERY, MINQUERY, + POSQUERY, UPTO, MINUPTO, and POSUPTO and their caseless and negative + versions may be followed by a multibyte character. */ + +#ifdef MAYBE_UTF_MULTI + case OP_STAR: + case OP_STARI: + case OP_NOTSTAR: + case OP_NOTSTARI: + + case OP_MINSTAR: + case OP_MINSTARI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + + case OP_POSSTAR: + case OP_POSSTARI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + + case OP_QUERY: + case OP_QUERYI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + + case OP_MINQUERY: + case OP_MINQUERYI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + + case OP_POSQUERY: + case OP_POSQUERYI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + if (utf && HAS_EXTRALEN(code[1])) code += GET_EXTRALEN(code[1]); + break; + + case OP_UPTO: + case OP_UPTOI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + + case OP_MINUPTO: + case OP_MINUPTOI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + + case OP_POSUPTO: + case OP_POSUPTOI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + if (utf && HAS_EXTRALEN(code[1 + IMM2_SIZE])) code += GET_EXTRALEN(code[1 + IMM2_SIZE]); + break; +#endif /* MAYBE_UTF_MULTI */ + + /* MARK, and PRUNE/SKIP/THEN with an argument must skip over the argument + string. */ + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + code += code[1]; + break; + + /* None of the remaining opcodes are required to match a character. */ + + default: + break; + } + } + +ISTRUE: +groupinfo |= GI_COULD_BE_EMPTY; + +ISFALSE: +if (group > 0) cb->groupinfo[group] = groupinfo | GI_SET_COULD_BE_EMPTY; + +return ((groupinfo & GI_COULD_BE_EMPTY) != 0)? CBE_EMPTY : CBE_NOTEMPTY; +} + + + +/************************************************* +* Check for counted repeat * +*************************************************/ + +/* This function is called when a '{' is encountered in a place where it might +start a quantifier. It looks ahead to see if it really is a quantifier, that +is, one of the forms {ddd} {ddd,} or {ddd,ddd} where the ddds are digits. + +Argument: pointer to the first char after '{' +Returns: TRUE or FALSE +*/ + +static BOOL +is_counted_repeat(PCRE2_SPTR p) +{ +if (!IS_DIGIT(*p)) return FALSE; +p++; +while (IS_DIGIT(*p)) p++; +if (*p == CHAR_RIGHT_CURLY_BRACKET) return TRUE; + +if (*p++ != CHAR_COMMA) return FALSE; +if (*p == CHAR_RIGHT_CURLY_BRACKET) return TRUE; + +if (!IS_DIGIT(*p)) return FALSE; +p++; +while (IS_DIGIT(*p)) p++; + +return (*p == CHAR_RIGHT_CURLY_BRACKET); +} + + + +/************************************************* +* Handle escapes * +*************************************************/ + +/* This function is called when a \ has been encountered. It either returns a +positive value for a simple escape such as \d, or 0 for a data character, which +is placed in chptr. A backreference to group n is returned as negative n. On +entry, ptr is pointing at the \. On exit, it points the final code unit of the +escape sequence. + +This function is also called from pcre2_substitute() to handle escape sequences +in replacement strings. In this case, the cb argument is NULL, and only +sequences that define a data character are recognised. The isclass argument is +not relevant, but the options argument is the final value of the compiled +pattern's options. + +There is one "trick" case: when a sequence such as [[:>:]] or \s in UCP mode is +processed, it is replaced by a nested alternative sequence. If this contains a +backslash (which is usually does), ptrend does not point to its end - it still +points to the end of the whole pattern. However, we can detect this case +because cb->nestptr[0] will be non-NULL. The nested sequences are all zero- +terminated and there are only ever two levels of nesting. + +Arguments: + ptrptr points to the input position pointer + ptrend points to the end of the input + chptr points to a returned data character + errorcodeptr points to the errorcode variable (containing zero) + options the current options bits + isclass TRUE if inside a character class + cb compile data block + +Returns: zero => a data character + positive => a special escape sequence + negative => a back reference + on error, errorcodeptr is set non-zero +*/ + +int +PRIV(check_escape)(PCRE2_SPTR *ptrptr, PCRE2_SPTR ptrend, uint32_t *chptr, + int *errorcodeptr, uint32_t options, BOOL isclass, compile_block *cb) +{ +BOOL utf = (options & PCRE2_UTF) != 0; +PCRE2_SPTR ptr = *ptrptr + 1; +register uint32_t c, cc; +int escape = 0; +int i; + +/* Find the end of a nested insert. */ + +if (cb != NULL && cb->nestptr[0] != NULL) + ptrend = ptr + PRIV(strlen)(ptr); + +/* If backslash is at the end of the string, it's an error. */ + +if (ptr >= ptrend) + { + *errorcodeptr = ERR1; + return 0; + } + +GETCHARINCTEST(c, ptr); /* Get character value, increment pointer */ +ptr--; /* Set pointer back to the last code unit */ + +/* Non-alphanumerics are literals, so we just leave the value in c. An initial +value test saves a memory lookup for code points outside the alphanumeric +range. Otherwise, do a table lookup. A non-zero result is something that can be +returned immediately. Otherwise further processing is required. */ + +if (c < ESCAPES_FIRST || c > ESCAPES_LAST) {} /* Definitely literal */ + +else if ((i = escapes[c - ESCAPES_FIRST]) != 0) + { + if (i > 0) c = (uint32_t)i; else /* Positive is a data character */ + { + escape = -i; /* Else return a special escape */ + if (escape == ESC_P || escape == ESC_p || escape == ESC_X) + cb->external_flags |= PCRE2_HASBKPORX; /* Note \P, \p, or \X */ + } + } + +/* Escapes that need further processing, including those that are unknown. +When called from pcre2_substitute(), only \c, \o, and \x are recognized (and \u +when BSUX is set). */ + +else + { + PCRE2_SPTR oldptr; + BOOL braced, negated, overflow; + unsigned int s; + + /* Filter calls from pcre2_substitute(). */ + + if (cb == NULL && c != CHAR_c && c != CHAR_o && c != CHAR_x && + (c != CHAR_u || (options & PCRE2_ALT_BSUX) != 0)) + { + *errorcodeptr = ERR3; + return 0; + } + + switch (c) + { + /* A number of Perl escapes are not handled by PCRE. We give an explicit + error. */ + + case CHAR_l: + case CHAR_L: + *errorcodeptr = ERR37; + break; + + /* \u is unrecognized when PCRE2_ALT_BSUX is not set. When it is treated + specially, \u must be followed by four hex digits. Otherwise it is a + lowercase u letter. */ + + case CHAR_u: + if ((options & PCRE2_ALT_BSUX) == 0) *errorcodeptr = ERR37; else + { + uint32_t xc; + if ((cc = XDIGIT(ptr[1])) == 0xff) break; /* Not a hex digit */ + if ((xc = XDIGIT(ptr[2])) == 0xff) break; /* Not a hex digit */ + cc = (cc << 4) | xc; + if ((xc = XDIGIT(ptr[3])) == 0xff) break; /* Not a hex digit */ + cc = (cc << 4) | xc; + if ((xc = XDIGIT(ptr[4])) == 0xff) break; /* Not a hex digit */ + c = (cc << 4) | xc; + ptr += 4; + if (utf) + { + if (c > 0x10ffffU) *errorcodeptr = ERR77; + else if (c >= 0xd800 && c <= 0xdfff) *errorcodeptr = ERR73; + } + else if (c > MAX_NON_UTF_CHAR) *errorcodeptr = ERR77; + } + break; + + case CHAR_U: + /* \U is unrecognized unless PCRE2_ALT_BSUX is set, in which case it is an + upper case letter. */ + if ((options & PCRE2_ALT_BSUX) == 0) *errorcodeptr = ERR37; + break; + + /* In a character class, \g is just a literal "g". Outside a character + class, \g must be followed by one of a number of specific things: + + (1) A number, either plain or braced. If positive, it is an absolute + backreference. If negative, it is a relative backreference. This is a Perl + 5.10 feature. + + (2) Perl 5.10 also supports \g{name} as a reference to a named group. This + is part of Perl's movement towards a unified syntax for back references. As + this is synonymous with \k{name}, we fudge it up by pretending it really + was \k. + + (3) For Oniguruma compatibility we also support \g followed by a name or a + number either in angle brackets or in single quotes. However, these are + (possibly recursive) subroutine calls, _not_ backreferences. Just return + the ESC_g code (cf \k). */ + + case CHAR_g: + if (isclass) break; + if (ptr[1] == CHAR_LESS_THAN_SIGN || ptr[1] == CHAR_APOSTROPHE) + { + escape = ESC_g; + break; + } + + /* Handle the Perl-compatible cases */ + + if (ptr[1] == CHAR_LEFT_CURLY_BRACKET) + { + PCRE2_SPTR p; + for (p = ptr+2; *p != CHAR_NULL && *p != CHAR_RIGHT_CURLY_BRACKET; p++) + if (*p != CHAR_MINUS && !IS_DIGIT(*p)) break; + if (*p != CHAR_NULL && *p != CHAR_RIGHT_CURLY_BRACKET) + { + escape = ESC_k; + break; + } + braced = TRUE; + ptr++; + } + else braced = FALSE; + + if (ptr[1] == CHAR_MINUS) + { + negated = TRUE; + ptr++; + } + else negated = FALSE; + + /* The integer range is limited by the machine's int representation. */ + s = 0; + overflow = FALSE; + while (IS_DIGIT(ptr[1])) + { + if (s > INT_MAX / 10 - 1) /* Integer overflow */ + { + overflow = TRUE; + break; + } + s = s * 10 + (int)(*(++ptr) - CHAR_0); + } + if (overflow) /* Integer overflow */ + { + while (IS_DIGIT(ptr[1])) ptr++; + *errorcodeptr = ERR61; + break; + } + + if (braced && *(++ptr) != CHAR_RIGHT_CURLY_BRACKET) + { + *errorcodeptr = ERR57; + break; + } + + if (s == 0) + { + *errorcodeptr = ERR58; + break; + } + + if (negated) + { + if (s > cb->bracount) + { + *errorcodeptr = ERR15; + break; + } + s = cb->bracount - (s - 1); + } + + escape = -(int)s; + break; + + /* The handling of escape sequences consisting of a string of digits + starting with one that is not zero is not straightforward. Perl has changed + over the years. Nowadays \g{} for backreferences and \o{} for octal are + recommended to avoid the ambiguities in the old syntax. + + Outside a character class, the digits are read as a decimal number. If the + number is less than 10, or if there are that many previous extracting left + brackets, it is a back reference. Otherwise, up to three octal digits are + read to form an escaped character code. Thus \123 is likely to be octal 123 + (cf \0123, which is octal 012 followed by the literal 3). + + Inside a character class, \ followed by a digit is always either a literal + 8 or 9 or an octal number. */ + + case CHAR_1: case CHAR_2: case CHAR_3: case CHAR_4: case CHAR_5: + case CHAR_6: case CHAR_7: case CHAR_8: case CHAR_9: + + if (!isclass) + { + oldptr = ptr; + /* The integer range is limited by the machine's int representation. */ + s = c - CHAR_0; + overflow = FALSE; + while (IS_DIGIT(ptr[1])) + { + if (s > INT_MAX / 10 - 1) /* Integer overflow */ + { + overflow = TRUE; + break; + } + s = s * 10 + (int)(*(++ptr) - CHAR_0); + } + if (overflow) /* Integer overflow */ + { + while (IS_DIGIT(ptr[1])) ptr++; + *errorcodeptr = ERR61; + break; + } + + /* \1 to \9 are always back references. \8x and \9x are too; \1x to \7x + are octal escapes if there are not that many previous captures. */ + + if (s < 10 || *oldptr >= CHAR_8 || s <= cb->bracount) + { + escape = -(int)s; /* Indicates a back reference */ + break; + } + ptr = oldptr; /* Put the pointer back and fall through */ + } + + /* Handle a digit following \ when the number is not a back reference, or + we are within a character class. If the first digit is 8 or 9, Perl used to + generate a binary zero byte and then treat the digit as a following + literal. At least by Perl 5.18 this changed so as not to insert the binary + zero. */ + + if ((c = *ptr) >= CHAR_8) break; + + /* Fall through with a digit less than 8 */ + + /* \0 always starts an octal number, but we may drop through to here with a + larger first octal digit. The original code used just to take the least + significant 8 bits of octal numbers (I think this is what early Perls used + to do). Nowadays we allow for larger numbers in UTF-8 mode and 16-bit mode, + but no more than 3 octal digits. */ + + case CHAR_0: + c -= CHAR_0; + while(i++ < 2 && ptr[1] >= CHAR_0 && ptr[1] <= CHAR_7) + c = c * 8 + *(++ptr) - CHAR_0; +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (!utf && c > 0xff) *errorcodeptr = ERR51; +#endif + break; + + /* \o is a relatively new Perl feature, supporting a more general way of + specifying character codes in octal. The only supported form is \o{ddd}. */ + + case CHAR_o: + if (ptr[1] != CHAR_LEFT_CURLY_BRACKET) *errorcodeptr = ERR55; else + if (ptr[2] == CHAR_RIGHT_CURLY_BRACKET) *errorcodeptr = ERR78; else + { + ptr += 2; + c = 0; + overflow = FALSE; + while (*ptr >= CHAR_0 && *ptr <= CHAR_7) + { + cc = *ptr++; + if (c == 0 && cc == CHAR_0) continue; /* Leading zeroes */ +#if PCRE2_CODE_UNIT_WIDTH == 32 + if (c >= 0x20000000l) { overflow = TRUE; break; } +#endif + c = (c << 3) + (cc - CHAR_0); +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (c > (utf ? 0x10ffffU : 0xffU)) { overflow = TRUE; break; } +#elif PCRE2_CODE_UNIT_WIDTH == 16 + if (c > (utf ? 0x10ffffU : 0xffffU)) { overflow = TRUE; break; } +#elif PCRE2_CODE_UNIT_WIDTH == 32 + if (utf && c > 0x10ffffU) { overflow = TRUE; break; } +#endif + } + if (overflow) + { + while (*ptr >= CHAR_0 && *ptr <= CHAR_7) ptr++; + *errorcodeptr = ERR34; + } + else if (*ptr == CHAR_RIGHT_CURLY_BRACKET) + { + if (utf && c >= 0xd800 && c <= 0xdfff) *errorcodeptr = ERR73; + } + else *errorcodeptr = ERR64; + } + break; + + /* \x is complicated. When PCRE2_ALT_BSUX is set, \x must be followed by + two hexadecimal digits. Otherwise it is a lowercase x letter. */ + + case CHAR_x: + if ((options & PCRE2_ALT_BSUX) != 0) + { + uint32_t xc; + if ((cc = XDIGIT(ptr[1])) == 0xff) break; /* Not a hex digit */ + if ((xc = XDIGIT(ptr[2])) == 0xff) break; /* Not a hex digit */ + c = (cc << 4) | xc; + ptr += 2; + } /* End PCRE2_ALT_BSUX handling */ + + /* Handle \x in Perl's style. \x{ddd} is a character number which can be + greater than 0xff in UTF-8 or non-8bit mode, but only if the ddd are hex + digits. If not, { used to be treated as a data character. However, Perl + seems to read hex digits up to the first non-such, and ignore the rest, so + that, for example \x{zz} matches a binary zero. This seems crazy, so PCRE + now gives an error. */ + + else + { + if (ptr[1] == CHAR_LEFT_CURLY_BRACKET) + { + ptr += 2; + if (*ptr == CHAR_RIGHT_CURLY_BRACKET) + { + *errorcodeptr = ERR78; + break; + } + c = 0; + overflow = FALSE; + + while ((cc = XDIGIT(*ptr)) != 0xff) + { + ptr++; + if (c == 0 && cc == 0) continue; /* Leading zeroes */ +#if PCRE2_CODE_UNIT_WIDTH == 32 + if (c >= 0x10000000l) { overflow = TRUE; break; } +#endif + c = (c << 4) | cc; + if ((utf && c > 0x10ffffU) || (!utf && c > MAX_NON_UTF_CHAR)) + { + overflow = TRUE; + break; + } + } + + if (overflow) + { + while (XDIGIT(*ptr) != 0xff) ptr++; + *errorcodeptr = ERR34; + } + else if (*ptr == CHAR_RIGHT_CURLY_BRACKET) + { + if (utf && c >= 0xd800 && c <= 0xdfff) *errorcodeptr = ERR73; + } + + /* If the sequence of hex digits does not end with '}', give an error. + We used just to recognize this construct and fall through to the normal + \x handling, but nowadays Perl gives an error, which seems much more + sensible, so we do too. */ + + else *errorcodeptr = ERR67; + } /* End of \x{} processing */ + + /* Read a single-byte hex-defined char (up to two hex digits after \x) */ + + else + { + c = 0; + if ((cc = XDIGIT(ptr[1])) == 0xff) break; /* Not a hex digit */ + ptr++; + c = cc; + if ((cc = XDIGIT(ptr[1])) == 0xff) break; /* Not a hex digit */ + ptr++; + c = (c << 4) | cc; + } /* End of \xdd handling */ + } /* End of Perl-style \x handling */ + break; + + /* The handling of \c is different in ASCII and EBCDIC environments. In an + ASCII (or Unicode) environment, an error is given if the character + following \c is not a printable ASCII character. Otherwise, the following + character is upper-cased if it is a letter, and after that the 0x40 bit is + flipped. The result is the value of the escape. + + In an EBCDIC environment the handling of \c is compatible with the + specification in the perlebcdic document. The following character must be + a letter or one of small number of special characters. These provide a + means of defining the character values 0-31. + + For testing the EBCDIC handling of \c in an ASCII environment, recognize + the EBCDIC value of 'c' explicitly. */ + +#if defined EBCDIC && 'a' != 0x81 + case 0x83: +#else + case CHAR_c: +#endif + + c = *(++ptr); + if (c >= CHAR_a && c <= CHAR_z) c = UPPER_CASE(c); + if (c == CHAR_NULL && ptr >= ptrend) + { + *errorcodeptr = ERR2; + break; + } + + /* Handle \c in an ASCII/Unicode environment. */ + +#ifndef EBCDIC /* ASCII/UTF-8 coding */ + if (c < 32 || c > 126) /* Excludes all non-printable ASCII */ + { + *errorcodeptr = ERR68; + break; + } + c ^= 0x40; + + /* Handle \c in an EBCDIC environment. The special case \c? is converted to + 255 (0xff) or 95 (0x5f) if other character suggest we are using th POSIX-BC + encoding. (This is the way Perl indicates that it handles \c?.) The other + valid sequences correspond to a list of specific characters. */ + +#else + if (c == CHAR_QUESTION_MARK) + c = ('\\' == 188 && '`' == 74)? 0x5f : 0xff; + else + { + for (i = 0; i < 32; i++) + { + if (c == ebcdic_escape_c[i]) break; + } + if (i < 32) c = i; else *errorcodeptr = ERR68; + } +#endif /* EBCDIC */ + + break; + + /* Any other alphanumeric following \ is an error. Perl gives an error only + if in warning mode, but PCRE doesn't have a warning mode. */ + + default: + *errorcodeptr = ERR3; + break; + } + } + +/* Perl supports \N{name} for character names, as well as plain \N for "not +newline". PCRE does not support \N{name}. However, it does support +quantification such as \N{2,3}. */ + +if (escape == ESC_N && ptr[1] == CHAR_LEFT_CURLY_BRACKET && + !is_counted_repeat(ptr+2)) + *errorcodeptr = ERR37; + +/* If PCRE2_UCP is set, we change the values for \d etc. */ + +if ((options & PCRE2_UCP) != 0 && escape >= ESC_D && escape <= ESC_w) + escape += (ESC_DU - ESC_D); + +/* Set the pointer to the final character before returning. */ + +*ptrptr = ptr; +*chptr = c; +return escape; +} + + + +#ifdef SUPPORT_UNICODE +/************************************************* +* Handle \P and \p * +*************************************************/ + +/* This function is called after \P or \p has been encountered, provided that +PCRE2 is compiled with support for UTF and Unicode properties. On entry, the +contents of ptrptr are pointing at the P or p. On exit, it is left pointing at +the final code unit of the escape sequence. + +Arguments: + ptrptr the pattern position pointer + negptr a boolean that is set TRUE for negation else FALSE + ptypeptr an unsigned int that is set to the type value + pdataptr an unsigned int that is set to the detailed property value + errorcodeptr the error code variable + cb the compile data + +Returns: TRUE if the type value was found, or FALSE for an invalid type +*/ + +static BOOL +get_ucp(PCRE2_SPTR *ptrptr, BOOL *negptr, unsigned int *ptypeptr, + unsigned int *pdataptr, int *errorcodeptr, compile_block *cb) +{ +register PCRE2_UCHAR c; +int i, bot, top; +PCRE2_SPTR ptr = *ptrptr; +PCRE2_UCHAR name[32]; + +*negptr = FALSE; +c = *(++ptr); + +/* \P or \p can be followed by a name in {}, optionally preceded by ^ for +negation. */ + +if (c == CHAR_LEFT_CURLY_BRACKET) + { + if (ptr[1] == CHAR_CIRCUMFLEX_ACCENT) + { + *negptr = TRUE; + ptr++; + } + for (i = 0; i < (int)(sizeof(name) / sizeof(PCRE2_UCHAR)) - 1; i++) + { + c = *(++ptr); + if (c == CHAR_NULL) goto ERROR_RETURN; + if (c == CHAR_RIGHT_CURLY_BRACKET) break; + name[i] = c; + } + if (c != CHAR_RIGHT_CURLY_BRACKET) goto ERROR_RETURN; + name[i] = 0; + } + +/* Otherwise there is just one following character, which must be an ASCII +letter. */ + +else if (MAX_255(c) && (cb->ctypes[c] & ctype_letter) != 0) + { + name[0] = c; + name[1] = 0; + } +else goto ERROR_RETURN; + +*ptrptr = ptr; + +/* Search for a recognized property name using binary chop. */ + +bot = 0; +top = PRIV(utt_size); + +while (bot < top) + { + int r; + i = (bot + top) >> 1; + r = PRIV(strcmp_c8)(name, PRIV(utt_names) + PRIV(utt)[i].name_offset); + if (r == 0) + { + *ptypeptr = PRIV(utt)[i].type; + *pdataptr = PRIV(utt)[i].value; + return TRUE; + } + if (r > 0) bot = i + 1; else top = i; + } +*errorcodeptr = ERR47; /* Unrecognized name */ +return FALSE; + +ERROR_RETURN: /* Malformed \P or \p */ +*errorcodeptr = ERR46; +*ptrptr = ptr; +return FALSE; +} +#endif + + + +/************************************************* +* Read repeat counts * +*************************************************/ + +/* Read an item of the form {n,m} and return the values. This is called only +after is_counted_repeat() has confirmed that a repeat-count quantifier exists, +so the syntax is guaranteed to be correct, but we need to check the values. + +Arguments: + p pointer to first char after '{' + minp pointer to int for min + maxp pointer to int for max + returned as -1 if no max + errorcodeptr points to error code variable + +Returns: pointer to '}' on success; + current ptr on error, with errorcodeptr set non-zero +*/ + +static PCRE2_SPTR +read_repeat_counts(PCRE2_SPTR p, int *minp, int *maxp, int *errorcodeptr) +{ +int min = 0; +int max = -1; + +while (IS_DIGIT(*p)) + { + min = min * 10 + (int)(*p++ - CHAR_0); + if (min > 65535) + { + *errorcodeptr = ERR5; + return p; + } + } + +if (*p == CHAR_RIGHT_CURLY_BRACKET) max = min; else + { + if (*(++p) != CHAR_RIGHT_CURLY_BRACKET) + { + max = 0; + while(IS_DIGIT(*p)) + { + max = max * 10 + (int)(*p++ - CHAR_0); + if (max > 65535) + { + *errorcodeptr = ERR5; + return p; + } + } + if (max < min) + { + *errorcodeptr = ERR4; + return p; + } + } + } + +*minp = min; +*maxp = max; +return p; +} + + + +/************************************************* +* Scan compiled regex for recursion reference * +*************************************************/ + +/* This function scans through a compiled pattern until it finds an instance of +OP_RECURSE. + +Arguments: + code points to start of expression + utf TRUE in UTF mode + +Returns: pointer to the opcode for OP_RECURSE, or NULL if not found +*/ + +static PCRE2_SPTR +find_recurse(PCRE2_SPTR code, BOOL utf) +{ +for (;;) + { + register PCRE2_UCHAR c = *code; + if (c == OP_END) return NULL; + if (c == OP_RECURSE) return code; + + /* XCLASS is used for classes that cannot be represented just by a bit map. + This includes negated single high-valued characters. CALLOUT_STR is used for + callouts with string arguments. In both cases the length in the table is + zero; the actual length is stored in the compiled code. */ + + if (c == OP_XCLASS) code += GET(code, 1); + else if (c == OP_CALLOUT_STR) code += GET(code, 1 + 2*LINK_SIZE); + + /* Otherwise, we can get the item's length from the table, except that for + repeated character types, we have to test for \p and \P, which have an extra + two bytes of parameters, and for MARK/PRUNE/SKIP/THEN with an argument, we + must add in its length. */ + + else + { + switch(c) + { + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + case OP_TYPEPOSUPTO: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + if (code[1 + IMM2_SIZE] == OP_PROP || code[1 + IMM2_SIZE] == OP_NOTPROP) + code += 2; + break; + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + code += code[1]; + break; + } + + /* Add in the fixed length from the table */ + + code += PRIV(OP_lengths)[c]; + + /* In UTF-8 and UTF-16 modes, opcodes that are followed by a character may + be followed by a multi-unit character. The length in the table is a + minimum, so we have to arrange to skip the extra units. */ + +#ifdef MAYBE_UTF_MULTI + if (utf) switch(c) + { + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + case OP_UPTO: + case OP_UPTOI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_MINUPTO: + case OP_MINUPTOI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + case OP_POSUPTO: + case OP_POSUPTOI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + case OP_STAR: + case OP_STARI: + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_MINSTAR: + case OP_MINSTARI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_POSSTAR: + case OP_POSSTARI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + case OP_PLUS: + case OP_PLUSI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_MINPLUS: + case OP_MINPLUSI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_POSPLUS: + case OP_POSPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + case OP_QUERY: + case OP_QUERYI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_MINQUERY: + case OP_MINQUERYI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + case OP_POSQUERY: + case OP_POSQUERYI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + if (HAS_EXTRALEN(code[-1])) code += GET_EXTRALEN(code[-1]); + break; + } +#else + (void)(utf); /* Keep compiler happy by referencing function argument */ +#endif /* MAYBE_UTF_MULTI */ + } + } +} + + + +/************************************************* +* Check for POSIX class syntax * +*************************************************/ + +/* This function is called when the sequence "[:" or "[." or "[=" is +encountered in a character class. It checks whether this is followed by a +sequence of characters terminated by a matching ":]" or ".]" or "=]". If we +reach an unescaped ']' without the special preceding character, return FALSE. + +Originally, this function only recognized a sequence of letters between the +terminators, but it seems that Perl recognizes any sequence of characters, +though of course unknown POSIX names are subsequently rejected. Perl gives an +"Unknown POSIX class" error for [:f\oo:] for example, where previously PCRE +didn't consider this to be a POSIX class. Likewise for [:1234:]. + +The problem in trying to be exactly like Perl is in the handling of escapes. We +have to be sure that [abc[:x\]pqr] is *not* treated as containing a POSIX +class, but [abc[:x\]pqr:]] is (so that an error can be generated). The code +below handles the special cases \\ and \], but does not try to do any other +escape processing. This makes it different from Perl for cases such as +[:l\ower:] where Perl recognizes it as the POSIX class "lower" but PCRE does +not recognize "l\ower". This is a lesser evil than not diagnosing bad classes +when Perl does, I think. + +A user pointed out that PCRE was rejecting [:a[:digit:]] whereas Perl was not. +It seems that the appearance of a nested POSIX class supersedes an apparent +external class. For example, [:a[:digit:]b:] matches "a", "b", ":", or +a digit. This is handled by returning FALSE if the start of a new group with +the same terminator is encountered, since the next closing sequence must close +the nested group, not the outer one. + +In Perl, unescaped square brackets may also appear as part of class names. For +example, [:a[:abc]b:] gives unknown POSIX class "[:abc]b:]". However, for +[:a[:abc]b][b:] it gives unknown POSIX class "[:abc]b][b:]", which does not +seem right at all. PCRE does not allow closing square brackets in POSIX class +names. + +Arguments: + ptr pointer to the initial [ + endptr where to return a pointer to the terminating ':', '.', or '=' + +Returns: TRUE or FALSE +*/ + +static BOOL +check_posix_syntax(PCRE2_SPTR ptr, PCRE2_SPTR *endptr) +{ +PCRE2_UCHAR terminator; /* Don't combine these lines; the Solaris cc */ +terminator = *(++ptr); /* compiler warns about "non-constant" initializer. */ + +for (++ptr; *ptr != CHAR_NULL; ptr++) + { + if (*ptr == CHAR_BACKSLASH && + (ptr[1] == CHAR_RIGHT_SQUARE_BRACKET || ptr[1] == CHAR_BACKSLASH)) + ptr++; + else if ((*ptr == CHAR_LEFT_SQUARE_BRACKET && ptr[1] == terminator) || + *ptr == CHAR_RIGHT_SQUARE_BRACKET) return FALSE; + else if (*ptr == terminator && ptr[1] == CHAR_RIGHT_SQUARE_BRACKET) + { + *endptr = ptr; + return TRUE; + } + } + +return FALSE; +} + + + +/************************************************* +* Check POSIX class name * +*************************************************/ + +/* This function is called to check the name given in a POSIX-style class entry +such as [:alnum:]. + +Arguments: + ptr points to the first letter + len the length of the name + +Returns: a value representing the name, or -1 if unknown +*/ + +static int +check_posix_name(PCRE2_SPTR ptr, int len) +{ +const char *pn = posix_names; +register int yield = 0; +while (posix_name_lengths[yield] != 0) + { + if (len == posix_name_lengths[yield] && + PRIV(strncmp_c8)(ptr, pn, (unsigned int)len) == 0) return yield; + pn += posix_name_lengths[yield] + 1; + yield++; + } +return -1; +} + + + +#ifdef SUPPORT_UNICODE +/************************************************* +* Get othercase range * +*************************************************/ + +/* This function is passed the start and end of a class range in UCT mode. It +searches up the characters, looking for ranges of characters in the "other" +case. Each call returns the next one, updating the start address. A character +with multiple other cases is returned on its own with a special return value. + +Arguments: + cptr points to starting character value; updated + d end value + ocptr where to put start of othercase range + odptr where to put end of othercase range + +Yield: -1 when no more + 0 when a range is returned + >0 the CASESET offset for char with multiple other cases + in this case, ocptr contains the original +*/ + +static int +get_othercase_range(uint32_t *cptr, uint32_t d, uint32_t *ocptr, + uint32_t *odptr) +{ +uint32_t c, othercase, next; +unsigned int co; + +/* Find the first character that has an other case. If it has multiple other +cases, return its case offset value. */ + +for (c = *cptr; c <= d; c++) + { + if ((co = UCD_CASESET(c)) != 0) + { + *ocptr = c++; /* Character that has the set */ + *cptr = c; /* Rest of input range */ + return (int)co; + } + if ((othercase = UCD_OTHERCASE(c)) != c) break; + } + +if (c > d) return -1; /* Reached end of range */ + +/* Found a character that has a single other case. Search for the end of the +range, which is either the end of the input range, or a character that has zero +or more than one other cases. */ + +*ocptr = othercase; +next = othercase + 1; + +for (++c; c <= d; c++) + { + if ((co = UCD_CASESET(c)) != 0 || UCD_OTHERCASE(c) != next) break; + next++; + } + +*odptr = next - 1; /* End of othercase range */ +*cptr = c; /* Rest of input range */ +return 0; +} +#endif /* SUPPORT_UNICODE */ + + + +/************************************************* +* Add a character or range to a class * +*************************************************/ + +/* This function packages up the logic of adding a character or range of +characters to a class. The character values in the arguments will be within the +valid values for the current mode (8-bit, 16-bit, UTF, etc). This function is +mutually recursive with the function immediately below. + +Arguments: + classbits the bit map for characters < 256 + uchardptr points to the pointer for extra data + options the options word + cb compile data + start start of range character + end end of range character + +Returns: the number of < 256 characters added + the pointer to extra data is updated +*/ + +static int +add_to_class(uint8_t *classbits, PCRE2_UCHAR **uchardptr, uint32_t options, + compile_block *cb, uint32_t start, uint32_t end) +{ +uint32_t c; +uint32_t classbits_end = (end <= 0xff ? end : 0xff); +int n8 = 0; + +/* If caseless matching is required, scan the range and process alternate +cases. In Unicode, there are 8-bit characters that have alternate cases that +are greater than 255 and vice-versa. Sometimes we can just extend the original +range. */ + +if ((options & PCRE2_CASELESS) != 0) + { +#ifdef SUPPORT_UNICODE + if ((options & PCRE2_UTF) != 0) + { + int rc; + uint32_t oc, od; + + options &= ~PCRE2_CASELESS; /* Remove for recursive calls */ + c = start; + + while ((rc = get_othercase_range(&c, end, &oc, &od)) >= 0) + { + /* Handle a single character that has more than one other case. */ + + if (rc > 0) n8 += add_list_to_class(classbits, uchardptr, options, cb, + PRIV(ucd_caseless_sets) + rc, oc); + + /* Do nothing if the other case range is within the original range. */ + + else if (oc >= start && od <= end) continue; + + /* Extend the original range if there is overlap, noting that if oc < c, we + can't have od > end because a subrange is always shorter than the basic + range. Otherwise, use a recursive call to add the additional range. */ + + else if (oc < start && od >= start - 1) start = oc; /* Extend downwards */ + else if (od > end && oc <= end + 1) + { + end = od; /* Extend upwards */ + if (end > classbits_end) classbits_end = (end <= 0xff ? end : 0xff); + } + else n8 += add_to_class(classbits, uchardptr, options, cb, oc, od); + } + } + else +#endif /* SUPPORT_UNICODE */ + + /* Not UTF mode */ + + for (c = start; c <= classbits_end; c++) + { + SETBIT(classbits, cb->fcc[c]); + n8++; + } + } + +/* Now handle the original range. Adjust the final value according to the bit +length - this means that the same lists of (e.g.) horizontal spaces can be used +in all cases. */ + +if ((options & PCRE2_UTF) == 0 && end > MAX_NON_UTF_CHAR) + end = MAX_NON_UTF_CHAR; + +/* Use the bitmap for characters < 256. Otherwise use extra data.*/ + +for (c = start; c <= classbits_end; c++) + { + /* Regardless of start, c will always be <= 255. */ + SETBIT(classbits, c); + n8++; + } + +#ifdef SUPPORT_WIDE_CHARS +if (start <= 0xff) start = 0xff + 1; + +if (end >= start) + { + PCRE2_UCHAR *uchardata = *uchardptr; + +#ifdef SUPPORT_UNICODE + if ((options & PCRE2_UTF) != 0) + { + if (start < end) + { + *uchardata++ = XCL_RANGE; + uchardata += PRIV(ord2utf)(start, uchardata); + uchardata += PRIV(ord2utf)(end, uchardata); + } + else if (start == end) + { + *uchardata++ = XCL_SINGLE; + uchardata += PRIV(ord2utf)(start, uchardata); + } + } + else +#endif /* SUPPORT_UNICODE */ + + /* Without UTF support, character values are constrained by the bit length, + and can only be > 256 for 16-bit and 32-bit libraries. */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 + {} +#else + if (start < end) + { + *uchardata++ = XCL_RANGE; + *uchardata++ = start; + *uchardata++ = end; + } + else if (start == end) + { + *uchardata++ = XCL_SINGLE; + *uchardata++ = start; + } +#endif + *uchardptr = uchardata; /* Updata extra data pointer */ + } +#else + (void)uchardptr; /* Avoid compiler warning */ +#endif /* SUPPORT_WIDE_CHARS */ + +return n8; /* Number of 8-bit characters */ +} + + + +/************************************************* +* Add a list of characters to a class * +*************************************************/ + +/* This function is used for adding a list of case-equivalent characters to a +class, and also for adding a list of horizontal or vertical whitespace. If the +list is in order (which it should be), ranges of characters are detected and +handled appropriately. This function is mutually recursive with the function +above. + +Arguments: + classbits the bit map for characters < 256 + uchardptr points to the pointer for extra data + options the options word + cb contains pointers to tables etc. + p points to row of 32-bit values, terminated by NOTACHAR + except character to omit; this is used when adding lists of + case-equivalent characters to avoid including the one we + already know about + +Returns: the number of < 256 characters added + the pointer to extra data is updated +*/ + +static int +add_list_to_class(uint8_t *classbits, PCRE2_UCHAR **uchardptr, uint32_t options, + compile_block *cb, const uint32_t *p, unsigned int except) +{ +int n8 = 0; +while (p[0] < NOTACHAR) + { + int n = 0; + if (p[0] != except) + { + while(p[n+1] == p[0] + n + 1) n++; + n8 += add_to_class(classbits, uchardptr, options, cb, p[0], p[n]); + } + p += n + 1; + } +return n8; +} + + + +/************************************************* +* Add characters not in a list to a class * +*************************************************/ + +/* This function is used for adding the complement of a list of horizontal or +vertical whitespace to a class. The list must be in order. + +Arguments: + classbits the bit map for characters < 256 + uchardptr points to the pointer for extra data + options the options word + cb contains pointers to tables etc. + p points to row of 32-bit values, terminated by NOTACHAR + +Returns: the number of < 256 characters added + the pointer to extra data is updated +*/ + +static int +add_not_list_to_class(uint8_t *classbits, PCRE2_UCHAR **uchardptr, + uint32_t options, compile_block *cb, const uint32_t *p) +{ +BOOL utf = (options & PCRE2_UTF) != 0; +int n8 = 0; +if (p[0] > 0) + n8 += add_to_class(classbits, uchardptr, options, cb, 0, p[0] - 1); +while (p[0] < NOTACHAR) + { + while (p[1] == p[0] + 1) p++; + n8 += add_to_class(classbits, uchardptr, options, cb, p[0] + 1, + (p[1] == NOTACHAR) ? (utf ? 0x10ffffu : 0xffffffffu) : p[1] - 1); + p++; + } +return n8; +} + + + +/************************************************* +* Process (*VERB) name for escapes * +*************************************************/ + +/* This function is called when the PCRE2_ALT_VERBNAMES option is set, to +process the characters in a verb's name argument. It is called twice, once with +codeptr == NULL, to find out the length of the processed name, and again to put +the name into memory. + +Arguments: + ptrptr pointer to the input pointer + codeptr pointer to the compiled code pointer + errorcodeptr pointer to the error code + options the options bits + utf TRUE if processing UTF + cb compile data block + +Returns: length of the processed name, or < 0 on error +*/ + +static int +process_verb_name(PCRE2_SPTR *ptrptr, PCRE2_UCHAR **codeptr, int *errorcodeptr, + uint32_t options, BOOL utf, compile_block *cb) +{ +int32_t arglen = 0; +BOOL inescq = FALSE; +PCRE2_SPTR ptr = *ptrptr; +PCRE2_UCHAR *code = (codeptr == NULL)? NULL : *codeptr; + +for (; ptr < cb->end_pattern; ptr++) + { + uint32_t x = *ptr; + + /* Skip over literals */ + + if (inescq) + { + if (x == CHAR_BACKSLASH && ptr[1] == CHAR_E) + { + inescq = FALSE; + ptr++;; + continue; + } + } + + else /* Not a literal character */ + { + if (x == CHAR_RIGHT_PARENTHESIS) break; + + /* Skip over comments and whitespace in extended mode. */ + + if ((options & PCRE2_EXTENDED) != 0) + { + PCRE2_SPTR wscptr = ptr; + while (MAX_255(x) && (cb->ctypes[x] & ctype_space) != 0) x = *(++ptr); + if (x == CHAR_NUMBER_SIGN) + { + ptr++; + while (*ptr != CHAR_NULL || ptr < cb->end_pattern) + { + if (IS_NEWLINE(ptr)) /* For non-fixed-length newline cases, */ + { /* IS_NEWLINE sets cb->nllen. */ + ptr += cb->nllen; + break; + } + ptr++; +#ifdef SUPPORT_UNICODE + if (utf) FORWARDCHAR(ptr); +#endif + } + } + + /* If we have skipped any characters, restart the loop. */ + + if (ptr > wscptr) + { + ptr--; + continue; + } + } + + /* Process escapes */ + + if (x == '\\') + { + int rc; + *errorcodeptr = 0; + rc = PRIV(check_escape)(&ptr, cb->end_pattern, &x, errorcodeptr, options, + FALSE, cb); + *ptrptr = ptr; /* For possible error */ + if (*errorcodeptr != 0) return -1; + if (rc != 0) + { + if (rc == ESC_Q) + { + inescq = TRUE; + continue; + } + if (rc == ESC_E) continue; + *errorcodeptr = ERR40; + return -1; + } + } + } + + /* We have the next character in the name. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + if (code == NULL) /* Just want the length */ + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + int i; + for (i = 0; i < PRIV(utf8_table1_size); i++) + if ((int)x <= PRIV(utf8_table1)[i]) break; + arglen += i; +#elif PCRE2_CODE_UNIT_WIDTH == 16 + if (x > 0xffff) arglen++; +#endif + } + else + { + PCRE2_UCHAR cbuff[8]; + x = PRIV(ord2utf)(x, cbuff); + memcpy(code, cbuff, CU2BYTES(x)); + code += x; + } + } + else +#endif /* SUPPORT_UNICODE */ + + /* Not UTF */ + { + if (code != NULL) *code++ = x; + } + + arglen++; + + if ((unsigned int)arglen > MAX_MARK) + { + *errorcodeptr = ERR76; + *ptrptr = ptr; + return -1; + } + } + +/* Update the pointers before returning. */ + +*ptrptr = ptr; +if (codeptr != NULL) *codeptr = code; +return arglen; +} + + + +/************************************************* +* Macro for the next two functions * +*************************************************/ + +/* Both scan_for_captures() and compile_branch() use this macro to generate a +fragment of code that reads the characters of a name and sets its length +(checking for not being too long). Count the characters dynamically, to avoid +the possibility of integer overflow. The same macro is used for reading *VERB +names. */ + +#define READ_NAME(ctype, errno, errset) \ + namelen = 0; \ + while (MAX_255(*ptr) && (cb->ctypes[*ptr] & ctype) != 0) \ + { \ + ptr++; \ + namelen++; \ + if (namelen > MAX_NAME_SIZE) \ + { \ + errset = errno; \ + goto FAILED; \ + } \ + } + + + +/************************************************* +* Scan regex to identify named groups * +*************************************************/ + +/* This function is called first of all, to scan for named capturing groups so +that information about them is fully available to both the compiling scans. +It skips over everything except parenthesized items. + +Arguments: + ptrptr points to pointer to the start of the pattern + options compiling dynamic options + cb pointer to the compile data block + +Returns: zero on success or a non-zero error code, with pointer updated +*/ + +typedef struct nest_save { + uint16_t nest_depth; + uint16_t reset_group; + uint16_t max_group; + uint16_t flags; +} nest_save; + +#define NSF_RESET 0x0001u +#define NSF_EXTENDED 0x0002u +#define NSF_DUPNAMES 0x0004u + +static uint32_t scan_for_captures(PCRE2_SPTR *ptrptr, uint32_t options, + compile_block *cb) +{ +uint32_t c; +uint32_t delimiter; +uint32_t nest_depth = 0; +uint32_t set, unset, *optset; +int errorcode = 0; +int escape; +int namelen; +int i; +BOOL inescq = FALSE; +BOOL isdupname; +BOOL skiptoket = FALSE; +BOOL utf = (options & PCRE2_UTF) != 0; +BOOL negate_class; +PCRE2_SPTR name; +PCRE2_SPTR start; +PCRE2_SPTR ptr = *ptrptr; +named_group *ng; +nest_save *top_nest = NULL; +nest_save *end_nests = (nest_save *)(cb->start_workspace + cb->workspace_size); + +/* The size of the nest_save structure might not be a factor of the size of the +workspace. Therefore we must round down end_nests so as to correctly avoid +creating a nest_save that spans the end of the workspace. */ + +end_nests = (nest_save *)((char *)end_nests - + ((cb->workspace_size * sizeof(PCRE2_UCHAR)) % sizeof(nest_save))); + +/* Now scan the pattern */ + +for (; ptr < cb->end_pattern; ptr++) + { + c = *ptr; + + /* Parenthesized groups set skiptoket when all following characters up to the + next closing parenthesis must be ignored. The parenthesis itself must be + processed (to end the nested parenthesized item). */ + + if (skiptoket) + { + if (c != CHAR_RIGHT_PARENTHESIS) continue; + skiptoket = FALSE; + } + + /* Skip over literals */ + + if (inescq) + { + if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E) + { + inescq = FALSE; + ptr++; + } + continue; + } + + /* Skip over comments and whitespace in extended mode. Need a loop to handle + whitespace after a comment. */ + + if ((options & PCRE2_EXTENDED) != 0) + { + for (;;) + { + while (MAX_255(c) && (cb->ctypes[c] & ctype_space) != 0) c = *(++ptr); + if (c != CHAR_NUMBER_SIGN) break; + ptr++; + while (*ptr != CHAR_NULL) + { + if (IS_NEWLINE(ptr)) /* For non-fixed-length newline cases, */ + { /* IS_NEWLINE sets cb->nllen. */ + ptr += cb->nllen; + break; + } + ptr++; +#ifdef SUPPORT_UNICODE + if (utf) FORWARDCHAR(ptr); +#endif + } + c = *ptr; /* Either NULL or the char after a newline */ + } + } + + /* Process the next pattern item. */ + + switch(c) + { + default: /* Most characters are just skipped */ + break; + + /* Skip escapes except for \Q */ + + case CHAR_BACKSLASH: + errorcode = 0; + escape = PRIV(check_escape)(&ptr, cb->end_pattern, &c, &errorcode, options, + FALSE, cb); + if (errorcode != 0) goto FAILED; + if (escape == ESC_Q) inescq = TRUE; + break; + + /* Skip a character class. The syntax is complicated so we have to + replicate some of what happens when a class is processed for real. */ + + case CHAR_LEFT_SQUARE_BRACKET: + if (PRIV(strncmp_c8)(ptr+1, STRING_WEIRD_STARTWORD, 6) == 0 || + PRIV(strncmp_c8)(ptr+1, STRING_WEIRD_ENDWORD, 6) == 0) + { + ptr += 6; + break; + } + + /* If the first character is '^', set the negation flag (not actually used + here, except to recognize only one ^) and skip it. If the first few + characters (either before or after ^) are \Q\E or \E we skip them too. This + makes for compatibility with Perl. */ + + negate_class = FALSE; + for (;;) + { + c = *(++ptr); /* First character in class */ + if (c == CHAR_BACKSLASH) + { + if (ptr[1] == CHAR_E) + ptr++; + else if (PRIV(strncmp_c8)(ptr + 1, STR_Q STR_BACKSLASH STR_E, 3) == 0) + ptr += 3; + else + break; + } + else if (!negate_class && c == CHAR_CIRCUMFLEX_ACCENT) + negate_class = TRUE; + else break; + } + + if (c == CHAR_RIGHT_SQUARE_BRACKET && + (cb->external_options & PCRE2_ALLOW_EMPTY_CLASS) != 0) + break; + + /* Loop for the contents of the class */ + + for (;;) + { + PCRE2_SPTR tempptr; + + if (c == CHAR_NULL && ptr >= cb->end_pattern) + { + errorcode = ERR6; /* Missing terminating ']' */ + goto FAILED; + } + +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(c)) + { /* Braces are required because the */ + GETCHARLEN(c, ptr, ptr); /* macro generates multiple statements */ + } +#endif + + /* Inside \Q...\E everything is literal except \E */ + + if (inescq) + { + if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E) /* If we are at \E */ + { + inescq = FALSE; /* Reset literal state */ + ptr++; /* Skip the 'E' */ + } + goto CONTINUE_CLASS; + } + + /* Skip POSIX class names. */ + if (c == CHAR_LEFT_SQUARE_BRACKET && + (ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT || + ptr[1] == CHAR_EQUALS_SIGN) && check_posix_syntax(ptr, &tempptr)) + { + ptr = tempptr + 1; + } + else if (c == CHAR_BACKSLASH) + { + errorcode = 0; + escape = PRIV(check_escape)(&ptr, cb->end_pattern, &c, &errorcode, + options, TRUE, cb); + if (errorcode != 0) goto FAILED; + if (escape == ESC_Q) inescq = TRUE; + } + + CONTINUE_CLASS: + c = *(++ptr); + if (c == CHAR_RIGHT_SQUARE_BRACKET && !inescq) break; + } /* End of class-processing loop */ + break; + + /* This is the real work of this function - handling parentheses. */ + + case CHAR_LEFT_PARENTHESIS: + nest_depth++; + + if (ptr[1] != CHAR_QUESTION_MARK) + { + if (ptr[1] != CHAR_ASTERISK) + { + if ((options & PCRE2_NO_AUTO_CAPTURE) == 0) cb->bracount++; + } + + /* (*something) - just skip to closing ket unless PCRE2_ALT_VERBNAMES is + set, in which case we have to process escapes in the string after the + name. */ + + else + { + ptr += 2; + while (MAX_255(*ptr) && (cb->ctypes[*ptr] & ctype_word) != 0) ptr++; + if (*ptr == CHAR_COLON) + { + ptr++; + if ((options & PCRE2_ALT_VERBNAMES) != 0) + { + if (process_verb_name(&ptr, NULL, &errorcode, options, utf, cb) < 0) + goto FAILED; + } + else + { + while (ptr < cb->end_pattern && *ptr != CHAR_RIGHT_PARENTHESIS) + ptr++; + } + } + nest_depth--; + } + } + + /* Handle (?...) groups */ + + else switch(ptr[2]) + { + default: + ptr += 2; + if (ptr[0] == CHAR_R || /* (?R) */ + ptr[0] == CHAR_NUMBER_SIGN || /* (?#) */ + IS_DIGIT(ptr[0]) || /* (?n) */ + (ptr[0] == CHAR_MINUS && IS_DIGIT(ptr[1]))) /* (?-n) */ + { + skiptoket = TRUE; + break; + } + + /* Handle (?| and (?imsxJU: which are the only other valid forms. Both + need a new block on the nest stack. */ + + if (top_nest == NULL) top_nest = (nest_save *)(cb->start_workspace); + else if (++top_nest >= end_nests) + { + errorcode = ERR84; + goto FAILED; + } + top_nest->nest_depth = nest_depth; + top_nest->flags = 0; + if ((options & PCRE2_EXTENDED) != 0) top_nest->flags |= NSF_EXTENDED; + if ((options & PCRE2_DUPNAMES) != 0) top_nest->flags |= NSF_DUPNAMES; + + if (*ptr == CHAR_VERTICAL_LINE) + { + top_nest->reset_group = cb->bracount; + top_nest->max_group = cb->bracount; + top_nest->flags |= NSF_RESET; + cb->external_flags |= PCRE2_DUPCAPUSED; + break; + } + + /* Scan options */ + + top_nest->reset_group = 0; + top_nest->max_group = 0; + + set = unset = 0; + optset = &set; + + /* Need only track (?x: and (?J: at this stage */ + + while (*ptr != CHAR_RIGHT_PARENTHESIS && *ptr != CHAR_COLON) + { + switch (*ptr++) + { + case CHAR_MINUS: optset = &unset; break; + + case CHAR_x: *optset |= PCRE2_EXTENDED; break; + + case CHAR_J: + *optset |= PCRE2_DUPNAMES; + cb->external_flags |= PCRE2_JCHANGED; + break; + + case CHAR_i: + case CHAR_m: + case CHAR_s: + case CHAR_U: + break; + + default: errorcode = ERR11; + ptr--; /* Correct the offset */ + goto FAILED; + } + } + + options = (options | set) & (~unset); + + /* If the options ended with ')' this is not the start of a nested + group with option changes, so the options change at this level. If the + previous level set up a nest block, discard the one we have just created. + Otherwise adjust it for the previous level. */ + + if (*ptr == CHAR_RIGHT_PARENTHESIS) + { + nest_depth--; + if (top_nest > (nest_save *)(cb->start_workspace) && + (top_nest-1)->nest_depth == nest_depth) top_nest --; + else top_nest->nest_depth = nest_depth; + } + break; + + /* Skip over a numerical or string argument for a callout. */ + + case CHAR_C: + ptr += 2; + if (ptr[1] == CHAR_RIGHT_PARENTHESIS) break; + if (IS_DIGIT(ptr[1])) + { + while (IS_DIGIT(ptr[1])) ptr++; + } + + /* Handle a string argument */ + + else + { + ptr++; + delimiter = 0; + for (i = 0; PRIV(callout_start_delims)[i] != 0; i++) + { + if (*ptr == PRIV(callout_start_delims)[i]) + { + delimiter = PRIV(callout_end_delims)[i]; + break; + } + } + + if (delimiter == 0) + { + errorcode = ERR82; + goto FAILED; + } + + start = ptr; + do + { + if (++ptr >= cb->end_pattern) + { + errorcode = ERR81; + ptr = start; /* To give a more useful message */ + goto FAILED; + } + if (ptr[0] == delimiter && ptr[1] == delimiter) ptr += 2; + } + while (ptr[0] != delimiter); + } + + /* Check terminating ) */ + + if (ptr[1] != CHAR_RIGHT_PARENTHESIS) + { + errorcode = ERR39; + ptr++; + goto FAILED; + } + break; + + /* Conditional group */ + + case CHAR_LEFT_PARENTHESIS: + if (ptr[3] != CHAR_QUESTION_MARK) /* Not assertion or callout */ + { + nest_depth++; + ptr += 2; + break; + } + + /* Must be an assertion or a callout */ + + switch(ptr[4]) + { + case CHAR_LESS_THAN_SIGN: + if (ptr[5] != CHAR_EXCLAMATION_MARK && ptr[5] != CHAR_EQUALS_SIGN) + goto MISSING_ASSERTION; + /* Fall through */ + + case CHAR_C: + case CHAR_EXCLAMATION_MARK: + case CHAR_EQUALS_SIGN: + ptr++; + break; + + default: + MISSING_ASSERTION: + ptr += 3; /* To improve error message */ + errorcode = ERR28; + goto FAILED; + } + break; + + case CHAR_COLON: + case CHAR_GREATER_THAN_SIGN: + case CHAR_EQUALS_SIGN: + case CHAR_EXCLAMATION_MARK: + case CHAR_AMPERSAND: + case CHAR_PLUS: + ptr += 2; + break; + + case CHAR_P: + if (ptr[3] != CHAR_LESS_THAN_SIGN) + { + ptr += 3; + break; + } + ptr++; + c = CHAR_GREATER_THAN_SIGN; /* Terminator */ + goto DEFINE_NAME; + + case CHAR_LESS_THAN_SIGN: + if (ptr[3] == CHAR_EQUALS_SIGN || ptr[3] == CHAR_EXCLAMATION_MARK) + { + ptr += 3; + break; + } + c = CHAR_GREATER_THAN_SIGN; /* Terminator */ + goto DEFINE_NAME; + + case CHAR_APOSTROPHE: + c = CHAR_APOSTROPHE; /* Terminator */ + + DEFINE_NAME: + name = ptr = ptr + 3; + + if (*ptr == c) /* Empty name */ + { + errorcode = ERR62; + goto FAILED; + } + + if (IS_DIGIT(*ptr)) + { + errorcode = ERR44; /* Group name must start with non-digit */ + goto FAILED; + } + + if (MAX_255(*ptr) && (cb->ctypes[*ptr] & ctype_word) == 0) + { + errorcode = ERR24; + goto FAILED; + } + + /* Advance ptr, set namelen and check its length. */ + READ_NAME(ctype_word, ERR48, errorcode); + + if (*ptr != c) + { + errorcode = ERR42; + goto FAILED; + } + + if (cb->names_found >= MAX_NAME_COUNT) + { + errorcode = ERR49; + goto FAILED; + } + + if (namelen + IMM2_SIZE + 1 > cb->name_entry_size) + cb->name_entry_size = namelen + IMM2_SIZE + 1; + + /* We have a valid name for this capturing group. */ + + cb->bracount++; + + /* Scan the list to check for duplicates. For duplicate names, if the + number is the same, break the loop, which causes the name to be + discarded; otherwise, if DUPNAMES is not set, give an error. + If it is set, allow the name with a different number, but continue + scanning in case this is a duplicate with the same number. For + non-duplicate names, give an error if the number is duplicated. */ + + isdupname = FALSE; + ng = cb->named_groups; + for (i = 0; i < cb->names_found; i++, ng++) + { + if (namelen == ng->length && + PRIV(strncmp)(name, ng->name, namelen) == 0) + { + if (ng->number == cb->bracount) break; + if ((options & PCRE2_DUPNAMES) == 0) + { + errorcode = ERR43; + goto FAILED; + } + isdupname = ng->isdup = TRUE; /* Mark as a duplicate */ + cb->dupnames = TRUE; /* Duplicate names exist */ + } + else if (ng->number == cb->bracount) + { + errorcode = ERR65; + goto FAILED; + } + } + + if (i < cb->names_found) break; /* Ignore duplicate with same number */ + + /* Increase the list size if necessary */ + + if (cb->names_found >= cb->named_group_list_size) + { + int newsize = cb->named_group_list_size * 2; + named_group *newspace = + cb->cx->memctl.malloc(newsize * sizeof(named_group), + cb->cx->memctl.memory_data); + if (newspace == NULL) + { + errorcode = ERR21; + goto FAILED; + } + + memcpy(newspace, cb->named_groups, + cb->named_group_list_size * sizeof(named_group)); + if (cb->named_group_list_size > NAMED_GROUP_LIST_SIZE) + cb->cx->memctl.free((void *)cb->named_groups, + cb->cx->memctl.memory_data); + cb->named_groups = newspace; + cb->named_group_list_size = newsize; + } + + /* Add this name to the list */ + + cb->named_groups[cb->names_found].name = name; + cb->named_groups[cb->names_found].length = namelen; + cb->named_groups[cb->names_found].number = cb->bracount; + cb->named_groups[cb->names_found].isdup = isdupname; + cb->names_found++; + break; + } /* End of (? switch */ + break; /* End of ( handling */ + + /* At an alternation, reset the capture count if we are in a (?| group. */ + + case CHAR_VERTICAL_LINE: + if (top_nest != NULL && top_nest->nest_depth == nest_depth && + (top_nest->flags & NSF_RESET) != 0) + { + if (cb->bracount > top_nest->max_group) + top_nest->max_group = cb->bracount; + cb->bracount = top_nest->reset_group; + } + break; + + /* At a right parenthesis, reset the capture count to the maximum if we + are in a (?| group and/or reset the extended option. */ + + case CHAR_RIGHT_PARENTHESIS: + if (top_nest != NULL && top_nest->nest_depth == nest_depth) + { + if ((top_nest->flags & NSF_RESET) != 0 && + top_nest->max_group > cb->bracount) + cb->bracount = top_nest->max_group; + if ((top_nest->flags & NSF_EXTENDED) != 0) options |= PCRE2_EXTENDED; + else options &= ~PCRE2_EXTENDED; + if ((top_nest->flags & NSF_DUPNAMES) != 0) options |= PCRE2_DUPNAMES; + else options &= ~PCRE2_DUPNAMES; + if (top_nest == (nest_save *)(cb->start_workspace)) top_nest = NULL; + else top_nest--; + } + if (nest_depth > 0) nest_depth--; /* Can be 0 for unmatched ) */ + break; + } + } + +cb->final_bracount = cb->bracount; +return 0; + +FAILED: +*ptrptr = ptr; +return errorcode; +} + + + +/************************************************* +* Compile one branch * +*************************************************/ + +/* Scan the pattern, compiling it into the a vector. If the options are +changed during the branch, the pointer is used to change the external options +bits. This function is used during the pre-compile phase when we are trying +to find out the amount of memory needed, as well as during the real compile +phase. The value of lengthptr distinguishes the two phases. + +Arguments: + optionsptr pointer to the option bits + codeptr points to the pointer to the current code point + ptrptr points to the current pattern pointer + errorcodeptr points to error code variable + firstcuptr place to put the first required code unit + firstcuflagsptr place to put the first code unit flags, or a negative number + reqcuptr place to put the last required code unit + reqcuflagsptr place to put the last required code unit flags, or a negative number + bcptr points to current branch chain + cond_depth conditional nesting depth + cb contains pointers to tables etc. + lengthptr NULL during the real compile phase + points to length accumulator during pre-compile phase + +Returns: TRUE on success + FALSE, with *errorcodeptr set non-zero on error +*/ + +static BOOL +compile_branch(uint32_t *optionsptr, PCRE2_UCHAR **codeptr, + PCRE2_SPTR *ptrptr, int *errorcodeptr, + uint32_t *firstcuptr, int32_t *firstcuflagsptr, + uint32_t *reqcuptr, int32_t *reqcuflagsptr, + branch_chain *bcptr, int cond_depth, + compile_block *cb, size_t *lengthptr) +{ +int repeat_min = 0, repeat_max = 0; /* To please picky compilers */ +int bravalue = 0; +uint32_t greedy_default, greedy_non_default; +uint32_t repeat_type, op_type; +uint32_t options = *optionsptr; /* May change dynamically */ +uint32_t firstcu, reqcu; +int32_t firstcuflags, reqcuflags; +uint32_t zeroreqcu, zerofirstcu; +int32_t zeroreqcuflags, zerofirstcuflags; +int32_t req_caseopt, reqvary, tempreqvary; +int after_manual_callout = 0; +int escape; +size_t length_prevgroup = 0; +register uint32_t c; +register PCRE2_UCHAR *code = *codeptr; +PCRE2_UCHAR *last_code = code; +PCRE2_UCHAR *orig_code = code; +PCRE2_UCHAR *tempcode; +BOOL inescq = FALSE; +BOOL groupsetfirstcu = FALSE; +PCRE2_SPTR ptr = *ptrptr; +PCRE2_SPTR tempptr; +PCRE2_UCHAR *previous = NULL; +PCRE2_UCHAR *previous_callout = NULL; +uint8_t classbits[32]; + +/* We can fish out the UTF setting once and for all into a BOOL, but we must +not do this for other options (e.g. PCRE2_EXTENDED) because they may change +dynamically as we process the pattern. */ + +#ifdef SUPPORT_UNICODE +BOOL utf = (options & PCRE2_UTF) != 0; +#if PCRE2_CODE_UNIT_WIDTH != 32 +PCRE2_UCHAR utf_units[6]; /* For setting up multi-cu chars */ +#endif + +#else /* No UTF support */ +BOOL utf = FALSE; +#endif + +/* Helper variables for OP_XCLASS opcode (for characters > 255). We define +class_uchardata always so that it can be passed to add_to_class() always, +though it will not be used in non-UTF 8-bit cases. This avoids having to supply +alternative calls for the different cases. */ + +PCRE2_UCHAR *class_uchardata; +#ifdef SUPPORT_WIDE_CHARS +BOOL xclass; +PCRE2_UCHAR *class_uchardata_base; +#endif + +/* Set up the default and non-default settings for greediness */ + +greedy_default = ((options & PCRE2_UNGREEDY) != 0); +greedy_non_default = greedy_default ^ 1; + +/* Initialize no first unit, no required unit. REQ_UNSET means "no char +matching encountered yet". It gets changed to REQ_NONE if we hit something that +matches a non-fixed first unit; reqcu just remains unset if we never find one. + +When we hit a repeat whose minimum is zero, we may have to adjust these values +to take the zero repeat into account. This is implemented by setting them to +zerofirstcu and zeroreqcu when such a repeat is encountered. The individual +item types that can be repeated set these backoff variables appropriately. */ + +firstcu = reqcu = zerofirstcu = zeroreqcu = 0; +firstcuflags = reqcuflags = zerofirstcuflags = zeroreqcuflags = REQ_UNSET; + +/* The variable req_caseopt contains either the REQ_CASELESS value or zero, +according to the current setting of the caseless flag. The REQ_CASELESS value +leaves the lower 28 bit empty. It is added into the firstcu or reqcu variables +to record the case status of the value. This is used only for ASCII characters. +*/ + +req_caseopt = ((options & PCRE2_CASELESS) != 0)? REQ_CASELESS:0; + +/* Switch on next character until the end of the branch */ + +for (;; ptr++) + { + BOOL negate_class; + BOOL should_flip_negation; + BOOL match_all_or_no_wide_chars; + BOOL possessive_quantifier; + BOOL is_quantifier; + BOOL is_recurse; + BOOL is_dupname; + BOOL reset_bracount; + int class_has_8bitchar; + int class_one_char; +#ifdef SUPPORT_WIDE_CHARS + BOOL xclass_has_prop; +#endif + int recno; /* Must be signed */ + int refsign; /* Must be signed */ + int terminator; /* Must be signed */ + unsigned int mclength; + unsigned int tempbracount; + uint32_t ec; + uint32_t newoptions; + uint32_t skipunits; + uint32_t subreqcu, subfirstcu; + int32_t subreqcuflags, subfirstcuflags; /* Must be signed */ + PCRE2_UCHAR mcbuffer[8]; + + /* Get next character in the pattern */ + + c = *ptr; + + /* If we are at the end of a nested substitution, revert to the outer level + string. Nesting only happens one or two levels deep, and the inserted string + is always zero terminated. */ + + if (c == CHAR_NULL && cb->nestptr[0] != NULL) + { + ptr = cb->nestptr[0]; + cb->nestptr[0] = cb->nestptr[1]; + cb->nestptr[1] = NULL; + c = *ptr; + } + + /* If we are in the pre-compile phase, accumulate the length used for the + previous cycle of this loop. */ + + if (lengthptr != NULL) + { + if (code > cb->start_workspace + cb->workspace_size - + WORK_SIZE_SAFETY_MARGIN) /* Check for overrun */ + { + *errorcodeptr = (code >= cb->start_workspace + cb->workspace_size)? + ERR52 : ERR86; + goto FAILED; + } + + /* There is at least one situation where code goes backwards: this is the + case of a zero quantifier after a class (e.g. [ab]{0}). At compile time, + the class is simply eliminated. However, it is created first, so we have to + allow memory for it. Therefore, don't ever reduce the length at this point. + */ + + if (code < last_code) code = last_code; + + /* Paranoid check for integer overflow */ + + if (OFLOW_MAX - *lengthptr < (size_t)(code - last_code)) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += code - last_code; + + /* If "previous" is set and it is not at the start of the work space, move + it back to there, in order to avoid filling up the work space. Otherwise, + if "previous" is NULL, reset the current code pointer to the start. */ + + if (previous != NULL) + { + if (previous > orig_code) + { + memmove(orig_code, previous, CU2BYTES(code - previous)); + code -= previous - orig_code; + previous = orig_code; + } + } + else code = orig_code; + + /* Remember where this code item starts so we can pick up the length + next time round. */ + + last_code = code; + } + + /* Before doing anything else we must handle all the special items that do + nothing, and which may come between an item and its quantifier. Otherwise, + when auto-callouts are enabled, a callout gets incorrectly inserted before + the quantifier is recognized. After recognizing a "do nothing" item, restart + the loop in case another one follows. */ + + /* If c is not NULL we are not at the end of the pattern. If it is NULL, we + may still be in the pattern with a NULL data item. In these cases, if we are + in \Q...\E, check for the \E that ends the literal string; if not, we have a + literal character. If not in \Q...\E, an isolated \E is ignored. */ + + if (c != CHAR_NULL || ptr < cb->end_pattern) + { + if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E) + { + inescq = FALSE; + ptr++; + continue; + } + else if (inescq) /* Literal character */ + { + if (previous_callout != NULL) + { + if (lengthptr == NULL) /* Don't attempt in pre-compile phase */ + complete_callout(previous_callout, ptr, cb); + previous_callout = NULL; + } + if ((options & PCRE2_AUTO_CALLOUT) != 0) + { + previous_callout = code; + code = auto_callout(code, ptr, cb); + } + goto NORMAL_CHAR; + } + + /* Check for the start of a \Q...\E sequence. We must do this here rather + than later in case it is immediately followed by \E, which turns it into a + "do nothing" sequence. */ + + if (c == CHAR_BACKSLASH && ptr[1] == CHAR_Q) + { + inescq = TRUE; + ptr++; + continue; + } + } + + /* In extended mode, skip white space and #-comments that end at newline. */ + + if ((options & PCRE2_EXTENDED) != 0) + { + PCRE2_SPTR wscptr = ptr; + while (MAX_255(c) && (cb->ctypes[c] & ctype_space) != 0) c = *(++ptr); + if (c == CHAR_NUMBER_SIGN) + { + ptr++; + while (ptr < cb->end_pattern) + { + if (IS_NEWLINE(ptr)) /* For non-fixed-length newline cases, */ + { /* IS_NEWLINE sets cb->nllen. */ + ptr += cb->nllen; + break; + } + ptr++; +#ifdef SUPPORT_UNICODE + if (utf) FORWARDCHAR(ptr); +#endif + } + } + + /* If we skipped any characters, restart the loop. Otherwise, we didn't see + a comment. */ + + if (ptr > wscptr) + { + ptr--; + continue; + } + } + + /* Skip over (?# comments. */ + + if (c == CHAR_LEFT_PARENTHESIS && ptr[1] == CHAR_QUESTION_MARK && + ptr[2] == CHAR_NUMBER_SIGN) + { + ptr += 3; + while (ptr < cb->end_pattern && *ptr != CHAR_RIGHT_PARENTHESIS) ptr++; + if (*ptr != CHAR_RIGHT_PARENTHESIS) + { + *errorcodeptr = ERR18; + goto FAILED; + } + continue; + } + + /* End of processing "do nothing" items. See if the next thing is a + quantifier. */ + + is_quantifier = + c == CHAR_ASTERISK || c == CHAR_PLUS || c == CHAR_QUESTION_MARK || + (c == CHAR_LEFT_CURLY_BRACKET && is_counted_repeat(ptr+1)); + + /* Fill in length of a previous callout and create an auto callout if + required, except when the next thing is a quantifier or when processing a + property substitution string for \w etc in UCP mode. */ + + if (!is_quantifier && cb->nestptr[0] == NULL) + { + if (previous_callout != NULL && after_manual_callout-- <= 0) + { + if (lengthptr == NULL) /* Don't attempt in pre-compile phase */ + complete_callout(previous_callout, ptr, cb); + previous_callout = NULL; + } + + if ((options & PCRE2_AUTO_CALLOUT) != 0) + { + previous_callout = code; + code = auto_callout(code, ptr, cb); + } + } + + /* Process the next pattern item. */ + + switch(c) + { + /* ===================================================================*/ + /* The branch terminates at string end or | or ) */ + + case CHAR_NULL: + if (ptr < cb->end_pattern) goto NORMAL_CHAR; /* Zero data character */ + /* Fall through */ + + case CHAR_VERTICAL_LINE: + case CHAR_RIGHT_PARENTHESIS: + *firstcuptr = firstcu; + *firstcuflagsptr = firstcuflags; + *reqcuptr = reqcu; + *reqcuflagsptr = reqcuflags; + *codeptr = code; + *ptrptr = ptr; + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < (size_t)(code - last_code)) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += code - last_code; /* To include callout length */ + } + return TRUE; + + + /* ===================================================================*/ + /* Handle single-character metacharacters. In multiline mode, ^ disables + the setting of any following char as a first character. */ + + case CHAR_CIRCUMFLEX_ACCENT: + previous = NULL; + if ((options & PCRE2_MULTILINE) != 0) + { + if (firstcuflags == REQ_UNSET) + zerofirstcuflags = firstcuflags = REQ_NONE; + *code++ = OP_CIRCM; + } + else *code++ = OP_CIRC; + break; + + case CHAR_DOLLAR_SIGN: + previous = NULL; + *code++ = ((options & PCRE2_MULTILINE) != 0)? OP_DOLLM : OP_DOLL; + break; + + /* There can never be a first char if '.' is first, whatever happens about + repeats. The value of reqcu doesn't change either. */ + + case CHAR_DOT: + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + previous = code; + *code++ = ((options & PCRE2_DOTALL) != 0)? OP_ALLANY: OP_ANY; + break; + + + /* ===================================================================*/ + /* Character classes. If the included characters are all < 256, we build a + 32-byte bitmap of the permitted characters, except in the special case + where there is only one such character. For negated classes, we build the + map as usual, then invert it at the end. However, we use a different opcode + so that data characters > 255 can be handled correctly. + + If the class contains characters outside the 0-255 range, a different + opcode is compiled. It may optionally have a bit map for characters < 256, + but those above are are explicitly listed afterwards. A flag byte tells + whether the bitmap is present, and whether this is a negated class or not. + + An isolated ']' character is not treated specially, so is just another data + character. In earlier versions of PCRE that used the original API there was + a "JavaScript compatibility mode" in which it gave an error. However, + JavaScript itself has changed in this respect so there is no longer any + need for this special handling. + + In another (POSIX) regex library, the ugly syntax [[:<:]] and [[:>:]] is + used for "start of word" and "end of word". As these are otherwise illegal + sequences, we don't break anything by recognizing them. They are replaced + by \b(?=\w) and \b(?<=\w) respectively. This can only happen at the top + nesting level, as no other inserted sequences will contains these oddities. + Sequences like [a[:<:]] are erroneous and are handled by the normal code + below. */ + + case CHAR_LEFT_SQUARE_BRACKET: + if (PRIV(strncmp_c8)(ptr+1, STRING_WEIRD_STARTWORD, 6) == 0) + { + cb->nestptr[0] = ptr + 7; + ptr = sub_start_of_word; /* Do not combine these statements; clang's */ + ptr--; /* sanitizer moans about a negative index. */ + continue; + } + + if (PRIV(strncmp_c8)(ptr+1, STRING_WEIRD_ENDWORD, 6) == 0) + { + cb->nestptr[0] = ptr + 7; + ptr = sub_end_of_word; /* Do not combine these statements; clang's */ + ptr--; /* sanitizer moans about a negative index. */ + continue; + } + + /* Handle a real character class. */ + + previous = code; + + /* PCRE supports POSIX class stuff inside a class. Perl gives an error if + they are encountered at the top level, so we'll do that too. */ + + if ((ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT || + ptr[1] == CHAR_EQUALS_SIGN) && + check_posix_syntax(ptr, &tempptr)) + { + *errorcodeptr = (ptr[1] == CHAR_COLON)? ERR12 : ERR13; + goto FAILED; + } + + /* If the first character is '^', set the negation flag and skip it. Also, + if the first few characters (either before or after ^) are \Q\E or \E we + skip them too. This makes for compatibility with Perl. */ + + negate_class = FALSE; + for (;;) + { + c = *(++ptr); + if (c == CHAR_BACKSLASH) + { + if (ptr[1] == CHAR_E) + ptr++; + else if (PRIV(strncmp_c8)(ptr + 1, STR_Q STR_BACKSLASH STR_E, 3) == 0) + ptr += 3; + else + break; + } + else if (!negate_class && c == CHAR_CIRCUMFLEX_ACCENT) + negate_class = TRUE; + else break; + } + + /* Empty classes are allowed if PCRE2_ALLOW_EMPTY_CLASS is set. Otherwise, + an initial ']' is taken as a data character -- the code below handles + that. When empty classes are allowed, [] must always fail, so generate + OP_FAIL, whereas [^] must match any character, so generate OP_ALLANY. */ + + if (c == CHAR_RIGHT_SQUARE_BRACKET && + (cb->external_options & PCRE2_ALLOW_EMPTY_CLASS) != 0) + { + *code++ = negate_class? OP_ALLANY : OP_FAIL; + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + break; + } + + /* If a non-extended class contains a negative special such as \S, we need + to flip the negation flag at the end, so that support for characters > 255 + works correctly (they are all included in the class). An extended class may + need to insert specific matching or non-matching code for wide characters. + */ + + should_flip_negation = match_all_or_no_wide_chars = FALSE; + + /* Extended class (xclass) will be used when characters > 255 + might match. */ + +#ifdef SUPPORT_WIDE_CHARS + xclass = FALSE; + class_uchardata = code + LINK_SIZE + 2; /* For XCLASS items */ + class_uchardata_base = class_uchardata; /* Save the start */ +#endif + + /* For optimization purposes, we track some properties of the class: + class_has_8bitchar will be non-zero if the class contains at least one 256 + character with a code point less than 256; class_one_char will be 1 if the + class contains just one character; xclass_has_prop will be TRUE if Unicode + property checks are present in the class. */ + + class_has_8bitchar = 0; + class_one_char = 0; +#ifdef SUPPORT_WIDE_CHARS + xclass_has_prop = FALSE; +#endif + + /* Initialize the 256-bit (32-byte) bit map to all zeros. We build the map + in a temporary bit of memory, in case the class contains fewer than two + 8-bit characters because in that case the compiled code doesn't use the bit + map. */ + + memset(classbits, 0, 32 * sizeof(uint8_t)); + + /* Process characters until ] is reached. As the test is at the end of the + loop, an initial ] is taken as a data character. At the start of the loop, + c contains the first code unit of the character. If it is zero, check for + the end of the pattern, to allow binary zero as data. */ + + for(;;) + { + PCRE2_SPTR oldptr; +#ifdef EBCDIC + BOOL range_is_literal = TRUE; +#endif + + if (c == CHAR_NULL && ptr >= cb->end_pattern) + { + *errorcodeptr = ERR6; /* Missing terminating ']' */ + goto FAILED; + } + +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(c)) + { /* Braces are required because the */ + GETCHARLEN(c, ptr, ptr); /* macro generates multiple statements */ + } +#endif + + /* Inside \Q...\E everything is literal except \E */ + + if (inescq) + { + if (c == CHAR_BACKSLASH && ptr[1] == CHAR_E) /* If we are at \E */ + { + inescq = FALSE; /* Reset literal state */ + ptr++; /* Skip the 'E' */ + goto CONTINUE_CLASS; /* Carry on with next char */ + } + goto CHECK_RANGE; /* Could be range if \E follows */ + } + + /* Handle POSIX class names. Perl allows a negation extension of the + form [:^name:]. A square bracket that doesn't match the syntax is + treated as a literal. We also recognize the POSIX constructions + [.ch.] and [=ch=] ("collating elements") and fault them, as Perl + 5.6 and 5.8 do. */ + + if (c == CHAR_LEFT_SQUARE_BRACKET && + (ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT || + ptr[1] == CHAR_EQUALS_SIGN) && check_posix_syntax(ptr, &tempptr)) + { + BOOL local_negate = FALSE; + int posix_class, taboffset, tabopt; + register const uint8_t *cbits = cb->cbits; + uint8_t pbits[32]; + + if (ptr[1] != CHAR_COLON) + { + *errorcodeptr = ERR13; + goto FAILED; + } + + ptr += 2; + if (*ptr == CHAR_CIRCUMFLEX_ACCENT) + { + local_negate = TRUE; + should_flip_negation = TRUE; /* Note negative special */ + ptr++; + } + + posix_class = check_posix_name(ptr, (int)(tempptr - ptr)); + if (posix_class < 0) + { + *errorcodeptr = ERR30; + goto FAILED; + } + + /* If matching is caseless, upper and lower are converted to + alpha. This relies on the fact that the class table starts with + alpha, lower, upper as the first 3 entries. */ + + if ((options & PCRE2_CASELESS) != 0 && posix_class <= 2) + posix_class = 0; + + /* When PCRE2_UCP is set, some of the POSIX classes are converted to + different escape sequences that use Unicode properties \p or \P. Others + that are not available via \p or \P generate XCL_PROP/XCL_NOTPROP + directly. UCP support is not available unless UTF support is.*/ + +#ifdef SUPPORT_UNICODE + if ((options & PCRE2_UCP) != 0) + { + unsigned int ptype = 0; + int pc = posix_class + ((local_negate)? POSIX_SUBSIZE/2 : 0); + + /* The posix_substitutes table specifies which POSIX classes can be + converted to \p or \P items. This can only happen at top nestling + level, as there will never be a POSIX class in a string that is + substituted for something else. */ + + if (posix_substitutes[pc] != NULL) + { + cb->nestptr[0] = tempptr + 1; + ptr = posix_substitutes[pc] - 1; + goto CONTINUE_CLASS; + } + + /* There are three other classes that generate special property calls + that are recognized only in an XCLASS. */ + + else switch(posix_class) + { + case PC_GRAPH: + ptype = PT_PXGRAPH; + /* Fall through */ + case PC_PRINT: + if (ptype == 0) ptype = PT_PXPRINT; + /* Fall through */ + case PC_PUNCT: + if (ptype == 0) ptype = PT_PXPUNCT; + *class_uchardata++ = local_negate? XCL_NOTPROP : XCL_PROP; + *class_uchardata++ = ptype; + *class_uchardata++ = 0; + xclass_has_prop = TRUE; + ptr = tempptr + 1; + goto CONTINUE_CLASS; + + /* For the other POSIX classes (ascii, xdigit) we are going to fall + through to the non-UCP case and build a bit map for characters with + code points less than 256. However, if we are in a negated POSIX + class, characters with code points greater than 255 must either all + match or all not match, depending on whether the whole class is not + or is negated. For example, for [[:^ascii:]... they must all match, + whereas for [^[:^xdigit:]... they must not. + + In the special case where there are no xclass items, this is + automatically handled by the use of OP_CLASS or OP_NCLASS, but an + explicit range is needed for OP_XCLASS. Setting a flag here causes + the range to be generated later when it is known that OP_XCLASS is + required. */ + + default: + match_all_or_no_wide_chars |= local_negate; + break; + } + } +#endif /* SUPPORT_UNICODE */ + + /* In the non-UCP case, or when UCP makes no difference, we build the + bit map for the POSIX class in a chunk of local store because we may be + adding and subtracting from it, and we don't want to subtract bits that + may be in the main map already. At the end we or the result into the + bit map that is being built. */ + + posix_class *= 3; + + /* Copy in the first table (always present) */ + + memcpy(pbits, cbits + posix_class_maps[posix_class], + 32 * sizeof(uint8_t)); + + /* If there is a second table, add or remove it as required. */ + + taboffset = posix_class_maps[posix_class + 1]; + tabopt = posix_class_maps[posix_class + 2]; + + if (taboffset >= 0) + { + if (tabopt >= 0) + for (c = 0; c < 32; c++) pbits[c] |= cbits[c + taboffset]; + else + for (c = 0; c < 32; c++) pbits[c] &= ~cbits[c + taboffset]; + } + + /* Now see if we need to remove any special characters. An option + value of 1 removes vertical space and 2 removes underscore. */ + + if (tabopt < 0) tabopt = -tabopt; + if (tabopt == 1) pbits[1] &= ~0x3c; + else if (tabopt == 2) pbits[11] &= 0x7f; + + /* Add the POSIX table or its complement into the main table that is + being built and we are done. */ + + if (local_negate) + for (c = 0; c < 32; c++) classbits[c] |= ~pbits[c]; + else + for (c = 0; c < 32; c++) classbits[c] |= pbits[c]; + + ptr = tempptr + 1; + /* Every class contains at least one < 256 character. */ + class_has_8bitchar = 1; + /* Every class contains at least two characters. */ + class_one_char = 2; + goto CONTINUE_CLASS; /* End of POSIX syntax handling */ + } + + /* Backslash may introduce a single character, or it may introduce one + of the specials, which just set a flag. The sequence \b is a special + case. Inside a class (and only there) it is treated as backspace. We + assume that other escapes have more than one character in them, so + speculatively set both class_has_8bitchar and class_one_char bigger + than one. Unrecognized escapes fall through and are faulted. */ + + if (c == CHAR_BACKSLASH) + { + escape = PRIV(check_escape)(&ptr, cb->end_pattern, &ec, errorcodeptr, + options, TRUE, cb); + if (*errorcodeptr != 0) goto FAILED; + if (escape == 0) /* Escaped single char */ + { + c = ec; +#ifdef EBCDIC + range_is_literal = FALSE; +#endif + } + else if (escape == ESC_b) c = CHAR_BS; /* \b is backspace in a class */ + else if (escape == ESC_N) /* \N is not supported in a class */ + { + *errorcodeptr = ERR71; + goto FAILED; + } + else if (escape == ESC_Q) /* Handle start of quoted string */ + { + if (ptr[1] == CHAR_BACKSLASH && ptr[2] == CHAR_E) + { + ptr += 2; /* avoid empty string */ + } + else inescq = TRUE; + goto CONTINUE_CLASS; + } + else if (escape == ESC_E) goto CONTINUE_CLASS; /* Ignore orphan \E */ + + else /* Handle \d-type escapes */ + { + register const uint8_t *cbits = cb->cbits; + /* Every class contains at least two < 256 characters. */ + class_has_8bitchar++; + /* Every class contains at least two characters. */ + class_one_char += 2; + + switch (escape) + { +#ifdef SUPPORT_UNICODE + case ESC_du: /* These are the values given for \d etc */ + case ESC_DU: /* when PCRE2_UCP is set. We replace the */ + case ESC_wu: /* escape sequence with an appropriate \p */ + case ESC_WU: /* or \P to test Unicode properties instead */ + case ESC_su: /* of the default ASCII testing. This might be */ + case ESC_SU: /* a 2nd-level nesting for [[:<:]] or [[:>:]]. */ + cb->nestptr[1] = cb->nestptr[0]; + cb->nestptr[0] = ptr; + ptr = substitutes[escape - ESC_DU] - 1; /* Just before substitute */ + class_has_8bitchar--; /* Undo! */ + break; +#endif + case ESC_d: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_digit]; + break; + + case ESC_D: + should_flip_negation = TRUE; + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_digit]; + break; + + case ESC_w: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_word]; + break; + + case ESC_W: + should_flip_negation = TRUE; + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_word]; + break; + + /* Perl 5.004 onwards omitted VT from \s, but restored it at Perl + 5.18. Before PCRE 8.34, we had to preserve the VT bit if it was + previously set by something earlier in the character class. + Luckily, the value of CHAR_VT is 0x0b in both ASCII and EBCDIC, so + we could just adjust the appropriate bit. From PCRE 8.34 we no + longer treat \s and \S specially. */ + + case ESC_s: + for (c = 0; c < 32; c++) classbits[c] |= cbits[c+cbit_space]; + break; + + case ESC_S: + should_flip_negation = TRUE; + for (c = 0; c < 32; c++) classbits[c] |= ~cbits[c+cbit_space]; + break; + + /* The rest apply in both UCP and non-UCP cases. */ + + case ESC_h: + (void)add_list_to_class(classbits, &class_uchardata, options, cb, + PRIV(hspace_list), NOTACHAR); + break; + + case ESC_H: + (void)add_not_list_to_class(classbits, &class_uchardata, options, + cb, PRIV(hspace_list)); + break; + + case ESC_v: + (void)add_list_to_class(classbits, &class_uchardata, options, cb, + PRIV(vspace_list), NOTACHAR); + break; + + case ESC_V: + (void)add_not_list_to_class(classbits, &class_uchardata, options, + cb, PRIV(vspace_list)); + break; + + case ESC_p: + case ESC_P: +#ifdef SUPPORT_UNICODE + { + BOOL negated; + unsigned int ptype = 0, pdata = 0; + if (!get_ucp(&ptr, &negated, &ptype, &pdata, errorcodeptr, cb)) + goto FAILED; + *class_uchardata++ = ((escape == ESC_p) != negated)? + XCL_PROP : XCL_NOTPROP; + *class_uchardata++ = ptype; + *class_uchardata++ = pdata; + xclass_has_prop = TRUE; + class_has_8bitchar--; /* Undo! */ + } + break; +#else + *errorcodeptr = ERR45; + goto FAILED; +#endif + /* Unrecognized escapes are faulted. */ + + default: + *errorcodeptr = ERR7; + goto FAILED; + } + + /* Handled \d-type escape */ + + goto CONTINUE_CLASS; + } + + /* Control gets here if the escape just defined a single character. + This is in c and may be greater than 256. */ + + escape = 0; + } /* End of backslash handling */ + + /* A character may be followed by '-' to form a range. However, Perl does + not permit ']' to be the end of the range. A '-' character at the end is + treated as a literal. Perl ignores orphaned \E sequences entirely. The + code for handling \Q and \E is messy. */ + + CHECK_RANGE: + while (ptr[1] == CHAR_BACKSLASH && ptr[2] == CHAR_E) + { + inescq = FALSE; + ptr += 2; + } + oldptr = ptr; + + /* Remember if \r or \n were explicitly used */ + + if (c == CHAR_CR || c == CHAR_NL) cb->external_flags |= PCRE2_HASCRORLF; + + /* Check for range */ + + if (!inescq && ptr[1] == CHAR_MINUS) + { + uint32_t d; + ptr += 2; + while (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_E) ptr += 2; + + /* If we hit \Q (not followed by \E) at this point, go into escaped + mode. */ + + while (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_Q) + { + ptr += 2; + if (*ptr == CHAR_BACKSLASH && ptr[1] == CHAR_E) + { ptr += 2; continue; } + inescq = TRUE; + break; + } + + /* Minus (hyphen) at the end of a class is treated as a literal, so put + back the pointer and jump to handle the character that preceded it. */ + + if (*ptr == CHAR_NULL || (!inescq && *ptr == CHAR_RIGHT_SQUARE_BRACKET)) + { + ptr = oldptr; + goto CLASS_SINGLE_CHARACTER; + } + + /* Otherwise, we have a potential range; pick up the next character */ + +#ifdef SUPPORT_UNICODE + if (utf) + { /* Braces are required because the */ + GETCHARLEN(d, ptr, ptr); /* macro generates multiple statements */ + } + else +#endif + d = *ptr; /* Not UTF mode */ + + /* The second part of a range can be a single-character escape + sequence, but not any of the other escapes. Perl treats a hyphen as a + literal in such circumstances. However, in Perl's warning mode, a + warning is given, so PCRE now faults it as it is almost certainly a + mistake on the user's part. */ + + if (!inescq) + { + if (d == CHAR_BACKSLASH) + { + int descape; + descape = PRIV(check_escape)(&ptr, cb->end_pattern, &d, + errorcodeptr, options, TRUE, cb); + if (*errorcodeptr != 0) goto FAILED; +#ifdef EBCDIC + range_is_literal = FALSE; +#endif + /* 0 means a character was put into d; \b is backspace; any other + special causes an error. */ + + if (descape != 0) + { + if (descape == ESC_b) d = CHAR_BS; else + { + *errorcodeptr = ERR50; + goto FAILED; + } + } + } + + /* A hyphen followed by a POSIX class is treated in the same way. */ + + else if (d == CHAR_LEFT_SQUARE_BRACKET && + (ptr[1] == CHAR_COLON || ptr[1] == CHAR_DOT || + ptr[1] == CHAR_EQUALS_SIGN) && + check_posix_syntax(ptr, &tempptr)) + { + *errorcodeptr = ERR50; + goto FAILED; + } + } + + /* Check that the two values are in the correct order. Optimize + one-character ranges. */ + + if (d < c) + { + *errorcodeptr = ERR8; + goto FAILED; + } + if (d == c) goto CLASS_SINGLE_CHARACTER; /* A few lines below */ + + /* We have found a character range, so single character optimizations + cannot be done anymore. Any value greater than 1 indicates that there + is more than one character. */ + + class_one_char = 2; + + /* Remember an explicit \r or \n, and add the range to the class. */ + + if (d == CHAR_CR || d == CHAR_NL) cb->external_flags |= PCRE2_HASCRORLF; + + /* In an EBCDIC environment, Perl treats alphabetic ranges specially + because there are holes in the encoding, and simply using the range A-Z + (for example) would include the characters in the holes. This applies + only to literal ranges; [\xC1-\xE9] is different to [A-Z]. */ + +#ifdef EBCDIC + if (range_is_literal && + (cb->ctypes[c] & ctype_letter) != 0 && + (cb->ctypes[d] & ctype_letter) != 0 && + (c <= CHAR_z) == (d <= CHAR_z)) + { + uint32_t uc = (c <= CHAR_z)? 0 : 64; + uint32_t C = c - uc; + uint32_t D = d - uc; + + if (C <= CHAR_i) + { + class_has_8bitchar += + add_to_class(classbits, &class_uchardata, options, cb, C + uc, + ((D < CHAR_i)? D : CHAR_i) + uc); + C = CHAR_j; + } + + if (C <= D && C <= CHAR_r) + { + class_has_8bitchar += + add_to_class(classbits, &class_uchardata, options, cb, C + uc, + ((D < CHAR_r)? D : CHAR_r) + uc); + C = CHAR_s; + } + + if (C <= D) + { + class_has_8bitchar += + add_to_class(classbits, &class_uchardata, options, cb, C + uc, + D + uc); + } + } + else +#endif + class_has_8bitchar += + add_to_class(classbits, &class_uchardata, options, cb, c, d); + goto CONTINUE_CLASS; /* Go get the next char in the class */ + } + + /* Handle a single character - we can get here for a normal non-escape + char, or after \ that introduces a single character or for an apparent + range that isn't. Only the value 1 matters for class_one_char, so don't + increase it if it is already 2 or more ... just in case there's a class + with a zillion characters in it. */ + + CLASS_SINGLE_CHARACTER: + if (class_one_char < 2) class_one_char++; + + /* If class_one_char is 1 and xclass_has_prop is false, we have the first + single character in the class, and there have been no prior ranges, or + XCLASS items generated by escapes. If this is the final character in the + class, we can optimize by turning the item into a 1-character OP_CHAR[I] + if it's positive, or OP_NOT[I] if it's negative. In the positive case, it + can cause firstcu to be set. Otherwise, there can be no first char if + this item is first, whatever repeat count may follow. In the case of + reqcu, save the previous value for reinstating. */ + + if (!inescq && +#ifdef SUPPORT_UNICODE + !xclass_has_prop && +#endif + class_one_char == 1 && ptr[1] == CHAR_RIGHT_SQUARE_BRACKET) + { + ptr++; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + + if (negate_class) + { +#ifdef SUPPORT_UNICODE + int d; +#endif + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + + /* For caseless UTF mode, check whether this character has more than + one other case. If so, generate a special OP_NOTPROP item instead of + OP_NOTI. */ + +#ifdef SUPPORT_UNICODE + if (utf && (options & PCRE2_CASELESS) != 0 && + (d = UCD_CASESET(c)) != 0) + { + *code++ = OP_NOTPROP; + *code++ = PT_CLIST; + *code++ = d; + } + else +#endif + /* Char has only one other case, or UCP not available */ + + { + *code++ = ((options & PCRE2_CASELESS) != 0)? OP_NOTI: OP_NOT; + code += PUTCHAR(c, code); + } + + /* We are finished with this character class */ + + goto END_CLASS; + } + + /* For a single, positive character, get the value into mcbuffer, and + then we can handle this with the normal one-character code. */ + + mclength = PUTCHAR(c, mcbuffer); + goto ONE_CHAR; + } /* End of 1-char optimization */ + + /* There is more than one character in the class, or an XCLASS item + has been generated. Add this character to the class. */ + + class_has_8bitchar += + add_to_class(classbits, &class_uchardata, options, cb, c, c); + + /* Continue to the next character in the class. Closing square bracket + not within \Q..\E ends the class. A NULL character terminates a + nested substitution string, but may be a data character in the main + pattern (tested at the start of this loop). */ + + CONTINUE_CLASS: + c = *(++ptr); + if (c == CHAR_NULL && cb->nestptr[0] != NULL) + { + ptr = cb->nestptr[0]; + cb->nestptr[0] = cb->nestptr[1]; + cb->nestptr[1] = NULL; + c = *(++ptr); + } + +#ifdef SUPPORT_WIDE_CHARS + /* If any wide characters have been encountered, set xclass = TRUE. Then, + in the pre-compile phase, accumulate the length of the wide characters + and reset the pointer. This is so that very large classes that contain a + zillion wide characters do not overwrite the work space (which is on the + stack). */ + + if (class_uchardata > class_uchardata_base) + { + xclass = TRUE; + if (lengthptr != NULL) + { + *lengthptr += class_uchardata - class_uchardata_base; + class_uchardata = class_uchardata_base; + } + } +#endif + /* An unescaped ] ends the class */ + + if (c == CHAR_RIGHT_SQUARE_BRACKET && !inescq) break; + } /* End of main class-processing loop */ + + /* If this is the first thing in the branch, there can be no first char + setting, whatever the repeat count. Any reqcu setting must remain + unchanged after any kind of repeat. */ + + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + + /* If there are characters with values > 255, or Unicode property settings + (\p or \P), we have to compile an extended class, with its own opcode, + unless there were no property settings and there was a negated special such + as \S in the class, and PCRE2_UCP is not set, because in that case all + characters > 255 are in or not in the class, so any that were explicitly + given as well can be ignored. + + In the UCP case, if certain negated POSIX classes ([:^ascii:] or + [^:xdigit:]) were present in a class, we either have to match or not match + all wide characters (depending on whether the whole class is or is not + negated). This requirement is indicated by match_all_or_no_wide_chars being + true. We do this by including an explicit range, which works in both cases. + + If, when generating an xclass, there are no characters < 256, we can omit + the bitmap in the actual compiled code. */ + +#ifdef SUPPORT_WIDE_CHARS +#ifdef SUPPORT_UNICODE + if (xclass && (xclass_has_prop || !should_flip_negation || + (options & PCRE2_UCP) != 0)) +#elif PCRE2_CODE_UNIT_WIDTH != 8 + if (xclass && (xclass_has_prop || !should_flip_negation)) +#endif + { + if (match_all_or_no_wide_chars) + { + *class_uchardata++ = XCL_RANGE; + class_uchardata += PRIV(ord2utf)(0x100, class_uchardata); + class_uchardata += PRIV(ord2utf)(MAX_UTF_CODE_POINT, class_uchardata); + } + *class_uchardata++ = XCL_END; /* Marks the end of extra data */ + *code++ = OP_XCLASS; + code += LINK_SIZE; + *code = negate_class? XCL_NOT:0; + if (xclass_has_prop) *code |= XCL_HASPROP; + + /* If the map is required, move up the extra data to make room for it; + otherwise just move the code pointer to the end of the extra data. */ + + if (class_has_8bitchar > 0) + { + *code++ |= XCL_MAP; + memmove(code + (32 / sizeof(PCRE2_UCHAR)), code, + CU2BYTES(class_uchardata - code)); + if (negate_class && !xclass_has_prop) + for (c = 0; c < 32; c++) classbits[c] = ~classbits[c]; + memcpy(code, classbits, 32); + code = class_uchardata + (32 / sizeof(PCRE2_UCHAR)); + } + else code = class_uchardata; + + /* Now fill in the complete length of the item */ + + PUT(previous, 1, (int)(code - previous)); + break; /* End of class handling */ + } +#endif + + /* If there are no characters > 255, or they are all to be included or + excluded, set the opcode to OP_CLASS or OP_NCLASS, depending on whether the + whole class was negated and whether there were negative specials such as \S + (non-UCP) in the class. Then copy the 32-byte map into the code vector, + negating it if necessary. */ + + *code++ = (negate_class == should_flip_negation) ? OP_CLASS : OP_NCLASS; + if (lengthptr == NULL) /* Save time in the pre-compile phase */ + { + if (negate_class) + for (c = 0; c < 32; c++) classbits[c] = ~classbits[c]; + memcpy(code, classbits, 32); + } + code += 32 / sizeof(PCRE2_UCHAR); + + END_CLASS: + break; + + + /* ===================================================================*/ + /* Various kinds of repeat; '{' is not necessarily a quantifier, but this + has been tested above. */ + + case CHAR_LEFT_CURLY_BRACKET: + if (!is_quantifier) goto NORMAL_CHAR; + ptr = read_repeat_counts(ptr+1, &repeat_min, &repeat_max, errorcodeptr); + if (*errorcodeptr != 0) goto FAILED; + goto REPEAT; + + case CHAR_ASTERISK: + repeat_min = 0; + repeat_max = -1; + goto REPEAT; + + case CHAR_PLUS: + repeat_min = 1; + repeat_max = -1; + goto REPEAT; + + case CHAR_QUESTION_MARK: + repeat_min = 0; + repeat_max = 1; + + REPEAT: + if (previous == NULL) + { + *errorcodeptr = ERR9; + goto FAILED; + } + + if (repeat_min == 0) + { + firstcu = zerofirstcu; /* Adjust for zero repeat */ + firstcuflags = zerofirstcuflags; + reqcu = zeroreqcu; /* Ditto */ + reqcuflags = zeroreqcuflags; + } + + /* Remember whether this is a variable length repeat */ + + reqvary = (repeat_min == repeat_max)? 0 : REQ_VARY; + + op_type = 0; /* Default single-char op codes */ + possessive_quantifier = FALSE; /* Default not possessive quantifier */ + + /* Save start of previous item, in case we have to move it up in order to + insert something before it. */ + + tempcode = previous; + + /* Before checking for a possessive quantifier, we must skip over + whitespace and comments in extended mode because Perl allows white space at + this point. */ + + if ((options & PCRE2_EXTENDED) != 0) + { + ptr++; + for (;;) + { + while (MAX_255(*ptr) && (cb->ctypes[*ptr] & ctype_space) != 0) ptr++; + if (*ptr != CHAR_NUMBER_SIGN) break; + ptr++; + while (ptr < cb->end_pattern) + { + if (IS_NEWLINE(ptr)) /* For non-fixed-length newline cases, */ + { /* IS_NEWLINE sets cb->nllen. */ + ptr += cb->nllen; + break; + } + ptr++; +#ifdef SUPPORT_UNICODE + if (utf) FORWARDCHAR(ptr); +#endif + } /* Loop for comment characters */ + } /* Loop for multiple comments */ + ptr--; /* Last code unit of previous character. */ + } + + /* If the next character is '+', we have a possessive quantifier. This + implies greediness, whatever the setting of the PCRE2_UNGREEDY option. + If the next character is '?' this is a minimizing repeat, by default, + but if PCRE2_UNGREEDY is set, it works the other way round. We change the + repeat type to the non-default. */ + + if (ptr[1] == CHAR_PLUS) + { + repeat_type = 0; /* Force greedy */ + possessive_quantifier = TRUE; + ptr++; + } + else if (ptr[1] == CHAR_QUESTION_MARK) + { + repeat_type = greedy_non_default; + ptr++; + } + else repeat_type = greedy_default; + + /* If the repeat is {1} we can ignore it. */ + + if (repeat_max == 1 && repeat_min == 1) goto END_REPEAT; + + /* If previous was a recursion call, wrap it in atomic brackets so that + previous becomes the atomic group. All recursions were so wrapped in the + past, but it no longer happens for non-repeated recursions. In fact, the + repeated ones could be re-implemented independently so as not to need this, + but for the moment we rely on the code for repeating groups. */ + + if (*previous == OP_RECURSE) + { + memmove(previous + 1 + LINK_SIZE, previous, CU2BYTES(1 + LINK_SIZE)); + *previous = OP_ONCE; + PUT(previous, 1, 2 + 2*LINK_SIZE); + previous[2 + 2*LINK_SIZE] = OP_KET; + PUT(previous, 3 + 2*LINK_SIZE, 2 + 2*LINK_SIZE); + code += 2 + 2 * LINK_SIZE; + length_prevgroup = 3 + 3*LINK_SIZE; + } + + /* Now handle repetition for the different types of item. */ + + /* If previous was a character or negated character match, abolish the item + and generate a repeat item instead. If a char item has a minimum of more + than one, ensure that it is set in reqcu - it might not be if a sequence + such as x{3} is the first thing in a branch because the x will have gone + into firstcu instead. */ + + if (*previous == OP_CHAR || *previous == OP_CHARI + || *previous == OP_NOT || *previous == OP_NOTI) + { + switch (*previous) + { + default: /* Make compiler happy. */ + case OP_CHAR: op_type = OP_STAR - OP_STAR; break; + case OP_CHARI: op_type = OP_STARI - OP_STAR; break; + case OP_NOT: op_type = OP_NOTSTAR - OP_STAR; break; + case OP_NOTI: op_type = OP_NOTSTARI - OP_STAR; break; + } + + /* Deal with UTF characters that take up more than one code unit. It's + easier to write this out separately than try to macrify it. Use c to + hold the length of the character in code units, plus UTF_LENGTH to flag + that it's a length rather than a small character. */ + +#ifdef MAYBE_UTF_MULTI + if (utf && NOT_FIRSTCU(code[-1])) + { + PCRE2_UCHAR *lastchar = code - 1; + BACKCHAR(lastchar); + c = (int)(code - lastchar); /* Length of UTF character */ + memcpy(utf_units, lastchar, CU2BYTES(c)); /* Save the char */ + c |= UTF_LENGTH; /* Flag c as a length */ + } + else +#endif /* MAYBE_UTF_MULTI */ + + /* Handle the case of a single charater - either with no UTF support, or + with UTF disabled, or for a single-code-unit UTF character. */ + { + c = code[-1]; + if (*previous <= OP_CHARI && repeat_min > 1) + { + reqcu = c; + reqcuflags = req_caseopt | cb->req_varyopt; + } + } + + goto OUTPUT_SINGLE_REPEAT; /* Code shared with single character types */ + } + + /* If previous was a character type match (\d or similar), abolish it and + create a suitable repeat item. The code is shared with single-character + repeats by setting op_type to add a suitable offset into repeat_type. Note + the the Unicode property types will be present only when SUPPORT_UNICODE is + defined, but we don't wrap the little bits of code here because it just + makes it horribly messy. */ + + else if (*previous < OP_EODN) + { + PCRE2_UCHAR *oldcode; + int prop_type, prop_value; + op_type = OP_TYPESTAR - OP_STAR; /* Use type opcodes */ + c = *previous; /* Save previous opcode */ + if (c == OP_PROP || c == OP_NOTPROP) + { + prop_type = previous[1]; + prop_value = previous[2]; + } + else + { + /* Come here from just above with a character in c */ + OUTPUT_SINGLE_REPEAT: + prop_type = prop_value = -1; + } + + /* At this point we either have prop_type == prop_value == -1 and either + a code point or a character type that is not OP_[NOT]PROP in c, or we + have OP_[NOT]PROP in c and prop_type/prop_value not negative. */ + + oldcode = code; /* Save where we were */ + code = previous; /* Usually overwrite previous item */ + + /* If the maximum is zero then the minimum must also be zero; Perl allows + this case, so we do too - by simply omitting the item altogether. */ + + if (repeat_max == 0) goto END_REPEAT; + + /* Combine the op_type with the repeat_type */ + + repeat_type += op_type; + + /* A minimum of zero is handled either as the special case * or ?, or as + an UPTO, with the maximum given. */ + + if (repeat_min == 0) + { + if (repeat_max == -1) *code++ = OP_STAR + repeat_type; + else if (repeat_max == 1) *code++ = OP_QUERY + repeat_type; + else + { + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max); + } + } + + /* A repeat minimum of 1 is optimized into some special cases. If the + maximum is unlimited, we use OP_PLUS. Otherwise, the original item is + left in place and, if the maximum is greater than 1, we use OP_UPTO with + one less than the maximum. */ + + else if (repeat_min == 1) + { + if (repeat_max == -1) + *code++ = OP_PLUS + repeat_type; + else + { + code = oldcode; /* Leave previous item in place */ + if (repeat_max == 1) goto END_REPEAT; + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max - 1); + } + } + + /* The case {n,n} is just an EXACT, while the general case {n,m} is + handled as an EXACT followed by an UPTO or STAR or QUERY. */ + + else + { + *code++ = OP_EXACT + op_type; /* NB EXACT doesn't have repeat_type */ + PUT2INC(code, 0, repeat_min); + + /* Unless repeat_max equals repeat_min, fill in the data for EXACT, and + then generate the second opcode. In UTF mode, multi-code-unit + characters have their length in c, with the UTF_LENGTH bit as a flag, + and the code units in utf_units. For a repeated Unicode property match, + there are two extra values that define the required property, and c + never has the UTF_LENGTH bit set. */ + + if (repeat_max != repeat_min) + { +#ifdef MAYBE_UTF_MULTI + if (utf && (c & UTF_LENGTH) != 0) + { + memcpy(code, utf_units, CU2BYTES(c & 7)); + code += c & 7; + } + else +#endif /* MAYBE_UTF_MULTI */ + { + *code++ = c; + if (prop_type >= 0) + { + *code++ = prop_type; + *code++ = prop_value; + } + } + + /* Now set up the following opcode */ + + if (repeat_max < 0) *code++ = OP_STAR + repeat_type; else + { + repeat_max -= repeat_min; + if (repeat_max == 1) + { + *code++ = OP_QUERY + repeat_type; + } + else + { + *code++ = OP_UPTO + repeat_type; + PUT2INC(code, 0, repeat_max); + } + } + } + } + + /* Fill in the character or character type for the final opcode. */ + +#ifdef MAYBE_UTF_MULTI + if (utf && (c & UTF_LENGTH) != 0) + { + memcpy(code, utf_units, CU2BYTES(c & 7)); + code += c & 7; + } + else +#endif /* MAYBEW_UTF_MULTI */ + { + *code++ = c; + if (prop_type >= 0) + { + *code++ = prop_type; + *code++ = prop_value; + } + } + } + + /* If previous was a character class or a back reference, we put the repeat + stuff after it, but just skip the item if the repeat was {0,0}. */ + + else if (*previous == OP_CLASS || *previous == OP_NCLASS || +#ifdef SUPPORT_WIDE_CHARS + *previous == OP_XCLASS || +#endif + *previous == OP_REF || *previous == OP_REFI || + *previous == OP_DNREF || *previous == OP_DNREFI) + { + if (repeat_max == 0) + { + code = previous; + goto END_REPEAT; + } + + if (repeat_min == 0 && repeat_max == -1) + *code++ = OP_CRSTAR + repeat_type; + else if (repeat_min == 1 && repeat_max == -1) + *code++ = OP_CRPLUS + repeat_type; + else if (repeat_min == 0 && repeat_max == 1) + *code++ = OP_CRQUERY + repeat_type; + else + { + *code++ = OP_CRRANGE + repeat_type; + PUT2INC(code, 0, repeat_min); + if (repeat_max == -1) repeat_max = 0; /* 2-byte encoding for max */ + PUT2INC(code, 0, repeat_max); + } + } + + /* If previous was a bracket group, we may have to replicate it in certain + cases. Note that at this point we can encounter only the "basic" bracket + opcodes such as BRA and CBRA, as this is the place where they get converted + into the more special varieties such as BRAPOS and SBRA. A test for >= + OP_ASSERT and <= OP_COND includes ASSERT, ASSERT_NOT, ASSERTBACK, + ASSERTBACK_NOT, ONCE, ONCE_NC, BRA, BRAPOS, CBRA, CBRAPOS, and COND. + Originally, PCRE did not allow repetition of assertions, but now it does, + for Perl compatibility. */ + + else if (*previous >= OP_ASSERT && *previous <= OP_COND) + { + register int i; + int len = (int)(code - previous); + PCRE2_UCHAR *bralink = NULL; + PCRE2_UCHAR *brazeroptr = NULL; + + /* Repeating a DEFINE group (or any group where the condition is always + FALSE and there is only one branch) is pointless, but Perl allows the + syntax, so we just ignore the repeat. */ + + if (*previous == OP_COND && previous[LINK_SIZE+1] == OP_FALSE && + previous[GET(previous, 1)] != OP_ALT) + goto END_REPEAT; + + /* There is no sense in actually repeating assertions. The only potential + use of repetition is in cases when the assertion is optional. Therefore, + if the minimum is greater than zero, just ignore the repeat. If the + maximum is not zero or one, set it to 1. */ + + if (*previous < OP_ONCE) /* Assertion */ + { + if (repeat_min > 0) goto END_REPEAT; + if (repeat_max < 0 || repeat_max > 1) repeat_max = 1; + } + + /* The case of a zero minimum is special because of the need to stick + OP_BRAZERO in front of it, and because the group appears once in the + data, whereas in other cases it appears the minimum number of times. For + this reason, it is simplest to treat this case separately, as otherwise + the code gets far too messy. There are several special subcases when the + minimum is zero. */ + + if (repeat_min == 0) + { + /* If the maximum is also zero, we used to just omit the group from the + output altogether, like this: + + ** if (repeat_max == 0) + ** { + ** code = previous; + ** goto END_REPEAT; + ** } + + However, that fails when a group or a subgroup within it is referenced + as a subroutine from elsewhere in the pattern, so now we stick in + OP_SKIPZERO in front of it so that it is skipped on execution. As we + don't have a list of which groups are referenced, we cannot do this + selectively. + + If the maximum is 1 or unlimited, we just have to stick in the BRAZERO + and do no more at this point. */ + + if (repeat_max <= 1) /* Covers 0, 1, and unlimited */ + { + memmove(previous + 1, previous, CU2BYTES(len)); + code++; + if (repeat_max == 0) + { + *previous++ = OP_SKIPZERO; + goto END_REPEAT; + } + brazeroptr = previous; /* Save for possessive optimizing */ + *previous++ = OP_BRAZERO + repeat_type; + } + + /* If the maximum is greater than 1 and limited, we have to replicate + in a nested fashion, sticking OP_BRAZERO before each set of brackets. + The first one has to be handled carefully because it's the original + copy, which has to be moved up. The remainder can be handled by code + that is common with the non-zero minimum case below. We have to + adjust the value or repeat_max, since one less copy is required. */ + + else + { + int offset; + memmove(previous + 2 + LINK_SIZE, previous, CU2BYTES(len)); + code += 2 + LINK_SIZE; + *previous++ = OP_BRAZERO + repeat_type; + *previous++ = OP_BRA; + + /* We chain together the bracket offset fields that have to be + filled in later when the ends of the brackets are reached. */ + + offset = (bralink == NULL)? 0 : (int)(previous - bralink); + bralink = previous; + PUTINC(previous, 0, offset); + } + + repeat_max--; + } + + /* If the minimum is greater than zero, replicate the group as many + times as necessary, and adjust the maximum to the number of subsequent + copies that we need. */ + + else + { + if (repeat_min > 1) + { + /* In the pre-compile phase, we don't actually do the replication. We + just adjust the length as if we had. Do some paranoid checks for + potential integer overflow. The INT64_OR_DOUBLE type is a 64-bit + integer type when available, otherwise double. */ + + if (lengthptr != NULL) + { + size_t delta = (repeat_min - 1)*length_prevgroup; + if ((INT64_OR_DOUBLE)(repeat_min - 1)* + (INT64_OR_DOUBLE)length_prevgroup > + (INT64_OR_DOUBLE)INT_MAX || + OFLOW_MAX - *lengthptr < delta) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += delta; + } + + /* This is compiling for real. If there is a set first byte for + the group, and we have not yet set a "required byte", set it. */ + + else + { + if (groupsetfirstcu && reqcuflags < 0) + { + reqcu = firstcu; + reqcuflags = firstcuflags; + } + for (i = 1; i < repeat_min; i++) + { + memcpy(code, previous, CU2BYTES(len)); + code += len; + } + } + } + + if (repeat_max > 0) repeat_max -= repeat_min; + } + + /* This code is common to both the zero and non-zero minimum cases. If + the maximum is limited, it replicates the group in a nested fashion, + remembering the bracket starts on a stack. In the case of a zero minimum, + the first one was set up above. In all cases the repeat_max now specifies + the number of additional copies needed. Again, we must remember to + replicate entries on the forward reference list. */ + + if (repeat_max >= 0) + { + /* In the pre-compile phase, we don't actually do the replication. We + just adjust the length as if we had. For each repetition we must add 1 + to the length for BRAZERO and for all but the last repetition we must + add 2 + 2*LINKSIZE to allow for the nesting that occurs. Do some + paranoid checks to avoid integer overflow. The INT64_OR_DOUBLE type is + a 64-bit integer type when available, otherwise double. */ + + if (lengthptr != NULL && repeat_max > 0) + { + size_t delta = repeat_max*(length_prevgroup + 1 + 2 + 2*LINK_SIZE) - + 2 - 2*LINK_SIZE; /* Last one doesn't nest */ + if ((INT64_OR_DOUBLE)repeat_max * + (INT64_OR_DOUBLE)(length_prevgroup + 1 + 2 + 2*LINK_SIZE) + > (INT64_OR_DOUBLE)INT_MAX || + OFLOW_MAX - *lengthptr < delta) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += delta; + } + + /* This is compiling for real */ + + else for (i = repeat_max - 1; i >= 0; i--) + { + *code++ = OP_BRAZERO + repeat_type; + + /* All but the final copy start a new nesting, maintaining the + chain of brackets outstanding. */ + + if (i != 0) + { + int offset; + *code++ = OP_BRA; + offset = (bralink == NULL)? 0 : (int)(code - bralink); + bralink = code; + PUTINC(code, 0, offset); + } + + memcpy(code, previous, CU2BYTES(len)); + code += len; + } + + /* Now chain through the pending brackets, and fill in their length + fields (which are holding the chain links pro tem). */ + + while (bralink != NULL) + { + int oldlinkoffset; + int offset = (int)(code - bralink + 1); + PCRE2_UCHAR *bra = code - offset; + oldlinkoffset = GET(bra, 1); + bralink = (oldlinkoffset == 0)? NULL : bralink - oldlinkoffset; + *code++ = OP_KET; + PUTINC(code, 0, offset); + PUT(bra, 1, offset); + } + } + + /* If the maximum is unlimited, set a repeater in the final copy. For + ONCE brackets, that's all we need to do. However, possessively repeated + ONCE brackets can be converted into non-capturing brackets, as the + behaviour of (?:xx)++ is the same as (?>xx)++ and this saves having to + deal with possessive ONCEs specially. + + Otherwise, when we are doing the actual compile phase, check to see + whether this group is one that could match an empty string. If so, + convert the initial operator to the S form (e.g. OP_BRA -> OP_SBRA) so + that runtime checking can be done. [This check is also applied to ONCE + groups at runtime, but in a different way.] + + Then, if the quantifier was possessive and the bracket is not a + conditional, we convert the BRA code to the POS form, and the KET code to + KETRPOS. (It turns out to be convenient at runtime to detect this kind of + subpattern at both the start and at the end.) The use of special opcodes + makes it possible to reduce greatly the stack usage in pcre2_match(). If + the group is preceded by OP_BRAZERO, convert this to OP_BRAPOSZERO. + + Then, if the minimum number of matches is 1 or 0, cancel the possessive + flag so that the default action below, of wrapping everything inside + atomic brackets, does not happen. When the minimum is greater than 1, + there will be earlier copies of the group, and so we still have to wrap + the whole thing. */ + + else + { + PCRE2_UCHAR *ketcode = code - 1 - LINK_SIZE; + PCRE2_UCHAR *bracode = ketcode - GET(ketcode, 1); + + /* Convert possessive ONCE brackets to non-capturing */ + + if ((*bracode == OP_ONCE || *bracode == OP_ONCE_NC) && + possessive_quantifier) *bracode = OP_BRA; + + /* For non-possessive ONCE brackets, all we need to do is to + set the KET. */ + + if (*bracode == OP_ONCE || *bracode == OP_ONCE_NC) + *ketcode = OP_KETRMAX + repeat_type; + + /* Handle non-ONCE brackets and possessive ONCEs (which have been + converted to non-capturing above). */ + + else + { + /* In the compile phase, check whether the group could match an empty + string. */ + + if (lengthptr == NULL) + { + PCRE2_UCHAR *scode = bracode; + do + { + int count = 0; + int rc = could_be_empty_branch(scode, ketcode, utf, cb, FALSE, + NULL, &count); + if (rc < 0) + { + *errorcodeptr = ERR86; + goto FAILED; + } + if (rc > 0) + { + *bracode += OP_SBRA - OP_BRA; + break; + } + scode += GET(scode, 1); + } + while (*scode == OP_ALT); + + /* A conditional group with only one branch has an implicit empty + alternative branch. */ + + if (*bracode == OP_COND && bracode[GET(bracode,1)] != OP_ALT) + *bracode = OP_SCOND; + } + + /* Handle possessive quantifiers. */ + + if (possessive_quantifier) + { + /* For COND brackets, we wrap the whole thing in a possessively + repeated non-capturing bracket, because we have not invented POS + versions of the COND opcodes. */ + + if (*bracode == OP_COND || *bracode == OP_SCOND) + { + int nlen = (int)(code - bracode); + memmove(bracode + 1 + LINK_SIZE, bracode, CU2BYTES(nlen)); + code += 1 + LINK_SIZE; + nlen += 1 + LINK_SIZE; + *bracode = (*bracode == OP_COND)? OP_BRAPOS : OP_SBRAPOS; + *code++ = OP_KETRPOS; + PUTINC(code, 0, nlen); + PUT(bracode, 1, nlen); + } + + /* For non-COND brackets, we modify the BRA code and use KETRPOS. */ + + else + { + *bracode += 1; /* Switch to xxxPOS opcodes */ + *ketcode = OP_KETRPOS; + } + + /* If the minimum is zero, mark it as possessive, then unset the + possessive flag when the minimum is 0 or 1. */ + + if (brazeroptr != NULL) *brazeroptr = OP_BRAPOSZERO; + if (repeat_min < 2) possessive_quantifier = FALSE; + } + + /* Non-possessive quantifier */ + + else *ketcode = OP_KETRMAX + repeat_type; + } + } + } + + /* If previous is OP_FAIL, it was generated by an empty class [] + (PCRE2_ALLOW_EMPTY_CLASS is set). The other ways in which OP_FAIL can be + generated, that is by (*FAIL) or (?!), set previous to NULL, which gives a + "nothing to repeat" error above. We can just ignore the repeat in empty + class case. */ + + else if (*previous == OP_FAIL) goto END_REPEAT; + + /* Else there's some kind of shambles */ + + else + { + *errorcodeptr = ERR10; + goto FAILED; + } + + /* If the character following a repeat is '+', possessive_quantifier is + TRUE. For some opcodes, there are special alternative opcodes for this + case. For anything else, we wrap the entire repeated item inside OP_ONCE + brackets. Logically, the '+' notation is just syntactic sugar, taken from + Sun's Java package, but the special opcodes can optimize it. + + Some (but not all) possessively repeated subpatterns have already been + completely handled in the code just above. For them, possessive_quantifier + is always FALSE at this stage. Note that the repeated item starts at + tempcode, not at previous, which might be the first part of a string whose + (former) last char we repeated. */ + + if (possessive_quantifier) + { + int len; + + /* Possessifying an EXACT quantifier has no effect, so we can ignore it. + However, QUERY, STAR, or UPTO may follow (for quantifiers such as {5,6}, + {5,}, or {5,10}). We skip over an EXACT item; if the length of what + remains is greater than zero, there's a further opcode that can be + handled. If not, do nothing, leaving the EXACT alone. */ + + switch(*tempcode) + { + case OP_TYPEEXACT: + tempcode += PRIV(OP_lengths)[*tempcode] + + ((tempcode[1 + IMM2_SIZE] == OP_PROP + || tempcode[1 + IMM2_SIZE] == OP_NOTPROP)? 2 : 0); + break; + + /* CHAR opcodes are used for exacts whose count is 1. */ + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + tempcode += PRIV(OP_lengths)[*tempcode]; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(tempcode[-1])) + tempcode += GET_EXTRALEN(tempcode[-1]); +#endif + break; + + /* For the class opcodes, the repeat operator appears at the end; + adjust tempcode to point to it. */ + + case OP_CLASS: + case OP_NCLASS: + tempcode += 1 + 32/sizeof(PCRE2_UCHAR); + break; + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + tempcode += GET(tempcode, 1); + break; +#endif + } + + /* If tempcode is equal to code (which points to the end of the repeated + item), it means we have skipped an EXACT item but there is no following + QUERY, STAR, or UPTO; the value of len will be 0, and we do nothing. In + all other cases, tempcode will be pointing to the repeat opcode, and will + be less than code, so the value of len will be greater than 0. */ + + len = (int)(code - tempcode); + if (len > 0) + { + unsigned int repcode = *tempcode; + + /* There is a table for possessifying opcodes, all of which are less + than OP_CALLOUT. A zero entry means there is no possessified version. + */ + + if (repcode < OP_CALLOUT && opcode_possessify[repcode] > 0) + *tempcode = opcode_possessify[repcode]; + + /* For opcode without a special possessified version, wrap the item in + ONCE brackets. */ + + else + { + memmove(tempcode + 1 + LINK_SIZE, tempcode, CU2BYTES(len)); + code += 1 + LINK_SIZE; + len += 1 + LINK_SIZE; + tempcode[0] = OP_ONCE; + *code++ = OP_KET; + PUTINC(code, 0, len); + PUT(tempcode, 1, len); + } + } + } + + /* In all case we no longer have a previous item. We also set the + "follows varying string" flag for subsequently encountered reqcus if + it isn't already set and we have just passed a varying length item. */ + + END_REPEAT: + previous = NULL; + cb->req_varyopt |= reqvary; + break; + + + /* ===================================================================*/ + /* Start of nested parenthesized sub-expression, or lookahead or lookbehind + or option setting or condition or all the other extended parenthesis forms. + We must save the current high-water-mark for the forward reference list so + that we know where they start for this group. However, because the list may + be extended when there are very many forward references (usually the result + of a replicated inner group), we must use an offset rather than an absolute + address. Note that (?# comments are dealt with at the top of the loop; + they do not get this far. */ + + case CHAR_LEFT_PARENTHESIS: + ptr++; + + /* Deal with various "verbs" that can be introduced by '*'. */ + + if (ptr[0] == CHAR_ASTERISK && (ptr[1] == ':' + || (MAX_255(ptr[1]) && ((cb->ctypes[ptr[1]] & ctype_letter) != 0)))) + { + int i, namelen; + int arglen = 0; + const char *vn = verbnames; + PCRE2_SPTR name = ptr + 1; + PCRE2_SPTR arg = NULL; + previous = NULL; + ptr++; + + /* Increment ptr, set namelen, check length */ + + READ_NAME(ctype_letter, ERR60, *errorcodeptr); + + /* It appears that Perl allows any characters whatsoever, other than + a closing parenthesis, to appear in arguments, so we no longer insist on + letters, digits, and underscores. Perl does not, however, do any + interpretation within arguments, and has no means of including a closing + parenthesis. PCRE supports escape processing but only when it is + requested by an option. Note that check_escape() will not return values + greater than the code unit maximum when not in UTF mode. */ + + if (*ptr == CHAR_COLON) + { + arg = ++ptr; + + if ((options & PCRE2_ALT_VERBNAMES) == 0) + { + arglen = 0; + while (ptr < cb->end_pattern && *ptr != CHAR_RIGHT_PARENTHESIS) + { + ptr++; /* Check length as we go */ + arglen++; /* along, to avoid the */ + if ((unsigned int)arglen > MAX_MARK) /* possibility of overflow. */ + { + *errorcodeptr = ERR76; + goto FAILED; + } + } + } + else + { + /* The length check is in process_verb_names() */ + arglen = process_verb_name(&ptr, NULL, errorcodeptr, options, + utf, cb); + if (arglen < 0) goto FAILED; + } + } + + if (*ptr != CHAR_RIGHT_PARENTHESIS) + { + *errorcodeptr = ERR60; + goto FAILED; + } + + /* Scan the table of verb names */ + + for (i = 0; i < verbcount; i++) + { + if (namelen == verbs[i].len && + PRIV(strncmp_c8)(name, vn, namelen) == 0) + { + int setverb; + + /* Check for open captures before ACCEPT and convert it to + ASSERT_ACCEPT if in an assertion. */ + + if (verbs[i].op == OP_ACCEPT) + { + open_capitem *oc; + if (arglen != 0) + { + *errorcodeptr = ERR59; + goto FAILED; + } + cb->had_accept = TRUE; + for (oc = cb->open_caps; oc != NULL; oc = oc->next) + { + *code++ = OP_CLOSE; + PUT2INC(code, 0, oc->number); + } + setverb = *code++ = + (cb->assert_depth > 0)? OP_ASSERT_ACCEPT : OP_ACCEPT; + + /* Do not set firstcu after *ACCEPT */ + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + } + + /* Handle other cases with/without an argument */ + + else if (arglen == 0) /* There is no argument */ + { + if (verbs[i].op < 0) /* Argument is mandatory */ + { + *errorcodeptr = ERR66; + goto FAILED; + } + setverb = *code++ = verbs[i].op; + } + + else /* An argument is present */ + { + if (verbs[i].op_arg < 0) /* Argument is forbidden */ + { + *errorcodeptr = ERR59; + goto FAILED; + } + setverb = *code++ = verbs[i].op_arg; + + /* Arguments can be very long, especially in 16- and 32-bit modes, + and can overflow the workspace in the first pass. Instead of + putting the argument into memory, we just update the length counter + and set up an empty argument. */ + + if (lengthptr != NULL) + { + *lengthptr += arglen; + *code++ = 0; + } + else + { + *code++ = arglen; + if ((options & PCRE2_ALT_VERBNAMES) != 0) + { + PCRE2_UCHAR *memcode = code; /* code is "register" */ + (void)process_verb_name(&arg, &memcode, errorcodeptr, options, + utf, cb); + code = memcode; + } + else /* No argument processing */ + { + memcpy(code, arg, CU2BYTES(arglen)); + code += arglen; + } + } + + *code++ = 0; + } + + switch (setverb) + { + case OP_THEN: + case OP_THEN_ARG: + cb->external_flags |= PCRE2_HASTHEN; + break; + + case OP_PRUNE: + case OP_PRUNE_ARG: + case OP_SKIP: + case OP_SKIP_ARG: + cb->had_pruneorskip = TRUE; + break; + } + + break; /* Found verb, exit loop */ + } + + vn += verbs[i].len + 1; + } + + if (i < verbcount) continue; /* Successfully handled a verb */ + *errorcodeptr = ERR60; /* Verb not recognized */ + goto FAILED; + } + + /* Initialization for "real" parentheses */ + + newoptions = options; + skipunits = 0; + bravalue = OP_CBRA; + reset_bracount = FALSE; + + /* Deal with the extended parentheses; all are introduced by '?', and the + appearance of any of them means that this is not a capturing group. */ + + if (*ptr == CHAR_QUESTION_MARK) + { + int i, count; + int namelen; /* Must be signed */ + uint32_t index; + uint32_t set, unset, *optset; + named_group *ng; + PCRE2_SPTR name; + PCRE2_UCHAR *slot; + + switch (*(++ptr)) + { + /* ------------------------------------------------------------ */ + case CHAR_VERTICAL_LINE: /* Reset capture count for each branch */ + reset_bracount = TRUE; + /* Fall through */ + + /* ------------------------------------------------------------ */ + case CHAR_COLON: /* Non-capturing bracket */ + bravalue = OP_BRA; + ptr++; + break; + + /* ------------------------------------------------------------ */ + case CHAR_LEFT_PARENTHESIS: + bravalue = OP_COND; /* Conditional group */ + tempptr = ptr; + + /* A condition can be an assertion, a number (referring to a numbered + group's having been set), a name (referring to a named group), or 'R', + referring to recursion. R and R&name are also permitted for + recursion tests. + + There are ways of testing a named group: (?(name)) is used by Python; + Perl 5.10 onwards uses (?() or (?('name')). + + There is one unfortunate ambiguity, caused by history. 'R' can be the + recursive thing or the name 'R' (and similarly for 'R' followed by + digits). We look for a name first; if not found, we try the other case. + + For compatibility with auto-callouts, we allow a callout to be + specified before a condition that is an assertion. First, check for the + syntax of a callout; if found, adjust the temporary pointer that is + used to check for an assertion condition. That's all that is needed! */ + + if (ptr[1] == CHAR_QUESTION_MARK && ptr[2] == CHAR_C) + { + if (IS_DIGIT(ptr[3]) || ptr[3] == CHAR_RIGHT_PARENTHESIS) + { + for (i = 3;; i++) if (!IS_DIGIT(ptr[i])) break; + if (ptr[i] == CHAR_RIGHT_PARENTHESIS) + tempptr += i + 1; + } + else + { + uint32_t delimiter = 0; + for (i = 0; PRIV(callout_start_delims)[i] != 0; i++) + { + if (ptr[3] == PRIV(callout_start_delims)[i]) + { + delimiter = PRIV(callout_end_delims)[i]; + break; + } + } + if (delimiter != 0) + { + for (i = 4; ptr + i < cb->end_pattern; i++) + { + if (ptr[i] == delimiter) + { + if (ptr[i+1] == delimiter) i++; + else + { + if (ptr[i+1] == CHAR_RIGHT_PARENTHESIS) tempptr += i + 2; + break; + } + } + } + } + } + + /* tempptr should now be pointing to the opening parenthesis of the + assertion condition. */ + + if (*tempptr != CHAR_LEFT_PARENTHESIS) + { + *errorcodeptr = ERR28; + goto FAILED; + } + } + + /* For conditions that are assertions, check the syntax, and then exit + the switch. This will take control down to where bracketed groups + are processed. The assertion will be handled as part of the group, + but we need to identify this case because the conditional assertion may + not be quantifier. */ + + if (tempptr[1] == CHAR_QUESTION_MARK && + (tempptr[2] == CHAR_EQUALS_SIGN || + tempptr[2] == CHAR_EXCLAMATION_MARK || + (tempptr[2] == CHAR_LESS_THAN_SIGN && + (tempptr[3] == CHAR_EQUALS_SIGN || + tempptr[3] == CHAR_EXCLAMATION_MARK)))) + { + cb->iscondassert = TRUE; + break; + } + + /* Other conditions use OP_CREF/OP_DNCREF/OP_RREF/OP_DNRREF, and all + need to skip at least 1+IMM2_SIZE bytes at the start of the group. */ + + code[1+LINK_SIZE] = OP_CREF; + skipunits = 1+IMM2_SIZE; + refsign = -1; /* => not a number */ + namelen = -1; /* => not a name; must set to avoid warning */ + name = NULL; /* Always set to avoid warning */ + recno = 0; /* Always set to avoid warning */ + + /* Point at character after (?( */ + + ptr++; + + /* Check for (?(VERSION[>]=n.m), which is a facility whereby indirect + users of PCRE2 via an application can discover which release of PCRE2 + is being used. */ + + if (PRIV(strncmp_c8)(ptr, STRING_VERSION, 7) == 0 && + ptr[7] != CHAR_RIGHT_PARENTHESIS) + { + BOOL ge = FALSE; + int major = 0; + int minor = 0; + + ptr += 7; + if (*ptr == CHAR_GREATER_THAN_SIGN) + { + ge = TRUE; + ptr++; + } + + /* NOTE: cannot write IS_DIGIT(*(++ptr)) here because IS_DIGIT + references its argument twice. */ + + if (*ptr != CHAR_EQUALS_SIGN || (ptr++, !IS_DIGIT(*ptr))) + { + *errorcodeptr = ERR79; + goto FAILED; + } + + while (IS_DIGIT(*ptr)) major = major * 10 + *ptr++ - '0'; + if (*ptr == CHAR_DOT) + { + ptr++; + while (IS_DIGIT(*ptr)) minor = minor * 10 + *ptr++ - '0'; + if (minor < 10) minor *= 10; + } + + if (*ptr != CHAR_RIGHT_PARENTHESIS || minor > 99) + { + *errorcodeptr = ERR79; + goto FAILED; + } + + if (ge) + code[1+LINK_SIZE] = ((PCRE2_MAJOR > major) || + (PCRE2_MAJOR == major && PCRE2_MINOR >= minor))? + OP_TRUE : OP_FALSE; + else + code[1+LINK_SIZE] = (PCRE2_MAJOR == major && PCRE2_MINOR == minor)? + OP_TRUE : OP_FALSE; + + ptr++; + skipunits = 1; + break; /* End of condition processing */ + } + + /* Check for a test for recursion in a named group. */ + + if (*ptr == CHAR_R && ptr[1] == CHAR_AMPERSAND) + { + terminator = -1; + ptr += 2; + code[1+LINK_SIZE] = OP_RREF; /* Change the type of test */ + } + + /* Check for a test for a named group's having been set, using the Perl + syntax (?() or (?('name'), and also allow for the original PCRE + syntax of (?(name) or for (?(+n), (?(-n), and just (?(n). */ + + else if (*ptr == CHAR_LESS_THAN_SIGN) + { + terminator = CHAR_GREATER_THAN_SIGN; + ptr++; + } + else if (*ptr == CHAR_APOSTROPHE) + { + terminator = CHAR_APOSTROPHE; + ptr++; + } + else + { + terminator = CHAR_NULL; + if (*ptr == CHAR_MINUS || *ptr == CHAR_PLUS) refsign = *ptr++; + else if (IS_DIGIT(*ptr)) refsign = 0; + } + + /* Handle a number */ + + if (refsign >= 0) + { + while (IS_DIGIT(*ptr)) + { + if (recno > INT_MAX / 10 - 1) /* Integer overflow */ + { + while (IS_DIGIT(*ptr)) ptr++; + *errorcodeptr = ERR61; + goto FAILED; + } + recno = recno * 10 + (int)(*ptr - CHAR_0); + ptr++; + } + } + + /* Otherwise we expect to read a name; anything else is an error. When + the referenced name is one of a number of duplicates, a different + opcode is used and it needs more memory. Unfortunately we cannot tell + whether this is the case in the first pass, so we have to allow for + more memory always. In the second pass, the additional to skipunits + happens later. */ + + else + { + if (IS_DIGIT(*ptr)) + { + *errorcodeptr = ERR44; /* Group name must start with non-digit */ + goto FAILED; + } + if (!MAX_255(*ptr) || (cb->ctypes[*ptr] & ctype_word) == 0) + { + *errorcodeptr = ERR28; /* Assertion expected */ + goto FAILED; + } + name = ptr; + /* Increment ptr, set namelen, check length */ + READ_NAME(ctype_word, ERR48, *errorcodeptr); + if (lengthptr != NULL) skipunits += IMM2_SIZE; + } + + /* Check the terminator */ + + if ((terminator > 0 && *ptr++ != (PCRE2_UCHAR)terminator) || + *ptr++ != CHAR_RIGHT_PARENTHESIS) + { + ptr--; /* Error offset */ + *errorcodeptr = ERR26; /* Malformed number or name */ + goto FAILED; + } + + /* Do no further checking in the pre-compile phase. */ + + if (lengthptr != NULL) break; + + /* In the real compile we do the work of looking for the actual + reference. If refsign is not negative, it means we have a number in + recno. */ + + if (refsign >= 0) + { + if (recno <= 0) + { + *errorcodeptr = ERR35; + goto FAILED; + } + if (refsign != 0) recno = (refsign == CHAR_MINUS)? + (cb->bracount + 1) - recno : recno + cb->bracount; + if (recno <= 0 || (uint32_t)recno > cb->final_bracount) + { + *errorcodeptr = ERR15; + goto FAILED; + } + PUT2(code, 2+LINK_SIZE, recno); + if ((uint32_t)recno > cb->top_backref) cb->top_backref = recno; + break; + } + + /* Otherwise look for the name. */ + + slot = cb->name_table; + for (i = 0; i < cb->names_found; i++) + { + if (PRIV(strncmp)(name, slot+IMM2_SIZE, namelen) == 0) break; + slot += cb->name_entry_size; + } + + /* Found the named subpattern. If the name is duplicated, add one to + the opcode to change CREF/RREF into DNCREF/DNRREF and insert + appropriate data values. Otherwise, just insert the unique subpattern + number. */ + + if (i < cb->names_found) + { + int offset = i; /* Offset of first name found */ + + count = 0; + for (;;) + { + recno = GET2(slot, 0); /* Number for last found */ + if ((uint32_t)recno > cb->top_backref) cb->top_backref = recno; + count++; + if (++i >= cb->names_found) break; + slot += cb->name_entry_size; + if (PRIV(strncmp)(name, slot+IMM2_SIZE, namelen) != 0 || + (slot+IMM2_SIZE)[namelen] != 0) break; + } + + if (count > 1) + { + PUT2(code, 2+LINK_SIZE, offset); + PUT2(code, 2+LINK_SIZE+IMM2_SIZE, count); + skipunits += IMM2_SIZE; + code[1+LINK_SIZE]++; + } + else /* Not a duplicated name */ + { + PUT2(code, 2+LINK_SIZE, recno); + } + } + + /* If terminator == CHAR_NULL it means that the name followed directly + after the opening parenthesis [e.g. (?(abc)...] and in this case there + are some further alternatives to try. For the cases where terminator != + CHAR_NULL [things like (?(... or (?('name')... or (?(R&name)... ] + we have now checked all the possibilities, so give an error. */ + + else if (terminator != CHAR_NULL) + { + *errorcodeptr = ERR15; + goto FAILED; + } + + /* Check for (?(R) for recursion. Allow digits after R to specify a + specific group number. */ + + else if (*name == CHAR_R) + { + recno = 0; + for (i = 1; i < namelen; i++) + { + if (!IS_DIGIT(name[i])) + { + *errorcodeptr = ERR15; /* Non-existent subpattern */ + goto FAILED; + } + if (recno > INT_MAX / 10 - 1) /* Integer overflow */ + { + *errorcodeptr = ERR61; + goto FAILED; + } + recno = recno * 10 + name[i] - CHAR_0; + } + if (recno == 0) recno = RREF_ANY; + code[1+LINK_SIZE] = OP_RREF; /* Change test type */ + PUT2(code, 2+LINK_SIZE, recno); + } + + /* Similarly, check for the (?(DEFINE) "condition", which is always + false. During compilation we set OP_DEFINE to distinguish this from + other OP_FALSE conditions so that it can be checked for having only one + branch, but after that the opcode is changed to OP_FALSE. */ + + else if (namelen == 6 && PRIV(strncmp_c8)(name, STRING_DEFINE, 6) == 0) + { + code[1+LINK_SIZE] = OP_DEFINE; + skipunits = 1; + } + + /* Reference to an unidentified subpattern. */ + + else + { + *errorcodeptr = ERR15; + goto FAILED; + } + break; + + + /* ------------------------------------------------------------ */ + case CHAR_EQUALS_SIGN: /* Positive lookahead */ + bravalue = OP_ASSERT; + cb->assert_depth += 1; + ptr++; + break; + + /* Optimize (?!) to (*FAIL) unless it is quantified - which is a weird + thing to do, but Perl allows all assertions to be quantified, and when + they contain capturing parentheses there may be a potential use for + this feature. Not that that applies to a quantified (?!) but we allow + it for uniformity. */ + + /* ------------------------------------------------------------ */ + case CHAR_EXCLAMATION_MARK: /* Negative lookahead */ + ptr++; + if (*ptr == CHAR_RIGHT_PARENTHESIS && ptr[1] != CHAR_ASTERISK && + ptr[1] != CHAR_PLUS && ptr[1] != CHAR_QUESTION_MARK && + (ptr[1] != CHAR_LEFT_CURLY_BRACKET || !is_counted_repeat(ptr+2))) + { + *code++ = OP_FAIL; + previous = NULL; + continue; + } + bravalue = OP_ASSERT_NOT; + cb->assert_depth += 1; + break; + + + /* ------------------------------------------------------------ */ + case CHAR_LESS_THAN_SIGN: /* Lookbehind or named define */ + switch (ptr[1]) + { + case CHAR_EQUALS_SIGN: /* Positive lookbehind */ + bravalue = OP_ASSERTBACK; + cb->assert_depth += 1; + ptr += 2; + break; + + case CHAR_EXCLAMATION_MARK: /* Negative lookbehind */ + bravalue = OP_ASSERTBACK_NOT; + cb->assert_depth += 1; + ptr += 2; + break; + + /* Must be a name definition - as the syntax was checked in the + pre-pass, we can assume here that it is valid. Skip over the name + and go to handle the numbered group. */ + + default: + while (*(++ptr) != CHAR_GREATER_THAN_SIGN); + ptr++; + goto NUMBERED_GROUP; + } + break; + + + /* ------------------------------------------------------------ */ + case CHAR_GREATER_THAN_SIGN: /* One-time brackets */ + bravalue = OP_ONCE; + ptr++; + break; + + + /* ------------------------------------------------------------ */ + case CHAR_C: /* Callout */ + previous_callout = code; /* Save for later completion */ + after_manual_callout = 1; /* Skip one item before completing */ + ptr++; /* Character after (?C */ + + /* A callout may have a string argument, delimited by one of a fixed + number of characters, or an undelimited numerical argument, or no + argument, which is the same as (?C0). Different opcodes are used for + the two cases. */ + + if (*ptr != CHAR_RIGHT_PARENTHESIS && !IS_DIGIT(*ptr)) + { + uint32_t delimiter = 0; + + for (i = 0; PRIV(callout_start_delims)[i] != 0; i++) + { + if (*ptr == PRIV(callout_start_delims)[i]) + { + delimiter = PRIV(callout_end_delims)[i]; + break; + } + } + + if (delimiter == 0) + { + *errorcodeptr = ERR82; + goto FAILED; + } + + /* During the pre-compile phase, we parse the string and update the + length. There is no need to generate any code. (In fact, the string + has already been parsed in the pre-pass that looks for named + parentheses, but it does no harm to leave this code in.) */ + + if (lengthptr != NULL) /* Only check the string */ + { + PCRE2_SPTR start = ptr; + do + { + if (++ptr >= cb->end_pattern) + { + *errorcodeptr = ERR81; + ptr = start; /* To give a more useful message */ + goto FAILED; + } + if (ptr[0] == delimiter && ptr[1] == delimiter) ptr += 2; + } + while (ptr[0] != delimiter); + + /* Start points to the opening delimiter, ptr points to the + closing delimiter. We must allow for including the delimiter and + for the terminating zero. Any doubled delimiters within the string + make this an overestimate, but it is not worth bothering about. */ + + (*lengthptr) += (ptr - start) + 2 + (1 + 4*LINK_SIZE); + } + + /* In the real compile we can copy the string, knowing that it is + syntactically OK. The starting delimiter is included so that the + client can discover it if they want. We also pass the start offset to + help a script language give better error messages. */ + + else + { + PCRE2_UCHAR *callout_string = code + (1 + 4*LINK_SIZE); + *callout_string++ = *ptr++; + PUT(code, 1 + 3*LINK_SIZE, (int)(ptr - cb->start_pattern)); /* Start offset */ + for(;;) + { + if (*ptr == delimiter) + { + if (ptr[1] == delimiter) ptr++; else break; + } + *callout_string++ = *ptr++; + } + *callout_string++ = CHAR_NULL; + code[0] = OP_CALLOUT_STR; + PUT(code, 1, (int)(ptr + 2 - cb->start_pattern)); /* Next offset */ + PUT(code, 1 + LINK_SIZE, 0); /* Default length */ + PUT(code, 1 + 2*LINK_SIZE, /* Compute size */ + (int)(callout_string - code)); + code = callout_string; + } + + /* Advance to what should be the closing parenthesis, which is + checked below. */ + + ptr++; + } + + /* Handle a callout with an optional numerical argument, which must be + less than or equal to 255. A missing argument gives 0. */ + + else + { + int n = 0; + code[0] = OP_CALLOUT; /* Numerical callout */ + while (IS_DIGIT(*ptr)) + { + n = n * 10 + *ptr++ - CHAR_0; + if (n > 255) + { + *errorcodeptr = ERR38; + goto FAILED; + } + } + PUT(code, 1, (int)(ptr - cb->start_pattern + 1)); /* Next offset */ + PUT(code, 1 + LINK_SIZE, 0); /* Default length */ + code[1 + 2*LINK_SIZE] = n; /* Callout number */ + code += PRIV(OP_lengths)[OP_CALLOUT]; + } + + /* Both formats must have a closing parenthesis */ + + if (*ptr != CHAR_RIGHT_PARENTHESIS) + { + *errorcodeptr = ERR39; + goto FAILED; + } + + /* Callouts cannot be quantified. */ + + previous = NULL; + continue; + + + /* ------------------------------------------------------------ */ + case CHAR_P: /* Python-style named subpattern handling */ + if (*(++ptr) == CHAR_EQUALS_SIGN || + *ptr == CHAR_GREATER_THAN_SIGN) /* Reference or recursion */ + { + is_recurse = *ptr == CHAR_GREATER_THAN_SIGN; + terminator = CHAR_RIGHT_PARENTHESIS; + goto NAMED_REF_OR_RECURSE; + } + else if (*ptr != CHAR_LESS_THAN_SIGN) /* Test for Python-style defn */ + { + *errorcodeptr = ERR41; + goto FAILED; + } + /* Fall through to handle (?P< as (?< is handled */ + + + /* ------------------------------------------------------------ */ + case CHAR_APOSTROPHE: /* Define a name - note fall through above */ + + /* The syntax was checked and the list of names was set up in the + pre-pass, so there is nothing to be done now except to skip over the + name. */ + + terminator = (*ptr == CHAR_LESS_THAN_SIGN)? + CHAR_GREATER_THAN_SIGN : CHAR_APOSTROPHE; + while (*(++ptr) != (unsigned int)terminator); + ptr++; + goto NUMBERED_GROUP; /* Set up numbered group */ + + + /* ------------------------------------------------------------ */ + case CHAR_AMPERSAND: /* Perl recursion/subroutine syntax */ + terminator = CHAR_RIGHT_PARENTHESIS; + is_recurse = TRUE; + /* Fall through */ + + /* We come here from the Python syntax above that handles both + references (?P=name) and recursion (?P>name), as well as falling + through from the Perl recursion syntax (?&name). We also come here from + the Perl \k or \k'name' back reference syntax and the \k{name} + .NET syntax, and the Oniguruma \g<...> and \g'...' subroutine syntax. */ + + NAMED_REF_OR_RECURSE: + name = ++ptr; + if (IS_DIGIT(*ptr)) + { + *errorcodeptr = ERR44; /* Group name must start with non-digit */ + goto FAILED; + } + /* Increment ptr, set namelen, check length */ + READ_NAME(ctype_word, ERR48, *errorcodeptr); + + /* In the pre-compile phase, do a syntax check. */ + + if (lengthptr != NULL) + { + if (namelen == 0) + { + *errorcodeptr = ERR62; + goto FAILED; + } + if (*ptr != (PCRE2_UCHAR)terminator) + { + *errorcodeptr = ERR42; + goto FAILED; + } + } + + /* Scan the list of names generated in the pre-pass in order to get + a number and whether or not this name is duplicated. */ + + recno = 0; + is_dupname = FALSE; + ng = cb->named_groups; + + for (i = 0; i < cb->names_found; i++, ng++) + { + if (namelen == ng->length && + PRIV(strncmp)(name, ng->name, namelen) == 0) + { + open_capitem *oc; + is_dupname = ng->isdup; + recno = ng->number; + + /* For a recursion, that's all that is needed. We can now go to the + code that handles numerical recursion. */ + + if (is_recurse) goto HANDLE_RECURSION; + + /* For a back reference, update the back reference map and the + maximum back reference. Then for each group we must check to see if + it is recursive, that is, it is inside the group that it + references. A flag is set so that the group can be made atomic. */ + + cb->backref_map |= (recno < 32)? (1u << recno) : 1; + if ((uint32_t)recno > cb->top_backref) cb->top_backref = recno; + + for (oc = cb->open_caps; oc != NULL; oc = oc->next) + { + if (oc->number == recno) + { + oc->flag = TRUE; + break; + } + } + } + } + + /* If the name was not found we have a bad reference. */ + + if (recno == 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + + /* If a back reference name is not duplicated, we can handle it as a + numerical reference. */ + + if (!is_dupname) goto HANDLE_REFERENCE; + + /* If a back reference name is duplicated, we generate a different + opcode to a numerical back reference. In the second pass we must search + for the index and count in the final name table. */ + + count = 0; + index = 0; + + if (lengthptr == NULL) + { + slot = cb->name_table; + for (i = 0; i < cb->names_found; i++) + { + if (PRIV(strncmp)(name, slot+IMM2_SIZE, namelen) == 0 && + slot[IMM2_SIZE+namelen] == 0) + { + if (count == 0) index = i; + count++; + } + slot += cb->name_entry_size; + } + + if (count == 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + } + + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + previous = code; + *code++ = ((options & PCRE2_CASELESS) != 0)? OP_DNREFI : OP_DNREF; + PUT2INC(code, 0, index); + PUT2INC(code, 0, count); + continue; /* End of back ref handling */ + + + /* ------------------------------------------------------------ */ + case CHAR_R: /* Recursion, same as (?0) */ + recno = 0; + if (*(++ptr) != CHAR_RIGHT_PARENTHESIS) + { + *errorcodeptr = ERR29; + goto FAILED; + } + goto HANDLE_RECURSION; + + + /* ------------------------------------------------------------ */ + case CHAR_MINUS: case CHAR_PLUS: /* Recursion or subroutine */ + case CHAR_0: case CHAR_1: case CHAR_2: case CHAR_3: case CHAR_4: + case CHAR_5: case CHAR_6: case CHAR_7: case CHAR_8: case CHAR_9: + { + terminator = CHAR_RIGHT_PARENTHESIS; + + /* Come here from the \g<...> and \g'...' code (Oniguruma + compatibility). However, the syntax has been checked to ensure that + the ... are a (signed) number, so that neither ERR63 nor ERR29 will + be called on this path, nor with the jump to OTHER_CHAR_AFTER_QUERY + ever be taken. */ + + HANDLE_NUMERICAL_RECURSION: + + if ((refsign = *ptr) == CHAR_PLUS) + { + ptr++; + if (!IS_DIGIT(*ptr)) + { + *errorcodeptr = ERR63; + goto FAILED; + } + } + else if (refsign == CHAR_MINUS) + { + if (!IS_DIGIT(ptr[1])) + goto OTHER_CHAR_AFTER_QUERY; + ptr++; + } + + recno = 0; + while (IS_DIGIT(*ptr)) + { + if (recno > INT_MAX / 10 - 1) /* Integer overflow */ + { + while (IS_DIGIT(*ptr)) ptr++; + *errorcodeptr = ERR61; + goto FAILED; + } + recno = recno * 10 + *ptr++ - CHAR_0; + } + + if (*ptr != (PCRE2_UCHAR)terminator) + { + *errorcodeptr = ERR29; + goto FAILED; + } + + if (refsign == CHAR_MINUS) + { + if (recno == 0) + { + *errorcodeptr = ERR58; + goto FAILED; + } + recno = (int)(cb->bracount + 1) - recno; + if (recno <= 0) + { + *errorcodeptr = ERR15; + goto FAILED; + } + } + else if (refsign == CHAR_PLUS) + { + if (recno == 0) + { + *errorcodeptr = ERR58; + goto FAILED; + } + recno += cb->bracount; + } + + if ((uint32_t)recno > cb->final_bracount) + { + *errorcodeptr = ERR15; + goto FAILED; + } + + /* Come here from code above that handles a named recursion. + We insert the number of the called group after OP_RECURSE. At the + end of compiling the pattern is scanned and these numbers are + replaced by offsets within the pattern. It is done like this to avoid + problems with forward references and adjusting offsets when groups + are duplicated and moved (as discovered in previous implementations). + Note that a recursion does not have a set first character (relevant + if it is repeated, because it will then be wrapped with ONCE + brackets). */ + + HANDLE_RECURSION: + previous = code; + *code = OP_RECURSE; + PUT(code, 1, recno); + code += 1 + LINK_SIZE; + groupsetfirstcu = FALSE; + cb->had_recurse = TRUE; + } + + /* Can't determine a first byte now */ + + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + continue; + + + /* ------------------------------------------------------------ */ + default: /* Other characters: check option setting */ + OTHER_CHAR_AFTER_QUERY: + set = unset = 0; + optset = &set; + + while (*ptr != CHAR_RIGHT_PARENTHESIS && *ptr != CHAR_COLON) + { + switch (*ptr++) + { + case CHAR_MINUS: optset = &unset; break; + + case CHAR_J: /* Record that it changed in the external options */ + *optset |= PCRE2_DUPNAMES; + cb->external_flags |= PCRE2_JCHANGED; + break; + + case CHAR_i: *optset |= PCRE2_CASELESS; break; + case CHAR_m: *optset |= PCRE2_MULTILINE; break; + case CHAR_s: *optset |= PCRE2_DOTALL; break; + case CHAR_x: *optset |= PCRE2_EXTENDED; break; + case CHAR_U: *optset |= PCRE2_UNGREEDY; break; + + default: *errorcodeptr = ERR11; + ptr--; /* Correct the offset */ + goto FAILED; + } + } + + /* Set up the changed option bits, but don't change anything yet. */ + + newoptions = (options | set) & (~unset); + + /* If the options ended with ')' this is not the start of a nested + group with option changes, so the options change at this level. They + must also be passed back for use in subsequent branches. Reset the + greedy defaults and the case value for firstcu and reqcu. */ + + if (*ptr == CHAR_RIGHT_PARENTHESIS) + { + *optionsptr = options = newoptions; + greedy_default = ((newoptions & PCRE2_UNGREEDY) != 0); + greedy_non_default = greedy_default ^ 1; + req_caseopt = ((newoptions & PCRE2_CASELESS) != 0)? REQ_CASELESS:0; + previous = NULL; /* This item can't be repeated */ + continue; /* It is complete */ + } + + /* If the options ended with ':' we are heading into a nested group + with possible change of options. Such groups are non-capturing and are + not assertions of any kind. All we need to do is skip over the ':'; + the newoptions value is handled below. */ + + bravalue = OP_BRA; + ptr++; + } /* End of switch for character following (? */ + } /* End of (? handling */ + + /* Opening parenthesis not followed by '*' or '?'. If PCRE2_NO_AUTO_CAPTURE + is set, all unadorned brackets become non-capturing and behave like (?:...) + brackets. */ + + else if ((options & PCRE2_NO_AUTO_CAPTURE) != 0) + { + bravalue = OP_BRA; + } + + /* Else we have a capturing group. */ + + else + { + NUMBERED_GROUP: + cb->bracount += 1; + PUT2(code, 1+LINK_SIZE, cb->bracount); + skipunits = IMM2_SIZE; + } + + /* Process nested bracketed regex. First check for parentheses nested too + deeply. */ + + if ((cb->parens_depth += 1) > (int)(cb->cx->parens_nest_limit)) + { + *errorcodeptr = ERR19; + goto FAILED; + } + + /* All assertions used not to be repeatable, but this was changed for Perl + compatibility. All kinds can now be repeated except for assertions that are + conditions (Perl also forbids these to be repeated). We copy code into a + non-register variable (tempcode) in order to be able to pass its address + because some compilers complain otherwise. At the start of a conditional + group whose condition is an assertion, cb->iscondassert is set. We unset it + here so as to allow assertions later in the group to be quantified. */ + + if (bravalue >= OP_ASSERT && bravalue <= OP_ASSERTBACK_NOT && + cb->iscondassert) + { + previous = NULL; + cb->iscondassert = FALSE; + } + else + { + previous = code; + } + + *code = bravalue; + tempcode = code; + tempreqvary = cb->req_varyopt; /* Save value before bracket */ + tempbracount = cb->bracount; /* Save value before bracket */ + length_prevgroup = 0; /* Initialize for pre-compile phase */ + + if (!compile_regex( + newoptions, /* The complete new option state */ + &tempcode, /* Where to put code (updated) */ + &ptr, /* Input pointer (updated) */ + errorcodeptr, /* Where to put an error message */ + (bravalue == OP_ASSERTBACK || + bravalue == OP_ASSERTBACK_NOT), /* TRUE if back assert */ + reset_bracount, /* True if (?| group */ + skipunits, /* Skip over bracket number */ + cond_depth + + ((bravalue == OP_COND)?1:0), /* Depth of condition subpatterns */ + &subfirstcu, /* For possible first char */ + &subfirstcuflags, + &subreqcu, /* For possible last char */ + &subreqcuflags, + bcptr, /* Current branch chain */ + cb, /* Compile data block */ + (lengthptr == NULL)? NULL : /* Actual compile phase */ + &length_prevgroup /* Pre-compile phase */ + )) + goto FAILED; + + cb->parens_depth -= 1; + + /* If this was an atomic group and there are no capturing groups within it, + generate OP_ONCE_NC instead of OP_ONCE. */ + + if (bravalue == OP_ONCE && cb->bracount <= tempbracount) + *code = OP_ONCE_NC; + + if (bravalue >= OP_ASSERT && bravalue <= OP_ASSERTBACK_NOT) + cb->assert_depth -= 1; + + /* At the end of compiling, code is still pointing to the start of the + group, while tempcode has been updated to point past the end of the group. + The pattern pointer (ptr) is on the bracket. + + If this is a conditional bracket, check that there are no more than + two branches in the group, or just one if it's a DEFINE group. We do this + in the real compile phase, not in the pre-pass, where the whole group may + not be available. */ + + if (bravalue == OP_COND && lengthptr == NULL) + { + PCRE2_UCHAR *tc = code; + int condcount = 0; + + do { + condcount++; + tc += GET(tc,1); + } + while (*tc != OP_KET); + + /* A DEFINE group is never obeyed inline (the "condition" is always + false). It must have only one branch. Having checked this, change the + opcode to OP_FALSE. */ + + if (code[LINK_SIZE+1] == OP_DEFINE) + { + if (condcount > 1) + { + *errorcodeptr = ERR54; + goto FAILED; + } + code[LINK_SIZE+1] = OP_FALSE; + bravalue = OP_DEFINE; /* Just a flag to suppress char handling below */ + } + + /* A "normal" conditional group. If there is just one branch, we must not + make use of its firstcu or reqcu, because this is equivalent to an + empty second branch. */ + + else + { + if (condcount > 2) + { + *errorcodeptr = ERR27; + goto FAILED; + } + if (condcount == 1) subfirstcuflags = subreqcuflags = REQ_NONE; + } + } + + /* Error if hit end of pattern */ + + if (*ptr != CHAR_RIGHT_PARENTHESIS) + { + *errorcodeptr = ERR14; + goto FAILED; + } + + /* In the pre-compile phase, update the length by the length of the group, + less the brackets at either end. Then reduce the compiled code to just a + set of non-capturing brackets so that it doesn't use much memory if it is + duplicated by a quantifier.*/ + + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < length_prevgroup - 2 - 2*LINK_SIZE) + { + *errorcodeptr = ERR20; + goto FAILED; + } + *lengthptr += length_prevgroup - 2 - 2*LINK_SIZE; + code++; /* This already contains bravalue */ + PUTINC(code, 0, 1 + LINK_SIZE); + *code++ = OP_KET; + PUTINC(code, 0, 1 + LINK_SIZE); + break; /* No need to waste time with special character handling */ + } + + /* Otherwise update the main code pointer to the end of the group. */ + + code = tempcode; + + /* For a DEFINE group, required and first character settings are not + relevant. */ + + if (bravalue == OP_DEFINE) break; + + /* Handle updating of the required and first characters for other types of + group. Update for normal brackets of all kinds, and conditions with two + branches (see code above). If the bracket is followed by a quantifier with + zero repeat, we have to back off. Hence the definition of zeroreqcu and + zerofirstcu outside the main loop so that they can be accessed for the + back off. */ + + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + groupsetfirstcu = FALSE; + + if (bravalue >= OP_ONCE) + { + /* If we have not yet set a firstcu in this branch, take it from the + subpattern, remembering that it was set here so that a repeat of more + than one can replicate it as reqcu if necessary. If the subpattern has + no firstcu, set "none" for the whole branch. In both cases, a zero + repeat forces firstcu to "none". */ + + if (firstcuflags == REQ_UNSET && subfirstcuflags != REQ_UNSET) + { + if (subfirstcuflags >= 0) + { + firstcu = subfirstcu; + firstcuflags = subfirstcuflags; + groupsetfirstcu = TRUE; + } + else firstcuflags = REQ_NONE; + zerofirstcuflags = REQ_NONE; + } + + /* If firstcu was previously set, convert the subpattern's firstcu + into reqcu if there wasn't one, using the vary flag that was in + existence beforehand. */ + + else if (subfirstcuflags >= 0 && subreqcuflags < 0) + { + subreqcu = subfirstcu; + subreqcuflags = subfirstcuflags | tempreqvary; + } + + /* If the subpattern set a required byte (or set a first byte that isn't + really the first byte - see above), set it. */ + + if (subreqcuflags >= 0) + { + reqcu = subreqcu; + reqcuflags = subreqcuflags; + } + } + + /* For a forward assertion, we take the reqcu, if set. This can be + helpful if the pattern that follows the assertion doesn't set a different + char. For example, it's useful for /(?=abcde).+/. We can't set firstcu + for an assertion, however because it leads to incorrect effect for patterns + such as /(?=a)a.+/ when the "real" "a" would then become a reqcu instead + of a firstcu. This is overcome by a scan at the end if there's no + firstcu, looking for an asserted first char. */ + + else if (bravalue == OP_ASSERT && subreqcuflags >= 0) + { + reqcu = subreqcu; + reqcuflags = subreqcuflags; + } + break; /* End of processing '(' */ + + + /* ===================================================================*/ + /* Handle metasequences introduced by \. For ones like \d, the ESC_ values + are arranged to be the negation of the corresponding OP_values in the + default case when PCRE2_UCP is not set. For the back references, the values + are negative the reference number. Only back references and those types + that consume a character may be repeated. We can test for values between + ESC_b and ESC_Z for the latter; this may have to change if any new ones are + ever created. + + Note: \Q and \E are handled at the start of the character-processing loop, + not here. */ + + case CHAR_BACKSLASH: + tempptr = ptr; + escape = PRIV(check_escape)(&ptr, cb->end_pattern, &ec, errorcodeptr, + options, FALSE, cb); + if (*errorcodeptr != 0) goto FAILED; + + if (escape == 0) /* The escape coded a single character */ + c = ec; + else + { + /* For metasequences that actually match a character, we disable the + setting of a first character if it hasn't already been set. */ + + if (firstcuflags == REQ_UNSET && escape > ESC_b && escape < ESC_Z) + firstcuflags = REQ_NONE; + + /* Set values to reset to if this is followed by a zero repeat. */ + + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + + /* \g or \g'name' is a subroutine call by name and \g or \g'n' + is a subroutine call by number (Oniguruma syntax). In fact, the value + ESC_g is returned only for these cases. So we don't need to check for < + or ' if the value is ESC_g. For the Perl syntax \g{n} the value is + -n, and for the Perl syntax \g{name} the result is ESC_k (as + that is a synonym for a named back reference). */ + + if (escape == ESC_g) + { + PCRE2_SPTR p; + uint32_t cf; + + terminator = (*(++ptr) == CHAR_LESS_THAN_SIGN)? + CHAR_GREATER_THAN_SIGN : CHAR_APOSTROPHE; + + /* These two statements stop the compiler for warning about possibly + unset variables caused by the jump to HANDLE_NUMERICAL_RECURSION. In + fact, because we do the check for a number below, the paths that + would actually be in error are never taken. */ + + skipunits = 0; + reset_bracount = FALSE; + + /* If it's not a signed or unsigned number, treat it as a name. */ + + cf = ptr[1]; + if (cf != CHAR_PLUS && cf != CHAR_MINUS && !IS_DIGIT(cf)) + { + is_recurse = TRUE; + goto NAMED_REF_OR_RECURSE; + } + + /* Signed or unsigned number (cf = ptr[1]) is known to be plus or minus + or a digit. */ + + p = ptr + 2; + while (IS_DIGIT(*p)) p++; + if (*p != (PCRE2_UCHAR)terminator) + { + *errorcodeptr = ERR57; + goto FAILED; + } + ptr++; + goto HANDLE_NUMERICAL_RECURSION; + } + + /* \k or \k'name' is a back reference by name (Perl syntax). + We also support \k{name} (.NET syntax). */ + + if (escape == ESC_k) + { + if ((ptr[1] != CHAR_LESS_THAN_SIGN && + ptr[1] != CHAR_APOSTROPHE && ptr[1] != CHAR_LEFT_CURLY_BRACKET)) + { + *errorcodeptr = ERR69; + goto FAILED; + } + is_recurse = FALSE; + terminator = (*(++ptr) == CHAR_LESS_THAN_SIGN)? + CHAR_GREATER_THAN_SIGN : (*ptr == CHAR_APOSTROPHE)? + CHAR_APOSTROPHE : CHAR_RIGHT_CURLY_BRACKET; + goto NAMED_REF_OR_RECURSE; + } + + /* Back references are handled specially; must disable firstcu if + not set to cope with cases like (?=(\w+))\1: which would otherwise set + ':' later. */ + + if (escape < 0) + { + open_capitem *oc; + recno = -escape; + + /* Come here from named backref handling when the reference is to a + single group (i.e. not to a duplicated name). */ + + HANDLE_REFERENCE: + if (recno > (int)cb->final_bracount) + { + *errorcodeptr = ERR15; + goto FAILED; + } + if (firstcuflags == REQ_UNSET) firstcuflags = REQ_NONE; + previous = code; + *code++ = ((options & PCRE2_CASELESS) != 0)? OP_REFI : OP_REF; + PUT2INC(code, 0, recno); + cb->backref_map |= (recno < 32)? (1u << recno) : 1; + if ((uint32_t)recno > cb->top_backref) cb->top_backref = recno; + + /* Check to see if this back reference is recursive, that it, it + is inside the group that it references. A flag is set so that the + group can be made atomic. */ + + for (oc = cb->open_caps; oc != NULL; oc = oc->next) + { + if (oc->number == recno) + { + oc->flag = TRUE; + break; + } + } + } + + /* So are Unicode property matches, if supported. */ + +#ifdef SUPPORT_UNICODE + else if (escape == ESC_P || escape == ESC_p) + { + BOOL negated; + unsigned int ptype = 0, pdata = 0; + if (!get_ucp(&ptr, &negated, &ptype, &pdata, errorcodeptr, cb)) + goto FAILED; + previous = code; + *code++ = ((escape == ESC_p) != negated)? OP_PROP : OP_NOTPROP; + *code++ = ptype; + *code++ = pdata; + } +#else + + /* If Unicode properties are not supported, \X, \P, and \p are not + allowed. */ + + else if (escape == ESC_X || escape == ESC_P || escape == ESC_p) + { + *errorcodeptr = ERR45; + goto FAILED; + } +#endif + + /* The use of \C can be locked out. */ + +#ifdef NEVER_BACKSLASH_C + else if (escape == ESC_C) + { + *errorcodeptr = ERR85; + goto FAILED; + } +#else + else if (escape == ESC_C && (options & PCRE2_NEVER_BACKSLASH_C) != 0) + { + *errorcodeptr = ERR83; + goto FAILED; + } +#endif + + /* For the rest (including \X when Unicode properties are supported), we + can obtain the OP value by negating the escape value in the default + situation when PCRE2_UCP is not set. When it *is* set, we substitute + Unicode property tests. Note that \b and \B do a one-character + lookbehind, and \A also behaves as if it does. */ + + else + { + if (escape == ESC_C) cb->external_flags |= PCRE2_HASBKC; /* Record */ + if ((escape == ESC_b || escape == ESC_B || escape == ESC_A) && + cb->max_lookbehind == 0) + cb->max_lookbehind = 1; +#ifdef SUPPORT_UNICODE + if (escape >= ESC_DU && escape <= ESC_wu) + { + cb->nestptr[1] = cb->nestptr[0]; /* Back up if at 2nd level */ + cb->nestptr[0] = ptr + 1; /* Where to resume */ + ptr = substitutes[escape - ESC_DU] - 1; /* Just before substitute */ + } + else +#endif + /* In non-UTF mode, we turn \C into OP_ALLANY instead of OP_ANYBYTE + so that it works in DFA mode and in lookbehinds. */ + + { + previous = (escape > ESC_b && escape < ESC_Z)? code : NULL; + *code++ = (!utf && escape == ESC_C)? OP_ALLANY : escape; + } + } + continue; + } + + /* We have a data character whose value is in c. In UTF-8 mode it may have + a value > 127. We set its representation in the length/buffer, and then + handle it as a data character. */ + + mclength = PUTCHAR(c, mcbuffer); + goto ONE_CHAR; + + + /* ===================================================================*/ + /* Handle a literal character. It is guaranteed not to be whitespace or # + when the extended flag is set. If we are in a UTF mode, it may be a + multi-unit literal character. */ + + default: + NORMAL_CHAR: + mclength = 1; + mcbuffer[0] = c; + +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(c)) + ACROSSCHAR(TRUE, ptr[1], mcbuffer[mclength++] = *(++ptr)); +#endif + + /* At this point we have the character's bytes in mcbuffer, and the length + in mclength. When not in UTF mode, the length is always 1. */ + + ONE_CHAR: + previous = code; + + /* For caseless UTF mode, check whether this character has more than one + other case. If so, generate a special OP_PROP item instead of OP_CHARI. */ + +#ifdef SUPPORT_UNICODE + if (utf && (options & PCRE2_CASELESS) != 0) + { + GETCHAR(c, mcbuffer); + if ((c = UCD_CASESET(c)) != 0) + { + *code++ = OP_PROP; + *code++ = PT_CLIST; + *code++ = c; + if (firstcuflags == REQ_UNSET) + firstcuflags = zerofirstcuflags = REQ_NONE; + break; + } + } +#endif + + /* Caseful matches, or not one of the multicase characters. */ + + *code++ = ((options & PCRE2_CASELESS) != 0)? OP_CHARI : OP_CHAR; + for (c = 0; c < mclength; c++) *code++ = mcbuffer[c]; + + /* Remember if \r or \n were seen */ + + if (mcbuffer[0] == CHAR_CR || mcbuffer[0] == CHAR_NL) + cb->external_flags |= PCRE2_HASCRORLF; + + /* Set the first and required bytes appropriately. If no previous first + byte, set it from this character, but revert to none on a zero repeat. + Otherwise, leave the firstcu value alone, and don't change it on a zero + repeat. */ + + if (firstcuflags == REQ_UNSET) + { + zerofirstcuflags = REQ_NONE; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + + /* If the character is more than one byte long, we can set firstcu + only if it is not to be matched caselessly. */ + + if (mclength == 1 || req_caseopt == 0) + { + firstcu = mcbuffer[0] | req_caseopt; + firstcu = mcbuffer[0]; + firstcuflags = req_caseopt; + + if (mclength != 1) + { + reqcu = code[-1]; + reqcuflags = cb->req_varyopt; + } + } + else firstcuflags = reqcuflags = REQ_NONE; + } + + /* firstcu was previously set; we can set reqcu only if the length is + 1 or the matching is caseful. */ + + else + { + zerofirstcu = firstcu; + zerofirstcuflags = firstcuflags; + zeroreqcu = reqcu; + zeroreqcuflags = reqcuflags; + if (mclength == 1 || req_caseopt == 0) + { + reqcu = code[-1]; + reqcuflags = req_caseopt | cb->req_varyopt; + } + } + + break; /* End of literal character handling */ + } + } /* end of big loop */ + +/* Control never reaches here by falling through, only by a goto for all the +error states. Pass back the position in the pattern so that it can be displayed +to the user for diagnosing the error. */ + +FAILED: +*ptrptr = ptr; +return FALSE; +} + + + +/************************************************* +* Compile regex: a sequence of alternatives * +*************************************************/ + +/* On entry, ptr is pointing past the bracket character, but on return it +points to the closing bracket, or vertical bar, or end of string. The code +variable is pointing at the byte into which the BRA operator has been stored. +This function is used during the pre-compile phase when we are trying to find +out the amount of memory needed, as well as during the real compile phase. The +value of lengthptr distinguishes the two phases. + +Arguments: + options option bits, including any changes for this subpattern + codeptr -> the address of the current code pointer + ptrptr -> the address of the current pattern pointer + errorcodeptr -> pointer to error code variable + lookbehind TRUE if this is a lookbehind assertion + reset_bracount TRUE to reset the count for each branch + skipunits skip this many code units at start (for brackets and OP_COND) + cond_depth depth of nesting for conditional subpatterns + firstcuptr place to put the first required code unit + firstcuflagsptr place to put the first code unit flags, or a negative number + reqcuptr place to put the last required code unit + reqcuflagsptr place to put the last required code unit flags, or a negative number + bcptr pointer to the chain of currently open branches + cb points to the data block with tables pointers etc. + lengthptr NULL during the real compile phase + points to length accumulator during pre-compile phase + +Returns: TRUE on success +*/ + +static BOOL +compile_regex(uint32_t options, PCRE2_UCHAR **codeptr, PCRE2_SPTR *ptrptr, + int *errorcodeptr, BOOL lookbehind, BOOL reset_bracount, uint32_t skipunits, + int cond_depth, uint32_t *firstcuptr, int32_t *firstcuflagsptr, + uint32_t *reqcuptr, int32_t *reqcuflagsptr, branch_chain *bcptr, + compile_block *cb, size_t *lengthptr) +{ +PCRE2_SPTR ptr = *ptrptr; +PCRE2_UCHAR *code = *codeptr; +PCRE2_UCHAR *last_branch = code; +PCRE2_UCHAR *start_bracket = code; +PCRE2_UCHAR *reverse_count = NULL; +open_capitem capitem; +int capnumber = 0; +uint32_t firstcu, reqcu; +int32_t firstcuflags, reqcuflags; +uint32_t branchfirstcu, branchreqcu; +int32_t branchfirstcuflags, branchreqcuflags; +size_t length; +unsigned int orig_bracount; +unsigned int max_bracount; +branch_chain bc; + +/* If set, call the external function that checks for stack availability. */ + +if (cb->cx->stack_guard != NULL && + cb->cx->stack_guard(cb->parens_depth, cb->cx->stack_guard_data)) + { + *errorcodeptr= ERR33; + return FALSE; + } + +/* Miscellaneous initialization */ + +bc.outer = bcptr; +bc.current_branch = code; + +firstcu = reqcu = 0; +firstcuflags = reqcuflags = REQ_UNSET; + +/* Accumulate the length for use in the pre-compile phase. Start with the +length of the BRA and KET and any extra code units that are required at the +beginning. We accumulate in a local variable to save frequent testing of +lengthptr for NULL. We cannot do this by looking at the value of 'code' at the +start and end of each alternative, because compiled items are discarded during +the pre-compile phase so that the work space is not exceeded. */ + +length = 2 + 2*LINK_SIZE + skipunits; + +/* WARNING: If the above line is changed for any reason, you must also change +the code that abstracts option settings at the start of the pattern and makes +them global. It tests the value of length for (2 + 2*LINK_SIZE) in the +pre-compile phase to find out whether or not anything has yet been compiled. + +If this is a capturing subpattern, add to the chain of open capturing items +so that we can detect them if (*ACCEPT) is encountered. This is also used to +detect groups that contain recursive back references to themselves. Note that +only OP_CBRA need be tested here; changing this opcode to one of its variants, +e.g. OP_SCBRAPOS, happens later, after the group has been compiled. */ + +if (*code == OP_CBRA) + { + capnumber = GET2(code, 1 + LINK_SIZE); + capitem.number = capnumber; + capitem.next = cb->open_caps; + capitem.flag = FALSE; + cb->open_caps = &capitem; + } + +/* Offset is set zero to mark that this bracket is still open */ + +PUT(code, 1, 0); +code += 1 + LINK_SIZE + skipunits; + +/* Loop for each alternative branch */ + +orig_bracount = max_bracount = cb->bracount; + +for (;;) + { + /* For a (?| group, reset the capturing bracket count so that each branch + uses the same numbers. */ + + if (reset_bracount) cb->bracount = orig_bracount; + + /* Set up dummy OP_REVERSE if lookbehind assertion */ + + if (lookbehind) + { + *code++ = OP_REVERSE; + reverse_count = code; + PUTINC(code, 0, 0); + length += 1 + LINK_SIZE; + } + + /* Now compile the branch; in the pre-compile phase its length gets added + into the length. */ + + if (!compile_branch(&options, &code, &ptr, errorcodeptr, &branchfirstcu, + &branchfirstcuflags, &branchreqcu, &branchreqcuflags, &bc, + cond_depth, cb, (lengthptr == NULL)? NULL : &length)) + { + *ptrptr = ptr; + return FALSE; + } + + /* Keep the highest bracket count in case (?| was used and some branch + has fewer than the rest. */ + + if (cb->bracount > max_bracount) max_bracount = cb->bracount; + + /* In the real compile phase, there is some post-processing to be done. */ + + if (lengthptr == NULL) + { + /* If this is the first branch, the firstcu and reqcu values for the + branch become the values for the regex. */ + + if (*last_branch != OP_ALT) + { + firstcu = branchfirstcu; + firstcuflags = branchfirstcuflags; + reqcu = branchreqcu; + reqcuflags = branchreqcuflags; + } + + /* If this is not the first branch, the first char and reqcu have to + match the values from all the previous branches, except that if the + previous value for reqcu didn't have REQ_VARY set, it can still match, + and we set REQ_VARY for the regex. */ + + else + { + /* If we previously had a firstcu, but it doesn't match the new branch, + we have to abandon the firstcu for the regex, but if there was + previously no reqcu, it takes on the value of the old firstcu. */ + + if (firstcuflags != branchfirstcuflags || firstcu != branchfirstcu) + { + if (firstcuflags >= 0) + { + if (reqcuflags < 0) + { + reqcu = firstcu; + reqcuflags = firstcuflags; + } + } + firstcuflags = REQ_NONE; + } + + /* If we (now or from before) have no firstcu, a firstcu from the + branch becomes a reqcu if there isn't a branch reqcu. */ + + if (firstcuflags < 0 && branchfirstcuflags >= 0 && + branchreqcuflags < 0) + { + branchreqcu = branchfirstcu; + branchreqcuflags = branchfirstcuflags; + } + + /* Now ensure that the reqcus match */ + + if (((reqcuflags & ~REQ_VARY) != (branchreqcuflags & ~REQ_VARY)) || + reqcu != branchreqcu) + reqcuflags = REQ_NONE; + else + { + reqcu = branchreqcu; + reqcuflags |= branchreqcuflags; /* To "or" REQ_VARY */ + } + } + + /* If lookbehind, check that this branch matches a fixed-length string, and + put the length into the OP_REVERSE item. Temporarily mark the end of the + branch with OP_END. If the branch contains OP_RECURSE, the result is + FFL_LATER (a negative value) because there may be forward references that + we can't check here. Set a flag to cause another lookbehind check at the + end. Why not do it all at the end? Because common errors can be picked up + here and the offset of the problem can be shown. */ + + if (lookbehind) + { + int fixed_length; + int count = 0; + *code = OP_END; + fixed_length = find_fixedlength(last_branch, (options & PCRE2_UTF) != 0, + FALSE, cb, NULL, &count); + if (fixed_length == FFL_LATER) + { + cb->check_lookbehind = TRUE; + } + else if (fixed_length < 0) + { + *errorcodeptr = fixed_length_errors[-fixed_length]; + *ptrptr = ptr; + return FALSE; + } + else + { + if (fixed_length > cb->max_lookbehind) + cb->max_lookbehind = fixed_length; + PUT(reverse_count, 0, fixed_length); + } + } + } + + /* Reached end of expression, either ')' or end of pattern. In the real + compile phase, go back through the alternative branches and reverse the chain + of offsets, with the field in the BRA item now becoming an offset to the + first alternative. If there are no alternatives, it points to the end of the + group. The length in the terminating ket is always the length of the whole + bracketed item. Return leaving the pointer at the terminating char. */ + + if (*ptr != CHAR_VERTICAL_LINE) + { + if (lengthptr == NULL) + { + size_t branch_length = code - last_branch; + do + { + size_t prev_length = GET(last_branch, 1); + PUT(last_branch, 1, branch_length); + branch_length = prev_length; + last_branch -= branch_length; + } + while (branch_length > 0); + } + + /* Fill in the ket */ + + *code = OP_KET; + PUT(code, 1, (int)(code - start_bracket)); + code += 1 + LINK_SIZE; + + /* If it was a capturing subpattern, check to see if it contained any + recursive back references. If so, we must wrap it in atomic brackets. In + any event, remove the block from the chain. */ + + if (capnumber > 0) + { + if (cb->open_caps->flag) + { + memmove(start_bracket + 1 + LINK_SIZE, start_bracket, + CU2BYTES(code - start_bracket)); + *start_bracket = OP_ONCE; + code += 1 + LINK_SIZE; + PUT(start_bracket, 1, (int)(code - start_bracket)); + *code = OP_KET; + PUT(code, 1, (int)(code - start_bracket)); + code += 1 + LINK_SIZE; + length += 2 + 2*LINK_SIZE; + } + cb->open_caps = cb->open_caps->next; + } + + /* Retain the highest bracket number, in case resetting was used. */ + + cb->bracount = max_bracount; + + /* Set values to pass back */ + + *codeptr = code; + *ptrptr = ptr; + *firstcuptr = firstcu; + *firstcuflagsptr = firstcuflags; + *reqcuptr = reqcu; + *reqcuflagsptr = reqcuflags; + if (lengthptr != NULL) + { + if (OFLOW_MAX - *lengthptr < length) + { + *errorcodeptr = ERR20; + return FALSE; + } + *lengthptr += length; + } + return TRUE; + } + + /* Another branch follows. In the pre-compile phase, we can move the code + pointer back to where it was for the start of the first branch. (That is, + pretend that each branch is the only one.) + + In the real compile phase, insert an ALT node. Its length field points back + to the previous branch while the bracket remains open. At the end the chain + is reversed. It's done like this so that the start of the bracket has a + zero offset until it is closed, making it possible to detect recursion. */ + + if (lengthptr != NULL) + { + code = *codeptr + 1 + LINK_SIZE + skipunits; + length += 1 + LINK_SIZE; + } + else + { + *code = OP_ALT; + PUT(code, 1, (int)(code - last_branch)); + bc.current_branch = last_branch = code; + code += 1 + LINK_SIZE; + } + + /* Advance past the vertical bar */ + + ptr++; + } +/* Control never reaches here */ +} + + + +/************************************************* +* Check for anchored pattern * +*************************************************/ + +/* Try to find out if this is an anchored regular expression. Consider each +alternative branch. If they all start with OP_SOD or OP_CIRC, or with a bracket +all of whose alternatives start with OP_SOD or OP_CIRC (recurse ad lib), then +it's anchored. However, if this is a multiline pattern, then only OP_SOD will +be found, because ^ generates OP_CIRCM in that mode. + +We can also consider a regex to be anchored if OP_SOM starts all its branches. +This is the code for \G, which means "match at start of match position, taking +into account the match offset". + +A branch is also implicitly anchored if it starts with .* and DOTALL is set, +because that will try the rest of the pattern at all possible matching points, +so there is no point trying again.... er .... + +.... except when the .* appears inside capturing parentheses, and there is a +subsequent back reference to those parentheses. We haven't enough information +to catch that case precisely. + +At first, the best we could do was to detect when .* was in capturing brackets +and the highest back reference was greater than or equal to that level. +However, by keeping a bitmap of the first 31 back references, we can catch some +of the more common cases more precisely. + +... A second exception is when the .* appears inside an atomic group, because +this prevents the number of characters it matches from being adjusted. + +Arguments: + code points to start of the compiled pattern + bracket_map a bitmap of which brackets we are inside while testing; this + handles up to substring 31; after that we just have to take + the less precise approach + cb points to the compile data block + atomcount atomic group level + +Returns: TRUE or FALSE +*/ + +static BOOL +is_anchored(register PCRE2_SPTR code, unsigned int bracket_map, + compile_block *cb, int atomcount) +{ +do { + PCRE2_SPTR scode = first_significant_code( + code + PRIV(OP_lengths)[*code], FALSE); + register int op = *scode; + + /* Non-capturing brackets */ + + if (op == OP_BRA || op == OP_BRAPOS || + op == OP_SBRA || op == OP_SBRAPOS) + { + if (!is_anchored(scode, bracket_map, cb, atomcount)) return FALSE; + } + + /* Capturing brackets */ + + else if (op == OP_CBRA || op == OP_CBRAPOS || + op == OP_SCBRA || op == OP_SCBRAPOS) + { + int n = GET2(scode, 1+LINK_SIZE); + int new_map = bracket_map | ((n < 32)? (1u << n) : 1); + if (!is_anchored(scode, new_map, cb, atomcount)) return FALSE; + } + + /* Positive forward assertions and conditions */ + + else if (op == OP_ASSERT || op == OP_COND) + { + if (!is_anchored(scode, bracket_map, cb, atomcount)) return FALSE; + } + + /* Atomic groups */ + + else if (op == OP_ONCE || op == OP_ONCE_NC) + { + if (!is_anchored(scode, bracket_map, cb, atomcount + 1)) + return FALSE; + } + + /* .* is not anchored unless DOTALL is set (which generates OP_ALLANY) and + it isn't in brackets that are or may be referenced or inside an atomic + group. There is also an option that disables auto-anchoring. */ + + else if ((op == OP_TYPESTAR || op == OP_TYPEMINSTAR || + op == OP_TYPEPOSSTAR)) + { + if (scode[1] != OP_ALLANY || (bracket_map & cb->backref_map) != 0 || + atomcount > 0 || cb->had_pruneorskip || + (cb->external_options & PCRE2_NO_DOTSTAR_ANCHOR) != 0) + return FALSE; + } + + /* Check for explicit anchoring */ + + else if (op != OP_SOD && op != OP_SOM && op != OP_CIRC) return FALSE; + + code += GET(code, 1); + } +while (*code == OP_ALT); /* Loop for each alternative */ +return TRUE; +} + + + +/************************************************* +* Check for starting with ^ or .* * +*************************************************/ + +/* This is called to find out if every branch starts with ^ or .* so that +"first char" processing can be done to speed things up in multiline +matching and for non-DOTALL patterns that start with .* (which must start at +the beginning or after \n). As in the case of is_anchored() (see above), we +have to take account of back references to capturing brackets that contain .* +because in that case we can't make the assumption. Also, the appearance of .* +inside atomic brackets or in a pattern that contains *PRUNE or *SKIP does not +count, because once again the assumption no longer holds. + +Arguments: + code points to start of the compiled pattern or a group + bracket_map a bitmap of which brackets we are inside while testing; this + handles up to substring 31; after that we just have to take + the less precise approach + cb points to the compile data + atomcount atomic group level + +Returns: TRUE or FALSE +*/ + +static BOOL +is_startline(PCRE2_SPTR code, unsigned int bracket_map, compile_block *cb, + int atomcount) +{ +do { + PCRE2_SPTR scode = first_significant_code( + code + PRIV(OP_lengths)[*code], FALSE); + register int op = *scode; + + /* If we are at the start of a conditional assertion group, *both* the + conditional assertion *and* what follows the condition must satisfy the test + for start of line. Other kinds of condition fail. Note that there may be an + auto-callout at the start of a condition. */ + + if (op == OP_COND) + { + scode += 1 + LINK_SIZE; + + if (*scode == OP_CALLOUT) scode += PRIV(OP_lengths)[OP_CALLOUT]; + else if (*scode == OP_CALLOUT_STR) scode += GET(scode, 1 + 2*LINK_SIZE); + + switch (*scode) + { + case OP_CREF: + case OP_DNCREF: + case OP_RREF: + case OP_DNRREF: + case OP_FAIL: + case OP_FALSE: + case OP_TRUE: + return FALSE; + + default: /* Assertion */ + if (!is_startline(scode, bracket_map, cb, atomcount)) return FALSE; + do scode += GET(scode, 1); while (*scode == OP_ALT); + scode += 1 + LINK_SIZE; + break; + } + scode = first_significant_code(scode, FALSE); + op = *scode; + } + + /* Non-capturing brackets */ + + if (op == OP_BRA || op == OP_BRAPOS || + op == OP_SBRA || op == OP_SBRAPOS) + { + if (!is_startline(scode, bracket_map, cb, atomcount)) return FALSE; + } + + /* Capturing brackets */ + + else if (op == OP_CBRA || op == OP_CBRAPOS || + op == OP_SCBRA || op == OP_SCBRAPOS) + { + int n = GET2(scode, 1+LINK_SIZE); + int new_map = bracket_map | ((n < 32)? (1u << n) : 1); + if (!is_startline(scode, new_map, cb, atomcount)) return FALSE; + } + + /* Positive forward assertions */ + + else if (op == OP_ASSERT) + { + if (!is_startline(scode, bracket_map, cb, atomcount)) return FALSE; + } + + /* Atomic brackets */ + + else if (op == OP_ONCE || op == OP_ONCE_NC) + { + if (!is_startline(scode, bracket_map, cb, atomcount + 1)) return FALSE; + } + + /* .* means "start at start or after \n" if it isn't in atomic brackets or + brackets that may be referenced, as long as the pattern does not contain + *PRUNE or *SKIP, because these break the feature. Consider, for example, + /.*?a(*PRUNE)b/ with the subject "aab", which matches "ab", i.e. not at the + start of a line. There is also an option that disables this optimization. */ + + else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR || op == OP_TYPEPOSSTAR) + { + if (scode[1] != OP_ANY || (bracket_map & cb->backref_map) != 0 || + atomcount > 0 || cb->had_pruneorskip || + (cb->external_options & PCRE2_NO_DOTSTAR_ANCHOR) != 0) + return FALSE; + } + + /* Check for explicit circumflex; anything else gives a FALSE result. Note + in particular that this includes atomic brackets OP_ONCE and OP_ONCE_NC + because the number of characters matched by .* cannot be adjusted inside + them. */ + + else if (op != OP_CIRC && op != OP_CIRCM) return FALSE; + + /* Move on to the next alternative */ + + code += GET(code, 1); + } +while (*code == OP_ALT); /* Loop for each alternative */ +return TRUE; +} + + + +/************************************************* +* Check for asserted fixed first code unit * +*************************************************/ + +/* During compilation, the "first code unit" settings from forward assertions +are discarded, because they can cause conflicts with actual literals that +follow. However, if we end up without a first code unit setting for an +unanchored pattern, it is worth scanning the regex to see if there is an +initial asserted first code unit. If all branches start with the same asserted +code unit, or with a non-conditional bracket all of whose alternatives start +with the same asserted code unit (recurse ad lib), then we return that code +unit, with the flags set to zero or REQ_CASELESS; otherwise return zero with +REQ_NONE in the flags. + +Arguments: + code points to start of compiled pattern + flags points to the first code unit flags + inassert TRUE if in an assertion + +Returns: the fixed first code unit, or 0 with REQ_NONE in flags +*/ + +static uint32_t +find_firstassertedcu(PCRE2_SPTR code, int32_t *flags, BOOL inassert) +{ +register uint32_t c = 0; +int cflags = REQ_NONE; + +*flags = REQ_NONE; +do { + uint32_t d; + int dflags; + int xl = (*code == OP_CBRA || *code == OP_SCBRA || + *code == OP_CBRAPOS || *code == OP_SCBRAPOS)? IMM2_SIZE:0; + PCRE2_SPTR scode = first_significant_code(code + 1+LINK_SIZE + xl, TRUE); + register PCRE2_UCHAR op = *scode; + + switch(op) + { + default: + return 0; + + case OP_BRA: + case OP_BRAPOS: + case OP_CBRA: + case OP_SCBRA: + case OP_CBRAPOS: + case OP_SCBRAPOS: + case OP_ASSERT: + case OP_ONCE: + case OP_ONCE_NC: + d = find_firstassertedcu(scode, &dflags, op == OP_ASSERT); + if (dflags < 0) + return 0; + if (cflags < 0) { c = d; cflags = dflags; } + else if (c != d || cflags != dflags) return 0; + break; + + case OP_EXACT: + scode += IMM2_SIZE; + /* Fall through */ + + case OP_CHAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + if (!inassert) return 0; + if (cflags < 0) { c = scode[1]; cflags = 0; } + else if (c != scode[1]) return 0; + break; + + case OP_EXACTI: + scode += IMM2_SIZE; + /* Fall through */ + + case OP_CHARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_POSPLUSI: + if (!inassert) return 0; + if (cflags < 0) { c = scode[1]; cflags = REQ_CASELESS; } + else if (c != scode[1]) return 0; + break; + } + + code += GET(code, 1); + } +while (*code == OP_ALT); + +*flags = cflags; +return c; +} + + + +/************************************************* +* Add an entry to the name/number table * +*************************************************/ + +/* This function is called between compiling passes to add an entry to the +name/number table, maintaining alphabetical order. Checking for permitted +and forbidden duplicates has already been done. + +Arguments: + cb the compile data block + name the name to add + length the length of the name + groupno the group number + +Returns: nothing +*/ + +static void +add_name_to_table(compile_block *cb, PCRE2_SPTR name, int length, + unsigned int groupno) +{ +int i; +PCRE2_UCHAR *slot = cb->name_table; + +for (i = 0; i < cb->names_found; i++) + { + int crc = memcmp(name, slot+IMM2_SIZE, CU2BYTES(length)); + if (crc == 0 && slot[IMM2_SIZE+length] != 0) + crc = -1; /* Current name is a substring */ + + /* Make space in the table and break the loop for an earlier name. For a + duplicate or later name, carry on. We do this for duplicates so that in the + simple case (when ?(| is not used) they are in order of their numbers. In all + cases they are in the order in which they appear in the pattern. */ + + if (crc < 0) + { + memmove(slot + cb->name_entry_size, slot, + CU2BYTES((cb->names_found - i) * cb->name_entry_size)); + break; + } + + /* Continue the loop for a later or duplicate name */ + + slot += cb->name_entry_size; + } + +PUT2(slot, 0, groupno); +memcpy(slot + IMM2_SIZE, name, CU2BYTES(length)); +cb->names_found++; + +/* Add a terminating zero and fill the rest of the slot with zeroes so that +the memory is all initialized. Otherwise valgrind moans about uninitialized +memory when saving serialized compiled patterns. */ + +memset(slot + IMM2_SIZE + length, 0, + CU2BYTES(cb->name_entry_size - length - IMM2_SIZE)); +} + + + +/************************************************* +* External function to compile a pattern * +*************************************************/ + +/* This function reads a regular expression in the form of a string and returns +a pointer to a block of store holding a compiled version of the expression. + +Arguments: + pattern the regular expression + patlen the length of the pattern, or PCRE2_ZERO_TERMINATED + options option bits + errorptr pointer to errorcode + erroroffset pointer to error offset + ccontext points to a compile context or is NULL + +Returns: pointer to compiled data block, or NULL on error, + with errorcode and erroroffset set +*/ + +PCRE2_EXP_DEFN pcre2_code * PCRE2_CALL_CONVENTION +pcre2_compile(PCRE2_SPTR pattern, PCRE2_SIZE patlen, uint32_t options, + int *errorptr, PCRE2_SIZE *erroroffset, pcre2_compile_context *ccontext) +{ +BOOL utf; /* Set TRUE for UTF mode */ +pcre2_real_code *re = NULL; /* What we will return */ +compile_block cb; /* "Static" compile-time data */ +const uint8_t *tables; /* Char tables base pointer */ + +PCRE2_UCHAR *code; /* Current pointer in compiled code */ +PCRE2_SPTR codestart; /* Start of compiled code */ +PCRE2_SPTR ptr; /* Current pointer in pattern */ + +size_t length = 1; /* Allow or final END opcode */ +size_t usedlength; /* Actual length used */ +size_t re_blocksize; /* Size of memory block */ + +int32_t firstcuflags, reqcuflags; /* Type of first/req code unit */ +uint32_t firstcu, reqcu; /* Value of first/req code unit */ +uint32_t setflags = 0; /* NL and BSR set flags */ + +uint32_t skipatstart; /* When checking (*UTF) etc */ +uint32_t limit_match = UINT32_MAX; /* Unset match limits */ +uint32_t limit_recursion = UINT32_MAX; + +int newline = 0; /* Unset; can be set by the pattern */ +int bsr = 0; /* Unset; can be set by the pattern */ +int errorcode = 0; /* Initialize to avoid compiler warn */ + +/* Comments at the head of this file explain about these variables. */ + +PCRE2_UCHAR *copied_pattern = NULL; +PCRE2_UCHAR stack_copied_pattern[COPIED_PATTERN_SIZE]; +named_group named_groups[NAMED_GROUP_LIST_SIZE]; + +/* The workspace is used in different ways in the different compiling phases. +It needs to be 16-bit aligned for the preliminary group scan, and 32-bit +aligned for the group information cache. */ + +uint32_t c32workspace[C32_WORK_SIZE]; +PCRE2_UCHAR *cworkspace = (PCRE2_UCHAR *)c32workspace; + + +/* -------------- Check arguments and set up the pattern ----------------- */ + +/* There must be error code and offset pointers. */ + +if (errorptr == NULL || erroroffset == NULL) return NULL; +*errorptr = ERR0; +*erroroffset = 0; + +/* There must be a pattern! */ + +if (pattern == NULL) + { + *errorptr = ERR16; + return NULL; + } + +/* Check that all undefined public option bits are zero. */ + +if ((options & ~PUBLIC_COMPILE_OPTIONS) != 0) + { + *errorptr = ERR17; + return NULL; + } + +/* A NULL compile context means "use a default context" */ + +if (ccontext == NULL) + ccontext = (pcre2_compile_context *)(&PRIV(default_compile_context)); + +/* A zero-terminated pattern is indicated by the special length value +PCRE2_ZERO_TERMINATED. Otherwise, we make a copy of the pattern and add a zero, +to ensure that it is always possible to look one code unit beyond the end of +the pattern's characters. In both cases, check that the pattern is overlong. */ + +if (patlen == PCRE2_ZERO_TERMINATED) + { + patlen = PRIV(strlen)(pattern); + if (patlen > ccontext->max_pattern_length) + { + *errorptr = ERR88; + return NULL; + } + } +else + { + if (patlen > ccontext->max_pattern_length) + { + *errorptr = ERR88; + return NULL; + } + if (patlen < COPIED_PATTERN_SIZE) + copied_pattern = stack_copied_pattern; + else + { + copied_pattern = ccontext->memctl.malloc(CU2BYTES(patlen + 1), + ccontext->memctl.memory_data); + if (copied_pattern == NULL) + { + *errorptr = ERR21; + return NULL; + } + } + memcpy(copied_pattern, pattern, CU2BYTES(patlen)); + copied_pattern[patlen] = 0; + pattern = copied_pattern; + } + +/* ------------ Initialize the "static" compile data -------------- */ + +tables = (ccontext->tables != NULL)? ccontext->tables : PRIV(default_tables); + +cb.lcc = tables + lcc_offset; /* Individual */ +cb.fcc = tables + fcc_offset; /* character */ +cb.cbits = tables + cbits_offset; /* tables */ +cb.ctypes = tables + ctypes_offset; + +cb.assert_depth = 0; +cb.bracount = cb.final_bracount = 0; +cb.cx = ccontext; +cb.dupnames = FALSE; +cb.end_pattern = pattern + patlen; +cb.nestptr[0] = cb.nestptr[1] = NULL; +cb.external_flags = 0; +cb.external_options = options; +cb.groupinfo = c32workspace; +cb.had_recurse = FALSE; +cb.iscondassert = FALSE; +cb.max_lookbehind = 0; +cb.name_entry_size = 0; +cb.name_table = NULL; +cb.named_groups = named_groups; +cb.named_group_list_size = NAMED_GROUP_LIST_SIZE; +cb.names_found = 0; +cb.open_caps = NULL; +cb.parens_depth = 0; +cb.req_varyopt = 0; +cb.start_code = cworkspace; +cb.start_pattern = pattern; +cb.start_workspace = cworkspace; +cb.workspace_size = COMPILE_WORK_SIZE; + +/* Maximum back reference and backref bitmap. The bitmap records up to 31 back +references to help in deciding whether (.*) can be treated as anchored or not. +*/ + +cb.top_backref = 0; +cb.backref_map = 0; + +/* --------------- Start looking at the pattern --------------- */ + +/* Check for global one-time option settings at the start of the pattern, and +remember the offset to the actual regex. */ + +ptr = pattern; +skipatstart = 0; + +while (ptr[skipatstart] == CHAR_LEFT_PARENTHESIS && + ptr[skipatstart+1] == CHAR_ASTERISK) + { + unsigned int i; + for (i = 0; i < sizeof(pso_list)/sizeof(pso); i++) + { + pso *p = pso_list + i; + + if (PRIV(strncmp_c8)(ptr+skipatstart+2, (char *)(p->name), p->length) == 0) + { + uint32_t c, pp; + + skipatstart += p->length + 2; + switch(p->type) + { + case PSO_OPT: + cb.external_options |= p->value; + break; + + case PSO_FLG: + setflags |= p->value; + break; + + case PSO_NL: + newline = p->value; + setflags |= PCRE2_NL_SET; + break; + + case PSO_BSR: + bsr = p->value; + setflags |= PCRE2_BSR_SET; + break; + + case PSO_LIMM: + case PSO_LIMR: + c = 0; + pp = skipatstart; + if (!IS_DIGIT(ptr[pp])) + { + errorcode = ERR60; + ptr += pp; + goto HAD_ERROR; + } + while (IS_DIGIT(ptr[pp])) + { + if (c > UINT32_MAX / 10 - 1) break; /* Integer overflow */ + c = c*10 + (ptr[pp++] - CHAR_0); + } + if (ptr[pp++] != CHAR_RIGHT_PARENTHESIS) + { + errorcode = ERR60; + ptr += pp; + goto HAD_ERROR; + } + if (p->type == PSO_LIMM) limit_match = c; + else limit_recursion = c; + skipatstart += pp - skipatstart; + break; + } + break; /* Out of the table scan loop */ + } + } + if (i >= sizeof(pso_list)/sizeof(pso)) break; /* Out of pso loop */ + } + +/* End of pattern-start options; advance to start of real regex. */ + +ptr += skipatstart; + +/* Can't support UTF or UCP unless PCRE2 has been compiled with UTF support. */ + +#ifndef SUPPORT_UNICODE +if ((cb.external_options & (PCRE2_UTF|PCRE2_UCP)) != 0) + { + errorcode = ERR32; + goto HAD_ERROR; + } +#endif + +/* Check UTF. We have the original options in 'options', with that value as +modified by (*UTF) etc in cb->external_options. */ + +utf = (cb.external_options & PCRE2_UTF) != 0; +if (utf) + { + if ((options & PCRE2_NEVER_UTF) != 0) + { + errorcode = ERR74; + goto HAD_ERROR; + } + if ((options & PCRE2_NO_UTF_CHECK) == 0 && + (errorcode = PRIV(valid_utf)(pattern, patlen, erroroffset)) != 0) + goto HAD_UTF_ERROR; + } + +/* Check UCP lockout. */ + +if ((cb.external_options & (PCRE2_UCP|PCRE2_NEVER_UCP)) == + (PCRE2_UCP|PCRE2_NEVER_UCP)) + { + errorcode = ERR75; + goto HAD_ERROR; + } + +/* Process the BSR setting. */ + +if (bsr == 0) bsr = ccontext->bsr_convention; + +/* Process the newline setting. */ + +if (newline == 0) newline = ccontext->newline_convention; +cb.nltype = NLTYPE_FIXED; +switch(newline) + { + case PCRE2_NEWLINE_CR: + cb.nllen = 1; + cb.nl[0] = CHAR_CR; + break; + + case PCRE2_NEWLINE_LF: + cb.nllen = 1; + cb.nl[0] = CHAR_NL; + break; + + case PCRE2_NEWLINE_CRLF: + cb.nllen = 2; + cb.nl[0] = CHAR_CR; + cb.nl[1] = CHAR_NL; + break; + + case PCRE2_NEWLINE_ANY: + cb.nltype = NLTYPE_ANY; + break; + + case PCRE2_NEWLINE_ANYCRLF: + cb.nltype = NLTYPE_ANYCRLF; + break; + + default: + errorcode = ERR56; + goto HAD_ERROR; + } + +/* Before we do anything else, do a pre-scan of the pattern in order to +discover the named groups and their numerical equivalents, so that this +information is always available for the remaining processing. */ + +errorcode = scan_for_captures(&ptr, cb.external_options, &cb); +if (errorcode != 0) goto HAD_ERROR; + +/* For obscure debugging this code can be enabled. */ + +#if 0 + { + int i; + named_group *ng = cb.named_groups; + fprintf(stderr, "+++Captures: %d\n", cb.final_bracount); + for (i = 0; i < cb.names_found; i++, ng++) + { + fprintf(stderr, "+++%3d %.*s\n", ng->number, ng->length, ng->name); + } + } +#endif + +/* Reset current bracket count to zero and current pointer to the start of the +pattern. */ + +cb.bracount = 0; +ptr = pattern + skipatstart; + +/* Pretend to compile the pattern while actually just accumulating the amount +of memory required in the 'length' variable. This behaviour is triggered by +passing a non-NULL final argument to compile_regex(). We pass a block of +workspace (cworkspace) for it to compile parts of the pattern into; the +compiled code is discarded when it is no longer needed, so hopefully this +workspace will never overflow, though there is a test for its doing so. + +On error, errorcode will be set non-zero, so we don't need to look at the +result of the function. The initial options have been put into the cb block so +that they can be changed if an option setting is found within the regex right +at the beginning. Bringing initial option settings outside can help speed up +starting point checks. We still have to pass a separate options variable (the +first argument) because that may change as the pattern is processed. */ + +code = cworkspace; +*code = OP_BRA; + +(void)compile_regex(cb.external_options, &code, &ptr, &errorcode, FALSE, + FALSE, 0, 0, &firstcu, &firstcuflags, &reqcu, &reqcuflags, NULL, + &cb, &length); + +if (errorcode != 0) goto HAD_ERROR; +if (length > MAX_PATTERN_SIZE) + { + errorcode = ERR20; + goto HAD_ERROR; + } + +/* Compute the size of, and then get and initialize, the data block for storing +the compiled pattern and names table. Integer overflow should no longer be +possible because nowadays we limit the maximum value of cb.names_found and +cb.name_entry_size. */ + +re_blocksize = sizeof(pcre2_real_code) + + CU2BYTES(length + cb.names_found * cb.name_entry_size); +re = (pcre2_real_code *) + ccontext->memctl.malloc(re_blocksize, ccontext->memctl.memory_data); +if (re == NULL) + { + errorcode = ERR21; + goto HAD_ERROR; + } + +re->memctl = ccontext->memctl; +re->tables = tables; +re->executable_jit = NULL; +memset(re->start_bitmap, 0, 32 * sizeof(uint8_t)); +re->blocksize = re_blocksize; +re->magic_number = MAGIC_NUMBER; +re->compile_options = options; +re->overall_options = cb.external_options; +re->flags = PCRE2_CODE_UNIT_WIDTH/8 | cb.external_flags | setflags; +re->limit_match = limit_match; +re->limit_recursion = limit_recursion; +re->first_codeunit = 0; +re->last_codeunit = 0; +re->bsr_convention = bsr; +re->newline_convention = newline; +re->max_lookbehind = 0; +re->minlength = 0; +re->top_bracket = 0; +re->top_backref = 0; +re->name_entry_size = cb.name_entry_size; +re->name_count = cb.names_found; + +/* The basic block is immediately followed by the name table, and the compiled +code follows after that. */ + +codestart = (PCRE2_SPTR)((uint8_t *)re + sizeof(pcre2_real_code)) + + re->name_entry_size * re->name_count; + +/* Workspace is needed to remember information about numbered groups: whether a +group can match an empty string and what its fixed length is. This is done to +avoid the possibility of recursive references causing very long compile times +when checking these features. Unnumbered groups do not have this exposure since +they cannot be referenced. We use an indexed vector for this purpose. If there +are sufficiently few groups, it can be the c32workspace vector, as set up +above. Otherwise we have to get/free a special vector. The vector must be +initialized to zero. */ + +if (cb.final_bracount >= C32_WORK_SIZE) + { + cb.groupinfo = ccontext->memctl.malloc( + (cb.final_bracount + 1)*sizeof(uint32_t), ccontext->memctl.memory_data); + if (cb.groupinfo == NULL) + { + errorcode = ERR21; + goto HAD_ERROR; + } + } +memset(cb.groupinfo, 0, (cb.final_bracount + 1) * sizeof(uint32_t)); + +/* Update the compile data block for the actual compile. The starting points of +the name/number translation table and of the code are passed around in the +compile data block. The start/end pattern and initial options are already set +from the pre-compile phase, as is the name_entry_size field. Reset the bracket +count and the names_found field. */ + +cb.parens_depth = 0; +cb.assert_depth = 0; +cb.bracount = 0; +cb.max_lookbehind = 0; +cb.name_table = (PCRE2_UCHAR *)((uint8_t *)re + sizeof(pcre2_real_code)); +cb.start_code = codestart; +cb.iscondassert = FALSE; +cb.req_varyopt = 0; +cb.had_accept = FALSE; +cb.had_pruneorskip = FALSE; +cb.check_lookbehind = FALSE; +cb.open_caps = NULL; + +/* If any named groups were found, create the name/number table from the list +created in the pre-pass. */ + +if (cb.names_found > 0) + { + int i = cb.names_found; + named_group *ng = cb.named_groups; + cb.names_found = 0; + for (; i > 0; i--, ng++) + add_name_to_table(&cb, ng->name, ng->length, ng->number); + } + +/* Set up a starting, non-extracting bracket, then compile the expression. On +error, errorcode will be set non-zero, so we don't need to look at the result +of the function here. */ + +ptr = pattern + skipatstart; +code = (PCRE2_UCHAR *)codestart; +*code = OP_BRA; +(void)compile_regex(re->overall_options, &code, &ptr, &errorcode, FALSE, FALSE, + 0, 0, &firstcu, &firstcuflags, &reqcu, &reqcuflags, NULL, &cb, NULL); + +re->top_bracket = cb.bracount; +re->top_backref = cb.top_backref; +re->max_lookbehind = cb.max_lookbehind; + +if (cb.had_accept) + { + reqcu = 0; /* Must disable after (*ACCEPT) */ + reqcuflags = REQ_NONE; + } + +/* If we have not reached end of pattern after a successful compile, there's an +excess bracket. Fill in the final opcode and check for disastrous overflow. +If no overflow, but the estimated length exceeds the really used length, adjust +the value of re->blocksize, and if valgrind support is configured, mark the +extra allocated memory as unaddressable, so that any out-of-bound reads can be +detected. */ + +if (errorcode == 0 && ptr < cb.end_pattern) errorcode = ERR22; +*code++ = OP_END; +usedlength = code - codestart; +if (usedlength > length) errorcode = ERR23; else + { + re->blocksize -= CU2BYTES(length - usedlength); +#ifdef SUPPORT_VALGRIND + VALGRIND_MAKE_MEM_NOACCESS(code, CU2BYTES(length - usedlength)); +#endif + } + +/* Scan the pattern for recursion/subroutine calls and convert the group +numbers into offsets. Maintain a small cache so that repeated groups containing +recursions are efficiently handled. */ + +#define RSCAN_CACHE_SIZE 8 + +if (errorcode == 0 && cb.had_recurse) + { + PCRE2_UCHAR *rcode; + PCRE2_SPTR rgroup; + int ccount = 0; + int start = RSCAN_CACHE_SIZE; + recurse_cache rc[RSCAN_CACHE_SIZE]; + + for (rcode = (PCRE2_UCHAR *)find_recurse(codestart, utf); + rcode != NULL; + rcode = (PCRE2_UCHAR *)find_recurse(rcode + 1 + LINK_SIZE, utf)) + { + int i, p, recno; + + recno = (int)GET(rcode, 1); + if (recno == 0) rgroup = codestart; else + { + PCRE2_SPTR search_from = codestart; + rgroup = NULL; + for (i = 0, p = start; i < ccount; i++, p = (p + 1) & 7) + { + if (recno == rc[p].recno) + { + rgroup = rc[p].group; + break; + } + + /* Group n+1 must always start to the right of group n, so we can save + search time below when the new group number is greater than any of the + previously found groups. */ + + if (recno > rc[p].recno) search_from = rc[p].group; + } + + if (rgroup == NULL) + { + rgroup = PRIV(find_bracket)(search_from, utf, recno); + if (rgroup == NULL) + { + errorcode = ERR53; + break; + } + if (--start < 0) start = RSCAN_CACHE_SIZE - 1; + rc[start].recno = recno; + rc[start].group = rgroup; + if (ccount < RSCAN_CACHE_SIZE) ccount++; + } + } + + PUT(rcode, 1, rgroup - codestart); + } + } + +/* In rare debugging situations we sometimes need to look at the compiled code +at this stage. */ + +#ifdef CALL_PRINTINT +pcre2_printint(re, stderr, TRUE); +fprintf(stderr, "Length=%lu Used=%lu\n", length, usedlength); +#endif + +/* After a successful compile, give an error if there's back reference to a +non-existent capturing subpattern. Then, unless disabled, check whether any +single character iterators can be auto-possessified. The function overwrites +the appropriate opcode values, so the type of the pointer must be cast. NOTE: +the intermediate variable "temp" is used in this code because at least one +compiler gives a warning about loss of "const" attribute if the cast +(PCRE2_UCHAR *)codestart is used directly in the function call. */ + +if (errorcode == 0) + { + if (re->top_backref > re->top_bracket) errorcode = ERR15; + else if ((re->overall_options & PCRE2_NO_AUTO_POSSESS) == 0) + { + PCRE2_UCHAR *temp = (PCRE2_UCHAR *)codestart; + if (PRIV(auto_possessify)(temp, utf, &cb) != 0) errorcode = ERR80; + } + } + +/* If there were any lookbehind assertions that contained OP_RECURSE +(recursions or subroutine calls), a flag is set for them to be checked here, +because they may contain forward references. Actual recursions cannot be fixed +length, but subroutine calls can. It is done like this so that those without +OP_RECURSE that are not fixed length get a diagnosic with a useful offset. The +exceptional ones forgo this. We scan the pattern to check that they are fixed +length, and set their lengths. */ + +if (errorcode == 0 && cb.check_lookbehind) + { + PCRE2_UCHAR *cc = (PCRE2_UCHAR *)codestart; + + /* Loop, searching for OP_REVERSE items, and process those that do not have + their length set. (Actually, it will also re-process any that have a length + of zero, but that is a pathological case, and it does no harm.) When we find + one, we temporarily terminate the branch it is in while we scan it. Note that + calling find_bracket() with a negative group number returns a pointer to the + OP_REVERSE item, not the actual lookbehind. */ + + for (cc = (PCRE2_UCHAR *)PRIV(find_bracket)(codestart, utf, -1); + cc != NULL; + cc = (PCRE2_UCHAR *)PRIV(find_bracket)(cc, utf, -1)) + { + if (GET(cc, 1) == 0) + { + int fixed_length; + int count = 0; + PCRE2_UCHAR *be = cc - 1 - LINK_SIZE + GET(cc, -LINK_SIZE); + int end_op = *be; + *be = OP_END; + fixed_length = find_fixedlength(cc, utf, TRUE, &cb, NULL, &count); + *be = end_op; + if (fixed_length < 0) + { + errorcode = fixed_length_errors[-fixed_length]; + break; + } + if (fixed_length > cb.max_lookbehind) cb.max_lookbehind = fixed_length; + PUT(cc, 1, fixed_length); + } + cc += 1 + LINK_SIZE; + } + + /* The previous value of the maximum lookbehind was transferred to the + compiled regex block above. We could have updated this value in the loop + above, but keep the two values in step, just in case some later code below + uses the cb value. */ + + re->max_lookbehind = cb.max_lookbehind; + } + +/* Failed to compile, or error while post-processing. Earlier errors get here +via the dreaded goto. */ + +if (errorcode != 0) + { + HAD_ERROR: + *erroroffset = (int)(ptr - pattern); + HAD_UTF_ERROR: + *errorptr = errorcode; + pcre2_code_free(re); + re = NULL; + goto EXIT; + } + +/* Successful compile. If the anchored option was not passed, set it if +we can determine that the pattern is anchored by virtue of ^ characters or \A +or anything else, such as starting with non-atomic .* when DOTALL is set and +there are no occurrences of *PRUNE or *SKIP (though there is an option to +disable this case). */ + +if ((re->overall_options & PCRE2_ANCHORED) == 0 && + is_anchored(codestart, 0, &cb, 0)) + re->overall_options |= PCRE2_ANCHORED; + +/* If the pattern is still not anchored and we do not have a first code unit, +see if there is one that is asserted (these are not saved during the compile +because they can cause conflicts with actual literals that follow). This code +need not be obeyed if PCRE2_NO_START_OPTIMIZE is set, as the data it would +create will not be used. */ + +if ((re->overall_options & (PCRE2_ANCHORED|PCRE2_NO_START_OPTIMIZE)) == 0) + { + if (firstcuflags < 0) + firstcu = find_firstassertedcu(codestart, &firstcuflags, FALSE); + + /* Save the data for a first code unit. */ + + if (firstcuflags >= 0) + { + re->first_codeunit = firstcu; + re->flags |= PCRE2_FIRSTSET; + + /* Handle caseless first code units. */ + + if ((firstcuflags & REQ_CASELESS) != 0) + { + if (firstcu < 128 || (!utf && firstcu < 255)) + { + if (cb.fcc[firstcu] != firstcu) re->flags |= PCRE2_FIRSTCASELESS; + } + + /* The first code unit is > 128 in UTF mode, or > 255 otherwise. In + 8-bit UTF mode, codepoints in the range 128-255 are introductory code + points and cannot have another case. In 16-bit and 32-bit modes, we can + check wide characters when UTF (and therefore UCP) is supported. */ + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + else if (firstcu <= MAX_UTF_CODE_POINT && + UCD_OTHERCASE(firstcu) != firstcu) + re->flags |= PCRE2_FIRSTCASELESS; +#endif + } + } + + /* When there is no first code unit, see if we can set the PCRE2_STARTLINE + flag. This is helpful for multiline matches when all branches start with ^ + and also when all branches start with non-atomic .* for non-DOTALL matches + when *PRUNE and SKIP are not present. (There is an option that disables this + case.) */ + + else if (is_startline(codestart, 0, &cb, 0)) re->flags |= PCRE2_STARTLINE; + } + +/* Handle the "required code unit", if one is set. In the case of an anchored +pattern, do this only if it follows a variable length item in the pattern. +Again, skip this if PCRE2_NO_START_OPTIMIZE is set. */ + +if (reqcuflags >= 0 && + ((re->overall_options & (PCRE2_ANCHORED|PCRE2_NO_START_OPTIMIZE)) == 0 || + (reqcuflags & REQ_VARY) != 0)) + { + re->last_codeunit = reqcu; + re->flags |= PCRE2_LASTSET; + + /* Handle caseless required code units as for first code units (above). */ + + if ((reqcuflags & REQ_CASELESS) != 0) + { + if (reqcu < 128 || (!utf && reqcu < 255)) + { + if (cb.fcc[reqcu] != reqcu) re->flags |= PCRE2_LASTCASELESS; + } +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + else if (reqcu <= MAX_UTF_CODE_POINT && UCD_OTHERCASE(reqcu) != reqcu) + re->flags |= PCRE2_LASTCASELESS; +#endif + } + } + +/* Check for a pattern than can match an empty string, so that this information +can be provided to applications. */ + +do + { + int count = 0; + int rc = could_be_empty_branch(codestart, code, utf, &cb, TRUE, NULL, &count); + if (rc < 0) + { + errorcode = ERR86; + goto HAD_ERROR; + } + if (rc > 0) + { + re->flags |= PCRE2_MATCH_EMPTY; + break; + } + codestart += GET(codestart, 1); + } +while (*codestart == OP_ALT); + +/* Finally, unless PCRE2_NO_START_OPTIMIZE is set, study the compiled pattern +to set up information such as a bitmap of starting code units and a minimum +matching length. */ + +if ((re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0 && + PRIV(study)(re) != 0) + { + errorcode = ERR31; + goto HAD_ERROR; + } + +/* Control ends up here in all cases. If memory was obtained for a +zero-terminated copy of the pattern, remember to free it before returning. Also +free the list of named groups if a larger one had to be obtained, and likewise +the group information vector. */ + +EXIT: +if (copied_pattern != stack_copied_pattern) + ccontext->memctl.free(copied_pattern, ccontext->memctl.memory_data); +if (cb.named_group_list_size > NAMED_GROUP_LIST_SIZE) + ccontext->memctl.free((void *)cb.named_groups, ccontext->memctl.memory_data); +if (cb.groupinfo != c32workspace) + ccontext->memctl.free((void *)cb.groupinfo, ccontext->memctl.memory_data); + +return re; /* Will be NULL after an error */ +} + +/* End of pcre2_compile.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2_config.c b/ProcessHacker/pcre/pcre2_config.c new file mode 100644 index 0000000..90de292 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_config.c @@ -0,0 +1,224 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4267) +#pragma warning(disable : 4146) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Save the configured link size, which is in bytes. In 16-bit and 32-bit modes +its value gets changed by pcre2_internal.h to be in code units. */ + +static int configured_link_size = LINK_SIZE; + +#include "pcre2_internal.h" + +/* These macros are the standard way of turning unquoted text into C strings. +They allow macros like PCRE2_MAJOR to be defined without quotes, which is +convenient for user programs that want to test their values. */ + +#define STRING(a) # a +#define XSTRING(s) STRING(s) + + +/************************************************* +* Return info about what features are configured * +*************************************************/ + +/* +Arguments: + what what information is required + where where to put the information + +Returns: 0 if data returned + >= 0 if where is NULL, giving length required + PCRE2_ERROR_BADOPTION if "where" not recognized + or JIT target requested when JIT not enabled +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_config(uint32_t what, void *where) +{ +if (where == NULL) /* Requests a length */ + { + switch(what) + { + default: + return PCRE2_ERROR_BADOPTION; + + case PCRE2_CONFIG_BSR: + case PCRE2_CONFIG_JIT: + case PCRE2_CONFIG_LINKSIZE: + case PCRE2_CONFIG_MATCHLIMIT: + case PCRE2_CONFIG_NEWLINE: + case PCRE2_CONFIG_PARENSLIMIT: + case PCRE2_CONFIG_RECURSIONLIMIT: + case PCRE2_CONFIG_STACKRECURSE: + case PCRE2_CONFIG_UNICODE: + return sizeof(uint32_t); + + /* These are handled below */ + + case PCRE2_CONFIG_JITTARGET: + case PCRE2_CONFIG_UNICODE_VERSION: + case PCRE2_CONFIG_VERSION: + break; + } + } + +switch (what) + { + default: + return PCRE2_ERROR_BADOPTION; + + case PCRE2_CONFIG_BSR: +#ifdef BSR_ANYCRLF + *((uint32_t *)where) = PCRE2_BSR_ANYCRLF; +#else + *((uint32_t *)where) = PCRE2_BSR_UNICODE; +#endif + break; + + case PCRE2_CONFIG_JIT: +#ifdef SUPPORT_JIT + *((uint32_t *)where) = 1; +#else + *((uint32_t *)where) = 0; +#endif + break; + + case PCRE2_CONFIG_JITTARGET: +#ifdef SUPPORT_JIT + { + const char *v = PRIV(jit_get_target)(); + return 1 + ((where == NULL)? + strlen(v) : PRIV(strcpy_c8)((PCRE2_UCHAR *)where, v)); + } +#else + return PCRE2_ERROR_BADOPTION; +#endif + + case PCRE2_CONFIG_LINKSIZE: + *((uint32_t *)where) = configured_link_size; + break; + + case PCRE2_CONFIG_MATCHLIMIT: + *((uint32_t *)where) = MATCH_LIMIT; + break; + + case PCRE2_CONFIG_NEWLINE: + *((uint32_t *)where) = NEWLINE_DEFAULT; + break; + + case PCRE2_CONFIG_PARENSLIMIT: + *((uint32_t *)where) = PARENS_NEST_LIMIT; + break; + + case PCRE2_CONFIG_RECURSIONLIMIT: + *((uint32_t *)where) = MATCH_LIMIT_RECURSION; + break; + + case PCRE2_CONFIG_STACKRECURSE: +#ifdef HEAP_MATCH_RECURSE + *((uint32_t *)where) = 0; +#else + *((uint32_t *)where) = 1; +#endif + break; + + case PCRE2_CONFIG_UNICODE_VERSION: + { +#if defined SUPPORT_UNICODE + const char *v = PRIV(unicode_version); +#else + const char *v = "Unicode not supported"; +#endif + return 1 + ((where == NULL)? + strlen(v): PRIV(strcpy_c8)((PCRE2_UCHAR *)where, v)); + } + break; + + case PCRE2_CONFIG_UNICODE: +#if defined SUPPORT_UNICODE + *((uint32_t *)where) = 1; +#else + *((uint32_t *)where) = 0; +#endif + break; + + /* The hackery in setting "v" below is to cope with the case when + PCRE2_PRERELEASE is set to an empty string (which it is for real releases). + If the second alternative is used in this case, it does not leave a space + before the date. On the other hand, if all four macros are put into a single + XSTRING when PCRE2_PRERELEASE is not empty, an unwanted space is inserted. + There are problems using an "obvious" approach like this: + + XSTRING(PCRE2_MAJOR) "." XSTRING(PCRE_MINOR) + XSTRING(PCRE2_PRERELEASE) " " XSTRING(PCRE_DATE) + + because, when PCRE2_PRERELEASE is empty, this leads to an attempted expansion + of STRING(). The C standard states: "If (before argument substitution) any + argument consists of no preprocessing tokens, the behavior is undefined." It + turns out the gcc treats this case as a single empty string - which is what + we really want - but Visual C grumbles about the lack of an argument for the + macro. Unfortunately, both are within their rights. As there seems to be no + way to test for a macro's value being empty at compile time, we have to + resort to a runtime test. */ + + case PCRE2_CONFIG_VERSION: + { + const char *v = (XSTRING(Z PCRE2_PRERELEASE)[1] == 0)? + XSTRING(PCRE2_MAJOR.PCRE2_MINOR PCRE2_DATE) : + XSTRING(PCRE2_MAJOR.PCRE2_MINOR) XSTRING(PCRE2_PRERELEASE PCRE2_DATE); + return 1 + ((where == NULL)? + strlen(v) : PRIV(strcpy_c8)((PCRE2_UCHAR *)where, v)); + } + } + +return 0; +} + +/* End of pcre2_config.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2_context.c b/ProcessHacker/pcre/pcre2_context.c new file mode 100644 index 0000000..9254d0f --- /dev/null +++ b/ProcessHacker/pcre/pcre2_context.c @@ -0,0 +1,394 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +// dmex: Required for PhAllocateSafe / PhFree +#include + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + + +/************************************************* +* Default malloc/free functions * +*************************************************/ + +/* Ignore the "user data" argument in each case. */ + +static void *default_malloc(size_t size, void *data) +{ +(void)data; +return PhAllocateSafe(size); +} + + +static void default_free(void *block, void *data) +{ +(void)data; +PhFree(block); +} + + + +/************************************************* +* Get a block and save memory control * +*************************************************/ + +/* This internal function is called to get a block of memory in which the +memory control data is to be stored at the start for future use. + +Arguments: + size amount of memory required + memctl pointer to a memctl block or NULL + +Returns: pointer to memory or NULL on failure +*/ + +extern void * +PRIV(memctl_malloc)(size_t size, pcre2_memctl *memctl) +{ +pcre2_memctl *newmemctl; +void *yield = (memctl == NULL)? malloc(size) : + memctl->malloc(size, memctl->memory_data); +if (yield == NULL) return NULL; +newmemctl = (pcre2_memctl *)yield; +if (memctl == NULL) + { + newmemctl->malloc = default_malloc; + newmemctl->free = default_free; + newmemctl->memory_data = NULL; + } +else *newmemctl = *memctl; +return yield; +} + + + +/************************************************* +* Create and initialize contexts * +*************************************************/ + +/* Initializing for compile and match contexts is done in separate, private +functions so that these can be called from functions such as pcre2_compile() +when an external context is not supplied. The initializing functions have an +option to set up default memory management. */ + +PCRE2_EXP_DEFN pcre2_general_context * PCRE2_CALL_CONVENTION +pcre2_general_context_create(void *(*private_malloc)(size_t, void *), + void (*private_free)(void *, void *), void *memory_data) +{ +pcre2_general_context *gcontext; +if (private_malloc == NULL) private_malloc = default_malloc; +if (private_free == NULL) private_free = default_free; +gcontext = private_malloc(sizeof(pcre2_real_general_context), memory_data); +if (gcontext == NULL) return NULL; +gcontext->memctl.malloc = private_malloc; +gcontext->memctl.free = private_free; +gcontext->memctl.memory_data = memory_data; +return gcontext; +} + + +/* A default compile context is set up to save having to initialize at run time +when no context is supplied to the compile function. */ + +const pcre2_compile_context PRIV(default_compile_context) = { + { default_malloc, default_free, NULL }, /* Default memory handling */ + NULL, /* Stack guard */ + NULL, /* Stack guard data */ + PRIV(default_tables), /* Character tables */ + PCRE2_UNSET, /* Max pattern length */ + BSR_DEFAULT, /* Backslash R default */ + NEWLINE_DEFAULT, /* Newline convention */ + PARENS_NEST_LIMIT }; /* As it says */ + +/* The create function copies the default into the new memory, but must +override the default memory handling functions if a gcontext was provided. */ + +PCRE2_EXP_DEFN pcre2_compile_context * PCRE2_CALL_CONVENTION +pcre2_compile_context_create(pcre2_general_context *gcontext) +{ +pcre2_compile_context *ccontext = PRIV(memctl_malloc)( + sizeof(pcre2_real_compile_context), (pcre2_memctl *)gcontext); +if (ccontext == NULL) return NULL; +*ccontext = PRIV(default_compile_context); +if (gcontext != NULL) + *((pcre2_memctl *)ccontext) = *((pcre2_memctl *)gcontext); +return ccontext; +} + + +/* A default match context is set up to save having to initialize at run time +when no context is supplied to a match function. */ + +const pcre2_match_context PRIV(default_match_context) = { + { default_malloc, default_free, NULL }, +#ifdef HEAP_MATCH_RECURSE + { default_malloc, default_free, NULL }, +#endif +#ifdef SUPPORT_JIT + NULL, + NULL, +#endif + NULL, + NULL, + PCRE2_UNSET, /* Offset limit */ + MATCH_LIMIT, + MATCH_LIMIT_RECURSION }; + +/* The create function copies the default into the new memory, but must +override the default memory handling functions if a gcontext was provided. */ + +PCRE2_EXP_DEFN pcre2_match_context * PCRE2_CALL_CONVENTION +pcre2_match_context_create(pcre2_general_context *gcontext) +{ +pcre2_match_context *mcontext = PRIV(memctl_malloc)( + sizeof(pcre2_real_match_context), (pcre2_memctl *)gcontext); +if (mcontext == NULL) return NULL; +*mcontext = PRIV(default_match_context); +if (gcontext != NULL) + *((pcre2_memctl *)mcontext) = *((pcre2_memctl *)gcontext); +return mcontext; +} + + +/************************************************* +* Context copy functions * +*************************************************/ + +PCRE2_EXP_DEFN pcre2_general_context * PCRE2_CALL_CONVENTION +pcre2_general_context_copy(pcre2_general_context *gcontext) +{ +pcre2_general_context *new = + gcontext->memctl.malloc(sizeof(pcre2_real_general_context), + gcontext->memctl.memory_data); +if (new == NULL) return NULL; +memcpy(new, gcontext, sizeof(pcre2_real_general_context)); +return new; +} + + +PCRE2_EXP_DEFN pcre2_compile_context * PCRE2_CALL_CONVENTION +pcre2_compile_context_copy(pcre2_compile_context *ccontext) +{ +pcre2_compile_context *new = + ccontext->memctl.malloc(sizeof(pcre2_real_compile_context), + ccontext->memctl.memory_data); +if (new == NULL) return NULL; +memcpy(new, ccontext, sizeof(pcre2_real_compile_context)); +return new; +} + + +PCRE2_EXP_DEFN pcre2_match_context * PCRE2_CALL_CONVENTION +pcre2_match_context_copy(pcre2_match_context *mcontext) +{ +pcre2_match_context *new = + mcontext->memctl.malloc(sizeof(pcre2_real_match_context), + mcontext->memctl.memory_data); +if (new == NULL) return NULL; +memcpy(new, mcontext, sizeof(pcre2_real_match_context)); +return new; +} + + + +/************************************************* +* Context free functions * +*************************************************/ + + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_general_context_free(pcre2_general_context *gcontext) +{ +if (gcontext != NULL) + gcontext->memctl.free(gcontext, gcontext->memctl.memory_data); +} + + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_compile_context_free(pcre2_compile_context *ccontext) +{ +if (ccontext != NULL) + ccontext->memctl.free(ccontext, ccontext->memctl.memory_data); +} + + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_match_context_free(pcre2_match_context *mcontext) +{ +if (mcontext != NULL) + mcontext->memctl.free(mcontext, mcontext->memctl.memory_data); +} + + + + +/************************************************* +* Set values in contexts * +*************************************************/ + +/* All these functions return 0 for success or PCRE2_ERROR_BADDATA if invalid +data is given. Only some of the functions are able to test the validity of the +data. */ + + +/* ------------ Compile contexts ------------ */ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_character_tables(pcre2_compile_context *ccontext, + const unsigned char *tables) +{ +ccontext->tables = tables; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_bsr(pcre2_compile_context *ccontext, uint32_t value) +{ +switch(value) + { + case PCRE2_BSR_ANYCRLF: + case PCRE2_BSR_UNICODE: + ccontext->bsr_convention = value; + return 0; + + default: + return PCRE2_ERROR_BADDATA; + } +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_max_pattern_length(pcre2_compile_context *ccontext, PCRE2_SIZE length) +{ +ccontext->max_pattern_length = length; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_newline(pcre2_compile_context *ccontext, uint32_t newline) +{ +switch(newline) + { + case PCRE2_NEWLINE_CR: + case PCRE2_NEWLINE_LF: + case PCRE2_NEWLINE_CRLF: + case PCRE2_NEWLINE_ANY: + case PCRE2_NEWLINE_ANYCRLF: + ccontext->newline_convention = newline; + return 0; + + default: + return PCRE2_ERROR_BADDATA; + } +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_parens_nest_limit(pcre2_compile_context *ccontext, uint32_t limit) +{ +ccontext->parens_nest_limit = limit; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_compile_recursion_guard(pcre2_compile_context *ccontext, + int (*guard)(uint32_t, void *), void *user_data) +{ +ccontext->stack_guard = guard; +ccontext->stack_guard_data = user_data; +return 0; +} + + +/* ------------ Match contexts ------------ */ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_callout(pcre2_match_context *mcontext, + int (*callout)(pcre2_callout_block *, void *), void *callout_data) +{ +mcontext->callout = callout; +mcontext->callout_data = callout_data; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_match_limit(pcre2_match_context *mcontext, uint32_t limit) +{ +mcontext->match_limit = limit; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_offset_limit(pcre2_match_context *mcontext, PCRE2_SIZE limit) +{ +mcontext->offset_limit = limit; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_recursion_limit(pcre2_match_context *mcontext, uint32_t limit) +{ +mcontext->recursion_limit = limit; +return 0; +} + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_set_recursion_memory_management(pcre2_match_context *mcontext, + void *(*mymalloc)(size_t, void *), void (*myfree)(void *, void *), + void *mydata) +{ +#ifdef HEAP_MATCH_RECURSE +mcontext->stack_memctl.malloc = mymalloc; +mcontext->stack_memctl.free = myfree; +mcontext->stack_memctl.memory_data = mydata; +#else +(void)mcontext; +(void)mymalloc; +(void)myfree; +(void)mydata; +#endif +return 0; +} + +/* End of pcre2_context.c */ diff --git a/ProcessHacker/pcre/pcre2_dfa_match.c b/ProcessHacker/pcre/pcre2_dfa_match.c new file mode 100644 index 0000000..008e21e --- /dev/null +++ b/ProcessHacker/pcre/pcre2_dfa_match.c @@ -0,0 +1,3624 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre2_dfa_match(), which is an +alternative matching function that uses a sort of DFA algorithm (not a true +FSM). This is NOT Perl-compatible, but it has advantages in certain +applications. */ + + +/* NOTE ABOUT PERFORMANCE: A user of this function sent some code that improved +the performance of his patterns greatly. I could not use it as it stood, as it +was not thread safe, and made assumptions about pattern sizes. Also, it caused +test 7 to loop, and test 9 to crash with a segfault. + +The issue is the check for duplicate states, which is done by a simple linear +search up the state list. (Grep for "duplicate" below to find the code.) For +many patterns, there will never be many states active at one time, so a simple +linear search is fine. In patterns that have many active states, it might be a +bottleneck. The suggested code used an indexing scheme to remember which states +had previously been used for each character, and avoided the linear search when +it knew there was no chance of a duplicate. This was implemented when adding +states to the state lists. + +I wrote some thread-safe, not-limited code to try something similar at the time +of checking for duplicates (instead of when adding states), using index vectors +on the stack. It did give a 13% improvement with one specially constructed +pattern for certain subject strings, but on other strings and on many of the +simpler patterns in the test suite it did worse. The major problem, I think, +was the extra time to initialize the index. This had to be done for each call +of internal_dfa_match(). (The supplied patch used a static vector, initialized +only once - I suspect this was the cause of the problems with the tests.) + +Overall, I concluded that the gains in some cases did not outweigh the losses +in others, so I abandoned this code. */ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4267) +#pragma warning(disable : 4146) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK mb /* Block containing newline information */ +#define PSSTART start_subject /* Field containing processed string start */ +#define PSEND end_subject /* Field containing processed string end */ + +#include "pcre2_internal.h" + +#define PUBLIC_DFA_MATCH_OPTIONS \ + (PCRE2_ANCHORED|PCRE2_NOTBOL|PCRE2_NOTEOL|PCRE2_NOTEMPTY| \ + PCRE2_NOTEMPTY_ATSTART|PCRE2_NO_UTF_CHECK|PCRE2_PARTIAL_HARD| \ + PCRE2_PARTIAL_SOFT|PCRE2_DFA_SHORTEST|PCRE2_DFA_RESTART) + + +/************************************************* +* Code parameters and static tables * +*************************************************/ + +/* These are offsets that are used to turn the OP_TYPESTAR and friends opcodes +into others, under special conditions. A gap of 20 between the blocks should be +enough. The resulting opcodes don't have to be less than 256 because they are +never stored, so we push them well clear of the normal opcodes. */ + +#define OP_PROP_EXTRA 300 +#define OP_EXTUNI_EXTRA 320 +#define OP_ANYNL_EXTRA 340 +#define OP_HSPACE_EXTRA 360 +#define OP_VSPACE_EXTRA 380 + + +/* This table identifies those opcodes that are followed immediately by a +character that is to be tested in some way. This makes it possible to +centralize the loading of these characters. In the case of Type * etc, the +"character" is the opcode for \D, \d, \S, \s, \W, or \w, which will always be a +small value. Non-zero values in the table are the offsets from the opcode where +the character is to be found. ***NOTE*** If the start of this table is +modified, the three tables that follow must also be modified. */ + +static const uint8_t coptable[] = { + 0, /* End */ + 0, 0, 0, 0, 0, /* \A, \G, \K, \B, \b */ + 0, 0, 0, 0, 0, 0, /* \D, \d, \S, \s, \W, \w */ + 0, 0, 0, /* Any, AllAny, Anybyte */ + 0, 0, /* \P, \p */ + 0, 0, 0, 0, 0, /* \R, \H, \h, \V, \v */ + 0, /* \X */ + 0, 0, 0, 0, 0, 0, /* \Z, \z, $, $M, ^, ^M */ + 1, /* Char */ + 1, /* Chari */ + 1, /* not */ + 1, /* noti */ + /* Positive single-char repeats */ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ + 1+IMM2_SIZE, 1+IMM2_SIZE, /* upto, minupto */ + 1+IMM2_SIZE, /* exact */ + 1, 1, 1, 1+IMM2_SIZE, /* *+, ++, ?+, upto+ */ + 1, 1, 1, 1, 1, 1, /* *I, *?I, +I, +?I, ?I, ??I */ + 1+IMM2_SIZE, 1+IMM2_SIZE, /* upto I, minupto I */ + 1+IMM2_SIZE, /* exact I */ + 1, 1, 1, 1+IMM2_SIZE, /* *+I, ++I, ?+I, upto+I */ + /* Negative single-char repeats - only for chars < 256 */ + 1, 1, 1, 1, 1, 1, /* NOT *, *?, +, +?, ?, ?? */ + 1+IMM2_SIZE, 1+IMM2_SIZE, /* NOT upto, minupto */ + 1+IMM2_SIZE, /* NOT exact */ + 1, 1, 1, 1+IMM2_SIZE, /* NOT *+, ++, ?+, upto+ */ + 1, 1, 1, 1, 1, 1, /* NOT *I, *?I, +I, +?I, ?I, ??I */ + 1+IMM2_SIZE, 1+IMM2_SIZE, /* NOT upto I, minupto I */ + 1+IMM2_SIZE, /* NOT exact I */ + 1, 1, 1, 1+IMM2_SIZE, /* NOT *+I, ++I, ?+I, upto+I */ + /* Positive type repeats */ + 1, 1, 1, 1, 1, 1, /* Type *, *?, +, +?, ?, ?? */ + 1+IMM2_SIZE, 1+IMM2_SIZE, /* Type upto, minupto */ + 1+IMM2_SIZE, /* Type exact */ + 1, 1, 1, 1+IMM2_SIZE, /* Type *+, ++, ?+, upto+ */ + /* Character class & ref repeats */ + 0, 0, 0, 0, 0, 0, /* *, *?, +, +?, ?, ?? */ + 0, 0, /* CRRANGE, CRMINRANGE */ + 0, 0, 0, 0, /* Possessive *+, ++, ?+, CRPOSRANGE */ + 0, /* CLASS */ + 0, /* NCLASS */ + 0, /* XCLASS - variable length */ + 0, /* REF */ + 0, /* REFI */ + 0, /* DNREF */ + 0, /* DNREFI */ + 0, /* RECURSE */ + 0, /* CALLOUT */ + 0, /* CALLOUT_STR */ + 0, /* Alt */ + 0, /* Ket */ + 0, /* KetRmax */ + 0, /* KetRmin */ + 0, /* KetRpos */ + 0, /* Reverse */ + 0, /* Assert */ + 0, /* Assert not */ + 0, /* Assert behind */ + 0, /* Assert behind not */ + 0, 0, /* ONCE, ONCE_NC */ + 0, 0, 0, 0, 0, /* BRA, BRAPOS, CBRA, CBRAPOS, COND */ + 0, 0, 0, 0, 0, /* SBRA, SBRAPOS, SCBRA, SCBRAPOS, SCOND */ + 0, 0, /* CREF, DNCREF */ + 0, 0, /* RREF, DNRREF */ + 0, 0, /* FALSE, TRUE */ + 0, 0, 0, /* BRAZERO, BRAMINZERO, BRAPOSZERO */ + 0, 0, 0, /* MARK, PRUNE, PRUNE_ARG */ + 0, 0, 0, 0, /* SKIP, SKIP_ARG, THEN, THEN_ARG */ + 0, 0, 0, 0, /* COMMIT, FAIL, ACCEPT, ASSERT_ACCEPT */ + 0, 0, 0 /* CLOSE, SKIPZERO, DEFINE */ +}; + +/* This table identifies those opcodes that inspect a character. It is used to +remember the fact that a character could have been inspected when the end of +the subject is reached. ***NOTE*** If the start of this table is modified, the +two tables that follow must also be modified. */ + +static const uint8_t poptable[] = { + 0, /* End */ + 0, 0, 0, 1, 1, /* \A, \G, \K, \B, \b */ + 1, 1, 1, 1, 1, 1, /* \D, \d, \S, \s, \W, \w */ + 1, 1, 1, /* Any, AllAny, Anybyte */ + 1, 1, /* \P, \p */ + 1, 1, 1, 1, 1, /* \R, \H, \h, \V, \v */ + 1, /* \X */ + 0, 0, 0, 0, 0, 0, /* \Z, \z, $, $M, ^, ^M */ + 1, /* Char */ + 1, /* Chari */ + 1, /* not */ + 1, /* noti */ + /* Positive single-char repeats */ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ + 1, 1, 1, /* upto, minupto, exact */ + 1, 1, 1, 1, /* *+, ++, ?+, upto+ */ + 1, 1, 1, 1, 1, 1, /* *I, *?I, +I, +?I, ?I, ??I */ + 1, 1, 1, /* upto I, minupto I, exact I */ + 1, 1, 1, 1, /* *+I, ++I, ?+I, upto+I */ + /* Negative single-char repeats - only for chars < 256 */ + 1, 1, 1, 1, 1, 1, /* NOT *, *?, +, +?, ?, ?? */ + 1, 1, 1, /* NOT upto, minupto, exact */ + 1, 1, 1, 1, /* NOT *+, ++, ?+, upto+ */ + 1, 1, 1, 1, 1, 1, /* NOT *I, *?I, +I, +?I, ?I, ??I */ + 1, 1, 1, /* NOT upto I, minupto I, exact I */ + 1, 1, 1, 1, /* NOT *+I, ++I, ?+I, upto+I */ + /* Positive type repeats */ + 1, 1, 1, 1, 1, 1, /* Type *, *?, +, +?, ?, ?? */ + 1, 1, 1, /* Type upto, minupto, exact */ + 1, 1, 1, 1, /* Type *+, ++, ?+, upto+ */ + /* Character class & ref repeats */ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ + 1, 1, /* CRRANGE, CRMINRANGE */ + 1, 1, 1, 1, /* Possessive *+, ++, ?+, CRPOSRANGE */ + 1, /* CLASS */ + 1, /* NCLASS */ + 1, /* XCLASS - variable length */ + 0, /* REF */ + 0, /* REFI */ + 0, /* DNREF */ + 0, /* DNREFI */ + 0, /* RECURSE */ + 0, /* CALLOUT */ + 0, /* CALLOUT_STR */ + 0, /* Alt */ + 0, /* Ket */ + 0, /* KetRmax */ + 0, /* KetRmin */ + 0, /* KetRpos */ + 0, /* Reverse */ + 0, /* Assert */ + 0, /* Assert not */ + 0, /* Assert behind */ + 0, /* Assert behind not */ + 0, 0, /* ONCE, ONCE_NC */ + 0, 0, 0, 0, 0, /* BRA, BRAPOS, CBRA, CBRAPOS, COND */ + 0, 0, 0, 0, 0, /* SBRA, SBRAPOS, SCBRA, SCBRAPOS, SCOND */ + 0, 0, /* CREF, DNCREF */ + 0, 0, /* RREF, DNRREF */ + 0, 0, /* FALSE, TRUE */ + 0, 0, 0, /* BRAZERO, BRAMINZERO, BRAPOSZERO */ + 0, 0, 0, /* MARK, PRUNE, PRUNE_ARG */ + 0, 0, 0, 0, /* SKIP, SKIP_ARG, THEN, THEN_ARG */ + 0, 0, 0, 0, /* COMMIT, FAIL, ACCEPT, ASSERT_ACCEPT */ + 0, 0, 0 /* CLOSE, SKIPZERO, DEFINE */ +}; + +/* These 2 tables allow for compact code for testing for \D, \d, \S, \s, \W, +and \w */ + +static const uint8_t toptable1[] = { + 0, 0, 0, 0, 0, 0, + ctype_digit, ctype_digit, + ctype_space, ctype_space, + ctype_word, ctype_word, + 0, 0 /* OP_ANY, OP_ALLANY */ +}; + +static const uint8_t toptable2[] = { + 0, 0, 0, 0, 0, 0, + ctype_digit, 0, + ctype_space, 0, + ctype_word, 0, + 1, 1 /* OP_ANY, OP_ALLANY */ +}; + + +/* Structure for holding data about a particular state, which is in effect the +current data for an active path through the match tree. It must consist +entirely of ints because the working vector we are passed, and which we put +these structures in, is a vector of ints. */ + +typedef struct stateblock { + int offset; /* Offset to opcode (-ve has meaning) */ + int count; /* Count for repeats */ + int data; /* Some use extra data */ +} stateblock; + +#define INTS_PER_STATEBLOCK (int)(sizeof(stateblock)/sizeof(int)) + + + +/************************************************* +* Match a Regular Expression - DFA engine * +*************************************************/ + +/* This internal function applies a compiled pattern to a subject string, +starting at a given point, using a DFA engine. This function is called from the +external one, possibly multiple times if the pattern is not anchored. The +function calls itself recursively for some kinds of subpattern. + +Arguments: + mb the match_data block with fixed information + this_start_code the opening bracket of this subexpression's code + current_subject where we currently are in the subject string + start_offset start offset in the subject string + offsets vector to contain the matching string offsets + offsetcount size of same + workspace vector of workspace + wscount size of same + rlevel function call recursion level + +Returns: > 0 => number of match offset pairs placed in offsets + = 0 => offsets overflowed; longest matches are present + -1 => failed to match + < -1 => some kind of unexpected problem + +The following macros are used for adding states to the two state vectors (one +for the current character, one for the following character). */ + +#define ADD_ACTIVE(x,y) \ + if (active_count++ < wscount) \ + { \ + next_active_state->offset = (x); \ + next_active_state->count = (y); \ + next_active_state++; \ + } \ + else return PCRE2_ERROR_DFA_WSSIZE + +#define ADD_ACTIVE_DATA(x,y,z) \ + if (active_count++ < wscount) \ + { \ + next_active_state->offset = (x); \ + next_active_state->count = (y); \ + next_active_state->data = (z); \ + next_active_state++; \ + } \ + else return PCRE2_ERROR_DFA_WSSIZE + +#define ADD_NEW(x,y) \ + if (new_count++ < wscount) \ + { \ + next_new_state->offset = (x); \ + next_new_state->count = (y); \ + next_new_state++; \ + } \ + else return PCRE2_ERROR_DFA_WSSIZE + +#define ADD_NEW_DATA(x,y,z) \ + if (new_count++ < wscount) \ + { \ + next_new_state->offset = (x); \ + next_new_state->count = (y); \ + next_new_state->data = (z); \ + next_new_state++; \ + } \ + else return PCRE2_ERROR_DFA_WSSIZE + +/* And now, here is the code */ + +static int +internal_dfa_match( + dfa_match_block *mb, + PCRE2_SPTR this_start_code, + PCRE2_SPTR current_subject, + PCRE2_SIZE start_offset, + PCRE2_SIZE *offsets, + uint32_t offsetcount, + int *workspace, + int wscount, + int rlevel) +{ +stateblock *active_states, *new_states, *temp_states; +stateblock *next_active_state, *next_new_state; + +const uint8_t *ctypes, *lcc, *fcc; +PCRE2_SPTR ptr; +PCRE2_SPTR end_code; +PCRE2_SPTR first_op; + +dfa_recursion_info new_recursive; + +int active_count, new_count, match_count; + +/* Some fields in the mb block are frequently referenced, so we load them into +independent variables in the hope that this will perform better. */ + +PCRE2_SPTR start_subject = mb->start_subject; +PCRE2_SPTR end_subject = mb->end_subject; +PCRE2_SPTR start_code = mb->start_code; + +#ifdef SUPPORT_UNICODE +BOOL utf = (mb->poptions & PCRE2_UTF) != 0; +#else +BOOL utf = FALSE; +#endif + +BOOL reset_could_continue = FALSE; + +rlevel++; +offsetcount &= (-2); + +wscount -= 2; +wscount = (wscount - (wscount % (INTS_PER_STATEBLOCK * 2))) / + (2 * INTS_PER_STATEBLOCK); + +ctypes = mb->tables + ctypes_offset; +lcc = mb->tables + lcc_offset; +fcc = mb->tables + fcc_offset; + +match_count = PCRE2_ERROR_NOMATCH; /* A negative number */ + +active_states = (stateblock *)(workspace + 2); +next_new_state = new_states = active_states + wscount; +new_count = 0; + +first_op = this_start_code + 1 + LINK_SIZE + + ((*this_start_code == OP_CBRA || *this_start_code == OP_SCBRA || + *this_start_code == OP_CBRAPOS || *this_start_code == OP_SCBRAPOS) + ? IMM2_SIZE:0); + +/* The first thing in any (sub) pattern is a bracket of some sort. Push all +the alternative states onto the list, and find out where the end is. This +makes is possible to use this function recursively, when we want to stop at a +matching internal ket rather than at the end. + +If the first opcode in the first alternative is OP_REVERSE, we are dealing with +a backward assertion. In that case, we have to find out the maximum amount to +move back, and set up each alternative appropriately. */ + +if (*first_op == OP_REVERSE) + { + size_t max_back = 0; + size_t gone_back; + + end_code = this_start_code; + do + { + size_t back = GET(end_code, 2+LINK_SIZE); + if (back > max_back) max_back = back; + end_code += GET(end_code, 1); + } + while (*end_code == OP_ALT); + + /* If we can't go back the amount required for the longest lookbehind + pattern, go back as far as we can; some alternatives may still be viable. */ + +#ifdef SUPPORT_UNICODE + /* In character mode we have to step back character by character */ + + if (utf) + { + for (gone_back = 0; gone_back < max_back; gone_back++) + { + if (current_subject <= start_subject) break; + current_subject--; + ACROSSCHAR(current_subject > start_subject, *current_subject, current_subject--); + } + } + else +#endif + + /* In byte-mode we can do this quickly. */ + + { + size_t current_offset = (size_t)(current_subject - start_subject); + gone_back = (current_offset < max_back)? current_offset : max_back; + current_subject -= gone_back; + } + + /* Save the earliest consulted character */ + + if (current_subject < mb->start_used_ptr) + mb->start_used_ptr = current_subject; + + /* Now we can process the individual branches. */ + + end_code = this_start_code; + do + { + size_t back = GET(end_code, 2+LINK_SIZE); + if (back <= gone_back) + { + int bstate = (int)(end_code - start_code + 2 + 2*LINK_SIZE); + ADD_NEW_DATA(-bstate, 0, gone_back - back); + } + end_code += GET(end_code, 1); + } + while (*end_code == OP_ALT); + } + +/* This is the code for a "normal" subpattern (not a backward assertion). The +start of a whole pattern is always one of these. If we are at the top level, +we may be asked to restart matching from the same point that we reached for a +previous partial match. We still have to scan through the top-level branches to +find the end state. */ + +else + { + end_code = this_start_code; + + /* Restarting */ + + if (rlevel == 1 && (mb->moptions & PCRE2_DFA_RESTART) != 0) + { + do { end_code += GET(end_code, 1); } while (*end_code == OP_ALT); + new_count = workspace[1]; + if (!workspace[0]) + memcpy(new_states, active_states, new_count * sizeof(stateblock)); + } + + /* Not restarting */ + + else + { + int length = 1 + LINK_SIZE + + ((*this_start_code == OP_CBRA || *this_start_code == OP_SCBRA || + *this_start_code == OP_CBRAPOS || *this_start_code == OP_SCBRAPOS) + ? IMM2_SIZE:0); + do + { + ADD_NEW((int)(end_code - start_code + length), 0); + end_code += GET(end_code, 1); + length = 1 + LINK_SIZE; + } + while (*end_code == OP_ALT); + } + } + +workspace[0] = 0; /* Bit indicating which vector is current */ + +/* Loop for scanning the subject */ + +ptr = current_subject; +for (;;) + { + int i, j; + int clen, dlen; + uint32_t c, d; + int forced_fail = 0; + BOOL partial_newline = FALSE; + BOOL could_continue = reset_could_continue; + reset_could_continue = FALSE; + + if (ptr > mb->last_used_ptr) mb->last_used_ptr = ptr; + + /* Make the new state list into the active state list and empty the + new state list. */ + + temp_states = active_states; + active_states = new_states; + new_states = temp_states; + active_count = new_count; + new_count = 0; + + workspace[0] ^= 1; /* Remember for the restarting feature */ + workspace[1] = active_count; + + /* Set the pointers for adding new states */ + + next_active_state = active_states + active_count; + next_new_state = new_states; + + /* Load the current character from the subject outside the loop, as many + different states may want to look at it, and we assume that at least one + will. */ + + if (ptr < end_subject) + { + clen = 1; /* Number of data items in the character */ +#ifdef SUPPORT_UNICODE + GETCHARLENTEST(c, ptr, clen); +#else + c = *ptr; +#endif /* SUPPORT_UNICODE */ + } + else + { + clen = 0; /* This indicates the end of the subject */ + c = NOTACHAR; /* This value should never actually be used */ + } + + /* Scan up the active states and act on each one. The result of an action + may be to add more states to the currently active list (e.g. on hitting a + parenthesis) or it may be to put states on the new list, for considering + when we move the character pointer on. */ + + for (i = 0; i < active_count; i++) + { + stateblock *current_state = active_states + i; + BOOL caseless = FALSE; + PCRE2_SPTR code; + int state_offset = current_state->offset; + int codevalue, rrc; + int count; + + /* A negative offset is a special case meaning "hold off going to this + (negated) state until the number of characters in the data field have + been skipped". If the could_continue flag was passed over from a previous + state, arrange for it to passed on. */ + + if (state_offset < 0) + { + if (current_state->data > 0) + { + ADD_NEW_DATA(state_offset, current_state->count, + current_state->data - 1); + if (could_continue) reset_could_continue = TRUE; + continue; + } + else + { + current_state->offset = state_offset = -state_offset; + } + } + + /* Check for a duplicate state with the same count, and skip if found. + See the note at the head of this module about the possibility of improving + performance here. */ + + for (j = 0; j < i; j++) + { + if (active_states[j].offset == state_offset && + active_states[j].count == current_state->count) + goto NEXT_ACTIVE_STATE; + } + + /* The state offset is the offset to the opcode */ + + code = start_code + state_offset; + codevalue = *code; + + /* If this opcode inspects a character, but we are at the end of the + subject, remember the fact for use when testing for a partial match. */ + + if (clen == 0 && poptable[codevalue] != 0) + could_continue = TRUE; + + /* If this opcode is followed by an inline character, load it. It is + tempting to test for the presence of a subject character here, but that + is wrong, because sometimes zero repetitions of the subject are + permitted. + + We also use this mechanism for opcodes such as OP_TYPEPLUS that take an + argument that is not a data character - but is always one byte long because + the values are small. We have to take special action to deal with \P, \p, + \H, \h, \V, \v and \X in this case. To keep the other cases fast, convert + these ones to new opcodes. */ + + if (coptable[codevalue] > 0) + { + dlen = 1; +#ifdef SUPPORT_UNICODE + if (utf) { GETCHARLEN(d, (code + coptable[codevalue]), dlen); } else +#endif /* SUPPORT_UNICODE */ + d = code[coptable[codevalue]]; + if (codevalue >= OP_TYPESTAR) + { + switch(d) + { + case OP_ANYBYTE: return PCRE2_ERROR_DFA_UITEM; + case OP_NOTPROP: + case OP_PROP: codevalue += OP_PROP_EXTRA; break; + case OP_ANYNL: codevalue += OP_ANYNL_EXTRA; break; + case OP_EXTUNI: codevalue += OP_EXTUNI_EXTRA; break; + case OP_NOT_HSPACE: + case OP_HSPACE: codevalue += OP_HSPACE_EXTRA; break; + case OP_NOT_VSPACE: + case OP_VSPACE: codevalue += OP_VSPACE_EXTRA; break; + default: break; + } + } + } + else + { + dlen = 0; /* Not strictly necessary, but compilers moan */ + d = NOTACHAR; /* if these variables are not set. */ + } + + + /* Now process the individual opcodes */ + + switch (codevalue) + { +/* ========================================================================== */ + /* These cases are never obeyed. This is a fudge that causes a compile- + time error if the vectors coptable or poptable, which are indexed by + opcode, are not the correct length. It seems to be the only way to do + such a check at compile time, as the sizeof() operator does not work + in the C preprocessor. */ + + case OP_TABLE_LENGTH: + case OP_TABLE_LENGTH + + ((sizeof(coptable) == OP_TABLE_LENGTH) && + (sizeof(poptable) == OP_TABLE_LENGTH)): + break; + +/* ========================================================================== */ + /* Reached a closing bracket. If not at the end of the pattern, carry + on with the next opcode. For repeating opcodes, also add the repeat + state. Note that KETRPOS will always be encountered at the end of the + subpattern, because the possessive subpattern repeats are always handled + using recursive calls. Thus, it never adds any new states. + + At the end of the (sub)pattern, unless we have an empty string and + PCRE2_NOTEMPTY is set, or PCRE2_NOTEMPTY_ATSTART is set and we are at the + start of the subject, save the match data, shifting up all previous + matches so we always have the longest first. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + case OP_KETRPOS: + if (code != end_code) + { + ADD_ACTIVE(state_offset + 1 + LINK_SIZE, 0); + if (codevalue != OP_KET) + { + ADD_ACTIVE(state_offset - GET(code, 1), 0); + } + } + else + { + if (ptr > current_subject || + ((mb->moptions & PCRE2_NOTEMPTY) == 0 && + ((mb->moptions & PCRE2_NOTEMPTY_ATSTART) == 0 || + current_subject > start_subject + mb->start_offset))) + { + if (match_count < 0) match_count = (offsetcount >= 2)? 1 : 0; + else if (match_count > 0 && ++match_count * 2 > (int)offsetcount) + match_count = 0; + count = ((match_count == 0)? (int)offsetcount : match_count * 2) - 2; + if (count > 0) memmove(offsets + 2, offsets, count * sizeof(PCRE2_SIZE)); + if (offsetcount >= 2) + { + offsets[0] = (int)(current_subject - start_subject); + offsets[1] = (int)(ptr - start_subject); + } + if ((mb->moptions & PCRE2_DFA_SHORTEST) != 0) return match_count; + } + } + break; + +/* ========================================================================== */ + /* These opcodes add to the current list of states without looking + at the current character. */ + + /*-----------------------------------------------------------------*/ + case OP_ALT: + do { code += GET(code, 1); } while (*code == OP_ALT); + ADD_ACTIVE((int)(code - start_code), 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_BRA: + case OP_SBRA: + do + { + ADD_ACTIVE((int)(code - start_code + 1 + LINK_SIZE), 0); + code += GET(code, 1); + } + while (*code == OP_ALT); + break; + + /*-----------------------------------------------------------------*/ + case OP_CBRA: + case OP_SCBRA: + ADD_ACTIVE((int)(code - start_code + 1 + LINK_SIZE + IMM2_SIZE), 0); + code += GET(code, 1); + while (*code == OP_ALT) + { + ADD_ACTIVE((int)(code - start_code + 1 + LINK_SIZE), 0); + code += GET(code, 1); + } + break; + + /*-----------------------------------------------------------------*/ + case OP_BRAZERO: + case OP_BRAMINZERO: + ADD_ACTIVE(state_offset + 1, 0); + code += 1 + GET(code, 2); + while (*code == OP_ALT) code += GET(code, 1); + ADD_ACTIVE((int)(code - start_code + 1 + LINK_SIZE), 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_SKIPZERO: + code += 1 + GET(code, 2); + while (*code == OP_ALT) code += GET(code, 1); + ADD_ACTIVE((int)(code - start_code + 1 + LINK_SIZE), 0); + break; + + /*-----------------------------------------------------------------*/ + case OP_CIRC: + if (ptr == start_subject && (mb->moptions & PCRE2_NOTBOL) == 0) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_CIRCM: + if ((ptr == start_subject && (mb->moptions & PCRE2_NOTBOL) == 0) || + ((ptr != end_subject || (mb->poptions & PCRE2_ALT_CIRCUMFLEX) != 0 ) + && WAS_NEWLINE(ptr))) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_EOD: + if (ptr >= end_subject) + { + if ((mb->moptions & PCRE2_PARTIAL_HARD) != 0) + could_continue = TRUE; + else { ADD_ACTIVE(state_offset + 1, 0); } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_SOD: + if (ptr == start_subject) { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_SOM: + if (ptr == start_subject + start_offset) { ADD_ACTIVE(state_offset + 1, 0); } + break; + + +/* ========================================================================== */ + /* These opcodes inspect the next subject character, and sometimes + the previous one as well, but do not have an argument. The variable + clen contains the length of the current character and is zero if we are + at the end of the subject. */ + + /*-----------------------------------------------------------------*/ + case OP_ANY: + if (clen > 0 && !IS_NEWLINE(ptr)) + { + if (ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else + { + ADD_NEW(state_offset + 1, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_ALLANY: + if (clen > 0) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_EODN: + if (clen == 0 && (mb->moptions & PCRE2_PARTIAL_HARD) != 0) + could_continue = TRUE; + else if (clen == 0 || (IS_NEWLINE(ptr) && ptr == end_subject - mb->nllen)) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_DOLL: + if ((mb->moptions & PCRE2_NOTEOL) == 0) + { + if (clen == 0 && (mb->moptions & PCRE2_PARTIAL_HARD) != 0) + could_continue = TRUE; + else if (clen == 0 || + ((mb->poptions & PCRE2_DOLLAR_ENDONLY) == 0 && IS_NEWLINE(ptr) && + (ptr == end_subject - mb->nllen) + )) + { ADD_ACTIVE(state_offset + 1, 0); } + else if (ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD|PCRE2_PARTIAL_SOFT)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + if ((mb->moptions & PCRE2_PARTIAL_HARD) != 0) + { + reset_could_continue = TRUE; + ADD_NEW_DATA(-(state_offset + 1), 0, 1); + } + else could_continue = partial_newline = TRUE; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_DOLLM: + if ((mb->moptions & PCRE2_NOTEOL) == 0) + { + if (clen == 0 && (mb->moptions & PCRE2_PARTIAL_HARD) != 0) + could_continue = TRUE; + else if (clen == 0 || + ((mb->poptions & PCRE2_DOLLAR_ENDONLY) == 0 && IS_NEWLINE(ptr))) + { ADD_ACTIVE(state_offset + 1, 0); } + else if (ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD|PCRE2_PARTIAL_SOFT)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + if ((mb->moptions & PCRE2_PARTIAL_HARD) != 0) + { + reset_could_continue = TRUE; + ADD_NEW_DATA(-(state_offset + 1), 0, 1); + } + else could_continue = partial_newline = TRUE; + } + } + else if (IS_NEWLINE(ptr)) + { ADD_ACTIVE(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + + case OP_DIGIT: + case OP_WHITESPACE: + case OP_WORDCHAR: + if (clen > 0 && c < 256 && + ((ctypes[c] & toptable1[codevalue]) ^ toptable2[codevalue]) != 0) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_DIGIT: + case OP_NOT_WHITESPACE: + case OP_NOT_WORDCHAR: + if (clen > 0 && (c >= 256 || + ((ctypes[c] & toptable1[codevalue]) ^ toptable2[codevalue]) != 0)) + { ADD_NEW(state_offset + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_WORD_BOUNDARY: + case OP_NOT_WORD_BOUNDARY: + { + int left_word, right_word; + + if (ptr > start_subject) + { + PCRE2_SPTR temp = ptr - 1; + if (temp < mb->start_used_ptr) mb->start_used_ptr = temp; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (utf) { BACKCHAR(temp); } +#endif + GETCHARTEST(d, temp); +#ifdef SUPPORT_UNICODE + if ((mb->poptions & PCRE2_UCP) != 0) + { + if (d == '_') left_word = TRUE; else + { + int cat = UCD_CATEGORY(d); + left_word = (cat == ucp_L || cat == ucp_N); + } + } + else +#endif + left_word = d < 256 && (ctypes[d] & ctype_word) != 0; + } + else left_word = FALSE; + + if (clen > 0) + { + if (ptr >= mb->last_used_ptr) + { + PCRE2_SPTR temp = ptr + 1; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (utf) { FORWARDCHARTEST(temp, mb->end_subject); } +#endif + mb->last_used_ptr = temp; + } +#ifdef SUPPORT_UNICODE + if ((mb->poptions & PCRE2_UCP) != 0) + { + if (c == '_') right_word = TRUE; else + { + int cat = UCD_CATEGORY(c); + right_word = (cat == ucp_L || cat == ucp_N); + } + } + else +#endif + right_word = c < 256 && (ctypes[c] & ctype_word) != 0; + } + else right_word = FALSE; + + if ((left_word == right_word) == (codevalue == OP_NOT_WORD_BOUNDARY)) + { ADD_ACTIVE(state_offset + 1, 0); } + } + break; + + + /*-----------------------------------------------------------------*/ + /* Check the next character by Unicode property. We will get here only + if the support is in the binary; otherwise a compile-time error occurs. + */ + +#ifdef SUPPORT_UNICODE + case OP_PROP: + case OP_NOTPROP: + if (clen > 0) + { + BOOL OK; + const uint32_t *cp; + const ucd_record * prop = GET_UCD(c); + switch(code[1]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = prop->chartype == ucp_Lu || prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt; + break; + + case PT_GC: + OK = PRIV(ucp_gentype)[prop->chartype] == code[2]; + break; + + case PT_PC: + OK = prop->chartype == code[2]; + break; + + case PT_SC: + OK = prop->script == code[2]; + break; + + /* These are specials for combination cases. */ + + case PT_ALNUM: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N; + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_Z; + break; + } + break; + + case PT_WORD: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE; + break; + + case PT_CLIST: + cp = PRIV(ucd_caseless_sets) + code[2]; + for (;;) + { + if (c < *cp) { OK = FALSE; break; } + if (c == *cp++) { OK = TRUE; break; } + } + break; + + case PT_UCNC: + OK = c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (codevalue == OP_PROP)) { ADD_NEW(state_offset + 3, 0); } + } + break; +#endif + + + +/* ========================================================================== */ + /* These opcodes likewise inspect the subject character, but have an + argument that is not a data character. It is one of these opcodes: + OP_ANY, OP_ALLANY, OP_DIGIT, OP_NOT_DIGIT, OP_WHITESPACE, OP_NOT_SPACE, + OP_WORDCHAR, OP_NOT_WORDCHAR. The value is loaded into d. */ + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + if (d == OP_ANY && ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || !IS_NEWLINE(ptr)) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (count > 0 && codevalue == OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + if (d == OP_ANY && ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || !IS_NEWLINE(ptr)) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + 2, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + if (d == OP_ANY && ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || !IS_NEWLINE(ptr)) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSSTAR) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEEXACT: + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + if (d == OP_ANY && ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || !IS_NEWLINE(ptr)) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (++count >= (int)GET2(code, 1)) + { ADD_NEW(state_offset + 1 + IMM2_SIZE + 1, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + ADD_ACTIVE(state_offset + 2 + IMM2_SIZE, 0); + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + if (d == OP_ANY && ptr + 1 >= mb->end_subject && + (mb->moptions & (PCRE2_PARTIAL_HARD)) != 0 && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + could_continue = partial_newline = TRUE; + } + else if ((c >= 256 && d != OP_DIGIT && d != OP_WHITESPACE && d != OP_WORDCHAR) || + (c < 256 && + (d != OP_ANY || !IS_NEWLINE(ptr)) && + ((ctypes[c] & toptable1[d]) ^ toptable2[d]) != 0)) + { + if (codevalue == OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW(state_offset + 2 + IMM2_SIZE, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + +/* ========================================================================== */ + /* These are virtual opcodes that are used when something like + OP_TYPEPLUS has OP_PROP, OP_NOTPROP, OP_ANYNL, or OP_EXTUNI as its + argument. It keeps the code above fast for the other cases. The argument + is in the d variable. */ + +#ifdef SUPPORT_UNICODE + case OP_PROP_EXTRA + OP_TYPEPLUS: + case OP_PROP_EXTRA + OP_TYPEMINPLUS: + case OP_PROP_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 4, 0); } + if (clen > 0) + { + BOOL OK; + const uint32_t *cp; + const ucd_record * prop = GET_UCD(c); + switch(code[2]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = prop->chartype == ucp_Lu || prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt; + break; + + case PT_GC: + OK = PRIV(ucp_gentype)[prop->chartype] == code[3]; + break; + + case PT_PC: + OK = prop->chartype == code[3]; + break; + + case PT_SC: + OK = prop->script == code[3]; + break; + + /* These are specials for combination cases. */ + + case PT_ALNUM: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N; + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_Z; + break; + } + break; + + case PT_WORD: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE; + break; + + case PT_CLIST: + cp = PRIV(ucd_caseless_sets) + code[3]; + for (;;) + { + if (c < *cp) { OK = FALSE; break; } + if (c == *cp++) { OK = TRUE; break; } + } + break; + + case PT_UCNC: + OK = c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (count > 0 && codevalue == OP_PROP_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEPLUS: + case OP_EXTUNI_EXTRA + OP_TYPEMINPLUS: + case OP_EXTUNI_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + int lgb, rgb; + PCRE2_SPTR nptr = ptr + clen; + int ncount = 0; + if (count > 0 && codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + lgb = UCD_GRAPHBREAK(c); + while (nptr < end_subject) + { + dlen = 1; + if (!utf) d = *nptr; else { GETCHARLEN(d, nptr, dlen); } + rgb = UCD_GRAPHBREAK(d); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + ncount++; + lgb = rgb; + nptr += dlen; + } + count++; + ADD_NEW_DATA(-state_offset, count, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEPLUS: + case OP_ANYNL_EXTRA + OP_TYPEMINPLUS: + case OP_ANYNL_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) break; + goto ANYNL01; + + case CHAR_CR: + if (ptr + 1 < end_subject && UCHAR21TEST(ptr + 1) == CHAR_LF) ncount = 1; + /* Fall through */ + + ANYNL01: + case CHAR_LF: + if (count > 0 && codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, ncount); + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEPLUS: + case OP_VSPACE_EXTRA + OP_TYPEMINPLUS: + case OP_VSPACE_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + BOOL OK; + switch (c) + { + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_VSPACE)) + { + if (count > 0 && codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEPLUS: + case OP_HSPACE_EXTRA + OP_TYPEMINPLUS: + case OP_HSPACE_EXTRA + OP_TYPEPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + 2, 0); } + if (clen > 0) + { + BOOL OK; + switch (c) + { + HSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (count > 0 && codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW_DATA(-state_offset, count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ +#ifdef SUPPORT_UNICODE + case OP_PROP_EXTRA + OP_TYPEQUERY: + case OP_PROP_EXTRA + OP_TYPEMINQUERY: + case OP_PROP_EXTRA + OP_TYPEPOSQUERY: + count = 4; + goto QS1; + + case OP_PROP_EXTRA + OP_TYPESTAR: + case OP_PROP_EXTRA + OP_TYPEMINSTAR: + case OP_PROP_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS1: + + ADD_ACTIVE(state_offset + 4, 0); + if (clen > 0) + { + BOOL OK; + const uint32_t *cp; + const ucd_record * prop = GET_UCD(c); + switch(code[2]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = prop->chartype == ucp_Lu || prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt; + break; + + case PT_GC: + OK = PRIV(ucp_gentype)[prop->chartype] == code[3]; + break; + + case PT_PC: + OK = prop->chartype == code[3]; + break; + + case PT_SC: + OK = prop->script == code[3]; + break; + + /* These are specials for combination cases. */ + + case PT_ALNUM: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N; + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_Z; + break; + } + break; + + case PT_WORD: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE; + break; + + case PT_CLIST: + cp = PRIV(ucd_caseless_sets) + code[3]; + for (;;) + { + if (c < *cp) { OK = FALSE; break; } + if (c == *cp++) { OK = TRUE; break; } + } + break; + + case PT_UCNC: + OK = c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (codevalue == OP_PROP_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_PROP_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + count, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEQUERY: + case OP_EXTUNI_EXTRA + OP_TYPEMINQUERY: + case OP_EXTUNI_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS2; + + case OP_EXTUNI_EXTRA + OP_TYPESTAR: + case OP_EXTUNI_EXTRA + OP_TYPEMINSTAR: + case OP_EXTUNI_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS2: + + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + int lgb, rgb; + PCRE2_SPTR nptr = ptr + clen; + int ncount = 0; + if (codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + lgb = UCD_GRAPHBREAK(c); + while (nptr < end_subject) + { + dlen = 1; + if (!utf) d = *nptr; else { GETCHARLEN(d, nptr, dlen); } + rgb = UCD_GRAPHBREAK(d); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + ncount++; + lgb = rgb; + nptr += dlen; + } + ADD_NEW_DATA(-(state_offset + count), 0, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEQUERY: + case OP_ANYNL_EXTRA + OP_TYPEMINQUERY: + case OP_ANYNL_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS3; + + case OP_ANYNL_EXTRA + OP_TYPESTAR: + case OP_ANYNL_EXTRA + OP_TYPEMINSTAR: + case OP_ANYNL_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS3: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) break; + goto ANYNL02; + + case CHAR_CR: + if (ptr + 1 < end_subject && UCHAR21TEST(ptr + 1) == CHAR_LF) ncount = 1; + /* Fall through */ + + ANYNL02: + case CHAR_LF: + if (codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + (int)count), 0, ncount); + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEQUERY: + case OP_VSPACE_EXTRA + OP_TYPEMINQUERY: + case OP_VSPACE_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS4; + + case OP_VSPACE_EXTRA + OP_TYPESTAR: + case OP_VSPACE_EXTRA + OP_TYPEMINSTAR: + case OP_VSPACE_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS4: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + BOOL OK; + switch (c) + { + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + if (OK == (d == OP_VSPACE)) + { + if (codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + (int)count), 0, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEQUERY: + case OP_HSPACE_EXTRA + OP_TYPEMINQUERY: + case OP_HSPACE_EXTRA + OP_TYPEPOSQUERY: + count = 2; + goto QS5; + + case OP_HSPACE_EXTRA + OP_TYPESTAR: + case OP_HSPACE_EXTRA + OP_TYPEMINSTAR: + case OP_HSPACE_EXTRA + OP_TYPEPOSSTAR: + count = 0; + + QS5: + ADD_ACTIVE(state_offset + 2, 0); + if (clen > 0) + { + BOOL OK; + switch (c) + { + HSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSSTAR || + codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW_DATA(-(state_offset + (int)count), 0, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ +#ifdef SUPPORT_UNICODE + case OP_PROP_EXTRA + OP_TYPEEXACT: + case OP_PROP_EXTRA + OP_TYPEUPTO: + case OP_PROP_EXTRA + OP_TYPEMINUPTO: + case OP_PROP_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_PROP_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 1 + IMM2_SIZE + 3, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + const uint32_t *cp; + const ucd_record * prop = GET_UCD(c); + switch(code[1 + IMM2_SIZE + 1]) + { + case PT_ANY: + OK = TRUE; + break; + + case PT_LAMP: + OK = prop->chartype == ucp_Lu || prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt; + break; + + case PT_GC: + OK = PRIV(ucp_gentype)[prop->chartype] == code[1 + IMM2_SIZE + 2]; + break; + + case PT_PC: + OK = prop->chartype == code[1 + IMM2_SIZE + 2]; + break; + + case PT_SC: + OK = prop->script == code[1 + IMM2_SIZE + 2]; + break; + + /* These are specials for combination cases. */ + + case PT_ALNUM: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N; + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_Z; + break; + } + break; + + case PT_WORD: + OK = PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE; + break; + + case PT_CLIST: + cp = PRIV(ucd_caseless_sets) + code[1 + IMM2_SIZE + 2]; + for (;;) + { + if (c < *cp) { OK = FALSE; break; } + if (c == *cp++) { OK = TRUE; break; } + } + break; + + case PT_UCNC: + OK = c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000; + break; + + /* Should never occur, but keep compilers from grumbling. */ + + default: + OK = codevalue != OP_PROP; + break; + } + + if (OK == (d == OP_PROP)) + { + if (codevalue == OP_PROP_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW(state_offset + 1 + IMM2_SIZE + 3, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXTUNI_EXTRA + OP_TYPEEXACT: + case OP_EXTUNI_EXTRA + OP_TYPEUPTO: + case OP_EXTUNI_EXTRA + OP_TYPEMINUPTO: + case OP_EXTUNI_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_EXTUNI_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 2 + IMM2_SIZE, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + int lgb, rgb; + PCRE2_SPTR nptr = ptr + clen; + int ncount = 0; + if (codevalue == OP_EXTUNI_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + lgb = UCD_GRAPHBREAK(c); + while (nptr < end_subject) + { + dlen = 1; + if (!utf) d = *nptr; else { GETCHARLEN(d, nptr, dlen); } + rgb = UCD_GRAPHBREAK(d); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + ncount++; + lgb = rgb; + nptr += dlen; + } + if (nptr >= end_subject && (mb->moptions & PCRE2_PARTIAL_HARD) != 0) + reset_could_continue = TRUE; + if (++count >= (int)GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 2 + IMM2_SIZE), 0, ncount); } + else + { ADD_NEW_DATA(-state_offset, count, ncount); } + } + break; +#endif + + /*-----------------------------------------------------------------*/ + case OP_ANYNL_EXTRA + OP_TYPEEXACT: + case OP_ANYNL_EXTRA + OP_TYPEUPTO: + case OP_ANYNL_EXTRA + OP_TYPEMINUPTO: + case OP_ANYNL_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_ANYNL_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 2 + IMM2_SIZE, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + int ncount = 0; + switch (c) + { + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) break; + goto ANYNL03; + + case CHAR_CR: + if (ptr + 1 < end_subject && UCHAR21TEST(ptr + 1) == CHAR_LF) ncount = 1; + /* Fall through */ + + ANYNL03: + case CHAR_LF: + if (codevalue == OP_ANYNL_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 2 + IMM2_SIZE), 0, ncount); } + else + { ADD_NEW_DATA(-state_offset, count, ncount); } + break; + + default: + break; + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE_EXTRA + OP_TYPEEXACT: + case OP_VSPACE_EXTRA + OP_TYPEUPTO: + case OP_VSPACE_EXTRA + OP_TYPEMINUPTO: + case OP_VSPACE_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_VSPACE_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 2 + IMM2_SIZE, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + switch (c) + { + VSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + } + + if (OK == (d == OP_VSPACE)) + { + if (codevalue == OP_VSPACE_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 2 + IMM2_SIZE), 0, 0); } + else + { ADD_NEW_DATA(-state_offset, count, 0); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE_EXTRA + OP_TYPEEXACT: + case OP_HSPACE_EXTRA + OP_TYPEUPTO: + case OP_HSPACE_EXTRA + OP_TYPEMINUPTO: + case OP_HSPACE_EXTRA + OP_TYPEPOSUPTO: + if (codevalue != OP_HSPACE_EXTRA + OP_TYPEEXACT) + { ADD_ACTIVE(state_offset + 2 + IMM2_SIZE, 0); } + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + BOOL OK; + switch (c) + { + HSPACE_CASES: + OK = TRUE; + break; + + default: + OK = FALSE; + break; + } + + if (OK == (d == OP_HSPACE)) + { + if (codevalue == OP_HSPACE_EXTRA + OP_TYPEPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW_DATA(-(state_offset + 2 + IMM2_SIZE), 0, 0); } + else + { ADD_NEW_DATA(-state_offset, count, 0); } + } + } + break; + +/* ========================================================================== */ + /* These opcodes are followed by a character that is usually compared + to the current subject character; it is loaded into d. We still get + here even if there is no subject character, because in some cases zero + repetitions are permitted. */ + + /*-----------------------------------------------------------------*/ + case OP_CHAR: + if (clen > 0 && c == d) { ADD_NEW(state_offset + dlen + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + case OP_CHARI: + if (clen == 0) break; + +#ifdef SUPPORT_UNICODE + if (utf) + { + if (c == d) { ADD_NEW(state_offset + dlen + 1, 0); } else + { + unsigned int othercase; + if (c < 128) + othercase = fcc[c]; + else + othercase = UCD_OTHERCASE(c); + if (d == othercase) { ADD_NEW(state_offset + dlen + 1, 0); } + } + } + else +#endif /* SUPPORT_UNICODE */ + /* Not UTF mode */ + { + if (TABLE_GET(c, lcc, c) == TABLE_GET(d, lcc, d)) + { ADD_NEW(state_offset + 2, 0); } + } + break; + + +#ifdef SUPPORT_UNICODE + /*-----------------------------------------------------------------*/ + /* This is a tricky one because it can match more than one character. + Find out how many characters to skip, and then set up a negative state + to wait for them to pass before continuing. */ + + case OP_EXTUNI: + if (clen > 0) + { + int lgb, rgb; + PCRE2_SPTR nptr = ptr + clen; + int ncount = 0; + lgb = UCD_GRAPHBREAK(c); + while (nptr < end_subject) + { + dlen = 1; + if (!utf) d = *nptr; else { GETCHARLEN(d, nptr, dlen); } + rgb = UCD_GRAPHBREAK(d); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + ncount++; + lgb = rgb; + nptr += dlen; + } + if (nptr >= end_subject && (mb->moptions & PCRE2_PARTIAL_HARD) != 0) + reset_could_continue = TRUE; + ADD_NEW_DATA(-(state_offset + 1), 0, ncount); + } + break; +#endif + + /*-----------------------------------------------------------------*/ + /* This is a tricky like EXTUNI because it too can match more than one + character (when CR is followed by LF). In this case, set up a negative + state to wait for one character to pass before continuing. */ + + case OP_ANYNL: + if (clen > 0) switch(c) + { + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) break; + + case CHAR_LF: + ADD_NEW(state_offset + 1, 0); + break; + + case CHAR_CR: + if (ptr + 1 >= end_subject) + { + ADD_NEW(state_offset + 1, 0); + if ((mb->moptions & PCRE2_PARTIAL_HARD) != 0) + reset_could_continue = TRUE; + } + else if (UCHAR21TEST(ptr + 1) == CHAR_LF) + { + ADD_NEW_DATA(-(state_offset + 1), 0, 1); + } + else + { + ADD_NEW(state_offset + 1, 0); + } + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_VSPACE: + if (clen > 0) switch(c) + { + VSPACE_CASES: + break; + + default: + ADD_NEW(state_offset + 1, 0); + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_VSPACE: + if (clen > 0) switch(c) + { + VSPACE_CASES: + ADD_NEW(state_offset + 1, 0); + break; + + default: + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_NOT_HSPACE: + if (clen > 0) switch(c) + { + HSPACE_CASES: + break; + + default: + ADD_NEW(state_offset + 1, 0); + break; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_HSPACE: + if (clen > 0) switch(c) + { + HSPACE_CASES: + ADD_NEW(state_offset + 1, 0); + break; + + default: + break; + } + break; + + /*-----------------------------------------------------------------*/ + /* Match a negated single character casefully. */ + + case OP_NOT: + if (clen > 0 && c != d) { ADD_NEW(state_offset + dlen + 1, 0); } + break; + + /*-----------------------------------------------------------------*/ + /* Match a negated single character caselessly. */ + + case OP_NOTI: + if (clen > 0) + { + unsigned int otherd; +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + if (c != d && c != otherd) + { ADD_NEW(state_offset + dlen + 1, 0); } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_PLUSI: + case OP_MINPLUSI: + case OP_POSPLUSI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTPOSPLUSI: + caseless = TRUE; + codevalue -= OP_STARI - OP_STAR; + + /* Fall through */ + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(state_offset + dlen + 1, 0); } + if (clen > 0) + { + uint32_t otherd = NOTACHAR; + if (caseless) + { +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (count > 0 && + (codevalue == OP_POSPLUS || codevalue == OP_NOTPOSPLUS)) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_QUERYI: + case OP_MINQUERYI: + case OP_POSQUERYI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTPOSQUERYI: + caseless = TRUE; + codevalue -= OP_STARI - OP_STAR; + /* Fall through */ + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTPOSQUERY: + ADD_ACTIVE(state_offset + dlen + 1, 0); + if (clen > 0) + { + uint32_t otherd = NOTACHAR; + if (caseless) + { +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSQUERY || codevalue == OP_NOTPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset + dlen + 1, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_STARI: + case OP_MINSTARI: + case OP_POSSTARI: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPOSSTARI: + caseless = TRUE; + codevalue -= OP_STARI - OP_STAR; + /* Fall through */ + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPOSSTAR: + ADD_ACTIVE(state_offset + dlen + 1, 0); + if (clen > 0) + { + uint32_t otherd = NOTACHAR; + if (caseless) + { +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSSTAR || codevalue == OP_NOTPOSSTAR) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset, 0); + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_EXACTI: + case OP_NOTEXACTI: + caseless = TRUE; + codevalue -= OP_STARI - OP_STAR; + /* Fall through */ + case OP_EXACT: + case OP_NOTEXACT: + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + uint32_t otherd = NOTACHAR; + if (caseless) + { +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (++count >= (int)GET2(code, 1)) + { ADD_NEW(state_offset + dlen + 1 + IMM2_SIZE, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_UPTOI: + case OP_MINUPTOI: + case OP_POSUPTOI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTPOSUPTOI: + caseless = TRUE; + codevalue -= OP_STARI - OP_STAR; + /* Fall through */ + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTPOSUPTO: + ADD_ACTIVE(state_offset + dlen + 1 + IMM2_SIZE, 0); + count = current_state->count; /* Number already matched */ + if (clen > 0) + { + uint32_t otherd = NOTACHAR; + if (caseless) + { +#ifdef SUPPORT_UNICODE + if (utf && d >= 128) + otherd = UCD_OTHERCASE(d); + else +#endif /* SUPPORT_UNICODE */ + otherd = TABLE_GET(d, fcc, d); + } + if ((c == d || c == otherd) == (codevalue < OP_NOTSTAR)) + { + if (codevalue == OP_POSUPTO || codevalue == OP_NOTPOSUPTO) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= (int)GET2(code, 1)) + { ADD_NEW(state_offset + dlen + 1 + IMM2_SIZE, 0); } + else + { ADD_NEW(state_offset, count); } + } + } + break; + + +/* ========================================================================== */ + /* These are the class-handling opcodes */ + + case OP_CLASS: + case OP_NCLASS: + case OP_XCLASS: + { + BOOL isinclass = FALSE; + int next_state_offset; + PCRE2_SPTR ecode; + + /* For a simple class, there is always just a 32-byte table, and we + can set isinclass from it. */ + + if (codevalue != OP_XCLASS) + { + ecode = code + 1 + (32 / sizeof(PCRE2_UCHAR)); + if (clen > 0) + { + isinclass = (c > 255)? (codevalue == OP_NCLASS) : + ((((uint8_t *)(code + 1))[c/8] & (1 << (c&7))) != 0); + } + } + + /* An extended class may have a table or a list of single characters, + ranges, or both, and it may be positive or negative. There's a + function that sorts all this out. */ + + else + { + ecode = code + GET(code, 1); + if (clen > 0) isinclass = PRIV(xclass)(c, code + 1 + LINK_SIZE, utf); + } + + /* At this point, isinclass is set for all kinds of class, and ecode + points to the byte after the end of the class. If there is a + quantifier, this is where it will be. */ + + next_state_offset = (int)(ecode - start_code); + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPOSSTAR: + ADD_ACTIVE(next_state_offset + 1, 0); + if (isinclass) + { + if (*ecode == OP_CRPOSSTAR) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(state_offset, 0); + } + break; + + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + count = current_state->count; /* Already matched */ + if (count > 0) { ADD_ACTIVE(next_state_offset + 1, 0); } + if (isinclass) + { + if (count > 0 && *ecode == OP_CRPOSPLUS) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + count++; + ADD_NEW(state_offset, count); + } + break; + + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSQUERY: + ADD_ACTIVE(next_state_offset + 1, 0); + if (isinclass) + { + if (*ecode == OP_CRPOSQUERY) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + ADD_NEW(next_state_offset + 1, 0); + } + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + count = current_state->count; /* Already matched */ + if (count >= (int)GET2(ecode, 1)) + { ADD_ACTIVE(next_state_offset + 1 + 2 * IMM2_SIZE, 0); } + if (isinclass) + { + int max = (int)GET2(ecode, 1 + IMM2_SIZE); + if (*ecode == OP_CRPOSRANGE) + { + active_count--; /* Remove non-match possibility */ + next_active_state--; + } + if (++count >= max && max != 0) /* Max 0 => no limit */ + { ADD_NEW(next_state_offset + 1 + 2 * IMM2_SIZE, 0); } + else + { ADD_NEW(state_offset, count); } + } + break; + + default: + if (isinclass) { ADD_NEW(next_state_offset, 0); } + break; + } + } + break; + +/* ========================================================================== */ + /* These are the opcodes for fancy brackets of various kinds. We have + to use recursion in order to handle them. The "always failing" assertion + (?!) is optimised to OP_FAIL when compiling, so we have to support that, + though the other "backtracking verbs" are not supported. */ + + case OP_FAIL: + forced_fail++; /* Count FAILs for multiple states */ + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + { + PCRE2_SPTR endasscode = code + GET(code, 1); + PCRE2_SIZE local_offsets[2]; + int rc; + int local_workspace[1000]; + + while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1); + + rc = internal_dfa_match( + mb, /* static match data */ + code, /* this subexpression's code */ + ptr, /* where we currently are */ + (int)(ptr - start_subject), /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(PCRE2_SIZE), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + rlevel); /* function recursion level */ + + if (rc == PCRE2_ERROR_DFA_UITEM) return rc; + if ((rc >= 0) == (codevalue == OP_ASSERT || codevalue == OP_ASSERTBACK)) + { ADD_ACTIVE((int)(endasscode + LINK_SIZE + 1 - start_code), 0); } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_COND: + case OP_SCOND: + { + PCRE2_SIZE local_offsets[1000]; + int local_workspace[1000]; + int codelink = GET(code, 1); + int condcode; + + /* Because of the way auto-callout works during compile, a callout item + is inserted between OP_COND and an assertion condition. This does not + happen for the other conditions. */ + + if (code[LINK_SIZE + 1] == OP_CALLOUT + || code[LINK_SIZE + 1] == OP_CALLOUT_STR) + { + unsigned int callout_length = (code[LINK_SIZE + 1] == OP_CALLOUT) + ? PRIV(OP_lengths)[OP_CALLOUT] : GET(code, 2 + 3*LINK_SIZE); + rrc = 0; + if (mb->callout != NULL) + { + pcre2_callout_block cb; + cb.version = 1; + cb.capture_top = 1; + cb.capture_last = 0; + cb.offset_vector = offsets; + cb.mark = NULL; /* No (*MARK) support */ + cb.subject = start_subject; + cb.subject_length = (PCRE2_SIZE)(end_subject - start_subject); + cb.start_match = (PCRE2_SIZE)(current_subject - start_subject); + cb.current_position = (PCRE2_SIZE)(ptr - start_subject); + cb.pattern_position = GET(code, LINK_SIZE + 2); + cb.next_item_length = GET(code, LINK_SIZE + 2 + LINK_SIZE); + + if (code[LINK_SIZE + 1] == OP_CALLOUT) + { + cb.callout_number = code[2 + 3*LINK_SIZE]; + cb.callout_string_offset = 0; + cb.callout_string = NULL; + cb.callout_string_length = 0; + } + else + { + cb.callout_number = 0; + cb.callout_string_offset = GET(code, 2 + 4*LINK_SIZE); + cb.callout_string = code + (2 + 5*LINK_SIZE) + 1; + cb.callout_string_length = + callout_length - (1 + 4*LINK_SIZE) - 2; + } + + if ((rrc = (mb->callout)(&cb, mb->callout_data)) < 0) + return rrc; /* Abandon */ + } + if (rrc > 0) break; /* Fail this thread */ + code += callout_length; /* Skip callout data */ + } + + condcode = code[LINK_SIZE+1]; + + /* Back reference conditions and duplicate named recursion conditions + are not supported */ + + if (condcode == OP_CREF || condcode == OP_DNCREF || + condcode == OP_DNRREF) + return PCRE2_ERROR_DFA_UCOND; + + /* The DEFINE condition is always false, and the assertion (?!) is + converted to OP_FAIL. */ + + if (condcode == OP_FALSE || condcode == OP_FAIL) + { ADD_ACTIVE(state_offset + codelink + LINK_SIZE + 1, 0); } + + /* There is also an always-true condition */ + + else if (condcode == OP_TRUE) + { ADD_ACTIVE(state_offset + LINK_SIZE + 2 + IMM2_SIZE, 0); } + + /* The only supported version of OP_RREF is for the value RREF_ANY, + which means "test if in any recursion". We can't test for specifically + recursed groups. */ + + else if (condcode == OP_RREF) + { + int value = GET2(code, LINK_SIZE + 2); + if (value != RREF_ANY) return PCRE2_ERROR_DFA_UCOND; + if (mb->recursive != NULL) + { ADD_ACTIVE(state_offset + LINK_SIZE + 2 + IMM2_SIZE, 0); } + else { ADD_ACTIVE(state_offset + codelink + LINK_SIZE + 1, 0); } + } + + /* Otherwise, the condition is an assertion */ + + else + { + int rc; + PCRE2_SPTR asscode = code + LINK_SIZE + 1; + PCRE2_SPTR endasscode = asscode + GET(asscode, 1); + + while (*endasscode == OP_ALT) endasscode += GET(endasscode, 1); + + rc = internal_dfa_match( + mb, /* fixed match data */ + asscode, /* this subexpression's code */ + ptr, /* where we currently are */ + (int)(ptr - start_subject), /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(PCRE2_SIZE), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + rlevel); /* function recursion level */ + + if (rc == PCRE2_ERROR_DFA_UITEM) return rc; + if ((rc >= 0) == + (condcode == OP_ASSERT || condcode == OP_ASSERTBACK)) + { ADD_ACTIVE((int)(endasscode + LINK_SIZE + 1 - start_code), 0); } + else + { ADD_ACTIVE(state_offset + codelink + LINK_SIZE + 1, 0); } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_RECURSE: + { + dfa_recursion_info *ri; + PCRE2_SIZE local_offsets[1000]; + int local_workspace[1000]; + PCRE2_SPTR callpat = start_code + GET(code, 1); + uint32_t recno = (callpat == mb->start_code)? 0 : + GET2(callpat, 1 + LINK_SIZE); + int rc; + + /* Check for repeating a recursion without advancing the subject + pointer. This should catch convoluted mutual recursions. (Some simple + cases are caught at compile time.) */ + + for (ri = mb->recursive; ri != NULL; ri = ri->prevrec) + if (recno == ri->group_num && ptr == ri->subject_position) + return PCRE2_ERROR_RECURSELOOP; + + /* Remember this recursion and where we started it so as to + catch infinite loops. */ + + new_recursive.group_num = recno; + new_recursive.subject_position = ptr; + new_recursive.prevrec = mb->recursive; + mb->recursive = &new_recursive; + + rc = internal_dfa_match( + mb, /* fixed match data */ + callpat, /* this subexpression's code */ + ptr, /* where we currently are */ + (int)(ptr - start_subject), /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(PCRE2_SIZE), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + rlevel); /* function recursion level */ + + mb->recursive = new_recursive.prevrec; /* Done this recursion */ + + /* Ran out of internal offsets */ + + if (rc == 0) return PCRE2_ERROR_DFA_RECURSE; + + /* For each successful matched substring, set up the next state with a + count of characters to skip before trying it. Note that the count is in + characters, not bytes. */ + + if (rc > 0) + { + for (rc = rc*2 - 2; rc >= 0; rc -= 2) + { + int charcount = local_offsets[rc+1] - local_offsets[rc]; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (utf) + { + PCRE2_SPTR p = start_subject + local_offsets[rc]; + PCRE2_SPTR pp = start_subject + local_offsets[rc+1]; + while (p < pp) if (NOT_FIRSTCU(*p++)) charcount--; + } +#endif + if (charcount > 0) + { + ADD_NEW_DATA(-(state_offset + LINK_SIZE + 1), 0, (charcount - 1)); + } + else + { + ADD_ACTIVE(state_offset + LINK_SIZE + 1, 0); + } + } + } + else if (rc != PCRE2_ERROR_NOMATCH) return rc; + } + break; + + /*-----------------------------------------------------------------*/ + case OP_BRAPOS: + case OP_SBRAPOS: + case OP_CBRAPOS: + case OP_SCBRAPOS: + case OP_BRAPOSZERO: + { + int charcount, matched_count; + PCRE2_SPTR local_ptr = ptr; + BOOL allow_zero; + + if (codevalue == OP_BRAPOSZERO) + { + allow_zero = TRUE; + codevalue = *(++code); /* Codevalue will be one of above BRAs */ + } + else allow_zero = FALSE; + + /* Loop to match the subpattern as many times as possible as if it were + a complete pattern. */ + + for (matched_count = 0;; matched_count++) + { + PCRE2_SIZE local_offsets[2]; + int local_workspace[1000]; + + int rc = internal_dfa_match( + mb, /* fixed match data */ + code, /* this subexpression's code */ + local_ptr, /* where we currently are */ + (int)(ptr - start_subject), /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(PCRE2_SIZE), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + rlevel); /* function recursion level */ + + /* Failed to match */ + + if (rc < 0) + { + if (rc != PCRE2_ERROR_NOMATCH) return rc; + break; + } + + /* Matched: break the loop if zero characters matched. */ + + charcount = local_offsets[1] - local_offsets[0]; + if (charcount == 0) break; + local_ptr += charcount; /* Advance temporary position ptr */ + } + + /* At this point we have matched the subpattern matched_count + times, and local_ptr is pointing to the character after the end of the + last match. */ + + if (matched_count > 0 || allow_zero) + { + PCRE2_SPTR end_subpattern = code; + int next_state_offset; + + do { end_subpattern += GET(end_subpattern, 1); } + while (*end_subpattern == OP_ALT); + next_state_offset = + (int)(end_subpattern - start_code + LINK_SIZE + 1); + + /* Optimization: if there are no more active states, and there + are no new states yet set up, then skip over the subject string + right here, to save looping. Otherwise, set up the new state to swing + into action when the end of the matched substring is reached. */ + + if (i + 1 >= active_count && new_count == 0) + { + ptr = local_ptr; + clen = 0; + ADD_NEW(next_state_offset, 0); + } + else + { + PCRE2_SPTR p = ptr; + PCRE2_SPTR pp = local_ptr; + charcount = (int)(pp - p); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (utf) while (p < pp) if (NOT_FIRSTCU(*p++)) charcount--; +#endif + ADD_NEW_DATA(-next_state_offset, 0, (charcount - 1)); + } + } + } + break; + + /*-----------------------------------------------------------------*/ + case OP_ONCE: + case OP_ONCE_NC: + { + PCRE2_SIZE local_offsets[2]; + int local_workspace[1000]; + + int rc = internal_dfa_match( + mb, /* fixed match data */ + code, /* this subexpression's code */ + ptr, /* where we currently are */ + (int)(ptr - start_subject), /* start offset */ + local_offsets, /* offset vector */ + sizeof(local_offsets)/sizeof(PCRE2_SIZE), /* size of same */ + local_workspace, /* workspace vector */ + sizeof(local_workspace)/sizeof(int), /* size of same */ + rlevel); /* function recursion level */ + + if (rc >= 0) + { + PCRE2_SPTR end_subpattern = code; + int charcount = local_offsets[1] - local_offsets[0]; + int next_state_offset, repeat_state_offset; + + do { end_subpattern += GET(end_subpattern, 1); } + while (*end_subpattern == OP_ALT); + next_state_offset = + (int)(end_subpattern - start_code + LINK_SIZE + 1); + + /* If the end of this subpattern is KETRMAX or KETRMIN, we must + arrange for the repeat state also to be added to the relevant list. + Calculate the offset, or set -1 for no repeat. */ + + repeat_state_offset = (*end_subpattern == OP_KETRMAX || + *end_subpattern == OP_KETRMIN)? + (int)(end_subpattern - start_code - GET(end_subpattern, 1)) : -1; + + /* If we have matched an empty string, add the next state at the + current character pointer. This is important so that the duplicate + checking kicks in, which is what breaks infinite loops that match an + empty string. */ + + if (charcount == 0) + { + ADD_ACTIVE(next_state_offset, 0); + } + + /* Optimization: if there are no more active states, and there + are no new states yet set up, then skip over the subject string + right here, to save looping. Otherwise, set up the new state to swing + into action when the end of the matched substring is reached. */ + + else if (i + 1 >= active_count && new_count == 0) + { + ptr += charcount; + clen = 0; + ADD_NEW(next_state_offset, 0); + + /* If we are adding a repeat state at the new character position, + we must fudge things so that it is the only current state. + Otherwise, it might be a duplicate of one we processed before, and + that would cause it to be skipped. */ + + if (repeat_state_offset >= 0) + { + next_active_state = active_states; + active_count = 0; + i = -1; + ADD_ACTIVE(repeat_state_offset, 0); + } + } + else + { +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (utf) + { + PCRE2_SPTR p = start_subject + local_offsets[0]; + PCRE2_SPTR pp = start_subject + local_offsets[1]; + while (p < pp) if (NOT_FIRSTCU(*p++)) charcount--; + } +#endif + ADD_NEW_DATA(-next_state_offset, 0, (charcount - 1)); + if (repeat_state_offset >= 0) + { ADD_NEW_DATA(-repeat_state_offset, 0, (charcount - 1)); } + } + } + else if (rc != PCRE2_ERROR_NOMATCH) return rc; + } + break; + + +/* ========================================================================== */ + /* Handle callouts */ + + case OP_CALLOUT: + case OP_CALLOUT_STR: + { + unsigned int callout_length = (*code == OP_CALLOUT) + ? PRIV(OP_lengths)[OP_CALLOUT] : GET(code, 1 + 2*LINK_SIZE); + rrc = 0; + + if (mb->callout != NULL) + { + pcre2_callout_block cb; + cb.version = 1; + cb.capture_top = 1; + cb.capture_last = 0; + cb.offset_vector = offsets; + cb.mark = NULL; /* No (*MARK) support */ + cb.subject = start_subject; + cb.subject_length = (PCRE2_SIZE)(end_subject - start_subject); + cb.start_match = (PCRE2_SIZE)(current_subject - start_subject); + cb.current_position = (PCRE2_SIZE)(ptr - start_subject); + cb.pattern_position = GET(code, 1); + cb.next_item_length = GET(code, 1 + LINK_SIZE); + + if (*code == OP_CALLOUT) + { + cb.callout_number = code[1 + 2*LINK_SIZE]; + cb.callout_string_offset = 0; + cb.callout_string = NULL; + cb.callout_string_length = 0; + } + else + { + cb.callout_number = 0; + cb.callout_string_offset = GET(code, 1 + 3*LINK_SIZE); + cb.callout_string = code + (1 + 4*LINK_SIZE) + 1; + cb.callout_string_length = + callout_length - (1 + 4*LINK_SIZE) - 2; + } + + if ((rrc = (mb->callout)(&cb, mb->callout_data)) < 0) + return rrc; /* Abandon */ + } + if (rrc == 0) + { ADD_ACTIVE(state_offset + callout_length, 0); } + } + break; + + +/* ========================================================================== */ + default: /* Unsupported opcode */ + return PCRE2_ERROR_DFA_UITEM; + } + + NEXT_ACTIVE_STATE: continue; + + } /* End of loop scanning active states */ + + /* We have finished the processing at the current subject character. If no + new states have been set for the next character, we have found all the + matches that we are going to find. If we are at the top level and partial + matching has been requested, check for appropriate conditions. + + The "forced_ fail" variable counts the number of (*F) encountered for the + character. If it is equal to the original active_count (saved in + workspace[1]) it means that (*F) was found on every active state. In this + case we don't want to give a partial match. + + The "could_continue" variable is true if a state could have continued but + for the fact that the end of the subject was reached. */ + + if (new_count <= 0) + { + if (rlevel == 1 && /* Top level, and */ + could_continue && /* Some could go on, and */ + forced_fail != workspace[1] && /* Not all forced fail & */ + ( /* either... */ + (mb->moptions & PCRE2_PARTIAL_HARD) != 0 /* Hard partial */ + || /* or... */ + ((mb->moptions & PCRE2_PARTIAL_SOFT) != 0 && /* Soft partial and */ + match_count < 0) /* no matches */ + ) && /* And... */ + ( + partial_newline || /* Either partial NL */ + ( /* or ... */ + ptr >= end_subject && /* End of subject and */ + ptr > mb->start_used_ptr) /* Inspected non-empty string */ + ) + ) + match_count = PCRE2_ERROR_PARTIAL; + break; /* In effect, "return", but see the comment below */ + } + + /* One or more states are active for the next character. */ + + ptr += clen; /* Advance to next subject character */ + } /* Loop to move along the subject string */ + +/* Control gets here from "break" a few lines above. We do it this way because +if we use "return" above, we have compiler trouble. Some compilers warn if +there's nothing here because they think the function doesn't return a value. On +the other hand, if we put a dummy statement here, some more clever compilers +complain that it can't be reached. Sigh. */ + +return match_count; +} + + + +/************************************************* +* Match a pattern using the DFA algorithm * +*************************************************/ + +/* This function matches a compiled pattern to a subject string, using the +alternate matching algorithm that finds all matches at once. + +Arguments: + code points to the compiled pattern + subject subject string + length length of subject string + startoffset where to start matching in the subject + options option bits + match_data points to a match data structure + gcontext points to a match context + workspace pointer to workspace + wscount size of workspace + +Returns: > 0 => number of match offset pairs placed in offsets + = 0 => offsets overflowed; longest matches are present + -1 => failed to match + < -1 => some kind of unexpected problem +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_dfa_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, + PCRE2_SIZE start_offset, uint32_t options, pcre2_match_data *match_data, + pcre2_match_context *mcontext, int *workspace, size_t wscount) +{ +const pcre2_real_code *re = (const pcre2_real_code *)code; + +PCRE2_SPTR start_match; +PCRE2_SPTR end_subject; +PCRE2_SPTR bumpalong_limit; +PCRE2_SPTR req_cu_ptr; + +BOOL utf, anchored, startline, firstline; + +BOOL has_first_cu = FALSE; +BOOL has_req_cu = FALSE; +PCRE2_UCHAR first_cu = 0; +PCRE2_UCHAR first_cu2 = 0; +PCRE2_UCHAR req_cu = 0; +PCRE2_UCHAR req_cu2 = 0; + +const uint8_t *start_bits = NULL; + +/* We need to have mb pointing to a match block, because the IS_NEWLINE macro +is used below, and it expects NLBLOCK to be defined as a pointer. */ + +dfa_match_block actual_match_block; +dfa_match_block *mb = &actual_match_block; + +/* A length equal to PCRE2_ZERO_TERMINATED implies a zero-terminated +subject string. */ + +if (length == PCRE2_ZERO_TERMINATED) length = PRIV(strlen)(subject); + +/* Plausibility checks */ + +if ((options & ~PUBLIC_DFA_MATCH_OPTIONS) != 0) return PCRE2_ERROR_BADOPTION; +if (re == NULL || subject == NULL || workspace == NULL || match_data == NULL) + return PCRE2_ERROR_NULL; +if (wscount < 20) return PCRE2_ERROR_DFA_WSSIZE; +if (start_offset > length) return PCRE2_ERROR_BADOFFSET; + +/* Check that the first field in the block is the magic number. If it is not, +return with PCRE2_ERROR_BADMAGIC. */ + +if (re->magic_number != MAGIC_NUMBER) return PCRE2_ERROR_BADMAGIC; + +/* Check the code unit width. */ + +if ((re->flags & PCRE2_MODE_MASK) != PCRE2_CODE_UNIT_WIDTH/8) + return PCRE2_ERROR_BADMODE; + +/* PCRE2_NOTEMPTY and PCRE2_NOTEMPTY_ATSTART are match-time flags in the +options variable for this function. Users of PCRE2 who are not calling the +function directly would like to have a way of setting these flags, in the same +way that they can set pcre2_compile() flags like PCRE2_NO_AUTOPOSSESS with +constructions like (*NO_AUTOPOSSESS). To enable this, (*NOTEMPTY) and +(*NOTEMPTY_ATSTART) set bits in the pattern's "flag" function which can now be +transferred to the options for this function. The bits are guaranteed to be +adjacent, but do not have the same values. This bit of Boolean trickery assumes +that the match-time bits are not more significant than the flag bits. If by +accident this is not the case, a compile-time division by zero error will +occur. */ + +#define FF (PCRE2_NOTEMPTY_SET|PCRE2_NE_ATST_SET) +#define OO (PCRE2_NOTEMPTY|PCRE2_NOTEMPTY_ATSTART) +options |= (re->flags & FF) / ((FF & (~FF+1)) / (OO & (~OO+1))); +#undef FF +#undef OO + +/* If restarting after a partial match, do some sanity checks on the contents +of the workspace. */ + +if ((options & PCRE2_DFA_RESTART) != 0) + { + if ((workspace[0] & (-2)) != 0 || workspace[1] < 1 || + workspace[1] > (int)((wscount - 2)/INTS_PER_STATEBLOCK)) + return PCRE2_ERROR_DFA_BADRESTART; + } + +/* Set some local values */ + +utf = (re->overall_options & PCRE2_UTF) != 0; +start_match = subject + start_offset; +end_subject = subject + length; +req_cu_ptr = start_match - 1; +anchored = (options & (PCRE2_ANCHORED|PCRE2_DFA_RESTART)) != 0 || + (re->overall_options & PCRE2_ANCHORED) != 0; + +/* The "must be at the start of a line" flags are used in a loop when finding +where to start. */ + +startline = (re->flags & PCRE2_STARTLINE) != 0; +firstline = (re->overall_options & PCRE2_FIRSTLINE) != 0; +bumpalong_limit = end_subject; + +/* Get data from the match context, if present, and fill in the fields in the +match block. It is an error to set an offset limit without setting the flag at +compile time. */ + +if (mcontext == NULL) + { + mb->callout = NULL; + mb->memctl = re->memctl; + } +else + { + if (mcontext->offset_limit != PCRE2_UNSET) + { + if ((re->overall_options & PCRE2_USE_OFFSET_LIMIT) == 0) + return PCRE2_ERROR_BADOFFSETLIMIT; + bumpalong_limit = subject + mcontext->offset_limit; + } + mb->callout = mcontext->callout; + mb->callout_data = mcontext->callout_data; + mb->memctl = mcontext->memctl; + } + +mb->start_code = (PCRE2_UCHAR *)((uint8_t *)re + sizeof(pcre2_real_code)) + + re->name_count * re->name_entry_size; +mb->tables = re->tables; +mb->start_subject = subject; +mb->end_subject = end_subject; +mb->start_offset = start_offset; +mb->moptions = options; +mb->poptions = re->overall_options; + +/* Process the \R and newline settings. */ + +mb->bsr_convention = re->bsr_convention; +mb->nltype = NLTYPE_FIXED; +switch(re->newline_convention) + { + case PCRE2_NEWLINE_CR: + mb->nllen = 1; + mb->nl[0] = CHAR_CR; + break; + + case PCRE2_NEWLINE_LF: + mb->nllen = 1; + mb->nl[0] = CHAR_NL; + break; + + case PCRE2_NEWLINE_CRLF: + mb->nllen = 2; + mb->nl[0] = CHAR_CR; + mb->nl[1] = CHAR_NL; + break; + + case PCRE2_NEWLINE_ANY: + mb->nltype = NLTYPE_ANY; + break; + + case PCRE2_NEWLINE_ANYCRLF: + mb->nltype = NLTYPE_ANYCRLF; + break; + + default: return PCRE2_ERROR_INTERNAL; + } + +/* Check a UTF string for validity if required. For 8-bit and 16-bit strings, +we must also check that a starting offset does not point into the middle of a +multiunit character. We check only the portion of the subject that is going to +be inspected during matching - from the offset minus the maximum back reference +to the given length. This saves time when a small part of a large subject is +being matched by the use of a starting offset. Note that the maximum lookbehind +is a number of characters, not code units. */ + +#ifdef SUPPORT_UNICODE +if (utf && (options & PCRE2_NO_UTF_CHECK) == 0) + { + PCRE2_SPTR check_subject = start_match; /* start_match includes offset */ + + if (start_offset > 0) + { +#if PCRE2_CODE_UNIT_WIDTH != 32 + unsigned int i; + if (start_match < end_subject && NOT_FIRSTCU(*start_match)) + return PCRE2_ERROR_BADUTFOFFSET; + for (i = re->max_lookbehind; i > 0 && check_subject > subject; i--) + { + check_subject--; + while (check_subject > subject && +#if PCRE2_CODE_UNIT_WIDTH == 8 + (*check_subject & 0xc0) == 0x80) +#else /* 16-bit */ + (*check_subject & 0xfc00) == 0xdc00) +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + check_subject--; + } +#else /* In the 32-bit library, one code unit equals one character. */ + check_subject -= re->max_lookbehind; + if (check_subject < subject) check_subject = subject; +#endif /* PCRE2_CODE_UNIT_WIDTH != 32 */ + } + + /* Validate the relevant portion of the subject. After an error, adjust the + offset to be an absolute offset in the whole string. */ + + match_data->rc = PRIV(valid_utf)(check_subject, + length - (check_subject - subject), &(match_data->startchar)); + if (match_data->rc != 0) + { + match_data->startchar += check_subject - subject; + return match_data->rc; + } + } +#endif /* SUPPORT_UNICODE */ + +/* Set up the first code unit to match, if available. The first_codeunit value +is never set for an anchored regular expression, but the anchoring may be +forced at run time, so we have to test for anchoring. The first code unit may +be unset for an unanchored pattern, of course. If there's no first code unit +there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->flags & PCRE2_FIRSTSET) != 0) + { + has_first_cu = TRUE; + first_cu = first_cu2 = (PCRE2_UCHAR)(re->first_codeunit); + if ((re->flags & PCRE2_FIRSTCASELESS) != 0) + { + first_cu2 = TABLE_GET(first_cu, mb->tables + fcc_offset, first_cu); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (utf && first_cu > 127) first_cu2 = UCD_OTHERCASE(first_cu); +#endif + } + } + else + if (!startline && (re->flags & PCRE2_FIRSTMAPSET) != 0) + start_bits = re->start_bitmap; + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. */ + +if ((re->flags & PCRE2_LASTSET) != 0) + { + has_req_cu = TRUE; + req_cu = req_cu2 = (PCRE2_UCHAR)(re->last_codeunit); + if ((re->flags & PCRE2_LASTCASELESS) != 0) + { + req_cu2 = TABLE_GET(req_cu, mb->tables + fcc_offset, req_cu); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (utf && req_cu > 127) req_cu2 = UCD_OTHERCASE(req_cu); +#endif + } + } + +/* Fill in fields that are always returned in the match data. */ + +match_data->code = re; +match_data->subject = subject; +match_data->mark = NULL; +match_data->matchedby = PCRE2_MATCHEDBY_DFA_INTERPRETER; + +/* Call the main matching function, looping for a non-anchored regex after a +failed match. If not restarting, perform certain optimizations at the start of +a match. */ + +for (;;) + { + int rc; + + /* ----------------- Start of match optimizations ---------------- */ + + /* There are some optimizations that avoid running the match if a known + starting point is not found, or if a known later code unit is not present. + However, there is an option (settable at compile time) that disables + these, for testing and for ensuring that all callouts do actually occur. + The optimizations must also be avoided when restarting a DFA match. */ + + if ((re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0 && + (options & PCRE2_DFA_RESTART) == 0) + { + PCRE2_SPTR save_end_subject = end_subject; + + /* If firstline is TRUE, the start of the match is constrained to the first + line of a multiline string. That is, the match must be before or at the + first newline. Implement this by temporarily adjusting end_subject so that + we stop the optimization scans at a newline. If the match fails at the + newline, later code breaks this loop. */ + + if (firstline) + { + PCRE2_SPTR t = start_match; +#ifdef SUPPORT_UNICODE + if (utf) + { + while (t < mb->end_subject && !IS_NEWLINE(t)) + { + t++; + ACROSSCHAR(t < end_subject, *t, t++); + } + } + else +#endif + while (t < mb->end_subject && !IS_NEWLINE(t)) t++; + end_subject = t; + } + + /* Advance to a unique first code unit if there is one. */ + + if (has_first_cu) + { + PCRE2_UCHAR smc; + if (first_cu != first_cu2) + while (start_match < end_subject && + (smc = UCHAR21TEST(start_match)) != first_cu && smc != first_cu2) + start_match++; + else + while (start_match < end_subject && UCHAR21TEST(start_match) != first_cu) + start_match++; + } + + /* Or to just after a linebreak for a multiline match */ + + else if (startline) + { + if (start_match > mb->start_subject + start_offset) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + while (start_match < end_subject && !WAS_NEWLINE(start_match)) + { + start_match++; + ACROSSCHAR(start_match < end_subject, *start_match, + start_match++); + } + } + else +#endif + while (start_match < end_subject && !WAS_NEWLINE(start_match)) + start_match++; + + /* If we have just passed a CR and the newline option is ANY or + ANYCRLF, and we are now at a LF, advance the match position by one more + code unit. */ + + if (start_match[-1] == CHAR_CR && + (mb->nltype == NLTYPE_ANY || mb->nltype == NLTYPE_ANYCRLF) && + start_match < end_subject && + UCHAR21TEST(start_match) == CHAR_NL) + start_match++; + } + } + + /* Or to a non-unique first code unit if any have been identified. The + bitmap contains only 256 bits. When code units are 16 or 32 bits wide, all + code units greater than 254 set the 255 bit. */ + + else if (start_bits != NULL) + { + while (start_match < end_subject) + { + register uint32_t c = UCHAR21TEST(start_match); +#if PCRE2_CODE_UNIT_WIDTH != 8 + if (c > 255) c = 255; +#endif + if ((start_bits[c/8] & (1 << (c&7))) != 0) break; + start_match++; + } + } + + /* Restore fudged end_subject */ + + end_subject = save_end_subject; + + /* The following two optimizations are disabled for partial matching. */ + + if ((mb->moptions & (PCRE2_PARTIAL_HARD|PCRE2_PARTIAL_SOFT)) == 0) + { + /* The minimum matching length is a lower bound; no actual string of that + length may actually match the pattern. Although the value is, strictly, + in characters, we treat it as code units to avoid spending too much time + in this optimization. */ + + if (end_subject - start_match < re->minlength) return PCRE2_ERROR_NOMATCH; + + /* If req_cu is set, we know that that code unit must appear in the + subject for the match to succeed. If the first code unit is set, req_cu + must be later in the subject; otherwise the test starts at the match + point. This optimization can save a huge amount of backtracking in + patterns with nested unlimited repeats that aren't going to match. + Writing separate code for cased/caseless versions makes it go faster, as + does using an autoincrement and backing off on a match. + + HOWEVER: when the subject string is very, very long, searching to its end + can take a long time, and give bad performance on quite ordinary + patterns. This showed up when somebody was matching something like + /^\d+C/ on a 32-megabyte string... so we don't do this when the string is + sufficiently long. */ + + if (has_req_cu && end_subject - start_match < REQ_CU_MAX) + { + register PCRE2_SPTR p = start_match + (has_first_cu? 1:0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_cu_ptr) + { + if (req_cu != req_cu2) + { + while (p < end_subject) + { + register uint32_t pp = UCHAR21INCTEST(p); + if (pp == req_cu || pp == req_cu2) { p--; break; } + } + } + else + { + while (p < end_subject) + { + if (UCHAR21INCTEST(p) == req_cu) { p--; break; } + } + } + + /* If we can't find the required code unit, break the matching loop, + forcing a match failure. */ + + if (p >= end_subject) break; + + /* If we have found the required code unit, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this code unit yet. */ + + req_cu_ptr = p; + } + } + } + } + + /* ------------ End of start of match optimizations ------------ */ + + /* Give no match if we have passed the bumpalong limit. */ + + if (start_match > bumpalong_limit) break; + + /* OK, now we can do the business */ + + mb->start_used_ptr = start_match; + mb->last_used_ptr = start_match; + mb->recursive = NULL; + + rc = internal_dfa_match( + mb, /* fixed match data */ + mb->start_code, /* this subexpression's code */ + start_match, /* where we currently are */ + start_offset, /* start offset in subject */ + match_data->ovector, /* offset vector */ + match_data->oveccount * 2, /* actual size of same */ + workspace, /* workspace vector */ + wscount, /* size of same */ + 0); /* function recurse level */ + + /* Anything other than "no match" means we are done, always; otherwise, carry + on only if not anchored. */ + + if (rc != PCRE2_ERROR_NOMATCH || anchored) + { + if (rc == PCRE2_ERROR_PARTIAL && match_data->oveccount > 0) + { + match_data->ovector[0] = (PCRE2_SIZE)(start_match - subject); + match_data->ovector[1] = (PCRE2_SIZE)(end_subject - subject); + } + match_data->leftchar = (PCRE2_SIZE)(mb->start_used_ptr - subject); + match_data->rightchar = mb->last_used_ptr - subject; + match_data->startchar = (PCRE2_SIZE)(start_match - subject); + match_data->rc = rc; + return rc; + } + + /* Advance to the next subject character unless we are at the end of a line + and firstline is set. */ + + if (firstline && IS_NEWLINE(start_match)) break; + start_match++; +#ifdef SUPPORT_UNICODE + if (utf) + { + ACROSSCHAR(start_match < end_subject, *start_match, + start_match++); + } +#endif + if (start_match > end_subject) break; + + /* If we have just passed a CR and we are now at a LF, and the pattern does + not contain any explicit matches for \r or \n, and the newline option is CRLF + or ANY or ANYCRLF, advance the match position by one more character. */ + + if (UCHAR21TEST(start_match - 1) == CHAR_CR && + start_match < end_subject && + UCHAR21TEST(start_match) == CHAR_NL && + (re->flags & PCRE2_HASCRORLF) == 0 && + (mb->nltype == NLTYPE_ANY || + mb->nltype == NLTYPE_ANYCRLF || + mb->nllen == 2)) + start_match++; + + } /* "Bumpalong" loop */ + + +return PCRE2_ERROR_NOMATCH; +} + +/* End of pcre2_dfa_match.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2_error.c b/ProcessHacker/pcre/pcre2_error.c new file mode 100644 index 0000000..f44de00 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_error.c @@ -0,0 +1,327 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4996) +#pragma warning(disable : 4267) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + +#define STRING(a) # a +#define XSTRING(s) STRING(s) + +/* The texts of compile-time error messages. Compile-time error numbers start +at COMPILE_ERROR_BASE (100). + +This used to be a table of strings, but in order to reduce the number of +relocations needed when a shared library is loaded dynamically, it is now one +long string. We cannot use a table of offsets, because the lengths of inserts +such as XSTRING(MAX_NAME_SIZE) are not known. Instead, +pcre2_get_error_message() counts through to the one it wants - this isn't a +performance issue because these strings are used only when there is an error. + +Each substring ends with \0 to insert a null character. This includes the final +substring, so that the whole string ends with \0\0, which can be detected when +counting through. */ + +static const char compile_error_texts[] = + "no error\0" + "\\ at end of pattern\0" + "\\c at end of pattern\0" + "unrecognized character follows \\\0" + "numbers out of order in {} quantifier\0" + /* 5 */ + "number too big in {} quantifier\0" + "missing terminating ] for character class\0" + "invalid escape sequence in character class\0" + "range out of order in character class\0" + "quantifier does not follow a repeatable item\0" + /* 10 */ + "internal error: unexpected repeat\0" + "unrecognized character after (? or (?-\0" + "POSIX named classes are supported only within a class\0" + "POSIX collating elements are not supported\0" + "missing closing parenthesis\0" + /* 15 */ + "reference to non-existent subpattern\0" + "pattern passed as NULL\0" + "unrecognised compile-time option bit(s)\0" + "missing ) after (?# comment\0" + "parentheses are too deeply nested\0" + /* 20 */ + "regular expression is too large\0" + "failed to allocate heap memory\0" + "unmatched closing parenthesis\0" + "internal error: code overflow\0" + "letter or underscore expected after (?< or (?'\0" + /* 25 */ + "lookbehind assertion is not fixed length\0" + "malformed number or name after (?(\0" + "conditional group contains more than two branches\0" + "assertion expected after (?( or (?(?C)\0" + "(?R or (?[+-]digits must be followed by )\0" + /* 30 */ + "unknown POSIX class name\0" + "internal error in pcre2_study(): should not occur\0" + "this version of PCRE2 does not have Unicode support\0" + "parentheses are too deeply nested (stack check)\0" + "character code point value in \\x{} or \\o{} is too large\0" + /* 35 */ + "invalid condition (?(0)\0" + "\\C is not allowed in a lookbehind assertion\0" + "PCRE does not support \\L, \\l, \\N{name}, \\U, or \\u\0" + "number after (?C is greater than 255\0" + "closing parenthesis for (?C expected\0" + /* 40 */ + "invalid escape sequence in (*VERB) name\0" + "unrecognized character after (?P\0" + "syntax error in subpattern name (missing terminator)\0" + "two named subpatterns have the same name (PCRE2_DUPNAMES not set)\0" + "group name must start with a non-digit\0" + /* 45 */ + "this version of PCRE2 does not have support for \\P, \\p, or \\X\0" + "malformed \\P or \\p sequence\0" + "unknown property name after \\P or \\p\0" + "subpattern name is too long (maximum " XSTRING(MAX_NAME_SIZE) " characters)\0" + "too many named subpatterns (maximum " XSTRING(MAX_NAME_COUNT) ")\0" + /* 50 */ + "invalid range in character class\0" + "octal value is greater than \\377 in 8-bit non-UTF-8 mode\0" + "internal error: overran compiling workspace\0" + "internal error: previously-checked referenced subpattern not found\0" + "DEFINE group contains more than one branch\0" + /* 55 */ + "missing opening brace after \\o\0" + "internal error: unknown newline setting\0" + "\\g is not followed by a braced, angle-bracketed, or quoted name/number or by a plain number\0" + "a numbered reference must not be zero\0" + "an argument is not allowed for (*ACCEPT), (*FAIL), or (*COMMIT)\0" + /* 60 */ + "(*VERB) not recognized or malformed\0" + "number is too big\0" + "subpattern name expected\0" + "digit expected after (?+\0" + "non-octal character in \\o{} (closing brace missing?)\0" + /* 65 */ + "different names for subpatterns of the same number are not allowed\0" + "(*MARK) must have an argument\0" + "non-hex character in \\x{} (closing brace missing?)\0" +#ifndef EBCDIC + "\\c must be followed by a printable ASCII character\0" +#else + "\\c must be followed by a letter or one of [\\]^_?\0" +#endif + "\\k is not followed by a braced, angle-bracketed, or quoted name\0" + /* 70 */ + "internal error: unknown opcode in find_fixedlength()\0" + "\\N is not supported in a class\0" + "SPARE ERROR\0" + "disallowed Unicode code point (>= 0xd800 && <= 0xdfff)\0" + "using UTF is disabled by the application\0" + /* 75 */ + "using UCP is disabled by the application\0" + "name is too long in (*MARK), (*PRUNE), (*SKIP), or (*THEN)\0" + "character code point value in \\u.... sequence is too large\0" + "digits missing in \\x{} or \\o{}\0" + "syntax error in (?(VERSION condition\0" + /* 80 */ + "internal error: unknown opcode in auto_possessify()\0" + "missing terminating delimiter for callout with string argument\0" + "unrecognized string delimiter follows (?C\0" + "using \\C is disabled by the application\0" + "(?| and/or (?J: or (?x: parentheses are too deeply nested\0" + /* 85 */ + "using \\C is disabled in this PCRE2 library\0" + "regular expression is too complicated\0" + "lookbehind assertion is too long\0" + "pattern string is longer than the limit set by the application\0" + ; + +/* Match-time and UTF error texts are in the same format. */ + +static const char match_error_texts[] = + "no error\0" + "no match\0" + "partial match\0" + "UTF-8 error: 1 byte missing at end\0" + "UTF-8 error: 2 bytes missing at end\0" + /* 5 */ + "UTF-8 error: 3 bytes missing at end\0" + "UTF-8 error: 4 bytes missing at end\0" + "UTF-8 error: 5 bytes missing at end\0" + "UTF-8 error: byte 2 top bits not 0x80\0" + "UTF-8 error: byte 3 top bits not 0x80\0" + /* 10 */ + "UTF-8 error: byte 4 top bits not 0x80\0" + "UTF-8 error: byte 5 top bits not 0x80\0" + "UTF-8 error: byte 6 top bits not 0x80\0" + "UTF-8 error: 5-byte character is not allowed (RFC 3629)\0" + "UTF-8 error: 6-byte character is not allowed (RFC 3629)\0" + /* 15 */ + "UTF-8 error: code points greater than 0x10ffff are not defined\0" + "UTF-8 error: code points 0xd800-0xdfff are not defined\0" + "UTF-8 error: overlong 2-byte sequence\0" + "UTF-8 error: overlong 3-byte sequence\0" + "UTF-8 error: overlong 4-byte sequence\0" + /* 20 */ + "UTF-8 error: overlong 5-byte sequence\0" + "UTF-8 error: overlong 6-byte sequence\0" + "UTF-8 error: isolated byte with 0x80 bit set\0" + "UTF-8 error: illegal byte (0xfe or 0xff)\0" + "UTF-16 error: missing low surrogate at end\0" + /* 25 */ + "UTF-16 error: invalid low surrogate\0" + "UTF-16 error: isolated low surrogate\0" + "UTF-32 error: code points 0xd800-0xdfff are not defined\0" + "UTF-32 error: code points greater than 0x10ffff are not defined\0" + "bad data value\0" + /* 30 */ + "patterns do not all use the same character tables\0" + "magic number missing\0" + "pattern compiled in wrong mode: 8/16/32-bit error\0" + "bad offset value\0" + "bad option value\0" + /* 35 */ + "invalid replacement string\0" + "bad offset into UTF string\0" + "callout error code\0" /* Never returned by PCRE2 itself */ + "invalid data in workspace for DFA restart\0" + "too much recursion for DFA matching\0" + /* 40 */ + "backreference condition or recursion test is not supported for DFA matching\0" + "function is not supported for DFA matching\0" + "pattern contains an item that is not supported for DFA matching\0" + "workspace size exceeded in DFA matching\0" + "internal error - pattern overwritten?\0" + /* 45 */ + "bad JIT option\0" + "JIT stack limit reached\0" + "match limit exceeded\0" + "no more memory\0" + "unknown substring\0" + /* 50 */ + "non-unique substring name\0" + "NULL argument passed\0" + "nested recursion at the same subject position\0" + "recursion limit exceeded\0" + "requested value is not available\0" + /* 55 */ + "requested value is not set\0" + "offset limit set without PCRE2_USE_OFFSET_LIMIT\0" + "bad escape sequence in replacement string\0" + "expected closing curly bracket in replacement string\0" + "bad substitution in replacement string\0" + /* 60 */ + "match with end before start is not supported\0" + "too many replacements (more than INT_MAX)\0" + ; + + +/************************************************* +* Return error message * +*************************************************/ + +/* This function copies an error message into a buffer whose units are of an +appropriate width. Error numbers are positive for compile-time errors, and +negative for match-time errors (except for UTF errors), but the numbers are all +distinct. + +Arguments: + enumber error number + buffer where to put the message (zero terminated) + size size of the buffer + +Returns: length of message if all is well + negative on error +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_get_error_message(int enumber, PCRE2_UCHAR *buffer, size_t size) +{ +char xbuff[128]; +const char *message; +size_t i; +uint32_t n; + +if (size == 0) return PCRE2_ERROR_NOMEMORY; + +if (enumber > COMPILE_ERROR_BASE) /* Compile error */ + { + message = compile_error_texts; + n = enumber - COMPILE_ERROR_BASE; + } +else /* Match or UTF error */ + { + message = match_error_texts; + n = -enumber; + } + +for (; n > 0; n--) + { + while (*message++ != CHAR_NULL) {}; + if (*message == CHAR_NULL) + { + sprintf(xbuff, "No text for error %d", enumber); + break; + } + } + +for (i = 0; *message != 0; i++) + { + if (i >= size - 1) + { + buffer[i] = 0; /* Terminate partial message */ + return PCRE2_ERROR_NOMEMORY; + } + buffer[i] = *message++; + } + +buffer[i] = 0; +return i; +} + +/* End of pcre2_error.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2_find_bracket.c b/ProcessHacker/pcre/pcre2_find_bracket.c new file mode 100644 index 0000000..30d407a --- /dev/null +++ b/ProcessHacker/pcre/pcre2_find_bracket.c @@ -0,0 +1,218 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains a single function that scans through a compiled pattern +until it finds a capturing bracket with the given number, or, if the number is +negative, an instance of OP_REVERSE for a lookbehind. The function is called +from pcre2_compile.c and also from pcre2_study.c when finding the minimum +matching length. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + +/************************************************* +* Scan compiled regex for specific bracket * +*************************************************/ + +/* +Arguments: + code points to start of expression + utf TRUE in UTF mode + number the required bracket number or negative to find a lookbehind + +Returns: pointer to the opcode for the bracket, or NULL if not found +*/ + +PCRE2_SPTR +PRIV(find_bracket)(PCRE2_SPTR code, BOOL utf, int number) +{ +for (;;) + { + register PCRE2_UCHAR c = *code; + + if (c == OP_END) return NULL; + + /* XCLASS is used for classes that cannot be represented just by a bit map. + This includes negated single high-valued characters. CALLOUT_STR is used for + callouts with string arguments. In both cases the length in the table is + zero; the actual length is stored in the compiled code. */ + + if (c == OP_XCLASS) code += GET(code, 1); + else if (c == OP_CALLOUT_STR) code += GET(code, 1 + 2*LINK_SIZE); + + /* Handle lookbehind */ + + else if (c == OP_REVERSE) + { + if (number < 0) return (PCRE2_UCHAR *)code; + code += PRIV(OP_lengths)[c]; + } + + /* Handle capturing bracket */ + + else if (c == OP_CBRA || c == OP_SCBRA || + c == OP_CBRAPOS || c == OP_SCBRAPOS) + { + int n = (int)GET2(code, 1+LINK_SIZE); + if (n == number) return (PCRE2_UCHAR *)code; + code += PRIV(OP_lengths)[c]; + } + + /* Otherwise, we can get the item's length from the table, except that for + repeated character types, we have to test for \p and \P, which have an extra + two bytes of parameters, and for MARK/PRUNE/SKIP/THEN with an argument, we + must add in its length. */ + + else + { + switch(c) + { + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + if (code[1] == OP_PROP || code[1] == OP_NOTPROP) code += 2; + break; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSUPTO: + if (code[1 + IMM2_SIZE] == OP_PROP || code[1 + IMM2_SIZE] == OP_NOTPROP) + code += 2; + break; + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + code += code[1]; + break; + } + + /* Add in the fixed length from the table */ + + code += PRIV(OP_lengths)[c]; + + /* In UTF-8 and UTF-16 modes, opcodes that are followed by a character may be + followed by a multi-byte character. The length in the table is a minimum, so + we have to arrange to skip the extra bytes. */ + +#ifdef MAYBE_UTF_MULTI + if (utf) switch(c) + { + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + case OP_UPTO: + case OP_UPTOI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_MINUPTO: + case OP_MINUPTOI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + case OP_POSUPTO: + case OP_POSUPTOI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + case OP_STAR: + case OP_STARI: + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_MINSTAR: + case OP_MINSTARI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_POSSTAR: + case OP_POSSTARI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + case OP_PLUS: + case OP_PLUSI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_MINPLUS: + case OP_MINPLUSI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_POSPLUS: + case OP_POSPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + case OP_QUERY: + case OP_QUERYI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_MINQUERY: + case OP_MINQUERYI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + case OP_POSQUERY: + case OP_POSQUERYI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + if (HAS_EXTRALEN(code[-1])) code += GET_EXTRALEN(code[-1]); + break; + } +#else + (void)(utf); /* Keep compiler happy by referencing function argument */ +#endif /* MAYBE_UTF_MULTI */ + } + } +} + +/* End of pcre2_find_bracket.c */ diff --git a/ProcessHacker/pcre/pcre2_internal.h b/ProcessHacker/pcre/pcre2_internal.h new file mode 100644 index 0000000..7c9f66c --- /dev/null +++ b/ProcessHacker/pcre/pcre2_internal.h @@ -0,0 +1,1944 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* We do not support both EBCDIC and Unicode at the same time. The "configure" +script prevents both being selected, but not everybody uses "configure". EBCDIC +is only supported for the 8-bit library, but the check for this has to be later +in this file, because the first part is not width-dependent, and is included by +pcre2test.c with CODE_UNIT_WIDTH == 0. */ + +#if defined EBCDIC && defined SUPPORT_UNICODE +#error The use of both EBCDIC and SUPPORT_UNICODE is not supported. +#endif + +/* Standard C headers */ + +#include +#include +#include +#include +#include +#include + +/* Macros to make boolean values more obvious. The #ifndef is to pacify +compiler warnings in environments where these macros are defined elsewhere. +Unfortunately, there is no way to do the same for the typedef. */ + +typedef int BOOL; +#ifndef FALSE +#define FALSE 0 +#define TRUE 1 +#endif + +/* Valgrind (memcheck) support */ + +#ifdef SUPPORT_VALGRIND +#include +#endif + +/* When compiling a DLL for Windows, the exported symbols have to be declared +using some MS magic. I found some useful information on this web page: +http://msdn2.microsoft.com/en-us/library/y4h7bcy6(VS.80).aspx. According to the +information there, using __declspec(dllexport) without "extern" we have a +definition; with "extern" we have a declaration. The settings here override the +setting in pcre2.h (which is included below); it defines only PCRE2_EXP_DECL, +which is all that is needed for applications (they just import the symbols). We +use: + + PCRE2_EXP_DECL for declarations + PCRE2_EXP_DEFN for definitions + +The reason for wrapping this in #ifndef PCRE2_EXP_DECL is so that pcre2test, +which is an application, but needs to import this file in order to "peek" at +internals, can #include pcre2.h first to get an application's-eye view. + +In principle, people compiling for non-Windows, non-Unix-like (i.e. uncommon, +special-purpose environments) might want to stick other stuff in front of +exported symbols. That's why, in the non-Windows case, we set PCRE2_EXP_DEFN +only if it is not already set. */ + +#ifndef PCRE2_EXP_DECL +# ifdef _WIN32 +# ifndef PCRE2_STATIC +# define PCRE2_EXP_DECL extern __declspec(dllexport) +# define PCRE2_EXP_DEFN __declspec(dllexport) +# else +# define PCRE2_EXP_DECL extern +# define PCRE2_EXP_DEFN +# endif +# else +# ifdef __cplusplus +# define PCRE2_EXP_DECL extern "C" +# else +# define PCRE2_EXP_DECL extern +# endif +# ifndef PCRE2_EXP_DEFN +# define PCRE2_EXP_DEFN PCRE2_EXP_DECL +# endif +# endif +#endif + +/* Include the public PCRE2 header and the definitions of UCP character +property values. This must follow the setting of PCRE2_EXP_DECL above. */ + +#include "pcre2.h" +#include "pcre2_ucp.h" + +/* When PCRE2 is compiled as a C++ library, the subject pointer can be replaced +with a custom type. This makes it possible, for example, to allow pcre2_match() +to process subject strings that are discontinuous by using a smart pointer +class. It must always be possible to inspect all of the subject string in +pcre2_match() because of the way it backtracks. */ + +/* WARNING: This is as yet untested for PCRE2. */ + +#ifdef CUSTOM_SUBJECT_PTR +#undef PCRE2_SPTR +#define PCRE2_SPTR CUSTOM_SUBJECT_PTR +#endif + +/* When compiling with the MSVC compiler, it is sometimes necessary to include +a "calling convention" before exported function names. (This is secondhand +information; I know nothing about MSVC myself). For example, something like + + void __cdecl function(....) + +might be needed. In order so make this easy, all the exported functions have +PCRE2_CALL_CONVENTION just before their names. It is rarely needed; if not +set, we ensure here that it has no effect. */ + +#ifndef PCRE2_CALL_CONVENTION +#define PCRE2_CALL_CONVENTION +#endif + +/* When checking for integer overflow in pcre2_compile(), we need to handle +large integers. If a 64-bit integer type is available, we can use that. +Otherwise we have to cast to double, which of course requires floating point +arithmetic. Handle this by defining a macro for the appropriate type. If +stdint.h is available, include it; it may define INT64_MAX. Systems that do not +have stdint.h (e.g. Solaris) may have inttypes.h. The macro int64_t may be set +by "configure". */ + +#if defined HAVE_STDINT_H +#include +#elif defined HAVE_INTTYPES_H +#include +#endif + +#if defined INT64_MAX || defined int64_t +#define INT64_OR_DOUBLE int64_t +#else +#define INT64_OR_DOUBLE double +#endif + +/* When compiling for use with the Virtual Pascal compiler, these functions +need to have their names changed. PCRE must be compiled with the -DVPCOMPAT +option on the command line. */ + +#ifdef VPCOMPAT +#define strlen(s) _strlen(s) +#define strncmp(s1,s2,m) _strncmp(s1,s2,m) +#define memcmp(s,c,n) _memcmp(s,c,n) +#define memcpy(d,s,n) _memcpy(d,s,n) +#define memmove(d,s,n) _memmove(d,s,n) +#define memset(s,c,n) _memset(s,c,n) +#else /* VPCOMPAT */ + +/* To cope with SunOS4 and other systems that lack memmove() but have bcopy(), +define a macro for memmove() if HAVE_MEMMOVE is false, provided that HAVE_BCOPY +is set. Otherwise, include an emulating function for those systems that have +neither (there some non-Unix environments where this is the case). */ + +#ifndef HAVE_MEMMOVE +#undef memmove /* some systems may have a macro */ +#ifdef HAVE_BCOPY +#define memmove(a, b, c) bcopy(b, a, c) +#else /* HAVE_BCOPY */ +static void * +pcre_memmove(void *d, const void *s, size_t n) +{ +size_t i; +unsigned char *dest = (unsigned char *)d; +const unsigned char *src = (const unsigned char *)s; +if (dest > src) + { + dest += n; + src += n; + for (i = 0; i < n; ++i) *(--dest) = *(--src); + return (void *)dest; + } +else + { + for (i = 0; i < n; ++i) *dest++ = *src++; + return (void *)(dest - n); + } +} +#define memmove(a, b, c) pcre_memmove(a, b, c) +#endif /* not HAVE_BCOPY */ +#endif /* not HAVE_MEMMOVE */ +#endif /* not VPCOMPAT */ + +/* External (in the C sense) functions and tables that are private to the +libraries are always referenced using the PRIV macro. This makes it possible +for pcre2test.c to include some of the source files from the libraries using a +different PRIV definition to avoid name clashes. It also makes it clear in the +code that a non-static object is being referenced. */ + +#ifndef PRIV +#define PRIV(name) _pcre2_##name +#endif + +/* This is an unsigned int value that no UTF character can ever have, as +Unicode doesn't go beyond 0x0010ffff. */ + +#define NOTACHAR 0xffffffff + +/* This is the largest valid UTF/Unicode code point. */ + +#define MAX_UTF_CODE_POINT 0x10ffff + +/* Compile-time errors are added to this value. As they are documented, it +should probably never be changed. */ + +#define COMPILE_ERROR_BASE 100 + +/* Define the default BSR convention. */ + +#ifdef BSR_ANYCRLF +#define BSR_DEFAULT PCRE2_BSR_ANYCRLF +#else +#define BSR_DEFAULT PCRE2_BSR_UNICODE +#endif + + +/* ---------------- Basic UTF-8 macros ---------------- */ + +/* These UTF-8 macros are always defined because they are used in pcre2test for +handling wide characters in 16-bit and 32-bit modes, even if an 8-bit library +is not supported. */ + +/* Tests whether a UTF-8 code point needs extra bytes to decode. */ + +#define HASUTF8EXTRALEN(c) ((c) >= 0xc0) + +/* The following macros were originally written in the form of loops that used +data from the tables whose names start with PRIV(utf8_table). They were +rewritten by a user so as not to use loops, because in some environments this +gives a significant performance advantage, and it seems never to do any harm. +*/ + +/* Base macro to pick up the remaining bytes of a UTF-8 character, not +advancing the pointer. */ + +#define GETUTF8(c, eptr) \ + { \ + if ((c & 0x20) == 0) \ + c = ((c & 0x1f) << 6) | (eptr[1] & 0x3f); \ + else if ((c & 0x10) == 0) \ + c = ((c & 0x0f) << 12) | ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \ + else if ((c & 0x08) == 0) \ + c = ((c & 0x07) << 18) | ((eptr[1] & 0x3f) << 12) | \ + ((eptr[2] & 0x3f) << 6) | (eptr[3] & 0x3f); \ + else if ((c & 0x04) == 0) \ + c = ((c & 0x03) << 24) | ((eptr[1] & 0x3f) << 18) | \ + ((eptr[2] & 0x3f) << 12) | ((eptr[3] & 0x3f) << 6) | \ + (eptr[4] & 0x3f); \ + else \ + c = ((c & 0x01) << 30) | ((eptr[1] & 0x3f) << 24) | \ + ((eptr[2] & 0x3f) << 18) | ((eptr[3] & 0x3f) << 12) | \ + ((eptr[4] & 0x3f) << 6) | (eptr[5] & 0x3f); \ + } + +/* Base macro to pick up the remaining bytes of a UTF-8 character, advancing +the pointer. */ + +#define GETUTF8INC(c, eptr) \ + { \ + if ((c & 0x20) == 0) \ + c = ((c & 0x1f) << 6) | (*eptr++ & 0x3f); \ + else if ((c & 0x10) == 0) \ + { \ + c = ((c & 0x0f) << 12) | ((*eptr & 0x3f) << 6) | (eptr[1] & 0x3f); \ + eptr += 2; \ + } \ + else if ((c & 0x08) == 0) \ + { \ + c = ((c & 0x07) << 18) | ((*eptr & 0x3f) << 12) | \ + ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \ + eptr += 3; \ + } \ + else if ((c & 0x04) == 0) \ + { \ + c = ((c & 0x03) << 24) | ((*eptr & 0x3f) << 18) | \ + ((eptr[1] & 0x3f) << 12) | ((eptr[2] & 0x3f) << 6) | \ + (eptr[3] & 0x3f); \ + eptr += 4; \ + } \ + else \ + { \ + c = ((c & 0x01) << 30) | ((*eptr & 0x3f) << 24) | \ + ((eptr[1] & 0x3f) << 18) | ((eptr[2] & 0x3f) << 12) | \ + ((eptr[3] & 0x3f) << 6) | (eptr[4] & 0x3f); \ + eptr += 5; \ + } \ + } + +/* Base macro to pick up the remaining bytes of a UTF-8 character, not +advancing the pointer, incrementing the length. */ + +#define GETUTF8LEN(c, eptr, len) \ + { \ + if ((c & 0x20) == 0) \ + { \ + c = ((c & 0x1f) << 6) | (eptr[1] & 0x3f); \ + len++; \ + } \ + else if ((c & 0x10) == 0) \ + { \ + c = ((c & 0x0f) << 12) | ((eptr[1] & 0x3f) << 6) | (eptr[2] & 0x3f); \ + len += 2; \ + } \ + else if ((c & 0x08) == 0) \ + {\ + c = ((c & 0x07) << 18) | ((eptr[1] & 0x3f) << 12) | \ + ((eptr[2] & 0x3f) << 6) | (eptr[3] & 0x3f); \ + len += 3; \ + } \ + else if ((c & 0x04) == 0) \ + { \ + c = ((c & 0x03) << 24) | ((eptr[1] & 0x3f) << 18) | \ + ((eptr[2] & 0x3f) << 12) | ((eptr[3] & 0x3f) << 6) | \ + (eptr[4] & 0x3f); \ + len += 4; \ + } \ + else \ + {\ + c = ((c & 0x01) << 30) | ((eptr[1] & 0x3f) << 24) | \ + ((eptr[2] & 0x3f) << 18) | ((eptr[3] & 0x3f) << 12) | \ + ((eptr[4] & 0x3f) << 6) | (eptr[5] & 0x3f); \ + len += 5; \ + } \ + } + +/* --------------- Whitespace macros ---------------- */ + +/* Tests for Unicode horizontal and vertical whitespace characters must check a +number of different values. Using a switch statement for this generates the +fastest code (no loop, no memory access), and there are several places in the +interpreter code where this happens. In order to ensure that all the case lists +remain in step, we use macros so that there is only one place where the lists +are defined. + +These values are also required as lists in pcre2_compile.c when processing \h, +\H, \v and \V in a character class. The lists are defined in pcre2_tables.c, +but macros that define the values are here so that all the definitions are +together. The lists must be in ascending character order, terminated by +NOTACHAR (which is 0xffffffff). + +Any changes should ensure that the various macros are kept in step with each +other. NOTE: The values also appear in pcre2_jit_compile.c. */ + +/* -------------- ASCII/Unicode environments -------------- */ + +#ifndef EBCDIC + +/* Character U+180E (Mongolian Vowel Separator) is not included in the list of +spaces in the Unicode file PropList.txt, and Perl does not recognize it as a +space. However, in many other sources it is listed as a space and has been in +PCRE for a long time. */ + +#define HSPACE_LIST \ + CHAR_HT, CHAR_SPACE, CHAR_NBSP, \ + 0x1680, 0x180e, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, \ + 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202f, 0x205f, 0x3000, \ + NOTACHAR + +#define HSPACE_MULTIBYTE_CASES \ + case 0x1680: /* OGHAM SPACE MARK */ \ + case 0x180e: /* MONGOLIAN VOWEL SEPARATOR */ \ + case 0x2000: /* EN QUAD */ \ + case 0x2001: /* EM QUAD */ \ + case 0x2002: /* EN SPACE */ \ + case 0x2003: /* EM SPACE */ \ + case 0x2004: /* THREE-PER-EM SPACE */ \ + case 0x2005: /* FOUR-PER-EM SPACE */ \ + case 0x2006: /* SIX-PER-EM SPACE */ \ + case 0x2007: /* FIGURE SPACE */ \ + case 0x2008: /* PUNCTUATION SPACE */ \ + case 0x2009: /* THIN SPACE */ \ + case 0x200A: /* HAIR SPACE */ \ + case 0x202f: /* NARROW NO-BREAK SPACE */ \ + case 0x205f: /* MEDIUM MATHEMATICAL SPACE */ \ + case 0x3000 /* IDEOGRAPHIC SPACE */ + +#define HSPACE_BYTE_CASES \ + case CHAR_HT: \ + case CHAR_SPACE: \ + case CHAR_NBSP + +#define HSPACE_CASES \ + HSPACE_BYTE_CASES: \ + HSPACE_MULTIBYTE_CASES + +#define VSPACE_LIST \ + CHAR_LF, CHAR_VT, CHAR_FF, CHAR_CR, CHAR_NEL, 0x2028, 0x2029, NOTACHAR + +#define VSPACE_MULTIBYTE_CASES \ + case 0x2028: /* LINE SEPARATOR */ \ + case 0x2029 /* PARAGRAPH SEPARATOR */ + +#define VSPACE_BYTE_CASES \ + case CHAR_LF: \ + case CHAR_VT: \ + case CHAR_FF: \ + case CHAR_CR: \ + case CHAR_NEL + +#define VSPACE_CASES \ + VSPACE_BYTE_CASES: \ + VSPACE_MULTIBYTE_CASES + +/* -------------- EBCDIC environments -------------- */ + +#else +#define HSPACE_LIST CHAR_HT, CHAR_SPACE, CHAR_NBSP, NOTACHAR + +#define HSPACE_BYTE_CASES \ + case CHAR_HT: \ + case CHAR_SPACE: \ + case CHAR_NBSP + +#define HSPACE_CASES HSPACE_BYTE_CASES + +#ifdef EBCDIC_NL25 +#define VSPACE_LIST \ + CHAR_VT, CHAR_FF, CHAR_CR, CHAR_NEL, CHAR_LF, NOTACHAR +#else +#define VSPACE_LIST \ + CHAR_VT, CHAR_FF, CHAR_CR, CHAR_LF, CHAR_NEL, NOTACHAR +#endif + +#define VSPACE_BYTE_CASES \ + case CHAR_LF: \ + case CHAR_VT: \ + case CHAR_FF: \ + case CHAR_CR: \ + case CHAR_NEL + +#define VSPACE_CASES VSPACE_BYTE_CASES +#endif /* EBCDIC */ + +/* -------------- End of whitespace macros -------------- */ + + +/* PCRE2 is able to support several different kinds of newline (CR, LF, CRLF, +"any" and "anycrlf" at present). The following macros are used to package up +testing for newlines. NLBLOCK, PSSTART, and PSEND are defined in the various +modules to indicate in which datablock the parameters exist, and what the +start/end of string field names are. */ + +#define NLTYPE_FIXED 0 /* Newline is a fixed length string */ +#define NLTYPE_ANY 1 /* Newline is any Unicode line ending */ +#define NLTYPE_ANYCRLF 2 /* Newline is CR, LF, or CRLF */ + +/* This macro checks for a newline at the given position */ + +#define IS_NEWLINE(p) \ + ((NLBLOCK->nltype != NLTYPE_FIXED)? \ + ((p) < NLBLOCK->PSEND && \ + PRIV(is_newline)((p), NLBLOCK->nltype, NLBLOCK->PSEND, \ + &(NLBLOCK->nllen), utf)) \ + : \ + ((p) <= NLBLOCK->PSEND - NLBLOCK->nllen && \ + UCHAR21TEST(p) == NLBLOCK->nl[0] && \ + (NLBLOCK->nllen == 1 || UCHAR21TEST(p+1) == NLBLOCK->nl[1]) \ + ) \ + ) + +/* This macro checks for a newline immediately preceding the given position */ + +#define WAS_NEWLINE(p) \ + ((NLBLOCK->nltype != NLTYPE_FIXED)? \ + ((p) > NLBLOCK->PSSTART && \ + PRIV(was_newline)((p), NLBLOCK->nltype, NLBLOCK->PSSTART, \ + &(NLBLOCK->nllen), utf)) \ + : \ + ((p) >= NLBLOCK->PSSTART + NLBLOCK->nllen && \ + UCHAR21TEST(p - NLBLOCK->nllen) == NLBLOCK->nl[0] && \ + (NLBLOCK->nllen == 1 || UCHAR21TEST(p - NLBLOCK->nllen + 1) == NLBLOCK->nl[1]) \ + ) \ + ) + +/* Private flags containing information about the compiled pattern. The first +three must not be changed, because whichever is set is actually the number of +bytes in a code unit in that mode. */ + +#define PCRE2_MODE8 0x00000001 /* compiled in 8 bit mode */ +#define PCRE2_MODE16 0x00000002 /* compiled in 16 bit mode */ +#define PCRE2_MODE32 0x00000004 /* compiled in 32 bit mode */ +#define PCRE2_FIRSTSET 0x00000010 /* first_code unit is set */ +#define PCRE2_FIRSTCASELESS 0x00000020 /* caseless first code unit */ +#define PCRE2_FIRSTMAPSET 0x00000040 /* bitmap of first code units is set */ +#define PCRE2_LASTSET 0x00000080 /* last code unit is set */ +#define PCRE2_LASTCASELESS 0x00000100 /* caseless last code unit */ +#define PCRE2_STARTLINE 0x00000200 /* start after \n for multiline */ +#define PCRE2_JCHANGED 0x00000400 /* j option used in pattern */ +#define PCRE2_HASCRORLF 0x00000800 /* explicit \r or \n in pattern */ +#define PCRE2_HASTHEN 0x00001000 /* pattern contains (*THEN) */ +#define PCRE2_MATCH_EMPTY 0x00002000 /* pattern can match empty string */ +#define PCRE2_BSR_SET 0x00004000 /* BSR was set in the pattern */ +#define PCRE2_NL_SET 0x00008000 /* newline was set in the pattern */ +#define PCRE2_NOTEMPTY_SET 0x00010000 /* (*NOTEMPTY) used ) keep */ +#define PCRE2_NE_ATST_SET 0x00020000 /* (*NOTEMPTY_ATSTART) used) together */ +#define PCRE2_DEREF_TABLES 0x00040000 /* release character tables */ +#define PCRE2_NOJIT 0x00080000 /* (*NOJIT) used */ +#define PCRE2_HASBKPORX 0x00100000 /* contains \P, \p, or \X */ +#define PCRE2_DUPCAPUSED 0x00200000 /* contains (?| */ +#define PCRE2_HASBKC 0x00400000 /* contains \C */ + +#define PCRE2_MODE_MASK (PCRE2_MODE8 | PCRE2_MODE16 | PCRE2_MODE32) + +/* Values for the matchedby field in a match data block. */ + +enum { PCRE2_MATCHEDBY_INTERPRETER, /* pcre2_match() */ + PCRE2_MATCHEDBY_DFA_INTERPRETER, /* pcre2_dfa_match() */ + PCRE2_MATCHEDBY_JIT }; /* pcre2_jit_match() */ + +/* Magic number to provide a small check against being handed junk. */ + +#define MAGIC_NUMBER 0x50435245UL /* 'PCRE' */ + +/* The maximum remaining length of subject we are prepared to search for a +req_unit match. */ + +#define REQ_CU_MAX 1000 + +/* Bit definitions for entries in the pcre_ctypes table. */ + +#define ctype_space 0x01 +#define ctype_letter 0x02 +#define ctype_digit 0x04 +#define ctype_xdigit 0x08 +#define ctype_word 0x10 /* alphanumeric or '_' */ +#define ctype_meta 0x80 /* regexp meta char or zero (end pattern) */ + +/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set +of bits for a class map. Some classes are built by combining these tables. */ + +#define cbit_space 0 /* [:space:] or \s */ +#define cbit_xdigit 32 /* [:xdigit:] */ +#define cbit_digit 64 /* [:digit:] or \d */ +#define cbit_upper 96 /* [:upper:] */ +#define cbit_lower 128 /* [:lower:] */ +#define cbit_word 160 /* [:word:] or \w */ +#define cbit_graph 192 /* [:graph:] */ +#define cbit_print 224 /* [:print:] */ +#define cbit_punct 256 /* [:punct:] */ +#define cbit_cntrl 288 /* [:cntrl:] */ +#define cbit_length 320 /* Length of the cbits table */ + +/* Offsets of the various tables from the base tables pointer, and +total length. */ + +#define lcc_offset 0 +#define fcc_offset 256 +#define cbits_offset 512 +#define ctypes_offset (cbits_offset + cbit_length) +#define tables_length (ctypes_offset + 256) + + +/* -------------------- Character and string names ------------------------ */ + +/* If PCRE is to support UTF-8 on EBCDIC platforms, we cannot use normal +character constants like '*' because the compiler would emit their EBCDIC code, +which is different from their ASCII/UTF-8 code. Instead we define macros for +the characters so that they always use the ASCII/UTF-8 code when UTF-8 support +is enabled. When UTF-8 support is not enabled, the definitions use character +literals. Both character and string versions of each character are needed, and +there are some longer strings as well. + +This means that, on EBCDIC platforms, the PCRE library can handle either +EBCDIC, or UTF-8, but not both. To support both in the same compiled library +would need different lookups depending on whether PCRE2_UTF was set or not. +This would make it impossible to use characters in switch/case statements, +which would reduce performance. For a theoretical use (which nobody has asked +for) in a minority area (EBCDIC platforms), this is not sensible. Any +application that did need both could compile two versions of the library, using +macros to give the functions distinct names. */ + +#ifndef SUPPORT_UNICODE + +/* UTF-8 support is not enabled; use the platform-dependent character literals +so that PCRE works in both ASCII and EBCDIC environments, but only in non-UTF +mode. Newline characters are problematic in EBCDIC. Though it has CR and LF +characters, a common practice has been to use its NL (0x15) character as the +line terminator in C-like processing environments. However, sometimes the LF +(0x25) character is used instead, according to this Unicode document: + +http://unicode.org/standard/reports/tr13/tr13-5.html + +PCRE defaults EBCDIC NL to 0x15, but has a build-time option to select 0x25 +instead. Whichever is *not* chosen is defined as NEL. + +In both ASCII and EBCDIC environments, CHAR_NL and CHAR_LF are synonyms for the +same code point. */ + +#ifdef EBCDIC + +#ifndef EBCDIC_NL25 +#define CHAR_NL '\x15' +#define CHAR_NEL '\x25' +#define STR_NL "\x15" +#define STR_NEL "\x25" +#else +#define CHAR_NL '\x25' +#define CHAR_NEL '\x15' +#define STR_NL "\x25" +#define STR_NEL "\x15" +#endif + +#define CHAR_LF CHAR_NL +#define STR_LF STR_NL + +#define CHAR_ESC '\047' +#define CHAR_DEL '\007' +#define CHAR_NBSP ((unsigned char)'\x41') +#define STR_ESC "\047" +#define STR_DEL "\007" + +#else /* Not EBCDIC */ + +/* In ASCII/Unicode, linefeed is '\n' and we equate this to NL for +compatibility. NEL is the Unicode newline character; make sure it is +a positive value. */ + +#define CHAR_LF '\n' +#define CHAR_NL CHAR_LF +#define CHAR_NEL ((unsigned char)'\x85') +#define CHAR_ESC '\033' +#define CHAR_DEL '\177' +#define CHAR_NBSP ((unsigned char)'\xa0') + +#define STR_LF "\n" +#define STR_NL STR_LF +#define STR_NEL "\x85" +#define STR_ESC "\033" +#define STR_DEL "\177" + +#endif /* EBCDIC */ + +/* The remaining definitions work in both environments. */ + +#define CHAR_NULL '\0' +#define CHAR_HT '\t' +#define CHAR_VT '\v' +#define CHAR_FF '\f' +#define CHAR_CR '\r' +#define CHAR_BS '\b' +#define CHAR_BEL '\a' + +#define CHAR_SPACE ' ' +#define CHAR_EXCLAMATION_MARK '!' +#define CHAR_QUOTATION_MARK '"' +#define CHAR_NUMBER_SIGN '#' +#define CHAR_DOLLAR_SIGN '$' +#define CHAR_PERCENT_SIGN '%' +#define CHAR_AMPERSAND '&' +#define CHAR_APOSTROPHE '\'' +#define CHAR_LEFT_PARENTHESIS '(' +#define CHAR_RIGHT_PARENTHESIS ')' +#define CHAR_ASTERISK '*' +#define CHAR_PLUS '+' +#define CHAR_COMMA ',' +#define CHAR_MINUS '-' +#define CHAR_DOT '.' +#define CHAR_SLASH '/' +#define CHAR_0 '0' +#define CHAR_1 '1' +#define CHAR_2 '2' +#define CHAR_3 '3' +#define CHAR_4 '4' +#define CHAR_5 '5' +#define CHAR_6 '6' +#define CHAR_7 '7' +#define CHAR_8 '8' +#define CHAR_9 '9' +#define CHAR_COLON ':' +#define CHAR_SEMICOLON ';' +#define CHAR_LESS_THAN_SIGN '<' +#define CHAR_EQUALS_SIGN '=' +#define CHAR_GREATER_THAN_SIGN '>' +#define CHAR_QUESTION_MARK '?' +#define CHAR_COMMERCIAL_AT '@' +#define CHAR_A 'A' +#define CHAR_B 'B' +#define CHAR_C 'C' +#define CHAR_D 'D' +#define CHAR_E 'E' +#define CHAR_F 'F' +#define CHAR_G 'G' +#define CHAR_H 'H' +#define CHAR_I 'I' +#define CHAR_J 'J' +#define CHAR_K 'K' +#define CHAR_L 'L' +#define CHAR_M 'M' +#define CHAR_N 'N' +#define CHAR_O 'O' +#define CHAR_P 'P' +#define CHAR_Q 'Q' +#define CHAR_R 'R' +#define CHAR_S 'S' +#define CHAR_T 'T' +#define CHAR_U 'U' +#define CHAR_V 'V' +#define CHAR_W 'W' +#define CHAR_X 'X' +#define CHAR_Y 'Y' +#define CHAR_Z 'Z' +#define CHAR_LEFT_SQUARE_BRACKET '[' +#define CHAR_BACKSLASH '\\' +#define CHAR_RIGHT_SQUARE_BRACKET ']' +#define CHAR_CIRCUMFLEX_ACCENT '^' +#define CHAR_UNDERSCORE '_' +#define CHAR_GRAVE_ACCENT '`' +#define CHAR_a 'a' +#define CHAR_b 'b' +#define CHAR_c 'c' +#define CHAR_d 'd' +#define CHAR_e 'e' +#define CHAR_f 'f' +#define CHAR_g 'g' +#define CHAR_h 'h' +#define CHAR_i 'i' +#define CHAR_j 'j' +#define CHAR_k 'k' +#define CHAR_l 'l' +#define CHAR_m 'm' +#define CHAR_n 'n' +#define CHAR_o 'o' +#define CHAR_p 'p' +#define CHAR_q 'q' +#define CHAR_r 'r' +#define CHAR_s 's' +#define CHAR_t 't' +#define CHAR_u 'u' +#define CHAR_v 'v' +#define CHAR_w 'w' +#define CHAR_x 'x' +#define CHAR_y 'y' +#define CHAR_z 'z' +#define CHAR_LEFT_CURLY_BRACKET '{' +#define CHAR_VERTICAL_LINE '|' +#define CHAR_RIGHT_CURLY_BRACKET '}' +#define CHAR_TILDE '~' + +#define STR_HT "\t" +#define STR_VT "\v" +#define STR_FF "\f" +#define STR_CR "\r" +#define STR_BS "\b" +#define STR_BEL "\a" + +#define STR_SPACE " " +#define STR_EXCLAMATION_MARK "!" +#define STR_QUOTATION_MARK "\"" +#define STR_NUMBER_SIGN "#" +#define STR_DOLLAR_SIGN "$" +#define STR_PERCENT_SIGN "%" +#define STR_AMPERSAND "&" +#define STR_APOSTROPHE "'" +#define STR_LEFT_PARENTHESIS "(" +#define STR_RIGHT_PARENTHESIS ")" +#define STR_ASTERISK "*" +#define STR_PLUS "+" +#define STR_COMMA "," +#define STR_MINUS "-" +#define STR_DOT "." +#define STR_SLASH "/" +#define STR_0 "0" +#define STR_1 "1" +#define STR_2 "2" +#define STR_3 "3" +#define STR_4 "4" +#define STR_5 "5" +#define STR_6 "6" +#define STR_7 "7" +#define STR_8 "8" +#define STR_9 "9" +#define STR_COLON ":" +#define STR_SEMICOLON ";" +#define STR_LESS_THAN_SIGN "<" +#define STR_EQUALS_SIGN "=" +#define STR_GREATER_THAN_SIGN ">" +#define STR_QUESTION_MARK "?" +#define STR_COMMERCIAL_AT "@" +#define STR_A "A" +#define STR_B "B" +#define STR_C "C" +#define STR_D "D" +#define STR_E "E" +#define STR_F "F" +#define STR_G "G" +#define STR_H "H" +#define STR_I "I" +#define STR_J "J" +#define STR_K "K" +#define STR_L "L" +#define STR_M "M" +#define STR_N "N" +#define STR_O "O" +#define STR_P "P" +#define STR_Q "Q" +#define STR_R "R" +#define STR_S "S" +#define STR_T "T" +#define STR_U "U" +#define STR_V "V" +#define STR_W "W" +#define STR_X "X" +#define STR_Y "Y" +#define STR_Z "Z" +#define STR_LEFT_SQUARE_BRACKET "[" +#define STR_BACKSLASH "\\" +#define STR_RIGHT_SQUARE_BRACKET "]" +#define STR_CIRCUMFLEX_ACCENT "^" +#define STR_UNDERSCORE "_" +#define STR_GRAVE_ACCENT "`" +#define STR_a "a" +#define STR_b "b" +#define STR_c "c" +#define STR_d "d" +#define STR_e "e" +#define STR_f "f" +#define STR_g "g" +#define STR_h "h" +#define STR_i "i" +#define STR_j "j" +#define STR_k "k" +#define STR_l "l" +#define STR_m "m" +#define STR_n "n" +#define STR_o "o" +#define STR_p "p" +#define STR_q "q" +#define STR_r "r" +#define STR_s "s" +#define STR_t "t" +#define STR_u "u" +#define STR_v "v" +#define STR_w "w" +#define STR_x "x" +#define STR_y "y" +#define STR_z "z" +#define STR_LEFT_CURLY_BRACKET "{" +#define STR_VERTICAL_LINE "|" +#define STR_RIGHT_CURLY_BRACKET "}" +#define STR_TILDE "~" + +#define STRING_ACCEPT0 "ACCEPT\0" +#define STRING_COMMIT0 "COMMIT\0" +#define STRING_F0 "F\0" +#define STRING_FAIL0 "FAIL\0" +#define STRING_MARK0 "MARK\0" +#define STRING_PRUNE0 "PRUNE\0" +#define STRING_SKIP0 "SKIP\0" +#define STRING_THEN "THEN" + +#define STRING_alpha0 "alpha\0" +#define STRING_lower0 "lower\0" +#define STRING_upper0 "upper\0" +#define STRING_alnum0 "alnum\0" +#define STRING_ascii0 "ascii\0" +#define STRING_blank0 "blank\0" +#define STRING_cntrl0 "cntrl\0" +#define STRING_digit0 "digit\0" +#define STRING_graph0 "graph\0" +#define STRING_print0 "print\0" +#define STRING_punct0 "punct\0" +#define STRING_space0 "space\0" +#define STRING_word0 "word\0" +#define STRING_xdigit "xdigit" + +#define STRING_DEFINE "DEFINE" +#define STRING_VERSION "VERSION" +#define STRING_WEIRD_STARTWORD "[:<:]]" +#define STRING_WEIRD_ENDWORD "[:>:]]" + +#define STRING_CR_RIGHTPAR "CR)" +#define STRING_LF_RIGHTPAR "LF)" +#define STRING_CRLF_RIGHTPAR "CRLF)" +#define STRING_ANY_RIGHTPAR "ANY)" +#define STRING_ANYCRLF_RIGHTPAR "ANYCRLF)" +#define STRING_BSR_ANYCRLF_RIGHTPAR "BSR_ANYCRLF)" +#define STRING_BSR_UNICODE_RIGHTPAR "BSR_UNICODE)" +#define STRING_UTF8_RIGHTPAR "UTF8)" +#define STRING_UTF16_RIGHTPAR "UTF16)" +#define STRING_UTF32_RIGHTPAR "UTF32)" +#define STRING_UTF_RIGHTPAR "UTF)" +#define STRING_UCP_RIGHTPAR "UCP)" +#define STRING_NO_AUTO_POSSESS_RIGHTPAR "NO_AUTO_POSSESS)" +#define STRING_NO_DOTSTAR_ANCHOR_RIGHTPAR "NO_DOTSTAR_ANCHOR)" +#define STRING_NO_JIT_RIGHTPAR "NO_JIT)" +#define STRING_NO_START_OPT_RIGHTPAR "NO_START_OPT)" +#define STRING_NOTEMPTY_RIGHTPAR "NOTEMPTY)" +#define STRING_NOTEMPTY_ATSTART_RIGHTPAR "NOTEMPTY_ATSTART)" +#define STRING_LIMIT_MATCH_EQ "LIMIT_MATCH=" +#define STRING_LIMIT_RECURSION_EQ "LIMIT_RECURSION=" +#define STRING_MARK "MARK" + +#else /* SUPPORT_UNICODE */ + +/* UTF-8 support is enabled; always use UTF-8 (=ASCII) character codes. This +works in both modes non-EBCDIC platforms, and on EBCDIC platforms in UTF-8 mode +only. */ + +#define CHAR_HT '\011' +#define CHAR_VT '\013' +#define CHAR_FF '\014' +#define CHAR_CR '\015' +#define CHAR_LF '\012' +#define CHAR_NL CHAR_LF +#define CHAR_NEL ((unsigned char)'\x85') +#define CHAR_BS '\010' +#define CHAR_BEL '\007' +#define CHAR_ESC '\033' +#define CHAR_DEL '\177' + +#define CHAR_NULL '\0' +#define CHAR_SPACE '\040' +#define CHAR_EXCLAMATION_MARK '\041' +#define CHAR_QUOTATION_MARK '\042' +#define CHAR_NUMBER_SIGN '\043' +#define CHAR_DOLLAR_SIGN '\044' +#define CHAR_PERCENT_SIGN '\045' +#define CHAR_AMPERSAND '\046' +#define CHAR_APOSTROPHE '\047' +#define CHAR_LEFT_PARENTHESIS '\050' +#define CHAR_RIGHT_PARENTHESIS '\051' +#define CHAR_ASTERISK '\052' +#define CHAR_PLUS '\053' +#define CHAR_COMMA '\054' +#define CHAR_MINUS '\055' +#define CHAR_DOT '\056' +#define CHAR_SLASH '\057' +#define CHAR_0 '\060' +#define CHAR_1 '\061' +#define CHAR_2 '\062' +#define CHAR_3 '\063' +#define CHAR_4 '\064' +#define CHAR_5 '\065' +#define CHAR_6 '\066' +#define CHAR_7 '\067' +#define CHAR_8 '\070' +#define CHAR_9 '\071' +#define CHAR_COLON '\072' +#define CHAR_SEMICOLON '\073' +#define CHAR_LESS_THAN_SIGN '\074' +#define CHAR_EQUALS_SIGN '\075' +#define CHAR_GREATER_THAN_SIGN '\076' +#define CHAR_QUESTION_MARK '\077' +#define CHAR_COMMERCIAL_AT '\100' +#define CHAR_A '\101' +#define CHAR_B '\102' +#define CHAR_C '\103' +#define CHAR_D '\104' +#define CHAR_E '\105' +#define CHAR_F '\106' +#define CHAR_G '\107' +#define CHAR_H '\110' +#define CHAR_I '\111' +#define CHAR_J '\112' +#define CHAR_K '\113' +#define CHAR_L '\114' +#define CHAR_M '\115' +#define CHAR_N '\116' +#define CHAR_O '\117' +#define CHAR_P '\120' +#define CHAR_Q '\121' +#define CHAR_R '\122' +#define CHAR_S '\123' +#define CHAR_T '\124' +#define CHAR_U '\125' +#define CHAR_V '\126' +#define CHAR_W '\127' +#define CHAR_X '\130' +#define CHAR_Y '\131' +#define CHAR_Z '\132' +#define CHAR_LEFT_SQUARE_BRACKET '\133' +#define CHAR_BACKSLASH '\134' +#define CHAR_RIGHT_SQUARE_BRACKET '\135' +#define CHAR_CIRCUMFLEX_ACCENT '\136' +#define CHAR_UNDERSCORE '\137' +#define CHAR_GRAVE_ACCENT '\140' +#define CHAR_a '\141' +#define CHAR_b '\142' +#define CHAR_c '\143' +#define CHAR_d '\144' +#define CHAR_e '\145' +#define CHAR_f '\146' +#define CHAR_g '\147' +#define CHAR_h '\150' +#define CHAR_i '\151' +#define CHAR_j '\152' +#define CHAR_k '\153' +#define CHAR_l '\154' +#define CHAR_m '\155' +#define CHAR_n '\156' +#define CHAR_o '\157' +#define CHAR_p '\160' +#define CHAR_q '\161' +#define CHAR_r '\162' +#define CHAR_s '\163' +#define CHAR_t '\164' +#define CHAR_u '\165' +#define CHAR_v '\166' +#define CHAR_w '\167' +#define CHAR_x '\170' +#define CHAR_y '\171' +#define CHAR_z '\172' +#define CHAR_LEFT_CURLY_BRACKET '\173' +#define CHAR_VERTICAL_LINE '\174' +#define CHAR_RIGHT_CURLY_BRACKET '\175' +#define CHAR_TILDE '\176' +#define CHAR_NBSP ((unsigned char)'\xa0') + +#define STR_HT "\011" +#define STR_VT "\013" +#define STR_FF "\014" +#define STR_CR "\015" +#define STR_NL "\012" +#define STR_BS "\010" +#define STR_BEL "\007" +#define STR_ESC "\033" +#define STR_DEL "\177" + +#define STR_SPACE "\040" +#define STR_EXCLAMATION_MARK "\041" +#define STR_QUOTATION_MARK "\042" +#define STR_NUMBER_SIGN "\043" +#define STR_DOLLAR_SIGN "\044" +#define STR_PERCENT_SIGN "\045" +#define STR_AMPERSAND "\046" +#define STR_APOSTROPHE "\047" +#define STR_LEFT_PARENTHESIS "\050" +#define STR_RIGHT_PARENTHESIS "\051" +#define STR_ASTERISK "\052" +#define STR_PLUS "\053" +#define STR_COMMA "\054" +#define STR_MINUS "\055" +#define STR_DOT "\056" +#define STR_SLASH "\057" +#define STR_0 "\060" +#define STR_1 "\061" +#define STR_2 "\062" +#define STR_3 "\063" +#define STR_4 "\064" +#define STR_5 "\065" +#define STR_6 "\066" +#define STR_7 "\067" +#define STR_8 "\070" +#define STR_9 "\071" +#define STR_COLON "\072" +#define STR_SEMICOLON "\073" +#define STR_LESS_THAN_SIGN "\074" +#define STR_EQUALS_SIGN "\075" +#define STR_GREATER_THAN_SIGN "\076" +#define STR_QUESTION_MARK "\077" +#define STR_COMMERCIAL_AT "\100" +#define STR_A "\101" +#define STR_B "\102" +#define STR_C "\103" +#define STR_D "\104" +#define STR_E "\105" +#define STR_F "\106" +#define STR_G "\107" +#define STR_H "\110" +#define STR_I "\111" +#define STR_J "\112" +#define STR_K "\113" +#define STR_L "\114" +#define STR_M "\115" +#define STR_N "\116" +#define STR_O "\117" +#define STR_P "\120" +#define STR_Q "\121" +#define STR_R "\122" +#define STR_S "\123" +#define STR_T "\124" +#define STR_U "\125" +#define STR_V "\126" +#define STR_W "\127" +#define STR_X "\130" +#define STR_Y "\131" +#define STR_Z "\132" +#define STR_LEFT_SQUARE_BRACKET "\133" +#define STR_BACKSLASH "\134" +#define STR_RIGHT_SQUARE_BRACKET "\135" +#define STR_CIRCUMFLEX_ACCENT "\136" +#define STR_UNDERSCORE "\137" +#define STR_GRAVE_ACCENT "\140" +#define STR_a "\141" +#define STR_b "\142" +#define STR_c "\143" +#define STR_d "\144" +#define STR_e "\145" +#define STR_f "\146" +#define STR_g "\147" +#define STR_h "\150" +#define STR_i "\151" +#define STR_j "\152" +#define STR_k "\153" +#define STR_l "\154" +#define STR_m "\155" +#define STR_n "\156" +#define STR_o "\157" +#define STR_p "\160" +#define STR_q "\161" +#define STR_r "\162" +#define STR_s "\163" +#define STR_t "\164" +#define STR_u "\165" +#define STR_v "\166" +#define STR_w "\167" +#define STR_x "\170" +#define STR_y "\171" +#define STR_z "\172" +#define STR_LEFT_CURLY_BRACKET "\173" +#define STR_VERTICAL_LINE "\174" +#define STR_RIGHT_CURLY_BRACKET "\175" +#define STR_TILDE "\176" + +#define STRING_ACCEPT0 STR_A STR_C STR_C STR_E STR_P STR_T "\0" +#define STRING_COMMIT0 STR_C STR_O STR_M STR_M STR_I STR_T "\0" +#define STRING_F0 STR_F "\0" +#define STRING_FAIL0 STR_F STR_A STR_I STR_L "\0" +#define STRING_MARK0 STR_M STR_A STR_R STR_K "\0" +#define STRING_PRUNE0 STR_P STR_R STR_U STR_N STR_E "\0" +#define STRING_SKIP0 STR_S STR_K STR_I STR_P "\0" +#define STRING_THEN STR_T STR_H STR_E STR_N + +#define STRING_alpha0 STR_a STR_l STR_p STR_h STR_a "\0" +#define STRING_lower0 STR_l STR_o STR_w STR_e STR_r "\0" +#define STRING_upper0 STR_u STR_p STR_p STR_e STR_r "\0" +#define STRING_alnum0 STR_a STR_l STR_n STR_u STR_m "\0" +#define STRING_ascii0 STR_a STR_s STR_c STR_i STR_i "\0" +#define STRING_blank0 STR_b STR_l STR_a STR_n STR_k "\0" +#define STRING_cntrl0 STR_c STR_n STR_t STR_r STR_l "\0" +#define STRING_digit0 STR_d STR_i STR_g STR_i STR_t "\0" +#define STRING_graph0 STR_g STR_r STR_a STR_p STR_h "\0" +#define STRING_print0 STR_p STR_r STR_i STR_n STR_t "\0" +#define STRING_punct0 STR_p STR_u STR_n STR_c STR_t "\0" +#define STRING_space0 STR_s STR_p STR_a STR_c STR_e "\0" +#define STRING_word0 STR_w STR_o STR_r STR_d "\0" +#define STRING_xdigit STR_x STR_d STR_i STR_g STR_i STR_t + +#define STRING_DEFINE STR_D STR_E STR_F STR_I STR_N STR_E +#define STRING_VERSION STR_V STR_E STR_R STR_S STR_I STR_O STR_N +#define STRING_WEIRD_STARTWORD STR_LEFT_SQUARE_BRACKET STR_COLON STR_LESS_THAN_SIGN STR_COLON STR_RIGHT_SQUARE_BRACKET STR_RIGHT_SQUARE_BRACKET +#define STRING_WEIRD_ENDWORD STR_LEFT_SQUARE_BRACKET STR_COLON STR_GREATER_THAN_SIGN STR_COLON STR_RIGHT_SQUARE_BRACKET STR_RIGHT_SQUARE_BRACKET + +#define STRING_CR_RIGHTPAR STR_C STR_R STR_RIGHT_PARENTHESIS +#define STRING_LF_RIGHTPAR STR_L STR_F STR_RIGHT_PARENTHESIS +#define STRING_CRLF_RIGHTPAR STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS +#define STRING_ANY_RIGHTPAR STR_A STR_N STR_Y STR_RIGHT_PARENTHESIS +#define STRING_ANYCRLF_RIGHTPAR STR_A STR_N STR_Y STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS +#define STRING_BSR_ANYCRLF_RIGHTPAR STR_B STR_S STR_R STR_UNDERSCORE STR_A STR_N STR_Y STR_C STR_R STR_L STR_F STR_RIGHT_PARENTHESIS +#define STRING_BSR_UNICODE_RIGHTPAR STR_B STR_S STR_R STR_UNDERSCORE STR_U STR_N STR_I STR_C STR_O STR_D STR_E STR_RIGHT_PARENTHESIS +#define STRING_UTF8_RIGHTPAR STR_U STR_T STR_F STR_8 STR_RIGHT_PARENTHESIS +#define STRING_UTF16_RIGHTPAR STR_U STR_T STR_F STR_1 STR_6 STR_RIGHT_PARENTHESIS +#define STRING_UTF32_RIGHTPAR STR_U STR_T STR_F STR_3 STR_2 STR_RIGHT_PARENTHESIS +#define STRING_UTF_RIGHTPAR STR_U STR_T STR_F STR_RIGHT_PARENTHESIS +#define STRING_UCP_RIGHTPAR STR_U STR_C STR_P STR_RIGHT_PARENTHESIS +#define STRING_NO_AUTO_POSSESS_RIGHTPAR STR_N STR_O STR_UNDERSCORE STR_A STR_U STR_T STR_O STR_UNDERSCORE STR_P STR_O STR_S STR_S STR_E STR_S STR_S STR_RIGHT_PARENTHESIS +#define STRING_NO_DOTSTAR_ANCHOR_RIGHTPAR STR_N STR_O STR_UNDERSCORE STR_D STR_O STR_T STR_S STR_T STR_A STR_R STR_UNDERSCORE STR_A STR_N STR_C STR_H STR_O STR_R STR_RIGHT_PARENTHESIS +#define STRING_NO_JIT_RIGHTPAR STR_N STR_O STR_UNDERSCORE STR_J STR_I STR_T STR_RIGHT_PARENTHESIS +#define STRING_NO_START_OPT_RIGHTPAR STR_N STR_O STR_UNDERSCORE STR_S STR_T STR_A STR_R STR_T STR_UNDERSCORE STR_O STR_P STR_T STR_RIGHT_PARENTHESIS +#define STRING_NOTEMPTY_RIGHTPAR STR_N STR_O STR_T STR_E STR_M STR_P STR_T STR_Y STR_RIGHT_PARENTHESIS +#define STRING_NOTEMPTY_ATSTART_RIGHTPAR STR_N STR_O STR_T STR_E STR_M STR_P STR_T STR_Y STR_UNDERSCORE STR_A STR_T STR_S STR_T STR_A STR_R STR_T STR_RIGHT_PARENTHESIS +#define STRING_LIMIT_MATCH_EQ STR_L STR_I STR_M STR_I STR_T STR_UNDERSCORE STR_M STR_A STR_T STR_C STR_H STR_EQUALS_SIGN +#define STRING_LIMIT_RECURSION_EQ STR_L STR_I STR_M STR_I STR_T STR_UNDERSCORE STR_R STR_E STR_C STR_U STR_R STR_S STR_I STR_O STR_N STR_EQUALS_SIGN +#define STRING_MARK STR_M STR_A STR_R STR_K + +#endif /* SUPPORT_UNICODE */ + +/* -------------------- End of character and string names -------------------*/ + +/* -------------------- Definitions for compiled patterns -------------------*/ + +/* Codes for different types of Unicode property */ + +#define PT_ANY 0 /* Any property - matches all chars */ +#define PT_LAMP 1 /* L& - the union of Lu, Ll, Lt */ +#define PT_GC 2 /* Specified general characteristic (e.g. L) */ +#define PT_PC 3 /* Specified particular characteristic (e.g. Lu) */ +#define PT_SC 4 /* Script (e.g. Han) */ +#define PT_ALNUM 5 /* Alphanumeric - the union of L and N */ +#define PT_SPACE 6 /* Perl space - Z plus 9,10,12,13 */ +#define PT_PXSPACE 7 /* POSIX space - Z plus 9,10,11,12,13 */ +#define PT_WORD 8 /* Word - L plus N plus underscore */ +#define PT_CLIST 9 /* Pseudo-property: match character list */ +#define PT_UCNC 10 /* Universal Character nameable character */ +#define PT_TABSIZE 11 /* Size of square table for autopossessify tests */ + +/* The following special properties are used only in XCLASS items, when POSIX +classes are specified and PCRE_UCP is set - in other words, for Unicode +handling of these classes. They are not available via the \p or \P escapes like +those in the above list, and so they do not take part in the autopossessifying +table. */ + +#define PT_PXGRAPH 11 /* [:graph:] - characters that mark the paper */ +#define PT_PXPRINT 12 /* [:print:] - [:graph:] plus non-control spaces */ +#define PT_PXPUNCT 13 /* [:punct:] - punctuation characters */ + +/* Flag bits and data types for the extended class (OP_XCLASS) for classes that +contain characters with values greater than 255. */ + +#define XCL_NOT 0x01 /* Flag: this is a negative class */ +#define XCL_MAP 0x02 /* Flag: a 32-byte map is present */ +#define XCL_HASPROP 0x04 /* Flag: property checks are present. */ + +#define XCL_END 0 /* Marks end of individual items */ +#define XCL_SINGLE 1 /* Single item (one multibyte char) follows */ +#define XCL_RANGE 2 /* A range (two multibyte chars) follows */ +#define XCL_PROP 3 /* Unicode property (2-byte property code follows) */ +#define XCL_NOTPROP 4 /* Unicode inverted property (ditto) */ + +/* Escape items that are just an encoding of a particular data value. These +appear in the escapes[] table in pcre2_compile.c as positive numbers. */ + +#ifndef ESC_a +#define ESC_a CHAR_BEL +#endif + +#ifndef ESC_e +#define ESC_e CHAR_ESC +#endif + +#ifndef ESC_f +#define ESC_f CHAR_FF +#endif + +#ifndef ESC_n +#define ESC_n CHAR_LF +#endif + +#ifndef ESC_r +#define ESC_r CHAR_CR +#endif + +/* We can't officially use ESC_t because it is a POSIX reserved identifier +(presumably because of all the others like size_t). */ + +#ifndef ESC_tee +#define ESC_tee CHAR_HT +#endif + +/* These are escaped items that aren't just an encoding of a particular data +value such as \n. They must have non-zero values, as check_escape() returns 0 +for a data character. In the escapes[] table in pcre2_compile.c their values +are negated in order to distinguish them from data values. + +They must appear here in the same order as in the opcode definitions below, up +to ESC_z. There's a dummy for OP_ALLANY because it corresponds to "." in DOTALL +mode rather than an escape sequence. It is also used for [^] in JavaScript +compatibility mode, and for \C in non-utf mode. In non-DOTALL mode, "." behaves +like \N. + +The special values ESC_DU, ESC_du, etc. are used instead of ESC_D, ESC_d, etc. +when PCRE_UCP is set and replacement of \d etc by \p sequences is required. +They must be contiguous, and remain in order so that the replacements can be +looked up from a table. + +Negative numbers are used to encode a backreference (\1, \2, \3, etc.) in +check_escape(). There are two tests in the code for an escape +greater than ESC_b and less than ESC_Z to detect the types that may be +repeated. These are the types that consume characters. If any new escapes are +put in between that don't consume a character, that code will have to change. +*/ + +enum { ESC_A = 1, ESC_G, ESC_K, ESC_B, ESC_b, ESC_D, ESC_d, ESC_S, ESC_s, + ESC_W, ESC_w, ESC_N, ESC_dum, ESC_C, ESC_P, ESC_p, ESC_R, ESC_H, + ESC_h, ESC_V, ESC_v, ESC_X, ESC_Z, ESC_z, + ESC_E, ESC_Q, ESC_g, ESC_k, + ESC_DU, ESC_du, ESC_SU, ESC_su, ESC_WU, ESC_wu }; + + +/********************** Opcode definitions ******************/ + +/****** NOTE NOTE NOTE ****** + +Starting from 1 (i.e. after OP_END), the values up to OP_EOD must correspond in +order to the list of escapes immediately above. Furthermore, values up to +OP_DOLLM must not be changed without adjusting the table called autoposstab in +pcre_compile.c + +Whenever this list is updated, the two macro definitions that follow must be +updated to match. The possessification table called "opcode_possessify" in +pcre_compile.c must also be updated, and also the tables called "coptable" +and "poptable" in pcre_dfa_exec.c. + +****** NOTE NOTE NOTE ******/ + + +/* The values between FIRST_AUTOTAB_OP and LAST_AUTOTAB_RIGHT_OP, inclusive, +are used in a table for deciding whether a repeated character type can be +auto-possessified. */ + +#define FIRST_AUTOTAB_OP OP_NOT_DIGIT +#define LAST_AUTOTAB_LEFT_OP OP_EXTUNI +#define LAST_AUTOTAB_RIGHT_OP OP_DOLLM + +enum { + OP_END, /* 0 End of pattern */ + + /* Values corresponding to backslashed metacharacters */ + + OP_SOD, /* 1 Start of data: \A */ + OP_SOM, /* 2 Start of match (subject + offset): \G */ + OP_SET_SOM, /* 3 Set start of match (\K) */ + OP_NOT_WORD_BOUNDARY, /* 4 \B */ + OP_WORD_BOUNDARY, /* 5 \b */ + OP_NOT_DIGIT, /* 6 \D */ + OP_DIGIT, /* 7 \d */ + OP_NOT_WHITESPACE, /* 8 \S */ + OP_WHITESPACE, /* 9 \s */ + OP_NOT_WORDCHAR, /* 10 \W */ + OP_WORDCHAR, /* 11 \w */ + + OP_ANY, /* 12 Match any character except newline (\N) */ + OP_ALLANY, /* 13 Match any character */ + OP_ANYBYTE, /* 14 Match any byte (\C); different to OP_ANY for UTF-8 */ + OP_NOTPROP, /* 15 \P (not Unicode property) */ + OP_PROP, /* 16 \p (Unicode property) */ + OP_ANYNL, /* 17 \R (any newline sequence) */ + OP_NOT_HSPACE, /* 18 \H (not horizontal whitespace) */ + OP_HSPACE, /* 19 \h (horizontal whitespace) */ + OP_NOT_VSPACE, /* 20 \V (not vertical whitespace) */ + OP_VSPACE, /* 21 \v (vertical whitespace) */ + OP_EXTUNI, /* 22 \X (extended Unicode sequence */ + OP_EODN, /* 23 End of data or \n at end of data (\Z) */ + OP_EOD, /* 24 End of data (\z) */ + + /* Line end assertions */ + + OP_DOLL, /* 25 End of line - not multiline */ + OP_DOLLM, /* 26 End of line - multiline */ + OP_CIRC, /* 27 Start of line - not multiline */ + OP_CIRCM, /* 28 Start of line - multiline */ + + /* Single characters; caseful must precede the caseless ones */ + + OP_CHAR, /* 29 Match one character, casefully */ + OP_CHARI, /* 30 Match one character, caselessly */ + OP_NOT, /* 31 Match one character, not the given one, casefully */ + OP_NOTI, /* 32 Match one character, not the given one, caselessly */ + + /* The following sets of 13 opcodes must always be kept in step because + the offset from the first one is used to generate the others. */ + + /* Repeated characters; caseful must precede the caseless ones */ + + OP_STAR, /* 33 The maximizing and minimizing versions of */ + OP_MINSTAR, /* 34 these six opcodes must come in pairs, with */ + OP_PLUS, /* 35 the minimizing one second. */ + OP_MINPLUS, /* 36 */ + OP_QUERY, /* 37 */ + OP_MINQUERY, /* 38 */ + + OP_UPTO, /* 39 From 0 to n matches of one character, caseful*/ + OP_MINUPTO, /* 40 */ + OP_EXACT, /* 41 Exactly n matches */ + + OP_POSSTAR, /* 42 Possessified star, caseful */ + OP_POSPLUS, /* 43 Possessified plus, caseful */ + OP_POSQUERY, /* 44 Posesssified query, caseful */ + OP_POSUPTO, /* 45 Possessified upto, caseful */ + + /* Repeated characters; caseless must follow the caseful ones */ + + OP_STARI, /* 46 */ + OP_MINSTARI, /* 47 */ + OP_PLUSI, /* 48 */ + OP_MINPLUSI, /* 49 */ + OP_QUERYI, /* 50 */ + OP_MINQUERYI, /* 51 */ + + OP_UPTOI, /* 52 From 0 to n matches of one character, caseless */ + OP_MINUPTOI, /* 53 */ + OP_EXACTI, /* 54 */ + + OP_POSSTARI, /* 55 Possessified star, caseless */ + OP_POSPLUSI, /* 56 Possessified plus, caseless */ + OP_POSQUERYI, /* 57 Posesssified query, caseless */ + OP_POSUPTOI, /* 58 Possessified upto, caseless */ + + /* The negated ones must follow the non-negated ones, and match them */ + /* Negated repeated character, caseful; must precede the caseless ones */ + + OP_NOTSTAR, /* 59 The maximizing and minimizing versions of */ + OP_NOTMINSTAR, /* 60 these six opcodes must come in pairs, with */ + OP_NOTPLUS, /* 61 the minimizing one second. They must be in */ + OP_NOTMINPLUS, /* 62 exactly the same order as those above. */ + OP_NOTQUERY, /* 63 */ + OP_NOTMINQUERY, /* 64 */ + + OP_NOTUPTO, /* 65 From 0 to n matches, caseful */ + OP_NOTMINUPTO, /* 66 */ + OP_NOTEXACT, /* 67 Exactly n matches */ + + OP_NOTPOSSTAR, /* 68 Possessified versions, caseful */ + OP_NOTPOSPLUS, /* 69 */ + OP_NOTPOSQUERY, /* 70 */ + OP_NOTPOSUPTO, /* 71 */ + + /* Negated repeated character, caseless; must follow the caseful ones */ + + OP_NOTSTARI, /* 72 */ + OP_NOTMINSTARI, /* 73 */ + OP_NOTPLUSI, /* 74 */ + OP_NOTMINPLUSI, /* 75 */ + OP_NOTQUERYI, /* 76 */ + OP_NOTMINQUERYI, /* 77 */ + + OP_NOTUPTOI, /* 78 From 0 to n matches, caseless */ + OP_NOTMINUPTOI, /* 79 */ + OP_NOTEXACTI, /* 80 Exactly n matches */ + + OP_NOTPOSSTARI, /* 81 Possessified versions, caseless */ + OP_NOTPOSPLUSI, /* 82 */ + OP_NOTPOSQUERYI, /* 83 */ + OP_NOTPOSUPTOI, /* 84 */ + + /* Character types */ + + OP_TYPESTAR, /* 85 The maximizing and minimizing versions of */ + OP_TYPEMINSTAR, /* 86 these six opcodes must come in pairs, with */ + OP_TYPEPLUS, /* 87 the minimizing one second. These codes must */ + OP_TYPEMINPLUS, /* 88 be in exactly the same order as those above. */ + OP_TYPEQUERY, /* 89 */ + OP_TYPEMINQUERY, /* 90 */ + + OP_TYPEUPTO, /* 91 From 0 to n matches */ + OP_TYPEMINUPTO, /* 92 */ + OP_TYPEEXACT, /* 93 Exactly n matches */ + + OP_TYPEPOSSTAR, /* 94 Possessified versions */ + OP_TYPEPOSPLUS, /* 95 */ + OP_TYPEPOSQUERY, /* 96 */ + OP_TYPEPOSUPTO, /* 97 */ + + /* These are used for character classes and back references; only the + first six are the same as the sets above. */ + + OP_CRSTAR, /* 98 The maximizing and minimizing versions of */ + OP_CRMINSTAR, /* 99 all these opcodes must come in pairs, with */ + OP_CRPLUS, /* 100 the minimizing one second. These codes must */ + OP_CRMINPLUS, /* 101 be in exactly the same order as those above. */ + OP_CRQUERY, /* 102 */ + OP_CRMINQUERY, /* 103 */ + + OP_CRRANGE, /* 104 These are different to the three sets above. */ + OP_CRMINRANGE, /* 105 */ + + OP_CRPOSSTAR, /* 106 Possessified versions */ + OP_CRPOSPLUS, /* 107 */ + OP_CRPOSQUERY, /* 108 */ + OP_CRPOSRANGE, /* 109 */ + + /* End of quantifier opcodes */ + + OP_CLASS, /* 110 Match a character class, chars < 256 only */ + OP_NCLASS, /* 111 Same, but the bitmap was created from a negative + class - the difference is relevant only when a + character > 255 is encountered. */ + OP_XCLASS, /* 112 Extended class for handling > 255 chars within the + class. This does both positive and negative. */ + OP_REF, /* 113 Match a back reference, casefully */ + OP_REFI, /* 114 Match a back reference, caselessly */ + OP_DNREF, /* 115 Match a duplicate name backref, casefully */ + OP_DNREFI, /* 116 Match a duplicate name backref, caselessly */ + OP_RECURSE, /* 117 Match a numbered subpattern (possibly recursive) */ + OP_CALLOUT, /* 118 Call out to external function if provided */ + OP_CALLOUT_STR, /* 119 Call out with string argument */ + + OP_ALT, /* 120 Start of alternation */ + OP_KET, /* 121 End of group that doesn't have an unbounded repeat */ + OP_KETRMAX, /* 122 These two must remain together and in this */ + OP_KETRMIN, /* 123 order. They are for groups the repeat for ever. */ + OP_KETRPOS, /* 124 Possessive unlimited repeat. */ + + /* The assertions must come before BRA, CBRA, ONCE, and COND, and the four + asserts must remain in order. */ + + OP_REVERSE, /* 125 Move pointer back - used in lookbehind assertions */ + OP_ASSERT, /* 126 Positive lookahead */ + OP_ASSERT_NOT, /* 127 Negative lookahead */ + OP_ASSERTBACK, /* 128 Positive lookbehind */ + OP_ASSERTBACK_NOT, /* 129 Negative lookbehind */ + + /* ONCE, ONCE_NC, BRA, BRAPOS, CBRA, CBRAPOS, and COND must come immediately + after the assertions, with ONCE first, as there's a test for >= ONCE for a + subpattern that isn't an assertion. The POS versions must immediately follow + the non-POS versions in each case. */ + + OP_ONCE, /* 130 Atomic group, contains captures */ + OP_ONCE_NC, /* 131 Atomic group containing no captures */ + OP_BRA, /* 132 Start of non-capturing bracket */ + OP_BRAPOS, /* 133 Ditto, with unlimited, possessive repeat */ + OP_CBRA, /* 134 Start of capturing bracket */ + OP_CBRAPOS, /* 135 Ditto, with unlimited, possessive repeat */ + OP_COND, /* 136 Conditional group */ + + /* These five must follow the previous five, in the same order. There's a + check for >= SBRA to distinguish the two sets. */ + + OP_SBRA, /* 137 Start of non-capturing bracket, check empty */ + OP_SBRAPOS, /* 138 Ditto, with unlimited, possessive repeat */ + OP_SCBRA, /* 139 Start of capturing bracket, check empty */ + OP_SCBRAPOS, /* 140 Ditto, with unlimited, possessive repeat */ + OP_SCOND, /* 141 Conditional group, check empty */ + + /* The next two pairs must (respectively) be kept together. */ + + OP_CREF, /* 142 Used to hold a capture number as condition */ + OP_DNCREF, /* 143 Used to point to duplicate names as a condition */ + OP_RREF, /* 144 Used to hold a recursion number as condition */ + OP_DNRREF, /* 145 Used to point to duplicate names as a condition */ + OP_FALSE, /* 146 Always false (used by DEFINE and VERSION) */ + OP_TRUE, /* 147 Always true (used by VERSION) */ + + OP_BRAZERO, /* 148 These two must remain together and in this */ + OP_BRAMINZERO, /* 149 order. */ + OP_BRAPOSZERO, /* 150 */ + + /* These are backtracking control verbs */ + + OP_MARK, /* 151 always has an argument */ + OP_PRUNE, /* 152 */ + OP_PRUNE_ARG, /* 153 same, but with argument */ + OP_SKIP, /* 154 */ + OP_SKIP_ARG, /* 155 same, but with argument */ + OP_THEN, /* 156 */ + OP_THEN_ARG, /* 157 same, but with argument */ + OP_COMMIT, /* 158 */ + + /* These are forced failure and success verbs */ + + OP_FAIL, /* 159 */ + OP_ACCEPT, /* 160 */ + OP_ASSERT_ACCEPT, /* 161 Used inside assertions */ + OP_CLOSE, /* 162 Used before OP_ACCEPT to close open captures */ + + /* This is used to skip a subpattern with a {0} quantifier */ + + OP_SKIPZERO, /* 163 */ + + /* This is used to identify a DEFINE group during compilation so that it can + be checked for having only one branch. It is changed to OP_FALSE before + compilation finishes. */ + + OP_DEFINE, /* 164 */ + + /* This is not an opcode, but is used to check that tables indexed by opcode + are the correct length, in order to catch updating errors - there have been + some in the past. */ + + OP_TABLE_LENGTH + +}; + +/* *** NOTE NOTE NOTE *** Whenever the list above is updated, the two macro +definitions that follow must also be updated to match. There are also tables +called "opcode_possessify" in pcre2_compile.c and "coptable" and "poptable" in +pcre2_dfa_exec.c that must be updated. */ + + +/* This macro defines textual names for all the opcodes. These are used only +for debugging, and some of them are only partial names. The macro is referenced +only in pcre2_printint.c, which fills out the full names in many cases (and in +some cases doesn't actually use these names at all). */ + +#define OP_NAME_LIST \ + "End", "\\A", "\\G", "\\K", "\\B", "\\b", "\\D", "\\d", \ + "\\S", "\\s", "\\W", "\\w", "Any", "AllAny", "Anybyte", \ + "notprop", "prop", "\\R", "\\H", "\\h", "\\V", "\\v", \ + "extuni", "\\Z", "\\z", \ + "$", "$", "^", "^", "char", "chari", "not", "noti", \ + "*", "*?", "+", "+?", "?", "??", \ + "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", \ + "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", \ + "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", \ + "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", "{", \ + "*+","++", "?+", "{", \ + "*", "*?", "+", "+?", "?", "??", "{", "{", \ + "*+","++", "?+", "{", \ + "class", "nclass", "xclass", "Ref", "Refi", "DnRef", "DnRefi", \ + "Recurse", "Callout", "CalloutStr", \ + "Alt", "Ket", "KetRmax", "KetRmin", "KetRpos", \ + "Reverse", "Assert", "Assert not", "AssertB", "AssertB not", \ + "Once", "Once_NC", \ + "Bra", "BraPos", "CBra", "CBraPos", \ + "Cond", \ + "SBra", "SBraPos", "SCBra", "SCBraPos", \ + "SCond", \ + "Cond ref", "Cond dnref", "Cond rec", "Cond dnrec", \ + "Cond false", "Cond true", \ + "Brazero", "Braminzero", "Braposzero", \ + "*MARK", "*PRUNE", "*PRUNE", "*SKIP", "*SKIP", \ + "*THEN", "*THEN", "*COMMIT", "*FAIL", \ + "*ACCEPT", "*ASSERT_ACCEPT", \ + "Close", "Skip zero", "Define" + + +/* This macro defines the length of fixed length operations in the compiled +regex. The lengths are used when searching for specific things, and also in the +debugging printing of a compiled regex. We use a macro so that it can be +defined close to the definitions of the opcodes themselves. + +As things have been extended, some of these are no longer fixed lenths, but are +minima instead. For example, the length of a single-character repeat may vary +in UTF-8 mode. The code that uses this table must know about such things. */ + +#define OP_LENGTHS \ + 1, /* End */ \ + 1, 1, 1, 1, 1, /* \A, \G, \K, \B, \b */ \ + 1, 1, 1, 1, 1, 1, /* \D, \d, \S, \s, \W, \w */ \ + 1, 1, 1, /* Any, AllAny, Anybyte */ \ + 3, 3, /* \P, \p */ \ + 1, 1, 1, 1, 1, /* \R, \H, \h, \V, \v */ \ + 1, /* \X */ \ + 1, 1, 1, 1, 1, 1, /* \Z, \z, $, $M ^, ^M */ \ + 2, /* Char - the minimum length */ \ + 2, /* Chari - the minimum length */ \ + 2, /* not */ \ + 2, /* noti */ \ + /* Positive single-char repeats ** These are */ \ + 2, 2, 2, 2, 2, 2, /* *, *?, +, +?, ?, ?? ** minima in */ \ + 2+IMM2_SIZE, 2+IMM2_SIZE, /* upto, minupto ** mode */ \ + 2+IMM2_SIZE, /* exact */ \ + 2, 2, 2, 2+IMM2_SIZE, /* *+, ++, ?+, upto+ */ \ + 2, 2, 2, 2, 2, 2, /* *I, *?I, +I, +?I, ?I, ??I ** UTF-8 */ \ + 2+IMM2_SIZE, 2+IMM2_SIZE, /* upto I, minupto I */ \ + 2+IMM2_SIZE, /* exact I */ \ + 2, 2, 2, 2+IMM2_SIZE, /* *+I, ++I, ?+I, upto+I */ \ + /* Negative single-char repeats - only for chars < 256 */ \ + 2, 2, 2, 2, 2, 2, /* NOT *, *?, +, +?, ?, ?? */ \ + 2+IMM2_SIZE, 2+IMM2_SIZE, /* NOT upto, minupto */ \ + 2+IMM2_SIZE, /* NOT exact */ \ + 2, 2, 2, 2+IMM2_SIZE, /* Possessive NOT *, +, ?, upto */ \ + 2, 2, 2, 2, 2, 2, /* NOT *I, *?I, +I, +?I, ?I, ??I */ \ + 2+IMM2_SIZE, 2+IMM2_SIZE, /* NOT upto I, minupto I */ \ + 2+IMM2_SIZE, /* NOT exact I */ \ + 2, 2, 2, 2+IMM2_SIZE, /* Possessive NOT *I, +I, ?I, upto I */ \ + /* Positive type repeats */ \ + 2, 2, 2, 2, 2, 2, /* Type *, *?, +, +?, ?, ?? */ \ + 2+IMM2_SIZE, 2+IMM2_SIZE, /* Type upto, minupto */ \ + 2+IMM2_SIZE, /* Type exact */ \ + 2, 2, 2, 2+IMM2_SIZE, /* Possessive *+, ++, ?+, upto+ */ \ + /* Character class & ref repeats */ \ + 1, 1, 1, 1, 1, 1, /* *, *?, +, +?, ?, ?? */ \ + 1+2*IMM2_SIZE, 1+2*IMM2_SIZE, /* CRRANGE, CRMINRANGE */ \ + 1, 1, 1, 1+2*IMM2_SIZE, /* Possessive *+, ++, ?+, CRPOSRANGE */ \ + 1+(32/sizeof(PCRE2_UCHAR)), /* CLASS */ \ + 1+(32/sizeof(PCRE2_UCHAR)), /* NCLASS */ \ + 0, /* XCLASS - variable length */ \ + 1+IMM2_SIZE, /* REF */ \ + 1+IMM2_SIZE, /* REFI */ \ + 1+2*IMM2_SIZE, /* DNREF */ \ + 1+2*IMM2_SIZE, /* DNREFI */ \ + 1+LINK_SIZE, /* RECURSE */ \ + 1+2*LINK_SIZE+1, /* CALLOUT */ \ + 0, /* CALLOUT_STR - variable length */ \ + 1+LINK_SIZE, /* Alt */ \ + 1+LINK_SIZE, /* Ket */ \ + 1+LINK_SIZE, /* KetRmax */ \ + 1+LINK_SIZE, /* KetRmin */ \ + 1+LINK_SIZE, /* KetRpos */ \ + 1+LINK_SIZE, /* Reverse */ \ + 1+LINK_SIZE, /* Assert */ \ + 1+LINK_SIZE, /* Assert not */ \ + 1+LINK_SIZE, /* Assert behind */ \ + 1+LINK_SIZE, /* Assert behind not */ \ + 1+LINK_SIZE, /* ONCE */ \ + 1+LINK_SIZE, /* ONCE_NC */ \ + 1+LINK_SIZE, /* BRA */ \ + 1+LINK_SIZE, /* BRAPOS */ \ + 1+LINK_SIZE+IMM2_SIZE, /* CBRA */ \ + 1+LINK_SIZE+IMM2_SIZE, /* CBRAPOS */ \ + 1+LINK_SIZE, /* COND */ \ + 1+LINK_SIZE, /* SBRA */ \ + 1+LINK_SIZE, /* SBRAPOS */ \ + 1+LINK_SIZE+IMM2_SIZE, /* SCBRA */ \ + 1+LINK_SIZE+IMM2_SIZE, /* SCBRAPOS */ \ + 1+LINK_SIZE, /* SCOND */ \ + 1+IMM2_SIZE, 1+2*IMM2_SIZE, /* CREF, DNCREF */ \ + 1+IMM2_SIZE, 1+2*IMM2_SIZE, /* RREF, DNRREF */ \ + 1, 1, /* FALSE, TRUE */ \ + 1, 1, 1, /* BRAZERO, BRAMINZERO, BRAPOSZERO */ \ + 3, 1, 3, /* MARK, PRUNE, PRUNE_ARG */ \ + 1, 3, /* SKIP, SKIP_ARG */ \ + 1, 3, /* THEN, THEN_ARG */ \ + 1, 1, 1, 1, /* COMMIT, FAIL, ACCEPT, ASSERT_ACCEPT */ \ + 1+IMM2_SIZE, 1, /* CLOSE, SKIPZERO */ \ + 1 /* DEFINE */ + +/* A magic value for OP_RREF to indicate the "any recursion" condition. */ + +#define RREF_ANY 0xffff + + +/* ---------- Private structures that are mode-independent. ---------- */ + +/* Structure to hold data for custom memory management. */ + +typedef struct pcre2_memctl { + void * (*malloc)(size_t, void *); + void (*free)(void *, void *); + void *memory_data; +} pcre2_memctl; + +/* Structure for building a chain of open capturing subpatterns during +compiling, so that instructions to close them can be compiled when (*ACCEPT) is +encountered. This is also used to identify subpatterns that contain recursive +back references to themselves, so that they can be made atomic. */ + +typedef struct open_capitem { + struct open_capitem *next; /* Chain link */ + uint16_t number; /* Capture number */ + uint16_t flag; /* Set TRUE if recursive back ref */ +} open_capitem; + +/* Layout of the UCP type table that translates property names into types and +codes. Each entry used to point directly to a name, but to reduce the number of +relocations in shared libraries, it now has an offset into a single string +instead. */ + +typedef struct { + uint16_t name_offset; + uint16_t type; + uint16_t value; +} ucp_type_table; + +/* Unicode character database (UCD) record format */ + +typedef struct { + uint8_t script; /* ucp_Arabic, etc. */ + uint8_t chartype; /* ucp_Cc, etc. (general categories) */ + uint8_t gbprop; /* ucp_gbControl, etc. (grapheme break property) */ + uint8_t caseset; /* offset to multichar other cases or zero */ + int32_t other_case; /* offset to other case, or zero if none */ +} ucd_record; + +/* UCD access macros */ + +#define UCD_BLOCK_SIZE 128 +#define GET_UCD(ch) (PRIV(ucd_records) + \ + PRIV(ucd_stage2)[PRIV(ucd_stage1)[(int)(ch) / UCD_BLOCK_SIZE] * \ + UCD_BLOCK_SIZE + (int)(ch) % UCD_BLOCK_SIZE]) + +#define UCD_CHARTYPE(ch) GET_UCD(ch)->chartype +#define UCD_SCRIPT(ch) GET_UCD(ch)->script +#define UCD_CATEGORY(ch) PRIV(ucp_gentype)[UCD_CHARTYPE(ch)] +#define UCD_GRAPHBREAK(ch) GET_UCD(ch)->gbprop +#define UCD_CASESET(ch) GET_UCD(ch)->caseset +#define UCD_OTHERCASE(ch) ((uint32_t)((int)ch + (int)(GET_UCD(ch)->other_case))) + +/* Header for serialized pcre2 codes. */ + +typedef struct pcre2_serialized_data { + uint32_t magic; + uint32_t version; + uint32_t config; + int32_t number_of_codes; +} pcre2_serialized_data; + + + +/* ----------------- Items that need PCRE2_CODE_UNIT_WIDTH ----------------- */ + +/* When this file is included by pcre2test, PCRE2_CODE_UNIT_WIDTH is defined as +0, so the following items are omitted. */ + +#if defined PCRE2_CODE_UNIT_WIDTH && PCRE2_CODE_UNIT_WIDTH != 0 + +/* EBCDIC is supported only for the 8-bit library. */ + +#if defined EBCDIC && PCRE2_CODE_UNIT_WIDTH != 8 +#error EBCDIC is not supported for the 16-bit or 32-bit libraries +#endif + +/* This is the largest non-UTF code point. */ + +#define MAX_NON_UTF_CHAR (0xffffffffU >> (32 - PCRE2_CODE_UNIT_WIDTH)) + +/* Internal shared data tables and variables. These are used by more than one +of the exported public functions. They have to be "external" in the C sense, +but are not part of the PCRE2 public API. Although the data for some of them is +identical in all libraries, they must have different names so that multiple +libraries can be simultaneously linked to a single application. However, UTF-8 +tables are needed only when compiling the 8-bit library. */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +extern const int PRIV(utf8_table1)[]; +extern const int PRIV(utf8_table1_size); +extern const int PRIV(utf8_table2)[]; +extern const int PRIV(utf8_table3)[]; +extern const uint8_t PRIV(utf8_table4)[]; +#endif + +#define _pcre2_OP_lengths PCRE2_SUFFIX(_pcre2_OP_lengths_) +#define _pcre2_callout_end_delims PCRE2_SUFFIX(_pcre2_callout_end_delims_) +#define _pcre2_callout_start_delims PCRE2_SUFFIX(_pcre2_callout_start_delims_) +#define _pcre2_default_compile_context PCRE2_SUFFIX(_pcre2_default_compile_context_) +#define _pcre2_default_match_context PCRE2_SUFFIX(_pcre2_default_match_context_) +#define _pcre2_default_tables PCRE2_SUFFIX(_pcre2_default_tables_) +#define _pcre2_hspace_list PCRE2_SUFFIX(_pcre2_hspace_list_) +#define _pcre2_vspace_list PCRE2_SUFFIX(_pcre2_vspace_list_) +#define _pcre2_ucd_caseless_sets PCRE2_SUFFIX(_pcre2_ucd_caseless_sets_) +#define _pcre2_ucd_records PCRE2_SUFFIX(_pcre2_ucd_records_) +#define _pcre2_ucd_stage1 PCRE2_SUFFIX(_pcre2_ucd_stage1_) +#define _pcre2_ucd_stage2 PCRE2_SUFFIX(_pcre2_ucd_stage2_) +#define _pcre2_ucp_gbtable PCRE2_SUFFIX(_pcre2_ucp_gbtable_) +#define _pcre2_ucp_gentype PCRE2_SUFFIX(_pcre2_ucp_gentype_) +#define _pcre2_ucp_typerange PCRE2_SUFFIX(_pcre2_ucp_typerange_) +#define _pcre2_unicode_version PCRE2_SUFFIX(_pcre2_unicode_version_) +#define _pcre2_utt PCRE2_SUFFIX(_pcre2_utt_) +#define _pcre2_utt_names PCRE2_SUFFIX(_pcre2_utt_names_) +#define _pcre2_utt_size PCRE2_SUFFIX(_pcre2_utt_size_) + +extern const uint8_t PRIV(OP_lengths)[]; +extern const uint32_t PRIV(callout_end_delims)[]; +extern const uint32_t PRIV(callout_start_delims)[]; +extern const pcre2_compile_context PRIV(default_compile_context); +extern const pcre2_match_context PRIV(default_match_context); +extern const uint8_t PRIV(default_tables)[]; +extern const uint32_t PRIV(hspace_list)[]; +extern const uint32_t PRIV(vspace_list)[]; +extern const uint32_t PRIV(ucd_caseless_sets)[]; +extern const ucd_record PRIV(ucd_records)[]; +extern const uint8_t PRIV(ucd_stage1)[]; +extern const uint16_t PRIV(ucd_stage2)[]; +extern const uint32_t PRIV(ucp_gbtable)[]; +extern const uint32_t PRIV(ucp_gentype)[]; +#ifdef SUPPORT_JIT +extern const int PRIV(ucp_typerange)[]; +#endif +extern const char *PRIV(unicode_version); +extern const ucp_type_table PRIV(utt)[]; +extern const char PRIV(utt_names)[]; +extern const size_t PRIV(utt_size); + +/* Mode-dependent macros and hidden and private structures are defined in a +separate file so that pcre2test can include them at all supported widths. When +compiling the library, PCRE2_CODE_UNIT_WIDTH will be defined, and we can +include them at the appropriate width, after setting up suffix macros for the +private structures. */ + +#define branch_chain PCRE2_SUFFIX(branch_chain_) +#define compile_block PCRE2_SUFFIX(compile_block_) +#define dfa_match_block PCRE2_SUFFIX(dfa_match_block_) +#define match_block PCRE2_SUFFIX(match_block_) +#define named_group PCRE2_SUFFIX(named_group_) + +#include "pcre2_intmodedep.h" + +/* Private "external" functions. These are internal functions that are called +from modules other than the one in which they are defined. They have to be +"external" in the C sense, but are not part of the PCRE public API. They are +not referenced from pcre2test, and must not be defined when no code unit width +is available. */ + +#define _pcre2_auto_possessify PCRE2_SUFFIX(_pcre2_auto_possessify_) +#define _pcre2_check_escape PCRE2_SUFFIX(_pcre2_check_escape_) +#define _pcre2_find_bracket PCRE2_SUFFIX(_pcre2_find_bracket_) +#define _pcre2_is_newline PCRE2_SUFFIX(_pcre2_is_newline_) +#define _pcre2_jit_free_rodata PCRE2_SUFFIX(_pcre2_jit_free_rodata_) +#define _pcre2_jit_free PCRE2_SUFFIX(_pcre2_jit_free_) +#define _pcre2_jit_get_size PCRE2_SUFFIX(_pcre2_jit_get_size_) +#define _pcre2_jit_get_target PCRE2_SUFFIX(_pcre2_jit_get_target_) +#define _pcre2_memctl_malloc PCRE2_SUFFIX(_pcre2_memctl_malloc_) +#define _pcre2_ord2utf PCRE2_SUFFIX(_pcre2_ord2utf_) +#define _pcre2_strcmp PCRE2_SUFFIX(_pcre2_strcmp_) +#define _pcre2_strcmp_c8 PCRE2_SUFFIX(_pcre2_strcmp_c8_) +#define _pcre2_strcpy_c8 PCRE2_SUFFIX(_pcre2_strcpy_c8_) +#define _pcre2_strlen PCRE2_SUFFIX(_pcre2_strlen_) +#define _pcre2_strncmp PCRE2_SUFFIX(_pcre2_strncmp_) +#define _pcre2_strncmp_c8 PCRE2_SUFFIX(_pcre2_strncmp_c8_) +#define _pcre2_study PCRE2_SUFFIX(_pcre2_study_) +#define _pcre2_valid_utf PCRE2_SUFFIX(_pcre2_valid_utf_) +#define _pcre2_was_newline PCRE2_SUFFIX(_pcre2_was_newline_) +#define _pcre2_xclass PCRE2_SUFFIX(_pcre2_xclass_) + +extern int _pcre2_auto_possessify(PCRE2_UCHAR *, BOOL, + const compile_block *); +extern int _pcre2_check_escape(PCRE2_SPTR *, PCRE2_SPTR, uint32_t *, + int *, uint32_t, BOOL, compile_block *); +extern PCRE2_SPTR _pcre2_find_bracket(PCRE2_SPTR, BOOL, int); +extern BOOL _pcre2_is_newline(PCRE2_SPTR, uint32_t, PCRE2_SPTR, + uint32_t *, BOOL); +extern void _pcre2_jit_free_rodata(void *, void *); +extern void _pcre2_jit_free(void *, pcre2_memctl *); +extern size_t _pcre2_jit_get_size(void *); +const char * _pcre2_jit_get_target(void); +extern void * _pcre2_memctl_malloc(size_t, pcre2_memctl *); +extern unsigned int _pcre2_ord2utf(uint32_t, PCRE2_UCHAR *); +extern int _pcre2_strcmp(PCRE2_SPTR, PCRE2_SPTR); +extern int _pcre2_strcmp_c8(PCRE2_SPTR, const char *); +extern PCRE2_SIZE _pcre2_strcpy_c8(PCRE2_UCHAR *, const char *); +extern PCRE2_SIZE _pcre2_strlen(PCRE2_SPTR); +extern int _pcre2_strncmp(PCRE2_SPTR, PCRE2_SPTR, size_t); +extern int _pcre2_strncmp_c8(PCRE2_SPTR, const char *, size_t); +extern int _pcre2_study(pcre2_real_code *); +extern int _pcre2_valid_utf(PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE *); +extern BOOL _pcre2_was_newline(PCRE2_SPTR, uint32_t, PCRE2_SPTR, + uint32_t *, BOOL); +extern BOOL _pcre2_xclass(uint32_t, PCRE2_SPTR, BOOL); +#endif /* PCRE2_CODE_UNIT_WIDTH */ + +/* End of pcre2_internal.h */ diff --git a/ProcessHacker/pcre/pcre2_intmodedep.h b/ProcessHacker/pcre/pcre2_intmodedep.h new file mode 100644 index 0000000..90b7959 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_intmodedep.h @@ -0,0 +1,852 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains mode-dependent macro and structure definitions. The +file is #included by pcre2_internal.h if PCRE2_CODE_UNIT_WIDTH is defined. +These mode-dependent items are kept in a separate file so that they can also be +#included multiple times for different code unit widths by pcre2test in order +to have access to the hidden structures at all supported widths. + +Some of the mode-dependent macros are required at different widths for +different parts of the pcre2test code (in particular, the included +pcre_printint.c file). We undefine them here so that they can be re-defined for +multiple inclusions. Not all of these are used in pcre2test, but it's easier +just to undefine them all. */ + +#undef ACROSSCHAR +#undef BACKCHAR +#undef BYTES2CU +#undef CU2BYTES +#undef FORWARDCHAR +#undef FORWARDCHARTEST +#undef GET +#undef GET2 +#undef GETCHAR +#undef GETCHARINC +#undef GETCHARINCTEST +#undef GETCHARLEN +#undef GETCHARLENTEST +#undef GETCHARTEST +#undef GET_EXTRALEN +#undef HAS_EXTRALEN +#undef IMM2_SIZE +#undef MAX_255 +#undef MAX_MARK +#undef MAX_PATTERN_SIZE +#undef MAX_UTF_SINGLE_CU +#undef NOT_FIRSTCU +#undef PUT +#undef PUT2 +#undef PUT2INC +#undef PUTCHAR +#undef PUTINC +#undef TABLE_GET + + + +/* -------------------------- MACROS ----------------------------- */ + +/* PCRE keeps offsets in its compiled code as at least 16-bit quantities +(always stored in big-endian order in 8-bit mode) by default. These are used, +for example, to link from the start of a subpattern to its alternatives and its +end. The use of 16 bits per offset limits the size of an 8-bit compiled regex +to around 64K, which is big enough for almost everybody. However, I received a +request for an even bigger limit. For this reason, and also to make the code +easier to maintain, the storing and loading of offsets from the compiled code +unit string is now handled by the macros that are defined here. + +The macros are controlled by the value of LINK_SIZE. This defaults to 2, but +values of 2 or 4 are also supported. */ + +/* ------------------- 8-bit support ------------------ */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 + +#if LINK_SIZE == 2 +#define PUT(a,n,d) \ + (a[n] = (d) >> 8), \ + (a[(n)+1] = (d) & 255) +#define GET(a,n) \ + (((a)[n] << 8) | (a)[(n)+1]) +#define MAX_PATTERN_SIZE (1 << 16) + +#elif LINK_SIZE == 3 +#define PUT(a,n,d) \ + (a[n] = (d) >> 16), \ + (a[(n)+1] = (d) >> 8), \ + (a[(n)+2] = (d) & 255) +#define GET(a,n) \ + (((a)[n] << 16) | ((a)[(n)+1] << 8) | (a)[(n)+2]) +#define MAX_PATTERN_SIZE (1 << 24) + +#elif LINK_SIZE == 4 +#define PUT(a,n,d) \ + (a[n] = (d) >> 24), \ + (a[(n)+1] = (d) >> 16), \ + (a[(n)+2] = (d) >> 8), \ + (a[(n)+3] = (d) & 255) +#define GET(a,n) \ + (((a)[n] << 24) | ((a)[(n)+1] << 16) | ((a)[(n)+2] << 8) | (a)[(n)+3]) +#define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */ + +#else +#error LINK_SIZE must be 2, 3, or 4 +#endif + + +/* ------------------- 16-bit support ------------------ */ + +#elif PCRE2_CODE_UNIT_WIDTH == 16 + +#if LINK_SIZE == 2 +#undef LINK_SIZE +#define LINK_SIZE 1 +#define PUT(a,n,d) \ + (a[n] = (d)) +#define GET(a,n) \ + (a[n]) +#define MAX_PATTERN_SIZE (1 << 16) + +#elif LINK_SIZE == 3 || LINK_SIZE == 4 +#undef LINK_SIZE +#define LINK_SIZE 2 +#define PUT(a,n,d) \ + (a[n] = (d) >> 16), \ + (a[(n)+1] = (d) & 65535) +#define GET(a,n) \ + (((a)[n] << 16) | (a)[(n)+1]) +#define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */ + +#else +#error LINK_SIZE must be 2, 3, or 4 +#endif + + +/* ------------------- 32-bit support ------------------ */ + +#elif PCRE2_CODE_UNIT_WIDTH == 32 +#undef LINK_SIZE +#define LINK_SIZE 1 +#define PUT(a,n,d) \ + (a[n] = (d)) +#define GET(a,n) \ + (a[n]) +#define MAX_PATTERN_SIZE (1 << 30) /* Keep it positive */ + +#else +#error Unsupported compiling mode +#endif + + +/* --------------- Other mode-specific macros ----------------- */ + +/* PCRE uses some other (at least) 16-bit quantities that do not change when +the size of offsets changes. There are used for repeat counts and for other +things such as capturing parenthesis numbers in back references. + +Define the number of code units required to hold a 16-bit count/offset, and +macros to load and store such a value. For reasons that I do not understand, +the expression in the 8-bit GET2 macro is treated by gcc as a signed +expression, even when a is declared as unsigned. It seems that any kind of +arithmetic results in a signed value. Hence the cast. */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define IMM2_SIZE 2 +#define GET2(a,n) (unsigned int)(((a)[n] << 8) | (a)[(n)+1]) +#define PUT2(a,n,d) a[n] = (d) >> 8, a[(n)+1] = (d) & 255 + +#else /* Code units are 16 or 32 bits */ +#define IMM2_SIZE 1 +#define GET2(a,n) a[n] +#define PUT2(a,n,d) a[n] = d +#endif + +/* Other macros that are different for 8-bit mode. The MAX_255 macro checks +whether its argument is less than 256. The maximum length of a MARK name must +fit in one code unit; currently it is set to 255 or 65535. The TABLE_GET macro +is used to access elements of tables containing exactly 256 items. When code +points can be greater than 255, a check is needed before accessing these +tables. */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define MAX_255(c) TRUE +#define MAX_MARK ((1u << 8) - 1) +#ifdef SUPPORT_UNICODE +#define SUPPORT_WIDE_CHARS +#endif /* SUPPORT_UNICODE */ +#define TABLE_GET(c, table, default) ((table)[c]) + +#else /* Code units are 16 or 32 bits */ +#define MAX_255(c) ((c) <= 255u) +#define MAX_MARK ((1u << 16) - 1) +#define SUPPORT_WIDE_CHARS +#define TABLE_GET(c, table, default) (MAX_255(c)? ((table)[c]):(default)) +#endif + + + +/* ----------------- Character-handling macros ----------------- */ + +/* There is a proposed future special "UTF-21" mode, in which only the lowest +21 bits of a 32-bit character are interpreted as UTF, with the remaining 11 +high-order bits available to the application for other uses. In preparation for +the future implementation of this mode, there are macros that load a data item +and, if in this special mode, mask it to 21 bits. These macros all have names +starting with UCHAR21. In all other modes, including the normal 32-bit +library, the macros all have the same simple definitions. When the new mode is +implemented, it is expected that these definitions will be varied appropriately +using #ifdef when compiling the library that supports the special mode. */ + +#define UCHAR21(eptr) (*(eptr)) +#define UCHAR21TEST(eptr) (*(eptr)) +#define UCHAR21INC(eptr) (*(eptr)++) +#define UCHAR21INCTEST(eptr) (*(eptr)++) + +/* When UTF encoding is being used, a character is no longer just a single +byte in 8-bit mode or a single short in 16-bit mode. The macros for character +handling generate simple sequences when used in the basic mode, and more +complicated ones for UTF characters. GETCHARLENTEST and other macros are not +used when UTF is not supported. To make sure they can never even appear when +UTF support is omitted, we don't even define them. */ + +#ifndef SUPPORT_UNICODE + +/* #define MAX_UTF_SINGLE_CU */ +/* #define HAS_EXTRALEN(c) */ +/* #define GET_EXTRALEN(c) */ +/* #define NOT_FIRSTCU(c) */ +#define GETCHAR(c, eptr) c = *eptr; +#define GETCHARTEST(c, eptr) c = *eptr; +#define GETCHARINC(c, eptr) c = *eptr++; +#define GETCHARINCTEST(c, eptr) c = *eptr++; +#define GETCHARLEN(c, eptr, len) c = *eptr; +#define PUTCHAR(c, p) (*p = c, 1) +/* #define GETCHARLENTEST(c, eptr, len) */ +/* #define BACKCHAR(eptr) */ +/* #define FORWARDCHAR(eptr) */ +/* #define FORWARCCHARTEST(eptr,end) */ +/* #define ACROSSCHAR(condition, eptr, action) */ + +#else /* SUPPORT_UNICODE */ + +/* ------------------- 8-bit support ------------------ */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define MAYBE_UTF_MULTI /* UTF chars may use multiple code units */ + +/* The largest UTF code point that can be encoded as a single code unit. */ + +#define MAX_UTF_SINGLE_CU 127 + +/* Tests whether the code point needs extra characters to decode. */ + +#define HAS_EXTRALEN(c) HASUTF8EXTRALEN(c) + +/* Returns with the additional number of characters if IS_MULTICHAR(c) is TRUE. +Otherwise it has an undefined behaviour. */ + +#define GET_EXTRALEN(c) (PRIV(utf8_table4)[(c) & 0x3f]) + +/* Returns TRUE, if the given value is not the first code unit of a UTF +sequence. */ + +#define NOT_FIRSTCU(c) (((c) & 0xc0) == 0x80) + +/* Get the next UTF-8 character, not advancing the pointer. This is called when +we know we are in UTF-8 mode. */ + +#define GETCHAR(c, eptr) \ + c = *eptr; \ + if (c >= 0xc0) GETUTF8(c, eptr); + +/* Get the next UTF-8 character, testing for UTF-8 mode, and not advancing the +pointer. */ + +#define GETCHARTEST(c, eptr) \ + c = *eptr; \ + if (utf && c >= 0xc0) GETUTF8(c, eptr); + +/* Get the next UTF-8 character, advancing the pointer. This is called when we +know we are in UTF-8 mode. */ + +#define GETCHARINC(c, eptr) \ + c = *eptr++; \ + if (c >= 0xc0) GETUTF8INC(c, eptr); + +/* Get the next character, testing for UTF-8 mode, and advancing the pointer. +This is called when we don't know if we are in UTF-8 mode. */ + +#define GETCHARINCTEST(c, eptr) \ + c = *eptr++; \ + if (utf && c >= 0xc0) GETUTF8INC(c, eptr); + +/* Get the next UTF-8 character, not advancing the pointer, incrementing length +if there are extra bytes. This is called when we know we are in UTF-8 mode. */ + +#define GETCHARLEN(c, eptr, len) \ + c = *eptr; \ + if (c >= 0xc0) GETUTF8LEN(c, eptr, len); + +/* Get the next UTF-8 character, testing for UTF-8 mode, not advancing the +pointer, incrementing length if there are extra bytes. This is called when we +do not know if we are in UTF-8 mode. */ + +#define GETCHARLENTEST(c, eptr, len) \ + c = *eptr; \ + if (utf && c >= 0xc0) GETUTF8LEN(c, eptr, len); + +/* If the pointer is not at the start of a character, move it back until +it is. This is called only in UTF-8 mode - we don't put a test within the macro +because almost all calls are already within a block of UTF-8 only code. */ + +#define BACKCHAR(eptr) while((*eptr & 0xc0) == 0x80) eptr-- + +/* Same as above, just in the other direction. */ +#define FORWARDCHAR(eptr) while((*eptr & 0xc0) == 0x80) eptr++ +#define FORWARDCHARTEST(eptr,end) while(eptr < end && (*eptr & 0xc0) == 0x80) eptr++ + +/* Same as above, but it allows a fully customizable form. */ +#define ACROSSCHAR(condition, eptr, action) \ + while((condition) && ((eptr) & 0xc0) == 0x80) action + +/* Deposit a character into memory, returning the number of code units. */ + +#define PUTCHAR(c, p) ((utf && c > MAX_UTF_SINGLE_CU)? \ + PRIV(ord2utf)(c,p) : (*p = c, 1)) + + +/* ------------------- 16-bit support ------------------ */ + +#elif PCRE2_CODE_UNIT_WIDTH == 16 +#define MAYBE_UTF_MULTI /* UTF chars may use multiple code units */ + +/* The largest UTF code point that can be encoded as a single code unit. */ + +#define MAX_UTF_SINGLE_CU 65535 + +/* Tests whether the code point needs extra characters to decode. */ + +#define HAS_EXTRALEN(c) (((c) & 0xfc00) == 0xd800) + +/* Returns with the additional number of characters if IS_MULTICHAR(c) is TRUE. +Otherwise it has an undefined behaviour. */ + +#define GET_EXTRALEN(c) 1 + +/* Returns TRUE, if the given value is not the first code unit of a UTF +sequence. */ + +#define NOT_FIRSTCU(c) (((c) & 0xfc00) == 0xdc00) + +/* Base macro to pick up the low surrogate of a UTF-16 character, not +advancing the pointer. */ + +#define GETUTF16(c, eptr) \ + { c = (((c & 0x3ff) << 10) | (eptr[1] & 0x3ff)) + 0x10000; } + +/* Get the next UTF-16 character, not advancing the pointer. This is called when +we know we are in UTF-16 mode. */ + +#define GETCHAR(c, eptr) \ + c = *eptr; \ + if ((c & 0xfc00) == 0xd800) GETUTF16(c, eptr); + +/* Get the next UTF-16 character, testing for UTF-16 mode, and not advancing the +pointer. */ + +#define GETCHARTEST(c, eptr) \ + c = *eptr; \ + if (utf && (c & 0xfc00) == 0xd800) GETUTF16(c, eptr); + +/* Base macro to pick up the low surrogate of a UTF-16 character, advancing +the pointer. */ + +#define GETUTF16INC(c, eptr) \ + { c = (((c & 0x3ff) << 10) | (*eptr++ & 0x3ff)) + 0x10000; } + +/* Get the next UTF-16 character, advancing the pointer. This is called when we +know we are in UTF-16 mode. */ + +#define GETCHARINC(c, eptr) \ + c = *eptr++; \ + if ((c & 0xfc00) == 0xd800) GETUTF16INC(c, eptr); + +/* Get the next character, testing for UTF-16 mode, and advancing the pointer. +This is called when we don't know if we are in UTF-16 mode. */ + +#define GETCHARINCTEST(c, eptr) \ + c = *eptr++; \ + if (utf && (c & 0xfc00) == 0xd800) GETUTF16INC(c, eptr); + +/* Base macro to pick up the low surrogate of a UTF-16 character, not +advancing the pointer, incrementing the length. */ + +#define GETUTF16LEN(c, eptr, len) \ + { c = (((c & 0x3ff) << 10) | (eptr[1] & 0x3ff)) + 0x10000; len++; } + +/* Get the next UTF-16 character, not advancing the pointer, incrementing +length if there is a low surrogate. This is called when we know we are in +UTF-16 mode. */ + +#define GETCHARLEN(c, eptr, len) \ + c = *eptr; \ + if ((c & 0xfc00) == 0xd800) GETUTF16LEN(c, eptr, len); + +/* Get the next UTF-816character, testing for UTF-16 mode, not advancing the +pointer, incrementing length if there is a low surrogate. This is called when +we do not know if we are in UTF-16 mode. */ + +#define GETCHARLENTEST(c, eptr, len) \ + c = *eptr; \ + if (utf && (c & 0xfc00) == 0xd800) GETUTF16LEN(c, eptr, len); + +/* If the pointer is not at the start of a character, move it back until +it is. This is called only in UTF-16 mode - we don't put a test within the +macro because almost all calls are already within a block of UTF-16 only +code. */ + +#define BACKCHAR(eptr) if ((*eptr & 0xfc00) == 0xdc00) eptr-- + +/* Same as above, just in the other direction. */ +#define FORWARDCHAR(eptr) if ((*eptr & 0xfc00) == 0xdc00) eptr++ +#define FORWARDCHARTEST(eptr,end) if (eptr < end && (*eptr & 0xfc00) == 0xdc00) eptr++ + +/* Same as above, but it allows a fully customizable form. */ +#define ACROSSCHAR(condition, eptr, action) \ + if ((condition) && ((eptr) & 0xfc00) == 0xdc00) action + +/* Deposit a character into memory, returning the number of code units. */ + +#define PUTCHAR(c, p) ((utf && c > MAX_UTF_SINGLE_CU)? \ + PRIV(ord2utf)(c,p) : (*p = c, 1)) + + +/* ------------------- 32-bit support ------------------ */ + +#else + +/* These are trivial for the 32-bit library, since all UTF-32 characters fit +into one PCRE2_UCHAR unit. */ + +#define MAX_UTF_SINGLE_CU (0x10ffffu) +#define HAS_EXTRALEN(c) (0) +#define GET_EXTRALEN(c) (0) +#define NOT_FIRSTCU(c) (0) + +/* Get the next UTF-32 character, not advancing the pointer. This is called when +we know we are in UTF-32 mode. */ + +#define GETCHAR(c, eptr) \ + c = *(eptr); + +/* Get the next UTF-32 character, testing for UTF-32 mode, and not advancing the +pointer. */ + +#define GETCHARTEST(c, eptr) \ + c = *(eptr); + +/* Get the next UTF-32 character, advancing the pointer. This is called when we +know we are in UTF-32 mode. */ + +#define GETCHARINC(c, eptr) \ + c = *((eptr)++); + +/* Get the next character, testing for UTF-32 mode, and advancing the pointer. +This is called when we don't know if we are in UTF-32 mode. */ + +#define GETCHARINCTEST(c, eptr) \ + c = *((eptr)++); + +/* Get the next UTF-32 character, not advancing the pointer, not incrementing +length (since all UTF-32 is of length 1). This is called when we know we are in +UTF-32 mode. */ + +#define GETCHARLEN(c, eptr, len) \ + GETCHAR(c, eptr) + +/* Get the next UTF-32character, testing for UTF-32 mode, not advancing the +pointer, not incrementing the length (since all UTF-32 is of length 1). +This is called when we do not know if we are in UTF-32 mode. */ + +#define GETCHARLENTEST(c, eptr, len) \ + GETCHARTEST(c, eptr) + +/* If the pointer is not at the start of a character, move it back until +it is. This is called only in UTF-32 mode - we don't put a test within the +macro because almost all calls are already within a block of UTF-32 only +code. + +These are all no-ops since all UTF-32 characters fit into one pcre_uchar. */ + +#define BACKCHAR(eptr) do { } while (0) + +/* Same as above, just in the other direction. */ + +#define FORWARDCHAR(eptr) do { } while (0) +#define FORWARDCHARTEST(eptr,end) do { } while (0) + +/* Same as above, but it allows a fully customizable form. */ + +#define ACROSSCHAR(condition, eptr, action) do { } while (0) + +/* Deposit a character into memory, returning the number of code units. */ + +#define PUTCHAR(c, p) (*p = c, 1) + +#endif /* UTF-32 character handling */ +#endif /* SUPPORT_UNICODE */ + + +/* Mode-dependent macros that have the same definition in all modes. */ + +#define CU2BYTES(x) ((x)*((PCRE2_CODE_UNIT_WIDTH/8))) +#define BYTES2CU(x) ((x)/((PCRE2_CODE_UNIT_WIDTH/8))) +#define PUTINC(a,n,d) PUT(a,n,d), a += LINK_SIZE +#define PUT2INC(a,n,d) PUT2(a,n,d), a += IMM2_SIZE + + +/* ----------------------- HIDDEN STRUCTURES ----------------------------- */ + +/* NOTE: All these structures *must* start with a pcre2_memctl structure. The +code that uses them is simpler because it assumes this. */ + +/* The real general context structure. At present it holds only data for custom +memory control. */ + +typedef struct pcre2_real_general_context { + pcre2_memctl memctl; +} pcre2_real_general_context; + +/* The real compile context structure */ + +typedef struct pcre2_real_compile_context { + pcre2_memctl memctl; + int (*stack_guard)(uint32_t, void *); + void *stack_guard_data; + const uint8_t *tables; + PCRE2_SIZE max_pattern_length; + uint16_t bsr_convention; + uint16_t newline_convention; + uint32_t parens_nest_limit; +} pcre2_real_compile_context; + +/* The real match context structure. */ + +typedef struct pcre2_real_match_context { + pcre2_memctl memctl; +#ifdef HEAP_MATCH_RECURSE + pcre2_memctl stack_memctl; +#endif +#ifdef SUPPORT_JIT + pcre2_jit_callback jit_callback; + void *jit_callback_data; +#endif + int (*callout)(pcre2_callout_block *, void *); + void *callout_data; + PCRE2_SIZE offset_limit; + uint32_t match_limit; + uint32_t recursion_limit; +} pcre2_real_match_context; + +/* The real compiled code structure. The type for the blocksize field is +defined specially because it is required in pcre2_serialize_decode() when +copying the size from possibly unaligned memory into a variable of the same +type. Use a macro rather than a typedef to avoid compiler warnings when this +file is included multiple times by pcre2test. LOOKBEHIND_MAX specifies the +largest lookbehind that is supported. (OP_REVERSE in a pattern has a 16-bit +argument in 8-bit and 16-bit modes, so we need no more than a 16-bit field +here.) */ + +#undef CODE_BLOCKSIZE_TYPE +#define CODE_BLOCKSIZE_TYPE size_t + +#undef LOOKBEHIND_MAX +#define LOOKBEHIND_MAX UINT16_MAX + +typedef struct pcre2_real_code { + pcre2_memctl memctl; /* Memory control fields */ + const uint8_t *tables; /* The character tables */ + void *executable_jit; /* Pointer to JIT code */ + uint8_t start_bitmap[32]; /* Bitmap for starting code unit < 256 */ + CODE_BLOCKSIZE_TYPE blocksize; /* Total (bytes) that was malloc-ed */ + uint32_t magic_number; /* Paranoid and endianness check */ + uint32_t compile_options; /* Options passed to pcre2_compile() */ + uint32_t overall_options; /* Options after processing the pattern */ + uint32_t flags; /* Various state flags */ + uint32_t limit_match; /* Limit set in the pattern */ + uint32_t limit_recursion; /* Limit set in the pattern */ + uint32_t first_codeunit; /* Starting code unit */ + uint32_t last_codeunit; /* This codeunit must be seen */ + uint16_t bsr_convention; /* What \R matches */ + uint16_t newline_convention; /* What is a newline? */ + uint16_t max_lookbehind; /* Longest lookbehind (characters) */ + uint16_t minlength; /* Minimum length of match */ + uint16_t top_bracket; /* Highest numbered group */ + uint16_t top_backref; /* Highest numbered back reference */ + uint16_t name_entry_size; /* Size (code units) of table entries */ + uint16_t name_count; /* Number of name entries in the table */ +} pcre2_real_code; + +/* The real match data structure. */ + +typedef struct pcre2_real_match_data { + pcre2_memctl memctl; + const pcre2_real_code *code; /* The pattern used for the match */ + PCRE2_SPTR subject; /* The subject that was matched */ + PCRE2_SPTR mark; /* Pointer to last mark */ + PCRE2_SIZE leftchar; /* Offset to leftmost code unit */ + PCRE2_SIZE rightchar; /* Offset to rightmost code unit */ + PCRE2_SIZE startchar; /* Offset to starting code unit */ + uint16_t matchedby; /* Type of match (normal, JIT, DFA) */ + uint16_t oveccount; /* Number of pairs */ + int rc; /* The return code from the match */ + PCRE2_SIZE ovector[1]; /* The first field */ +} pcre2_real_match_data; + + +/* ----------------------- PRIVATE STRUCTURES ----------------------------- */ + +/* These structures are not needed for pcre2test. */ + +#ifndef PCRE2_PCRE2TEST + +/* Structure for checking for mutual recursion when scanning compiled code. */ + +typedef struct recurse_check { + struct recurse_check *prev; + PCRE2_SPTR group; +} recurse_check; + +/* Structure for building a cache when filling in recursion offsets. */ + +typedef struct recurse_cache { + PCRE2_SPTR group; + int recno; +} recurse_cache; + +/* Structure for maintaining a chain of pointers to the currently incomplete +branches, for testing for left recursion while compiling. */ + +typedef struct branch_chain { + struct branch_chain *outer; + PCRE2_UCHAR *current_branch; +} branch_chain; + +/* Structure for building a list of named groups during the first pass of +compiling. */ + +typedef struct named_group { + PCRE2_SPTR name; /* Points to the name in the pattern */ + uint32_t number; /* Group number */ + uint16_t length; /* Length of the name */ + uint16_t isdup; /* TRUE if a duplicate */ +} named_group; + +/* Structure for passing "static" information around between the functions +doing the compiling, so that they are thread-safe. */ + +typedef struct compile_block { + pcre2_real_compile_context *cx; /* Points to the compile context */ + const uint8_t *lcc; /* Points to lower casing table */ + const uint8_t *fcc; /* Points to case-flipping table */ + const uint8_t *cbits; /* Points to character type table */ + const uint8_t *ctypes; /* Points to table of type maps */ + PCRE2_SPTR start_workspace; /* The start of working space */ + PCRE2_SPTR start_code; /* The start of the compiled code */ + PCRE2_SPTR start_pattern; /* The start of the pattern */ + PCRE2_SPTR end_pattern; /* The end of the pattern */ + PCRE2_SPTR nestptr[2]; /* Pointer(s) saved for string substitution */ + PCRE2_UCHAR *name_table; /* The name/number table */ + size_t workspace_size; /* Size of workspace */ + uint16_t names_found; /* Number of entries so far */ + uint16_t name_entry_size; /* Size of each entry */ + open_capitem *open_caps; /* Chain of open capture items */ + named_group *named_groups; /* Points to vector in pre-compile */ + uint32_t named_group_list_size; /* Number of entries in the list */ + uint32_t external_options; /* External (initial) options */ + uint32_t external_flags; /* External flag bits to be set */ + uint32_t bracount; /* Count of capturing parens as we compile */ + uint32_t final_bracount; /* Saved value after first pass */ + uint32_t *groupinfo; /* Group info vector */ + uint32_t top_backref; /* Maximum back reference */ + uint32_t backref_map; /* Bitmap of low back refs */ + uint32_t nltype; /* Newline type */ + uint32_t nllen; /* Newline string length */ + PCRE2_UCHAR nl[4]; /* Newline string when fixed length */ + int max_lookbehind; /* Maximum lookbehind (characters) */ + int parens_depth; /* Depth of nested parentheses */ + int assert_depth; /* Depth of nested assertions */ + int req_varyopt; /* "After variable item" flag for reqbyte */ + BOOL had_accept; /* (*ACCEPT) encountered */ + BOOL had_pruneorskip; /* (*PRUNE) or (*SKIP) encountered */ + BOOL had_recurse; /* Had a recursion or subroutine call */ + BOOL check_lookbehind; /* Lookbehinds need later checking */ + BOOL dupnames; /* Duplicate names exist */ + BOOL iscondassert; /* Next assert is a condition */ +} compile_block; + +/* Structure for keeping the properties of the in-memory stack used +by the JIT matcher. */ + +typedef struct pcre2_real_jit_stack { + pcre2_memctl memctl; + void* stack; +} pcre2_real_jit_stack; + +/* Structure for keeping a chain of heap blocks used for saving ovectors +during pattern recursion when the ovector is larger than can be saved on +the system stack. */ + +typedef struct ovecsave_frame { + struct ovecsave_frame *next; /* Next frame on free chain */ + PCRE2_SIZE saved_ovec[1]; /* First vector element */ +} ovecsave_frame; + +/* Structure for items in a linked list that represents an explicit recursive +call within the pattern; used by pcre_match(). */ + +typedef struct recursion_info { + struct recursion_info *prevrec; /* Previous recursion record (or NULL) */ + unsigned int group_num; /* Number of group that was called */ + PCRE2_SIZE *ovec_save; /* Pointer to saved ovector frame */ + uint32_t saved_capture_last; /* Last capture number */ + PCRE2_SPTR subject_position; /* Position at start of recursion */ +} recursion_info; + +/* A similar structure for pcre_dfa_match(). */ + +typedef struct dfa_recursion_info { + struct dfa_recursion_info *prevrec; + PCRE2_SPTR subject_position; + uint32_t group_num; +} dfa_recursion_info; + +/* Structure for building a chain of data for holding the values of the subject +pointer at the start of each subpattern, so as to detect when an empty string +has been matched by a subpattern - to break infinite loops; used by +pcre2_match(). */ + +typedef struct eptrblock { + struct eptrblock *epb_prev; + PCRE2_SPTR epb_saved_eptr; +} eptrblock; + +/* Structure for passing "static" information around between the functions +doing traditional NFA matching (pcre2_match() and friends). */ + +typedef struct match_block { + pcre2_memctl memctl; /* For general use */ +#ifdef HEAP_MATCH_RECURSE + pcre2_memctl stack_memctl; /* For "stack" frames */ +#endif + uint32_t match_call_count; /* As it says */ + uint32_t match_limit; /* As it says */ + uint32_t match_limit_recursion; /* As it says */ + BOOL hitend; /* Hit the end of the subject at some point */ + BOOL hasthen; /* Pattern contains (*THEN) */ + const uint8_t *lcc; /* Points to lower casing table */ + const uint8_t *fcc; /* Points to case-flipping table */ + const uint8_t *ctypes; /* Points to table of type maps */ + PCRE2_SIZE *ovector; /* Pointer to the offset vector */ + PCRE2_SIZE offset_end; /* One past the end */ + PCRE2_SIZE offset_max; /* The maximum usable for return data */ + PCRE2_SIZE start_offset; /* The start offset value */ + PCRE2_SIZE end_offset_top; /* Highwater mark at end of match */ + uint16_t partial; /* PARTIAL options */ + uint16_t bsr_convention; /* \R interpretation */ + uint16_t name_count; /* Number of names in name table */ + uint16_t name_entry_size; /* Size of entry in names table */ + PCRE2_SPTR name_table; /* Table of group names */ + PCRE2_SPTR start_code; /* For use when recursing */ + PCRE2_SPTR start_subject; /* Start of the subject string */ + PCRE2_SPTR end_subject; /* End of the subject string */ + PCRE2_SPTR start_match_ptr; /* Start of matched string */ + PCRE2_SPTR end_match_ptr; /* Subject position at end match */ + PCRE2_SPTR start_used_ptr; /* Earliest consulted character */ + PCRE2_SPTR last_used_ptr; /* Latest consulted character */ + PCRE2_SPTR mark; /* Mark pointer to pass back on success */ + PCRE2_SPTR nomatch_mark; /* Mark pointer to pass back on failure */ + PCRE2_SPTR once_target; /* Where to back up to for atomic groups */ + uint32_t moptions; /* Match options */ + uint32_t poptions; /* Pattern options */ + uint32_t capture_last; /* Most recent capture number + overflow flag */ + uint32_t skip_arg_count; /* For counting SKIP_ARGs */ + uint32_t ignore_skip_arg; /* For re-run when SKIP arg name not found */ + uint32_t match_function_type; /* Set for certain special calls of match() */ + uint32_t nltype; /* Newline type */ + uint32_t nllen; /* Newline string length */ + PCRE2_UCHAR nl[4]; /* Newline string when fixed */ + eptrblock *eptrchain; /* Chain of eptrblocks for tail recursions */ + recursion_info *recursive; /* Linked list of recursion data */ + ovecsave_frame *ovecsave_chain; /* Linked list of free ovecsave blocks */ + void *callout_data; /* To pass back to callouts */ + int (*callout)(pcre2_callout_block *,void *); /* Callout function or NULL */ +#ifdef HEAP_MATCH_RECURSE + void *match_frames_base; /* For remembering malloc'd frames */ +#endif +} match_block; + +/* A similar structure is used for the same purpose by the DFA matching +functions. */ + +typedef struct dfa_match_block { + pcre2_memctl memctl; /* For general use */ + PCRE2_SPTR start_code; /* Start of the compiled pattern */ + PCRE2_SPTR start_subject ; /* Start of the subject string */ + PCRE2_SPTR end_subject; /* End of subject string */ + PCRE2_SPTR start_used_ptr; /* Earliest consulted character */ + PCRE2_SPTR last_used_ptr; /* Latest consulted character */ + const uint8_t *tables; /* Character tables */ + PCRE2_SIZE start_offset; /* The start offset value */ + uint32_t moptions; /* Match options */ + uint32_t poptions; /* Pattern options */ + uint32_t nltype; /* Newline type */ + uint32_t nllen; /* Newline string length */ + PCRE2_UCHAR nl[4]; /* Newline string when fixed */ + uint16_t bsr_convention; /* \R interpretation */ + void *callout_data; /* To pass back to callouts */ + int (*callout)(pcre2_callout_block *,void *); /* Callout function or NULL */ + dfa_recursion_info *recursive; /* Linked list of recursion data */ +} dfa_match_block; + +#endif /* PCRE2_PCRE2TEST */ + +/* End of pcre2_intmodedep.h */ diff --git a/ProcessHacker/pcre/pcre2_jit_compile.c b/ProcessHacker/pcre/pcre2_jit_compile.c new file mode 100644 index 0000000..660454d --- /dev/null +++ b/ProcessHacker/pcre/pcre2_jit_compile.c @@ -0,0 +1,11503 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + +#ifdef SUPPORT_JIT + +/* All-in-one: Since we use the JIT compiler only from here, +we just include it. This way we don't need to touch the build +system files. */ + +#define SLJIT_CONFIG_AUTO 1 +#define SLJIT_CONFIG_STATIC 1 +#define SLJIT_VERBOSE 0 + +#ifdef PCRE2_DEBUG +#define SLJIT_DEBUG 1 +#else +#define SLJIT_DEBUG 0 +#endif + +#define SLJIT_MALLOC(size, allocator_data) pcre2_jit_malloc(size, allocator_data) +#define SLJIT_FREE(ptr, allocator_data) pcre2_jit_free(ptr, allocator_data) + +static void * pcre2_jit_malloc(size_t size, void *allocator_data) +{ +pcre2_memctl *allocator = ((pcre2_memctl*)allocator_data); +return allocator->malloc(size, allocator->memory_data); +} + +static void pcre2_jit_free(void *ptr, void *allocator_data) +{ +pcre2_memctl *allocator = ((pcre2_memctl*)allocator_data); +allocator->free(ptr, allocator->memory_data); +} + +#include "sljit/sljitLir.c" + +#if defined SLJIT_CONFIG_UNSUPPORTED && SLJIT_CONFIG_UNSUPPORTED +#error Unsupported architecture +#endif + +/* Defines for debugging purposes. */ + +/* 1 - Use unoptimized capturing brackets. + 2 - Enable capture_last_ptr (includes option 1). */ +/* #define DEBUG_FORCE_UNOPTIMIZED_CBRAS 2 */ + +/* 1 - Always have a control head. */ +/* #define DEBUG_FORCE_CONTROL_HEAD 1 */ + +/* Allocate memory for the regex stack on the real machine stack. +Fast, but limited size. */ +#define MACHINE_STACK_SIZE 32768 + +/* Growth rate for stack allocated by the OS. Should be the multiply +of page size. */ +#define STACK_GROWTH_RATE 8192 + +/* Enable to check that the allocation could destroy temporaries. */ +#if defined SLJIT_DEBUG && SLJIT_DEBUG +#define DESTROY_REGISTERS 1 +#endif + +/* +Short summary about the backtracking mechanism empolyed by the jit code generator: + +The code generator follows the recursive nature of the PERL compatible regular +expressions. The basic blocks of regular expressions are condition checkers +whose execute different commands depending on the result of the condition check. +The relationship between the operators can be horizontal (concatenation) and +vertical (sub-expression) (See struct backtrack_common for more details). + + 'ab' - 'a' and 'b' regexps are concatenated + 'a+' - 'a' is the sub-expression of the '+' operator + +The condition checkers are boolean (true/false) checkers. Machine code is generated +for the checker itself and for the actions depending on the result of the checker. +The 'true' case is called as the matching path (expected path), and the other is called as +the 'backtrack' path. Branch instructions are expesive for all CPUs, so we avoid taken +branches on the matching path. + + Greedy star operator (*) : + Matching path: match happens. + Backtrack path: match failed. + Non-greedy star operator (*?) : + Matching path: no need to perform a match. + Backtrack path: match is required. + +The following example shows how the code generated for a capturing bracket +with two alternatives. Let A, B, C, D are arbirary regular expressions, and +we have the following regular expression: + + A(B|C)D + +The generated code will be the following: + + A matching path + '(' matching path (pushing arguments to the stack) + B matching path + ')' matching path (pushing arguments to the stack) + D matching path + return with successful match + + D backtrack path + ')' backtrack path (If we arrived from "C" jump to the backtrack of "C") + B backtrack path + C expected path + jump to D matching path + C backtrack path + A backtrack path + + Notice, that the order of backtrack code paths are the opposite of the fast + code paths. In this way the topmost value on the stack is always belong + to the current backtrack code path. The backtrack path must check + whether there is a next alternative. If so, it needs to jump back to + the matching path eventually. Otherwise it needs to clear out its own stack + frame and continue the execution on the backtrack code paths. +*/ + +/* +Saved stack frames: + +Atomic blocks and asserts require reloading the values of private data +when the backtrack mechanism performed. Because of OP_RECURSE, the data +are not necessarly known in compile time, thus we need a dynamic restore +mechanism. + +The stack frames are stored in a chain list, and have the following format: +([ capturing bracket offset ][ start value ][ end value ])+ ... [ 0 ] [ previous head ] + +Thus we can restore the private data to a particular point in the stack. +*/ + +typedef struct jit_arguments { + /* Pointers first. */ + struct sljit_stack *stack; + PCRE2_SPTR str; + PCRE2_SPTR begin; + PCRE2_SPTR end; + pcre2_match_data *match_data; + PCRE2_SPTR startchar_ptr; + PCRE2_UCHAR *mark_ptr; + int (*callout)(pcre2_callout_block *, void *); + void *callout_data; + /* Everything else after. */ + sljit_uw offset_limit; + sljit_ui limit_match; + uint32_t oveccount; + uint32_t options; +} jit_arguments; + +#define JIT_NUMBER_OF_COMPILE_MODES 3 + +typedef struct executable_functions { + void *executable_funcs[JIT_NUMBER_OF_COMPILE_MODES]; + void *read_only_data_heads[JIT_NUMBER_OF_COMPILE_MODES]; + sljit_uw executable_sizes[JIT_NUMBER_OF_COMPILE_MODES]; + sljit_ui top_bracket; + sljit_ui limit_match; +} executable_functions; + +typedef struct jump_list { + struct sljit_jump *jump; + struct jump_list *next; +} jump_list; + +typedef struct stub_list { + struct sljit_jump *start; + struct sljit_label *quit; + struct stub_list *next; +} stub_list; + +typedef struct label_addr_list { + struct sljit_label *label; + sljit_uw *update_addr; + struct label_addr_list *next; +} label_addr_list; + +enum frame_types { + no_frame = -1, + no_stack = -2 +}; + +enum control_types { + type_mark = 0, + type_then_trap = 1 +}; + +typedef int (SLJIT_CALL *jit_function)(jit_arguments *args); + +/* The following structure is the key data type for the recursive +code generator. It is allocated by compile_matchingpath, and contains +the arguments for compile_backtrackingpath. Must be the first member +of its descendants. */ +typedef struct backtrack_common { + /* Concatenation stack. */ + struct backtrack_common *prev; + jump_list *nextbacktracks; + /* Internal stack (for component operators). */ + struct backtrack_common *top; + jump_list *topbacktracks; + /* Opcode pointer. */ + PCRE2_SPTR cc; +} backtrack_common; + +typedef struct assert_backtrack { + backtrack_common common; + jump_list *condfailed; + /* Less than 0 if a frame is not needed. */ + int framesize; + /* Points to our private memory word on the stack. */ + int private_data_ptr; + /* For iterators. */ + struct sljit_label *matchingpath; +} assert_backtrack; + +typedef struct bracket_backtrack { + backtrack_common common; + /* Where to coninue if an alternative is successfully matched. */ + struct sljit_label *alternative_matchingpath; + /* For rmin and rmax iterators. */ + struct sljit_label *recursive_matchingpath; + /* For greedy ? operator. */ + struct sljit_label *zero_matchingpath; + /* Contains the branches of a failed condition. */ + union { + /* Both for OP_COND, OP_SCOND. */ + jump_list *condfailed; + assert_backtrack *assert; + /* For OP_ONCE. Less than 0 if not needed. */ + int framesize; + } u; + /* Points to our private memory word on the stack. */ + int private_data_ptr; +} bracket_backtrack; + +typedef struct bracketpos_backtrack { + backtrack_common common; + /* Points to our private memory word on the stack. */ + int private_data_ptr; + /* Reverting stack is needed. */ + int framesize; + /* Allocated stack size. */ + int stacksize; +} bracketpos_backtrack; + +typedef struct braminzero_backtrack { + backtrack_common common; + struct sljit_label *matchingpath; +} braminzero_backtrack; + +typedef struct char_iterator_backtrack { + backtrack_common common; + /* Next iteration. */ + struct sljit_label *matchingpath; + union { + jump_list *backtracks; + struct { + unsigned int othercasebit; + PCRE2_UCHAR chr; + BOOL enabled; + } charpos; + } u; +} char_iterator_backtrack; + +typedef struct ref_iterator_backtrack { + backtrack_common common; + /* Next iteration. */ + struct sljit_label *matchingpath; +} ref_iterator_backtrack; + +typedef struct recurse_entry { + struct recurse_entry *next; + /* Contains the function entry. */ + struct sljit_label *entry; + /* Collects the calls until the function is not created. */ + jump_list *calls; + /* Points to the starting opcode. */ + sljit_sw start; +} recurse_entry; + +typedef struct recurse_backtrack { + backtrack_common common; + BOOL inlined_pattern; +} recurse_backtrack; + +#define OP_THEN_TRAP OP_TABLE_LENGTH + +typedef struct then_trap_backtrack { + backtrack_common common; + /* If then_trap is not NULL, this structure contains the real + then_trap for the backtracking path. */ + struct then_trap_backtrack *then_trap; + /* Points to the starting opcode. */ + sljit_sw start; + /* Exit point for the then opcodes of this alternative. */ + jump_list *quit; + /* Frame size of the current alternative. */ + int framesize; +} then_trap_backtrack; + +#define MAX_RANGE_SIZE 4 + +typedef struct compiler_common { + /* The sljit ceneric compiler. */ + struct sljit_compiler *compiler; + /* First byte code. */ + PCRE2_SPTR start; + /* Maps private data offset to each opcode. */ + sljit_si *private_data_ptrs; + /* Chain list of read-only data ptrs. */ + void *read_only_data_head; + /* Tells whether the capturing bracket is optimized. */ + sljit_ub *optimized_cbracket; + /* Tells whether the starting offset is a target of then. */ + sljit_ub *then_offsets; + /* Current position where a THEN must jump. */ + then_trap_backtrack *then_trap; + /* Starting offset of private data for capturing brackets. */ + sljit_si cbra_ptr; + /* Output vector starting point. Must be divisible by 2. */ + sljit_si ovector_start; + /* Points to the starting character of the current match. */ + sljit_si start_ptr; + /* Last known position of the requested byte. */ + sljit_si req_char_ptr; + /* Head of the last recursion. */ + sljit_si recursive_head_ptr; + /* First inspected character for partial matching. + (Needed for avoiding zero length partial matches.) */ + sljit_si start_used_ptr; + /* Starting pointer for partial soft matches. */ + sljit_si hit_start; + /* Pointer of the match end position. */ + sljit_si match_end_ptr; + /* Points to the marked string. */ + sljit_si mark_ptr; + /* Recursive control verb management chain. */ + sljit_si control_head_ptr; + /* Points to the last matched capture block index. */ + sljit_si capture_last_ptr; + /* Fast forward skipping byte code pointer. */ + PCRE2_SPTR fast_forward_bc_ptr; + /* Locals used by fast fail optimization. */ + sljit_si fast_fail_start_ptr; + sljit_si fast_fail_end_ptr; + + /* Flipped and lower case tables. */ + const sljit_ub *fcc; + sljit_sw lcc; + /* Mode can be PCRE2_JIT_COMPLETE and others. */ + int mode; + /* TRUE, when minlength is greater than 0. */ + BOOL might_be_empty; + /* \K is found in the pattern. */ + BOOL has_set_som; + /* (*SKIP:arg) is found in the pattern. */ + BOOL has_skip_arg; + /* (*THEN) is found in the pattern. */ + BOOL has_then; + /* (*SKIP) or (*SKIP:arg) is found in lookbehind assertion. */ + BOOL has_skip_in_assert_back; + /* Currently in recurse or negative assert. */ + BOOL local_exit; + /* Currently in a positive assert. */ + BOOL positive_assert; + /* Newline control. */ + int nltype; + sljit_ui nlmax; + sljit_ui nlmin; + int newline; + int bsr_nltype; + sljit_ui bsr_nlmax; + sljit_ui bsr_nlmin; + /* Dollar endonly. */ + int endonly; + /* Tables. */ + sljit_sw ctypes; + /* Named capturing brackets. */ + PCRE2_SPTR name_table; + sljit_sw name_count; + sljit_sw name_entry_size; + + /* Labels and jump lists. */ + struct sljit_label *partialmatchlabel; + struct sljit_label *quit_label; + struct sljit_label *forced_quit_label; + struct sljit_label *accept_label; + struct sljit_label *ff_newline_shortcut; + stub_list *stubs; + label_addr_list *label_addrs; + recurse_entry *entries; + recurse_entry *currententry; + jump_list *partialmatch; + jump_list *quit; + jump_list *positive_assert_quit; + jump_list *forced_quit; + jump_list *accept; + jump_list *calllimit; + jump_list *stackalloc; + jump_list *revertframes; + jump_list *wordboundary; + jump_list *anynewline; + jump_list *hspace; + jump_list *vspace; + jump_list *casefulcmp; + jump_list *caselesscmp; + jump_list *reset_match; + BOOL unset_backref; + BOOL alt_circumflex; +#ifdef SUPPORT_UNICODE + BOOL utf; + BOOL use_ucp; + jump_list *getucd; +#if PCRE2_CODE_UNIT_WIDTH == 8 + jump_list *utfreadchar; + jump_list *utfreadchar16; + jump_list *utfreadtype8; +#endif +#endif /* SUPPORT_UNICODE */ +} compiler_common; + +/* For byte_sequence_compare. */ + +typedef struct compare_context { + int length; + int sourcereg; +#if defined SLJIT_UNALIGNED && SLJIT_UNALIGNED + int ucharptr; + union { + sljit_si asint; + sljit_uh asushort; +#if PCRE2_CODE_UNIT_WIDTH == 8 + sljit_ub asbyte; + sljit_ub asuchars[4]; +#elif PCRE2_CODE_UNIT_WIDTH == 16 + sljit_uh asuchars[2]; +#elif PCRE2_CODE_UNIT_WIDTH == 32 + sljit_ui asuchars[1]; +#endif + } c; + union { + sljit_si asint; + sljit_uh asushort; +#if PCRE2_CODE_UNIT_WIDTH == 8 + sljit_ub asbyte; + sljit_ub asuchars[4]; +#elif PCRE2_CODE_UNIT_WIDTH == 16 + sljit_uh asuchars[2]; +#elif PCRE2_CODE_UNIT_WIDTH == 32 + sljit_ui asuchars[1]; +#endif + } oc; +#endif +} compare_context; + +/* Undefine sljit macros. */ +#undef CMP + +/* Used for accessing the elements of the stack. */ +#define STACK(i) ((-(i) - 1) * (int)sizeof(sljit_sw)) + +#define TMP1 SLJIT_R0 +#define TMP2 SLJIT_R2 +#define TMP3 SLJIT_R3 +#define STR_PTR SLJIT_S0 +#define STR_END SLJIT_S1 +#define STACK_TOP SLJIT_R1 +#define STACK_LIMIT SLJIT_S2 +#define COUNT_MATCH SLJIT_S3 +#define ARGUMENTS SLJIT_S4 +#define RETURN_ADDR SLJIT_R4 + +/* Local space layout. */ +/* These two locals can be used by the current opcode. */ +#define LOCALS0 (0 * sizeof(sljit_sw)) +#define LOCALS1 (1 * sizeof(sljit_sw)) +/* Two local variables for possessive quantifiers (char1 cannot use them). */ +#define POSSESSIVE0 (2 * sizeof(sljit_sw)) +#define POSSESSIVE1 (3 * sizeof(sljit_sw)) +/* Max limit of recursions. */ +#define LIMIT_MATCH (4 * sizeof(sljit_sw)) +/* The output vector is stored on the stack, and contains pointers +to characters. The vector data is divided into two groups: the first +group contains the start / end character pointers, and the second is +the start pointers when the end of the capturing group has not yet reached. */ +#define OVECTOR_START (common->ovector_start) +#define OVECTOR(i) (OVECTOR_START + (i) * (sljit_sw)sizeof(sljit_sw)) +#define OVECTOR_PRIV(i) (common->cbra_ptr + (i) * (sljit_sw)sizeof(sljit_sw)) +#define PRIVATE_DATA(cc) (common->private_data_ptrs[(cc) - common->start]) + +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define MOV_UCHAR SLJIT_MOV_UB +#define MOVU_UCHAR SLJIT_MOVU_UB +#define IN_UCHARS(x) (x) +#elif PCRE2_CODE_UNIT_WIDTH == 16 +#define MOV_UCHAR SLJIT_MOV_UH +#define MOVU_UCHAR SLJIT_MOVU_UH +#define UCHAR_SHIFT (1) +#define IN_UCHARS(x) ((x) << UCHAR_SHIFT) +#elif PCRE2_CODE_UNIT_WIDTH == 32 +#define MOV_UCHAR SLJIT_MOV_UI +#define MOVU_UCHAR SLJIT_MOVU_UI +#define UCHAR_SHIFT (2) +#define IN_UCHARS(x) ((x) << UCHAR_SHIFT) +#else +#error Unsupported compiling mode +#endif + +/* Shortcuts. */ +#define DEFINE_COMPILER \ + struct sljit_compiler *compiler = common->compiler +#define OP1(op, dst, dstw, src, srcw) \ + sljit_emit_op1(compiler, (op), (dst), (dstw), (src), (srcw)) +#define OP2(op, dst, dstw, src1, src1w, src2, src2w) \ + sljit_emit_op2(compiler, (op), (dst), (dstw), (src1), (src1w), (src2), (src2w)) +#define LABEL() \ + sljit_emit_label(compiler) +#define JUMP(type) \ + sljit_emit_jump(compiler, (type)) +#define JUMPTO(type, label) \ + sljit_set_label(sljit_emit_jump(compiler, (type)), (label)) +#define JUMPHERE(jump) \ + sljit_set_label((jump), sljit_emit_label(compiler)) +#define SET_LABEL(jump, label) \ + sljit_set_label((jump), (label)) +#define CMP(type, src1, src1w, src2, src2w) \ + sljit_emit_cmp(compiler, (type), (src1), (src1w), (src2), (src2w)) +#define CMPTO(type, src1, src1w, src2, src2w, label) \ + sljit_set_label(sljit_emit_cmp(compiler, (type), (src1), (src1w), (src2), (src2w)), (label)) +#define OP_FLAGS(op, dst, dstw, src, srcw, type) \ + sljit_emit_op_flags(compiler, (op), (dst), (dstw), (src), (srcw), (type)) +#define GET_LOCAL_BASE(dst, dstw, offset) \ + sljit_get_local_base(compiler, (dst), (dstw), (offset)) + +#define READ_CHAR_MAX 0x7fffffff + +static PCRE2_SPTR bracketend(PCRE2_SPTR cc) +{ +SLJIT_ASSERT((*cc >= OP_ASSERT && *cc <= OP_ASSERTBACK_NOT) || (*cc >= OP_ONCE && *cc <= OP_SCOND)); +do cc += GET(cc, 1); while (*cc == OP_ALT); +SLJIT_ASSERT(*cc >= OP_KET && *cc <= OP_KETRPOS); +cc += 1 + LINK_SIZE; +return cc; +} + +static int no_alternatives(PCRE2_SPTR cc) +{ +int count = 0; +SLJIT_ASSERT((*cc >= OP_ASSERT && *cc <= OP_ASSERTBACK_NOT) || (*cc >= OP_ONCE && *cc <= OP_SCOND)); +do + { + cc += GET(cc, 1); + count++; + } +while (*cc == OP_ALT); +SLJIT_ASSERT(*cc >= OP_KET && *cc <= OP_KETRPOS); +return count; +} + +/* Functions whose might need modification for all new supported opcodes: + next_opcode + check_opcode_types + set_private_data_ptrs + get_framesize + init_frame + get_private_data_copy_length + copy_private_data + compile_matchingpath + compile_backtrackingpath +*/ + +static PCRE2_SPTR next_opcode(compiler_common *common, PCRE2_SPTR cc) +{ +SLJIT_UNUSED_ARG(common); +switch(*cc) + { + case OP_SOD: + case OP_SOM: + case OP_SET_SOM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + case OP_NOTPROP: + case OP_PROP: + case OP_ANYNL: + case OP_NOT_HSPACE: + case OP_HSPACE: + case OP_NOT_VSPACE: + case OP_VSPACE: + case OP_EXTUNI: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSSTAR: + case OP_CRPOSPLUS: + case OP_CRPOSQUERY: + case OP_CRPOSRANGE: + case OP_CLASS: + case OP_NCLASS: + case OP_REF: + case OP_REFI: + case OP_DNREF: + case OP_DNREFI: + case OP_RECURSE: + case OP_CALLOUT: + case OP_ALT: + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_KETRPOS: + case OP_REVERSE: + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRA: + case OP_BRAPOS: + case OP_CBRA: + case OP_CBRAPOS: + case OP_COND: + case OP_SBRA: + case OP_SBRAPOS: + case OP_SCBRA: + case OP_SCBRAPOS: + case OP_SCOND: + case OP_CREF: + case OP_DNCREF: + case OP_RREF: + case OP_DNRREF: + case OP_FALSE: + case OP_TRUE: + case OP_BRAZERO: + case OP_BRAMINZERO: + case OP_BRAPOSZERO: + case OP_PRUNE: + case OP_SKIP: + case OP_THEN: + case OP_COMMIT: + case OP_FAIL: + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + case OP_CLOSE: + case OP_SKIPZERO: + return cc + PRIV(OP_lengths)[*cc]; + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_UPTOI: + case OP_MINUPTOI: + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + cc += PRIV(OP_lengths)[*cc]; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + return cc; + + /* Special cases. */ + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSUPTO: + return cc + PRIV(OP_lengths)[*cc] - 1; + + case OP_ANYBYTE: +#ifdef SUPPORT_UNICODE + if (common->utf) return NULL; +#endif + return cc + 1; + + case OP_CALLOUT_STR: + return cc + GET(cc, 1 + 2*LINK_SIZE); + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + return cc + GET(cc, 1); +#endif + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + return cc + 1 + 2 + cc[1]; + + default: + /* All opcodes are supported now! */ + SLJIT_ASSERT_STOP(); + return NULL; + } +} + +static BOOL check_opcode_types(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend) +{ +int count; +PCRE2_SPTR slot; +PCRE2_SPTR assert_back_end = cc - 1; + +/* Calculate important variables (like stack size) and checks whether all opcodes are supported. */ +while (cc < ccend) + { + switch(*cc) + { + case OP_SET_SOM: + common->has_set_som = TRUE; + common->might_be_empty = TRUE; + cc += 1; + break; + + case OP_REF: + case OP_REFI: + common->optimized_cbracket[GET2(cc, 1)] = 0; + cc += 1 + IMM2_SIZE; + break; + + case OP_CBRAPOS: + case OP_SCBRAPOS: + common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] = 0; + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_COND: + case OP_SCOND: + /* Only AUTO_CALLOUT can insert this opcode. We do + not intend to support this case. */ + if (cc[1 + LINK_SIZE] == OP_CALLOUT || cc[1 + LINK_SIZE] == OP_CALLOUT_STR) + return FALSE; + cc += 1 + LINK_SIZE; + break; + + case OP_CREF: + common->optimized_cbracket[GET2(cc, 1)] = 0; + cc += 1 + IMM2_SIZE; + break; + + case OP_DNREF: + case OP_DNREFI: + case OP_DNCREF: + count = GET2(cc, 1 + IMM2_SIZE); + slot = common->name_table + GET2(cc, 1) * common->name_entry_size; + while (count-- > 0) + { + common->optimized_cbracket[GET2(slot, 0)] = 0; + slot += common->name_entry_size; + } + cc += 1 + 2 * IMM2_SIZE; + break; + + case OP_RECURSE: + /* Set its value only once. */ + if (common->recursive_head_ptr == 0) + { + common->recursive_head_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } + cc += 1 + LINK_SIZE; + break; + + case OP_CALLOUT: + case OP_CALLOUT_STR: + if (common->capture_last_ptr == 0) + { + common->capture_last_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } + cc += (*cc == OP_CALLOUT) ? PRIV(OP_lengths)[OP_CALLOUT] : GET(cc, 1 + 2*LINK_SIZE); + break; + + case OP_ASSERTBACK: + slot = bracketend(cc); + if (slot > assert_back_end) + assert_back_end = slot; + cc += 1 + LINK_SIZE; + break; + + case OP_THEN_ARG: + common->has_then = TRUE; + common->control_head_ptr = 1; + /* Fall through. */ + + case OP_PRUNE_ARG: + case OP_MARK: + if (common->mark_ptr == 0) + { + common->mark_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } + cc += 1 + 2 + cc[1]; + break; + + case OP_THEN: + common->has_then = TRUE; + common->control_head_ptr = 1; + cc += 1; + break; + + case OP_SKIP: + if (cc < assert_back_end) + common->has_skip_in_assert_back = TRUE; + cc += 1; + break; + + case OP_SKIP_ARG: + common->control_head_ptr = 1; + common->has_skip_arg = TRUE; + if (cc < assert_back_end) + common->has_skip_in_assert_back = TRUE; + cc += 1 + 2 + cc[1]; + break; + + default: + cc = next_opcode(common, cc); + if (cc == NULL) + return FALSE; + break; + } + } +return TRUE; +} + +static BOOL is_accelerated_repeat(PCRE2_SPTR cc) +{ +switch(*cc) + { + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + return (cc[1] != OP_ANYNL && cc[1] != OP_EXTUNI); + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSSTAR: + case OP_POSPLUS: + + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_POSSTARI: + case OP_POSPLUSI: + + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + return TRUE; + + case OP_CLASS: + case OP_NCLASS: +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + cc += (*cc == OP_XCLASS) ? GET(cc, 1) : (int)(1 + (32 / sizeof(PCRE2_UCHAR))); +#else + cc += (1 + (32 / sizeof(PCRE2_UCHAR))); +#endif + + switch(*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSSTAR: + case OP_CRPOSPLUS: + return TRUE; + } + break; + } +return FALSE; +} + +static SLJIT_INLINE BOOL detect_fast_forward_skip(compiler_common *common, int *private_data_start) +{ +PCRE2_SPTR cc = common->start; +PCRE2_SPTR end; + +/* Skip not repeated brackets. */ +while (TRUE) + { + switch(*cc) + { + case OP_SOD: + case OP_SOM: + case OP_SET_SOM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + /* Zero width assertions. */ + cc++; + continue; + } + + if (*cc != OP_BRA && *cc != OP_CBRA) + break; + + end = cc + GET(cc, 1); + if (*end != OP_KET || PRIVATE_DATA(end) != 0) + return FALSE; + if (*cc == OP_CBRA) + { + if (common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0) + return FALSE; + cc += IMM2_SIZE; + } + cc += 1 + LINK_SIZE; + } + +if (is_accelerated_repeat(cc)) + { + common->fast_forward_bc_ptr = cc; + common->private_data_ptrs[(cc + 1) - common->start] = *private_data_start; + *private_data_start += sizeof(sljit_sw); + return TRUE; + } +return FALSE; +} + +static SLJIT_INLINE void detect_fast_fail(compiler_common *common, PCRE2_SPTR cc, int *private_data_start, sljit_si depth) +{ + PCRE2_SPTR next_alt; + + SLJIT_ASSERT(*cc == OP_BRA || *cc == OP_CBRA); + + if (*cc == OP_CBRA && common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0) + return; + + next_alt = bracketend(cc) - (1 + LINK_SIZE); + if (*next_alt != OP_KET || PRIVATE_DATA(next_alt) != 0) + return; + + do + { + next_alt = cc + GET(cc, 1); + + cc += 1 + LINK_SIZE + ((*cc == OP_CBRA) ? IMM2_SIZE : 0); + + while (TRUE) + { + switch(*cc) + { + case OP_SOD: + case OP_SOM: + case OP_SET_SOM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + /* Zero width assertions. */ + cc++; + continue; + } + break; + } + + if (depth > 0 && (*cc == OP_BRA || *cc == OP_CBRA)) + detect_fast_fail(common, cc, private_data_start, depth - 1); + + if (is_accelerated_repeat(cc)) + { + common->private_data_ptrs[(cc + 1) - common->start] = *private_data_start; + + if (common->fast_fail_start_ptr == 0) + common->fast_fail_start_ptr = *private_data_start; + + *private_data_start += sizeof(sljit_sw); + common->fast_fail_end_ptr = *private_data_start; + + if (*private_data_start > SLJIT_MAX_LOCAL_SIZE) + return; + } + + cc = next_alt; + } + while (*cc == OP_ALT); +} + +static int get_class_iterator_size(PCRE2_SPTR cc) +{ +sljit_ui min; +sljit_ui max; +switch(*cc) + { + case OP_CRSTAR: + case OP_CRPLUS: + return 2; + + case OP_CRMINSTAR: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + return 1; + + case OP_CRRANGE: + case OP_CRMINRANGE: + min = GET2(cc, 1); + max = GET2(cc, 1 + IMM2_SIZE); + if (max == 0) + return (*cc == OP_CRRANGE) ? 2 : 1; + max -= min; + if (max > 2) + max = 2; + return max; + + default: + return 0; + } +} + +static BOOL detect_repeat(compiler_common *common, PCRE2_SPTR begin) +{ +PCRE2_SPTR end = bracketend(begin); +PCRE2_SPTR next; +PCRE2_SPTR next_end; +PCRE2_SPTR max_end; +PCRE2_UCHAR type; +sljit_sw length = end - begin; +sljit_si min, max, i; + +/* Detect fixed iterations first. */ +if (end[-(1 + LINK_SIZE)] != OP_KET) + return FALSE; + +/* Already detected repeat. */ +if (common->private_data_ptrs[end - common->start - LINK_SIZE] != 0) + return TRUE; + +next = end; +min = 1; +while (1) + { + if (*next != *begin) + break; + next_end = bracketend(next); + if (next_end - next != length || memcmp(begin, next, IN_UCHARS(length)) != 0) + break; + next = next_end; + min++; + } + +if (min == 2) + return FALSE; + +max = 0; +max_end = next; +if (*next == OP_BRAZERO || *next == OP_BRAMINZERO) + { + type = *next; + while (1) + { + if (next[0] != type || next[1] != OP_BRA || next[2 + LINK_SIZE] != *begin) + break; + next_end = bracketend(next + 2 + LINK_SIZE); + if (next_end - next != (length + 2 + LINK_SIZE) || memcmp(begin, next + 2 + LINK_SIZE, IN_UCHARS(length)) != 0) + break; + next = next_end; + max++; + } + + if (next[0] == type && next[1] == *begin && max >= 1) + { + next_end = bracketend(next + 1); + if (next_end - next == (length + 1) && memcmp(begin, next + 1, IN_UCHARS(length)) == 0) + { + for (i = 0; i < max; i++, next_end += 1 + LINK_SIZE) + if (*next_end != OP_KET) + break; + + if (i == max) + { + common->private_data_ptrs[max_end - common->start - LINK_SIZE] = next_end - max_end; + common->private_data_ptrs[max_end - common->start - LINK_SIZE + 1] = (type == OP_BRAZERO) ? OP_UPTO : OP_MINUPTO; + /* +2 the original and the last. */ + common->private_data_ptrs[max_end - common->start - LINK_SIZE + 2] = max + 2; + if (min == 1) + return TRUE; + min--; + max_end -= (1 + LINK_SIZE) + GET(max_end, -LINK_SIZE); + } + } + } + } + +if (min >= 3) + { + common->private_data_ptrs[end - common->start - LINK_SIZE] = max_end - end; + common->private_data_ptrs[end - common->start - LINK_SIZE + 1] = OP_EXACT; + common->private_data_ptrs[end - common->start - LINK_SIZE + 2] = min; + return TRUE; + } + +return FALSE; +} + +#define CASE_ITERATOR_PRIVATE_DATA_1 \ + case OP_MINSTAR: \ + case OP_MINPLUS: \ + case OP_QUERY: \ + case OP_MINQUERY: \ + case OP_MINSTARI: \ + case OP_MINPLUSI: \ + case OP_QUERYI: \ + case OP_MINQUERYI: \ + case OP_NOTMINSTAR: \ + case OP_NOTMINPLUS: \ + case OP_NOTQUERY: \ + case OP_NOTMINQUERY: \ + case OP_NOTMINSTARI: \ + case OP_NOTMINPLUSI: \ + case OP_NOTQUERYI: \ + case OP_NOTMINQUERYI: + +#define CASE_ITERATOR_PRIVATE_DATA_2A \ + case OP_STAR: \ + case OP_PLUS: \ + case OP_STARI: \ + case OP_PLUSI: \ + case OP_NOTSTAR: \ + case OP_NOTPLUS: \ + case OP_NOTSTARI: \ + case OP_NOTPLUSI: + +#define CASE_ITERATOR_PRIVATE_DATA_2B \ + case OP_UPTO: \ + case OP_MINUPTO: \ + case OP_UPTOI: \ + case OP_MINUPTOI: \ + case OP_NOTUPTO: \ + case OP_NOTMINUPTO: \ + case OP_NOTUPTOI: \ + case OP_NOTMINUPTOI: + +#define CASE_ITERATOR_TYPE_PRIVATE_DATA_1 \ + case OP_TYPEMINSTAR: \ + case OP_TYPEMINPLUS: \ + case OP_TYPEQUERY: \ + case OP_TYPEMINQUERY: + +#define CASE_ITERATOR_TYPE_PRIVATE_DATA_2A \ + case OP_TYPESTAR: \ + case OP_TYPEPLUS: + +#define CASE_ITERATOR_TYPE_PRIVATE_DATA_2B \ + case OP_TYPEUPTO: \ + case OP_TYPEMINUPTO: + +static void set_private_data_ptrs(compiler_common *common, int *private_data_start, PCRE2_SPTR ccend) +{ +PCRE2_SPTR cc = common->start; +PCRE2_SPTR alternative; +PCRE2_SPTR end = NULL; +int private_data_ptr = *private_data_start; +int space, size, bracketlen; +BOOL repeat_check = TRUE; + +while (cc < ccend) + { + space = 0; + size = 0; + bracketlen = 0; + if (private_data_ptr > SLJIT_MAX_LOCAL_SIZE) + break; + + if (repeat_check && (*cc == OP_ONCE || *cc == OP_ONCE_NC || *cc == OP_BRA || *cc == OP_CBRA || *cc == OP_COND)) + { + if (detect_repeat(common, cc)) + { + /* These brackets are converted to repeats, so no global + based single character repeat is allowed. */ + if (cc >= end) + end = bracketend(cc); + } + } + repeat_check = TRUE; + + switch(*cc) + { + case OP_KET: + if (common->private_data_ptrs[cc + 1 - common->start] != 0) + { + common->private_data_ptrs[cc - common->start] = private_data_ptr; + private_data_ptr += sizeof(sljit_sw); + cc += common->private_data_ptrs[cc + 1 - common->start]; + } + cc += 1 + LINK_SIZE; + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRAPOS: + case OP_SBRA: + case OP_SBRAPOS: + case OP_SCOND: + common->private_data_ptrs[cc - common->start] = private_data_ptr; + private_data_ptr += sizeof(sljit_sw); + bracketlen = 1 + LINK_SIZE; + break; + + case OP_CBRAPOS: + case OP_SCBRAPOS: + common->private_data_ptrs[cc - common->start] = private_data_ptr; + private_data_ptr += sizeof(sljit_sw); + bracketlen = 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_COND: + /* Might be a hidden SCOND. */ + alternative = cc + GET(cc, 1); + if (*alternative == OP_KETRMAX || *alternative == OP_KETRMIN) + { + common->private_data_ptrs[cc - common->start] = private_data_ptr; + private_data_ptr += sizeof(sljit_sw); + } + bracketlen = 1 + LINK_SIZE; + break; + + case OP_BRA: + bracketlen = 1 + LINK_SIZE; + break; + + case OP_CBRA: + case OP_SCBRA: + bracketlen = 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_BRAZERO: + case OP_BRAMINZERO: + case OP_BRAPOSZERO: + repeat_check = FALSE; + size = 1; + break; + + CASE_ITERATOR_PRIVATE_DATA_1 + space = 1; + size = -2; + break; + + CASE_ITERATOR_PRIVATE_DATA_2A + space = 2; + size = -2; + break; + + CASE_ITERATOR_PRIVATE_DATA_2B + space = 2; + size = -(2 + IMM2_SIZE); + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_1 + space = 1; + size = 1; + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_2A + if (cc[1] != OP_ANYNL && cc[1] != OP_EXTUNI) + space = 2; + size = 1; + break; + + case OP_TYPEUPTO: + if (cc[1 + IMM2_SIZE] != OP_ANYNL && cc[1 + IMM2_SIZE] != OP_EXTUNI) + space = 2; + size = 1 + IMM2_SIZE; + break; + + case OP_TYPEMINUPTO: + space = 2; + size = 1 + IMM2_SIZE; + break; + + case OP_CLASS: + case OP_NCLASS: + size = 1 + 32 / sizeof(PCRE2_UCHAR); + space = get_class_iterator_size(cc + size); + break; + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + size = GET(cc, 1); + space = get_class_iterator_size(cc + size); + break; +#endif + + default: + cc = next_opcode(common, cc); + SLJIT_ASSERT(cc != NULL); + break; + } + + /* Character iterators, which are not inside a repeated bracket, + gets a private slot instead of allocating it on the stack. */ + if (space > 0 && cc >= end) + { + common->private_data_ptrs[cc - common->start] = private_data_ptr; + private_data_ptr += sizeof(sljit_sw) * space; + } + + if (size != 0) + { + if (size < 0) + { + cc += -size; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + } + else + cc += size; + } + + if (bracketlen > 0) + { + if (cc >= end) + { + end = bracketend(cc); + if (end[-1 - LINK_SIZE] == OP_KET) + end = NULL; + } + cc += bracketlen; + } + } +*private_data_start = private_data_ptr; +} + +/* Returns with a frame_types (always < 0) if no need for frame. */ +static int get_framesize(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, BOOL recursive, BOOL *needs_control_head) +{ +int length = 0; +int possessive = 0; +BOOL stack_restore = FALSE; +BOOL setsom_found = recursive; +BOOL setmark_found = recursive; +/* The last capture is a local variable even for recursions. */ +BOOL capture_last_found = FALSE; + +#if defined DEBUG_FORCE_CONTROL_HEAD && DEBUG_FORCE_CONTROL_HEAD +SLJIT_ASSERT(common->control_head_ptr != 0); +*needs_control_head = TRUE; +#else +*needs_control_head = FALSE; +#endif + +if (ccend == NULL) + { + ccend = bracketend(cc) - (1 + LINK_SIZE); + if (!recursive && (*cc == OP_CBRAPOS || *cc == OP_SCBRAPOS)) + { + possessive = length = (common->capture_last_ptr != 0) ? 5 : 3; + /* This is correct regardless of common->capture_last_ptr. */ + capture_last_found = TRUE; + } + cc = next_opcode(common, cc); + } + +SLJIT_ASSERT(cc != NULL); +while (cc < ccend) + switch(*cc) + { + case OP_SET_SOM: + SLJIT_ASSERT(common->has_set_som); + stack_restore = TRUE; + if (!setsom_found) + { + length += 2; + setsom_found = TRUE; + } + cc += 1; + break; + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_THEN_ARG: + SLJIT_ASSERT(common->mark_ptr != 0); + stack_restore = TRUE; + if (!setmark_found) + { + length += 2; + setmark_found = TRUE; + } + if (common->control_head_ptr != 0) + *needs_control_head = TRUE; + cc += 1 + 2 + cc[1]; + break; + + case OP_RECURSE: + stack_restore = TRUE; + if (common->has_set_som && !setsom_found) + { + length += 2; + setsom_found = TRUE; + } + if (common->mark_ptr != 0 && !setmark_found) + { + length += 2; + setmark_found = TRUE; + } + if (common->capture_last_ptr != 0 && !capture_last_found) + { + length += 2; + capture_last_found = TRUE; + } + cc += 1 + LINK_SIZE; + break; + + case OP_CBRA: + case OP_CBRAPOS: + case OP_SCBRA: + case OP_SCBRAPOS: + stack_restore = TRUE; + if (common->capture_last_ptr != 0 && !capture_last_found) + { + length += 2; + capture_last_found = TRUE; + } + length += 3; + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_THEN: + stack_restore = TRUE; + if (common->control_head_ptr != 0) + *needs_control_head = TRUE; + cc ++; + break; + + default: + stack_restore = TRUE; + /* Fall through. */ + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + case OP_ANYBYTE: + case OP_NOTPROP: + case OP_PROP: + case OP_ANYNL: + case OP_NOT_HSPACE: + case OP_HSPACE: + case OP_NOT_VSPACE: + case OP_VSPACE: + case OP_EXTUNI: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + + case OP_TYPEEXACT: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSUPTO: + + case OP_CLASS: + case OP_NCLASS: + case OP_XCLASS: + + case OP_CALLOUT: + case OP_CALLOUT_STR: + + cc = next_opcode(common, cc); + SLJIT_ASSERT(cc != NULL); + break; + } + +/* Possessive quantifiers can use a special case. */ +if (SLJIT_UNLIKELY(possessive == length)) + return stack_restore ? no_frame : no_stack; + +if (length > 0) + return length + 1; +return stack_restore ? no_frame : no_stack; +} + +static void init_frame(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, int stackpos, int stacktop, BOOL recursive) +{ +DEFINE_COMPILER; +BOOL setsom_found = recursive; +BOOL setmark_found = recursive; +/* The last capture is a local variable even for recursions. */ +BOOL capture_last_found = FALSE; +int offset; + +/* >= 1 + shortest item size (2) */ +SLJIT_UNUSED_ARG(stacktop); +SLJIT_ASSERT(stackpos >= stacktop + 2); + +stackpos = STACK(stackpos); +if (ccend == NULL) + { + ccend = bracketend(cc) - (1 + LINK_SIZE); + if (recursive || (*cc != OP_CBRAPOS && *cc != OP_SCBRAPOS)) + cc = next_opcode(common, cc); + } + +SLJIT_ASSERT(cc != NULL); +while (cc < ccend) + switch(*cc) + { + case OP_SET_SOM: + SLJIT_ASSERT(common->has_set_som); + if (!setsom_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -OVECTOR(0)); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + setsom_found = TRUE; + } + cc += 1; + break; + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_THEN_ARG: + SLJIT_ASSERT(common->mark_ptr != 0); + if (!setmark_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->mark_ptr); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + setmark_found = TRUE; + } + cc += 1 + 2 + cc[1]; + break; + + case OP_RECURSE: + if (common->has_set_som && !setsom_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -OVECTOR(0)); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + setsom_found = TRUE; + } + if (common->mark_ptr != 0 && !setmark_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->mark_ptr); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + setmark_found = TRUE; + } + if (common->capture_last_ptr != 0 && !capture_last_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->capture_last_ptr); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + capture_last_found = TRUE; + } + cc += 1 + LINK_SIZE; + break; + + case OP_CBRA: + case OP_CBRAPOS: + case OP_SCBRA: + case OP_SCBRAPOS: + if (common->capture_last_ptr != 0 && !capture_last_found) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, -common->capture_last_ptr); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + capture_last_found = TRUE; + } + offset = (GET2(cc, 1 + LINK_SIZE)) << 1; + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, OVECTOR(offset)); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP1, 0); + stackpos += (int)sizeof(sljit_sw); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, TMP2, 0); + stackpos += (int)sizeof(sljit_sw); + + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + default: + cc = next_opcode(common, cc); + SLJIT_ASSERT(cc != NULL); + break; + } + +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackpos, SLJIT_IMM, 0); +SLJIT_ASSERT(stackpos == STACK(stacktop)); +} + +static SLJIT_INLINE int get_private_data_copy_length(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, BOOL needs_control_head) +{ +int private_data_length = needs_control_head ? 3 : 2; +int size; +PCRE2_SPTR alternative; +/* Calculate the sum of the private machine words. */ +while (cc < ccend) + { + size = 0; + switch(*cc) + { + case OP_KET: + if (PRIVATE_DATA(cc) != 0) + { + private_data_length++; + SLJIT_ASSERT(PRIVATE_DATA(cc + 1) != 0); + cc += PRIVATE_DATA(cc + 1); + } + cc += 1 + LINK_SIZE; + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRAPOS: + case OP_SBRA: + case OP_SBRAPOS: + case OP_SCOND: + private_data_length++; + SLJIT_ASSERT(PRIVATE_DATA(cc) != 0); + cc += 1 + LINK_SIZE; + break; + + case OP_CBRA: + case OP_SCBRA: + if (common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0) + private_data_length++; + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_CBRAPOS: + case OP_SCBRAPOS: + private_data_length += 2; + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_COND: + /* Might be a hidden SCOND. */ + alternative = cc + GET(cc, 1); + if (*alternative == OP_KETRMAX || *alternative == OP_KETRMIN) + private_data_length++; + cc += 1 + LINK_SIZE; + break; + + CASE_ITERATOR_PRIVATE_DATA_1 + if (PRIVATE_DATA(cc)) + private_data_length++; + cc += 2; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_PRIVATE_DATA_2A + if (PRIVATE_DATA(cc)) + private_data_length += 2; + cc += 2; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_PRIVATE_DATA_2B + if (PRIVATE_DATA(cc)) + private_data_length += 2; + cc += 2 + IMM2_SIZE; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_1 + if (PRIVATE_DATA(cc)) + private_data_length++; + cc += 1; + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_2A + if (PRIVATE_DATA(cc)) + private_data_length += 2; + cc += 1; + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_2B + if (PRIVATE_DATA(cc)) + private_data_length += 2; + cc += 1 + IMM2_SIZE; + break; + + case OP_CLASS: + case OP_NCLASS: +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + size = (*cc == OP_XCLASS) ? GET(cc, 1) : 1 + 32 / (int)sizeof(PCRE2_UCHAR); +#else + size = 1 + 32 / (int)sizeof(PCRE2_UCHAR); +#endif + if (PRIVATE_DATA(cc)) + private_data_length += get_class_iterator_size(cc + size); + cc += size; + break; + + default: + cc = next_opcode(common, cc); + SLJIT_ASSERT(cc != NULL); + break; + } + } +SLJIT_ASSERT(cc == ccend); +return private_data_length; +} + +static void copy_private_data(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, + BOOL save, int stackptr, int stacktop, BOOL needs_control_head) +{ +DEFINE_COMPILER; +int srcw[2]; +int count, size; +BOOL tmp1next = TRUE; +BOOL tmp1empty = TRUE; +BOOL tmp2empty = TRUE; +PCRE2_SPTR alternative; +enum { + start, + loop, + end +} status; + +status = save ? start : loop; +stackptr = STACK(stackptr - 2); +stacktop = STACK(stacktop - 1); + +if (!save) + { + stackptr += (needs_control_head ? 2 : 1) * sizeof(sljit_sw); + if (stackptr < stacktop) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), stackptr); + stackptr += sizeof(sljit_sw); + tmp1empty = FALSE; + } + if (stackptr < stacktop) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), stackptr); + stackptr += sizeof(sljit_sw); + tmp2empty = FALSE; + } + /* The tmp1next must be TRUE in either way. */ + } + +do + { + count = 0; + switch(status) + { + case start: + SLJIT_ASSERT(save && common->recursive_head_ptr != 0); + count = 1; + srcw[0] = common->recursive_head_ptr; + if (needs_control_head) + { + SLJIT_ASSERT(common->control_head_ptr != 0); + count = 2; + srcw[1] = common->control_head_ptr; + } + status = loop; + break; + + case loop: + if (cc >= ccend) + { + status = end; + break; + } + + switch(*cc) + { + case OP_KET: + if (PRIVATE_DATA(cc) != 0) + { + count = 1; + srcw[0] = PRIVATE_DATA(cc); + SLJIT_ASSERT(PRIVATE_DATA(cc + 1) != 0); + cc += PRIVATE_DATA(cc + 1); + } + cc += 1 + LINK_SIZE; + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRAPOS: + case OP_SBRA: + case OP_SBRAPOS: + case OP_SCOND: + count = 1; + srcw[0] = PRIVATE_DATA(cc); + SLJIT_ASSERT(srcw[0] != 0); + cc += 1 + LINK_SIZE; + break; + + case OP_CBRA: + case OP_SCBRA: + if (common->optimized_cbracket[GET2(cc, 1 + LINK_SIZE)] == 0) + { + count = 1; + srcw[0] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE)); + } + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_CBRAPOS: + case OP_SCBRAPOS: + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = OVECTOR_PRIV(GET2(cc, 1 + LINK_SIZE)); + SLJIT_ASSERT(srcw[0] != 0 && srcw[1] != 0); + cc += 1 + LINK_SIZE + IMM2_SIZE; + break; + + case OP_COND: + /* Might be a hidden SCOND. */ + alternative = cc + GET(cc, 1); + if (*alternative == OP_KETRMAX || *alternative == OP_KETRMIN) + { + count = 1; + srcw[0] = PRIVATE_DATA(cc); + SLJIT_ASSERT(srcw[0] != 0); + } + cc += 1 + LINK_SIZE; + break; + + CASE_ITERATOR_PRIVATE_DATA_1 + if (PRIVATE_DATA(cc)) + { + count = 1; + srcw[0] = PRIVATE_DATA(cc); + } + cc += 2; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_PRIVATE_DATA_2A + if (PRIVATE_DATA(cc)) + { + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw); + } + cc += 2; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_PRIVATE_DATA_2B + if (PRIVATE_DATA(cc)) + { + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = PRIVATE_DATA(cc) + sizeof(sljit_sw); + } + cc += 2 + IMM2_SIZE; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_1 + if (PRIVATE_DATA(cc)) + { + count = 1; + srcw[0] = PRIVATE_DATA(cc); + } + cc += 1; + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_2A + if (PRIVATE_DATA(cc)) + { + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = srcw[0] + sizeof(sljit_sw); + } + cc += 1; + break; + + CASE_ITERATOR_TYPE_PRIVATE_DATA_2B + if (PRIVATE_DATA(cc)) + { + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = srcw[0] + sizeof(sljit_sw); + } + cc += 1 + IMM2_SIZE; + break; + + case OP_CLASS: + case OP_NCLASS: +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + size = (*cc == OP_XCLASS) ? GET(cc, 1) : 1 + 32 / (int)sizeof(PCRE2_UCHAR); +#else + size = 1 + 32 / (int)sizeof(PCRE2_UCHAR); +#endif + if (PRIVATE_DATA(cc)) + switch(get_class_iterator_size(cc + size)) + { + case 1: + count = 1; + srcw[0] = PRIVATE_DATA(cc); + break; + + case 2: + count = 2; + srcw[0] = PRIVATE_DATA(cc); + srcw[1] = srcw[0] + sizeof(sljit_sw); + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + cc += size; + break; + + default: + cc = next_opcode(common, cc); + SLJIT_ASSERT(cc != NULL); + break; + } + break; + + case end: + SLJIT_ASSERT_STOP(); + break; + } + + while (count > 0) + { + count--; + if (save) + { + if (tmp1next) + { + if (!tmp1empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP1, 0); + stackptr += sizeof(sljit_sw); + } + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), srcw[count]); + tmp1empty = FALSE; + tmp1next = FALSE; + } + else + { + if (!tmp2empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP2, 0); + stackptr += sizeof(sljit_sw); + } + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), srcw[count]); + tmp2empty = FALSE; + tmp1next = TRUE; + } + } + else + { + if (tmp1next) + { + SLJIT_ASSERT(!tmp1empty); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), srcw[count], TMP1, 0); + tmp1empty = stackptr >= stacktop; + if (!tmp1empty) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), stackptr); + stackptr += sizeof(sljit_sw); + } + tmp1next = FALSE; + } + else + { + SLJIT_ASSERT(!tmp2empty); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), srcw[count], TMP2, 0); + tmp2empty = stackptr >= stacktop; + if (!tmp2empty) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), stackptr); + stackptr += sizeof(sljit_sw); + } + tmp1next = TRUE; + } + } + } + } +while (status != end); + +if (save) + { + if (tmp1next) + { + if (!tmp1empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP1, 0); + stackptr += sizeof(sljit_sw); + } + if (!tmp2empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP2, 0); + stackptr += sizeof(sljit_sw); + } + } + else + { + if (!tmp2empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP2, 0); + stackptr += sizeof(sljit_sw); + } + if (!tmp1empty) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), stackptr, TMP1, 0); + stackptr += sizeof(sljit_sw); + } + } + } +SLJIT_ASSERT(cc == ccend && stackptr == stacktop && (save || (tmp1empty && tmp2empty))); +} + +static SLJIT_INLINE PCRE2_SPTR set_then_offsets(compiler_common *common, PCRE2_SPTR cc, sljit_ub *current_offset) +{ +PCRE2_SPTR end = bracketend(cc); +BOOL has_alternatives = cc[GET(cc, 1)] == OP_ALT; + +/* Assert captures then. */ +if (*cc >= OP_ASSERT && *cc <= OP_ASSERTBACK_NOT) + current_offset = NULL; +/* Conditional block does not. */ +if (*cc == OP_COND || *cc == OP_SCOND) + has_alternatives = FALSE; + +cc = next_opcode(common, cc); +if (has_alternatives) + current_offset = common->then_offsets + (cc - common->start); + +while (cc < end) + { + if ((*cc >= OP_ASSERT && *cc <= OP_ASSERTBACK_NOT) || (*cc >= OP_ONCE && *cc <= OP_SCOND)) + cc = set_then_offsets(common, cc, current_offset); + else + { + if (*cc == OP_ALT && has_alternatives) + current_offset = common->then_offsets + (cc + 1 + LINK_SIZE - common->start); + if (*cc >= OP_THEN && *cc <= OP_THEN_ARG && current_offset != NULL) + *current_offset = 1; + cc = next_opcode(common, cc); + } + } + +return end; +} + +#undef CASE_ITERATOR_PRIVATE_DATA_1 +#undef CASE_ITERATOR_PRIVATE_DATA_2A +#undef CASE_ITERATOR_PRIVATE_DATA_2B +#undef CASE_ITERATOR_TYPE_PRIVATE_DATA_1 +#undef CASE_ITERATOR_TYPE_PRIVATE_DATA_2A +#undef CASE_ITERATOR_TYPE_PRIVATE_DATA_2B + +static SLJIT_INLINE BOOL is_powerof2(unsigned int value) +{ +return (value & (value - 1)) == 0; +} + +static SLJIT_INLINE void set_jumps(jump_list *list, struct sljit_label *label) +{ +while (list) + { + /* sljit_set_label is clever enough to do nothing + if either the jump or the label is NULL. */ + SET_LABEL(list->jump, label); + list = list->next; + } +} + +static SLJIT_INLINE void add_jump(struct sljit_compiler *compiler, jump_list **list, struct sljit_jump *jump) +{ +jump_list *list_item = sljit_alloc_memory(compiler, sizeof(jump_list)); +if (list_item) + { + list_item->next = *list; + list_item->jump = jump; + *list = list_item; + } +} + +static void add_stub(compiler_common *common, struct sljit_jump *start) +{ +DEFINE_COMPILER; +stub_list *list_item = sljit_alloc_memory(compiler, sizeof(stub_list)); + +if (list_item) + { + list_item->start = start; + list_item->quit = LABEL(); + list_item->next = common->stubs; + common->stubs = list_item; + } +} + +static void flush_stubs(compiler_common *common) +{ +DEFINE_COMPILER; +stub_list *list_item = common->stubs; + +while (list_item) + { + JUMPHERE(list_item->start); + add_jump(compiler, &common->stackalloc, JUMP(SLJIT_FAST_CALL)); + JUMPTO(SLJIT_JUMP, list_item->quit); + list_item = list_item->next; + } +common->stubs = NULL; +} + +static void add_label_addr(compiler_common *common, sljit_uw *update_addr) +{ +DEFINE_COMPILER; +label_addr_list *label_addr; + +label_addr = sljit_alloc_memory(compiler, sizeof(label_addr_list)); +if (label_addr == NULL) + return; +label_addr->label = LABEL(); +label_addr->update_addr = update_addr; +label_addr->next = common->label_addrs; +common->label_addrs = label_addr; +} + +static SLJIT_INLINE void count_match(compiler_common *common) +{ +DEFINE_COMPILER; + +OP2(SLJIT_SUB | SLJIT_SET_E, COUNT_MATCH, 0, COUNT_MATCH, 0, SLJIT_IMM, 1); +add_jump(compiler, &common->calllimit, JUMP(SLJIT_ZERO)); +} + +static SLJIT_INLINE void allocate_stack(compiler_common *common, int size) +{ +/* May destroy all locals and registers except TMP2. */ +DEFINE_COMPILER; + +SLJIT_ASSERT(size > 0); +OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw)); +#ifdef DESTROY_REGISTERS +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 12345); +OP1(SLJIT_MOV, TMP3, 0, TMP1, 0); +OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP1, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, TMP1, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP1, 0); +#endif +add_stub(common, CMP(SLJIT_GREATER, STACK_TOP, 0, STACK_LIMIT, 0)); +} + +static SLJIT_INLINE void free_stack(compiler_common *common, int size) +{ +DEFINE_COMPILER; +SLJIT_ASSERT(size > 0); +OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, size * sizeof(sljit_sw)); +} + +static sljit_uw * allocate_read_only_data(compiler_common *common, sljit_uw size) +{ +DEFINE_COMPILER; +sljit_uw *result; + +if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + +result = (sljit_uw *)SLJIT_MALLOC(size + sizeof(sljit_uw), compiler->allocator_data); +if (SLJIT_UNLIKELY(result == NULL)) + { + sljit_set_compiler_memory_error(compiler); + return NULL; + } + +*(void**)result = common->read_only_data_head; +common->read_only_data_head = (void *)result; +return result + 1; +} + +static SLJIT_INLINE void reset_ovector(compiler_common *common, int length) +{ +DEFINE_COMPILER; +struct sljit_label *loop; +sljit_si i; + +/* At this point we can freely use all temporary registers. */ +SLJIT_ASSERT(length > 1); +/* TMP1 returns with begin - 1. */ +OP2(SLJIT_SUB, SLJIT_R0, 0, SLJIT_MEM1(SLJIT_S0), SLJIT_OFFSETOF(jit_arguments, begin), SLJIT_IMM, IN_UCHARS(1)); +if (length < 8) + { + for (i = 1; i < length; i++) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(i), SLJIT_R0, 0); + } +else + { + GET_LOCAL_BASE(SLJIT_R1, 0, OVECTOR_START); + OP1(SLJIT_MOV, SLJIT_R2, 0, SLJIT_IMM, length - 1); + loop = LABEL(); + OP1(SLJIT_MOVU, SLJIT_MEM1(SLJIT_R1), sizeof(sljit_sw), SLJIT_R0, 0); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_R2, 0, SLJIT_R2, 0, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, loop); + } +} + +static SLJIT_INLINE void reset_fast_fail(compiler_common *common) +{ +DEFINE_COMPILER; +sljit_si i; + +SLJIT_ASSERT(common->fast_fail_start_ptr < common->fast_fail_end_ptr); + +OP2(SLJIT_SUB, TMP1, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +for (i = common->fast_fail_start_ptr; i < common->fast_fail_end_ptr; i += sizeof(sljit_sw)) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), i, TMP1, 0); +} + +static SLJIT_INLINE void do_reset_match(compiler_common *common, int length) +{ +DEFINE_COMPILER; +struct sljit_label *loop; +int i; + +SLJIT_ASSERT(length > 1); +/* OVECTOR(1) contains the "string begin - 1" constant. */ +if (length > 2) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1)); +if (length < 8) + { + for (i = 2; i < length; i++) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(i), TMP1, 0); + } +else + { + GET_LOCAL_BASE(TMP2, 0, OVECTOR_START + sizeof(sljit_sw)); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_IMM, length - 2); + loop = LABEL(); + OP1(SLJIT_MOVU, SLJIT_MEM1(TMP2), sizeof(sljit_sw), TMP1, 0); + OP2(SLJIT_SUB | SLJIT_SET_E, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, loop); + } + +OP1(SLJIT_MOV, STACK_TOP, 0, ARGUMENTS, 0); +if (common->mark_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, SLJIT_IMM, 0); +if (common->control_head_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(STACK_TOP), SLJIT_OFFSETOF(jit_arguments, stack)); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->start_ptr); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(STACK_TOP), SLJIT_OFFSETOF(struct sljit_stack, base)); +} + +static sljit_sw SLJIT_CALL do_search_mark(sljit_sw *current, PCRE2_SPTR skip_arg) +{ +while (current != NULL) + { + switch (current[-2]) + { + case type_then_trap: + break; + + case type_mark: + if (PRIV(strcmp)(skip_arg, (PCRE2_SPTR)current[-3]) == 0) + return current[-4]; + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + SLJIT_ASSERT(current > (sljit_sw*)current[-1]); + current = (sljit_sw*)current[-1]; + } +return -1; +} + +static SLJIT_INLINE void copy_ovector(compiler_common *common, int topbracket) +{ +DEFINE_COMPILER; +struct sljit_label *loop; + +/* At this point we can freely use all registers. */ +OP1(SLJIT_MOV, SLJIT_S2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1)); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(1), STR_PTR, 0); + +OP1(SLJIT_MOV, SLJIT_R0, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, SLJIT_S0, 0, SLJIT_MEM1(SLJIT_SP), common->start_ptr); +if (common->mark_ptr != 0) + OP1(SLJIT_MOV, SLJIT_R2, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr); +OP1(SLJIT_MOV_UI, SLJIT_R1, 0, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, oveccount)); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, startchar_ptr), SLJIT_S0, 0); +if (common->mark_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, mark_ptr), SLJIT_R2, 0); +OP2(SLJIT_ADD, SLJIT_R2, 0, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, match_data), + SLJIT_IMM, SLJIT_OFFSETOF(pcre2_match_data, ovector) - sizeof(PCRE2_SIZE)); + +GET_LOCAL_BASE(SLJIT_S0, 0, OVECTOR_START); +OP1(SLJIT_MOV, SLJIT_R0, 0, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(jit_arguments, begin)); + +loop = LABEL(); +OP2(SLJIT_SUB, SLJIT_S1, 0, SLJIT_MEM1(SLJIT_S0), 0, SLJIT_R0, 0); +OP2(SLJIT_ADD, SLJIT_S0, 0, SLJIT_S0, 0, SLJIT_IMM, sizeof(sljit_sw)); +/* Copy the integer value to the output buffer */ +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +OP2(SLJIT_ASHR, SLJIT_S1, 0, SLJIT_S1, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif +SLJIT_ASSERT(sizeof(PCRE2_SIZE) == 4 || sizeof(PCRE2_SIZE) == 8); +if (sizeof(PCRE2_SIZE) == 4) + OP1(SLJIT_MOVU_UI, SLJIT_MEM1(SLJIT_R2), sizeof(PCRE2_SIZE), SLJIT_S1, 0); +else + OP1(SLJIT_MOVU, SLJIT_MEM1(SLJIT_R2), sizeof(PCRE2_SIZE), SLJIT_S1, 0); +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, 1); +JUMPTO(SLJIT_NOT_ZERO, loop); + +/* Calculate the return value, which is the maximum ovector value. */ +if (topbracket > 1) + { + GET_LOCAL_BASE(SLJIT_R0, 0, OVECTOR_START + topbracket * 2 * sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_R1, 0, SLJIT_IMM, topbracket + 1); + + /* OVECTOR(0) is never equal to SLJIT_S2. */ + loop = LABEL(); + OP1(SLJIT_MOVU, SLJIT_R2, 0, SLJIT_MEM1(SLJIT_R0), -(2 * (sljit_sw)sizeof(sljit_sw))); + OP2(SLJIT_SUB, SLJIT_R1, 0, SLJIT_R1, 0, SLJIT_IMM, 1); + CMPTO(SLJIT_EQUAL, SLJIT_R2, 0, SLJIT_S2, 0, loop); + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_R1, 0); + } +else + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, 1); +} + +static SLJIT_INLINE void return_with_partial_match(compiler_common *common, struct sljit_label *quit) +{ +DEFINE_COMPILER; +sljit_si mov_opcode; + +SLJIT_COMPILE_ASSERT(STR_END == SLJIT_S1, str_end_must_be_saved_reg2); +SLJIT_ASSERT(common->start_used_ptr != 0 && common->start_ptr != 0 + && (common->mode == PCRE2_JIT_PARTIAL_SOFT ? common->hit_start != 0 : common->hit_start == 0)); + +OP1(SLJIT_MOV, SLJIT_R1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, SLJIT_R2, 0, SLJIT_MEM1(SLJIT_SP), + common->mode == PCRE2_JIT_PARTIAL_SOFT ? common->hit_start : common->start_ptr); +OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_PARTIAL); + +/* Store match begin and end. */ +OP1(SLJIT_MOV, SLJIT_S0, 0, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(jit_arguments, begin)); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(jit_arguments, startchar_ptr), SLJIT_R2, 0); +OP1(SLJIT_MOV, SLJIT_R1, 0, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(jit_arguments, match_data)); + +mov_opcode = (sizeof(PCRE2_SIZE) == 4) ? SLJIT_MOV_UI : SLJIT_MOV; + +OP2(SLJIT_SUB, SLJIT_R2, 0, SLJIT_R2, 0, SLJIT_S0, 0); +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +OP2(SLJIT_ASHR, SLJIT_R2, 0, SLJIT_R2, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif +OP1(mov_opcode, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(pcre2_match_data, ovector), SLJIT_R2, 0); + +OP2(SLJIT_SUB, STR_END, 0, STR_END, 0, SLJIT_S0, 0); +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +OP2(SLJIT_ASHR, STR_END, 0, STR_END, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif +OP1(mov_opcode, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(pcre2_match_data, ovector) + sizeof(PCRE2_SIZE), STR_END, 0); + +JUMPTO(SLJIT_JUMP, quit); +} + +static SLJIT_INLINE void check_start_used_ptr(compiler_common *common) +{ +/* May destroy TMP1. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +if (common->mode == PCRE2_JIT_PARTIAL_SOFT) + { + /* The value of -1 must be kept for start_used_ptr! */ + OP2(SLJIT_ADD, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, SLJIT_IMM, 1); + /* Jumps if start_used_ptr < STR_PTR, or start_used_ptr == -1. Although overwriting + is not necessary if start_used_ptr == STR_PTR, it does not hurt as well. */ + jump = CMP(SLJIT_LESS_EQUAL, TMP1, 0, STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); + JUMPHERE(jump); + } +else if (common->mode == PCRE2_JIT_PARTIAL_HARD) + { + jump = CMP(SLJIT_LESS_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); + JUMPHERE(jump); + } +} + +static SLJIT_INLINE BOOL char_has_othercase(compiler_common *common, PCRE2_SPTR cc) +{ +/* Detects if the character has an othercase. */ +unsigned int c; + +#ifdef SUPPORT_UNICODE +if (common->utf) + { + GETCHAR(c, cc); + if (c > 127) + { + return c != UCD_OTHERCASE(c); + } +#if PCRE2_CODE_UNIT_WIDTH != 8 + return common->fcc[c] != c; +#endif + } +else +#endif + c = *cc; +return MAX_255(c) ? common->fcc[c] != c : FALSE; +} + +static SLJIT_INLINE unsigned int char_othercase(compiler_common *common, unsigned int c) +{ +/* Returns with the othercase. */ +#ifdef SUPPORT_UNICODE +if (common->utf && c > 127) + { + return UCD_OTHERCASE(c); + } +#endif +return TABLE_GET(c, common->fcc, c); +} + +static unsigned int char_get_othercase_bit(compiler_common *common, PCRE2_SPTR cc) +{ +/* Detects if the character and its othercase has only 1 bit difference. */ +unsigned int c, oc, bit; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +int n; +#endif + +#ifdef SUPPORT_UNICODE +if (common->utf) + { + GETCHAR(c, cc); + if (c <= 127) + oc = common->fcc[c]; + else + { + oc = UCD_OTHERCASE(c); + } + } +else + { + c = *cc; + oc = TABLE_GET(c, common->fcc, c); + } +#else +c = *cc; +oc = TABLE_GET(c, common->fcc, c); +#endif + +SLJIT_ASSERT(c != oc); + +bit = c ^ oc; +/* Optimized for English alphabet. */ +if (c <= 127 && bit == 0x20) + return (0 << 8) | 0x20; + +/* Since c != oc, they must have at least 1 bit difference. */ +if (!is_powerof2(bit)) + return 0; + +#if PCRE2_CODE_UNIT_WIDTH == 8 + +#ifdef SUPPORT_UNICODE +if (common->utf && c > 127) + { + n = GET_EXTRALEN(*cc); + while ((bit & 0x3f) == 0) + { + n--; + bit >>= 6; + } + return (n << 8) | bit; + } +#endif /* SUPPORT_UNICODE */ +return (0 << 8) | bit; + +#elif PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + +#ifdef SUPPORT_UNICODE +if (common->utf && c > 65535) + { + if (bit >= (1 << 10)) + bit >>= 10; + else + return (bit < 256) ? ((2 << 8) | bit) : ((3 << 8) | (bit >> 8)); + } +#endif /* SUPPORT_UNICODE */ +return (bit < 256) ? ((0 << 8) | bit) : ((1 << 8) | (bit >> 8)); + +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16|32] */ +} + +static void check_partial(compiler_common *common, BOOL force) +{ +/* Checks whether a partial matching is occurred. Does not modify registers. */ +DEFINE_COMPILER; +struct sljit_jump *jump = NULL; + +SLJIT_ASSERT(!force || common->mode != PCRE2_JIT_COMPLETE); + +if (common->mode == PCRE2_JIT_COMPLETE) + return; + +if (!force) + jump = CMP(SLJIT_GREATER_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); +else if (common->mode == PCRE2_JIT_PARTIAL_SOFT) + jump = CMP(SLJIT_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, SLJIT_IMM, -1); + +if (common->mode == PCRE2_JIT_PARTIAL_SOFT) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, 0); +else + { + if (common->partialmatchlabel != NULL) + JUMPTO(SLJIT_JUMP, common->partialmatchlabel); + else + add_jump(compiler, &common->partialmatch, JUMP(SLJIT_JUMP)); + } + +if (jump != NULL) + JUMPHERE(jump); +} + +static void check_str_end(compiler_common *common, jump_list **end_reached) +{ +/* Does not affect registers. Usually used in a tight spot. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +if (common->mode == PCRE2_JIT_COMPLETE) + { + add_jump(compiler, end_reached, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + return; + } + +jump = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); +if (common->mode == PCRE2_JIT_PARTIAL_SOFT) + { + add_jump(compiler, end_reached, CMP(SLJIT_GREATER_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, 0); + add_jump(compiler, end_reached, JUMP(SLJIT_JUMP)); + } +else + { + add_jump(compiler, end_reached, CMP(SLJIT_GREATER_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0)); + if (common->partialmatchlabel != NULL) + JUMPTO(SLJIT_JUMP, common->partialmatchlabel); + else + add_jump(compiler, &common->partialmatch, JUMP(SLJIT_JUMP)); + } +JUMPHERE(jump); +} + +static void detect_partial_match(compiler_common *common, jump_list **backtracks) +{ +DEFINE_COMPILER; +struct sljit_jump *jump; + +if (common->mode == PCRE2_JIT_COMPLETE) + { + add_jump(compiler, backtracks, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + return; + } + +/* Partial matching mode. */ +jump = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); +add_jump(compiler, backtracks, CMP(SLJIT_GREATER_EQUAL, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0)); +if (common->mode == PCRE2_JIT_PARTIAL_SOFT) + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, 0); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + } +else + { + if (common->partialmatchlabel != NULL) + JUMPTO(SLJIT_JUMP, common->partialmatchlabel); + else + add_jump(compiler, &common->partialmatch, JUMP(SLJIT_JUMP)); + } +JUMPHERE(jump); +} + +static void peek_char(compiler_common *common, sljit_ui max) +{ +/* Reads the character into TMP1, keeps STR_PTR. +Does not check STR_END. TMP2 Destroyed. */ +DEFINE_COMPILER; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +struct sljit_jump *jump; +#endif + +SLJIT_UNUSED_ARG(max); + +OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { + if (max < 128) return; + + jump = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + add_jump(compiler, &common->utfreadchar, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + JUMPHERE(jump); + } +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 */ + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf) + { + if (max < 0xd800) return; + + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, 0xd800); + jump = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0xdc00 - 0xd800 - 1); + /* TMP2 contains the high surrogate. */ + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x40); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 10); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3ff); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + JUMPHERE(jump); + } +#endif +} + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + +static BOOL is_char7_bitset(const sljit_ub *bitset, BOOL nclass) +{ +/* Tells whether the character codes below 128 are enough +to determine a match. */ +const sljit_ub value = nclass ? 0xff : 0; +const sljit_ub *end = bitset + 32; + +bitset += 16; +do + { + if (*bitset++ != value) + return FALSE; + } +while (bitset < end); +return TRUE; +} + +static void read_char7_type(compiler_common *common, BOOL full_read) +{ +/* Reads the precise character type of a character into TMP1, if the character +is less than 128. Otherwise it returns with zero. Does not check STR_END. The +full_read argument tells whether characters above max are accepted or not. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +SLJIT_ASSERT(common->utf); + +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), 0); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP2), common->ctypes); + +if (full_read) + { + jump = CMP(SLJIT_LESS, TMP2, 0, SLJIT_IMM, 0xc0); + OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + JUMPHERE(jump); + } +} + +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 */ + +static void read_char_range(compiler_common *common, sljit_ui min, sljit_ui max, BOOL update_str_ptr) +{ +/* Reads the precise value of a character into TMP1, if the character is +between min and max (c >= min && c <= max). Otherwise it returns with a value +outside the range. Does not check STR_END. */ +DEFINE_COMPILER; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +struct sljit_jump *jump; +#endif +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +struct sljit_jump *jump2; +#endif + +SLJIT_UNUSED_ARG(update_str_ptr); +SLJIT_UNUSED_ARG(min); +SLJIT_UNUSED_ARG(max); +SLJIT_ASSERT(min <= max); + +OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { + if (max < 128 && !update_str_ptr) return; + + jump = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0); + if (min >= 0x10000) + { + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, 0xf0); + if (update_str_ptr) + OP1(SLJIT_MOV_UB, RETURN_ADDR, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + jump2 = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0x7); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(2)); + if (!update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(3)); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + JUMPHERE(jump2); + if (update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, RETURN_ADDR, 0); + } + else if (min >= 0x800 && max <= 0xffff) + { + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, 0xe0); + if (update_str_ptr) + OP1(SLJIT_MOV_UB, RETURN_ADDR, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + jump2 = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0xf); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); + if (!update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + JUMPHERE(jump2); + if (update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, RETURN_ADDR, 0); + } + else if (max >= 0x800) + add_jump(compiler, (max < 0x10000) ? &common->utfreadchar16 : &common->utfreadchar, JUMP(SLJIT_FAST_CALL)); + else if (max < 128) + { + OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + } + else + { + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + if (!update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + else + OP1(SLJIT_MOV_UB, RETURN_ADDR, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + if (update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, RETURN_ADDR, 0); + } + JUMPHERE(jump); + } +#endif + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf) + { + if (max >= 0x10000) + { + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, 0xd800); + jump = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0xdc00 - 0xd800 - 1); + /* TMP2 contains the high surrogate. */ + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x40); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 10); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3ff); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + JUMPHERE(jump); + return; + } + + if (max < 0xd800 && !update_str_ptr) return; + + /* Skip low surrogate if necessary. */ + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, 0xd800); + jump = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0xdc00 - 0xd800 - 1); + if (update_str_ptr) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + if (max >= 0xd800) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 0x10000); + JUMPHERE(jump); + } +#endif +} + +static SLJIT_INLINE void read_char(compiler_common *common) +{ +read_char_range(common, 0, READ_CHAR_MAX, TRUE); +} + +static void read_char8_type(compiler_common *common, BOOL update_str_ptr) +{ +/* Reads the character type into TMP1, updates STR_PTR. Does not check STR_END. */ +DEFINE_COMPILER; +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 +struct sljit_jump *jump; +#endif +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +struct sljit_jump *jump2; +#endif + +SLJIT_UNUSED_ARG(update_str_ptr); + +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), 0); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { + /* This can be an extra read in some situations, but hopefully + it is needed in most cases. */ + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP2), common->ctypes); + jump = CMP(SLJIT_LESS, TMP2, 0, SLJIT_IMM, 0xc0); + if (!update_str_ptr) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 6); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); + OP2(SLJIT_OR, TMP2, 0, TMP2, 0, TMP1, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 0); + jump2 = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 255); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP2), common->ctypes); + JUMPHERE(jump2); + } + else + add_jump(compiler, &common->utfreadtype8, JUMP(SLJIT_FAST_CALL)); + JUMPHERE(jump); + return; + } +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 +/* The ctypes array contains only 256 values. */ +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 0); +jump = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 255); +#endif +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP2), common->ctypes); +#if PCRE2_CODE_UNIT_WIDTH != 8 +JUMPHERE(jump); +#endif + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf && update_str_ptr) + { + /* Skip low surrogate if necessary. */ + OP2(SLJIT_SUB, TMP2, 0, TMP2, 0, SLJIT_IMM, 0xd800); + jump = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0xdc00 - 0xd800 - 1); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + JUMPHERE(jump); + } +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 16 */ +} + +static void skip_char_back(compiler_common *common) +{ +/* Goes one character back. Affects STR_PTR and TMP1. Does not check begin. */ +DEFINE_COMPILER; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +#if PCRE2_CODE_UNIT_WIDTH == 8 +struct sljit_label *label; + +if (common->utf) + { + label = LABEL(); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), -IN_UCHARS(1)); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xc0); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0x80, label); + return; + } +#elif PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), -IN_UCHARS(1)); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + /* Skip low surrogate if necessary. */ + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xdc00); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + return; + } +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16] */ +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 */ +OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +} + +static void check_newlinechar(compiler_common *common, int nltype, jump_list **backtracks, BOOL jumpifmatch) +{ +/* Character comes in TMP1. Checks if it is a newline. TMP2 may be destroyed. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +if (nltype == NLTYPE_ANY) + { + add_jump(compiler, &common->anynewline, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, JUMP(jumpifmatch ? SLJIT_NOT_ZERO : SLJIT_ZERO)); + } +else if (nltype == NLTYPE_ANYCRLF) + { + if (jumpifmatch) + { + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR)); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL)); + } + else + { + jump = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL)); + JUMPHERE(jump); + } + } +else + { + SLJIT_ASSERT(nltype == NLTYPE_FIXED && common->newline < 256); + add_jump(compiler, backtracks, CMP(jumpifmatch ? SLJIT_EQUAL : SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, common->newline)); + } +} + +#ifdef SUPPORT_UNICODE + +#if PCRE2_CODE_UNIT_WIDTH == 8 +static void do_utfreadchar(compiler_common *common) +{ +/* Fast decoding a UTF-8 character. TMP1 contains the first byte +of the character (>= 0xc0). Return char value in TMP1, length in TMP2. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); +OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + +/* Searching for the first zero. */ +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800); +jump = JUMP(SLJIT_NOT_ZERO); +/* Two byte sequence. */ +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, IN_UCHARS(2)); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +JUMPHERE(jump); +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); +OP2(SLJIT_XOR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x800); +OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x10000); +jump = JUMP(SLJIT_NOT_ZERO); +/* Three byte sequence. */ +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, IN_UCHARS(3)); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +/* Four byte sequence. */ +JUMPHERE(jump); +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(2)); +OP2(SLJIT_XOR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x10000); +OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(3)); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, IN_UCHARS(4)); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +static void do_utfreadchar16(compiler_common *common) +{ +/* Fast decoding a UTF-8 character. TMP1 contains the first byte +of the character (>= 0xc0). Return value in TMP1. */ +DEFINE_COMPILER; +struct sljit_jump *jump; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); +OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + +/* Searching for the first zero. */ +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x800); +jump = JUMP(SLJIT_NOT_ZERO); +/* Two byte sequence. */ +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +JUMPHERE(jump); +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x400); +OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_NOT_ZERO); +/* This code runs only in 8 bit mode. No need to shift the value. */ +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); +OP2(SLJIT_XOR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x800); +OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 6); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); +/* Three byte sequence. */ +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +static void do_utfreadtype8(compiler_common *common) +{ +/* Fast decoding a UTF-8 character type. TMP2 contains the first byte +of the character (>= 0xc0). Return value in TMP1. */ +DEFINE_COMPILER; +struct sljit_jump *jump; +struct sljit_jump *compare; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); + +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, 0x20); +jump = JUMP(SLJIT_NOT_ZERO); +/* Two byte sequence. */ +OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 0x1f); +/* The upper 5 bits are known at this point. */ +compare = CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, 0x3); +OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 6); +OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x3f); +OP2(SLJIT_OR, TMP2, 0, TMP2, 0, TMP1, 0); +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP2), common->ctypes); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +JUMPHERE(compare); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 0); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +/* We only have types for characters less than 256. */ +JUMPHERE(jump); +OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(utf8_table4) - 0xc0); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 0); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + +/* UCD_BLOCK_SIZE must be 128 (see the assert below). */ +#define UCD_BLOCK_MASK 127 +#define UCD_BLOCK_SHIFT 7 + +static void do_getucd(compiler_common *common) +{ +/* Search the UCD record for the character comes in TMP1. +Returns chartype in TMP1 and UCD offset in TMP2. */ +DEFINE_COMPILER; + +SLJIT_ASSERT(UCD_BLOCK_SIZE == 128 && sizeof(ucd_record) == 8); + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP2(SLJIT_LSHR, TMP2, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_SHIFT); +OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(ucd_stage1)); +OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_MASK); +OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, UCD_BLOCK_SHIFT); +OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, TMP2, 0); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_stage2)); +OP1(SLJIT_MOV_UH, TMP2, 0, SLJIT_MEM2(TMP2, TMP1), 1); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, chartype)); +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM2(TMP1, TMP2), 3); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +#endif /* SUPPORT_UNICODE */ + +static SLJIT_INLINE struct sljit_label *mainloop_entry(compiler_common *common, BOOL hascrorlf, uint32_t overall_options) +{ +DEFINE_COMPILER; +struct sljit_label *mainloop; +struct sljit_label *newlinelabel = NULL; +struct sljit_jump *start; +struct sljit_jump *end = NULL; +struct sljit_jump *end2 = NULL; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +struct sljit_jump *singlechar; +#endif +jump_list *newline = NULL; +BOOL newlinecheck = FALSE; +BOOL readuchar = FALSE; + +if (!(hascrorlf || (overall_options & PCRE2_FIRSTLINE) != 0) + && (common->nltype == NLTYPE_ANY || common->nltype == NLTYPE_ANYCRLF || common->newline > 255)) + newlinecheck = TRUE; + +SLJIT_ASSERT(common->forced_quit_label == NULL); + +if ((overall_options & PCRE2_FIRSTLINE) != 0) + { + /* Search for the end of the first line. */ + SLJIT_ASSERT(common->match_end_ptr != 0); + OP1(SLJIT_MOV, TMP3, 0, STR_PTR, 0); + + if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + mainloop = LABEL(); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + end = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-1)); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff, mainloop); + CMPTO(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, common->newline & 0xff, mainloop); + JUMPHERE(end); + OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + } + else + { + end = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + mainloop = LABEL(); + /* Continual stores does not cause data dependency. */ + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, STR_PTR, 0); + read_char_range(common, common->nlmin, common->nlmax, TRUE); + check_newlinechar(common, common->nltype, &newline, TRUE); + CMPTO(SLJIT_LESS, STR_PTR, 0, STR_END, 0, mainloop); + JUMPHERE(end); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, STR_PTR, 0); + set_jumps(newline, LABEL()); + } + + OP1(SLJIT_MOV, STR_PTR, 0, TMP3, 0); + } +else if ((overall_options & PCRE2_USE_OFFSET_LIMIT) != 0) + { + /* Check whether offset limit is set and valid. */ + SLJIT_ASSERT(common->match_end_ptr != 0); + + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, offset_limit)); + OP1(SLJIT_MOV, TMP2, 0, STR_END, 0); + end = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, (sljit_sw) PCRE2_UNSET); + OP1(SLJIT_MOV, TMP2, 0, ARGUMENTS, 0); +#if PCRE2_CODE_UNIT_WIDTH == 16 + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); +#elif PCRE2_CODE_UNIT_WIDTH == 32 + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 2); +#endif + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, begin)); + OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP1, 0); + end2 = CMP(SLJIT_LESS_EQUAL, TMP2, 0, STR_END, 0); + OP1(SLJIT_MOV, TMP2, 0, STR_END, 0); + JUMPHERE(end2); + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_NOMATCH); + add_jump(compiler, &common->forced_quit, CMP(SLJIT_LESS, TMP2, 0, STR_PTR, 0)); + JUMPHERE(end); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, TMP2, 0); + } + +start = JUMP(SLJIT_JUMP); + +if (newlinecheck) + { + newlinelabel = LABEL(); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + end = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, common->newline & 0xff); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + end2 = JUMP(SLJIT_JUMP); + } + +mainloop = LABEL(); + +/* Increasing the STR_PTR here requires one less jump in the most common case. */ +#ifdef SUPPORT_UNICODE +if (common->utf) readuchar = TRUE; +#endif +if (newlinecheck) readuchar = TRUE; + +if (readuchar) + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + +if (newlinecheck) + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff, newlinelabel); + +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { + singlechar = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + JUMPHERE(singlechar); + } +#elif PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf) + { + singlechar = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + JUMPHERE(singlechar); + } +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16] */ +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 */ +JUMPHERE(start); + +if (newlinecheck) + { + JUMPHERE(end); + JUMPHERE(end2); + } + +return mainloop; +} + +#define MAX_N_CHARS 16 +#define MAX_DIFF_CHARS 6 + +static SLJIT_INLINE void add_prefix_char(PCRE2_UCHAR chr, PCRE2_UCHAR *chars) +{ +PCRE2_UCHAR i, len; + +len = chars[0]; +if (len == 255) + return; + +if (len == 0) + { + chars[0] = 1; + chars[1] = chr; + return; + } + +for (i = len; i > 0; i--) + if (chars[i] == chr) + return; + +if (len >= MAX_DIFF_CHARS - 1) + { + chars[0] = 255; + return; + } + +len++; +chars[len] = chr; +chars[0] = len; +} + +static int scan_prefix(compiler_common *common, PCRE2_SPTR cc, PCRE2_UCHAR *chars, int max_chars, uint32_t *rec_count) +{ +/* Recursive function, which scans prefix literals. */ +BOOL last, any, class, caseless; +int len, repeat, len_save, consumed = 0; +sljit_ui chr; /* Any unicode character. */ +sljit_ub *bytes, *bytes_end, byte; +PCRE2_SPTR alternative, cc_save, oc; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +PCRE2_UCHAR othercase[8]; +#elif defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 16 +PCRE2_UCHAR othercase[2]; +#else +PCRE2_UCHAR othercase[1]; +#endif + +repeat = 1; +while (TRUE) + { + if (*rec_count == 0) + return 0; + (*rec_count)--; + + last = TRUE; + any = FALSE; + class = FALSE; + caseless = FALSE; + + switch (*cc) + { + case OP_CHARI: + caseless = TRUE; + case OP_CHAR: + last = FALSE; + cc++; + break; + + case OP_SOD: + case OP_SOM: + case OP_SET_SOM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + /* Zero width assertions. */ + cc++; + continue; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + cc = bracketend(cc); + continue; + + case OP_PLUSI: + case OP_MINPLUSI: + case OP_POSPLUSI: + caseless = TRUE; + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + cc++; + break; + + case OP_EXACTI: + caseless = TRUE; + case OP_EXACT: + repeat = GET2(cc, 1); + last = FALSE; + cc += 1 + IMM2_SIZE; + break; + + case OP_QUERYI: + case OP_MINQUERYI: + case OP_POSQUERYI: + caseless = TRUE; + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + len = 1; + cc++; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(*cc)) len += GET_EXTRALEN(*cc); +#endif + max_chars = scan_prefix(common, cc + len, chars, max_chars, rec_count); + if (max_chars == 0) + return consumed; + last = FALSE; + break; + + case OP_KET: + cc += 1 + LINK_SIZE; + continue; + + case OP_ALT: + cc += GET(cc, 1); + continue; + + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRA: + case OP_BRAPOS: + case OP_CBRA: + case OP_CBRAPOS: + alternative = cc + GET(cc, 1); + while (*alternative == OP_ALT) + { + max_chars = scan_prefix(common, alternative + 1 + LINK_SIZE, chars, max_chars, rec_count); + if (max_chars == 0) + return consumed; + alternative += GET(alternative, 1); + } + + if (*cc == OP_CBRA || *cc == OP_CBRAPOS) + cc += IMM2_SIZE; + cc += 1 + LINK_SIZE; + continue; + + case OP_CLASS: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && !is_char7_bitset((const sljit_ub *)(cc + 1), FALSE)) return consumed; +#endif + class = TRUE; + break; + + case OP_NCLASS: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) return consumed; +#endif + class = TRUE; + break; + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) return consumed; +#endif + any = TRUE; + cc += GET(cc, 1); + break; +#endif + + case OP_DIGIT: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && !is_char7_bitset((const sljit_ub *)common->ctypes - cbit_length + cbit_digit, FALSE)) + return consumed; +#endif + any = TRUE; + cc++; + break; + + case OP_WHITESPACE: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && !is_char7_bitset((const sljit_ub *)common->ctypes - cbit_length + cbit_space, FALSE)) + return consumed; +#endif + any = TRUE; + cc++; + break; + + case OP_WORDCHAR: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && !is_char7_bitset((const sljit_ub *)common->ctypes - cbit_length + cbit_word, FALSE)) + return consumed; +#endif + any = TRUE; + cc++; + break; + + case OP_NOT: + case OP_NOTI: + cc++; + /* Fall through. */ + case OP_NOT_DIGIT: + case OP_NOT_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_ANY: + case OP_ALLANY: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) return consumed; +#endif + any = TRUE; + cc++; + break; + +#ifdef SUPPORT_UNICODE + case OP_NOTPROP: + case OP_PROP: +#if PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) return consumed; +#endif + any = TRUE; + cc += 1 + 2; + break; +#endif + + case OP_TYPEEXACT: + repeat = GET2(cc, 1); + cc += 1 + IMM2_SIZE; + continue; + + case OP_NOTEXACT: + case OP_NOTEXACTI: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) return consumed; +#endif + any = TRUE; + repeat = GET2(cc, 1); + cc += 1 + IMM2_SIZE + 1; + break; + + default: + return consumed; + } + + if (any) + { + do + { + chars[0] = 255; + + consumed++; + if (--max_chars == 0) + return consumed; + chars += MAX_DIFF_CHARS; + } + while (--repeat > 0); + + repeat = 1; + continue; + } + + if (class) + { + bytes = (sljit_ub*) (cc + 1); + cc += 1 + 32 / sizeof(PCRE2_UCHAR); + + switch (*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPOSSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSQUERY: + max_chars = scan_prefix(common, cc + 1, chars, max_chars, rec_count); + if (max_chars == 0) + return consumed; + break; + + default: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + repeat = GET2(cc, 1); + if (repeat <= 0) + return consumed; + break; + } + + do + { + if (bytes[31] & 0x80) + chars[0] = 255; + else if (chars[0] != 255) + { + bytes_end = bytes + 32; + chr = 0; + do + { + byte = *bytes++; + SLJIT_ASSERT((chr & 0x7) == 0); + if (byte == 0) + chr += 8; + else + { + do + { + if ((byte & 0x1) != 0) + add_prefix_char(chr, chars); + byte >>= 1; + chr++; + } + while (byte != 0); + chr = (chr + 7) & ~7; + } + } + while (chars[0] != 255 && bytes < bytes_end); + bytes = bytes_end - 32; + } + + consumed++; + if (--max_chars == 0) + return consumed; + chars += MAX_DIFF_CHARS; + } + while (--repeat > 0); + + switch (*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPOSSTAR: + return consumed; + + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSQUERY: + cc++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + if (GET2(cc, 1) != GET2(cc, 1 + IMM2_SIZE)) + return consumed; + cc += 1 + 2 * IMM2_SIZE; + break; + } + + repeat = 1; + continue; + } + + len = 1; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(*cc)) len += GET_EXTRALEN(*cc); +#endif + + if (caseless && char_has_othercase(common, cc)) + { +#ifdef SUPPORT_UNICODE + if (common->utf) + { + GETCHAR(chr, cc); + if ((int)PRIV(ord2utf)(char_othercase(common, chr), othercase) != len) + return consumed; + } + else +#endif + { + chr = *cc; + othercase[0] = TABLE_GET(chr, common->fcc, chr); + } + } + else + { + caseless = FALSE; + othercase[0] = 0; /* Stops compiler warning - PH */ + } + + len_save = len; + cc_save = cc; + while (TRUE) + { + oc = othercase; + do + { + chr = *cc; + add_prefix_char(*cc, chars); + + if (caseless) + add_prefix_char(*oc, chars); + + len--; + consumed++; + if (--max_chars == 0) + return consumed; + chars += MAX_DIFF_CHARS; + cc++; + oc++; + } + while (len > 0); + + if (--repeat == 0) + break; + + len = len_save; + cc = cc_save; + } + + repeat = 1; + if (last) + return consumed; + } +} + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +static sljit_si character_to_int32(PCRE2_UCHAR chr) +{ +sljit_si value = (sljit_si)chr; +#if PCRE2_CODE_UNIT_WIDTH == 8 +#define SSE2_COMPARE_TYPE_INDEX 0 +return (value << 24) | (value << 16) | (value << 8) | value; +#elif PCRE2_CODE_UNIT_WIDTH == 16 +#define SSE2_COMPARE_TYPE_INDEX 1 +return (value << 16) | value; +#elif PCRE2_CODE_UNIT_WIDTH == 32 +#define SSE2_COMPARE_TYPE_INDEX 2 +return value; +#else +#error "Unsupported unit width" +#endif +} + +static SLJIT_INLINE void fast_forward_first_char2_sse2(compiler_common *common, PCRE2_UCHAR char1, PCRE2_UCHAR char2) +{ +DEFINE_COMPILER; +struct sljit_label *start; +struct sljit_jump *quit[3]; +struct sljit_jump *nomatch; +sljit_ub instruction[8]; +sljit_si tmp1_ind = sljit_get_register_index(TMP1); +sljit_si tmp2_ind = sljit_get_register_index(TMP2); +sljit_si str_ptr_ind = sljit_get_register_index(STR_PTR); +BOOL load_twice = FALSE; +PCRE2_UCHAR bit; + +bit = char1 ^ char2; +if (!is_powerof2(bit)) + bit = 0; + +if ((char1 != char2) && bit == 0) + load_twice = TRUE; + +quit[0] = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + +/* First part (unaligned start) */ + +OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, character_to_int32(char1 | bit)); + +SLJIT_ASSERT(tmp1_ind < 8 && tmp2_ind == 1); + +/* MOVD xmm, r/m32 */ +instruction[0] = 0x66; +instruction[1] = 0x0f; +instruction[2] = 0x6e; +instruction[3] = 0xc0 | (2 << 3) | tmp1_ind; +sljit_emit_op_custom(compiler, instruction, 4); + +if (char1 != char2) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, character_to_int32(bit != 0 ? bit : char2)); + + /* MOVD xmm, r/m32 */ + instruction[3] = 0xc0 | (3 << 3) | tmp1_ind; + sljit_emit_op_custom(compiler, instruction, 4); + } + +/* PSHUFD xmm1, xmm2/m128, imm8 */ +instruction[2] = 0x70; +instruction[3] = 0xc0 | (2 << 3) | 2; +instruction[4] = 0; +sljit_emit_op_custom(compiler, instruction, 5); + +if (char1 != char2) + { + /* PSHUFD xmm1, xmm2/m128, imm8 */ + instruction[3] = 0xc0 | (3 << 3) | 3; + instruction[4] = 0; + sljit_emit_op_custom(compiler, instruction, 5); + } + +OP2(SLJIT_AND, TMP2, 0, STR_PTR, 0, SLJIT_IMM, 0xf); +OP2(SLJIT_AND, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, ~0xf); + +/* MOVDQA xmm1, xmm2/m128 */ +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +if (str_ptr_ind < 8) + { + instruction[2] = 0x6f; + instruction[3] = (0 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + + if (load_twice) + { + instruction[3] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + } + } +else + { + instruction[1] = 0x41; + instruction[2] = 0x0f; + instruction[3] = 0x6f; + instruction[4] = (0 << 3) | (str_ptr_ind & 0x7); + sljit_emit_op_custom(compiler, instruction, 5); + + if (load_twice) + { + instruction[4] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 5); + } + instruction[1] = 0x0f; + } + +#else + +instruction[2] = 0x6f; +instruction[3] = (0 << 3) | str_ptr_ind; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + instruction[3] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + } + +#endif + +if (bit != 0) + { + /* POR xmm1, xmm2/m128 */ + instruction[2] = 0xeb; + instruction[3] = 0xc0 | (0 << 3) | 3; + sljit_emit_op_custom(compiler, instruction, 4); + } + +/* PCMPEQB/W/D xmm1, xmm2/m128 */ +instruction[2] = 0x74 + SSE2_COMPARE_TYPE_INDEX; +instruction[3] = 0xc0 | (0 << 3) | 2; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + instruction[3] = 0xc0 | (1 << 3) | 3; + sljit_emit_op_custom(compiler, instruction, 4); + } + +/* PMOVMSKB reg, xmm */ +instruction[2] = 0xd7; +instruction[3] = 0xc0 | (tmp1_ind << 3) | 0; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + OP1(SLJIT_MOV, TMP3, 0, TMP2, 0); + instruction[3] = 0xc0 | (tmp2_ind << 3) | 1; + sljit_emit_op_custom(compiler, instruction, 4); + + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + OP1(SLJIT_MOV, TMP2, 0, TMP3, 0); + } + +OP2(SLJIT_ASHR, TMP1, 0, TMP1, 0, TMP2, 0); + +/* BSF r32, r/m32 */ +instruction[0] = 0x0f; +instruction[1] = 0xbc; +instruction[2] = 0xc0 | (tmp1_ind << 3) | tmp1_ind; +sljit_emit_op_custom(compiler, instruction, 3); + +nomatch = JUMP(SLJIT_ZERO); + +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); +quit[1] = JUMP(SLJIT_JUMP); + +JUMPHERE(nomatch); + +start = LABEL(); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, 16); +quit[2] = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + +/* Second part (aligned) */ + +instruction[0] = 0x66; +instruction[1] = 0x0f; + +/* MOVDQA xmm1, xmm2/m128 */ +#if (defined SLJIT_CONFIG_X86_64 && SLJIT_CONFIG_X86_64) + +if (str_ptr_ind < 8) + { + instruction[2] = 0x6f; + instruction[3] = (0 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + + if (load_twice) + { + instruction[3] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + } + } +else + { + instruction[1] = 0x41; + instruction[2] = 0x0f; + instruction[3] = 0x6f; + instruction[4] = (0 << 3) | (str_ptr_ind & 0x7); + sljit_emit_op_custom(compiler, instruction, 5); + + if (load_twice) + { + instruction[4] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 5); + } + instruction[1] = 0x0f; + } + +#else + +instruction[2] = 0x6f; +instruction[3] = (0 << 3) | str_ptr_ind; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + instruction[3] = (1 << 3) | str_ptr_ind; + sljit_emit_op_custom(compiler, instruction, 4); + } + +#endif + +if (bit != 0) + { + /* POR xmm1, xmm2/m128 */ + instruction[2] = 0xeb; + instruction[3] = 0xc0 | (0 << 3) | 3; + sljit_emit_op_custom(compiler, instruction, 4); + } + +/* PCMPEQB/W/D xmm1, xmm2/m128 */ +instruction[2] = 0x74 + SSE2_COMPARE_TYPE_INDEX; +instruction[3] = 0xc0 | (0 << 3) | 2; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + instruction[3] = 0xc0 | (1 << 3) | 3; + sljit_emit_op_custom(compiler, instruction, 4); + } + +/* PMOVMSKB reg, xmm */ +instruction[2] = 0xd7; +instruction[3] = 0xc0 | (tmp1_ind << 3) | 0; +sljit_emit_op_custom(compiler, instruction, 4); + +if (load_twice) + { + instruction[3] = 0xc0 | (tmp2_ind << 3) | 1; + sljit_emit_op_custom(compiler, instruction, 4); + + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, TMP2, 0); + } + +/* BSF r32, r/m32 */ +instruction[0] = 0x0f; +instruction[1] = 0xbc; +instruction[2] = 0xc0 | (tmp1_ind << 3) | tmp1_ind; +sljit_emit_op_custom(compiler, instruction, 3); + +JUMPTO(SLJIT_ZERO, start); + +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + +start = LABEL(); +SET_LABEL(quit[0], start); +SET_LABEL(quit[1], start); +SET_LABEL(quit[2], start); +} + +#undef SSE2_COMPARE_TYPE_INDEX + +#endif + +static void fast_forward_first_char2(compiler_common *common, PCRE2_UCHAR char1, PCRE2_UCHAR char2, sljit_si offset) +{ +DEFINE_COMPILER; +struct sljit_label *start; +struct sljit_jump *quit; +struct sljit_jump *found; +PCRE2_UCHAR mask; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +struct sljit_label *utf_start = NULL; +struct sljit_jump *utf_quit = NULL; +#endif +BOOL has_match_end = (common->match_end_ptr != 0); + +if (offset > 0) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(offset)); + +if (has_match_end) + { + OP1(SLJIT_MOV, TMP3, 0, STR_END, 0); + + OP2(SLJIT_ADD, STR_END, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr, SLJIT_IMM, IN_UCHARS(offset + 1)); +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + if (sljit_x86_is_cmov_available()) + { + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_END, 0, TMP3, 0); + sljit_x86_emit_cmov(compiler, SLJIT_GREATER, STR_END, TMP3, 0); + } +#endif + { + quit = CMP(SLJIT_LESS_EQUAL, STR_END, 0, TMP3, 0); + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + JUMPHERE(quit); + } + } + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +if (common->utf && offset > 0) + utf_start = LABEL(); +#endif + +#if (defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86) + +/* SSE2 accelerated first character search. */ + +if (sljit_x86_is_sse2_available()) + { + fast_forward_first_char2_sse2(common, char1, char2); + + SLJIT_ASSERT(common->mode == PCRE2_JIT_COMPLETE || offset == 0); + if (common->mode == PCRE2_JIT_COMPLETE) + { + /* In complete mode, we don't need to run a match when STR_PTR == STR_END. */ + SLJIT_ASSERT(common->forced_quit_label == NULL); + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_NOMATCH); + add_jump(compiler, &common->forced_quit, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf && offset > 0) + { + SLJIT_ASSERT(common->mode == PCRE2_JIT_COMPLETE); + + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-offset)); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +#if PCRE2_CODE_UNIT_WIDTH == 8 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xc0); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0x80, utf_start); +#elif PCRE2_CODE_UNIT_WIDTH == 16 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0xdc00, utf_start); +#else +#error "Unknown code width" +#endif + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + } +#endif + + if (offset > 0) + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(offset)); + } + else if (sljit_x86_is_cmov_available()) + { + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_PTR, 0, STR_END, 0); + sljit_x86_emit_cmov(compiler, SLJIT_GREATER_EQUAL, STR_PTR, has_match_end ? SLJIT_MEM1(SLJIT_SP) : STR_END, has_match_end ? common->match_end_ptr : 0); + } + else + { + quit = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); + OP1(SLJIT_MOV, STR_PTR, 0, has_match_end ? SLJIT_MEM1(SLJIT_SP) : STR_END, has_match_end ? common->match_end_ptr : 0); + JUMPHERE(quit); + } + + if (has_match_end) + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + return; + } + +#endif + +quit = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + +start = LABEL(); +OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + +if (char1 == char2) + found = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, char1); +else + { + mask = char1 ^ char2; + if (is_powerof2(mask)) + { + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, mask); + found = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, char1 | mask); + } + else + { + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char1); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, char2); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + found = JUMP(SLJIT_NOT_ZERO); + } + } + +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +CMPTO(SLJIT_LESS, STR_PTR, 0, STR_END, 0, start); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +if (common->utf && offset > 0) + utf_quit = JUMP(SLJIT_JUMP); +#endif + +JUMPHERE(found); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +if (common->utf && offset > 0) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-offset)); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +#if PCRE2_CODE_UNIT_WIDTH == 8 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xc0); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0x80, utf_start); +#elif PCRE2_CODE_UNIT_WIDTH == 16 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0xdc00, utf_start); +#else +#error "Unknown code width" +#endif + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + JUMPHERE(utf_quit); + } +#endif + +JUMPHERE(quit); + +if (has_match_end) + { + quit = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + if (offset > 0) + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(offset)); + JUMPHERE(quit); + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + } + +if (offset > 0) + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(offset)); +} + +static SLJIT_INLINE BOOL fast_forward_first_n_chars(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_label *start; +struct sljit_jump *quit; +struct sljit_jump *match; +/* bytes[0] represent the number of characters between 0 +and MAX_N_BYTES - 1, 255 represents any character. */ +PCRE2_UCHAR chars[MAX_N_CHARS * MAX_DIFF_CHARS]; +sljit_si offset; +PCRE2_UCHAR mask; +PCRE2_UCHAR *char_set, *char_set_end; +int i, max, from; +int range_right = -1, range_len; +sljit_ub *update_table = NULL; +BOOL in_range; +uint32_t rec_count; + +for (i = 0; i < MAX_N_CHARS; i++) + chars[i * MAX_DIFF_CHARS] = 0; + +rec_count = 10000; +max = scan_prefix(common, common->start, chars, MAX_N_CHARS, &rec_count); + +if (max < 1) + return FALSE; + +in_range = FALSE; +/* Prevent compiler "uninitialized" warning */ +from = 0; +range_len = 4 /* minimum length */ - 1; +for (i = 0; i <= max; i++) + { + if (in_range && (i - from) > range_len && (chars[(i - 1) * MAX_DIFF_CHARS] < 255)) + { + range_len = i - from; + range_right = i - 1; + } + + if (i < max && chars[i * MAX_DIFF_CHARS] < 255) + { + SLJIT_ASSERT(chars[i * MAX_DIFF_CHARS] > 0); + if (!in_range) + { + in_range = TRUE; + from = i; + } + } + else + in_range = FALSE; + } + +if (range_right >= 0) + { + update_table = (sljit_ub *)allocate_read_only_data(common, 256); + if (update_table == NULL) + return TRUE; + memset(update_table, IN_UCHARS(range_len), 256); + + for (i = 0; i < range_len; i++) + { + char_set = chars + ((range_right - i) * MAX_DIFF_CHARS); + SLJIT_ASSERT(char_set[0] > 0 && char_set[0] < 255); + char_set_end = char_set + char_set[0]; + char_set++; + while (char_set <= char_set_end) + { + if (update_table[(*char_set) & 0xff] > IN_UCHARS(i)) + update_table[(*char_set) & 0xff] = IN_UCHARS(i); + char_set++; + } + } + } + +offset = -1; +/* Scan forward. */ +for (i = 0; i < max; i++) + { + if (offset == -1) + { + if (chars[i * MAX_DIFF_CHARS] <= 2) + offset = i; + } + else if (chars[offset * MAX_DIFF_CHARS] == 2 && chars[i * MAX_DIFF_CHARS] <= 2) + { + if (chars[i * MAX_DIFF_CHARS] == 1) + offset = i; + else + { + mask = chars[offset * MAX_DIFF_CHARS + 1] ^ chars[offset * MAX_DIFF_CHARS + 2]; + if (!is_powerof2(mask)) + { + mask = chars[i * MAX_DIFF_CHARS + 1] ^ chars[i * MAX_DIFF_CHARS + 2]; + if (is_powerof2(mask)) + offset = i; + } + } + } + } + +if (range_right < 0) + { + if (offset < 0) + return FALSE; + SLJIT_ASSERT(chars[offset * MAX_DIFF_CHARS] >= 1 && chars[offset * MAX_DIFF_CHARS] <= 2); + /* Works regardless the value is 1 or 2. */ + mask = chars[offset * MAX_DIFF_CHARS + chars[offset * MAX_DIFF_CHARS]]; + fast_forward_first_char2(common, chars[offset * MAX_DIFF_CHARS + 1], mask, offset); + return TRUE; + } + +if (range_right == offset) + offset = -1; + +SLJIT_ASSERT(offset == -1 || (chars[offset * MAX_DIFF_CHARS] >= 1 && chars[offset * MAX_DIFF_CHARS] <= 2)); + +max -= 1; +SLJIT_ASSERT(max > 0); +if (common->match_end_ptr != 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + OP1(SLJIT_MOV, TMP3, 0, STR_END, 0); + OP2(SLJIT_SUB, STR_END, 0, STR_END, 0, SLJIT_IMM, IN_UCHARS(max)); + quit = CMP(SLJIT_LESS_EQUAL, STR_END, 0, TMP1, 0); + OP1(SLJIT_MOV, STR_END, 0, TMP1, 0); + JUMPHERE(quit); + } +else + OP2(SLJIT_SUB, STR_END, 0, STR_END, 0, SLJIT_IMM, IN_UCHARS(max)); + +SLJIT_ASSERT(range_right >= 0); + +#if !(defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +OP1(SLJIT_MOV, RETURN_ADDR, 0, SLJIT_IMM, (sljit_sw)update_table); +#endif + +start = LABEL(); +quit = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + +#if PCRE2_CODE_UNIT_WIDTH == 8 || (defined SLJIT_LITTLE_ENDIAN && SLJIT_LITTLE_ENDIAN) +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(range_right)); +#else +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(range_right + 1) - 1); +#endif + +#if !(defined SLJIT_CONFIG_X86_32 && SLJIT_CONFIG_X86_32) +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM2(RETURN_ADDR, TMP1), 0); +#else +OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)update_table); +#endif +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); +CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, 0, start); + +if (offset >= 0) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(offset)); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + + if (chars[offset * MAX_DIFF_CHARS] == 1) + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, chars[offset * MAX_DIFF_CHARS + 1], start); + else + { + mask = chars[offset * MAX_DIFF_CHARS + 1] ^ chars[offset * MAX_DIFF_CHARS + 2]; + if (is_powerof2(mask)) + { + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, mask); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, chars[offset * MAX_DIFF_CHARS + 1] | mask, start); + } + else + { + match = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, chars[offset * MAX_DIFF_CHARS + 1]); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, chars[offset * MAX_DIFF_CHARS + 2], start); + JUMPHERE(match); + } + } + } + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 +if (common->utf && offset != 0) + { + if (offset < 0) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + } + else + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-1)); +#if PCRE2_CODE_UNIT_WIDTH == 8 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xc0); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0x80, start); +#elif PCRE2_CODE_UNIT_WIDTH == 16 + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0xdc00, start); +#else +#error "Unknown code width" +#endif + if (offset < 0) + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + } +#endif + +if (offset >= 0) + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +JUMPHERE(quit); + +if (common->match_end_ptr != 0) + { + if (range_right >= 0) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + if (range_right >= 0) + { + quit = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP1, 0); + OP1(SLJIT_MOV, STR_PTR, 0, TMP1, 0); + JUMPHERE(quit); + } + } +else + OP2(SLJIT_ADD, STR_END, 0, STR_END, 0, SLJIT_IMM, IN_UCHARS(max)); +return TRUE; +} + +#undef MAX_N_CHARS +#undef MAX_N_BYTES + +static SLJIT_INLINE void fast_forward_first_char(compiler_common *common, PCRE2_UCHAR first_char, BOOL caseless) +{ +PCRE2_UCHAR oc; + +oc = first_char; +if (caseless) + { + oc = TABLE_GET(first_char, common->fcc, first_char); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (first_char > 127 && common->utf) + oc = UCD_OTHERCASE(first_char); +#endif + } + +fast_forward_first_char2(common, first_char, oc, 0); +} + +static SLJIT_INLINE void fast_forward_newline(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_label *loop; +struct sljit_jump *lastchar; +struct sljit_jump *firstchar; +struct sljit_jump *quit; +struct sljit_jump *foundcr = NULL; +struct sljit_jump *notfoundnl; +jump_list *newline = NULL; + +if (common->match_end_ptr != 0) + { + OP1(SLJIT_MOV, TMP3, 0, STR_END, 0); + OP1(SLJIT_MOV, STR_END, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + } + +if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + lastchar = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, begin)); + firstchar = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP2, 0); + + OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, IN_UCHARS(2)); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, STR_PTR, 0, TMP1, 0); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER_EQUAL); +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + + loop = LABEL(); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + quit = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-2)); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-1)); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff, loop); + CMPTO(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, common->newline & 0xff, loop); + + JUMPHERE(quit); + JUMPHERE(firstchar); + JUMPHERE(lastchar); + + if (common->match_end_ptr != 0) + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + return; + } + +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); +firstchar = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP2, 0); +skip_char_back(common); + +loop = LABEL(); +common->ff_newline_shortcut = loop; + +read_char_range(common, common->nlmin, common->nlmax, TRUE); +lastchar = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); +if (common->nltype == NLTYPE_ANY || common->nltype == NLTYPE_ANYCRLF) + foundcr = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR); +check_newlinechar(common, common->nltype, &newline, FALSE); +set_jumps(newline, loop); + +if (common->nltype == NLTYPE_ANY || common->nltype == NLTYPE_ANYCRLF) + { + quit = JUMP(SLJIT_JUMP); + JUMPHERE(foundcr); + notfoundnl = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, CHAR_NL); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); +#if PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, UCHAR_SHIFT); +#endif + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + JUMPHERE(notfoundnl); + JUMPHERE(quit); + } +JUMPHERE(lastchar); +JUMPHERE(firstchar); + +if (common->match_end_ptr != 0) + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); +} + +static BOOL check_class_ranges(compiler_common *common, const sljit_ub *bits, BOOL nclass, BOOL invert, jump_list **backtracks); + +static SLJIT_INLINE void fast_forward_start_bits(compiler_common *common, const sljit_ub *start_bits) +{ +DEFINE_COMPILER; +struct sljit_label *start; +struct sljit_jump *quit; +struct sljit_jump *found = NULL; +jump_list *matches = NULL; +#if PCRE2_CODE_UNIT_WIDTH != 8 +struct sljit_jump *jump; +#endif + +if (common->match_end_ptr != 0) + { + OP1(SLJIT_MOV, RETURN_ADDR, 0, STR_END, 0); + OP1(SLJIT_MOV, STR_END, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + } + +start = LABEL(); +quit = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); +OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); +#ifdef SUPPORT_UNICODE +if (common->utf) + OP1(SLJIT_MOV, TMP3, 0, TMP1, 0); +#endif + +if (!check_class_ranges(common, start_bits, (start_bits[31] & 0x80) != 0, TRUE, &matches)) + { +#if PCRE2_CODE_UNIT_WIDTH != 8 + jump = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 255); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, 255); + JUMPHERE(jump); +#endif + OP2(SLJIT_AND, TMP2, 0, TMP1, 0, SLJIT_IMM, 0x7); + OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)start_bits); + OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0); + found = JUMP(SLJIT_NOT_ZERO); + } + +#ifdef SUPPORT_UNICODE +if (common->utf) + OP1(SLJIT_MOV, TMP1, 0, TMP3, 0); +#endif +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +#ifdef SUPPORT_UNICODE +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { + CMPTO(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0, start); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + } +#elif PCRE2_CODE_UNIT_WIDTH == 16 +if (common->utf) + { + CMPTO(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800, start); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + } +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16] */ +#endif /* SUPPORT_UNICODE */ +JUMPTO(SLJIT_JUMP, start); +if (found != NULL) + JUMPHERE(found); +if (matches != NULL) + set_jumps(matches, LABEL()); +JUMPHERE(quit); + +if (common->match_end_ptr != 0) + OP1(SLJIT_MOV, STR_END, 0, RETURN_ADDR, 0); +} + +static SLJIT_INLINE struct sljit_jump *search_requested_char(compiler_common *common, PCRE2_UCHAR req_char, BOOL caseless, BOOL has_firstchar) +{ +DEFINE_COMPILER; +struct sljit_label *loop; +struct sljit_jump *toolong; +struct sljit_jump *alreadyfound; +struct sljit_jump *found; +struct sljit_jump *foundoc = NULL; +struct sljit_jump *notfound; +sljit_ui oc, bit; + +SLJIT_ASSERT(common->req_char_ptr != 0); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->req_char_ptr); +OP2(SLJIT_ADD, TMP1, 0, STR_PTR, 0, SLJIT_IMM, REQ_CU_MAX); +toolong = CMP(SLJIT_LESS, TMP1, 0, STR_END, 0); +alreadyfound = CMP(SLJIT_LESS, STR_PTR, 0, TMP2, 0); + +if (has_firstchar) + OP2(SLJIT_ADD, TMP1, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +else + OP1(SLJIT_MOV, TMP1, 0, STR_PTR, 0); + +loop = LABEL(); +notfound = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, STR_END, 0); + +OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(TMP1), 0); +oc = req_char; +if (caseless) + { + oc = TABLE_GET(req_char, common->fcc, req_char); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (req_char > 127 && common->utf) + oc = UCD_OTHERCASE(req_char); +#endif + } +if (req_char == oc) + found = CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, req_char); +else + { + bit = req_char ^ oc; + if (is_powerof2(bit)) + { + OP2(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_IMM, bit); + found = CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, req_char | bit); + } + else + { + found = CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, req_char); + foundoc = CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, oc); + } + } +OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, IN_UCHARS(1)); +JUMPTO(SLJIT_JUMP, loop); + +JUMPHERE(found); +if (foundoc) + JUMPHERE(foundoc); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->req_char_ptr, TMP1, 0); +JUMPHERE(alreadyfound); +JUMPHERE(toolong); +return notfound; +} + +static void do_revertframes(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_jump *jump; +struct sljit_label *mainloop; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP1(SLJIT_MOV, TMP1, 0, STACK_TOP, 0); +GET_LOCAL_BASE(TMP3, 0, 0); + +/* Drop frames until we reach STACK_TOP. */ +mainloop = LABEL(); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), 0); +OP2(SLJIT_SUB | SLJIT_SET_S, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, 0); +jump = JUMP(SLJIT_SIG_LESS_EQUAL); + +OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP3, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(TMP1), sizeof(sljit_sw)); +OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), sizeof(sljit_sw), SLJIT_MEM1(TMP1), 2 * sizeof(sljit_sw)); +OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 3 * sizeof(sljit_sw)); +JUMPTO(SLJIT_JUMP, mainloop); + +JUMPHERE(jump); +jump = JUMP(SLJIT_SIG_LESS); +/* End of dropping frames. */ +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); + +JUMPHERE(jump); +OP1(SLJIT_NEG, TMP2, 0, TMP2, 0); +OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, TMP3, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), 0, SLJIT_MEM1(TMP1), sizeof(sljit_sw)); +OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 2 * sizeof(sljit_sw)); +JUMPTO(SLJIT_JUMP, mainloop); +} + +static void check_wordboundary(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_jump *skipread; +jump_list *skipread_list = NULL; +#if PCRE2_CODE_UNIT_WIDTH != 8 || defined SUPPORT_UNICODE +struct sljit_jump *jump; +#endif + +SLJIT_COMPILE_ASSERT(ctype_word == 0x10, ctype_word_must_be_16); + +sljit_emit_fast_enter(compiler, SLJIT_MEM1(SLJIT_SP), LOCALS0); +/* Get type of the previous char, and put it to LOCALS1. */ +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, begin)); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, SLJIT_IMM, 0); +skipread = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP1, 0); +skip_char_back(common); +check_start_used_ptr(common); +read_char(common); + +/* Testing char type. */ +#ifdef SUPPORT_UNICODE +if (common->use_ucp) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, 1); + jump = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_UNDERSCORE); + add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Nd - ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + JUMPHERE(jump); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP2, 0); + } +else +#endif + { +#if PCRE2_CODE_UNIT_WIDTH != 8 + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); +#elif defined SUPPORT_UNICODE + /* Here LOCALS1 has already been zeroed. */ + jump = NULL; + if (common->utf) + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), common->ctypes); + OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 4 /* ctype_word */); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP1, 0); +#if PCRE2_CODE_UNIT_WIDTH != 8 + JUMPHERE(jump); +#elif defined SUPPORT_UNICODE + if (jump != NULL) + JUMPHERE(jump); +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + } +JUMPHERE(skipread); + +OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, 0); +check_str_end(common, &skipread_list); +peek_char(common, READ_CHAR_MAX); + +/* Testing char type. This is a code duplication. */ +#ifdef SUPPORT_UNICODE +if (common->use_ucp) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, 1); + jump = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_UNDERSCORE); + add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_Lu - ucp_Ll); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ucp_Nd - ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ucp_No - ucp_Nd); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + JUMPHERE(jump); + } +else +#endif + { +#if PCRE2_CODE_UNIT_WIDTH != 8 + /* TMP2 may be destroyed by peek_char. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, 0); + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); +#elif defined SUPPORT_UNICODE + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, 0); + jump = NULL; + if (common->utf) + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); +#endif + OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP1), common->ctypes); + OP2(SLJIT_LSHR, TMP2, 0, TMP2, 0, SLJIT_IMM, 4 /* ctype_word */); + OP2(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_IMM, 1); +#if PCRE2_CODE_UNIT_WIDTH != 8 + JUMPHERE(jump); +#elif defined SUPPORT_UNICODE + if (jump != NULL) + JUMPHERE(jump); +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + } +set_jumps(skipread_list, LABEL()); + +OP2(SLJIT_XOR | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1); +sljit_emit_fast_return(compiler, SLJIT_MEM1(SLJIT_SP), LOCALS0); +} + +static BOOL check_class_ranges(compiler_common *common, const sljit_ub *bits, BOOL nclass, BOOL invert, jump_list **backtracks) +{ +/* May destroy TMP1. */ +DEFINE_COMPILER; +int ranges[MAX_RANGE_SIZE]; +sljit_ub bit, cbit, all; +int i, byte, length = 0; + +bit = bits[0] & 0x1; +/* All bits will be zero or one (since bit is zero or one). */ +all = -bit; + +for (i = 0; i < 256; ) + { + byte = i >> 3; + if ((i & 0x7) == 0 && bits[byte] == all) + i += 8; + else + { + cbit = (bits[byte] >> (i & 0x7)) & 0x1; + if (cbit != bit) + { + if (length >= MAX_RANGE_SIZE) + return FALSE; + ranges[length] = i; + length++; + bit = cbit; + all = -cbit; + } + i++; + } + } + +if (((bit == 0) && nclass) || ((bit == 1) && !nclass)) + { + if (length >= MAX_RANGE_SIZE) + return FALSE; + ranges[length] = 256; + length++; + } + +if (length < 0 || length > 4) + return FALSE; + +bit = bits[0] & 0x1; +if (invert) bit ^= 0x1; + +/* No character is accepted. */ +if (length == 0 && bit == 0) + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + +switch(length) + { + case 0: + /* When bit != 0, all characters are accepted. */ + return TRUE; + + case 1: + add_jump(compiler, backtracks, CMP(bit == 0 ? SLJIT_LESS : SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, ranges[0])); + return TRUE; + + case 2: + if (ranges[0] + 1 != ranges[1]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[0]); + add_jump(compiler, backtracks, CMP(bit != 0 ? SLJIT_LESS : SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, ranges[1] - ranges[0])); + } + else + add_jump(compiler, backtracks, CMP(bit != 0 ? SLJIT_EQUAL : SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[0])); + return TRUE; + + case 3: + if (bit != 0) + { + add_jump(compiler, backtracks, CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, ranges[2])); + if (ranges[0] + 1 != ranges[1]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[0]); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[1] - ranges[0])); + } + else + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[0])); + return TRUE; + } + + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[0])); + if (ranges[1] + 1 != ranges[2]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[1]); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[2] - ranges[1])); + } + else + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[1])); + return TRUE; + + case 4: + if ((ranges[1] - ranges[0]) == (ranges[3] - ranges[2]) + && (ranges[0] | (ranges[2] - ranges[0])) == ranges[2] + && (ranges[1] & (ranges[2] - ranges[0])) == 0 + && is_powerof2(ranges[2] - ranges[0])) + { + SLJIT_ASSERT((ranges[0] & (ranges[2] - ranges[0])) == 0 && (ranges[2] & ranges[3] & (ranges[2] - ranges[0])) != 0); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[2] - ranges[0]); + if (ranges[2] + 1 != ranges[3]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[2]); + add_jump(compiler, backtracks, CMP(bit != 0 ? SLJIT_LESS : SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, ranges[3] - ranges[2])); + } + else + add_jump(compiler, backtracks, CMP(bit != 0 ? SLJIT_EQUAL : SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[2])); + return TRUE; + } + + if (bit != 0) + { + i = 0; + if (ranges[0] + 1 != ranges[1]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[0]); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[1] - ranges[0])); + i = ranges[0]; + } + else + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[0])); + + if (ranges[2] + 1 != ranges[3]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[2] - i); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[3] - ranges[2])); + } + else + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[2] - i)); + return TRUE; + } + + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[0]); + add_jump(compiler, backtracks, CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, ranges[3] - ranges[0])); + if (ranges[1] + 1 != ranges[2]) + { + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, ranges[1] - ranges[0]); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, ranges[2] - ranges[1])); + } + else + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, ranges[1] - ranges[0])); + return TRUE; + + default: + SLJIT_ASSERT_STOP(); + return FALSE; + } +} + +static void check_anynewline(compiler_common *common) +{ +/* Check whether TMP1 contains a newline character. TMP2 destroyed. */ +DEFINE_COMPILER; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); + +OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x0a); +OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a); +OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a); +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { +#endif + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x1); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a); +#if PCRE2_CODE_UNIT_WIDTH == 8 + } +#endif +#endif /* SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == [16|32] */ +OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +static void check_hspace(compiler_common *common) +{ +/* Check whether TMP1 contains a newline character. TMP2 destroyed. */ +DEFINE_COMPILER; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); + +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x09); +OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x20); +OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xa0); +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { +#endif + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x1680); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x2000); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x200A - 0x2000); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x202f - 0x2000); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x205f - 0x2000); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x3000 - 0x2000); +#if PCRE2_CODE_UNIT_WIDTH == 8 + } +#endif +#endif /* SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == [16|32] */ +OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +static void check_vspace(compiler_common *common) +{ +/* Check whether TMP1 contains a newline character. TMP2 destroyed. */ +DEFINE_COMPILER; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); + +OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x0a); +OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x0d - 0x0a); +OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); +OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x0a); +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utf) + { +#endif + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, 0x1); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2029 - 0x0a); +#if PCRE2_CODE_UNIT_WIDTH == 8 + } +#endif +#endif /* SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == [16|32] */ +OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +#define CHAR1 STR_END +#define CHAR2 STACK_TOP + +static void do_casefulcmp(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_jump *jump; +struct sljit_label *label; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP2, 0); +OP1(SLJIT_MOV, TMP3, 0, CHAR1, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, CHAR2, 0); +OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, IN_UCHARS(1)); +OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +label = LABEL(); +OP1(MOVU_UCHAR, CHAR1, 0, SLJIT_MEM1(TMP1), IN_UCHARS(1)); +OP1(MOVU_UCHAR, CHAR2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); +jump = CMP(SLJIT_NOT_EQUAL, CHAR1, 0, CHAR2, 0); +OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1)); +JUMPTO(SLJIT_NOT_ZERO, label); + +JUMPHERE(jump); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +OP1(SLJIT_MOV, CHAR1, 0, TMP3, 0); +OP1(SLJIT_MOV, CHAR2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +#define LCC_TABLE STACK_LIMIT + +static void do_caselesscmp(compiler_common *common) +{ +DEFINE_COMPILER; +struct sljit_jump *jump; +struct sljit_label *label; + +sljit_emit_fast_enter(compiler, RETURN_ADDR, 0); +OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + +OP1(SLJIT_MOV, TMP3, 0, LCC_TABLE, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, CHAR1, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, CHAR2, 0); +OP1(SLJIT_MOV, LCC_TABLE, 0, SLJIT_IMM, common->lcc); +OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, IN_UCHARS(1)); +OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + +label = LABEL(); +OP1(MOVU_UCHAR, CHAR1, 0, SLJIT_MEM1(TMP1), IN_UCHARS(1)); +OP1(MOVU_UCHAR, CHAR2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); +#if PCRE2_CODE_UNIT_WIDTH != 8 +jump = CMP(SLJIT_GREATER, CHAR1, 0, SLJIT_IMM, 255); +#endif +OP1(SLJIT_MOV_UB, CHAR1, 0, SLJIT_MEM2(LCC_TABLE, CHAR1), 0); +#if PCRE2_CODE_UNIT_WIDTH != 8 +JUMPHERE(jump); +jump = CMP(SLJIT_GREATER, CHAR2, 0, SLJIT_IMM, 255); +#endif +OP1(SLJIT_MOV_UB, CHAR2, 0, SLJIT_MEM2(LCC_TABLE, CHAR2), 0); +#if PCRE2_CODE_UNIT_WIDTH != 8 +JUMPHERE(jump); +#endif +jump = CMP(SLJIT_NOT_EQUAL, CHAR1, 0, CHAR2, 0); +OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, IN_UCHARS(1)); +JUMPTO(SLJIT_NOT_ZERO, label); + +JUMPHERE(jump); +OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +OP1(SLJIT_MOV, LCC_TABLE, 0, TMP3, 0); +OP1(SLJIT_MOV, CHAR1, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); +OP1(SLJIT_MOV, CHAR2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1); +sljit_emit_fast_return(compiler, RETURN_ADDR, 0); +} + +#undef LCC_TABLE +#undef CHAR1 +#undef CHAR2 + +#if defined SUPPORT_UNICODE + +static PCRE2_SPTR SLJIT_CALL do_utf_caselesscmp(PCRE2_SPTR src1, jit_arguments *args, PCRE2_SPTR end1) +{ +/* This function would be ineffective to do in JIT level. */ +sljit_ui c1, c2; +PCRE2_SPTR src2 = args->startchar_ptr; +PCRE2_SPTR end2 = args->end; +const ucd_record *ur; +const sljit_ui *pp; + +while (src1 < end1) + { + if (src2 >= end2) + return (PCRE2_SPTR)1; + GETCHARINC(c1, src1); + GETCHARINC(c2, src2); + ur = GET_UCD(c2); + if (c1 != c2 && c1 != c2 + ur->other_case) + { + pp = PRIV(ucd_caseless_sets) + ur->caseset; + for (;;) + { + if (c1 < *pp) return NULL; + if (c1 == *pp++) break; + } + } + } +return src2; +} + +#endif /* SUPPORT_UNICODE */ + +static PCRE2_SPTR compile_char1_matchingpath(compiler_common *common, PCRE2_UCHAR type, PCRE2_SPTR cc, jump_list **backtracks, BOOL check_str_ptr); + +static PCRE2_SPTR byte_sequence_compare(compiler_common *common, BOOL caseless, PCRE2_SPTR cc, + compare_context *context, jump_list **backtracks) +{ +DEFINE_COMPILER; +unsigned int othercasebit = 0; +PCRE2_SPTR othercasechar = NULL; +#ifdef SUPPORT_UNICODE +int utflength; +#endif + +if (caseless && char_has_othercase(common, cc)) + { + othercasebit = char_get_othercase_bit(common, cc); + SLJIT_ASSERT(othercasebit); + /* Extracting bit difference info. */ +#if PCRE2_CODE_UNIT_WIDTH == 8 + othercasechar = cc + (othercasebit >> 8); + othercasebit &= 0xff; +#elif PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + /* Note that this code only handles characters in the BMP. If there + ever are characters outside the BMP whose othercase differs in only one + bit from itself (there currently are none), this code will need to be + revised for PCRE2_CODE_UNIT_WIDTH == 32. */ + othercasechar = cc + (othercasebit >> 9); + if ((othercasebit & 0x100) != 0) + othercasebit = (othercasebit & 0xff) << 8; + else + othercasebit &= 0xff; +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16|32] */ + } + +if (context->sourcereg == -1) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 +#if defined SLJIT_UNALIGNED && SLJIT_UNALIGNED + if (context->length >= 4) + OP1(SLJIT_MOV_SI, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); + else if (context->length >= 2) + OP1(SLJIT_MOV_UH, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); + else +#endif + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); +#elif PCRE2_CODE_UNIT_WIDTH == 16 +#if defined SLJIT_UNALIGNED && SLJIT_UNALIGNED + if (context->length >= 4) + OP1(SLJIT_MOV_SI, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); + else +#endif + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); +#elif PCRE2_CODE_UNIT_WIDTH == 32 + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), -context->length); +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16|32] */ + context->sourcereg = TMP2; + } + +#ifdef SUPPORT_UNICODE +utflength = 1; +if (common->utf && HAS_EXTRALEN(*cc)) + utflength += GET_EXTRALEN(*cc); + +do + { +#endif + + context->length -= IN_UCHARS(1); +#if (defined SLJIT_UNALIGNED && SLJIT_UNALIGNED) && (PCRE2_CODE_UNIT_WIDTH == 8 || PCRE2_CODE_UNIT_WIDTH == 16) + + /* Unaligned read is supported. */ + if (othercasebit != 0 && othercasechar == cc) + { + context->c.asuchars[context->ucharptr] = *cc | othercasebit; + context->oc.asuchars[context->ucharptr] = othercasebit; + } + else + { + context->c.asuchars[context->ucharptr] = *cc; + context->oc.asuchars[context->ucharptr] = 0; + } + context->ucharptr++; + +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (context->ucharptr >= 4 || context->length == 0 || (context->ucharptr == 2 && context->length == 1)) +#else + if (context->ucharptr >= 2 || context->length == 0) +#endif + { + if (context->length >= 4) + OP1(SLJIT_MOV_SI, context->sourcereg, 0, SLJIT_MEM1(STR_PTR), -context->length); + else if (context->length >= 2) + OP1(SLJIT_MOV_UH, context->sourcereg, 0, SLJIT_MEM1(STR_PTR), -context->length); +#if PCRE2_CODE_UNIT_WIDTH == 8 + else if (context->length >= 1) + OP1(SLJIT_MOV_UB, context->sourcereg, 0, SLJIT_MEM1(STR_PTR), -context->length); +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + context->sourcereg = context->sourcereg == TMP1 ? TMP2 : TMP1; + + switch(context->ucharptr) + { + case 4 / sizeof(PCRE2_UCHAR): + if (context->oc.asint != 0) + OP2(SLJIT_OR, context->sourcereg, 0, context->sourcereg, 0, SLJIT_IMM, context->oc.asint); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, context->sourcereg, 0, SLJIT_IMM, context->c.asint | context->oc.asint)); + break; + + case 2 / sizeof(PCRE2_UCHAR): + if (context->oc.asushort != 0) + OP2(SLJIT_OR, context->sourcereg, 0, context->sourcereg, 0, SLJIT_IMM, context->oc.asushort); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, context->sourcereg, 0, SLJIT_IMM, context->c.asushort | context->oc.asushort)); + break; + +#if PCRE2_CODE_UNIT_WIDTH == 8 + case 1: + if (context->oc.asbyte != 0) + OP2(SLJIT_OR, context->sourcereg, 0, context->sourcereg, 0, SLJIT_IMM, context->oc.asbyte); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, context->sourcereg, 0, SLJIT_IMM, context->c.asbyte | context->oc.asbyte)); + break; +#endif + + default: + SLJIT_ASSERT_STOP(); + break; + } + context->ucharptr = 0; + } + +#else + + /* Unaligned read is unsupported or in 32 bit mode. */ + if (context->length >= 1) + OP1(MOV_UCHAR, context->sourcereg, 0, SLJIT_MEM1(STR_PTR), -context->length); + + context->sourcereg = context->sourcereg == TMP1 ? TMP2 : TMP1; + + if (othercasebit != 0 && othercasechar == cc) + { + OP2(SLJIT_OR, context->sourcereg, 0, context->sourcereg, 0, SLJIT_IMM, othercasebit); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, context->sourcereg, 0, SLJIT_IMM, *cc | othercasebit)); + } + else + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, context->sourcereg, 0, SLJIT_IMM, *cc)); + +#endif + + cc++; +#ifdef SUPPORT_UNICODE + utflength--; + } +while (utflength > 0); +#endif + +return cc; +} + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + +#define SET_TYPE_OFFSET(value) \ + if ((value) != typeoffset) \ + { \ + if ((value) < typeoffset) \ + OP2(SLJIT_ADD, typereg, 0, typereg, 0, SLJIT_IMM, typeoffset - (value)); \ + else \ + OP2(SLJIT_SUB, typereg, 0, typereg, 0, SLJIT_IMM, (value) - typeoffset); \ + } \ + typeoffset = (value); + +#define SET_CHAR_OFFSET(value) \ + if ((value) != charoffset) \ + { \ + if ((value) < charoffset) \ + OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(charoffset - (value))); \ + else \ + OP2(SLJIT_SUB, TMP1, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)((value) - charoffset)); \ + } \ + charoffset = (value); + +static void compile_xclass_matchingpath(compiler_common *common, PCRE2_SPTR cc, jump_list **backtracks) +{ +DEFINE_COMPILER; +jump_list *found = NULL; +jump_list **list = (cc[0] & XCL_NOT) == 0 ? &found : backtracks; +sljit_uw c, charoffset, max = 256, min = READ_CHAR_MAX; +struct sljit_jump *jump = NULL; +PCRE2_SPTR ccbegin; +int compares, invertcmp, numberofcmps; +#if defined SUPPORT_UNICODE && (PCRE2_CODE_UNIT_WIDTH == 8 || PCRE2_CODE_UNIT_WIDTH == 16) +BOOL utf = common->utf; +#endif + +#ifdef SUPPORT_UNICODE +BOOL needstype = FALSE, needsscript = FALSE, needschar = FALSE; +BOOL charsaved = FALSE; +int typereg = TMP1; +const sljit_ui *other_cases; +sljit_uw typeoffset; +#endif + +/* Scanning the necessary info. */ +cc++; +ccbegin = cc; +compares = 0; + +if (cc[-1] & XCL_MAP) + { + min = 0; + cc += 32 / sizeof(PCRE2_UCHAR); + } + +while (*cc != XCL_END) + { + compares++; + if (*cc == XCL_SINGLE) + { + cc ++; + GETCHARINCTEST(c, cc); + if (c > max) max = c; + if (c < min) min = c; +#ifdef SUPPORT_UNICODE + needschar = TRUE; +#endif + } + else if (*cc == XCL_RANGE) + { + cc ++; + GETCHARINCTEST(c, cc); + if (c < min) min = c; + GETCHARINCTEST(c, cc); + if (c > max) max = c; +#ifdef SUPPORT_UNICODE + needschar = TRUE; +#endif + } +#ifdef SUPPORT_UNICODE + else + { + SLJIT_ASSERT(*cc == XCL_PROP || *cc == XCL_NOTPROP); + cc++; + if (*cc == PT_CLIST) + { + other_cases = PRIV(ucd_caseless_sets) + cc[1]; + while (*other_cases != NOTACHAR) + { + if (*other_cases > max) max = *other_cases; + if (*other_cases < min) min = *other_cases; + other_cases++; + } + } + else + { + max = READ_CHAR_MAX; + min = 0; + } + + switch(*cc) + { + case PT_ANY: + /* Any either accepts everything or ignored. */ + if (cc[-1] == XCL_PROP) + { + compile_char1_matchingpath(common, OP_ALLANY, cc, backtracks, FALSE); + if (list == backtracks) + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + return; + } + break; + + case PT_LAMP: + case PT_GC: + case PT_PC: + case PT_ALNUM: + needstype = TRUE; + break; + + case PT_SC: + needsscript = TRUE; + break; + + case PT_SPACE: + case PT_PXSPACE: + case PT_WORD: + case PT_PXGRAPH: + case PT_PXPRINT: + case PT_PXPUNCT: + needstype = TRUE; + needschar = TRUE; + break; + + case PT_CLIST: + case PT_UCNC: + needschar = TRUE; + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + cc += 2; + } +#endif + } +SLJIT_ASSERT(compares > 0); + +/* We are not necessary in utf mode even in 8 bit mode. */ +cc = ccbegin; +read_char_range(common, min, max, (cc[-1] & XCL_NOT) != 0); + +if ((cc[-1] & XCL_HASPROP) == 0) + { + if ((cc[-1] & XCL_MAP) != 0) + { + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); + if (!check_class_ranges(common, (const sljit_ub *)cc, (((const sljit_ub *)cc)[31] & 0x80) != 0, TRUE, &found)) + { + OP2(SLJIT_AND, TMP2, 0, TMP1, 0, SLJIT_IMM, 0x7); + OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc); + OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0); + add_jump(compiler, &found, JUMP(SLJIT_NOT_ZERO)); + } + + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + JUMPHERE(jump); + + cc += 32 / sizeof(PCRE2_UCHAR); + } + else + { + OP2(SLJIT_SUB, TMP2, 0, TMP1, 0, SLJIT_IMM, min); + add_jump(compiler, (cc[-1] & XCL_NOT) == 0 ? backtracks : &found, CMP(SLJIT_GREATER, TMP2, 0, SLJIT_IMM, max - min)); + } + } +else if ((cc[-1] & XCL_MAP) != 0) + { + OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP1, 0); +#ifdef SUPPORT_UNICODE + charsaved = TRUE; +#endif + if (!check_class_ranges(common, (const sljit_ub *)cc, FALSE, TRUE, list)) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + jump = NULL; + if (common->utf) +#endif + jump = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); + + OP2(SLJIT_AND, TMP2, 0, TMP1, 0, SLJIT_IMM, 0x7); + OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc); + OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0); + add_jump(compiler, list, JUMP(SLJIT_NOT_ZERO)); + +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf) +#endif + JUMPHERE(jump); + } + + OP1(SLJIT_MOV, TMP1, 0, RETURN_ADDR, 0); + cc += 32 / sizeof(PCRE2_UCHAR); + } + +#ifdef SUPPORT_UNICODE +if (needstype || needsscript) + { + if (needschar && !charsaved) + OP1(SLJIT_MOV, RETURN_ADDR, 0, TMP1, 0); + + OP2(SLJIT_LSHR, TMP2, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_SHIFT); + OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(ucd_stage1)); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, UCD_BLOCK_MASK); + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, UCD_BLOCK_SHIFT); + OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, TMP2, 0); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_stage2)); + OP1(SLJIT_MOV_UH, TMP2, 0, SLJIT_MEM2(TMP2, TMP1), 1); + + /* Before anything else, we deal with scripts. */ + if (needsscript) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, script)); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM2(TMP1, TMP2), 3); + + ccbegin = cc; + + while (*cc != XCL_END) + { + if (*cc == XCL_SINGLE) + { + cc ++; + GETCHARINCTEST(c, cc); + } + else if (*cc == XCL_RANGE) + { + cc ++; + GETCHARINCTEST(c, cc); + GETCHARINCTEST(c, cc); + } + else + { + SLJIT_ASSERT(*cc == XCL_PROP || *cc == XCL_NOTPROP); + cc++; + if (*cc == PT_SC) + { + compares--; + invertcmp = (compares == 0 && list != backtracks); + if (cc[-1] == XCL_NOTPROP) + invertcmp ^= 0x1; + jump = CMP(SLJIT_EQUAL ^ invertcmp, TMP1, 0, SLJIT_IMM, (int)cc[1]); + add_jump(compiler, compares > 0 ? list : backtracks, jump); + } + cc += 2; + } + } + + cc = ccbegin; + } + + if (needschar) + { + OP1(SLJIT_MOV, TMP1, 0, RETURN_ADDR, 0); + } + + if (needstype) + { + if (!needschar) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, chartype)); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM2(TMP1, TMP2), 3); + } + else + { + OP2(SLJIT_SHL, TMP2, 0, TMP2, 0, SLJIT_IMM, 3); + OP1(SLJIT_MOV_UB, RETURN_ADDR, 0, SLJIT_MEM1(TMP2), (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, chartype)); + typereg = RETURN_ADDR; + } + } + } +#endif + +/* Generating code. */ +charoffset = 0; +numberofcmps = 0; +#ifdef SUPPORT_UNICODE +typeoffset = 0; +#endif + +while (*cc != XCL_END) + { + compares--; + invertcmp = (compares == 0 && list != backtracks); + jump = NULL; + + if (*cc == XCL_SINGLE) + { + cc ++; + GETCHARINCTEST(c, cc); + + if (numberofcmps < 3 && (*cc == XCL_SINGLE || *cc == XCL_RANGE)) + { + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, numberofcmps == 0 ? SLJIT_UNUSED : TMP2, 0, SLJIT_EQUAL); + numberofcmps++; + } + else if (numberofcmps > 0) + { + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + numberofcmps = 0; + } + else + { + jump = CMP(SLJIT_EQUAL ^ invertcmp, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + numberofcmps = 0; + } + } + else if (*cc == XCL_RANGE) + { + cc ++; + GETCHARINCTEST(c, cc); + SET_CHAR_OFFSET(c); + GETCHARINCTEST(c, cc); + + if (numberofcmps < 3 && (*cc == XCL_SINGLE || *cc == XCL_RANGE)) + { + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + OP_FLAGS(numberofcmps == 0 ? SLJIT_MOV : SLJIT_OR, TMP2, 0, numberofcmps == 0 ? SLJIT_UNUSED : TMP2, 0, SLJIT_LESS_EQUAL); + numberofcmps++; + } + else if (numberofcmps > 0) + { + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + numberofcmps = 0; + } + else + { + jump = CMP(SLJIT_LESS_EQUAL ^ invertcmp, TMP1, 0, SLJIT_IMM, (sljit_sw)(c - charoffset)); + numberofcmps = 0; + } + } +#ifdef SUPPORT_UNICODE + else + { + SLJIT_ASSERT(*cc == XCL_PROP || *cc == XCL_NOTPROP); + if (*cc == XCL_NOTPROP) + invertcmp ^= 0x1; + cc++; + switch(*cc) + { + case PT_ANY: + if (!invertcmp) + jump = JUMP(SLJIT_JUMP); + break; + + case PT_LAMP: + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - typeoffset); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ll - typeoffset); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lt - typeoffset); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + case PT_GC: + c = PRIV(ucp_typerange)[(int)cc[1] * 2]; + SET_TYPE_OFFSET(c); + jump = CMP(SLJIT_LESS_EQUAL ^ invertcmp, typereg, 0, SLJIT_IMM, PRIV(ucp_typerange)[(int)cc[1] * 2 + 1] - c); + break; + + case PT_PC: + jump = CMP(SLJIT_EQUAL ^ invertcmp, typereg, 0, SLJIT_IMM, (int)cc[1] - typeoffset); + break; + + case PT_SC: + compares++; + /* Do nothing. */ + break; + + case PT_SPACE: + case PT_PXSPACE: + SET_CHAR_OFFSET(9); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd - 0x9); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x85 - 0x9); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x9); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + SET_TYPE_OFFSET(ucp_Zl); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Zl); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + case PT_WORD: + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_UNDERSCORE - charoffset)); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + /* Fall through. */ + + case PT_ALNUM: + SET_TYPE_OFFSET(ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Lu - ucp_Ll); + OP_FLAGS((*cc == PT_ALNUM) ? SLJIT_MOV : SLJIT_OR, TMP2, 0, (*cc == PT_ALNUM) ? SLJIT_UNUSED : TMP2, 0, SLJIT_LESS_EQUAL); + SET_TYPE_OFFSET(ucp_Nd); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_No - ucp_Nd); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + case PT_CLIST: + other_cases = PRIV(ucd_caseless_sets) + cc[1]; + + /* At least three characters are required. + Otherwise this case would be handled by the normal code path. */ + SLJIT_ASSERT(other_cases[0] != NOTACHAR && other_cases[1] != NOTACHAR && other_cases[2] != NOTACHAR); + SLJIT_ASSERT(other_cases[0] < other_cases[1] && other_cases[1] < other_cases[2]); + + /* Optimizing character pairs, if their difference is power of 2. */ + if (is_powerof2(other_cases[1] ^ other_cases[0])) + { + if (charoffset == 0) + OP2(SLJIT_OR, TMP2, 0, TMP1, 0, SLJIT_IMM, other_cases[1] ^ other_cases[0]); + else + { + OP2(SLJIT_ADD, TMP2, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)charoffset); + OP2(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_IMM, other_cases[1] ^ other_cases[0]); + } + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[1]); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + other_cases += 2; + } + else if (is_powerof2(other_cases[2] ^ other_cases[1])) + { + if (charoffset == 0) + OP2(SLJIT_OR, TMP2, 0, TMP1, 0, SLJIT_IMM, other_cases[2] ^ other_cases[1]); + else + { + OP2(SLJIT_ADD, TMP2, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)charoffset); + OP2(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_IMM, other_cases[1] ^ other_cases[0]); + } + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, other_cases[2]); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(other_cases[0] - charoffset)); + OP_FLAGS(SLJIT_OR | ((other_cases[3] == NOTACHAR) ? SLJIT_SET_E : 0), TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + other_cases += 3; + } + else + { + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset)); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + } + + while (*other_cases != NOTACHAR) + { + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(*other_cases++ - charoffset)); + OP_FLAGS(SLJIT_OR | ((*other_cases == NOTACHAR) ? SLJIT_SET_E : 0), TMP2, 0, TMP2, 0, SLJIT_EQUAL); + } + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + case PT_UCNC: + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_DOLLAR_SIGN - charoffset)); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_COMMERCIAL_AT - charoffset)); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(CHAR_GRAVE_ACCENT - charoffset)); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + SET_CHAR_OFFSET(0xa0); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (sljit_sw)(0xd7ff - charoffset)); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + SET_CHAR_OFFSET(0); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xe000 - 0); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_GREATER_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + case PT_PXGRAPH: + /* C and Z groups are the farthest two groups. */ + SET_TYPE_OFFSET(ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER); + + jump = CMP(SLJIT_NOT_EQUAL, typereg, 0, SLJIT_IMM, ucp_Cf - ucp_Ll); + + /* In case of ucp_Cf, we overwrite the result. */ + SET_CHAR_OFFSET(0x2066); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x180e - 0x2066); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + JUMPHERE(jump); + jump = CMP(SLJIT_ZERO ^ invertcmp, TMP2, 0, SLJIT_IMM, 0); + break; + + case PT_PXPRINT: + /* C and Z groups are the farthest two groups. */ + SET_TYPE_OFFSET(ucp_Ll); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Ll); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_GREATER); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Zs - ucp_Ll); + OP_FLAGS(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_NOT_EQUAL); + + jump = CMP(SLJIT_NOT_EQUAL, typereg, 0, SLJIT_IMM, ucp_Cf - ucp_Ll); + + /* In case of ucp_Cf, we overwrite the result. */ + SET_CHAR_OFFSET(0x2066); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x2069 - 0x2066); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x061c - 0x2066); + OP_FLAGS(SLJIT_OR, TMP2, 0, TMP2, 0, SLJIT_EQUAL); + + JUMPHERE(jump); + jump = CMP(SLJIT_ZERO ^ invertcmp, TMP2, 0, SLJIT_IMM, 0); + break; + + case PT_PXPUNCT: + SET_TYPE_OFFSET(ucp_Sc); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_So - ucp_Sc); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS_EQUAL); + + SET_CHAR_OFFSET(0); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0x7f); + OP_FLAGS(SLJIT_AND, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + + SET_TYPE_OFFSET(ucp_Pc); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, typereg, 0, SLJIT_IMM, ucp_Ps - ucp_Pc); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_LESS_EQUAL); + jump = JUMP(SLJIT_NOT_ZERO ^ invertcmp); + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + cc += 2; + } +#endif + + if (jump != NULL) + add_jump(compiler, compares > 0 ? list : backtracks, jump); + } + +if (found != NULL) + set_jumps(found, LABEL()); +} + +#undef SET_TYPE_OFFSET +#undef SET_CHAR_OFFSET + +#endif + +static PCRE2_SPTR compile_simple_assertion_matchingpath(compiler_common *common, PCRE2_UCHAR type, PCRE2_SPTR cc, jump_list **backtracks) +{ +DEFINE_COMPILER; +int length; +struct sljit_jump *jump[4]; +#ifdef SUPPORT_UNICODE +struct sljit_label *label; +#endif /* SUPPORT_UNICODE */ + +switch(type) + { + case OP_SOD: + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, begin)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, TMP1, 0)); + return cc; + + case OP_SOM: + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, TMP1, 0)); + return cc; + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + add_jump(compiler, &common->wordboundary, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, JUMP(type == OP_NOT_WORD_BOUNDARY ? SLJIT_NOT_ZERO : SLJIT_ZERO)); + return cc; + + case OP_EODN: + /* Requires rather complex checks. */ + jump[0] = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + if (common->mode == PCRE2_JIT_COMPLETE) + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, STR_END, 0)); + else + { + jump[1] = CMP(SLJIT_EQUAL, TMP2, 0, STR_END, 0); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0); + OP_FLAGS(SLJIT_MOV, TMP2, 0, SLJIT_UNUSED, 0, SLJIT_LESS); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff); + OP_FLAGS(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_NOT_EQUAL); + add_jump(compiler, backtracks, JUMP(SLJIT_NOT_EQUAL)); + check_partial(common, TRUE); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + JUMPHERE(jump[1]); + } + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, common->newline & 0xff)); + } + else if (common->nltype == NLTYPE_FIXED) + { + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, STR_END, 0)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, common->newline)); + } + else + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + jump[1] = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR); + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); + OP2(SLJIT_SUB | SLJIT_SET_U, SLJIT_UNUSED, 0, TMP2, 0, STR_END, 0); + jump[2] = JUMP(SLJIT_GREATER); + add_jump(compiler, backtracks, JUMP(SLJIT_LESS)); + /* Equal. */ + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); + jump[3] = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + + JUMPHERE(jump[1]); + if (common->nltype == NLTYPE_ANYCRLF) + { + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP2, 0, STR_END, 0)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL)); + } + else + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, STR_PTR, 0); + read_char_range(common, common->nlmin, common->nlmax, TRUE); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, STR_END, 0)); + add_jump(compiler, &common->anynewline, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, JUMP(SLJIT_ZERO)); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1); + } + JUMPHERE(jump[2]); + JUMPHERE(jump[3]); + } + JUMPHERE(jump[0]); + check_partial(common, FALSE); + return cc; + + case OP_EOD: + add_jump(compiler, backtracks, CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0)); + check_partial(common, FALSE); + return cc; + + case OP_CIRC: + OP1(SLJIT_MOV, TMP2, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, begin)); + add_jump(compiler, backtracks, CMP(SLJIT_GREATER, STR_PTR, 0, TMP1, 0)); + OP2(SLJIT_IAND | SLJIT_SET_E, SLJIT_UNUSED, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, options), SLJIT_IMM, PCRE2_NOTBOL); + add_jump(compiler, backtracks, JUMP(SLJIT_NOT_ZERO)); + return cc; + + case OP_CIRCM: + OP1(SLJIT_MOV, TMP2, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, begin)); + jump[1] = CMP(SLJIT_GREATER, STR_PTR, 0, TMP1, 0); + OP2(SLJIT_IAND | SLJIT_SET_E, SLJIT_UNUSED, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, options), SLJIT_IMM, PCRE2_NOTBOL); + add_jump(compiler, backtracks, JUMP(SLJIT_NOT_ZERO)); + jump[0] = JUMP(SLJIT_JUMP); + JUMPHERE(jump[1]); + + if (!common->alt_circumflex) + add_jump(compiler, backtracks, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + + if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + OP2(SLJIT_SUB, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, TMP2, 0, TMP1, 0)); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-2)); + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-1)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, common->newline & 0xff)); + } + else + { + skip_char_back(common); + read_char_range(common, common->nlmin, common->nlmax, TRUE); + check_newlinechar(common, common->nltype, backtracks, FALSE); + } + JUMPHERE(jump[0]); + return cc; + + case OP_DOLL: + OP1(SLJIT_MOV, TMP2, 0, ARGUMENTS, 0); + OP2(SLJIT_IAND | SLJIT_SET_E, SLJIT_UNUSED, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, options), SLJIT_IMM, PCRE2_NOTEOL); + add_jump(compiler, backtracks, JUMP(SLJIT_NOT_ZERO)); + + if (!common->endonly) + compile_simple_assertion_matchingpath(common, OP_EODN, cc, backtracks); + else + { + add_jump(compiler, backtracks, CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0)); + check_partial(common, FALSE); + } + return cc; + + case OP_DOLLM: + jump[1] = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); + OP1(SLJIT_MOV, TMP2, 0, ARGUMENTS, 0); + OP2(SLJIT_IAND | SLJIT_SET_E, SLJIT_UNUSED, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(jit_arguments, options), SLJIT_IMM, PCRE2_NOTEOL); + add_jump(compiler, backtracks, JUMP(SLJIT_NOT_ZERO)); + check_partial(common, FALSE); + jump[0] = JUMP(SLJIT_JUMP); + JUMPHERE(jump[1]); + + if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(2)); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + if (common->mode == PCRE2_JIT_COMPLETE) + add_jump(compiler, backtracks, CMP(SLJIT_GREATER, TMP2, 0, STR_END, 0)); + else + { + jump[1] = CMP(SLJIT_LESS_EQUAL, TMP2, 0, STR_END, 0); + /* STR_PTR = STR_END - IN_UCHARS(1) */ + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff)); + check_partial(common, TRUE); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + JUMPHERE(jump[1]); + } + + OP1(MOV_UCHAR, TMP2, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(1)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, common->newline & 0xff)); + } + else + { + peek_char(common, common->nlmax); + check_newlinechar(common, common->nltype, backtracks, FALSE); + } + JUMPHERE(jump[0]); + return cc; + + case OP_REVERSE: + length = GET(cc, 0); + if (length == 0) + return cc + LINK_SIZE; + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +#ifdef SUPPORT_UNICODE + if (common->utf) + { + OP1(SLJIT_MOV, TMP3, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, begin)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, length); + label = LABEL(); + add_jump(compiler, backtracks, CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP3, 0)); + skip_char_back(common); + OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, TMP2, 0, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + } + else +#endif + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, begin)); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(length)); + add_jump(compiler, backtracks, CMP(SLJIT_LESS, STR_PTR, 0, TMP1, 0)); + } + check_start_used_ptr(common); + return cc + LINK_SIZE; + } +SLJIT_ASSERT_STOP(); +return cc; +} + +static PCRE2_SPTR compile_char1_matchingpath(compiler_common *common, PCRE2_UCHAR type, PCRE2_SPTR cc, jump_list **backtracks, BOOL check_str_ptr) +{ +DEFINE_COMPILER; +int length; +unsigned int c, oc, bit; +compare_context context; +struct sljit_jump *jump[3]; +jump_list *end_list; +#ifdef SUPPORT_UNICODE +struct sljit_label *label; +PCRE2_UCHAR propdata[5]; +#endif /* SUPPORT_UNICODE */ + +switch(type) + { + case OP_NOT_DIGIT: + case OP_DIGIT: + /* Digits are usually 0-9, so it is worth to optimize them. */ + if (check_str_ptr) + detect_partial_match(common, backtracks); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && is_char7_bitset((const sljit_ub*)common->ctypes - cbit_length + cbit_digit, FALSE)) + read_char7_type(common, type == OP_NOT_DIGIT); + else +#endif + read_char8_type(common, type == OP_NOT_DIGIT); + /* Flip the starting bit in the negative case. */ + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_digit); + add_jump(compiler, backtracks, JUMP(type == OP_DIGIT ? SLJIT_ZERO : SLJIT_NOT_ZERO)); + return cc; + + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + if (check_str_ptr) + detect_partial_match(common, backtracks); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && is_char7_bitset((const sljit_ub*)common->ctypes - cbit_length + cbit_space, FALSE)) + read_char7_type(common, type == OP_NOT_WHITESPACE); + else +#endif + read_char8_type(common, type == OP_NOT_WHITESPACE); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_space); + add_jump(compiler, backtracks, JUMP(type == OP_WHITESPACE ? SLJIT_ZERO : SLJIT_NOT_ZERO)); + return cc; + + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + if (check_str_ptr) + detect_partial_match(common, backtracks); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (common->utf && is_char7_bitset((const sljit_ub*)common->ctypes - cbit_length + cbit_word, FALSE)) + read_char7_type(common, type == OP_NOT_WORDCHAR); + else +#endif + read_char8_type(common, type == OP_NOT_WORDCHAR); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, ctype_word); + add_jump(compiler, backtracks, JUMP(type == OP_WORDCHAR ? SLJIT_ZERO : SLJIT_NOT_ZERO)); + return cc; + + case OP_ANY: + if (check_str_ptr) + detect_partial_match(common, backtracks); + read_char_range(common, common->nlmin, common->nlmax, TRUE); + if (common->nltype == NLTYPE_FIXED && common->newline > 255) + { + jump[0] = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, (common->newline >> 8) & 0xff); + end_list = NULL; + if (common->mode != PCRE2_JIT_PARTIAL_HARD) + add_jump(compiler, &end_list, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + else + check_str_end(common, &end_list); + + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, common->newline & 0xff)); + set_jumps(end_list, LABEL()); + JUMPHERE(jump[0]); + } + else + check_newlinechar(common, common->nltype, backtracks, TRUE); + return cc; + + case OP_ALLANY: + if (check_str_ptr) + detect_partial_match(common, backtracks); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) + { + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); +#if PCRE2_CODE_UNIT_WIDTH == 8 || PCRE2_CODE_UNIT_WIDTH == 16 +#if PCRE2_CODE_UNIT_WIDTH == 8 + jump[0] = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); +#elif PCRE2_CODE_UNIT_WIDTH == 16 + jump[0] = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xd800); + OP2(SLJIT_AND, TMP1, 0, TMP1, 0, SLJIT_IMM, 0xfc00); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, SLJIT_IMM, 0xd800); + OP_FLAGS(SLJIT_MOV, TMP1, 0, SLJIT_UNUSED, 0, SLJIT_EQUAL); + OP2(SLJIT_SHL, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); +#endif + JUMPHERE(jump[0]); +#endif /* PCRE2_CODE_UNIT_WIDTH == [8|16] */ + return cc; + } +#endif + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + return cc; + + case OP_ANYBYTE: + if (check_str_ptr) + detect_partial_match(common, backtracks); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + return cc; + +#ifdef SUPPORT_UNICODE + case OP_NOTPROP: + case OP_PROP: + propdata[0] = XCL_HASPROP; + propdata[1] = type == OP_NOTPROP ? XCL_NOTPROP : XCL_PROP; + propdata[2] = cc[0]; + propdata[3] = cc[1]; + propdata[4] = XCL_END; + if (check_str_ptr) + detect_partial_match(common, backtracks); + compile_xclass_matchingpath(common, propdata, backtracks); + return cc + 2; +#endif + + case OP_ANYNL: + if (check_str_ptr) + detect_partial_match(common, backtracks); + read_char_range(common, common->bsr_nlmin, common->bsr_nlmax, FALSE); + jump[0] = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_CR); + /* We don't need to handle soft partial matching case. */ + end_list = NULL; + if (common->mode != PCRE2_JIT_PARTIAL_HARD) + add_jump(compiler, &end_list, CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0)); + else + check_str_end(common, &end_list); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + jump[1] = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, CHAR_NL); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + jump[2] = JUMP(SLJIT_JUMP); + JUMPHERE(jump[0]); + check_newlinechar(common, common->bsr_nltype, backtracks, FALSE); + set_jumps(end_list, LABEL()); + JUMPHERE(jump[1]); + JUMPHERE(jump[2]); + return cc; + + case OP_NOT_HSPACE: + case OP_HSPACE: + if (check_str_ptr) + detect_partial_match(common, backtracks); + read_char_range(common, 0x9, 0x3000, type == OP_NOT_HSPACE); + add_jump(compiler, &common->hspace, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, JUMP(type == OP_NOT_HSPACE ? SLJIT_NOT_ZERO : SLJIT_ZERO)); + return cc; + + case OP_NOT_VSPACE: + case OP_VSPACE: + if (check_str_ptr) + detect_partial_match(common, backtracks); + read_char_range(common, 0xa, 0x2029, type == OP_NOT_VSPACE); + add_jump(compiler, &common->vspace, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, JUMP(type == OP_NOT_VSPACE ? SLJIT_NOT_ZERO : SLJIT_ZERO)); + return cc; + +#ifdef SUPPORT_UNICODE + case OP_EXTUNI: + if (check_str_ptr) + detect_partial_match(common, backtracks); + read_char(common); + add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, gbprop)); + /* Optimize register allocation: use a real register. */ + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, STACK_TOP, 0); + OP1(SLJIT_MOV_UB, STACK_TOP, 0, SLJIT_MEM2(TMP1, TMP2), 3); + + label = LABEL(); + jump[0] = CMP(SLJIT_GREATER_EQUAL, STR_PTR, 0, STR_END, 0); + OP1(SLJIT_MOV, TMP3, 0, STR_PTR, 0); + read_char(common); + add_jump(compiler, &common->getucd, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, (sljit_sw)PRIV(ucd_records) + SLJIT_OFFSETOF(ucd_record, gbprop)); + OP1(SLJIT_MOV_UB, TMP2, 0, SLJIT_MEM2(TMP1, TMP2), 3); + + OP2(SLJIT_SHL, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, 2); + OP1(SLJIT_MOV_UI, TMP1, 0, SLJIT_MEM1(STACK_TOP), (sljit_sw)PRIV(ucp_gbtable)); + OP1(SLJIT_MOV, STACK_TOP, 0, TMP2, 0); + OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0); + JUMPTO(SLJIT_NOT_ZERO, label); + + OP1(SLJIT_MOV, STR_PTR, 0, TMP3, 0); + JUMPHERE(jump[0]); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); + + if (common->mode == PCRE2_JIT_PARTIAL_HARD) + { + jump[0] = CMP(SLJIT_LESS, STR_PTR, 0, STR_END, 0); + /* Since we successfully read a char above, partial matching must occure. */ + check_partial(common, TRUE); + JUMPHERE(jump[0]); + } + return cc; +#endif + + case OP_CHAR: + case OP_CHARI: + length = 1; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(*cc)) length += GET_EXTRALEN(*cc); +#endif + if (common->mode == PCRE2_JIT_COMPLETE && check_str_ptr + && (type == OP_CHAR || !char_has_othercase(common, cc) || char_get_othercase_bit(common, cc) != 0)) + { + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(length)); + add_jump(compiler, backtracks, CMP(SLJIT_GREATER, STR_PTR, 0, STR_END, 0)); + + context.length = IN_UCHARS(length); + context.sourcereg = -1; +#if defined SLJIT_UNALIGNED && SLJIT_UNALIGNED + context.ucharptr = 0; +#endif + return byte_sequence_compare(common, type == OP_CHARI, cc, &context, backtracks); + } + + if (check_str_ptr) + detect_partial_match(common, backtracks); +#ifdef SUPPORT_UNICODE + if (common->utf) + { + GETCHAR(c, cc); + } + else +#endif + c = *cc; + + if (type == OP_CHAR || !char_has_othercase(common, cc)) + { + read_char_range(common, c, c, FALSE); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, c)); + return cc + length; + } + oc = char_othercase(common, c); + read_char_range(common, c < oc ? c : oc, c > oc ? c : oc, FALSE); + bit = c ^ oc; + if (is_powerof2(bit)) + { + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, bit); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, c | bit)); + return cc + length; + } + jump[0] = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, c); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, oc)); + JUMPHERE(jump[0]); + return cc + length; + + case OP_NOT: + case OP_NOTI: + if (check_str_ptr) + detect_partial_match(common, backtracks); + + length = 1; +#ifdef SUPPORT_UNICODE + if (common->utf) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + c = *cc; + if (c < 128) + { + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(STR_PTR), 0); + if (type == OP_NOT || !char_has_othercase(common, cc)) + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, c)); + else + { + /* Since UTF8 code page is fixed, we know that c is in [a-z] or [A-Z] range. */ + OP2(SLJIT_OR, TMP2, 0, TMP1, 0, SLJIT_IMM, 0x20); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, c | 0x20)); + } + /* Skip the variable-length character. */ + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + jump[0] = CMP(SLJIT_LESS, TMP1, 0, SLJIT_IMM, 0xc0); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)PRIV(utf8_table4) - 0xc0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP1, 0); + JUMPHERE(jump[0]); + return cc + 1; + } + else +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + { + GETCHARLEN(c, cc, length); + } + } + else +#endif /* SUPPORT_UNICODE */ + c = *cc; + + if (type == OP_NOT || !char_has_othercase(common, cc)) + { + read_char_range(common, c, c, TRUE); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, c)); + } + else + { + oc = char_othercase(common, c); + read_char_range(common, c < oc ? c : oc, c > oc ? c : oc, TRUE); + bit = c ^ oc; + if (is_powerof2(bit)) + { + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, bit); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, c | bit)); + } + else + { + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, c)); + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, oc)); + } + } + return cc + length; + + case OP_CLASS: + case OP_NCLASS: + if (check_str_ptr) + detect_partial_match(common, backtracks); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + bit = (common->utf && is_char7_bitset((const sljit_ub *)cc, type == OP_NCLASS)) ? 127 : 255; + read_char_range(common, 0, bit, type == OP_NCLASS); +#else + read_char_range(common, 0, 255, type == OP_NCLASS); +#endif + + if (check_class_ranges(common, (const sljit_ub *)cc, type == OP_NCLASS, FALSE, backtracks)) + return cc + 32 / sizeof(PCRE2_UCHAR); + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + jump[0] = NULL; + if (common->utf) + { + jump[0] = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, bit); + if (type == OP_CLASS) + { + add_jump(compiler, backtracks, jump[0]); + jump[0] = NULL; + } + } +#elif PCRE2_CODE_UNIT_WIDTH != 8 + jump[0] = CMP(SLJIT_GREATER, TMP1, 0, SLJIT_IMM, 255); + if (type == OP_CLASS) + { + add_jump(compiler, backtracks, jump[0]); + jump[0] = NULL; + } +#endif /* SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 */ + + OP2(SLJIT_AND, TMP2, 0, TMP1, 0, SLJIT_IMM, 0x7); + OP2(SLJIT_LSHR, TMP1, 0, TMP1, 0, SLJIT_IMM, 3); + OP1(SLJIT_MOV_UB, TMP1, 0, SLJIT_MEM1(TMP1), (sljit_sw)cc); + OP2(SLJIT_SHL, TMP2, 0, SLJIT_IMM, 1, TMP2, 0); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP1, 0, TMP2, 0); + add_jump(compiler, backtracks, JUMP(SLJIT_ZERO)); + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + if (jump[0] != NULL) + JUMPHERE(jump[0]); +#endif + return cc + 32 / sizeof(PCRE2_UCHAR); + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + case OP_XCLASS: + if (check_str_ptr) + detect_partial_match(common, backtracks); + compile_xclass_matchingpath(common, cc + LINK_SIZE, backtracks); + return cc + GET(cc, 0) - 1; +#endif + } +SLJIT_ASSERT_STOP(); +return cc; +} + +static SLJIT_INLINE PCRE2_SPTR compile_charn_matchingpath(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, jump_list **backtracks) +{ +/* This function consumes at least one input character. */ +/* To decrease the number of length checks, we try to concatenate the fixed length character sequences. */ +DEFINE_COMPILER; +PCRE2_SPTR ccbegin = cc; +compare_context context; +int size; + +context.length = 0; +do + { + if (cc >= ccend) + break; + + if (*cc == OP_CHAR) + { + size = 1; +#ifdef SUPPORT_UNICODE + if (common->utf && HAS_EXTRALEN(cc[1])) + size += GET_EXTRALEN(cc[1]); +#endif + } + else if (*cc == OP_CHARI) + { + size = 1; +#ifdef SUPPORT_UNICODE + if (common->utf) + { + if (char_has_othercase(common, cc + 1) && char_get_othercase_bit(common, cc + 1) == 0) + size = 0; + else if (HAS_EXTRALEN(cc[1])) + size += GET_EXTRALEN(cc[1]); + } + else +#endif + if (char_has_othercase(common, cc + 1) && char_get_othercase_bit(common, cc + 1) == 0) + size = 0; + } + else + size = 0; + + cc += 1 + size; + context.length += IN_UCHARS(size); + } +while (size > 0 && context.length <= 128); + +cc = ccbegin; +if (context.length > 0) + { + /* We have a fixed-length byte sequence. */ + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, context.length); + add_jump(compiler, backtracks, CMP(SLJIT_GREATER, STR_PTR, 0, STR_END, 0)); + + context.sourcereg = -1; +#if defined SLJIT_UNALIGNED && SLJIT_UNALIGNED + context.ucharptr = 0; +#endif + do cc = byte_sequence_compare(common, *cc == OP_CHARI, cc + 1, &context, backtracks); while (context.length > 0); + return cc; + } + +/* A non-fixed length character will be checked if length == 0. */ +return compile_char1_matchingpath(common, *cc, cc + 1, backtracks, TRUE); +} + +/* Forward definitions. */ +static void compile_matchingpath(compiler_common *, PCRE2_SPTR, PCRE2_SPTR, backtrack_common *); +static void compile_backtrackingpath(compiler_common *, struct backtrack_common *); + +#define PUSH_BACKTRACK(size, ccstart, error) \ + do \ + { \ + backtrack = sljit_alloc_memory(compiler, (size)); \ + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) \ + return error; \ + memset(backtrack, 0, size); \ + backtrack->prev = parent->top; \ + backtrack->cc = (ccstart); \ + parent->top = backtrack; \ + } \ + while (0) + +#define PUSH_BACKTRACK_NOVALUE(size, ccstart) \ + do \ + { \ + backtrack = sljit_alloc_memory(compiler, (size)); \ + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) \ + return; \ + memset(backtrack, 0, size); \ + backtrack->prev = parent->top; \ + backtrack->cc = (ccstart); \ + parent->top = backtrack; \ + } \ + while (0) + +#define BACKTRACK_AS(type) ((type *)backtrack) + +static void compile_dnref_search(compiler_common *common, PCRE2_SPTR cc, jump_list **backtracks) +{ +/* The OVECTOR offset goes to TMP2. */ +DEFINE_COMPILER; +int count = GET2(cc, 1 + IMM2_SIZE); +PCRE2_SPTR slot = common->name_table + GET2(cc, 1) * common->name_entry_size; +unsigned int offset; +jump_list *found = NULL; + +SLJIT_ASSERT(*cc == OP_DNREF || *cc == OP_DNREFI); + +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1)); + +count--; +while (count-- > 0) + { + offset = GET2(slot, 0) << 1; + GET_LOCAL_BASE(TMP2, 0, OVECTOR(offset)); + add_jump(compiler, &found, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0)); + slot += common->name_entry_size; + } + +offset = GET2(slot, 0) << 1; +GET_LOCAL_BASE(TMP2, 0, OVECTOR(offset)); +if (backtracks != NULL && !common->unset_backref) + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0)); + +set_jumps(found, LABEL()); +} + +static void compile_ref_matchingpath(compiler_common *common, PCRE2_SPTR cc, jump_list **backtracks, BOOL withchecks, BOOL emptyfail) +{ +DEFINE_COMPILER; +BOOL ref = (*cc == OP_REF || *cc == OP_REFI); +int offset = 0; +struct sljit_jump *jump = NULL; +struct sljit_jump *partial; +struct sljit_jump *nopartial; + +if (ref) + { + offset = GET2(cc, 1) << 1; + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + /* OVECTOR(1) contains the "string begin - 1" constant. */ + if (withchecks && !common->unset_backref) + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1))); + } +else + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), 0); + +#if defined SUPPORT_UNICODE +if (common->utf && *cc == OP_REFI) + { + SLJIT_ASSERT(TMP1 == SLJIT_R0 && STACK_TOP == SLJIT_R1 && TMP2 == SLJIT_R2); + if (ref) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + else + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw)); + + if (withchecks) + jump = CMP(SLJIT_EQUAL, TMP1, 0, TMP2, 0); + + /* Needed to save important temporary registers. */ + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, STACK_TOP, 0); + OP1(SLJIT_MOV, SLJIT_R1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_R1), SLJIT_OFFSETOF(jit_arguments, startchar_ptr), STR_PTR, 0); + sljit_emit_ijump(compiler, SLJIT_CALL3, SLJIT_IMM, SLJIT_FUNC_OFFSET(do_utf_caselesscmp)); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); + if (common->mode == PCRE2_JIT_COMPLETE) + add_jump(compiler, backtracks, CMP(SLJIT_LESS_EQUAL, SLJIT_RETURN_REG, 0, SLJIT_IMM, 1)); + else + { + add_jump(compiler, backtracks, CMP(SLJIT_EQUAL, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0)); + nopartial = CMP(SLJIT_NOT_EQUAL, SLJIT_RETURN_REG, 0, SLJIT_IMM, 1); + check_partial(common, FALSE); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + JUMPHERE(nopartial); + } + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_RETURN_REG, 0); + } +else +#endif /* SUPPORT_UNICODE */ + { + if (ref) + OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP1, 0); + else + OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw), TMP1, 0); + + if (withchecks) + jump = JUMP(SLJIT_ZERO); + + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, TMP2, 0); + partial = CMP(SLJIT_GREATER, STR_PTR, 0, STR_END, 0); + if (common->mode == PCRE2_JIT_COMPLETE) + add_jump(compiler, backtracks, partial); + + add_jump(compiler, *cc == OP_REF ? &common->casefulcmp : &common->caselesscmp, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, 0)); + + if (common->mode != PCRE2_JIT_COMPLETE) + { + nopartial = JUMP(SLJIT_JUMP); + JUMPHERE(partial); + /* TMP2 -= STR_END - STR_PTR */ + OP2(SLJIT_SUB, TMP2, 0, TMP2, 0, STR_PTR, 0); + OP2(SLJIT_ADD, TMP2, 0, TMP2, 0, STR_END, 0); + partial = CMP(SLJIT_EQUAL, TMP2, 0, SLJIT_IMM, 0); + OP1(SLJIT_MOV, STR_PTR, 0, STR_END, 0); + add_jump(compiler, *cc == OP_REF ? &common->casefulcmp : &common->caselesscmp, JUMP(SLJIT_FAST_CALL)); + add_jump(compiler, backtracks, CMP(SLJIT_NOT_EQUAL, TMP2, 0, SLJIT_IMM, 0)); + JUMPHERE(partial); + check_partial(common, FALSE); + add_jump(compiler, backtracks, JUMP(SLJIT_JUMP)); + JUMPHERE(nopartial); + } + } + +if (jump != NULL) + { + if (emptyfail) + add_jump(compiler, backtracks, jump); + else + JUMPHERE(jump); + } +} + +static SLJIT_INLINE PCRE2_SPTR compile_ref_iterator_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +BOOL ref = (*cc == OP_REF || *cc == OP_REFI); +backtrack_common *backtrack; +PCRE2_UCHAR type; +int offset = 0; +struct sljit_label *label; +struct sljit_jump *zerolength; +struct sljit_jump *jump = NULL; +PCRE2_SPTR ccbegin = cc; +int min = 0, max = 0; +BOOL minimize; + +PUSH_BACKTRACK(sizeof(ref_iterator_backtrack), cc, NULL); + +if (ref) + offset = GET2(cc, 1) << 1; +else + cc += IMM2_SIZE; +type = cc[1 + IMM2_SIZE]; + +SLJIT_COMPILE_ASSERT((OP_CRSTAR & 0x1) == 0, crstar_opcode_must_be_even); +minimize = (type & 0x1) != 0; +switch(type) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + min = 0; + max = 0; + cc += 1 + IMM2_SIZE + 1; + break; + case OP_CRPLUS: + case OP_CRMINPLUS: + min = 1; + max = 0; + cc += 1 + IMM2_SIZE + 1; + break; + case OP_CRQUERY: + case OP_CRMINQUERY: + min = 0; + max = 1; + cc += 1 + IMM2_SIZE + 1; + break; + case OP_CRRANGE: + case OP_CRMINRANGE: + min = GET2(cc, 1 + IMM2_SIZE + 1); + max = GET2(cc, 1 + IMM2_SIZE + 1 + IMM2_SIZE); + cc += 1 + IMM2_SIZE + 1 + 2 * IMM2_SIZE; + break; + default: + SLJIT_ASSERT_STOP(); + break; + } + +if (!minimize) + { + if (min == 0) + { + allocate_stack(common, 2); + if (ref) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, 0); + /* Temporary release of STR_PTR. */ + OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + /* Handles both invalid and empty cases. Since the minimum repeat, + is zero the invalid case is basically the same as an empty case. */ + if (ref) + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + else + { + compile_dnref_search(common, ccbegin, NULL); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1, TMP2, 0); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw)); + } + /* Restore if not zero length. */ + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + } + else + { + allocate_stack(common, 1); + if (ref) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + if (ref) + { + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1))); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + } + else + { + compile_dnref_search(common, ccbegin, &backtrack->topbacktracks); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1, TMP2, 0); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw)); + } + } + + if (min > 1 || max > 1) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0, SLJIT_IMM, 0); + + label = LABEL(); + if (!ref) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1); + compile_ref_matchingpath(common, ccbegin, &backtrack->topbacktracks, FALSE, FALSE); + + if (min > 1 || max > 1) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0); + OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0, TMP1, 0); + if (min > 1) + CMPTO(SLJIT_LESS, TMP1, 0, SLJIT_IMM, min, label); + if (max > 1) + { + jump = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, max); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + JUMPTO(SLJIT_JUMP, label); + JUMPHERE(jump); + } + } + + if (max == 0) + { + /* Includes min > 1 case as well. */ + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + JUMPTO(SLJIT_JUMP, label); + } + + JUMPHERE(zerolength); + BACKTRACK_AS(ref_iterator_backtrack)->matchingpath = LABEL(); + + count_match(common); + return cc; + } + +allocate_stack(common, ref ? 2 : 3); +if (ref) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); +if (type != OP_CRMINSTAR) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, 0); + +if (min == 0) + { + /* Handles both invalid and empty cases. Since the minimum repeat, + is zero the invalid case is basically the same as an empty case. */ + if (ref) + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + else + { + compile_dnref_search(common, ccbegin, NULL); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(2), TMP2, 0); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw)); + } + /* Length is non-zero, we can match real repeats. */ + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + jump = JUMP(SLJIT_JUMP); + } +else + { + if (ref) + { + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1))); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + } + else + { + compile_dnref_search(common, ccbegin, &backtrack->topbacktracks); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(2), TMP2, 0); + zerolength = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_MEM1(TMP2), sizeof(sljit_sw)); + } + } + +BACKTRACK_AS(ref_iterator_backtrack)->matchingpath = LABEL(); +if (max > 0) + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_GREATER_EQUAL, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, max)); + +if (!ref) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(2)); +compile_ref_matchingpath(common, ccbegin, &backtrack->topbacktracks, TRUE, TRUE); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + +if (min > 1) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP1, 0); + CMPTO(SLJIT_LESS, TMP1, 0, SLJIT_IMM, min, BACKTRACK_AS(ref_iterator_backtrack)->matchingpath); + } +else if (max > 0) + OP2(SLJIT_ADD, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, 1); + +if (jump != NULL) + JUMPHERE(jump); +JUMPHERE(zerolength); + +count_match(common); +return cc; +} + +static SLJIT_INLINE PCRE2_SPTR compile_recurse_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +recurse_entry *entry = common->entries; +recurse_entry *prev = NULL; +sljit_sw start = GET(cc, 1); +PCRE2_SPTR start_cc; +BOOL needs_control_head; + +PUSH_BACKTRACK(sizeof(recurse_backtrack), cc, NULL); + +/* Inlining simple patterns. */ +if (get_framesize(common, common->start + start, NULL, TRUE, &needs_control_head) == no_stack) + { + start_cc = common->start + start; + compile_matchingpath(common, next_opcode(common, start_cc), bracketend(start_cc) - (1 + LINK_SIZE), backtrack); + BACKTRACK_AS(recurse_backtrack)->inlined_pattern = TRUE; + return cc + 1 + LINK_SIZE; + } + +while (entry != NULL) + { + if (entry->start == start) + break; + prev = entry; + entry = entry->next; + } + +if (entry == NULL) + { + entry = sljit_alloc_memory(compiler, sizeof(recurse_entry)); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + entry->next = NULL; + entry->entry = NULL; + entry->calls = NULL; + entry->start = start; + + if (prev != NULL) + prev->next = entry; + else + common->entries = entry; + } + +if (common->has_set_som && common->mark_ptr != 0) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); + allocate_stack(common, 2); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP1, 0); + } +else if (common->has_set_som || common->mark_ptr != 0) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->has_set_som ? (int)(OVECTOR(0)) : common->mark_ptr); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + } + +if (entry->entry == NULL) + add_jump(compiler, &entry->calls, JUMP(SLJIT_FAST_CALL)); +else + JUMPTO(SLJIT_FAST_CALL, entry->entry); +/* Leave if the match is failed. */ +add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0)); +return cc + 1 + LINK_SIZE; +} + +static int SLJIT_CALL do_callout(struct jit_arguments *arguments, pcre2_callout_block *callout_block, PCRE2_SPTR *jit_ovector) +{ +PCRE2_SPTR begin = arguments->begin; +PCRE2_SIZE *ovector = arguments->match_data->ovector; +uint32_t oveccount = arguments->oveccount; +uint32_t i; + +if (arguments->callout == NULL) + return 0; + +callout_block->version = 1; + +/* Offsets in subject. */ +callout_block->subject_length = arguments->end - arguments->begin; +callout_block->start_match = (PCRE2_SPTR)callout_block->subject - arguments->begin; +callout_block->current_position = (PCRE2_SPTR)callout_block->offset_vector - arguments->begin; +callout_block->subject = begin; + +/* Convert and copy the JIT offset vector to the ovector array. */ +callout_block->capture_top = 0; +callout_block->offset_vector = ovector; +for (i = 2; i < oveccount; i += 2) + { + ovector[i] = jit_ovector[i] - begin; + ovector[i + 1] = jit_ovector[i + 1] - begin; + if (jit_ovector[i] >= begin) + callout_block->capture_top = i; + } + +callout_block->capture_top = (callout_block->capture_top >> 1) + 1; +ovector[0] = PCRE2_UNSET; +ovector[1] = PCRE2_UNSET; +return (arguments->callout)(callout_block, arguments->callout_data); +} + +/* Aligning to 8 byte. */ +#define CALLOUT_ARG_SIZE \ + (((int)sizeof(pcre2_callout_block) + 7) & ~7) + +#define CALLOUT_ARG_OFFSET(arg) \ + (-CALLOUT_ARG_SIZE + SLJIT_OFFSETOF(pcre2_callout_block, arg)) + +static SLJIT_INLINE PCRE2_SPTR compile_callout_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +sljit_si mov_opcode; +unsigned int callout_length = (*cc == OP_CALLOUT) + ? PRIV(OP_lengths)[OP_CALLOUT] : GET(cc, 1 + 2 * LINK_SIZE); +sljit_sw value1; +sljit_sw value2; +sljit_sw value3; + +PUSH_BACKTRACK(sizeof(backtrack_common), cc, NULL); + +allocate_stack(common, CALLOUT_ARG_SIZE / sizeof(sljit_sw)); + +SLJIT_ASSERT(common->capture_last_ptr != 0); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr); +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +value1 = (*cc == OP_CALLOUT) ? cc[1 + 2 * LINK_SIZE] : 0; +OP1(SLJIT_MOV_UI, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(callout_number), SLJIT_IMM, value1); +OP1(SLJIT_MOV_UI, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(capture_last), TMP2, 0); + +/* These pointer sized fields temporarly stores internal variables. */ +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(offset_vector), STR_PTR, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(subject), TMP2, 0); + +if (common->mark_ptr != 0) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, mark_ptr)); +mov_opcode = (sizeof(PCRE2_SIZE) == 4) ? SLJIT_MOV_UI : SLJIT_MOV; +OP1(mov_opcode, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(pattern_position), SLJIT_IMM, GET(cc, 1)); +OP1(mov_opcode, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(next_item_length), SLJIT_IMM, GET(cc, 1 + LINK_SIZE)); + +if (*cc == OP_CALLOUT) + { + value1 = 0; + value2 = 0; + value3 = 0; + } +else + { + value1 = (sljit_sw) (cc + (1 + 4*LINK_SIZE) + 1); + value2 = (callout_length - (1 + 4*LINK_SIZE + 2)); + value3 = (sljit_sw) (GET(cc, 1 + 3*LINK_SIZE)); + } + +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(callout_string), SLJIT_IMM, value1); +OP1(mov_opcode, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(callout_string_length), SLJIT_IMM, value2); +OP1(mov_opcode, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(callout_string_offset), SLJIT_IMM, value3); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), CALLOUT_ARG_OFFSET(mark), (common->mark_ptr != 0) ? TMP2 : SLJIT_IMM, 0); + +/* Needed to save important temporary registers. */ +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, STACK_TOP, 0); +OP2(SLJIT_SUB, SLJIT_R1, 0, STACK_TOP, 0, SLJIT_IMM, CALLOUT_ARG_SIZE); +GET_LOCAL_BASE(SLJIT_R2, 0, OVECTOR_START); +sljit_emit_ijump(compiler, SLJIT_CALL3, SLJIT_IMM, SLJIT_FUNC_OFFSET(do_callout)); +OP1(SLJIT_MOV_SI, SLJIT_RETURN_REG, 0, SLJIT_RETURN_REG, 0); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); +free_stack(common, CALLOUT_ARG_SIZE / sizeof(sljit_sw)); + +/* Check return value. */ +OP2(SLJIT_SUB | SLJIT_SET_S, SLJIT_UNUSED, 0, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0); +add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_SIG_GREATER)); +if (common->forced_quit_label == NULL) + add_jump(compiler, &common->forced_quit, JUMP(SLJIT_SIG_LESS)); +else + JUMPTO(SLJIT_SIG_LESS, common->forced_quit_label); +return cc + callout_length; +} + +#undef CALLOUT_ARG_SIZE +#undef CALLOUT_ARG_OFFSET + +static SLJIT_INLINE BOOL assert_needs_str_ptr_saving(PCRE2_SPTR cc) +{ +while (TRUE) + { + switch (*cc) + { + case OP_CALLOUT_STR: + cc += GET(cc, 1 + 2*LINK_SIZE); + break; + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_CALLOUT: + case OP_ALT: + cc += PRIV(OP_lengths)[*cc]; + break; + + case OP_KET: + return FALSE; + + default: + return TRUE; + } + } +} + +static PCRE2_SPTR compile_assert_matchingpath(compiler_common *common, PCRE2_SPTR cc, assert_backtrack *backtrack, BOOL conditional) +{ +DEFINE_COMPILER; +int framesize; +int extrasize; +BOOL needs_control_head; +int private_data_ptr; +backtrack_common altbacktrack; +PCRE2_SPTR ccbegin; +PCRE2_UCHAR opcode; +PCRE2_UCHAR bra = OP_BRA; +jump_list *tmp = NULL; +jump_list **target = (conditional) ? &backtrack->condfailed : &backtrack->common.topbacktracks; +jump_list **found; +/* Saving previous accept variables. */ +BOOL save_local_exit = common->local_exit; +BOOL save_positive_assert = common->positive_assert; +then_trap_backtrack *save_then_trap = common->then_trap; +struct sljit_label *save_quit_label = common->quit_label; +struct sljit_label *save_accept_label = common->accept_label; +jump_list *save_quit = common->quit; +jump_list *save_positive_assert_quit = common->positive_assert_quit; +jump_list *save_accept = common->accept; +struct sljit_jump *jump; +struct sljit_jump *brajump = NULL; + +/* Assert captures then. */ +common->then_trap = NULL; + +if (*cc == OP_BRAZERO || *cc == OP_BRAMINZERO) + { + SLJIT_ASSERT(!conditional); + bra = *cc; + cc++; + } +private_data_ptr = PRIVATE_DATA(cc); +SLJIT_ASSERT(private_data_ptr != 0); +framesize = get_framesize(common, cc, NULL, FALSE, &needs_control_head); +backtrack->framesize = framesize; +backtrack->private_data_ptr = private_data_ptr; +opcode = *cc; +SLJIT_ASSERT(opcode >= OP_ASSERT && opcode <= OP_ASSERTBACK_NOT); +found = (opcode == OP_ASSERT || opcode == OP_ASSERTBACK) ? &tmp : target; +ccbegin = cc; +cc += GET(cc, 1); + +if (bra == OP_BRAMINZERO) + { + /* This is a braminzero backtrack path. */ + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + brajump = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + } + +if (framesize < 0) + { + extrasize = 1; + if (bra == OP_BRA && !assert_needs_str_ptr_saving(ccbegin + 1 + LINK_SIZE)) + extrasize = 0; + + if (needs_control_head) + extrasize++; + + if (framesize == no_frame) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0); + + if (extrasize > 0) + allocate_stack(common, extrasize); + + if (needs_control_head) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + + if (extrasize > 0) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + + if (needs_control_head) + { + SLJIT_ASSERT(extrasize == 2); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP1, 0); + } + } +else + { + extrasize = needs_control_head ? 3 : 2; + allocate_stack(common, framesize + extrasize); + + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP2(SLJIT_SUB, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP2, 0); + if (needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + + if (needs_control_head) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(2), TMP1, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0); + } + else + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP1, 0); + + init_frame(common, ccbegin, NULL, framesize + extrasize - 1, extrasize, FALSE); + } + +memset(&altbacktrack, 0, sizeof(backtrack_common)); +if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + /* Negative assert is stronger than positive assert. */ + common->local_exit = TRUE; + common->quit_label = NULL; + common->quit = NULL; + common->positive_assert = FALSE; + } +else + common->positive_assert = TRUE; +common->positive_assert_quit = NULL; + +while (1) + { + common->accept_label = NULL; + common->accept = NULL; + altbacktrack.top = NULL; + altbacktrack.topbacktracks = NULL; + + if (*ccbegin == OP_ALT && extrasize > 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + + altbacktrack.cc = ccbegin; + compile_matchingpath(common, ccbegin + 1 + LINK_SIZE, cc, &altbacktrack); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + { + if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + common->local_exit = save_local_exit; + common->quit_label = save_quit_label; + common->quit = save_quit; + } + common->positive_assert = save_positive_assert; + common->then_trap = save_then_trap; + common->accept_label = save_accept_label; + common->positive_assert_quit = save_positive_assert_quit; + common->accept = save_accept; + return NULL; + } + common->accept_label = LABEL(); + if (common->accept != NULL) + set_jumps(common->accept, common->accept_label); + + /* Reset stack. */ + if (framesize < 0) + { + if (framesize == no_frame) + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + else if (extrasize > 0) + free_stack(common, extrasize); + + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), 0); + } + else + { + if ((opcode != OP_ASSERT_NOT && opcode != OP_ASSERTBACK_NOT) || conditional) + { + /* We don't need to keep the STR_PTR, only the previous private_data_ptr. */ + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw)); + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), 0); + } + else + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), (framesize + 1) * sizeof(sljit_sw)); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + } + } + + if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + /* We know that STR_PTR was stored on the top of the stack. */ + if (conditional) + { + if (extrasize > 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), needs_control_head ? sizeof(sljit_sw) : 0); + } + else if (bra == OP_BRAZERO) + { + if (framesize < 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 1) * sizeof(sljit_sw)); + else + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw)); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (framesize + extrasize - 1) * sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0); + } + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else if (framesize >= 0) + { + /* For OP_BRA and OP_BRAMINZERO. */ + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw)); + } + } + add_jump(compiler, found, JUMP(SLJIT_JUMP)); + + compile_backtrackingpath(common, altbacktrack.top); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + { + if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + common->local_exit = save_local_exit; + common->quit_label = save_quit_label; + common->quit = save_quit; + } + common->positive_assert = save_positive_assert; + common->then_trap = save_then_trap; + common->accept_label = save_accept_label; + common->positive_assert_quit = save_positive_assert_quit; + common->accept = save_accept; + return NULL; + } + set_jumps(altbacktrack.topbacktracks, LABEL()); + + if (*cc != OP_ALT) + break; + + ccbegin = cc; + cc += GET(cc, 1); + } + +if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + SLJIT_ASSERT(common->positive_assert_quit == NULL); + /* Makes the check less complicated below. */ + common->positive_assert_quit = common->quit; + } + +/* None of them matched. */ +if (common->positive_assert_quit != NULL) + { + jump = JUMP(SLJIT_JUMP); + set_jumps(common->positive_assert_quit, LABEL()); + SLJIT_ASSERT(framesize != no_stack); + if (framesize < 0) + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, extrasize * sizeof(sljit_sw)); + else + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + extrasize) * sizeof(sljit_sw)); + } + JUMPHERE(jump); + } + +if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(1)); + +if (opcode == OP_ASSERT || opcode == OP_ASSERTBACK) + { + /* Assert is failed. */ + if ((conditional && extrasize > 0) || bra == OP_BRAZERO) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + + if (framesize < 0) + { + /* The topmost item should be 0. */ + if (bra == OP_BRAZERO) + { + if (extrasize == 2) + free_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else if (extrasize > 0) + free_stack(common, extrasize); + } + else + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(extrasize - 1)); + /* The topmost item should be 0. */ + if (bra == OP_BRAZERO) + { + free_stack(common, framesize + extrasize - 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else + free_stack(common, framesize + extrasize); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0); + } + jump = JUMP(SLJIT_JUMP); + if (bra != OP_BRAZERO) + add_jump(compiler, target, jump); + + /* Assert is successful. */ + set_jumps(tmp, LABEL()); + if (framesize < 0) + { + /* We know that STR_PTR was stored on the top of the stack. */ + if (extrasize > 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 1) * sizeof(sljit_sw)); + + /* Keep the STR_PTR on the top of the stack. */ + if (bra == OP_BRAZERO) + { + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + if (extrasize == 2) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + } + else if (bra == OP_BRAMINZERO) + { + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + } + else + { + if (bra == OP_BRA) + { + /* We don't need to keep the STR_PTR, only the previous private_data_ptr. */ + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 1) * sizeof(sljit_sw)); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), (extrasize - 2) * sizeof(sljit_sw)); + } + else + { + /* We don't need to keep the STR_PTR, only the previous private_data_ptr. */ + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + 2) * sizeof(sljit_sw)); + if (extrasize == 2) + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + if (bra == OP_BRAMINZERO) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), bra == OP_BRAZERO ? STR_PTR : SLJIT_IMM, 0); + } + } + } + + if (bra == OP_BRAZERO) + { + backtrack->matchingpath = LABEL(); + SET_LABEL(jump, backtrack->matchingpath); + } + else if (bra == OP_BRAMINZERO) + { + JUMPTO(SLJIT_JUMP, backtrack->matchingpath); + JUMPHERE(brajump); + if (framesize >= 0) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), framesize * sizeof(sljit_sw)); + } + set_jumps(backtrack->common.topbacktracks, LABEL()); + } + } +else + { + /* AssertNot is successful. */ + if (framesize < 0) + { + if (extrasize > 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + + if (bra != OP_BRA) + { + if (extrasize == 2) + free_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else if (extrasize > 0) + free_stack(common, extrasize); + } + else + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(extrasize - 1)); + /* The topmost item should be 0. */ + if (bra != OP_BRA) + { + free_stack(common, framesize + extrasize - 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + else + free_stack(common, framesize + extrasize); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0); + } + + if (bra == OP_BRAZERO) + backtrack->matchingpath = LABEL(); + else if (bra == OP_BRAMINZERO) + { + JUMPTO(SLJIT_JUMP, backtrack->matchingpath); + JUMPHERE(brajump); + } + + if (bra != OP_BRA) + { + SLJIT_ASSERT(found == &backtrack->common.topbacktracks); + set_jumps(backtrack->common.topbacktracks, LABEL()); + backtrack->common.topbacktracks = NULL; + } + } + +if (opcode == OP_ASSERT_NOT || opcode == OP_ASSERTBACK_NOT) + { + common->local_exit = save_local_exit; + common->quit_label = save_quit_label; + common->quit = save_quit; + } +common->positive_assert = save_positive_assert; +common->then_trap = save_then_trap; +common->accept_label = save_accept_label; +common->positive_assert_quit = save_positive_assert_quit; +common->accept = save_accept; +return cc + 1 + LINK_SIZE; +} + +static SLJIT_INLINE void match_once_common(compiler_common *common, PCRE2_UCHAR ket, int framesize, int private_data_ptr, BOOL has_alternatives, BOOL needs_control_head) +{ +DEFINE_COMPILER; +int stacksize; + +if (framesize < 0) + { + if (framesize == no_frame) + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + else + { + stacksize = needs_control_head ? 1 : 0; + if (ket != OP_KET || has_alternatives) + stacksize++; + + if (stacksize > 0) + free_stack(common, stacksize); + } + + if (needs_control_head) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), (ket != OP_KET || has_alternatives) ? sizeof(sljit_sw) : 0); + + /* TMP2 which is set here used by OP_KETRMAX below. */ + if (ket == OP_KETRMAX) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), 0); + else if (ket == OP_KETRMIN) + { + /* Move the STR_PTR to the private_data_ptr. */ + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), 0); + } + } +else + { + stacksize = (ket != OP_KET || has_alternatives) ? 2 : 1; + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, (framesize + stacksize) * sizeof(sljit_sw)); + if (needs_control_head) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), 0); + + if (ket == OP_KETRMAX) + { + /* TMP2 which is set here used by OP_KETRMAX below. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + } + } +if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, TMP1, 0); +} + +static SLJIT_INLINE int match_capture_common(compiler_common *common, int stacksize, int offset, int private_data_ptr) +{ +DEFINE_COMPILER; + +if (common->capture_last_ptr != 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, SLJIT_IMM, offset >> 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP1, 0); + stacksize++; + } +if (common->optimized_cbracket[offset >> 1] == 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP1, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize + 1), TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + stacksize += 2; + } +return stacksize; +} + +/* + Handling bracketed expressions is probably the most complex part. + + Stack layout naming characters: + S - Push the current STR_PTR + 0 - Push a 0 (NULL) + A - Push the current STR_PTR. Needed for restoring the STR_PTR + before the next alternative. Not pushed if there are no alternatives. + M - Any values pushed by the current alternative. Can be empty, or anything. + C - Push the previous OVECTOR(i), OVECTOR(i+1) and OVECTOR_PRIV(i) to the stack. + L - Push the previous local (pointed by localptr) to the stack + () - opional values stored on the stack + ()* - optonal, can be stored multiple times + + The following list shows the regular expression templates, their PCRE byte codes + and stack layout supported by pcre-sljit. + + (?:) OP_BRA | OP_KET A M + () OP_CBRA | OP_KET C M + (?:)+ OP_BRA | OP_KETRMAX 0 A M S ( A M S )* + OP_SBRA | OP_KETRMAX 0 L M S ( L M S )* + (?:)+? OP_BRA | OP_KETRMIN 0 A M S ( A M S )* + OP_SBRA | OP_KETRMIN 0 L M S ( L M S )* + ()+ OP_CBRA | OP_KETRMAX 0 C M S ( C M S )* + OP_SCBRA | OP_KETRMAX 0 C M S ( C M S )* + ()+? OP_CBRA | OP_KETRMIN 0 C M S ( C M S )* + OP_SCBRA | OP_KETRMIN 0 C M S ( C M S )* + (?:)? OP_BRAZERO | OP_BRA | OP_KET S ( A M 0 ) + (?:)?? OP_BRAMINZERO | OP_BRA | OP_KET S ( A M 0 ) + ()? OP_BRAZERO | OP_CBRA | OP_KET S ( C M 0 ) + ()?? OP_BRAMINZERO | OP_CBRA | OP_KET S ( C M 0 ) + (?:)* OP_BRAZERO | OP_BRA | OP_KETRMAX S 0 ( A M S )* + OP_BRAZERO | OP_SBRA | OP_KETRMAX S 0 ( L M S )* + (?:)*? OP_BRAMINZERO | OP_BRA | OP_KETRMIN S 0 ( A M S )* + OP_BRAMINZERO | OP_SBRA | OP_KETRMIN S 0 ( L M S )* + ()* OP_BRAZERO | OP_CBRA | OP_KETRMAX S 0 ( C M S )* + OP_BRAZERO | OP_SCBRA | OP_KETRMAX S 0 ( C M S )* + ()*? OP_BRAMINZERO | OP_CBRA | OP_KETRMIN S 0 ( C M S )* + OP_BRAMINZERO | OP_SCBRA | OP_KETRMIN S 0 ( C M S )* + + + Stack layout naming characters: + A - Push the alternative index (starting from 0) on the stack. + Not pushed if there is no alternatives. + M - Any values pushed by the current alternative. Can be empty, or anything. + + The next list shows the possible content of a bracket: + (|) OP_*BRA | OP_ALT ... M A + (?()|) OP_*COND | OP_ALT M A + (?>|) OP_ONCE | OP_ALT ... [stack trace] M A + (?>|) OP_ONCE_NC | OP_ALT ... [stack trace] M A + Or nothing, if trace is unnecessary +*/ + +static PCRE2_SPTR compile_bracket_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +PCRE2_UCHAR opcode; +int private_data_ptr = 0; +int offset = 0; +int i, stacksize; +int repeat_ptr = 0, repeat_length = 0; +int repeat_type = 0, repeat_count = 0; +PCRE2_SPTR ccbegin; +PCRE2_SPTR matchingpath; +PCRE2_SPTR slot; +PCRE2_UCHAR bra = OP_BRA; +PCRE2_UCHAR ket; +assert_backtrack *assert; +BOOL has_alternatives; +BOOL needs_control_head = FALSE; +struct sljit_jump *jump; +struct sljit_jump *skip; +struct sljit_label *rmax_label = NULL; +struct sljit_jump *braminzero = NULL; + +PUSH_BACKTRACK(sizeof(bracket_backtrack), cc, NULL); + +if (*cc == OP_BRAZERO || *cc == OP_BRAMINZERO) + { + bra = *cc; + cc++; + opcode = *cc; + } + +opcode = *cc; +ccbegin = cc; +matchingpath = bracketend(cc) - 1 - LINK_SIZE; +ket = *matchingpath; +if (ket == OP_KET && PRIVATE_DATA(matchingpath) != 0) + { + repeat_ptr = PRIVATE_DATA(matchingpath); + repeat_length = PRIVATE_DATA(matchingpath + 1); + repeat_type = PRIVATE_DATA(matchingpath + 2); + repeat_count = PRIVATE_DATA(matchingpath + 3); + SLJIT_ASSERT(repeat_length != 0 && repeat_type != 0 && repeat_count != 0); + if (repeat_type == OP_UPTO) + ket = OP_KETRMAX; + if (repeat_type == OP_MINUPTO) + ket = OP_KETRMIN; + } + +matchingpath = ccbegin + 1 + LINK_SIZE; +SLJIT_ASSERT(ket == OP_KET || ket == OP_KETRMAX || ket == OP_KETRMIN); +SLJIT_ASSERT(!((bra == OP_BRAZERO && ket == OP_KETRMIN) || (bra == OP_BRAMINZERO && ket == OP_KETRMAX))); +cc += GET(cc, 1); + +has_alternatives = *cc == OP_ALT; +if (SLJIT_UNLIKELY(opcode == OP_COND || opcode == OP_SCOND)) + { + SLJIT_COMPILE_ASSERT(OP_DNRREF == OP_RREF + 1 && OP_FALSE == OP_RREF + 2 && OP_TRUE == OP_RREF + 3, + compile_time_checks_must_be_grouped_together); + has_alternatives = ((*matchingpath >= OP_RREF && *matchingpath <= OP_TRUE) || *matchingpath == OP_FAIL) ? FALSE : TRUE; + } + +if (SLJIT_UNLIKELY(opcode == OP_COND) && (*cc == OP_KETRMAX || *cc == OP_KETRMIN)) + opcode = OP_SCOND; +if (SLJIT_UNLIKELY(opcode == OP_ONCE_NC)) + opcode = OP_ONCE; + +if (opcode == OP_CBRA || opcode == OP_SCBRA) + { + /* Capturing brackets has a pre-allocated space. */ + offset = GET2(ccbegin, 1 + LINK_SIZE); + if (common->optimized_cbracket[offset] == 0) + { + private_data_ptr = OVECTOR_PRIV(offset); + offset <<= 1; + } + else + { + offset <<= 1; + private_data_ptr = OVECTOR(offset); + } + BACKTRACK_AS(bracket_backtrack)->private_data_ptr = private_data_ptr; + matchingpath += IMM2_SIZE; + } +else if (opcode == OP_ONCE || opcode == OP_SBRA || opcode == OP_SCOND) + { + /* Other brackets simply allocate the next entry. */ + private_data_ptr = PRIVATE_DATA(ccbegin); + SLJIT_ASSERT(private_data_ptr != 0); + BACKTRACK_AS(bracket_backtrack)->private_data_ptr = private_data_ptr; + if (opcode == OP_ONCE) + BACKTRACK_AS(bracket_backtrack)->u.framesize = get_framesize(common, ccbegin, NULL, FALSE, &needs_control_head); + } + +/* Instructions before the first alternative. */ +stacksize = 0; +if (ket == OP_KETRMAX || (ket == OP_KETRMIN && bra != OP_BRAMINZERO)) + stacksize++; +if (bra == OP_BRAZERO) + stacksize++; + +if (stacksize > 0) + allocate_stack(common, stacksize); + +stacksize = 0; +if (ket == OP_KETRMAX || (ket == OP_KETRMIN && bra != OP_BRAMINZERO)) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), SLJIT_IMM, 0); + stacksize++; + } + +if (bra == OP_BRAZERO) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + +if (bra == OP_BRAMINZERO) + { + /* This is a backtrack path! (Since the try-path of OP_BRAMINZERO matches to the empty string) */ + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + if (ket != OP_KETRMIN) + { + free_stack(common, 1); + braminzero = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + } + else + { + if (opcode == OP_ONCE || opcode >= OP_SBRA) + { + jump = CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + /* Nothing stored during the first run. */ + skip = JUMP(SLJIT_JUMP); + JUMPHERE(jump); + /* Checking zero-length iteration. */ + if (opcode != OP_ONCE || BACKTRACK_AS(bracket_backtrack)->u.framesize < 0) + { + /* When we come from outside, private_data_ptr contains the previous STR_PTR. */ + braminzero = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + } + else + { + /* Except when the whole stack frame must be saved. */ + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + braminzero = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), (BACKTRACK_AS(bracket_backtrack)->u.framesize + 1) * sizeof(sljit_sw)); + } + JUMPHERE(skip); + } + else + { + jump = CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + JUMPHERE(jump); + } + } + } + +if (repeat_type != 0) + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, repeat_count); + if (repeat_type == OP_EXACT) + rmax_label = LABEL(); + } + +if (ket == OP_KETRMIN) + BACKTRACK_AS(bracket_backtrack)->recursive_matchingpath = LABEL(); + +if (ket == OP_KETRMAX) + { + rmax_label = LABEL(); + if (has_alternatives && opcode != OP_ONCE && opcode < OP_SBRA && repeat_type == 0) + BACKTRACK_AS(bracket_backtrack)->alternative_matchingpath = rmax_label; + } + +/* Handling capturing brackets and alternatives. */ +if (opcode == OP_ONCE) + { + stacksize = 0; + if (needs_control_head) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + stacksize++; + } + + if (BACKTRACK_AS(bracket_backtrack)->u.framesize < 0) + { + /* Neither capturing brackets nor recursions are found in the block. */ + if (ket == OP_KETRMIN) + { + stacksize += 2; + if (!needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + } + else + { + if (BACKTRACK_AS(bracket_backtrack)->u.framesize == no_frame) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0); + if (ket == OP_KETRMAX || has_alternatives) + stacksize++; + } + + if (stacksize > 0) + allocate_stack(common, stacksize); + + stacksize = 0; + if (needs_control_head) + { + stacksize++; + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + } + + if (ket == OP_KETRMIN) + { + if (needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + if (BACKTRACK_AS(bracket_backtrack)->u.framesize == no_frame) + OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, needs_control_head ? (2 * sizeof(sljit_sw)) : sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize + 1), TMP2, 0); + } + else if (ket == OP_KETRMAX || has_alternatives) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + } + else + { + if (ket != OP_KET || has_alternatives) + stacksize++; + + stacksize += BACKTRACK_AS(bracket_backtrack)->u.framesize + 1; + allocate_stack(common, stacksize); + + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP2(SLJIT_SUB, TMP2, 0, STACK_TOP, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw)); + + stacksize = needs_control_head ? 1 : 0; + if (ket != OP_KET || has_alternatives) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP2, 0); + stacksize++; + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP1, 0); + } + else + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP1, 0); + } + init_frame(common, ccbegin, NULL, BACKTRACK_AS(bracket_backtrack)->u.framesize + stacksize, stacksize + 1, FALSE); + } + } +else if (opcode == OP_CBRA || opcode == OP_SCBRA) + { + /* Saving the previous values. */ + if (common->optimized_cbracket[offset >> 1] != 0) + { + SLJIT_ASSERT(private_data_ptr == OVECTOR(offset)); + allocate_stack(common, 2); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr + sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP1, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP2, 0); + } + else + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + } + } +else if (opcode == OP_SBRA || opcode == OP_SCOND) + { + /* Saving the previous value. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + } +else if (has_alternatives) + { + /* Pushing the starting string pointer. */ + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + } + +/* Generating code for the first alternative. */ +if (opcode == OP_COND || opcode == OP_SCOND) + { + if (*matchingpath == OP_CREF) + { + SLJIT_ASSERT(has_alternatives); + add_jump(compiler, &(BACKTRACK_AS(bracket_backtrack)->u.condfailed), + CMP(SLJIT_EQUAL, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(matchingpath, 1) << 1), SLJIT_MEM1(SLJIT_SP), OVECTOR(1))); + matchingpath += 1 + IMM2_SIZE; + } + else if (*matchingpath == OP_DNCREF) + { + SLJIT_ASSERT(has_alternatives); + + i = GET2(matchingpath, 1 + IMM2_SIZE); + slot = common->name_table + GET2(matchingpath, 1) * common->name_entry_size; + OP1(SLJIT_MOV, TMP3, 0, STR_PTR, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(1)); + OP2(SLJIT_SUB | SLJIT_SET_E, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(slot, 0) << 1), TMP1, 0); + slot += common->name_entry_size; + i--; + while (i-- > 0) + { + OP2(SLJIT_SUB, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(GET2(slot, 0) << 1), TMP1, 0); + OP2(SLJIT_OR | SLJIT_SET_E, TMP2, 0, TMP2, 0, STR_PTR, 0); + slot += common->name_entry_size; + } + OP1(SLJIT_MOV, STR_PTR, 0, TMP3, 0); + add_jump(compiler, &(BACKTRACK_AS(bracket_backtrack)->u.condfailed), JUMP(SLJIT_ZERO)); + matchingpath += 1 + 2 * IMM2_SIZE; + } + else if ((*matchingpath >= OP_RREF && *matchingpath <= OP_TRUE) || *matchingpath == OP_FAIL) + { + /* Never has other case. */ + BACKTRACK_AS(bracket_backtrack)->u.condfailed = NULL; + SLJIT_ASSERT(!has_alternatives); + + if (*matchingpath == OP_TRUE) + { + stacksize = 1; + matchingpath++; + } + else if (*matchingpath == OP_FALSE || *matchingpath == OP_FAIL) + stacksize = 0; + else if (*matchingpath == OP_RREF) + { + stacksize = GET2(matchingpath, 1); + if (common->currententry == NULL) + stacksize = 0; + else if (stacksize == RREF_ANY) + stacksize = 1; + else if (common->currententry->start == 0) + stacksize = stacksize == 0; + else + stacksize = stacksize == (int)GET2(common->start, common->currententry->start + 1 + LINK_SIZE); + + if (stacksize != 0) + matchingpath += 1 + IMM2_SIZE; + } + else + { + if (common->currententry == NULL || common->currententry->start == 0) + stacksize = 0; + else + { + stacksize = GET2(matchingpath, 1 + IMM2_SIZE); + slot = common->name_table + GET2(matchingpath, 1) * common->name_entry_size; + i = (int)GET2(common->start, common->currententry->start + 1 + LINK_SIZE); + while (stacksize > 0) + { + if ((int)GET2(slot, 0) == i) + break; + slot += common->name_entry_size; + stacksize--; + } + } + + if (stacksize != 0) + matchingpath += 1 + 2 * IMM2_SIZE; + } + + /* The stacksize == 0 is a common "else" case. */ + if (stacksize == 0) + { + if (*cc == OP_ALT) + { + matchingpath = cc + 1 + LINK_SIZE; + cc += GET(cc, 1); + } + else + matchingpath = cc; + } + } + else + { + SLJIT_ASSERT(has_alternatives && *matchingpath >= OP_ASSERT && *matchingpath <= OP_ASSERTBACK_NOT); + /* Similar code as PUSH_BACKTRACK macro. */ + assert = sljit_alloc_memory(compiler, sizeof(assert_backtrack)); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + memset(assert, 0, sizeof(assert_backtrack)); + assert->common.cc = matchingpath; + BACKTRACK_AS(bracket_backtrack)->u.assert = assert; + matchingpath = compile_assert_matchingpath(common, matchingpath, assert, TRUE); + } + } + +compile_matchingpath(common, matchingpath, cc, backtrack); +if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + +if (opcode == OP_ONCE) + match_once_common(common, ket, BACKTRACK_AS(bracket_backtrack)->u.framesize, private_data_ptr, has_alternatives, needs_control_head); + +stacksize = 0; +if (repeat_type == OP_MINUPTO) + { + /* We need to preserve the counter. TMP2 will be used below. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), repeat_ptr); + stacksize++; + } +if (ket != OP_KET || bra != OP_BRA) + stacksize++; +if (offset != 0) + { + if (common->capture_last_ptr != 0) + stacksize++; + if (common->optimized_cbracket[offset >> 1] == 0) + stacksize += 2; + } +if (has_alternatives && opcode != OP_ONCE) + stacksize++; + +if (stacksize > 0) + allocate_stack(common, stacksize); + +stacksize = 0; +if (repeat_type == OP_MINUPTO) + { + /* TMP2 was set above. */ + OP2(SLJIT_SUB, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP2, 0, SLJIT_IMM, 1); + stacksize++; + } + +if (ket != OP_KET || bra != OP_BRA) + { + if (ket != OP_KET) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + else + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), SLJIT_IMM, 0); + stacksize++; + } + +if (offset != 0) + stacksize = match_capture_common(common, stacksize, offset, private_data_ptr); + +if (has_alternatives) + { + if (opcode != OP_ONCE) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), SLJIT_IMM, 0); + if (ket != OP_KETRMAX) + BACKTRACK_AS(bracket_backtrack)->alternative_matchingpath = LABEL(); + } + +/* Must be after the matchingpath label. */ +if (offset != 0 && common->optimized_cbracket[offset >> 1] != 0) + { + SLJIT_ASSERT(private_data_ptr == OVECTOR(offset + 0)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); + } + +if (ket == OP_KETRMAX) + { + if (repeat_type != 0) + { + if (has_alternatives) + BACKTRACK_AS(bracket_backtrack)->alternative_matchingpath = LABEL(); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, rmax_label); + /* Drop STR_PTR for greedy plus quantifier. */ + if (opcode != OP_ONCE) + free_stack(common, 1); + } + else if (opcode == OP_ONCE || opcode >= OP_SBRA) + { + if (has_alternatives) + BACKTRACK_AS(bracket_backtrack)->alternative_matchingpath = LABEL(); + /* Checking zero-length iteration. */ + if (opcode != OP_ONCE) + { + CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STR_PTR, 0, rmax_label); + /* Drop STR_PTR for greedy plus quantifier. */ + if (bra != OP_BRAZERO) + free_stack(common, 1); + } + else + /* TMP2 must contain the starting STR_PTR. */ + CMPTO(SLJIT_NOT_EQUAL, TMP2, 0, STR_PTR, 0, rmax_label); + } + else + JUMPTO(SLJIT_JUMP, rmax_label); + BACKTRACK_AS(bracket_backtrack)->recursive_matchingpath = LABEL(); + } + +if (repeat_type == OP_EXACT) + { + count_match(common); + OP2(SLJIT_SUB | SLJIT_SET_E, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, rmax_label); + } +else if (repeat_type == OP_UPTO) + { + /* We need to preserve the counter. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), repeat_ptr); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + } + +if (bra == OP_BRAZERO) + BACKTRACK_AS(bracket_backtrack)->zero_matchingpath = LABEL(); + +if (bra == OP_BRAMINZERO) + { + /* This is a backtrack path! (From the viewpoint of OP_BRAMINZERO) */ + JUMPTO(SLJIT_JUMP, ((braminzero_backtrack *)parent)->matchingpath); + if (braminzero != NULL) + { + JUMPHERE(braminzero); + /* We need to release the end pointer to perform the + backtrack for the zero-length iteration. When + framesize is < 0, OP_ONCE will do the release itself. */ + if (opcode == OP_ONCE && BACKTRACK_AS(bracket_backtrack)->u.framesize >= 0) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + } + else if (ket == OP_KETRMIN && opcode != OP_ONCE) + free_stack(common, 1); + } + /* Continue to the normal backtrack. */ + } + +if ((ket != OP_KET && bra != OP_BRAMINZERO) || bra == OP_BRAZERO) + count_match(common); + +/* Skip the other alternatives. */ +while (*cc == OP_ALT) + cc += GET(cc, 1); +cc += 1 + LINK_SIZE; + +if (opcode == OP_ONCE) + { + /* We temporarily encode the needs_control_head in the lowest bit. + Note: on the target architectures of SLJIT the ((x << 1) >> 1) returns + the same value for small signed numbers (including negative numbers). */ + BACKTRACK_AS(bracket_backtrack)->u.framesize = (BACKTRACK_AS(bracket_backtrack)->u.framesize << 1) | (needs_control_head ? 1 : 0); + } +return cc + repeat_length; +} + +static PCRE2_SPTR compile_bracketpos_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +PCRE2_UCHAR opcode; +int private_data_ptr; +int cbraprivptr = 0; +BOOL needs_control_head; +int framesize; +int stacksize; +int offset = 0; +BOOL zero = FALSE; +PCRE2_SPTR ccbegin = NULL; +int stack; /* Also contains the offset of control head. */ +struct sljit_label *loop = NULL; +struct jump_list *emptymatch = NULL; + +PUSH_BACKTRACK(sizeof(bracketpos_backtrack), cc, NULL); +if (*cc == OP_BRAPOSZERO) + { + zero = TRUE; + cc++; + } + +opcode = *cc; +private_data_ptr = PRIVATE_DATA(cc); +SLJIT_ASSERT(private_data_ptr != 0); +BACKTRACK_AS(bracketpos_backtrack)->private_data_ptr = private_data_ptr; +switch(opcode) + { + case OP_BRAPOS: + case OP_SBRAPOS: + ccbegin = cc + 1 + LINK_SIZE; + break; + + case OP_CBRAPOS: + case OP_SCBRAPOS: + offset = GET2(cc, 1 + LINK_SIZE); + /* This case cannot be optimized in the same was as + normal capturing brackets. */ + SLJIT_ASSERT(common->optimized_cbracket[offset] == 0); + cbraprivptr = OVECTOR_PRIV(offset); + offset <<= 1; + ccbegin = cc + 1 + LINK_SIZE + IMM2_SIZE; + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + +framesize = get_framesize(common, cc, NULL, FALSE, &needs_control_head); +BACKTRACK_AS(bracketpos_backtrack)->framesize = framesize; +if (framesize < 0) + { + if (offset != 0) + { + stacksize = 2; + if (common->capture_last_ptr != 0) + stacksize++; + } + else + stacksize = 1; + + if (needs_control_head) + stacksize++; + if (!zero) + stacksize++; + + BACKTRACK_AS(bracketpos_backtrack)->stacksize = stacksize; + allocate_stack(common, stacksize); + if (framesize == no_frame) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0); + + stack = 0; + if (offset != 0) + { + stack = 2; + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP1, 0); + if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), TMP2, 0); + if (needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + if (common->capture_last_ptr != 0) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(2), TMP1, 0); + stack = 3; + } + } + else + { + if (needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + stack = 1; + } + + if (needs_control_head) + stack++; + if (!zero) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stack), SLJIT_IMM, 1); + if (needs_control_head) + { + stack--; + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stack), TMP2, 0); + } + } +else + { + stacksize = framesize + 1; + if (!zero) + stacksize++; + if (needs_control_head) + stacksize++; + if (offset == 0) + stacksize++; + BACKTRACK_AS(bracketpos_backtrack)->stacksize = stacksize; + + allocate_stack(common, stacksize); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + if (needs_control_head) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), private_data_ptr, STACK_TOP, 0, SLJIT_IMM, -STACK(stacksize - 1)); + + stack = 0; + if (!zero) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 1); + stack = 1; + } + if (needs_control_head) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stack), TMP2, 0); + stack++; + } + if (offset == 0) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stack), STR_PTR, 0); + stack++; + } + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stack), TMP1, 0); + init_frame(common, cc, NULL, stacksize - 1, stacksize - framesize, FALSE); + stack -= 1 + (offset == 0); + } + +if (offset != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), cbraprivptr, STR_PTR, 0); + +loop = LABEL(); +while (*cc != OP_KETRPOS) + { + backtrack->top = NULL; + backtrack->topbacktracks = NULL; + cc += GET(cc, 1); + + compile_matchingpath(common, ccbegin, cc, backtrack); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + + if (framesize < 0) + { + if (framesize == no_frame) + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + + if (offset != 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), cbraprivptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), cbraprivptr, STR_PTR, 0); + if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, SLJIT_IMM, offset >> 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + } + else + { + if (opcode == OP_SBRAPOS) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + } + + /* Even if the match is empty, we need to reset the control head. */ + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(stack)); + + if (opcode == OP_SBRAPOS || opcode == OP_SCBRAPOS) + add_jump(compiler, &emptymatch, CMP(SLJIT_EQUAL, TMP1, 0, STR_PTR, 0)); + + if (!zero) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize - 1), SLJIT_IMM, 0); + } + else + { + if (offset != 0) + { + OP2(SLJIT_ADD, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_IMM, stacksize * sizeof(sljit_sw)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), cbraprivptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), cbraprivptr, STR_PTR, 0); + if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, SLJIT_IMM, offset >> 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + } + else + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP2(SLJIT_ADD, STACK_TOP, 0, TMP2, 0, SLJIT_IMM, stacksize * sizeof(sljit_sw)); + if (opcode == OP_SBRAPOS) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw), STR_PTR, 0); + } + + /* Even if the match is empty, we need to reset the control head. */ + if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_MEM1(STACK_TOP), STACK(stack)); + + if (opcode == OP_SBRAPOS || opcode == OP_SCBRAPOS) + add_jump(compiler, &emptymatch, CMP(SLJIT_EQUAL, TMP1, 0, STR_PTR, 0)); + + if (!zero) + { + if (framesize < 0) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize - 1), SLJIT_IMM, 0); + else + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + } + } + + JUMPTO(SLJIT_JUMP, loop); + flush_stubs(common); + + compile_backtrackingpath(common, backtrack->top); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return NULL; + set_jumps(backtrack->topbacktracks, LABEL()); + + if (framesize < 0) + { + if (offset != 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), cbraprivptr); + else + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + } + else + { + if (offset != 0) + { + /* Last alternative. */ + if (*cc == OP_KETRPOS) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), cbraprivptr); + } + else + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(TMP2), (framesize + 1) * sizeof(sljit_sw)); + } + } + + if (*cc == OP_KETRPOS) + break; + ccbegin = cc + 1 + LINK_SIZE; + } + +/* We don't have to restore the control head in case of a failed match. */ + +backtrack->topbacktracks = NULL; +if (!zero) + { + if (framesize < 0) + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), STACK(stacksize - 1), SLJIT_IMM, 0)); + else /* TMP2 is set to [private_data_ptr] above. */ + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(TMP2), (stacksize - 1) * sizeof(sljit_sw), SLJIT_IMM, 0)); + } + +/* None of them matched. */ +set_jumps(emptymatch, LABEL()); +count_match(common); +return cc + 1 + LINK_SIZE; +} + +static SLJIT_INLINE PCRE2_SPTR get_iterator_parameters(compiler_common *common, PCRE2_SPTR cc, PCRE2_UCHAR *opcode, PCRE2_UCHAR *type, sljit_ui *max, sljit_ui *exact, PCRE2_SPTR *end) +{ +int class_len; + +*opcode = *cc; +*exact = 0; + +if (*opcode >= OP_STAR && *opcode <= OP_POSUPTO) + { + cc++; + *type = OP_CHAR; + } +else if (*opcode >= OP_STARI && *opcode <= OP_POSUPTOI) + { + cc++; + *type = OP_CHARI; + *opcode -= OP_STARI - OP_STAR; + } +else if (*opcode >= OP_NOTSTAR && *opcode <= OP_NOTPOSUPTO) + { + cc++; + *type = OP_NOT; + *opcode -= OP_NOTSTAR - OP_STAR; + } +else if (*opcode >= OP_NOTSTARI && *opcode <= OP_NOTPOSUPTOI) + { + cc++; + *type = OP_NOTI; + *opcode -= OP_NOTSTARI - OP_STAR; + } +else if (*opcode >= OP_TYPESTAR && *opcode <= OP_TYPEPOSUPTO) + { + cc++; + *opcode -= OP_TYPESTAR - OP_STAR; + *type = OP_END; + } +else + { + SLJIT_ASSERT(*opcode == OP_CLASS || *opcode == OP_NCLASS || *opcode == OP_XCLASS); + *type = *opcode; + cc++; + class_len = (*type < OP_XCLASS) ? (int)(1 + (32 / sizeof(PCRE2_UCHAR))) : GET(cc, 0); + *opcode = cc[class_len - 1]; + + if (*opcode >= OP_CRSTAR && *opcode <= OP_CRMINQUERY) + { + *opcode -= OP_CRSTAR - OP_STAR; + *end = cc + class_len; + + if (*opcode == OP_PLUS || *opcode == OP_MINPLUS) + { + *exact = 1; + *opcode -= OP_PLUS - OP_STAR; + } + } + else if (*opcode >= OP_CRPOSSTAR && *opcode <= OP_CRPOSQUERY) + { + *opcode -= OP_CRPOSSTAR - OP_POSSTAR; + *end = cc + class_len; + + if (*opcode == OP_POSPLUS) + { + *exact = 1; + *opcode = OP_POSSTAR; + } + } + else + { + SLJIT_ASSERT(*opcode == OP_CRRANGE || *opcode == OP_CRMINRANGE || *opcode == OP_CRPOSRANGE); + *max = GET2(cc, (class_len + IMM2_SIZE)); + *exact = GET2(cc, class_len); + + if (*max == 0) + { + if (*opcode == OP_CRPOSRANGE) + *opcode = OP_POSSTAR; + else + *opcode -= OP_CRRANGE - OP_STAR; + } + else + { + *max -= *exact; + if (*max == 0) + *opcode = OP_EXACT; + else if (*max == 1) + { + if (*opcode == OP_CRPOSRANGE) + *opcode = OP_POSQUERY; + else + *opcode -= OP_CRRANGE - OP_QUERY; + } + else + { + if (*opcode == OP_CRPOSRANGE) + *opcode = OP_POSUPTO; + else + *opcode -= OP_CRRANGE - OP_UPTO; + } + } + *end = cc + class_len + 2 * IMM2_SIZE; + } + return cc; + } + +switch(*opcode) + { + case OP_EXACT: + *exact = GET2(cc, 0); + cc += IMM2_SIZE; + break; + + case OP_PLUS: + case OP_MINPLUS: + *exact = 1; + *opcode -= OP_PLUS - OP_STAR; + break; + + case OP_POSPLUS: + *exact = 1; + *opcode = OP_POSSTAR; + break; + + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + *max = GET2(cc, 0); + cc += IMM2_SIZE; + break; + } + +if (*type == OP_END) + { + *type = *cc; + *end = next_opcode(common, cc); + cc++; + return cc; + } + +*end = cc + 1; +#ifdef SUPPORT_UNICODE +if (common->utf && HAS_EXTRALEN(*cc)) *end += GET_EXTRALEN(*cc); +#endif +return cc; +} + +static PCRE2_SPTR compile_iterator_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +PCRE2_UCHAR opcode; +PCRE2_UCHAR type; +sljit_ui max = 0, exact; +BOOL fast_fail; +sljit_si fast_str_ptr; +BOOL charpos_enabled; +PCRE2_UCHAR charpos_char; +unsigned int charpos_othercasebit; +PCRE2_SPTR end; +jump_list *no_match = NULL; +jump_list *no_char1_match = NULL; +struct sljit_jump *jump = NULL; +struct sljit_label *label; +int private_data_ptr = PRIVATE_DATA(cc); +int base = (private_data_ptr == 0) ? SLJIT_MEM1(STACK_TOP) : SLJIT_MEM1(SLJIT_SP); +int offset0 = (private_data_ptr == 0) ? STACK(0) : private_data_ptr; +int offset1 = (private_data_ptr == 0) ? STACK(1) : private_data_ptr + (int)sizeof(sljit_sw); +int tmp_base, tmp_offset; + +PUSH_BACKTRACK(sizeof(char_iterator_backtrack), cc, NULL); + +fast_str_ptr = PRIVATE_DATA(cc + 1); +fast_fail = TRUE; + +SLJIT_ASSERT(common->fast_forward_bc_ptr == NULL || fast_str_ptr == 0 || cc == common->fast_forward_bc_ptr); + +if (cc == common->fast_forward_bc_ptr) + fast_fail = FALSE; +else if (common->fast_fail_start_ptr == 0) + fast_str_ptr = 0; + +SLJIT_ASSERT(common->fast_forward_bc_ptr != NULL || fast_str_ptr == 0 + || (fast_str_ptr >= common->fast_fail_start_ptr && fast_str_ptr <= common->fast_fail_end_ptr)); + +cc = get_iterator_parameters(common, cc, &opcode, &type, &max, &exact, &end); + +if (type != OP_EXTUNI) + { + tmp_base = TMP3; + tmp_offset = 0; + } +else + { + tmp_base = SLJIT_MEM1(SLJIT_SP); + tmp_offset = POSSESSIVE0; + } + +if (fast_fail && fast_str_ptr != 0) + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), fast_str_ptr)); + +/* Handle fixed part first. */ +if (exact > 1) + { + SLJIT_ASSERT(fast_str_ptr == 0); + if (common->mode == PCRE2_JIT_COMPLETE +#ifdef SUPPORT_UNICODE + && !common->utf +#endif + ) + { + OP2(SLJIT_ADD, TMP1, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(exact)); + add_jump(compiler, &backtrack->topbacktracks, CMP(SLJIT_GREATER, TMP1, 0, STR_END, 0)); + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, exact); + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, FALSE); + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + } + else + { + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, exact); + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, TRUE); + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + } + } +else if (exact == 1) + compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, TRUE); + +switch(opcode) + { + case OP_STAR: + case OP_UPTO: + SLJIT_ASSERT(fast_str_ptr == 0 || opcode == OP_STAR); + + if (type == OP_ANYNL || type == OP_EXTUNI) + { + SLJIT_ASSERT(private_data_ptr == 0); + SLJIT_ASSERT(fast_str_ptr == 0); + + allocate_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, 0); + + if (opcode == OP_UPTO) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0, SLJIT_IMM, max); + + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &BACKTRACK_AS(char_iterator_backtrack)->u.backtracks, TRUE); + if (opcode == OP_UPTO) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0); + OP2(SLJIT_SUB | SLJIT_SET_E, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + jump = JUMP(SLJIT_ZERO); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE0, TMP1, 0); + } + + /* We cannot use TMP3 because of this allocate_stack. */ + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + JUMPTO(SLJIT_JUMP, label); + if (jump != NULL) + JUMPHERE(jump); + } + else + { + charpos_enabled = FALSE; + charpos_char = 0; + charpos_othercasebit = 0; + + if ((type != OP_CHAR && type != OP_CHARI) && (*end == OP_CHAR || *end == OP_CHARI)) + { + charpos_enabled = TRUE; +#ifdef SUPPORT_UNICODE + charpos_enabled = !common->utf || !HAS_EXTRALEN(end[1]); +#endif + if (charpos_enabled && *end == OP_CHARI && char_has_othercase(common, end + 1)) + { + charpos_othercasebit = char_get_othercase_bit(common, end + 1); + if (charpos_othercasebit == 0) + charpos_enabled = FALSE; + } + + if (charpos_enabled) + { + charpos_char = end[1]; + /* Consumpe the OP_CHAR opcode. */ + end += 2; +#if PCRE2_CODE_UNIT_WIDTH == 8 + SLJIT_ASSERT((charpos_othercasebit >> 8) == 0); +#elif PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + SLJIT_ASSERT((charpos_othercasebit >> 9) == 0); + if ((charpos_othercasebit & 0x100) != 0) + { + charpos_othercasebit = (charpos_othercasebit & 0xff) << 8; + } +#endif + if (charpos_othercasebit != 0) + charpos_char |= charpos_othercasebit; + + BACKTRACK_AS(char_iterator_backtrack)->u.charpos.enabled = TRUE; + BACKTRACK_AS(char_iterator_backtrack)->u.charpos.chr = charpos_char; + BACKTRACK_AS(char_iterator_backtrack)->u.charpos.othercasebit = charpos_othercasebit; + } + } + + if (charpos_enabled) + { + if (opcode == OP_UPTO) + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, max + 1); + + /* Search the first instance of charpos_char. */ + jump = JUMP(SLJIT_JUMP); + label = LABEL(); + if (opcode == OP_UPTO) + { + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_ZERO)); + } + compile_char1_matchingpath(common, type, cc, &backtrack->topbacktracks, FALSE); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + JUMPHERE(jump); + + detect_partial_match(common, &backtrack->topbacktracks); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + if (charpos_othercasebit != 0) + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, charpos_othercasebit); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, charpos_char, label); + + if (private_data_ptr == 0) + allocate_stack(common, 2); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + OP1(SLJIT_MOV, base, offset1, STR_PTR, 0); + if (opcode == OP_UPTO) + { + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + add_jump(compiler, &no_match, JUMP(SLJIT_ZERO)); + } + + /* Search the last instance of charpos_char. */ + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &no_match, FALSE); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + detect_partial_match(common, &no_match); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(0)); + if (charpos_othercasebit != 0) + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, charpos_othercasebit); + if (opcode == OP_STAR) + { + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, charpos_char, label); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + } + else + { + jump = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, charpos_char); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + JUMPHERE(jump); + } + + if (opcode == OP_UPTO) + { + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + } + else + JUMPTO(SLJIT_JUMP, label); + + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + } +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + else if (common->utf) + { + if (private_data_ptr == 0) + allocate_stack(common, 2); + + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + OP1(SLJIT_MOV, base, offset1, STR_PTR, 0); + + if (opcode == OP_UPTO) + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, max); + + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &no_match, TRUE); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + + if (opcode == OP_UPTO) + { + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + } + else + JUMPTO(SLJIT_JUMP, label); + + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + } +#endif + else + { + if (private_data_ptr == 0) + allocate_stack(common, 2); + + OP1(SLJIT_MOV, base, offset1, STR_PTR, 0); + if (opcode == OP_UPTO) + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, max); + + label = LABEL(); + detect_partial_match(common, &no_match); + compile_char1_matchingpath(common, type, cc, &no_char1_match, FALSE); + if (opcode == OP_UPTO) + { + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + } + else + JUMPTO(SLJIT_JUMP, label); + + set_jumps(no_char1_match, LABEL()); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + } + } + BACKTRACK_AS(char_iterator_backtrack)->matchingpath = LABEL(); + break; + + case OP_MINSTAR: + if (private_data_ptr == 0) + allocate_stack(common, 1); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + BACKTRACK_AS(char_iterator_backtrack)->matchingpath = LABEL(); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + break; + + case OP_MINUPTO: + SLJIT_ASSERT(fast_str_ptr == 0); + if (private_data_ptr == 0) + allocate_stack(common, 2); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + OP1(SLJIT_MOV, base, offset1, SLJIT_IMM, max + 1); + BACKTRACK_AS(char_iterator_backtrack)->matchingpath = LABEL(); + break; + + case OP_QUERY: + case OP_MINQUERY: + SLJIT_ASSERT(fast_str_ptr == 0); + if (private_data_ptr == 0) + allocate_stack(common, 1); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + if (opcode == OP_QUERY) + compile_char1_matchingpath(common, type, cc, &BACKTRACK_AS(char_iterator_backtrack)->u.backtracks, TRUE); + BACKTRACK_AS(char_iterator_backtrack)->matchingpath = LABEL(); + break; + + case OP_EXACT: + break; + + case OP_POSSTAR: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) + { + OP1(SLJIT_MOV, tmp_base, tmp_offset, STR_PTR, 0); + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &no_match, TRUE); + OP1(SLJIT_MOV, tmp_base, tmp_offset, STR_PTR, 0); + JUMPTO(SLJIT_JUMP, label); + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, tmp_base, tmp_offset); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + break; + } +#endif + label = LABEL(); + detect_partial_match(common, &no_match); + compile_char1_matchingpath(common, type, cc, &no_char1_match, FALSE); + JUMPTO(SLJIT_JUMP, label); + set_jumps(no_char1_match, LABEL()); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + set_jumps(no_match, LABEL()); + if (fast_str_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), fast_str_ptr, STR_PTR, 0); + break; + + case OP_POSUPTO: + SLJIT_ASSERT(fast_str_ptr == 0); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 32 + if (common->utf) + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1, STR_PTR, 0); + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, max); + label = LABEL(); + compile_char1_matchingpath(common, type, cc, &no_match, TRUE); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1, STR_PTR, 0); + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), POSSESSIVE1); + break; + } +#endif + OP1(SLJIT_MOV, tmp_base, tmp_offset, SLJIT_IMM, max); + label = LABEL(); + detect_partial_match(common, &no_match); + compile_char1_matchingpath(common, type, cc, &no_char1_match, FALSE); + OP2(SLJIT_SUB | SLJIT_SET_E, tmp_base, tmp_offset, tmp_base, tmp_offset, SLJIT_IMM, 1); + JUMPTO(SLJIT_NOT_ZERO, label); + OP2(SLJIT_ADD, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + set_jumps(no_char1_match, LABEL()); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + set_jumps(no_match, LABEL()); + break; + + case OP_POSQUERY: + SLJIT_ASSERT(fast_str_ptr == 0); + OP1(SLJIT_MOV, tmp_base, tmp_offset, STR_PTR, 0); + compile_char1_matchingpath(common, type, cc, &no_match, TRUE); + OP1(SLJIT_MOV, tmp_base, tmp_offset, STR_PTR, 0); + set_jumps(no_match, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, tmp_base, tmp_offset); + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + +count_match(common); +return end; +} + +static SLJIT_INLINE PCRE2_SPTR compile_fail_accept_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; + +PUSH_BACKTRACK(sizeof(backtrack_common), cc, NULL); + +if (*cc == OP_FAIL) + { + add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_JUMP)); + return cc + 1; + } + +if (*cc == OP_ASSERT_ACCEPT || common->currententry != NULL || !common->might_be_empty) + { + /* No need to check notempty conditions. */ + if (common->accept_label == NULL) + add_jump(compiler, &common->accept, JUMP(SLJIT_JUMP)); + else + JUMPTO(SLJIT_JUMP, common->accept_label); + return cc + 1; + } + +if (common->accept_label == NULL) + add_jump(compiler, &common->accept, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0))); +else + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0), common->accept_label); +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV_UI, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, options)); +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, PCRE2_NOTEMPTY); +add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_NOT_ZERO)); +OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, PCRE2_NOTEMPTY_ATSTART); +if (common->accept_label == NULL) + add_jump(compiler, &common->accept, JUMP(SLJIT_ZERO)); +else + JUMPTO(SLJIT_ZERO, common->accept_label); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); +if (common->accept_label == NULL) + add_jump(compiler, &common->accept, CMP(SLJIT_NOT_EQUAL, TMP2, 0, STR_PTR, 0)); +else + CMPTO(SLJIT_NOT_EQUAL, TMP2, 0, STR_PTR, 0, common->accept_label); +add_jump(compiler, &backtrack->topbacktracks, JUMP(SLJIT_JUMP)); +return cc + 1; +} + +static SLJIT_INLINE PCRE2_SPTR compile_close_matchingpath(compiler_common *common, PCRE2_SPTR cc) +{ +DEFINE_COMPILER; +int offset = GET2(cc, 1); +BOOL optimized_cbracket = common->optimized_cbracket[offset] != 0; + +/* Data will be discarded anyway... */ +if (common->currententry != NULL) + return cc + 1 + IMM2_SIZE; + +if (!optimized_cbracket) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR_PRIV(offset)); +offset <<= 1; +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); +if (!optimized_cbracket) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); +return cc + 1 + IMM2_SIZE; +} + +static SLJIT_INLINE PCRE2_SPTR compile_control_verb_matchingpath(compiler_common *common, PCRE2_SPTR cc, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +PCRE2_UCHAR opcode = *cc; +PCRE2_SPTR ccend = cc + 1; + +if (opcode == OP_PRUNE_ARG || opcode == OP_SKIP_ARG || opcode == OP_THEN_ARG) + ccend += 2 + cc[1]; + +PUSH_BACKTRACK(sizeof(backtrack_common), cc, NULL); + +if (opcode == OP_SKIP) + { + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + return ccend; + } + +if (opcode == OP_PRUNE_ARG || opcode == OP_THEN_ARG) + { + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, (sljit_sw)(cc + 2)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, mark_ptr), TMP2, 0); + } + +return ccend; +} + +static PCRE2_UCHAR then_trap_opcode[1] = { OP_THEN_TRAP }; + +static SLJIT_INLINE void compile_then_trap_matchingpath(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +BOOL needs_control_head; +int size; + +PUSH_BACKTRACK_NOVALUE(sizeof(then_trap_backtrack), cc); +common->then_trap = BACKTRACK_AS(then_trap_backtrack); +BACKTRACK_AS(then_trap_backtrack)->common.cc = then_trap_opcode; +BACKTRACK_AS(then_trap_backtrack)->start = (sljit_sw)(cc - common->start); +BACKTRACK_AS(then_trap_backtrack)->framesize = get_framesize(common, cc, ccend, FALSE, &needs_control_head); + +size = BACKTRACK_AS(then_trap_backtrack)->framesize; +size = 3 + (size < 0 ? 0 : size); + +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); +allocate_stack(common, size); +if (size > 3) + OP2(SLJIT_SUB, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0, SLJIT_IMM, (size - 3) * sizeof(sljit_sw)); +else + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(size - 1), SLJIT_IMM, BACKTRACK_AS(then_trap_backtrack)->start); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(size - 2), SLJIT_IMM, type_then_trap); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(size - 3), TMP2, 0); + +size = BACKTRACK_AS(then_trap_backtrack)->framesize; +if (size >= 0) + init_frame(common, cc, ccend, size - 1, 0, FALSE); +} + +static void compile_matchingpath(compiler_common *common, PCRE2_SPTR cc, PCRE2_SPTR ccend, backtrack_common *parent) +{ +DEFINE_COMPILER; +backtrack_common *backtrack; +BOOL has_then_trap = FALSE; +then_trap_backtrack *save_then_trap = NULL; + +SLJIT_ASSERT(*ccend == OP_END || (*ccend >= OP_ALT && *ccend <= OP_KETRPOS)); + +if (common->has_then && common->then_offsets[cc - common->start] != 0) + { + SLJIT_ASSERT(*ccend != OP_END && common->control_head_ptr != 0); + has_then_trap = TRUE; + save_then_trap = common->then_trap; + /* Tail item on backtrack. */ + compile_then_trap_matchingpath(common, cc, ccend, parent); + } + +while (cc < ccend) + { + switch(*cc) + { + case OP_SOD: + case OP_SOM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + case OP_EODN: + case OP_EOD: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + case OP_REVERSE: + cc = compile_simple_assertion_matchingpath(common, *cc, cc + 1, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks); + break; + + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + case OP_ANYBYTE: + case OP_NOTPROP: + case OP_PROP: + case OP_ANYNL: + case OP_NOT_HSPACE: + case OP_HSPACE: + case OP_NOT_VSPACE: + case OP_VSPACE: + case OP_EXTUNI: + case OP_NOT: + case OP_NOTI: + cc = compile_char1_matchingpath(common, *cc, cc + 1, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE); + break; + + case OP_SET_SOM: + PUSH_BACKTRACK_NOVALUE(sizeof(backtrack_common), cc); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(0), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP2, 0); + cc++; + break; + + case OP_CHAR: + case OP_CHARI: + if (common->mode == PCRE2_JIT_COMPLETE) + cc = compile_charn_matchingpath(common, cc, ccend, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks); + else + cc = compile_char1_matchingpath(common, *cc, cc + 1, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE); + break; + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_UPTOI: + case OP_MINUPTOI: + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSUPTO: + cc = compile_iterator_matchingpath(common, cc, parent); + break; + + case OP_CLASS: + case OP_NCLASS: + if (cc[1 + (32 / sizeof(PCRE2_UCHAR))] >= OP_CRSTAR && cc[1 + (32 / sizeof(PCRE2_UCHAR))] <= OP_CRPOSRANGE) + cc = compile_iterator_matchingpath(common, cc, parent); + else + cc = compile_char1_matchingpath(common, *cc, cc + 1, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE); + break; + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH == 16 || PCRE2_CODE_UNIT_WIDTH == 32 + case OP_XCLASS: + if (*(cc + GET(cc, 1)) >= OP_CRSTAR && *(cc + GET(cc, 1)) <= OP_CRPOSRANGE) + cc = compile_iterator_matchingpath(common, cc, parent); + else + cc = compile_char1_matchingpath(common, *cc, cc + 1, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE); + break; +#endif + + case OP_REF: + case OP_REFI: + if (cc[1 + IMM2_SIZE] >= OP_CRSTAR && cc[1 + IMM2_SIZE] <= OP_CRPOSRANGE) + cc = compile_ref_iterator_matchingpath(common, cc, parent); + else + { + compile_ref_matchingpath(common, cc, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE, FALSE); + cc += 1 + IMM2_SIZE; + } + break; + + case OP_DNREF: + case OP_DNREFI: + if (cc[1 + 2 * IMM2_SIZE] >= OP_CRSTAR && cc[1 + 2 * IMM2_SIZE] <= OP_CRPOSRANGE) + cc = compile_ref_iterator_matchingpath(common, cc, parent); + else + { + compile_dnref_search(common, cc, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks); + compile_ref_matchingpath(common, cc, parent->top != NULL ? &parent->top->nextbacktracks : &parent->topbacktracks, TRUE, FALSE); + cc += 1 + 2 * IMM2_SIZE; + } + break; + + case OP_RECURSE: + cc = compile_recurse_matchingpath(common, cc, parent); + break; + + case OP_CALLOUT: + case OP_CALLOUT_STR: + cc = compile_callout_matchingpath(common, cc, parent); + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + PUSH_BACKTRACK_NOVALUE(sizeof(assert_backtrack), cc); + cc = compile_assert_matchingpath(common, cc, BACKTRACK_AS(assert_backtrack), FALSE); + break; + + case OP_BRAMINZERO: + PUSH_BACKTRACK_NOVALUE(sizeof(braminzero_backtrack), cc); + cc = bracketend(cc + 1); + if (*(cc - 1 - LINK_SIZE) != OP_KETRMIN) + { + allocate_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + } + else + { + allocate_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), STR_PTR, 0); + } + BACKTRACK_AS(braminzero_backtrack)->matchingpath = LABEL(); + count_match(common); + break; + + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRA: + case OP_CBRA: + case OP_COND: + case OP_SBRA: + case OP_SCBRA: + case OP_SCOND: + cc = compile_bracket_matchingpath(common, cc, parent); + break; + + case OP_BRAZERO: + if (cc[1] > OP_ASSERTBACK_NOT) + cc = compile_bracket_matchingpath(common, cc, parent); + else + { + PUSH_BACKTRACK_NOVALUE(sizeof(assert_backtrack), cc); + cc = compile_assert_matchingpath(common, cc, BACKTRACK_AS(assert_backtrack), FALSE); + } + break; + + case OP_BRAPOS: + case OP_CBRAPOS: + case OP_SBRAPOS: + case OP_SCBRAPOS: + case OP_BRAPOSZERO: + cc = compile_bracketpos_matchingpath(common, cc, parent); + break; + + case OP_MARK: + PUSH_BACKTRACK_NOVALUE(sizeof(backtrack_common), cc); + SLJIT_ASSERT(common->mark_ptr != 0); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), common->mark_ptr); + allocate_stack(common, common->has_skip_arg ? 5 : 1); + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(common->has_skip_arg ? 4 : 0), TMP2, 0); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, (sljit_sw)(cc + 2)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, mark_ptr), TMP2, 0); + if (common->has_skip_arg) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, STACK_TOP, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(1), SLJIT_IMM, type_mark); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(2), SLJIT_IMM, (sljit_sw)(cc + 2)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(3), STR_PTR, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), TMP1, 0); + } + cc += 1 + 2 + cc[1]; + break; + + case OP_PRUNE: + case OP_PRUNE_ARG: + case OP_SKIP: + case OP_SKIP_ARG: + case OP_THEN: + case OP_THEN_ARG: + case OP_COMMIT: + cc = compile_control_verb_matchingpath(common, cc, parent); + break; + + case OP_FAIL: + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + cc = compile_fail_accept_matchingpath(common, cc, parent); + break; + + case OP_CLOSE: + cc = compile_close_matchingpath(common, cc); + break; + + case OP_SKIPZERO: + cc = bracketend(cc + 1); + break; + + default: + SLJIT_ASSERT_STOP(); + return; + } + if (cc == NULL) + return; + } + +if (has_then_trap) + { + /* Head item on backtrack. */ + PUSH_BACKTRACK_NOVALUE(sizeof(then_trap_backtrack), cc); + BACKTRACK_AS(then_trap_backtrack)->common.cc = then_trap_opcode; + BACKTRACK_AS(then_trap_backtrack)->then_trap = common->then_trap; + common->then_trap = save_then_trap; + } +SLJIT_ASSERT(cc == ccend); +} + +#undef PUSH_BACKTRACK +#undef PUSH_BACKTRACK_NOVALUE +#undef BACKTRACK_AS + +#define COMPILE_BACKTRACKINGPATH(current) \ + do \ + { \ + compile_backtrackingpath(common, (current)); \ + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) \ + return; \ + } \ + while (0) + +#define CURRENT_AS(type) ((type *)current) + +static void compile_iterator_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +PCRE2_SPTR cc = current->cc; +PCRE2_UCHAR opcode; +PCRE2_UCHAR type; +sljit_ui max = 0, exact; +struct sljit_label *label = NULL; +struct sljit_jump *jump = NULL; +jump_list *jumplist = NULL; +PCRE2_SPTR end; +int private_data_ptr = PRIVATE_DATA(cc); +int base = (private_data_ptr == 0) ? SLJIT_MEM1(STACK_TOP) : SLJIT_MEM1(SLJIT_SP); +int offset0 = (private_data_ptr == 0) ? STACK(0) : private_data_ptr; +int offset1 = (private_data_ptr == 0) ? STACK(1) : private_data_ptr + (int)sizeof(sljit_sw); + +cc = get_iterator_parameters(common, cc, &opcode, &type, &max, &exact, &end); + +switch(opcode) + { + case OP_STAR: + case OP_UPTO: + if (type == OP_ANYNL || type == OP_EXTUNI) + { + SLJIT_ASSERT(private_data_ptr == 0); + set_jumps(CURRENT_AS(char_iterator_backtrack)->u.backtracks, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(char_iterator_backtrack)->matchingpath); + } + else + { + if (CURRENT_AS(char_iterator_backtrack)->u.charpos.enabled) + { + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP1(SLJIT_MOV, TMP2, 0, base, offset1); + OP2(SLJIT_SUB, STR_PTR, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(1)); + + jump = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, TMP2, 0); + label = LABEL(); + OP1(MOV_UCHAR, TMP1, 0, SLJIT_MEM1(STR_PTR), IN_UCHARS(-1)); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + if (CURRENT_AS(char_iterator_backtrack)->u.charpos.othercasebit != 0) + OP2(SLJIT_OR, TMP1, 0, TMP1, 0, SLJIT_IMM, CURRENT_AS(char_iterator_backtrack)->u.charpos.othercasebit); + CMPTO(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, CURRENT_AS(char_iterator_backtrack)->u.charpos.chr, CURRENT_AS(char_iterator_backtrack)->matchingpath); + skip_char_back(common); + CMPTO(SLJIT_GREATER, STR_PTR, 0, TMP2, 0, label); + } + else + { + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + jump = CMP(SLJIT_LESS_EQUAL, STR_PTR, 0, base, offset1); + skip_char_back(common); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + JUMPTO(SLJIT_JUMP, CURRENT_AS(char_iterator_backtrack)->matchingpath); + } + JUMPHERE(jump); + if (private_data_ptr == 0) + free_stack(common, 2); + } + break; + + case OP_MINSTAR: + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + compile_char1_matchingpath(common, type, cc, &jumplist, TRUE); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + JUMPTO(SLJIT_JUMP, CURRENT_AS(char_iterator_backtrack)->matchingpath); + set_jumps(jumplist, LABEL()); + if (private_data_ptr == 0) + free_stack(common, 1); + break; + + case OP_MINUPTO: + OP1(SLJIT_MOV, TMP1, 0, base, offset1); + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP2(SLJIT_SUB | SLJIT_SET_E, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); + add_jump(compiler, &jumplist, JUMP(SLJIT_ZERO)); + + OP1(SLJIT_MOV, base, offset1, TMP1, 0); + compile_char1_matchingpath(common, type, cc, &jumplist, TRUE); + OP1(SLJIT_MOV, base, offset0, STR_PTR, 0); + JUMPTO(SLJIT_JUMP, CURRENT_AS(char_iterator_backtrack)->matchingpath); + + set_jumps(jumplist, LABEL()); + if (private_data_ptr == 0) + free_stack(common, 2); + break; + + case OP_QUERY: + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP1(SLJIT_MOV, base, offset0, SLJIT_IMM, 0); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(char_iterator_backtrack)->matchingpath); + jump = JUMP(SLJIT_JUMP); + set_jumps(CURRENT_AS(char_iterator_backtrack)->u.backtracks, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP1(SLJIT_MOV, base, offset0, SLJIT_IMM, 0); + JUMPTO(SLJIT_JUMP, CURRENT_AS(char_iterator_backtrack)->matchingpath); + JUMPHERE(jump); + if (private_data_ptr == 0) + free_stack(common, 1); + break; + + case OP_MINQUERY: + OP1(SLJIT_MOV, STR_PTR, 0, base, offset0); + OP1(SLJIT_MOV, base, offset0, SLJIT_IMM, 0); + jump = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + compile_char1_matchingpath(common, type, cc, &jumplist, TRUE); + JUMPTO(SLJIT_JUMP, CURRENT_AS(char_iterator_backtrack)->matchingpath); + set_jumps(jumplist, LABEL()); + JUMPHERE(jump); + if (private_data_ptr == 0) + free_stack(common, 1); + break; + + case OP_EXACT: + case OP_POSSTAR: + case OP_POSQUERY: + case OP_POSUPTO: + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + + set_jumps(current->topbacktracks, LABEL()); +} + +static SLJIT_INLINE void compile_ref_iterator_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +PCRE2_SPTR cc = current->cc; +BOOL ref = (*cc == OP_REF || *cc == OP_REFI); +PCRE2_UCHAR type; + +type = cc[ref ? 1 + IMM2_SIZE : 1 + 2 * IMM2_SIZE]; + +if ((type & 0x1) == 0) + { + /* Maximize case. */ + set_jumps(current->topbacktracks, LABEL()); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(ref_iterator_backtrack)->matchingpath); + return; + } + +OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); +CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(ref_iterator_backtrack)->matchingpath); +set_jumps(current->topbacktracks, LABEL()); +free_stack(common, ref ? 2 : 3); +} + +static SLJIT_INLINE void compile_recurse_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; + +if (CURRENT_AS(recurse_backtrack)->inlined_pattern) + compile_backtrackingpath(common, current->top); +set_jumps(current->topbacktracks, LABEL()); +if (CURRENT_AS(recurse_backtrack)->inlined_pattern) + return; + +if (common->has_set_som && common->mark_ptr != 0) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + free_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(0), TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, TMP1, 0); + } +else if (common->has_set_som || common->mark_ptr != 0) + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->has_set_som ? (int)(OVECTOR(0)) : common->mark_ptr, TMP2, 0); + } +} + +static void compile_assert_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +PCRE2_SPTR cc = current->cc; +PCRE2_UCHAR bra = OP_BRA; +struct sljit_jump *brajump = NULL; + +SLJIT_ASSERT(*cc != OP_BRAMINZERO); +if (*cc == OP_BRAZERO) + { + bra = *cc; + cc++; + } + +if (bra == OP_BRAZERO) + { + SLJIT_ASSERT(current->topbacktracks == NULL); + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + } + +if (CURRENT_AS(assert_backtrack)->framesize < 0) + { + set_jumps(current->topbacktracks, LABEL()); + + if (bra == OP_BRAZERO) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(assert_backtrack)->matchingpath); + free_stack(common, 1); + } + return; + } + +if (bra == OP_BRAZERO) + { + if (*cc == OP_ASSERT_NOT || *cc == OP_ASSERTBACK_NOT) + { + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(assert_backtrack)->matchingpath); + free_stack(common, 1); + return; + } + free_stack(common, 1); + brajump = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0); + } + +if (*cc == OP_ASSERT || *cc == OP_ASSERTBACK) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(assert_backtrack)->private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(assert_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(assert_backtrack)->framesize * sizeof(sljit_sw)); + + set_jumps(current->topbacktracks, LABEL()); + } +else + set_jumps(current->topbacktracks, LABEL()); + +if (bra == OP_BRAZERO) + { + /* We know there is enough place on the stack. */ + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), SLJIT_IMM, 0); + JUMPTO(SLJIT_JUMP, CURRENT_AS(assert_backtrack)->matchingpath); + JUMPHERE(brajump); + } +} + +static void compile_bracket_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +int opcode, stacksize, alt_count, alt_max; +int offset = 0; +int private_data_ptr = CURRENT_AS(bracket_backtrack)->private_data_ptr; +int repeat_ptr = 0, repeat_type = 0, repeat_count = 0; +PCRE2_SPTR cc = current->cc; +PCRE2_SPTR ccbegin; +PCRE2_SPTR ccprev; +PCRE2_UCHAR bra = OP_BRA; +PCRE2_UCHAR ket; +assert_backtrack *assert; +sljit_uw *next_update_addr = NULL; +BOOL has_alternatives; +BOOL needs_control_head = FALSE; +struct sljit_jump *brazero = NULL; +struct sljit_jump *alt1 = NULL; +struct sljit_jump *alt2 = NULL; +struct sljit_jump *once = NULL; +struct sljit_jump *cond = NULL; +struct sljit_label *rmin_label = NULL; +struct sljit_label *exact_label = NULL; + +if (*cc == OP_BRAZERO || *cc == OP_BRAMINZERO) + { + bra = *cc; + cc++; + } + +opcode = *cc; +ccbegin = bracketend(cc) - 1 - LINK_SIZE; +ket = *ccbegin; +if (ket == OP_KET && PRIVATE_DATA(ccbegin) != 0) + { + repeat_ptr = PRIVATE_DATA(ccbegin); + repeat_type = PRIVATE_DATA(ccbegin + 2); + repeat_count = PRIVATE_DATA(ccbegin + 3); + SLJIT_ASSERT(repeat_type != 0 && repeat_count != 0); + if (repeat_type == OP_UPTO) + ket = OP_KETRMAX; + if (repeat_type == OP_MINUPTO) + ket = OP_KETRMIN; + } +ccbegin = cc; +cc += GET(cc, 1); +has_alternatives = *cc == OP_ALT; +if (SLJIT_UNLIKELY(opcode == OP_COND) || SLJIT_UNLIKELY(opcode == OP_SCOND)) + has_alternatives = (ccbegin[1 + LINK_SIZE] >= OP_ASSERT && ccbegin[1 + LINK_SIZE] <= OP_ASSERTBACK_NOT) || CURRENT_AS(bracket_backtrack)->u.condfailed != NULL; +if (opcode == OP_CBRA || opcode == OP_SCBRA) + offset = (GET2(ccbegin, 1 + LINK_SIZE)) << 1; +if (SLJIT_UNLIKELY(opcode == OP_COND) && (*cc == OP_KETRMAX || *cc == OP_KETRMIN)) + opcode = OP_SCOND; +if (SLJIT_UNLIKELY(opcode == OP_ONCE_NC)) + opcode = OP_ONCE; + +alt_max = has_alternatives ? no_alternatives(ccbegin) : 0; + +/* Decoding the needs_control_head in framesize. */ +if (opcode == OP_ONCE) + { + needs_control_head = (CURRENT_AS(bracket_backtrack)->u.framesize & 0x1) != 0; + CURRENT_AS(bracket_backtrack)->u.framesize >>= 1; + } + +if (ket != OP_KET && repeat_type != 0) + { + /* TMP1 is used in OP_KETRMIN below. */ + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + if (repeat_type == OP_UPTO) + OP2(SLJIT_ADD, SLJIT_MEM1(SLJIT_SP), repeat_ptr, TMP1, 0, SLJIT_IMM, 1); + else + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), repeat_ptr, TMP1, 0); + } + +if (ket == OP_KETRMAX) + { + if (bra == OP_BRAZERO) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + brazero = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, 0); + } + } +else if (ket == OP_KETRMIN) + { + if (bra != OP_BRAMINZERO) + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + if (repeat_type != 0) + { + /* TMP1 was set a few lines above. */ + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, 0, CURRENT_AS(bracket_backtrack)->recursive_matchingpath); + /* Drop STR_PTR for non-greedy plus quantifier. */ + if (opcode != OP_ONCE) + free_stack(common, 1); + } + else if (opcode >= OP_SBRA || opcode == OP_ONCE) + { + /* Checking zero-length iteration. */ + if (opcode != OP_ONCE || CURRENT_AS(bracket_backtrack)->u.framesize < 0) + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr, CURRENT_AS(bracket_backtrack)->recursive_matchingpath); + else + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_MEM1(TMP1), (CURRENT_AS(bracket_backtrack)->u.framesize + 1) * sizeof(sljit_sw), CURRENT_AS(bracket_backtrack)->recursive_matchingpath); + } + /* Drop STR_PTR for non-greedy plus quantifier. */ + if (opcode != OP_ONCE) + free_stack(common, 1); + } + else + JUMPTO(SLJIT_JUMP, CURRENT_AS(bracket_backtrack)->recursive_matchingpath); + } + rmin_label = LABEL(); + if (repeat_type != 0) + OP2(SLJIT_ADD, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1); + } +else if (bra == OP_BRAZERO) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + brazero = CMP(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, 0); + } +else if (repeat_type == OP_EXACT) + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1); + exact_label = LABEL(); + } + +if (offset != 0) + { + if (common->capture_last_ptr != 0) + { + SLJIT_ASSERT(common->optimized_cbracket[offset >> 1] == 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, TMP1, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(2)); + free_stack(common, 3); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP2, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP1, 0); + } + else if (common->optimized_cbracket[offset >> 1] == 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + free_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP2, 0); + } + } + +if (SLJIT_UNLIKELY(opcode == OP_ONCE)) + { + if (CURRENT_AS(bracket_backtrack)->u.framesize >= 0) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + } + once = JUMP(SLJIT_JUMP); + } +else if (SLJIT_UNLIKELY(opcode == OP_COND) || SLJIT_UNLIKELY(opcode == OP_SCOND)) + { + if (has_alternatives) + { + /* Always exactly one alternative. */ + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + + alt_max = 2; + alt1 = CMP(SLJIT_EQUAL, TMP1, 0, SLJIT_IMM, sizeof(sljit_uw)); + } + } +else if (has_alternatives) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + + if (alt_max > 4) + { + /* Table jump if alt_max is greater than 4. */ + next_update_addr = allocate_read_only_data(common, alt_max * sizeof(sljit_uw)); + if (SLJIT_UNLIKELY(next_update_addr == NULL)) + return; + sljit_emit_ijump(compiler, SLJIT_JUMP, SLJIT_MEM1(TMP1), (sljit_sw)next_update_addr); + add_label_addr(common, next_update_addr++); + } + else + { + if (alt_max == 4) + alt2 = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, 2 * sizeof(sljit_uw)); + alt1 = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, sizeof(sljit_uw)); + } + } + +COMPILE_BACKTRACKINGPATH(current->top); +if (current->topbacktracks) + set_jumps(current->topbacktracks, LABEL()); + +if (SLJIT_UNLIKELY(opcode == OP_COND) || SLJIT_UNLIKELY(opcode == OP_SCOND)) + { + /* Conditional block always has at most one alternative. */ + if (ccbegin[1 + LINK_SIZE] >= OP_ASSERT && ccbegin[1 + LINK_SIZE] <= OP_ASSERTBACK_NOT) + { + SLJIT_ASSERT(has_alternatives); + assert = CURRENT_AS(bracket_backtrack)->u.assert; + if (assert->framesize >= 0 && (ccbegin[1 + LINK_SIZE] == OP_ASSERT || ccbegin[1 + LINK_SIZE] == OP_ASSERTBACK)) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), assert->framesize * sizeof(sljit_sw)); + } + cond = JUMP(SLJIT_JUMP); + set_jumps(CURRENT_AS(bracket_backtrack)->u.assert->condfailed, LABEL()); + } + else if (CURRENT_AS(bracket_backtrack)->u.condfailed != NULL) + { + SLJIT_ASSERT(has_alternatives); + cond = JUMP(SLJIT_JUMP); + set_jumps(CURRENT_AS(bracket_backtrack)->u.condfailed, LABEL()); + } + else + SLJIT_ASSERT(!has_alternatives); + } + +if (has_alternatives) + { + alt_count = sizeof(sljit_uw); + do + { + current->top = NULL; + current->topbacktracks = NULL; + current->nextbacktracks = NULL; + /* Conditional blocks always have an additional alternative, even if it is empty. */ + if (*cc == OP_ALT) + { + ccprev = cc + 1 + LINK_SIZE; + cc += GET(cc, 1); + if (opcode != OP_COND && opcode != OP_SCOND) + { + if (opcode != OP_ONCE) + { + if (private_data_ptr != 0) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), private_data_ptr); + else + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + } + else + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(needs_control_head ? 1 : 0)); + } + compile_matchingpath(common, ccprev, cc, current); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return; + } + + /* Instructions after the current alternative is successfully matched. */ + /* There is a similar code in compile_bracket_matchingpath. */ + if (opcode == OP_ONCE) + match_once_common(common, ket, CURRENT_AS(bracket_backtrack)->u.framesize, private_data_ptr, has_alternatives, needs_control_head); + + stacksize = 0; + if (repeat_type == OP_MINUPTO) + { + /* We need to preserve the counter. TMP2 will be used below. */ + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), repeat_ptr); + stacksize++; + } + if (ket != OP_KET || bra != OP_BRA) + stacksize++; + if (offset != 0) + { + if (common->capture_last_ptr != 0) + stacksize++; + if (common->optimized_cbracket[offset >> 1] == 0) + stacksize += 2; + } + if (opcode != OP_ONCE) + stacksize++; + + if (stacksize > 0) + allocate_stack(common, stacksize); + + stacksize = 0; + if (repeat_type == OP_MINUPTO) + { + /* TMP2 was set above. */ + OP2(SLJIT_SUB, SLJIT_MEM1(STACK_TOP), STACK(stacksize), TMP2, 0, SLJIT_IMM, 1); + stacksize++; + } + + if (ket != OP_KET || bra != OP_BRA) + { + if (ket != OP_KET) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), STR_PTR, 0); + else + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), SLJIT_IMM, 0); + stacksize++; + } + + if (offset != 0) + stacksize = match_capture_common(common, stacksize, offset, private_data_ptr); + + if (opcode != OP_ONCE) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(stacksize), SLJIT_IMM, alt_count); + + if (offset != 0 && ket == OP_KETRMAX && common->optimized_cbracket[offset >> 1] != 0) + { + /* If ket is not OP_KETRMAX, this code path is executed after the jump to alternative_matchingpath. */ + SLJIT_ASSERT(private_data_ptr == OVECTOR(offset + 0)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), STR_PTR, 0); + } + + JUMPTO(SLJIT_JUMP, CURRENT_AS(bracket_backtrack)->alternative_matchingpath); + + if (opcode != OP_ONCE) + { + if (alt_max > 4) + add_label_addr(common, next_update_addr++); + else + { + if (alt_count != 2 * sizeof(sljit_uw)) + { + JUMPHERE(alt1); + if (alt_max == 3 && alt_count == sizeof(sljit_uw)) + alt2 = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, 2 * sizeof(sljit_uw)); + } + else + { + JUMPHERE(alt2); + if (alt_max == 4) + alt1 = CMP(SLJIT_GREATER_EQUAL, TMP1, 0, SLJIT_IMM, 3 * sizeof(sljit_uw)); + } + } + alt_count += sizeof(sljit_uw); + } + + COMPILE_BACKTRACKINGPATH(current->top); + if (current->topbacktracks) + set_jumps(current->topbacktracks, LABEL()); + SLJIT_ASSERT(!current->nextbacktracks); + } + while (*cc == OP_ALT); + + if (cond != NULL) + { + SLJIT_ASSERT(opcode == OP_COND || opcode == OP_SCOND); + assert = CURRENT_AS(bracket_backtrack)->u.assert; + if ((ccbegin[1 + LINK_SIZE] == OP_ASSERT_NOT || ccbegin[1 + LINK_SIZE] == OP_ASSERTBACK_NOT) && assert->framesize >= 0) + { + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), assert->private_data_ptr, SLJIT_MEM1(STACK_TOP), assert->framesize * sizeof(sljit_sw)); + } + JUMPHERE(cond); + } + + /* Free the STR_PTR. */ + if (private_data_ptr == 0) + free_stack(common, 1); + } + +if (offset != 0) + { + /* Using both tmp register is better for instruction scheduling. */ + if (common->optimized_cbracket[offset >> 1] != 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + free_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP2, 0); + } + else + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0); + } + } +else if (opcode == OP_SBRA || opcode == OP_SCOND) + { + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + } +else if (opcode == OP_ONCE) + { + cc = ccbegin + GET(ccbegin, 1); + stacksize = needs_control_head ? 1 : 0; + + if (CURRENT_AS(bracket_backtrack)->u.framesize >= 0) + { + /* Reset head and drop saved frame. */ + stacksize += CURRENT_AS(bracket_backtrack)->u.framesize + ((ket != OP_KET || *cc == OP_ALT) ? 2 : 1); + } + else if (ket == OP_KETRMAX || (*cc == OP_ALT && ket != OP_KETRMIN)) + { + /* The STR_PTR must be released. */ + stacksize++; + } + + if (stacksize > 0) + free_stack(common, stacksize); + + JUMPHERE(once); + /* Restore previous private_data_ptr */ + if (CURRENT_AS(bracket_backtrack)->u.framesize >= 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(bracket_backtrack)->u.framesize * sizeof(sljit_sw)); + else if (ket == OP_KETRMIN) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + /* See the comment below. */ + free_stack(common, 2); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), private_data_ptr, TMP1, 0); + } + } + +if (repeat_type == OP_EXACT) + { + OP2(SLJIT_ADD, TMP1, 0, SLJIT_MEM1(SLJIT_SP), repeat_ptr, SLJIT_IMM, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), repeat_ptr, TMP1, 0); + CMPTO(SLJIT_LESS_EQUAL, TMP1, 0, SLJIT_IMM, repeat_count, exact_label); + } +else if (ket == OP_KETRMAX) + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + if (bra != OP_BRAZERO) + free_stack(common, 1); + + CMPTO(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, 0, CURRENT_AS(bracket_backtrack)->recursive_matchingpath); + if (bra == OP_BRAZERO) + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + JUMPTO(SLJIT_JUMP, CURRENT_AS(bracket_backtrack)->zero_matchingpath); + JUMPHERE(brazero); + free_stack(common, 1); + } + } +else if (ket == OP_KETRMIN) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + + /* OP_ONCE removes everything in case of a backtrack, so we don't + need to explicitly release the STR_PTR. The extra release would + affect badly the free_stack(2) above. */ + if (opcode != OP_ONCE) + free_stack(common, 1); + CMPTO(SLJIT_NOT_EQUAL, TMP1, 0, SLJIT_IMM, 0, rmin_label); + if (opcode == OP_ONCE) + free_stack(common, bra == OP_BRAMINZERO ? 2 : 1); + else if (bra == OP_BRAMINZERO) + free_stack(common, 1); + } +else if (bra == OP_BRAZERO) + { + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + JUMPTO(SLJIT_JUMP, CURRENT_AS(bracket_backtrack)->zero_matchingpath); + JUMPHERE(brazero); + } +} + +static SLJIT_INLINE void compile_bracketpos_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +int offset; +struct sljit_jump *jump; + +if (CURRENT_AS(bracketpos_backtrack)->framesize < 0) + { + if (*current->cc == OP_CBRAPOS || *current->cc == OP_SCBRAPOS) + { + offset = (GET2(current->cc, 1 + LINK_SIZE)) << 1; + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(1)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset), TMP1, 0); + if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(2)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(offset + 1), TMP2, 0); + if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, TMP1, 0); + } + set_jumps(current->topbacktracks, LABEL()); + free_stack(common, CURRENT_AS(bracketpos_backtrack)->stacksize); + return; + } + +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(bracketpos_backtrack)->private_data_ptr); +add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + +if (current->topbacktracks) + { + jump = JUMP(SLJIT_JUMP); + set_jumps(current->topbacktracks, LABEL()); + /* Drop the stack frame. */ + free_stack(common, CURRENT_AS(bracketpos_backtrack)->stacksize); + JUMPHERE(jump); + } +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), CURRENT_AS(bracketpos_backtrack)->private_data_ptr, SLJIT_MEM1(STACK_TOP), CURRENT_AS(bracketpos_backtrack)->framesize * sizeof(sljit_sw)); +} + +static SLJIT_INLINE void compile_braminzero_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +assert_backtrack backtrack; + +current->top = NULL; +current->topbacktracks = NULL; +current->nextbacktracks = NULL; +if (current->cc[1] > OP_ASSERTBACK_NOT) + { + /* Manual call of compile_bracket_matchingpath and compile_bracket_backtrackingpath. */ + compile_bracket_matchingpath(common, current->cc, current); + compile_bracket_backtrackingpath(common, current->top); + } +else + { + memset(&backtrack, 0, sizeof(backtrack)); + backtrack.common.cc = current->cc; + backtrack.matchingpath = CURRENT_AS(braminzero_backtrack)->matchingpath; + /* Manual call of compile_assert_matchingpath. */ + compile_assert_matchingpath(common, current->cc, &backtrack, FALSE); + } +SLJIT_ASSERT(!current->nextbacktracks && !current->topbacktracks); +} + +static SLJIT_INLINE void compile_control_verb_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +PCRE2_UCHAR opcode = *current->cc; +struct sljit_label *loop; +struct sljit_jump *jump; + +if (opcode == OP_THEN || opcode == OP_THEN_ARG) + { + if (common->then_trap != NULL) + { + SLJIT_ASSERT(common->control_head_ptr != 0); + + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_IMM, type_then_trap); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_IMM, common->then_trap->start); + jump = JUMP(SLJIT_JUMP); + + loop = LABEL(); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(STACK_TOP), -(int)sizeof(sljit_sw)); + JUMPHERE(jump); + CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), -(int)(2 * sizeof(sljit_sw)), TMP1, 0, loop); + CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(STACK_TOP), -(int)(3 * sizeof(sljit_sw)), TMP2, 0, loop); + add_jump(compiler, &common->then_trap->quit, JUMP(SLJIT_JUMP)); + return; + } + else if (common->positive_assert) + { + add_jump(compiler, &common->positive_assert_quit, JUMP(SLJIT_JUMP)); + return; + } + } + +if (common->local_exit) + { + if (common->quit_label == NULL) + add_jump(compiler, &common->quit, JUMP(SLJIT_JUMP)); + else + JUMPTO(SLJIT_JUMP, common->quit_label); + return; + } + +if (opcode == OP_SKIP_ARG) + { + SLJIT_ASSERT(common->control_head_ptr != 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS0, STACK_TOP, 0); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_IMM, (sljit_sw)(current->cc + 2)); + sljit_emit_ijump(compiler, SLJIT_CALL2, SLJIT_IMM, SLJIT_FUNC_OFFSET(do_search_mark)); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), LOCALS0); + + OP1(SLJIT_MOV, STR_PTR, 0, TMP1, 0); + add_jump(compiler, &common->reset_match, CMP(SLJIT_NOT_EQUAL, STR_PTR, 0, SLJIT_IMM, -1)); + return; + } + +if (opcode == OP_SKIP) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); +else + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_IMM, 0); +add_jump(compiler, &common->reset_match, JUMP(SLJIT_JUMP)); +} + +static SLJIT_INLINE void compile_then_trap_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +struct sljit_jump *jump; +int size; + +if (CURRENT_AS(then_trap_backtrack)->then_trap) + { + common->then_trap = CURRENT_AS(then_trap_backtrack)->then_trap; + return; + } + +size = CURRENT_AS(then_trap_backtrack)->framesize; +size = 3 + (size < 0 ? 0 : size); + +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(size - 3)); +free_stack(common, size); +jump = JUMP(SLJIT_JUMP); + +set_jumps(CURRENT_AS(then_trap_backtrack)->quit, LABEL()); +/* STACK_TOP is set by THEN. */ +if (CURRENT_AS(then_trap_backtrack)->framesize >= 0) + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); +free_stack(common, 3); + +JUMPHERE(jump); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, TMP1, 0); +} + +static void compile_backtrackingpath(compiler_common *common, struct backtrack_common *current) +{ +DEFINE_COMPILER; +then_trap_backtrack *save_then_trap = common->then_trap; + +while (current) + { + if (current->nextbacktracks != NULL) + set_jumps(current->nextbacktracks, LABEL()); + switch(*current->cc) + { + case OP_SET_SOM: + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(0), TMP1, 0); + break; + + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_UPTOI: + case OP_MINUPTOI: + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSUPTO: + case OP_CLASS: + case OP_NCLASS: +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: +#endif + compile_iterator_backtrackingpath(common, current); + break; + + case OP_REF: + case OP_REFI: + case OP_DNREF: + case OP_DNREFI: + compile_ref_iterator_backtrackingpath(common, current); + break; + + case OP_RECURSE: + compile_recurse_backtrackingpath(common, current); + break; + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + compile_assert_backtrackingpath(common, current); + break; + + case OP_ONCE: + case OP_ONCE_NC: + case OP_BRA: + case OP_CBRA: + case OP_COND: + case OP_SBRA: + case OP_SCBRA: + case OP_SCOND: + compile_bracket_backtrackingpath(common, current); + break; + + case OP_BRAZERO: + if (current->cc[1] > OP_ASSERTBACK_NOT) + compile_bracket_backtrackingpath(common, current); + else + compile_assert_backtrackingpath(common, current); + break; + + case OP_BRAPOS: + case OP_CBRAPOS: + case OP_SBRAPOS: + case OP_SCBRAPOS: + case OP_BRAPOSZERO: + compile_bracketpos_backtrackingpath(common, current); + break; + + case OP_BRAMINZERO: + compile_braminzero_backtrackingpath(common, current); + break; + + case OP_MARK: + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), STACK(common->has_skip_arg ? 4 : 0)); + if (common->has_skip_arg) + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + free_stack(common, common->has_skip_arg ? 5 : 1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, TMP1, 0); + if (common->has_skip_arg) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, TMP2, 0); + break; + + case OP_THEN: + case OP_THEN_ARG: + case OP_PRUNE: + case OP_PRUNE_ARG: + case OP_SKIP: + case OP_SKIP_ARG: + compile_control_verb_backtrackingpath(common, current); + break; + + case OP_COMMIT: + if (!common->local_exit) + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_NOMATCH); + if (common->quit_label == NULL) + add_jump(compiler, &common->quit, JUMP(SLJIT_JUMP)); + else + JUMPTO(SLJIT_JUMP, common->quit_label); + break; + + case OP_CALLOUT: + case OP_CALLOUT_STR: + case OP_FAIL: + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + set_jumps(current->topbacktracks, LABEL()); + break; + + case OP_THEN_TRAP: + /* A virtual opcode for then traps. */ + compile_then_trap_backtrackingpath(common, current); + break; + + default: + SLJIT_ASSERT_STOP(); + break; + } + current = current->prev; + } +common->then_trap = save_then_trap; +} + +static SLJIT_INLINE void compile_recurse(compiler_common *common) +{ +DEFINE_COMPILER; +PCRE2_SPTR cc = common->start + common->currententry->start; +PCRE2_SPTR ccbegin = cc + 1 + LINK_SIZE + (*cc == OP_BRA ? 0 : IMM2_SIZE); +PCRE2_SPTR ccend = bracketend(cc) - (1 + LINK_SIZE); +BOOL needs_control_head; +int framesize = get_framesize(common, cc, NULL, TRUE, &needs_control_head); +int private_data_size = get_private_data_copy_length(common, ccbegin, ccend, needs_control_head); +int alternativesize; +BOOL needs_frame; +backtrack_common altbacktrack; +struct sljit_jump *jump; + +/* Recurse captures then. */ +common->then_trap = NULL; + +SLJIT_ASSERT(*cc == OP_BRA || *cc == OP_CBRA || *cc == OP_CBRAPOS || *cc == OP_SCBRA || *cc == OP_SCBRAPOS); +needs_frame = framesize >= 0; +if (!needs_frame) + framesize = 0; +alternativesize = *(cc + GET(cc, 1)) == OP_ALT ? 1 : 0; + +SLJIT_ASSERT(common->currententry->entry == NULL && common->recursive_head_ptr != 0); +common->currententry->entry = LABEL(); +set_jumps(common->currententry->calls, common->currententry->entry); + +sljit_emit_fast_enter(compiler, TMP2, 0); +count_match(common); +allocate_stack(common, private_data_size + framesize + alternativesize); +OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(private_data_size + framesize + alternativesize - 1), TMP2, 0); +copy_private_data(common, ccbegin, ccend, TRUE, private_data_size + framesize + alternativesize, framesize + alternativesize, needs_control_head); +if (needs_control_head) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, STACK_TOP, 0); +if (needs_frame) + init_frame(common, cc, NULL, framesize + alternativesize - 1, alternativesize, TRUE); + +if (alternativesize > 0) + OP1(SLJIT_MOV, SLJIT_MEM1(STACK_TOP), STACK(0), STR_PTR, 0); + +memset(&altbacktrack, 0, sizeof(backtrack_common)); +common->quit_label = NULL; +common->accept_label = NULL; +common->quit = NULL; +common->accept = NULL; +altbacktrack.cc = ccbegin; +cc += GET(cc, 1); +while (1) + { + altbacktrack.top = NULL; + altbacktrack.topbacktracks = NULL; + + if (altbacktrack.cc != ccbegin) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(STACK_TOP), STACK(0)); + + compile_matchingpath(common, altbacktrack.cc, cc, &altbacktrack); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return; + + add_jump(compiler, &common->accept, JUMP(SLJIT_JUMP)); + + compile_backtrackingpath(common, altbacktrack.top); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + return; + set_jumps(altbacktrack.topbacktracks, LABEL()); + + if (*cc != OP_ALT) + break; + + altbacktrack.cc = cc + 1 + LINK_SIZE; + cc += GET(cc, 1); + } + +/* None of them matched. */ +OP1(SLJIT_MOV, TMP3, 0, SLJIT_IMM, 0); +jump = JUMP(SLJIT_JUMP); + +if (common->quit != NULL) + { + set_jumps(common->quit, LABEL()); + OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr); + if (needs_frame) + { + OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw)); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw)); + } + OP1(SLJIT_MOV, TMP3, 0, SLJIT_IMM, 0); + common->quit = NULL; + add_jump(compiler, &common->quit, JUMP(SLJIT_JUMP)); + } + +set_jumps(common->accept, LABEL()); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr); +if (needs_frame) + { + OP2(SLJIT_SUB, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw)); + add_jump(compiler, &common->revertframes, JUMP(SLJIT_FAST_CALL)); + OP2(SLJIT_ADD, STACK_TOP, 0, STACK_TOP, 0, SLJIT_IMM, (framesize + alternativesize) * sizeof(sljit_sw)); + } +OP1(SLJIT_MOV, TMP3, 0, SLJIT_IMM, 1); + +JUMPHERE(jump); +if (common->quit != NULL) + set_jumps(common->quit, LABEL()); +copy_private_data(common, ccbegin, ccend, FALSE, private_data_size + framesize + alternativesize, framesize + alternativesize, needs_control_head); +free_stack(common, private_data_size + framesize + alternativesize); +if (needs_control_head) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(STACK_TOP), 2 * sizeof(sljit_sw)); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), sizeof(sljit_sw)); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, TMP1, 0); + OP1(SLJIT_MOV, TMP1, 0, TMP3, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, TMP2, 0); + } +else + { + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(STACK_TOP), sizeof(sljit_sw)); + OP1(SLJIT_MOV, TMP1, 0, TMP3, 0); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->recursive_head_ptr, TMP2, 0); + } +sljit_emit_fast_return(compiler, SLJIT_MEM1(STACK_TOP), 0); +} + +#undef COMPILE_BACKTRACKINGPATH +#undef CURRENT_AS + +static int jit_compile(pcre2_code *code, uint32_t mode) +{ +pcre2_real_code *re = (pcre2_real_code *)code; +struct sljit_compiler *compiler; +backtrack_common rootbacktrack; +compiler_common common_data; +compiler_common *common = &common_data; +const sljit_ub *tables = re->tables; +void *allocator_data = &re->memctl; +int private_data_size; +PCRE2_SPTR ccend; +executable_functions *functions; +void *executable_func; +sljit_uw executable_size; +sljit_uw total_length; +label_addr_list *label_addr; +struct sljit_label *mainloop_label = NULL; +struct sljit_label *continue_match_label; +struct sljit_label *empty_match_found_label = NULL; +struct sljit_label *empty_match_backtrack_label = NULL; +struct sljit_label *reset_match_label; +struct sljit_label *quit_label; +struct sljit_jump *jump; +struct sljit_jump *minlength_check_failed = NULL; +struct sljit_jump *reqbyte_notfound = NULL; +struct sljit_jump *empty_match = NULL; + +SLJIT_ASSERT(tables); + +memset(&rootbacktrack, 0, sizeof(backtrack_common)); +memset(common, 0, sizeof(compiler_common)); +common->name_table = (PCRE2_SPTR)((uint8_t *)re + sizeof(pcre2_real_code)); +rootbacktrack.cc = common->name_table + re->name_count * re->name_entry_size; + +common->start = rootbacktrack.cc; +common->read_only_data_head = NULL; +common->fcc = tables + fcc_offset; +common->lcc = (sljit_sw)(tables + lcc_offset); +common->mode = mode; +common->might_be_empty = re->minlength == 0; +common->nltype = NLTYPE_FIXED; +switch(re->newline_convention) + { + case PCRE2_NEWLINE_CR: common->newline = CHAR_CR; break; + case PCRE2_NEWLINE_LF: common->newline = CHAR_NL; break; + case PCRE2_NEWLINE_CRLF: common->newline = (CHAR_CR << 8) | CHAR_NL; break; + case PCRE2_NEWLINE_ANY: common->newline = (CHAR_CR << 8) | CHAR_NL; common->nltype = NLTYPE_ANY; break; + case PCRE2_NEWLINE_ANYCRLF: common->newline = (CHAR_CR << 8) | CHAR_NL; common->nltype = NLTYPE_ANYCRLF; break; + default: return PCRE2_ERROR_INTERNAL; + } +common->nlmax = READ_CHAR_MAX; +common->nlmin = 0; +if (re->bsr_convention == PCRE2_BSR_UNICODE) + common->bsr_nltype = NLTYPE_ANY; +else if (re->bsr_convention == PCRE2_BSR_ANYCRLF) + common->bsr_nltype = NLTYPE_ANYCRLF; +else + { +#ifdef BSR_ANYCRLF + common->bsr_nltype = NLTYPE_ANYCRLF; +#else + common->bsr_nltype = NLTYPE_ANY; +#endif + } +common->bsr_nlmax = READ_CHAR_MAX; +common->bsr_nlmin = 0; +common->endonly = (re->overall_options & PCRE2_DOLLAR_ENDONLY) != 0; +common->ctypes = (sljit_sw)(tables + ctypes_offset); +common->name_count = re->name_count; +common->name_entry_size = re->name_entry_size; +common->unset_backref = (re->overall_options & PCRE2_MATCH_UNSET_BACKREF) != 0; +common->alt_circumflex = (re->overall_options & PCRE2_ALT_CIRCUMFLEX) != 0; +#ifdef SUPPORT_UNICODE +/* PCRE_UTF[16|32] have the same value as PCRE_UTF8. */ +common->utf = (re->overall_options & PCRE2_UTF) != 0; +common->use_ucp = (re->overall_options & PCRE2_UCP) != 0; +if (common->utf) + { + if (common->nltype == NLTYPE_ANY) + common->nlmax = 0x2029; + else if (common->nltype == NLTYPE_ANYCRLF) + common->nlmax = (CHAR_CR > CHAR_NL) ? CHAR_CR : CHAR_NL; + else + { + /* We only care about the first newline character. */ + common->nlmax = common->newline & 0xff; + } + + if (common->nltype == NLTYPE_FIXED) + common->nlmin = common->newline & 0xff; + else + common->nlmin = (CHAR_CR < CHAR_NL) ? CHAR_CR : CHAR_NL; + + if (common->bsr_nltype == NLTYPE_ANY) + common->bsr_nlmax = 0x2029; + else + common->bsr_nlmax = (CHAR_CR > CHAR_NL) ? CHAR_CR : CHAR_NL; + common->bsr_nlmin = (CHAR_CR < CHAR_NL) ? CHAR_CR : CHAR_NL; + } +#endif /* SUPPORT_UNICODE */ +ccend = bracketend(common->start); + +/* Calculate the local space size on the stack. */ +common->ovector_start = LIMIT_MATCH + sizeof(sljit_sw); +common->optimized_cbracket = (sljit_ub *)SLJIT_MALLOC(re->top_bracket + 1, allocator_data); +if (!common->optimized_cbracket) + return PCRE2_ERROR_NOMEMORY; +#if defined DEBUG_FORCE_UNOPTIMIZED_CBRAS && DEBUG_FORCE_UNOPTIMIZED_CBRAS == 1 +memset(common->optimized_cbracket, 0, re->top_bracket + 1); +#else +memset(common->optimized_cbracket, 1, re->top_bracket + 1); +#endif + +SLJIT_ASSERT(*common->start == OP_BRA && ccend[-(1 + LINK_SIZE)] == OP_KET); +#if defined DEBUG_FORCE_UNOPTIMIZED_CBRAS && DEBUG_FORCE_UNOPTIMIZED_CBRAS == 2 +common->capture_last_ptr = common->ovector_start; +common->ovector_start += sizeof(sljit_sw); +#endif +if (!check_opcode_types(common, common->start, ccend)) + { + SLJIT_FREE(common->optimized_cbracket, allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + +/* Checking flags and updating ovector_start. */ +if (mode == PCRE2_JIT_COMPLETE && (re->flags & PCRE2_LASTSET) != 0 && (re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0) + { + common->req_char_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } +if (mode != PCRE2_JIT_COMPLETE) + { + common->start_used_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + if (mode == PCRE2_JIT_PARTIAL_SOFT) + { + common->hit_start = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } + } +if ((re->overall_options & (PCRE2_FIRSTLINE | PCRE2_USE_OFFSET_LIMIT)) != 0) + { + common->match_end_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } +#if defined DEBUG_FORCE_CONTROL_HEAD && DEBUG_FORCE_CONTROL_HEAD +common->control_head_ptr = 1; +#endif +if (common->control_head_ptr != 0) + { + common->control_head_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } +if (common->has_set_som) + { + /* Saving the real start pointer is necessary. */ + common->start_ptr = common->ovector_start; + common->ovector_start += sizeof(sljit_sw); + } + +/* Aligning ovector to even number of sljit words. */ +if ((common->ovector_start & sizeof(sljit_sw)) != 0) + common->ovector_start += sizeof(sljit_sw); + +if (common->start_ptr == 0) + common->start_ptr = OVECTOR(0); + +/* Capturing brackets cannot be optimized if callouts are allowed. */ +if (common->capture_last_ptr != 0) + memset(common->optimized_cbracket, 0, re->top_bracket + 1); + +SLJIT_ASSERT(!(common->req_char_ptr != 0 && common->start_used_ptr != 0)); +common->cbra_ptr = OVECTOR_START + (re->top_bracket + 1) * 2 * sizeof(sljit_sw); + +total_length = ccend - common->start; +common->private_data_ptrs = (sljit_si *)SLJIT_MALLOC(total_length * (sizeof(sljit_si) + (common->has_then ? 1 : 0)), allocator_data); +if (!common->private_data_ptrs) + { + SLJIT_FREE(common->optimized_cbracket, allocator_data); + return PCRE2_ERROR_NOMEMORY; + } +memset(common->private_data_ptrs, 0, total_length * sizeof(sljit_si)); + +private_data_size = common->cbra_ptr + (re->top_bracket + 1) * sizeof(sljit_sw); +set_private_data_ptrs(common, &private_data_size, ccend); +if ((re->overall_options & PCRE2_ANCHORED) == 0 && (re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0) + { + if (!detect_fast_forward_skip(common, &private_data_size) && !common->has_skip_in_assert_back) + detect_fast_fail(common, common->start, &private_data_size, 4); + } + +SLJIT_ASSERT(common->fast_fail_start_ptr <= common->fast_fail_end_ptr); + +if (private_data_size > SLJIT_MAX_LOCAL_SIZE) + { + SLJIT_FREE(common->private_data_ptrs, allocator_data); + SLJIT_FREE(common->optimized_cbracket, allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + +if (common->has_then) + { + common->then_offsets = (sljit_ub *)(common->private_data_ptrs + total_length); + memset(common->then_offsets, 0, total_length); + set_then_offsets(common, common->start, NULL); + } + +compiler = sljit_create_compiler(allocator_data); +if (!compiler) + { + SLJIT_FREE(common->optimized_cbracket, allocator_data); + SLJIT_FREE(common->private_data_ptrs, allocator_data); + return PCRE2_ERROR_NOMEMORY; + } +common->compiler = compiler; + +/* Main pcre_jit_exec entry. */ +sljit_emit_enter(compiler, 0, 1, 5, 5, 0, 0, private_data_size); + +/* Register init. */ +reset_ovector(common, (re->top_bracket + 1) * 2); +if (common->req_char_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->req_char_ptr, SLJIT_R0, 0); + +OP1(SLJIT_MOV, ARGUMENTS, 0, SLJIT_S0, 0); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_S0, 0); +OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); +OP1(SLJIT_MOV, STR_END, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, end)); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, stack)); +OP1(SLJIT_MOV_UI, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, limit_match)); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(struct sljit_stack, base)); +OP1(SLJIT_MOV, STACK_LIMIT, 0, SLJIT_MEM1(TMP2), SLJIT_OFFSETOF(struct sljit_stack, limit)); +OP2(SLJIT_ADD, TMP1, 0, TMP1, 0, SLJIT_IMM, 1); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LIMIT_MATCH, TMP1, 0); + +if (common->fast_fail_start_ptr < common->fast_fail_end_ptr) + reset_fast_fail(common); + +if (mode == PCRE2_JIT_PARTIAL_SOFT) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, -1); +if (common->mark_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->mark_ptr, SLJIT_IMM, 0); +if (common->control_head_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->control_head_ptr, SLJIT_IMM, 0); + +/* Main part of the matching */ +if ((re->overall_options & PCRE2_ANCHORED) == 0) + { + mainloop_label = mainloop_entry(common, (re->flags & PCRE2_HASCRORLF) != 0, re->overall_options); + continue_match_label = LABEL(); + /* Forward search if possible. */ + if ((re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0) + { + if (mode == PCRE2_JIT_COMPLETE && fast_forward_first_n_chars(common)) + ; + else if ((re->flags & PCRE2_FIRSTSET) != 0) + fast_forward_first_char(common, (PCRE2_UCHAR)(re->first_codeunit), (re->flags & PCRE2_FIRSTCASELESS) != 0); + else if ((re->flags & PCRE2_STARTLINE) != 0) + fast_forward_newline(common); + else if ((re->flags & PCRE2_FIRSTMAPSET) != 0) + fast_forward_start_bits(common, re->start_bitmap); + } + } +else + continue_match_label = LABEL(); + +if (mode == PCRE2_JIT_COMPLETE && re->minlength > 0 && (re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0) + { + OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_NOMATCH); + OP2(SLJIT_ADD, TMP2, 0, STR_PTR, 0, SLJIT_IMM, IN_UCHARS(re->minlength)); + minlength_check_failed = CMP(SLJIT_GREATER, TMP2, 0, STR_END, 0); + } +if (common->req_char_ptr != 0) + reqbyte_notfound = search_requested_char(common, (PCRE2_UCHAR)(re->last_codeunit), (re->flags & PCRE2_LASTCASELESS) != 0, (re->flags & PCRE2_FIRSTSET) != 0); + +/* Store the current STR_PTR in OVECTOR(0). */ +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), OVECTOR(0), STR_PTR, 0); +/* Copy the limit of allowed recursions. */ +OP1(SLJIT_MOV, COUNT_MATCH, 0, SLJIT_MEM1(SLJIT_SP), LIMIT_MATCH); +if (common->capture_last_ptr != 0) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->capture_last_ptr, SLJIT_IMM, 0); +if (common->fast_forward_bc_ptr != NULL) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), PRIVATE_DATA(common->fast_forward_bc_ptr + 1), STR_PTR, 0); + +if (common->start_ptr != OVECTOR(0)) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_ptr, STR_PTR, 0); + +/* Copy the beginning of the string. */ +if (mode == PCRE2_JIT_PARTIAL_SOFT) + { + jump = CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, -1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); + JUMPHERE(jump); + } +else if (mode == PCRE2_JIT_PARTIAL_HARD) + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, STR_PTR, 0); + +compile_matchingpath(common, common->start, ccend, &rootbacktrack); +if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + { + sljit_free_compiler(compiler); + SLJIT_FREE(common->optimized_cbracket, allocator_data); + SLJIT_FREE(common->private_data_ptrs, allocator_data); + PRIV(jit_free_rodata)(common->read_only_data_head, compiler->allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + +if (common->might_be_empty) + { + empty_match = CMP(SLJIT_EQUAL, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), OVECTOR(0)); + empty_match_found_label = LABEL(); + } + +common->accept_label = LABEL(); +if (common->accept != NULL) + set_jumps(common->accept, common->accept_label); + +/* This means we have a match. Update the ovector. */ +copy_ovector(common, re->top_bracket + 1); +common->quit_label = common->forced_quit_label = LABEL(); +if (common->quit != NULL) + set_jumps(common->quit, common->quit_label); +if (common->forced_quit != NULL) + set_jumps(common->forced_quit, common->forced_quit_label); +if (minlength_check_failed != NULL) + SET_LABEL(minlength_check_failed, common->forced_quit_label); +sljit_emit_return(compiler, SLJIT_MOV, SLJIT_RETURN_REG, 0); + +if (mode != PCRE2_JIT_COMPLETE) + { + common->partialmatchlabel = LABEL(); + set_jumps(common->partialmatch, common->partialmatchlabel); + return_with_partial_match(common, common->quit_label); + } + +if (common->might_be_empty) + empty_match_backtrack_label = LABEL(); +compile_backtrackingpath(common, rootbacktrack.top); +if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + { + sljit_free_compiler(compiler); + SLJIT_FREE(common->optimized_cbracket, allocator_data); + SLJIT_FREE(common->private_data_ptrs, allocator_data); + PRIV(jit_free_rodata)(common->read_only_data_head, compiler->allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + +SLJIT_ASSERT(rootbacktrack.prev == NULL); +reset_match_label = LABEL(); + +if (mode == PCRE2_JIT_PARTIAL_SOFT) + { + /* Update hit_start only in the first time. */ + jump = CMP(SLJIT_NOT_EQUAL, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, 0); + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->start_ptr); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->start_used_ptr, SLJIT_IMM, -1); + OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), common->hit_start, TMP1, 0); + JUMPHERE(jump); + } + +/* Check we have remaining characters. */ +if ((re->overall_options & PCRE2_ANCHORED) == 0 && common->match_end_ptr != 0) + { + OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(SLJIT_SP), common->match_end_ptr); + } + +if (common->fast_forward_bc_ptr != NULL) + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), PRIVATE_DATA(common->fast_forward_bc_ptr + 1)); +else + OP1(SLJIT_MOV, STR_PTR, 0, SLJIT_MEM1(SLJIT_SP), common->start_ptr); + +if ((re->overall_options & PCRE2_ANCHORED) == 0) + { + if (common->ff_newline_shortcut != NULL) + { + /* There cannot be more newlines if PCRE2_FIRSTLINE is set. */ + if ((re->overall_options & PCRE2_FIRSTLINE) == 0) + { + if (common->match_end_ptr != 0) + { + OP1(SLJIT_MOV, TMP3, 0, STR_END, 0); + OP1(SLJIT_MOV, STR_END, 0, TMP1, 0); + CMPTO(SLJIT_LESS, STR_PTR, 0, TMP1, 0, common->ff_newline_shortcut); + OP1(SLJIT_MOV, STR_END, 0, TMP3, 0); + } + else + CMPTO(SLJIT_LESS, STR_PTR, 0, STR_END, 0, common->ff_newline_shortcut); + } + } + else + { + CMPTO(SLJIT_LESS, STR_PTR, 0, (common->match_end_ptr == 0) ? STR_END : TMP1, 0, mainloop_label); + } + } + +/* No more remaining characters. */ +if (reqbyte_notfound != NULL) + JUMPHERE(reqbyte_notfound); + +if (mode == PCRE2_JIT_PARTIAL_SOFT) + CMPTO(SLJIT_NOT_EQUAL, SLJIT_MEM1(SLJIT_SP), common->hit_start, SLJIT_IMM, -1, common->partialmatchlabel); + +OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_NOMATCH); +JUMPTO(SLJIT_JUMP, common->quit_label); + +flush_stubs(common); + +if (common->might_be_empty) + { + JUMPHERE(empty_match); + OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); + OP1(SLJIT_MOV_UI, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, options)); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, PCRE2_NOTEMPTY); + JUMPTO(SLJIT_NOT_ZERO, empty_match_backtrack_label); + OP2(SLJIT_AND | SLJIT_SET_E, SLJIT_UNUSED, 0, TMP2, 0, SLJIT_IMM, PCRE2_NOTEMPTY_ATSTART); + JUMPTO(SLJIT_ZERO, empty_match_found_label); + OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, str)); + CMPTO(SLJIT_NOT_EQUAL, TMP2, 0, STR_PTR, 0, empty_match_found_label); + JUMPTO(SLJIT_JUMP, empty_match_backtrack_label); + } + +common->fast_forward_bc_ptr = NULL; +common->fast_fail_start_ptr = 0; +common->fast_fail_end_ptr = 0; +common->currententry = common->entries; +common->local_exit = TRUE; +quit_label = common->quit_label; +while (common->currententry != NULL) + { + /* Might add new entries. */ + compile_recurse(common); + if (SLJIT_UNLIKELY(sljit_get_compiler_error(compiler))) + { + sljit_free_compiler(compiler); + SLJIT_FREE(common->optimized_cbracket, allocator_data); + SLJIT_FREE(common->private_data_ptrs, allocator_data); + PRIV(jit_free_rodata)(common->read_only_data_head, compiler->allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + flush_stubs(common); + common->currententry = common->currententry->next; + } +common->local_exit = FALSE; +common->quit_label = quit_label; + +/* Allocating stack, returns with PCRE_ERROR_JIT_STACKLIMIT if fails. */ +/* This is a (really) rare case. */ +set_jumps(common->stackalloc, LABEL()); +/* RETURN_ADDR is not a saved register. */ +sljit_emit_fast_enter(compiler, SLJIT_MEM1(SLJIT_SP), LOCALS0); +OP1(SLJIT_MOV, SLJIT_MEM1(SLJIT_SP), LOCALS1, TMP2, 0); +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, stack)); +OP1(SLJIT_MOV, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, top), STACK_TOP, 0); +OP2(SLJIT_ADD, TMP2, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, limit), SLJIT_IMM, STACK_GROWTH_RATE); + +sljit_emit_ijump(compiler, SLJIT_CALL2, SLJIT_IMM, SLJIT_FUNC_OFFSET(sljit_stack_resize)); +jump = CMP(SLJIT_NOT_EQUAL, SLJIT_RETURN_REG, 0, SLJIT_IMM, 0); +OP1(SLJIT_MOV, TMP1, 0, ARGUMENTS, 0); +OP1(SLJIT_MOV, TMP1, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(jit_arguments, stack)); +OP1(SLJIT_MOV, STACK_TOP, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, top)); +OP1(SLJIT_MOV, STACK_LIMIT, 0, SLJIT_MEM1(TMP1), SLJIT_OFFSETOF(struct sljit_stack, limit)); +OP1(SLJIT_MOV, TMP2, 0, SLJIT_MEM1(SLJIT_SP), LOCALS1); +sljit_emit_fast_return(compiler, SLJIT_MEM1(SLJIT_SP), LOCALS0); + +/* Allocation failed. */ +JUMPHERE(jump); +/* We break the return address cache here, but this is a really rare case. */ +OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_JIT_STACKLIMIT); +JUMPTO(SLJIT_JUMP, common->quit_label); + +/* Call limit reached. */ +set_jumps(common->calllimit, LABEL()); +OP1(SLJIT_MOV, SLJIT_RETURN_REG, 0, SLJIT_IMM, PCRE2_ERROR_MATCHLIMIT); +JUMPTO(SLJIT_JUMP, common->quit_label); + +if (common->revertframes != NULL) + { + set_jumps(common->revertframes, LABEL()); + do_revertframes(common); + } +if (common->wordboundary != NULL) + { + set_jumps(common->wordboundary, LABEL()); + check_wordboundary(common); + } +if (common->anynewline != NULL) + { + set_jumps(common->anynewline, LABEL()); + check_anynewline(common); + } +if (common->hspace != NULL) + { + set_jumps(common->hspace, LABEL()); + check_hspace(common); + } +if (common->vspace != NULL) + { + set_jumps(common->vspace, LABEL()); + check_vspace(common); + } +if (common->casefulcmp != NULL) + { + set_jumps(common->casefulcmp, LABEL()); + do_casefulcmp(common); + } +if (common->caselesscmp != NULL) + { + set_jumps(common->caselesscmp, LABEL()); + do_caselesscmp(common); + } +if (common->reset_match != NULL) + { + set_jumps(common->reset_match, LABEL()); + do_reset_match(common, (re->top_bracket + 1) * 2); + CMPTO(SLJIT_GREATER, STR_PTR, 0, TMP1, 0, continue_match_label); + OP1(SLJIT_MOV, STR_PTR, 0, TMP1, 0); + JUMPTO(SLJIT_JUMP, reset_match_label); + } +#ifdef SUPPORT_UNICODE +#if PCRE2_CODE_UNIT_WIDTH == 8 +if (common->utfreadchar != NULL) + { + set_jumps(common->utfreadchar, LABEL()); + do_utfreadchar(common); + } +if (common->utfreadchar16 != NULL) + { + set_jumps(common->utfreadchar16, LABEL()); + do_utfreadchar16(common); + } +if (common->utfreadtype8 != NULL) + { + set_jumps(common->utfreadtype8, LABEL()); + do_utfreadtype8(common); + } +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ +if (common->getucd != NULL) + { + set_jumps(common->getucd, LABEL()); + do_getucd(common); + } +#endif /* SUPPORT_UNICODE */ + +SLJIT_FREE(common->optimized_cbracket, allocator_data); +SLJIT_FREE(common->private_data_ptrs, allocator_data); + +executable_func = sljit_generate_code(compiler); +executable_size = sljit_get_generated_code_size(compiler); +label_addr = common->label_addrs; +while (label_addr != NULL) + { + *label_addr->update_addr = sljit_get_label_addr(label_addr->label); + label_addr = label_addr->next; + } +sljit_free_compiler(compiler); +if (executable_func == NULL) + { + PRIV(jit_free_rodata)(common->read_only_data_head, compiler->allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + +/* Reuse the function descriptor if possible. */ +if (re->executable_jit != NULL) + functions = (executable_functions *)re->executable_jit; +else + { + functions = SLJIT_MALLOC(sizeof(executable_functions), allocator_data); + if (functions == NULL) + { + /* This case is highly unlikely since we just recently + freed a lot of memory. Not impossible though. */ + sljit_free_code(executable_func); + PRIV(jit_free_rodata)(common->read_only_data_head, compiler->allocator_data); + return PCRE2_ERROR_NOMEMORY; + } + memset(functions, 0, sizeof(executable_functions)); + functions->top_bracket = re->top_bracket + 1; + functions->limit_match = re->limit_match; + re->executable_jit = functions; + } + +/* Turn mode into an index. */ +if (mode == PCRE2_JIT_COMPLETE) + mode = 0; +else + mode = (mode == PCRE2_JIT_PARTIAL_SOFT) ? 1 : 2; + +SLJIT_ASSERT(mode < JIT_NUMBER_OF_COMPILE_MODES); +functions->executable_funcs[mode] = executable_func; +functions->read_only_data_heads[mode] = common->read_only_data_head; +functions->executable_sizes[mode] = executable_size; +return 0; +} + +#endif + +/************************************************* +* JIT compile a Regular Expression * +*************************************************/ + +/* This function used JIT to convert a previously-compiled pattern into machine +code. + +Arguments: + code a compiled pattern + options JIT option bits + +Returns: 0: success or (*NOJIT) was used + <0: an error code +*/ + +#define PUBLIC_JIT_COMPILE_OPTIONS \ + (PCRE2_JIT_COMPLETE|PCRE2_JIT_PARTIAL_SOFT|PCRE2_JIT_PARTIAL_HARD) + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_jit_compile(pcre2_code *code, uint32_t options) +{ +#ifndef SUPPORT_JIT + +(void)code; +(void)options; +return PCRE2_ERROR_JIT_BADOPTION; + +#else /* SUPPORT_JIT */ + +pcre2_real_code *re = (pcre2_real_code *)code; +executable_functions *functions; +int result; + +if (code == NULL) + return PCRE2_ERROR_NULL; + +if ((options & ~PUBLIC_JIT_COMPILE_OPTIONS) != 0) + return PCRE2_ERROR_JIT_BADOPTION; + +if ((re->flags & PCRE2_NOJIT) != 0) return 0; + +functions = (executable_functions *)re->executable_jit; + +if ((options & PCRE2_JIT_COMPLETE) != 0 && (functions == NULL + || functions->executable_funcs[0] == NULL)) { + result = jit_compile(code, PCRE2_JIT_COMPLETE); + if (result != 0) + return result; + } + +if ((options & PCRE2_JIT_PARTIAL_SOFT) != 0 && (functions == NULL + || functions->executable_funcs[1] == NULL)) { + result = jit_compile(code, PCRE2_JIT_PARTIAL_SOFT); + if (result != 0) + return result; + } + +if ((options & PCRE2_JIT_PARTIAL_HARD) != 0 && (functions == NULL + || functions->executable_funcs[2] == NULL)) { + result = jit_compile(code, PCRE2_JIT_PARTIAL_HARD); + if (result != 0) + return result; + } + +return 0; + +#endif /* SUPPORT_JIT */ +} + +/* JIT compiler uses an all-in-one approach. This improves security, + since the code generator functions are not exported. */ + +#define INCLUDED_FROM_PCRE2_JIT_COMPILE + +#include "pcre2_jit_match.c" +#include "pcre2_jit_misc.c" + +/* End of pcre2_jit_compile.c */ diff --git a/ProcessHacker/pcre/pcre2_jit_match.c b/ProcessHacker/pcre/pcre2_jit_match.c new file mode 100644 index 0000000..d804cfe --- /dev/null +++ b/ProcessHacker/pcre/pcre2_jit_match.c @@ -0,0 +1,189 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#ifndef INCLUDED_FROM_PCRE2_JIT_COMPILE +#error This file must be included from pcre2_jit_compile.c. +#endif + +#ifdef SUPPORT_JIT + +static SLJIT_NOINLINE int jit_machine_stack_exec(jit_arguments *arguments, jit_function executable_func) +{ +sljit_ub local_space[MACHINE_STACK_SIZE]; +struct sljit_stack local_stack; + +local_stack.top = (sljit_sw)&local_space; +local_stack.base = local_stack.top; +local_stack.limit = local_stack.base + MACHINE_STACK_SIZE; +local_stack.max_limit = local_stack.limit; +arguments->stack = &local_stack; +return executable_func(arguments); +} + +#endif + + +/************************************************* +* Do a JIT pattern match * +*************************************************/ + +/* This function runs a JIT pattern match. + +Arguments: + code points to the compiled expression + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + match_data points to a match_data block + mcontext points to a match context + jit_stack points to a JIT stack + +Returns: > 0 => success; value is the number of ovector pairs filled + = 0 => success, but ovector is not big enough + -1 => failed to match (PCRE_ERROR_NOMATCH) + < -1 => some kind of unexpected problem +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_jit_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, + PCRE2_SIZE start_offset, uint32_t options, pcre2_match_data *match_data, + pcre2_match_context *mcontext) +{ +#ifndef SUPPORT_JIT + +(void)code; +(void)subject; +(void)length; +(void)start_offset; +(void)options; +(void)match_data; +(void)mcontext; +return PCRE2_ERROR_JIT_BADOPTION; + +#else /* SUPPORT_JIT */ + +pcre2_real_code *re = (pcre2_real_code *)code; +executable_functions *functions = (executable_functions *)re->executable_jit; +pcre2_jit_stack *jit_stack; +uint32_t oveccount = match_data->oveccount; +uint32_t max_oveccount; +union { + void *executable_func; + jit_function call_executable_func; +} convert_executable_func; +jit_arguments arguments; +int rc; +int index = 0; + +if ((options & PCRE2_PARTIAL_HARD) != 0) + index = 2; +else if ((options & PCRE2_PARTIAL_SOFT) != 0) + index = 1; + +if (functions->executable_funcs[index] == NULL) + return PCRE2_ERROR_JIT_BADOPTION; + +/* Sanity checks should be handled by pcre_exec. */ +arguments.str = subject + start_offset; +arguments.begin = subject; +arguments.end = subject + length; +arguments.match_data = match_data; +arguments.startchar_ptr = subject; +arguments.mark_ptr = NULL; +arguments.options = options; + +if (mcontext != NULL) + { + arguments.callout = mcontext->callout; + arguments.callout_data = mcontext->callout_data; + arguments.offset_limit = mcontext->offset_limit; + arguments.limit_match = (mcontext->match_limit < re->limit_match)? + mcontext->match_limit : re->limit_match; + if (mcontext->jit_callback != NULL) + jit_stack = mcontext->jit_callback(mcontext->jit_callback_data); + else + jit_stack = (pcre2_jit_stack *)mcontext->jit_callback_data; + } +else + { + arguments.callout = NULL; + arguments.callout_data = NULL; + arguments.offset_limit = PCRE2_UNSET; + arguments.limit_match = (MATCH_LIMIT < re->limit_match)? + MATCH_LIMIT : re->limit_match; + jit_stack = NULL; + } + +/* JIT only need two offsets for each ovector entry. Hence + the last 1/3 of the ovector will never be touched. */ + +max_oveccount = functions->top_bracket; +if (oveccount > max_oveccount) + oveccount = max_oveccount; +arguments.oveccount = oveccount << 1; + + +convert_executable_func.executable_func = functions->executable_funcs[index]; +if (jit_stack != NULL) + { + arguments.stack = (struct sljit_stack *)(jit_stack->stack); + rc = convert_executable_func.call_executable_func(&arguments); + } +else + rc = jit_machine_stack_exec(&arguments, convert_executable_func.call_executable_func); + +if (rc > (int)oveccount) + rc = 0; +match_data->code = re; +match_data->subject = subject; +match_data->rc = rc; +match_data->startchar = arguments.startchar_ptr - subject; +match_data->leftchar = 0; +match_data->rightchar = 0; +match_data->mark = arguments.mark_ptr; +match_data->matchedby = PCRE2_MATCHEDBY_JIT; + +return match_data->rc; + +#endif /* SUPPORT_JIT */ +} + +/* End of pcre2_jit_match.c */ diff --git a/ProcessHacker/pcre/pcre2_jit_misc.c b/ProcessHacker/pcre/pcre2_jit_misc.c new file mode 100644 index 0000000..efdb055 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_jit_misc.c @@ -0,0 +1,227 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +#ifndef INCLUDED_FROM_PCRE2_JIT_COMPILE +#error This file must be included from pcre2_jit_compile.c. +#endif + + + +/************************************************* +* Free JIT read-only data * +*************************************************/ + +void +PRIV(jit_free_rodata)(void *current, void *allocator_data) +{ +#ifndef SUPPORT_JIT +(void)current; +(void)allocator_data; +#else /* SUPPORT_JIT */ +void *next; + +SLJIT_UNUSED_ARG(allocator_data); + +while (current != NULL) + { + next = *(void**)current; + SLJIT_FREE(current, allocator_data); + current = next; + } + +#endif /* SUPPORT_JIT */ +} + +/************************************************* +* Free JIT compiled code * +*************************************************/ + +void +PRIV(jit_free)(void *executable_jit, pcre2_memctl *memctl) +{ +#ifndef SUPPORT_JIT +(void)executable_jit; +(void)memctl; +#else /* SUPPORT_JIT */ + +executable_functions *functions = (executable_functions *)executable_jit; +void *allocator_data = memctl; +int i; + +for (i = 0; i < JIT_NUMBER_OF_COMPILE_MODES; i++) + { + if (functions->executable_funcs[i] != NULL) + sljit_free_code(functions->executable_funcs[i]); + PRIV(jit_free_rodata)(functions->read_only_data_heads[i], allocator_data); + } + +SLJIT_FREE(functions, allocator_data); + +#endif /* SUPPORT_JIT */ +} + + +/************************************************* +* Free unused JIT memory * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_jit_free_unused_memory(pcre2_general_context *gcontext) +{ +#ifndef SUPPORT_JIT +(void)gcontext; /* Suppress warning */ +#else /* SUPPORT_JIT */ +SLJIT_UNUSED_ARG(gcontext); +sljit_free_unused_memory_exec(); +#endif /* SUPPORT_JIT */ +} + + + +/************************************************* +* Allocate a JIT stack * +*************************************************/ + +PCRE2_EXP_DEFN pcre2_jit_stack * PCRE2_CALL_CONVENTION +pcre2_jit_stack_create(size_t startsize, size_t maxsize, + pcre2_general_context *gcontext) +{ +#ifndef SUPPORT_JIT + +(void)gcontext; +(void)startsize; +(void)maxsize; +return NULL; + +#else /* SUPPORT_JIT */ + +pcre2_jit_stack *jit_stack; + +if (startsize < 1 || maxsize < 1) + return NULL; +if (startsize > maxsize) + startsize = maxsize; +startsize = (startsize + STACK_GROWTH_RATE - 1) & ~(STACK_GROWTH_RATE - 1); +maxsize = (maxsize + STACK_GROWTH_RATE - 1) & ~(STACK_GROWTH_RATE - 1); + +jit_stack = PRIV(memctl_malloc)(sizeof(pcre2_real_jit_stack), (pcre2_memctl *)gcontext); +if (jit_stack == NULL) return NULL; +jit_stack->stack = sljit_allocate_stack(startsize, maxsize, &jit_stack->memctl); +return jit_stack; + +#endif +} + + +/************************************************* +* Assign a JIT stack to a pattern * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_jit_stack_assign(pcre2_match_context *mcontext, pcre2_jit_callback callback, + void *callback_data) +{ +#ifndef SUPPORT_JIT +(void)mcontext; +(void)callback; +(void)callback_data; +#else /* SUPPORT_JIT */ + +if (mcontext == NULL) return; +mcontext->jit_callback = callback; +mcontext->jit_callback_data = callback_data; + +#endif /* SUPPORT_JIT */ +} + + +/************************************************* +* Free a JIT stack * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_jit_stack_free(pcre2_jit_stack *jit_stack) +{ +#ifndef SUPPORT_JIT +(void)jit_stack; +#else /* SUPPORT_JIT */ +if (jit_stack != NULL) + { + sljit_free_stack((struct sljit_stack *)(jit_stack->stack), &jit_stack->memctl); + jit_stack->memctl.free(jit_stack, jit_stack->memctl.memory_data); + } +#endif /* SUPPORT_JIT */ +} + + +/************************************************* +* Get target CPU type * +*************************************************/ + +const char* +PRIV(jit_get_target)(void) +{ +#ifndef SUPPORT_JIT +return "JIT is not supported"; +#else /* SUPPORT_JIT */ +return sljit_get_platform_name(); +#endif /* SUPPORT_JIT */ +} + + +/************************************************* +* Get size of JIT code * +*************************************************/ + +size_t +PRIV(jit_get_size)(void *executable_jit) +{ +#ifndef SUPPORT_JIT +(void)executable_jit; +return 0; +#else /* SUPPORT_JIT */ +sljit_uw *executable_sizes = ((executable_functions *)executable_jit)->executable_sizes; +SLJIT_COMPILE_ASSERT(JIT_NUMBER_OF_COMPILE_MODES == 3, number_of_compile_modes_changed); +return executable_sizes[0] + executable_sizes[1] + executable_sizes[2]; +#endif +} + +/* End of pcre2_jit_misc.c */ diff --git a/ProcessHacker/pcre/pcre2_maketables.c b/ProcessHacker/pcre/pcre2_maketables.c new file mode 100644 index 0000000..846a542 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_maketables.c @@ -0,0 +1,158 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains the external function pcre2_maketables(), which builds +character tables for PCRE2 in the current locale. The file is compiled on its +own as part of the PCRE2 library. However, it is also included in the +compilation of dftables.c, in which case the macro DFTABLES is defined. */ + +#ifndef DFTABLES +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "pcre2_internal.h" +#endif + + + +/************************************************* +* Create PCRE2 character tables * +*************************************************/ + +/* This function builds a set of character tables for use by PCRE2 and returns +a pointer to them. They are build using the ctype functions, and consequently +their contents will depend upon the current locale setting. When compiled as +part of the library, the store is obtained via a general context malloc, if +supplied, but when DFTABLES is defined (when compiling the dftables auxiliary +program) malloc() is used, and the function has a different name so as not to +clash with the prototype in pcre2.h. + +Arguments: none when DFTABLES is defined + else a PCRE2 general context or NULL +Returns: pointer to the contiguous block of data +*/ + +#ifdef DFTABLES /* Included in freestanding dftables.c program */ +static const uint8_t *maketables(void) +{ +uint8_t *yield = (uint8_t *)malloc(tables_length); + +#else /* Not DFTABLES, compiling the library */ +PCRE2_EXP_DEFN const uint8_t * PCRE2_CALL_CONVENTION +pcre2_maketables(pcre2_general_context *gcontext) +{ +uint8_t *yield = (uint8_t *)((gcontext != NULL)? + gcontext->memctl.malloc(tables_length, gcontext->memctl.memory_data) : + malloc(tables_length)); +#endif /* DFTABLES */ + +int i; +uint8_t *p; + +if (yield == NULL) return NULL; +p = yield; + +/* First comes the lower casing table */ + +for (i = 0; i < 256; i++) *p++ = tolower(i); + +/* Next the case-flipping table */ + +for (i = 0; i < 256; i++) *p++ = islower(i)? toupper(i) : tolower(i); + +/* Then the character class tables. Don't try to be clever and save effort on +exclusive ones - in some locales things may be different. + +Note that the table for "space" includes everything "isspace" gives, including +VT in the default locale. This makes it work for the POSIX class [:space:]. +From release 8.34 is is also correct for Perl space, because Perl added VT at +release 5.18. + +Note also that it is possible for a character to be alnum or alpha without +being lower or upper, such as "male and female ordinals" (\xAA and \xBA) in the +fr_FR locale (at least under Debian Linux's locales as of 12/2005). So we must +test for alnum specially. */ + +memset(p, 0, cbit_length); +for (i = 0; i < 256; i++) + { + if (isdigit(i)) p[cbit_digit + i/8] |= 1 << (i&7); + if (isupper(i)) p[cbit_upper + i/8] |= 1 << (i&7); + if (islower(i)) p[cbit_lower + i/8] |= 1 << (i&7); + if (isalnum(i)) p[cbit_word + i/8] |= 1 << (i&7); + if (i == '_') p[cbit_word + i/8] |= 1 << (i&7); + if (isspace(i)) p[cbit_space + i/8] |= 1 << (i&7); + if (isxdigit(i))p[cbit_xdigit + i/8] |= 1 << (i&7); + if (isgraph(i)) p[cbit_graph + i/8] |= 1 << (i&7); + if (isprint(i)) p[cbit_print + i/8] |= 1 << (i&7); + if (ispunct(i)) p[cbit_punct + i/8] |= 1 << (i&7); + if (iscntrl(i)) p[cbit_cntrl + i/8] |= 1 << (i&7); + } +p += cbit_length; + +/* Finally, the character type table. In this, we used to exclude VT from the +white space chars, because Perl didn't recognize it as such for \s and for +comments within regexes. However, Perl changed at release 5.18, so PCRE changed +at release 8.34. */ + +for (i = 0; i < 256; i++) + { + int x = 0; + if (isspace(i)) x += ctype_space; + if (isalpha(i)) x += ctype_letter; + if (isdigit(i)) x += ctype_digit; + if (isxdigit(i)) x += ctype_xdigit; + if (isalnum(i) || i == '_') x += ctype_word; + + /* Note: strchr includes the terminating zero in the characters it considers. + In this instance, that is ok because we want binary zero to be flagged as a + meta-character, which in this sense is any character that terminates a run + of data characters. */ + + if (strchr("\\*+?{^.$|()[", i) != 0) x += ctype_meta; + *p++ = x; + } + +return yield; +} + +/* End of pcre2_maketables.c */ diff --git a/ProcessHacker/pcre/pcre2_match.c b/ProcessHacker/pcre/pcre2_match.c new file mode 100644 index 0000000..61d34c6 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_match.c @@ -0,0 +1,7249 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4267) +#pragma warning(disable : 4146) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define NLBLOCK mb /* Block containing newline information */ +#define PSSTART start_subject /* Field containing processed string start */ +#define PSEND end_subject /* Field containing processed string end */ + +#include "pcre2_internal.h" + +/* Masks for identifying the public options that are permitted at match time. +*/ + +#define PUBLIC_MATCH_OPTIONS \ + (PCRE2_ANCHORED|PCRE2_NOTBOL|PCRE2_NOTEOL|PCRE2_NOTEMPTY| \ + PCRE2_NOTEMPTY_ATSTART|PCRE2_NO_UTF_CHECK|PCRE2_PARTIAL_HARD| \ + PCRE2_PARTIAL_SOFT) + +#define PUBLIC_JIT_MATCH_OPTIONS \ + (PCRE2_NO_UTF_CHECK|PCRE2_NOTBOL|PCRE2_NOTEOL|PCRE2_NOTEMPTY|\ + PCRE2_NOTEMPTY_ATSTART|PCRE2_PARTIAL_SOFT|PCRE2_PARTIAL_HARD) + +/* The mb->capture_last field uses the lower 16 bits for the last captured +substring (which can never be greater than 65535) and a bit in the top half +to mean "capture vector overflowed". This odd way of doing things was +implemented when it was realized that preserving and restoring the overflow bit +whenever the last capture number was saved/restored made for a neater +interface, and doing it this way saved on (a) another variable, which would +have increased the stack frame size (a big NO-NO in PCRE) and (b) another +separate set of save/restore instructions. The following defines are used in +implementing this. */ + +#define CAPLMASK 0x0000ffff /* The bits used for last_capture */ +#define OVFLMASK 0xffff0000 /* The bits used for the overflow flag */ +#define OVFLBIT 0x00010000 /* The bit that is set for overflow */ + +/* Bits for setting in mb->match_function_type to indicate two special types +of call to match(). We do it this way to save on using another stack variable, +as stack usage is to be discouraged. */ + +#define MATCH_CONDASSERT 1 /* Called to check a condition assertion */ +#define MATCH_CBEGROUP 2 /* Could-be-empty unlimited repeat group */ + +/* Non-error returns from the match() function. Error returns are externally +defined PCRE2_ERROR_xxx codes, which are all negative. */ + +#define MATCH_MATCH 1 +#define MATCH_NOMATCH 0 + +/* Special internal returns from the match() function. Make them sufficiently +negative to avoid the external error codes. */ + +#define MATCH_ACCEPT (-999) +#define MATCH_KETRPOS (-998) +#define MATCH_ONCE (-997) +/* The next 5 must be kept together and in sequence so that a test that checks +for any one of them can use a range. */ +#define MATCH_COMMIT (-996) +#define MATCH_PRUNE (-995) +#define MATCH_SKIP (-994) +#define MATCH_SKIP_ARG (-993) +#define MATCH_THEN (-992) +#define MATCH_BACKTRACK_MAX MATCH_THEN +#define MATCH_BACKTRACK_MIN MATCH_COMMIT + +/* Min and max values for the common repeats; for the maxima, 0 => infinity */ + +static const char rep_min[] = { 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, }; +static const char rep_max[] = { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, }; + +/* Maximum number of ovector elements that can be saved on the system stack +when processing OP_RECURSE in non-HEAP_MATCH_RECURSE mode. If the ovector is +bigger, malloc() is used. This value should be a multiple of 3, because the +ovector length is always a multiple of 3. */ + +#define OP_RECURSE_STACK_SAVE_MAX 45 + + + +/************************************************* +* Match a back-reference * +*************************************************/ + +/* This function is called only when it is known that the offset lies within +the offsets that have so far been used in the match. Note that in caseless +UTF-8 mode, the number of subject bytes matched may be different to the number +of reference bytes. (In theory this could also happen in UTF-16 mode, but it +seems unlikely.) + +Arguments: + offset index into the offset vector + offset_top top of the used offset vector + eptr pointer into the subject + mb points to match block + caseless TRUE if caseless + lengthptr pointer for returning the length matched + +Returns: = 0 sucessful match; number of code units matched is set + < 0 no match + > 0 partial match +*/ + +static int +match_ref(PCRE2_SIZE offset, PCRE2_SIZE offset_top, register PCRE2_SPTR eptr, + match_block *mb, BOOL caseless, PCRE2_SIZE *lengthptr) +{ +#if defined SUPPORT_UNICODE +BOOL utf = (mb->poptions & PCRE2_UTF) != 0; +#endif + +register PCRE2_SPTR p; +PCRE2_SIZE length; +PCRE2_SPTR eptr_start = eptr; + +/* Deal with an unset group. The default is no match, but there is an option to +match an empty string. */ + +if (offset >= offset_top || mb->ovector[offset] == PCRE2_UNSET) + { + if ((mb->poptions & PCRE2_MATCH_UNSET_BACKREF) != 0) + { + *lengthptr = 0; + return 0; /* Match */ + } + else return -1; /* No match */ + } + +/* Separate the caseless and UTF cases for speed. */ + +p = mb->start_subject + mb->ovector[offset]; +length = mb->ovector[offset+1] - mb->ovector[offset]; + +if (caseless) + { +#if defined SUPPORT_UNICODE + if (utf) + { + /* Match characters up to the end of the reference. NOTE: the number of + code units matched may differ, because in UTF-8 there are some characters + whose upper and lower case versions code have different numbers of bytes. + For example, U+023A (2 bytes in UTF-8) is the upper case version of U+2C65 + (3 bytes in UTF-8); a sequence of 3 of the former uses 6 bytes, as does a + sequence of two of the latter. It is important, therefore, to check the + length along the reference, not along the subject (earlier code did this + wrong). */ + + PCRE2_SPTR endptr = p + length; + while (p < endptr) + { + uint32_t c, d; + const ucd_record *ur; + if (eptr >= mb->end_subject) return 1; /* Partial match */ + GETCHARINC(c, eptr); + GETCHARINC(d, p); + ur = GET_UCD(d); + if (c != d && c != (uint32_t)((int)d + ur->other_case)) + { + const uint32_t *pp = PRIV(ucd_caseless_sets) + ur->caseset; + for (;;) + { + if (c < *pp) return -1; /* No match */ + if (c == *pp++) break; + } + } + } + } + else +#endif + + /* Not in UTF mode */ + + { + for (; length > 0; length--) + { + uint32_t cc, cp; + if (eptr >= mb->end_subject) return 1; /* Partial match */ + cc = UCHAR21TEST(eptr); + cp = UCHAR21TEST(p); + if (TABLE_GET(cp, mb->lcc, cp) != TABLE_GET(cc, mb->lcc, cc)) + return -1; /* No match */ + p++; + eptr++; + } + } + } + +/* In the caseful case, we can just compare the code units, whether or not we +are in UTF mode. */ + +else + { + for (; length > 0; length--) + { + if (eptr >= mb->end_subject) return 1; /* Partial match */ + if (UCHAR21INCTEST(p) != UCHAR21INCTEST(eptr)) return -1; /*No match */ + } + } + +*lengthptr = eptr - eptr_start; +return 0; /* Match */ +} + + + +/*************************************************************************** +**************************************************************************** + RECURSION IN THE match() FUNCTION + +The match() function is highly recursive, though not every recursive call +increases the recursion depth. Nevertheless, some regular expressions can cause +it to recurse to a great depth. I was writing for Unix, so I just let it call +itself recursively. This uses the stack for saving everything that has to be +saved for a recursive call. On Unix, the stack can be large, and this works +fine. + +It turns out that on some non-Unix-like systems there are problems with +programs that use a lot of stack. (This despite the fact that every last chip +has oodles of memory these days, and techniques for extending the stack have +been known for decades.) So.... + +There is a fudge, triggered by defining HEAP_MATCH_RECURSE, which avoids +recursive calls by keeping local variables that need to be preserved in blocks +of memory on the heap instead instead of on the stack. Macros are used to +achieve this so that the actual code doesn't look very different to what it +always used to. + +The original heap-recursive code used longjmp(). However, it seems that this +can be very slow on some operating systems. Following a suggestion from Stan +Switzer, the use of longjmp() has been abolished, at the cost of having to +provide a unique number for each call to RMATCH. There is no way of generating +a sequence of numbers at compile time in C. I have given them names, to make +them stand out more clearly. + +Crude tests on x86 Linux show a small speedup of around 5-8%. However, on +FreeBSD, avoiding longjmp() more than halves the time taken to run the standard +tests. Furthermore, not using longjmp() means that local dynamic variables +don't have indeterminate values; this has meant that the frame size can be +reduced because the result can be "passed back" by straight setting of the +variable instead of being passed in the frame. +**************************************************************************** +***************************************************************************/ + +/* Numbers for RMATCH calls. When this list is changed, the code at HEAP_RETURN +below must be updated in sync. */ + +enum { RM1=1, RM2, RM3, RM4, RM5, RM6, RM7, RM8, RM9, RM10, + RM11, RM12, RM13, RM14, RM15, RM16, RM17, RM18, RM19, RM20, + RM21, RM22, RM23, RM24, RM25, RM26, RM27, RM28, RM29, RM30, + RM31, RM32, RM33, RM34, RM35, RM36, RM37, RM38, RM39, RM40, + RM41, RM42, RM43, RM44, RM45, RM46, RM47, RM48, RM49, RM50, + RM51, RM52, RM53, RM54, RM55, RM56, RM57, RM58, RM59, RM60, + RM61, RM62, RM63, RM64, RM65, RM66, RM67, RM68 }; + +/* These versions of the macros use the stack, as normal. Note that the "rw" +argument of RMATCH isn't actually used in this definition. */ + +#ifndef HEAP_MATCH_RECURSE +#define REGISTER register +#define RMATCH(ra,rb,rc,rd,re,rw) \ + rrc = match(ra,rb,mstart,rc,rd,re,rdepth+1) +#define RRETURN(ra) return ra +#else + +/* These versions of the macros manage a private stack on the heap. Note that +the "rd" argument of RMATCH isn't actually used in this definition. It's the mb +argument of match(), which never changes. */ + +#define REGISTER + +#define RMATCH(ra,rb,rc,rd,re,rw)\ + {\ + heapframe *newframe = frame->Xnextframe;\ + if (newframe == NULL)\ + {\ + newframe = (heapframe *)(mb->stack_memctl.malloc)\ + (sizeof(heapframe), mb->stack_memctl.memory_data);\ + if (newframe == NULL) RRETURN(PCRE2_ERROR_NOMEMORY);\ + newframe->Xnextframe = NULL;\ + frame->Xnextframe = newframe;\ + }\ + frame->Xwhere = rw;\ + newframe->Xeptr = ra;\ + newframe->Xecode = rb;\ + newframe->Xmstart = mstart;\ + newframe->Xoffset_top = rc;\ + newframe->Xeptrb = re;\ + newframe->Xrdepth = frame->Xrdepth + 1;\ + newframe->Xprevframe = frame;\ + frame = newframe;\ + goto HEAP_RECURSE;\ + L_##rw:;\ + } + +#define RRETURN(ra)\ + {\ + heapframe *oldframe = frame;\ + frame = oldframe->Xprevframe;\ + if (frame != NULL)\ + {\ + rrc = ra;\ + goto HEAP_RETURN;\ + }\ + return ra;\ + } + + +/* Structure for remembering the local variables in a private frame. Arrange it +so as to minimize the number of holes. */ + +typedef struct heapframe { + struct heapframe *Xprevframe; + struct heapframe *Xnextframe; + +#ifdef SUPPORT_UNICODE + PCRE2_SPTR Xcharptr; +#endif + PCRE2_SPTR Xeptr; + PCRE2_SPTR Xecode; + PCRE2_SPTR Xmstart; + PCRE2_SPTR Xcallpat; + PCRE2_SPTR Xdata; + PCRE2_SPTR Xnext_ecode; + PCRE2_SPTR Xpp; + PCRE2_SPTR Xprev; + PCRE2_SPTR Xsaved_eptr; + + eptrblock *Xeptrb; + + PCRE2_SIZE Xlength; + PCRE2_SIZE Xoffset; + PCRE2_SIZE Xoffset_top; + PCRE2_SIZE Xsave_offset1, Xsave_offset2, Xsave_offset3; + + uint32_t Xfc; + uint32_t Xnumber; + uint32_t Xrdepth; + uint32_t Xop; + uint32_t Xsave_capture_last; + +#ifdef SUPPORT_UNICODE + uint32_t Xprop_value; + int Xprop_type; + int Xprop_fail_result; + int Xoclength; +#endif + + int Xcodelink; + int Xctype; + int Xfi; + int Xmax; + int Xmin; + int Xwhere; /* Where to jump back to */ + + BOOL Xcondition; + BOOL Xcur_is_word; + BOOL Xprev_is_word; + + eptrblock Xnewptrb; + recursion_info Xnew_recursive; + +#ifdef SUPPORT_UNICODE + PCRE2_UCHAR Xocchars[6]; +#endif +} heapframe; + +#endif + + +/*************************************************************************** +***************************************************************************/ + + +/* When HEAP_MATCH_RECURSE is not defined, the match() function implements +backtrack points by calling itself recursively in all but one case. The one +special case is when processing OP_RECURSE, which specifies recursion in the +pattern. The entire ovector must be saved and restored while processing +OP_RECURSE. If the ovector is small enough, instead of calling match() +directly, op_recurse_ovecsave() is called. This function uses the system stack +to save the ovector while calling match() to process the pattern recursion. */ + +#ifndef HEAP_MATCH_RECURSE + +/* We need a prototype for match() because it is mutually recursive with +op_recurse_ovecsave(). */ + +static int +match(REGISTER PCRE2_SPTR eptr, REGISTER PCRE2_SPTR ecode, PCRE2_SPTR mstart, + PCRE2_SIZE offset_top, match_block *mb, eptrblock *eptrb, uint32_t rdepth); + + +/************************************************* +* Process OP_RECURSE, stacking ovector * +*************************************************/ + +/* When this function is called, mb->recursive has already been updated to +point to a new recursion data block, and all its fields other than ovec_save +have been set. + +This function exists so that the local vector variable ovecsave is no longer +defined in the match() function, as it was in PCRE1. It is used only when there +is recursion in the pattern, so it wastes a lot of stack to have it defined for +every call of match(). We now use this function as an indirect way of calling +match() only in the case when ovecsave is needed. (David Wheeler used to say +"All problems in computer science can be solved by another level of +indirection.") + +HOWEVER: when this file is compiled by gcc in an optimizing mode, because this +function is called only once, and only from within match(), gcc will "inline" +it - that is, move it inside match() - and this completely negates its reason +for existence. Therefore, we mark it as non-inline when gcc is in use. + +Arguments: + eptr pointer to current character in subject + callpat the recursion point in the pattern + mstart pointer to the current match start position (can be modified + by encountering \K) + offset_top current top pointer (highest ovector offset used + 1) + mb pointer to "static" info block for the match + eptrb pointer to chain of blocks containing eptr at start of + brackets - for testing for empty matches + rdepth the recursion depth + +Returns: a match() return code +*/ + +static int +#ifdef __GNUC__ +__attribute__ ((noinline)) +#endif +op_recurse_ovecsave(REGISTER PCRE2_SPTR eptr, PCRE2_SPTR callpat, + PCRE2_SPTR mstart, PCRE2_SIZE offset_top, match_block *mb, eptrblock *eptrb, + uint32_t rdepth) +{ +register int rrc; +BOOL cbegroup = *callpat >= OP_SBRA; +recursion_info *new_recursive = mb->recursive; +PCRE2_SIZE ovecsave[OP_RECURSE_STACK_SAVE_MAX]; + +/* Save the ovector */ + +new_recursive->ovec_save = ovecsave; +memcpy(ovecsave, mb->ovector, mb->offset_end * sizeof(PCRE2_SIZE)); + +/* Do the recursion. After processing each alternative, restore the ovector +data and the last captured value. */ + +do + { + if (cbegroup) mb->match_function_type |= MATCH_CBEGROUP; + rrc = match(eptr, callpat + PRIV(OP_lengths)[*callpat], mstart, offset_top, + mb, eptrb, rdepth + 1); + memcpy(mb->ovector, new_recursive->ovec_save, + mb->offset_end * sizeof(PCRE2_SIZE)); + mb->capture_last = new_recursive->saved_capture_last; + if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) return rrc; + + /* PCRE does not allow THEN, SKIP, PRUNE or COMMIT to escape beyond a + recursion; they cause a NOMATCH for the entire recursion. These codes + are defined in a range that can be tested for. */ + + if (rrc >= MATCH_BACKTRACK_MIN && rrc <= MATCH_BACKTRACK_MAX) + return MATCH_NOMATCH; + + /* Any return code other than NOMATCH is an error. Otherwise, advance to the + next alternative or to the end of the recursing subpattern. If there were + nested recursions, mb->recursive might be changed, so reset it before + looping. */ + + if (rrc != MATCH_NOMATCH) return rrc; + mb->recursive = new_recursive; + callpat += GET(callpat, 1); + } +while (*callpat == OP_ALT); /* Loop for the alternatives */ + +/* None of the alternatives matched. */ + +return MATCH_NOMATCH; +} +#endif /* HEAP_MATCH_RECURSE */ + + + +/************************************************* +* Match from current position * +*************************************************/ + +/* This function is called recursively in many circumstances. Whenever it +returns a negative (error) response, the outer incarnation must also return the +same response. */ + +/* These macros pack up tests that are used for partial matching, and which +appear several times in the code. We set the "hit end" flag if the pointer is +at the end of the subject and also past the earliest inspected character (i.e. +something has been matched, even if not part of the actual matched string). For +hard partial matching, we then return immediately. The second one is used when +we already know we are past the end of the subject. */ + +#define CHECK_PARTIAL()\ + if (mb->partial != 0 && eptr >= mb->end_subject && \ + eptr > mb->start_used_ptr) \ + { \ + mb->hitend = TRUE; \ + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); \ + } + +#define SCHECK_PARTIAL()\ + if (mb->partial != 0 && eptr > mb->start_used_ptr) \ + { \ + mb->hitend = TRUE; \ + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); \ + } + + +/* Performance note: It might be tempting to extract commonly used fields from +the mb structure (e.g. utf, end_subject) into individual variables to improve +performance. Tests using gcc on a SPARC disproved this; in the first case, it +made performance worse. + +Arguments: + eptr pointer to current character in subject + ecode pointer to current position in compiled code + mstart pointer to the current match start position (can be modified + by encountering \K) + offset_top current top pointer (highest ovector offset used + 1) + mb pointer to "static" info block for the match + eptrb pointer to chain of blocks containing eptr at start of + brackets - for testing for empty matches + rdepth the recursion depth + +Returns: MATCH_MATCH if matched ) these values are >= 0 + MATCH_NOMATCH if failed to match ) + a negative MATCH_xxx value for PRUNE, SKIP, etc + a negative PCRE2_ERROR_xxx value if aborted by an error condition + (e.g. stopped by repeated call or recursion limit) +*/ + +static int +match(REGISTER PCRE2_SPTR eptr, REGISTER PCRE2_SPTR ecode, PCRE2_SPTR mstart, + PCRE2_SIZE offset_top, match_block *mb, eptrblock *eptrb, uint32_t rdepth) +{ +/* These variables do not need to be preserved over recursion in this function, +so they can be ordinary variables in all cases. Mark some of them with +"register" because they are used a lot in loops. */ + +register int rrc; /* Returns from recursive calls */ +register int i; /* Used for loops not involving calls to RMATCH() */ +register uint32_t c; /* Character values not kept over RMATCH() calls */ +register BOOL utf; /* Local copy of UTF flag for speed */ + +BOOL minimize, possessive; /* Quantifier options */ +BOOL caseless; +int condcode; + +/* When recursion is not being used, all "local" variables that have to be +preserved over calls to RMATCH() are part of a "frame". We set up the top-level +frame on the stack here; subsequent instantiations are obtained from the heap +whenever RMATCH() does a "recursion". See the macro definitions above. Putting +the top-level on the stack rather than malloc-ing them all gives a performance +boost in many cases where there is not much "recursion". */ + +#ifdef HEAP_MATCH_RECURSE +heapframe *frame = (heapframe *)mb->match_frames_base; + +/* Copy in the original argument variables */ + +frame->Xeptr = eptr; +frame->Xecode = ecode; +frame->Xmstart = mstart; +frame->Xoffset_top = offset_top; +frame->Xeptrb = eptrb; +frame->Xrdepth = rdepth; + +/* This is where control jumps back to to effect "recursion" */ + +HEAP_RECURSE: + +/* Macros make the argument variables come from the current frame */ + +#define eptr frame->Xeptr +#define ecode frame->Xecode +#define mstart frame->Xmstart +#define offset_top frame->Xoffset_top +#define eptrb frame->Xeptrb +#define rdepth frame->Xrdepth + +/* Ditto for the local variables */ + +#ifdef SUPPORT_UNICODE +#define charptr frame->Xcharptr +#define prop_value frame->Xprop_value +#define prop_type frame->Xprop_type +#define prop_fail_result frame->Xprop_fail_result +#define oclength frame->Xoclength +#define occhars frame->Xocchars +#endif + + +#define callpat frame->Xcallpat +#define codelink frame->Xcodelink +#define data frame->Xdata +#define next_ecode frame->Xnext_ecode +#define pp frame->Xpp +#define prev frame->Xprev +#define saved_eptr frame->Xsaved_eptr + +#define new_recursive frame->Xnew_recursive + +#define ctype frame->Xctype +#define fc frame->Xfc +#define fi frame->Xfi +#define length frame->Xlength +#define max frame->Xmax +#define min frame->Xmin +#define number frame->Xnumber +#define offset frame->Xoffset +#define op frame->Xop +#define save_capture_last frame->Xsave_capture_last +#define save_offset1 frame->Xsave_offset1 +#define save_offset2 frame->Xsave_offset2 +#define save_offset3 frame->Xsave_offset3 + +#define condition frame->Xcondition +#define cur_is_word frame->Xcur_is_word +#define prev_is_word frame->Xprev_is_word + +#define newptrb frame->Xnewptrb + +/* When normal stack-based recursion is being used for match(), local variables +are allocated on the stack and get preserved during recursion in the usual way. +In this environment, fi and i, and fc and c, can be the same variables. */ + +#else /* HEAP_MATCH_RECURSE not defined */ +#define fi i +#define fc c + +/* Many of the following variables are used only in small blocks of the code. +My normal style of coding would have declared them within each of those blocks. +However, in order to accommodate the version of this code that uses an external +"stack" implemented on the heap, it is easier to declare them all here, so the +declarations can be cut out in a block. The only declarations within blocks +below are for variables that do not have to be preserved over a recursive call +to RMATCH(). */ + +#ifdef SUPPORT_UNICODE +PCRE2_SPTR charptr; +#endif +PCRE2_SPTR callpat; +PCRE2_SPTR data; +PCRE2_SPTR next_ecode; +PCRE2_SPTR pp; +PCRE2_SPTR prev; +PCRE2_SPTR saved_eptr; + +PCRE2_SIZE length; +PCRE2_SIZE offset; +PCRE2_SIZE save_offset1, save_offset2, save_offset3; + +uint32_t number; +uint32_t op; +uint32_t save_capture_last; + +#ifdef SUPPORT_UNICODE +uint32_t prop_value; +int prop_type; +int prop_fail_result; +int oclength; +PCRE2_UCHAR occhars[6]; +#endif + +int codelink; +int ctype; +int max; +int min; + +BOOL condition; +BOOL cur_is_word; +BOOL prev_is_word; + +eptrblock newptrb; +recursion_info new_recursive; +#endif /* HEAP_MATCH_RECURSE not defined */ + +/* To save space on the stack and in the heap frame, I have doubled up on some +of the local variables that are used only in localised parts of the code, but +still need to be preserved over recursive calls of match(). These macros define +the alternative names that are used. */ + +#define allow_zero cur_is_word +#define cbegroup condition +#define code_offset codelink +#define condassert condition +#define foc number +#define matched_once prev_is_word +#define save_mark data + +/* These statements are here to stop the compiler complaining about unitialized +variables. */ + +#ifdef SUPPORT_UNICODE +prop_value = 0; +prop_fail_result = 0; +#endif + + +/* This label is used for tail recursion, which is used in a few cases even +when HEAP_MATCH_RECURSE is not defined, in order to reduce the amount of stack +that is used. Thanks to Ian Taylor for noticing this possibility and sending +the original patch. */ + +TAIL_RECURSE: + +/* OK, now we can get on with the real code of the function. Recursive calls +are specified by the macro RMATCH and RRETURN is used to return. When +HEAP_MATCH_RECURSE is *not* defined, these just turn into a recursive call to +match() and a "return", respectively. However, RMATCH isn't like a function +call because it's quite a complicated macro. It has to be used in one +particular way. This shouldn't, however, impact performance when true recursion +is being used. */ + +#ifdef SUPPORT_UNICODE +utf = (mb->poptions & PCRE2_UTF) != 0; +#else +utf = FALSE; +#endif + +/* First check that we haven't called match() too many times, or that we +haven't exceeded the recursive call limit. */ + +if (mb->match_call_count++ >= mb->match_limit) RRETURN(PCRE2_ERROR_MATCHLIMIT); +if (rdepth >= mb->match_limit_recursion) RRETURN(PCRE2_ERROR_RECURSIONLIMIT); + +/* At the start of a group with an unlimited repeat that may match an empty +string, the variable mb->match_function_type contains the MATCH_CBEGROUP bit. +It is done this way to save having to use another function argument, which +would take up space on the stack. See also MATCH_CONDASSERT below. + +When MATCH_CBEGROUP is set, add the current subject pointer to the chain of +such remembered pointers, to be checked when we hit the closing ket, in order +to break infinite loops that match no characters. When match() is called in +other circumstances, don't add to the chain. The MATCH_CBEGROUP feature must +NOT be used with tail recursion, because the memory block that is used is on +the stack, so a new one may be required for each match(). */ + +if ((mb->match_function_type & MATCH_CBEGROUP) != 0) + { + newptrb.epb_saved_eptr = eptr; + newptrb.epb_prev = eptrb; + eptrb = &newptrb; + mb->match_function_type &= ~MATCH_CBEGROUP; + } + +/* Now, at last, we can start processing the opcodes. */ + +for (;;) + { + minimize = possessive = FALSE; + op = *ecode; + + switch(op) + { + case OP_MARK: + mb->nomatch_mark = ecode + 2; + mb->mark = NULL; /* In case previously set by assertion */ + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode] + ecode[1], offset_top, mb, + eptrb, RM55); + if ((rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) && + mb->mark == NULL) mb->mark = ecode + 2; + + /* A return of MATCH_SKIP_ARG means that matching failed at SKIP with an + argument, and we must check whether that argument matches this MARK's + argument. It is passed back in mb->start_match_ptr (an overloading of that + variable). If it does match, we reset that variable to the current subject + position and return MATCH_SKIP. Otherwise, pass back the return code + unaltered. */ + + else if (rrc == MATCH_SKIP_ARG && + PRIV(strcmp)(ecode + 2, mb->start_match_ptr) == 0) + { + mb->start_match_ptr = eptr; + RRETURN(MATCH_SKIP); + } + RRETURN(rrc); + + case OP_FAIL: + RRETURN(MATCH_NOMATCH); + + case OP_COMMIT: + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM52); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_COMMIT); + + case OP_PRUNE: + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM51); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_PRUNE); + + case OP_PRUNE_ARG: + mb->nomatch_mark = ecode + 2; + mb->mark = NULL; /* In case previously set by assertion */ + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode] + ecode[1], offset_top, mb, + eptrb, RM56); + if ((rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) && + mb->mark == NULL) mb->mark = ecode + 2; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + RRETURN(MATCH_PRUNE); + + case OP_SKIP: + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM53); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->start_match_ptr = eptr; /* Pass back current position */ + RRETURN(MATCH_SKIP); + + /* Note that, for Perl compatibility, SKIP with an argument does NOT set + nomatch_mark. When a pattern match ends with a SKIP_ARG for which there was + not a matching mark, we have to re-run the match, ignoring the SKIP_ARG + that failed and any that precede it (either they also failed, or were not + triggered). To do this, we maintain a count of executed SKIP_ARGs. If a + SKIP_ARG gets to top level, the match is re-run with mb->ignore_skip_arg + set to the count of the one that failed. */ + + case OP_SKIP_ARG: + mb->skip_arg_count++; + if (mb->skip_arg_count <= mb->ignore_skip_arg) + { + ecode += PRIV(OP_lengths)[*ecode] + ecode[1]; + break; + } + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode] + ecode[1], offset_top, mb, + eptrb, RM57); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + + /* Pass back the current skip name by overloading mb->start_match_ptr and + returning the special MATCH_SKIP_ARG return code. This will either be + caught by a matching MARK, or get to the top, where it causes a rematch + with mb->ignore_skip_arg set to the value of mb->skip_arg_count. */ + + mb->start_match_ptr = ecode + 2; + RRETURN(MATCH_SKIP_ARG); + + /* For THEN (and THEN_ARG) we pass back the address of the opcode, so that + the branch in which it occurs can be determined. Overload the start of + match pointer to do this. */ + + case OP_THEN: + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM54); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->start_match_ptr = ecode; + RRETURN(MATCH_THEN); + + case OP_THEN_ARG: + mb->nomatch_mark = ecode + 2; + mb->mark = NULL; /* In case previously set by assertion */ + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode] + ecode[1], offset_top, + mb, eptrb, RM58); + if ((rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) && + mb->mark == NULL) mb->mark = ecode + 2; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->start_match_ptr = ecode; + RRETURN(MATCH_THEN); + + /* Handle an atomic group that does not contain any capturing parentheses. + This can be handled like an assertion. Prior to 8.13, all atomic groups + were handled this way. In 8.13, the code was changed as below for ONCE, so + that backups pass through the group and thereby reset captured values. + However, this uses a lot more stack, so in 8.20, atomic groups that do not + contain any captures generate OP_ONCE_NC, which can be handled in the old, + less stack intensive way. + + Check the alternative branches in turn - the matching won't pass the KET + for this kind of subpattern. If any one branch matches, we carry on as at + the end of a normal bracket, leaving the subject pointer, but resetting + the start-of-match value in case it was changed by \K. */ + + case OP_ONCE_NC: + prev = ecode; + saved_eptr = eptr; + save_mark = mb->mark; + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, eptrb, RM64); + if (rrc == MATCH_MATCH) /* Note: _not_ MATCH_ACCEPT */ + { + mstart = mb->start_match_ptr; + break; + } + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += GET(ecode,1); + mb->mark = save_mark; + } + while (*ecode == OP_ALT); + + /* If hit the end of the group (which could be repeated), fail */ + + if (*ecode != OP_ONCE_NC && *ecode != OP_ALT) RRETURN(MATCH_NOMATCH); + + /* Continue as from after the group, updating the offsets high water + mark, since extracts may have been taken. */ + + do ecode += GET(ecode, 1); while (*ecode == OP_ALT); + + offset_top = mb->end_offset_top; + eptr = mb->end_match_ptr; + + /* For a non-repeating ket, just continue at this level. This also + happens for a repeating ket if no characters were matched in the group. + This is the forcible breaking of infinite loops as implemented in Perl + 5.005. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + ecode += 1+LINK_SIZE; + break; + } + + /* The repeating kets try the rest of the pattern or restart from the + preceding bracket, in the appropriate order. The second "call" of match() + uses tail recursion, to avoid using another stack frame. */ + + if (*ecode == OP_KETRMIN) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, eptrb, RM65); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode = prev; + goto TAIL_RECURSE; + } + else /* OP_KETRMAX */ + { + RMATCH(eptr, prev, offset_top, mb, eptrb, RM66); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += 1 + LINK_SIZE; + goto TAIL_RECURSE; + } + /* Control never gets here */ + + /* Handle a capturing bracket, other than those that are possessive with an + unlimited repeat. If there is space in the offset vector, save the current + subject position in the working slot at the top of the vector. We mustn't + change the current values of the data slot, because they may be set from a + previous iteration of this group, and be referred to by a reference inside + the group. A failure to match might occur after the group has succeeded, + if something later on doesn't match. For this reason, we need to restore + the working value and also the values of the final offsets, in case they + were set by a previous iteration of the same bracket. + + If there isn't enough space in the offset vector, treat this as if it were + a non-capturing bracket. Don't worry about setting the flag for the error + case here; that is handled in the code for KET. */ + + case OP_CBRA: + case OP_SCBRA: + number = GET2(ecode, 1+LINK_SIZE); + offset = number << 1; + + if (offset < mb->offset_max) + { + save_offset1 = mb->ovector[offset]; + save_offset2 = mb->ovector[offset+1]; + save_offset3 = mb->ovector[mb->offset_end - number]; + save_capture_last = mb->capture_last; + save_mark = mb->mark; + + mb->ovector[mb->offset_end - number] = eptr - mb->start_subject; + + for (;;) + { + if (op >= OP_SBRA) mb->match_function_type |= MATCH_CBEGROUP; + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM1); + if (rrc == MATCH_ONCE) break; /* Backing up through an atomic group */ + + /* If we backed up to a THEN, check whether it is within the current + branch by comparing the address of the THEN that is passed back with + the end of the branch. If it is within the current branch, and the + branch is one of two or more alternatives (it either starts or ends + with OP_ALT), we have reached the limit of THEN's action, so convert + the return code to NOMATCH, which will cause normal backtracking to + happen from now on. Otherwise, THEN is passed back to an outer + alternative. This implements Perl's treatment of parenthesized groups, + where a group not containing | does not affect the current alternative, + that is, (X) is NOT the same as (X|(*F)). */ + + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + /* Anything other than NOMATCH is passed back. */ + + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->capture_last = save_capture_last; + ecode += GET(ecode, 1); + mb->mark = save_mark; + if (*ecode != OP_ALT) break; + } + + mb->ovector[offset] = save_offset1; + mb->ovector[offset+1] = save_offset2; + mb->ovector[mb->offset_end - number] = save_offset3; + + /* At this point, rrc will be one of MATCH_ONCE or MATCH_NOMATCH. */ + + RRETURN(rrc); + } + + /* FALL THROUGH ... Insufficient room for saving captured contents. Treat + as a non-capturing bracket. */ + + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + /* VVVVVVVVVVVVVVVVVVVVVVVVV */ + + /* Non-capturing or atomic group, except for possessive with unlimited + repeat and ONCE group with no captures. Loop for all the alternatives. + + When we get to the final alternative within the brackets, we used to return + the result of a recursive call to match() whatever happened so it was + possible to reduce stack usage by turning this into a tail recursion, + except in the case of a possibly empty group. However, now that there is + the possiblity of (*THEN) occurring in the final alternative, this + optimization is no longer always possible. + + We can optimize if we know there are no (*THEN)s in the pattern; at present + this is the best that can be done. + + MATCH_ONCE is returned when the end of an atomic group is successfully + reached, but subsequent matching fails. It passes back up the tree (causing + captured values to be reset) until the original atomic group level is + reached. This is tested by comparing mb->once_target with the start of the + group. At this point, the return is converted into MATCH_NOMATCH so that + previous backup points can be taken. */ + + case OP_ONCE: + case OP_BRA: + case OP_SBRA: + + for (;;) + { + if (op >= OP_SBRA || op == OP_ONCE) + mb->match_function_type |= MATCH_CBEGROUP; + + /* If this is not a possibly empty group, and there are no (*THEN)s in + the pattern, and this is the final alternative, optimize as described + above. */ + + else if (!mb->hasthen && ecode[GET(ecode, 1)] != OP_ALT) + { + ecode += PRIV(OP_lengths)[*ecode]; + goto TAIL_RECURSE; + } + + /* In all other cases, we have to make another call to match(). */ + + save_mark = mb->mark; + save_capture_last = mb->capture_last; + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, eptrb, + RM2); + + /* See comment in the code for capturing groups above about handling + THEN. */ + + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + if (rrc != MATCH_NOMATCH) + { + if (rrc == MATCH_ONCE) + { + PCRE2_SPTR scode = ecode; + if (*scode != OP_ONCE) /* If not at start, find it */ + { + while (*scode == OP_ALT) scode += GET(scode, 1); + scode -= GET(scode, 1); + } + if (mb->once_target == scode) rrc = MATCH_NOMATCH; + } + RRETURN(rrc); + } + ecode += GET(ecode, 1); + mb->mark = save_mark; + if (*ecode != OP_ALT) break; + mb->capture_last = save_capture_last; + } + + RRETURN(MATCH_NOMATCH); + + /* Handle possessive capturing brackets with an unlimited repeat. We come + here from BRAZERO with allow_zero set TRUE. The ovector values are + handled similarly to the normal case above. However, the matching is + different. The end of these brackets will always be OP_KETRPOS, which + returns MATCH_KETRPOS without going further in the pattern. By this means + we can handle the group by iteration rather than recursion, thereby + reducing the amount of stack needed. If the ovector is too small for + capturing, treat as non-capturing. */ + + case OP_CBRAPOS: + case OP_SCBRAPOS: + allow_zero = FALSE; + + POSSESSIVE_CAPTURE: + number = GET2(ecode, 1+LINK_SIZE); + offset = number << 1; + if (offset >= mb->offset_max) goto POSSESSIVE_NON_CAPTURE; + + matched_once = FALSE; + code_offset = (int)(ecode - mb->start_code); + + save_offset1 = mb->ovector[offset]; + save_offset2 = mb->ovector[offset+1]; + save_offset3 = mb->ovector[mb->offset_end - number]; + save_capture_last = mb->capture_last; + + /* Each time round the loop, save the current subject position for use + when the group matches. For MATCH_MATCH, the group has matched, so we + restart it with a new subject starting position, remembering that we had + at least one match. For MATCH_NOMATCH, carry on with the alternatives, as + usual. If we haven't matched any alternatives in any iteration, check to + see if a previous iteration matched. If so, the group has matched; + continue from afterwards. Otherwise it has failed; restore the previous + capture values before returning NOMATCH. */ + + for (;;) + { + mb->ovector[mb->offset_end - number] = eptr - mb->start_subject; + if (op >= OP_SBRA) mb->match_function_type |= MATCH_CBEGROUP; + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM63); + if (rrc == MATCH_KETRPOS) + { + offset_top = mb->end_offset_top; + ecode = mb->start_code + code_offset; + save_capture_last = mb->capture_last; + matched_once = TRUE; + mstart = mb->start_match_ptr; /* In case \K changed it */ + if (eptr == mb->end_match_ptr) /* Matched an empty string */ + { + do ecode += GET(ecode, 1); while (*ecode == OP_ALT); + break; + } + eptr = mb->end_match_ptr; + continue; + } + + /* See comment in the code for capturing groups above about handling + THEN. */ + + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->capture_last = save_capture_last; + ecode += GET(ecode, 1); + if (*ecode != OP_ALT) break; + } + + if (!matched_once) + { + mb->ovector[offset] = save_offset1; + mb->ovector[offset+1] = save_offset2; + mb->ovector[mb->offset_end - number] = save_offset3; + } + + if (allow_zero || matched_once) + { + ecode += 1 + LINK_SIZE; + break; + } + RRETURN(MATCH_NOMATCH); + + /* Non-capturing possessive bracket with unlimited repeat. We come here + from BRAZERO with allow_zero = TRUE. The code is similar to the above, + without the capturing complication. It is written out separately for speed + and cleanliness. */ + + case OP_BRAPOS: + case OP_SBRAPOS: + allow_zero = FALSE; + + POSSESSIVE_NON_CAPTURE: + matched_once = FALSE; + code_offset = (int)(ecode - mb->start_code); + save_capture_last = mb->capture_last; + + for (;;) + { + if (op >= OP_SBRA) mb->match_function_type |= MATCH_CBEGROUP; + RMATCH(eptr, ecode + PRIV(OP_lengths)[*ecode], offset_top, mb, + eptrb, RM48); + if (rrc == MATCH_KETRPOS) + { + offset_top = mb->end_offset_top; + ecode = mb->start_code + code_offset; + matched_once = TRUE; + mstart = mb->start_match_ptr; /* In case \K reset it */ + if (eptr == mb->end_match_ptr) /* Matched an empty string */ + { + do ecode += GET(ecode, 1); while (*ecode == OP_ALT); + break; + } + eptr = mb->end_match_ptr; + continue; + } + + /* See comment in the code for capturing groups above about handling + THEN. */ + + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += GET(ecode, 1); + if (*ecode != OP_ALT) break; + mb->capture_last = save_capture_last; + } + + if (matched_once || allow_zero) + { + ecode += 1 + LINK_SIZE; + break; + } + RRETURN(MATCH_NOMATCH); + + /* Control never reaches here. */ + + /* Conditional group: compilation checked that there are no more than two + branches. If the condition is false, skipping the first branch takes us + past the end of the item if there is only one branch, but that's exactly + what we want. */ + + case OP_COND: + case OP_SCOND: + + /* The variable codelink will be added to ecode when the condition is + false, to get to the second branch. Setting it to the offset to the ALT + or KET, then incrementing ecode achieves this effect. We now have ecode + pointing to the condition or callout. */ + + codelink = GET(ecode, 1); /* Offset to the second branch */ + ecode += 1 + LINK_SIZE; /* From this opcode */ + + /* Because of the way auto-callout works during compile, a callout item is + inserted between OP_COND and an assertion condition. */ + + if (*ecode == OP_CALLOUT || *ecode == OP_CALLOUT_STR) + { + unsigned int callout_length = (*ecode == OP_CALLOUT) + ? PRIV(OP_lengths)[OP_CALLOUT] : GET(ecode, 1 + 2*LINK_SIZE); + + if (mb->callout != NULL) + { + pcre2_callout_block cb; + cb.version = 1; + cb.capture_top = offset_top/2; + cb.capture_last = mb->capture_last & CAPLMASK; + cb.offset_vector = mb->ovector; + cb.mark = mb->nomatch_mark; + cb.subject = mb->start_subject; + cb.subject_length = (PCRE2_SIZE)(mb->end_subject - mb->start_subject); + cb.start_match = (PCRE2_SIZE)(mstart - mb->start_subject); + cb.current_position = (PCRE2_SIZE)(eptr - mb->start_subject); + cb.pattern_position = GET(ecode, 1); + cb.next_item_length = GET(ecode, 1 + LINK_SIZE); + + if (*ecode == OP_CALLOUT) + { + cb.callout_number = ecode[1 + 2*LINK_SIZE]; + cb.callout_string_offset = 0; + cb.callout_string = NULL; + cb.callout_string_length = 0; + } + else + { + cb.callout_number = 0; + cb.callout_string_offset = GET(ecode, 1 + 3*LINK_SIZE); + cb.callout_string = ecode + (1 + 4*LINK_SIZE) + 1; + cb.callout_string_length = + callout_length - (1 + 4*LINK_SIZE) - 2; + } + + if ((rrc = mb->callout(&cb, mb->callout_data)) > 0) + RRETURN(MATCH_NOMATCH); + if (rrc < 0) RRETURN(rrc); + } + + /* Advance ecode past the callout, so it now points to the condition. We + must adjust codelink so that the value of ecode+codelink is unchanged. */ + + ecode += callout_length; + codelink -= callout_length; + } + + /* Test the various possible conditions */ + + condition = FALSE; + switch(condcode = *ecode) + { + case OP_RREF: /* Numbered group recursion test */ + if (mb->recursive != NULL) /* Not recursing => FALSE */ + { + uint32_t recno = GET2(ecode, 1); /* Recursion group number*/ + condition = (recno == RREF_ANY || recno == mb->recursive->group_num); + } + break; + + case OP_DNRREF: /* Duplicate named group recursion test */ + if (mb->recursive != NULL) + { + int count = GET2(ecode, 1 + IMM2_SIZE); + PCRE2_SPTR slot = mb->name_table + GET2(ecode, 1) * mb->name_entry_size; + while (count-- > 0) + { + uint32_t recno = GET2(slot, 0); + condition = recno == mb->recursive->group_num; + if (condition) break; + slot += mb->name_entry_size; + } + } + break; + + case OP_CREF: /* Numbered group used test */ + offset = GET2(ecode, 1) << 1; /* Doubled ref number */ + condition = offset < offset_top && + mb->ovector[offset] != PCRE2_UNSET; + break; + + case OP_DNCREF: /* Duplicate named group used test */ + { + int count = GET2(ecode, 1 + IMM2_SIZE); + PCRE2_SPTR slot = mb->name_table + GET2(ecode, 1) * mb->name_entry_size; + while (count-- > 0) + { + offset = GET2(slot, 0) << 1; + condition = offset < offset_top && + mb->ovector[offset] != PCRE2_UNSET; + if (condition) break; + slot += mb->name_entry_size; + } + } + break; + + case OP_FALSE: + case OP_FAIL: /* The assertion (?!) becomes OP_FAIL */ + break; + + case OP_TRUE: + condition = TRUE; + break; + + /* The condition is an assertion. Call match() to evaluate it - setting + the MATCH_CONDASSERT bit in mb->match_function_type causes it to stop at + the end of an assertion. */ + + default: + mb->match_function_type |= MATCH_CONDASSERT; + RMATCH(eptr, ecode, offset_top, mb, NULL, RM3); + if (rrc == MATCH_MATCH) + { + if (mb->end_offset_top > offset_top) + offset_top = mb->end_offset_top; /* Captures may have happened */ + condition = TRUE; + + /* Advance ecode past the assertion to the start of the first branch, + but adjust it so that the general choosing code below works. If the + assertion has a quantifier that allows zero repeats we must skip over + the BRAZERO. This is a lunatic thing to do, but somebody did! */ + + if (*ecode == OP_BRAZERO) ecode++; + ecode += GET(ecode, 1); + while (*ecode == OP_ALT) ecode += GET(ecode, 1); + ecode += 1 + LINK_SIZE - PRIV(OP_lengths)[condcode]; + } + + /* PCRE doesn't allow the effect of (*THEN) to escape beyond an + assertion; it is therefore treated as NOMATCH. Any other return is an + error. */ + + else if (rrc != MATCH_NOMATCH && rrc != MATCH_THEN) + { + RRETURN(rrc); /* Need braces because of following else */ + } + break; + } + + /* Choose branch according to the condition */ + + ecode += condition? PRIV(OP_lengths)[condcode] : codelink; + + /* We are now at the branch that is to be obeyed. As there is only one, we + can use tail recursion to avoid using another stack frame, except when + there is unlimited repeat of a possibly empty group. In the latter case, a + recursive call to match() is always required, unless the second alternative + doesn't exist, in which case we can just plough on. Note that, for + compatibility with Perl, the | in a conditional group is NOT treated as + creating two alternatives. If a THEN is encountered in the branch, it + propagates out to the enclosing alternative (unless nested in a deeper set + of alternatives, of course). */ + + if (condition || ecode[-(1+LINK_SIZE)] == OP_ALT) + { + if (op != OP_SCOND) + { + goto TAIL_RECURSE; + } + + mb->match_function_type |= MATCH_CBEGROUP; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM49); + RRETURN(rrc); + } + + /* Condition false & no alternative; continue after the group. */ + + else + { + } + break; + + + /* Before OP_ACCEPT there may be any number of OP_CLOSE opcodes, + to close any currently open capturing brackets. */ + + case OP_CLOSE: + number = GET2(ecode, 1); /* Must be less than 65536 */ + offset = number << 1; + mb->capture_last = (mb->capture_last & OVFLMASK) | number; + if (offset >= mb->offset_max) mb->capture_last |= OVFLBIT; else + { + mb->ovector[offset] = + mb->ovector[mb->offset_end - number]; + mb->ovector[offset+1] = eptr - mb->start_subject; + + /* If this group is at or above the current highwater mark, ensure that + any groups between the current high water mark and this group are marked + unset and then update the high water mark. */ + + if (offset >= offset_top) + { + register PCRE2_SIZE *iptr = mb->ovector + offset_top; + register PCRE2_SIZE *iend = mb->ovector + offset; + while (iptr < iend) *iptr++ = PCRE2_UNSET; + offset_top = offset + 2; + } + } + ecode += 1 + IMM2_SIZE; + break; + + + /* End of the pattern, either real or forced. In an assertion ACCEPT, + update the last used pointer. */ + + case OP_ASSERT_ACCEPT: + if (eptr > mb->last_used_ptr) mb->last_used_ptr = eptr; + + case OP_ACCEPT: + case OP_END: + + /* If we have matched an empty string, fail if not in an assertion and not + in a recursion if either PCRE2_NOTEMPTY is set, or if PCRE2_NOTEMPTY_ATSTART + is set and we have matched at the start of the subject. In both cases, + backtracking will then try other alternatives, if any. */ + + if (eptr == mstart && op != OP_ASSERT_ACCEPT && + mb->recursive == NULL && + ((mb->moptions & PCRE2_NOTEMPTY) != 0 || + ((mb->moptions & PCRE2_NOTEMPTY_ATSTART) != 0 && + mstart == mb->start_subject + mb->start_offset))) + RRETURN(MATCH_NOMATCH); + + /* Otherwise, we have a match. */ + + mb->end_match_ptr = eptr; /* Record where we ended */ + mb->end_offset_top = offset_top; /* and how many extracts were taken */ + mb->start_match_ptr = mstart; /* and the start (\K can modify) */ + + /* For some reason, the macros don't work properly if an expression is + given as the argument to RRETURN when the heap is in use. */ + + rrc = (op == OP_END)? MATCH_MATCH : MATCH_ACCEPT; + RRETURN(rrc); + + /* Assertion brackets. Check the alternative branches in turn - the + matching won't pass the KET for an assertion. If any one branch matches, + the assertion is true. Lookbehind assertions have an OP_REVERSE item at the + start of each branch to move the current point backwards, so the code at + this level is identical to the lookahead case. When the assertion is part + of a condition, we want to return immediately afterwards. The caller of + this incarnation of the match() function will have set MATCH_CONDASSERT in + mb->match_function type, and one of these opcodes will be the first opcode + that is processed. We use a local variable that is preserved over calls to + match() to remember this case. */ + + case OP_ASSERT: + case OP_ASSERTBACK: + save_mark = mb->mark; + if ((mb->match_function_type & MATCH_CONDASSERT) != 0) + { + condassert = TRUE; + mb->match_function_type &= ~MATCH_CONDASSERT; + } + else condassert = FALSE; + + /* Loop for each branch */ + + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, NULL, RM4); + + /* A match means that the assertion is true; break out of the loop + that matches its alternatives. */ + + if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) + { + mstart = mb->start_match_ptr; /* In case \K reset it */ + break; + } + + /* If not matched, restore the previous mark setting. */ + + mb->mark = save_mark; + + /* See comment in the code for capturing groups above about handling + THEN. */ + + if (rrc == MATCH_THEN) + { + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + rrc = MATCH_NOMATCH; + } + + /* Anything other than NOMATCH causes the entire assertion to fail, + passing back the return code. This includes COMMIT, SKIP, PRUNE and an + uncaptured THEN, which means they take their normal effect. This + consistent approach does not always have exactly the same effect as in + Perl. */ + + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode += GET(ecode, 1); + } + while (*ecode == OP_ALT); /* Continue for next alternative */ + + /* If we have tried all the alternative branches, the assertion has + failed. If not, we broke out after a match. */ + + if (*ecode == OP_KET) RRETURN(MATCH_NOMATCH); + + /* If checking an assertion for a condition, return MATCH_MATCH. */ + + if (condassert) RRETURN(MATCH_MATCH); + + /* Continue from after a successful assertion, updating the offsets high + water mark, since extracts may have been taken during the assertion. */ + + do ecode += GET(ecode,1); while (*ecode == OP_ALT); + ecode += 1 + LINK_SIZE; + offset_top = mb->end_offset_top; + continue; + + /* Negative assertion: all branches must fail to match for the assertion to + succeed. */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK_NOT: + save_mark = mb->mark; + if ((mb->match_function_type & MATCH_CONDASSERT) != 0) + { + condassert = TRUE; + mb->match_function_type &= ~MATCH_CONDASSERT; + } + else condassert = FALSE; + + /* Loop for each alternative branch. */ + + do + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, NULL, RM5); + mb->mark = save_mark; /* Always restore the mark setting */ + + switch(rrc) + { + case MATCH_MATCH: /* A successful match means */ + case MATCH_ACCEPT: /* the assertion has failed. */ + RRETURN(MATCH_NOMATCH); + + case MATCH_NOMATCH: /* Carry on with next branch */ + break; + + /* See comment in the code for capturing groups above about handling + THEN. */ + + case MATCH_THEN: + next_ecode = ecode + GET(ecode,1); + if (mb->start_match_ptr < next_ecode && + (*ecode == OP_ALT || *next_ecode == OP_ALT)) + { + rrc = MATCH_NOMATCH; + break; + } + /* Otherwise fall through. */ + + /* COMMIT, SKIP, PRUNE, and an uncaptured THEN cause the whole + assertion to fail to match, without considering any more alternatives. + Failing to match means the assertion is true. This is a consistent + approach, but does not always have the same effect as in Perl. */ + + case MATCH_COMMIT: + case MATCH_SKIP: + case MATCH_SKIP_ARG: + case MATCH_PRUNE: + do ecode += GET(ecode,1); while (*ecode == OP_ALT); + goto NEG_ASSERT_TRUE; /* Break out of alternation loop */ + + /* Anything else is an error */ + + default: + RRETURN(rrc); + } + + /* Continue with next branch */ + + ecode += GET(ecode,1); + } + while (*ecode == OP_ALT); + + /* All branches in the assertion failed to match. */ + + NEG_ASSERT_TRUE: + if (condassert) RRETURN(MATCH_MATCH); /* Condition assertion */ + ecode += 1 + LINK_SIZE; /* Continue with current branch */ + continue; + + /* Move the subject pointer back. This occurs only at the start of + each branch of a lookbehind assertion. If we are too close to the start to + move back, this match function fails. When working with UTF-8 we move + back a number of characters, not bytes. */ + + case OP_REVERSE: + i = GET(ecode, 1); +#ifdef SUPPORT_UNICODE + if (utf) + { + while (i-- > 0) + { + if (eptr <= mb->start_subject) RRETURN(MATCH_NOMATCH); + eptr--; + BACKCHAR(eptr); + } + } + else +#endif + + /* No UTF-8 support, or not in UTF-8 mode: count is byte count */ + + { + if (i > eptr - mb->start_subject) RRETURN(MATCH_NOMATCH); + eptr -= i; + } + + /* Save the earliest consulted character, then skip to next op code */ + + if (eptr < mb->start_used_ptr) mb->start_used_ptr = eptr; + ecode += 1 + LINK_SIZE; + break; + + /* The callout item calls an external function, if one is provided, passing + details of the match so far. This is mainly for debugging, though the + function is able to force a failure. */ + + case OP_CALLOUT: + case OP_CALLOUT_STR: + { + unsigned int callout_length = (*ecode == OP_CALLOUT) + ? PRIV(OP_lengths)[OP_CALLOUT] : GET(ecode, 1 + 2*LINK_SIZE); + + if (mb->callout != NULL) + { + pcre2_callout_block cb; + cb.version = 1; + cb.callout_number = ecode[LINK_SIZE + 1]; + cb.capture_top = offset_top/2; + cb.capture_last = mb->capture_last & CAPLMASK; + cb.offset_vector = mb->ovector; + cb.mark = mb->nomatch_mark; + cb.subject = mb->start_subject; + cb.subject_length = (PCRE2_SIZE)(mb->end_subject - mb->start_subject); + cb.start_match = (PCRE2_SIZE)(mstart - mb->start_subject); + cb.current_position = (PCRE2_SIZE)(eptr - mb->start_subject); + cb.pattern_position = GET(ecode, 1); + cb.next_item_length = GET(ecode, 1 + LINK_SIZE); + + if (*ecode == OP_CALLOUT) + { + cb.callout_number = ecode[1 + 2*LINK_SIZE]; + cb.callout_string_offset = 0; + cb.callout_string = NULL; + cb.callout_string_length = 0; + } + else + { + cb.callout_number = 0; + cb.callout_string_offset = GET(ecode, 1 + 3*LINK_SIZE); + cb.callout_string = ecode + (1 + 4*LINK_SIZE) + 1; + cb.callout_string_length = + callout_length - (1 + 4*LINK_SIZE) - 2; + } + + if ((rrc = mb->callout(&cb, mb->callout_data)) > 0) + RRETURN(MATCH_NOMATCH); + if (rrc < 0) RRETURN(rrc); + } + ecode += callout_length; + } + break; + + /* Recursion either matches the current regex, or some subexpression. The + offset data is the offset to the starting bracket from the start of the + whole pattern. (This is so that it works from duplicated subpatterns.) + + The state of the capturing groups is preserved over recursion, and + re-instated afterwards. We don't know how many are started and not yet + finished (offset_top records the completed total) so we just have to save + all the potential data. There may be up to 65535 such values, which is too + large to put on the stack, but using malloc for small numbers seems + expensive. As a compromise, the stack is used when there are no more than + OP_RECURSE_STACK_SAVE_MAX values to store; otherwise malloc is used. + + There are also other values that have to be saved. We use a chained + sequence of blocks that actually live on the stack. Thanks to Robin Houston + for the original version of this logic. It has, however, been hacked around + a lot, so he is not to blame for the current way it works. */ + + case OP_RECURSE: + { + ovecsave_frame *fr; + recursion_info *ri; + uint32_t recno; + + callpat = mb->start_code + GET(ecode, 1); + recno = (callpat == mb->start_code)? 0 : GET2(callpat, 1 + LINK_SIZE); + + /* Check for repeating a pattern recursion without advancing the subject + pointer. This should catch convoluted mutual recursions. (Some simple + cases are caught at compile time.) */ + + for (ri = mb->recursive; ri != NULL; ri = ri->prevrec) + if (recno == ri->group_num && eptr == ri->subject_position) + RRETURN(PCRE2_ERROR_RECURSELOOP); + + /* Add to "recursing stack" */ + + new_recursive.group_num = recno; + new_recursive.saved_capture_last = mb->capture_last; + new_recursive.subject_position = eptr; + new_recursive.prevrec = mb->recursive; + mb->recursive = &new_recursive; + + /* Where to continue from afterwards */ + + ecode += 1 + LINK_SIZE; + + /* When we are using the system stack for match() recursion we can call a + function that uses the system stack for preserving the ovector while + processing the pattern recursion, but only if the ovector is small + enough. */ + +#ifndef HEAP_MATCH_RECURSE + if (mb->offset_end <= OP_RECURSE_STACK_SAVE_MAX) + { + rrc = op_recurse_ovecsave(eptr, callpat, mstart, offset_top, mb, + eptrb, rdepth); + mb->recursive = new_recursive.prevrec; + if (rrc != MATCH_MATCH && rrc != MATCH_ACCEPT) RRETURN(rrc); + + /* Set where we got to in the subject, and reset the start, in case + it was changed by \K. This *is* propagated back out of a recursion, + for Perl compatibility. */ + + eptr = mb->end_match_ptr; + mstart = mb->start_match_ptr; + break; /* End of processing OP_RECURSE */ + } +#endif + /* If the ovector is too big, or if we are using the heap for match() + recursion, we have to use the heap for saving the ovector. Used ovecsave + frames are kept on a chain and re-used. This makes a small improvement in + execution time on Linux. */ + + if (mb->ovecsave_chain != NULL) + { + new_recursive.ovec_save = mb->ovecsave_chain->saved_ovec; + mb->ovecsave_chain = mb->ovecsave_chain->next; + } + else + { + fr = (ovecsave_frame *)(mb->memctl.malloc(sizeof(ovecsave_frame *) + + mb->offset_end * sizeof(PCRE2_SIZE), mb->memctl.memory_data)); + if (fr == NULL) RRETURN(PCRE2_ERROR_NOMEMORY); + new_recursive.ovec_save = fr->saved_ovec; + } + + memcpy(new_recursive.ovec_save, mb->ovector, + mb->offset_end * sizeof(PCRE2_SIZE)); + + /* Do the recursion. After processing each alternative, restore the + ovector data and the last captured value. This code has the same overall + logic as the code in the op_recurse_ovecsave() function, but is adapted + to use RMATCH/RRETURN and to release the heap block containing the saved + ovector. */ + + cbegroup = (*callpat >= OP_SBRA); + do + { + if (cbegroup) mb->match_function_type |= MATCH_CBEGROUP; + RMATCH(eptr, callpat + PRIV(OP_lengths)[*callpat], offset_top, + mb, eptrb, RM6); + memcpy(mb->ovector, new_recursive.ovec_save, + mb->offset_end * sizeof(PCRE2_SIZE)); + mb->capture_last = new_recursive.saved_capture_last; + mb->recursive = new_recursive.prevrec; + + if (rrc == MATCH_MATCH || rrc == MATCH_ACCEPT) + { + fr = (ovecsave_frame *) + ((uint8_t *)new_recursive.ovec_save - sizeof(ovecsave_frame *)); + fr->next = mb->ovecsave_chain; + mb->ovecsave_chain = fr; + + /* Set where we got to in the subject, and reset the start, in case + it was changed by \K. This *is* propagated back out of a recursion, + for Perl compatibility. */ + + eptr = mb->end_match_ptr; + mstart = mb->start_match_ptr; + goto RECURSION_MATCHED; /* Exit loop; end processing */ + } + + /* PCRE does not allow THEN, SKIP, PRUNE or COMMIT to escape beyond a + recursion; they cause a NOMATCH for the entire recursion. These codes + are defined in a range that can be tested for. */ + + if (rrc >= MATCH_BACKTRACK_MIN && rrc <= MATCH_BACKTRACK_MAX) + { + rrc = MATCH_NOMATCH; + goto RECURSION_RETURN; + } + + /* Any return code other than NOMATCH is an error. */ + + if (rrc != MATCH_NOMATCH) goto RECURSION_RETURN; + mb->recursive = &new_recursive; + callpat += GET(callpat, 1); + } + while (*callpat == OP_ALT); + + RECURSION_RETURN: + mb->recursive = new_recursive.prevrec; + fr = (ovecsave_frame *) + ((uint8_t *)new_recursive.ovec_save - sizeof(ovecsave_frame *)); + fr->next = mb->ovecsave_chain; + mb->ovecsave_chain = fr; + RRETURN(rrc); + } + + RECURSION_MATCHED: + break; + + /* An alternation is the end of a branch; scan along to find the end of the + bracketed group and go to there. */ + + case OP_ALT: + do ecode += GET(ecode,1); while (*ecode == OP_ALT); + break; + + /* BRAZERO, BRAMINZERO and SKIPZERO occur just before a bracket group, + indicating that it may occur zero times. It may repeat infinitely, or not + at all - i.e. it could be ()* or ()? or even (){0} in the pattern. Brackets + with fixed upper repeat limits are compiled as a number of copies, with the + optional ones preceded by BRAZERO or BRAMINZERO. */ + + case OP_BRAZERO: + next_ecode = ecode + 1; + RMATCH(eptr, next_ecode, offset_top, mb, eptrb, RM10); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + do next_ecode += GET(next_ecode, 1); while (*next_ecode == OP_ALT); + ecode = next_ecode + 1 + LINK_SIZE; + break; + + case OP_BRAMINZERO: + next_ecode = ecode + 1; + do next_ecode += GET(next_ecode, 1); while (*next_ecode == OP_ALT); + RMATCH(eptr, next_ecode + 1+LINK_SIZE, offset_top, mb, eptrb, RM11); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + ecode++; + break; + + case OP_SKIPZERO: + next_ecode = ecode+1; + do next_ecode += GET(next_ecode,1); while (*next_ecode == OP_ALT); + ecode = next_ecode + 1 + LINK_SIZE; + break; + + /* BRAPOSZERO occurs before a possessive bracket group. Don't do anything + here; just jump to the group, with allow_zero set TRUE. */ + + case OP_BRAPOSZERO: + op = *(++ecode); + allow_zero = TRUE; + if (op == OP_CBRAPOS || op == OP_SCBRAPOS) goto POSSESSIVE_CAPTURE; + goto POSSESSIVE_NON_CAPTURE; + + /* End of a group, repeated or non-repeating. */ + + case OP_KET: + case OP_KETRMIN: + case OP_KETRMAX: + case OP_KETRPOS: + prev = ecode - GET(ecode, 1); + + /* If this was a group that remembered the subject start, in order to break + infinite repeats of empty string matches, retrieve the subject start from + the chain. Otherwise, set it NULL. */ + + if (*prev >= OP_SBRA || *prev == OP_ONCE) + { + saved_eptr = eptrb->epb_saved_eptr; /* Value at start of group */ + eptrb = eptrb->epb_prev; /* Backup to previous group */ + } + else saved_eptr = NULL; + + /* If we are at the end of an assertion group or a non-capturing atomic + group, stop matching and return MATCH_MATCH, but record the current high + water mark for use by positive assertions. We also need to record the match + start in case it was changed by \K. */ + + if ((*prev >= OP_ASSERT && *prev <= OP_ASSERTBACK_NOT) || + *prev == OP_ONCE_NC) + { + mb->end_match_ptr = eptr; /* For ONCE_NC */ + mb->end_offset_top = offset_top; + mb->start_match_ptr = mstart; + if (eptr > mb->last_used_ptr) mb->last_used_ptr = eptr; + RRETURN(MATCH_MATCH); /* Sets mb->mark */ + } + + /* For capturing groups we have to check the group number back at the start + and if necessary complete handling an extraction by setting the offsets and + bumping the high water mark. Whole-pattern recursion is coded as a recurse + into group 0, so it won't be picked up here. Instead, we catch it when the + OP_END is reached. Other recursion is handled here. We just have to record + the current subject position and start match pointer and give a MATCH + return. */ + + if (*prev == OP_CBRA || *prev == OP_SCBRA || + *prev == OP_CBRAPOS || *prev == OP_SCBRAPOS) + { + number = GET2(prev, 1+LINK_SIZE); + offset = number << 1; + + /* Handle a recursively called group. */ + + if (mb->recursive != NULL && mb->recursive->group_num == number) + { + mb->end_match_ptr = eptr; + mb->start_match_ptr = mstart; + if (eptr > mb->last_used_ptr) mb->last_used_ptr = eptr; + RRETURN(MATCH_MATCH); + } + + /* Deal with capturing */ + + mb->capture_last = (mb->capture_last & OVFLMASK) | number; + if (offset >= mb->offset_max) mb->capture_last |= OVFLBIT; else + { + /* If offset is greater than offset_top, it means that we are + "skipping" a capturing group, and that group's offsets must be marked + unset. In earlier versions of PCRE, all the offsets were unset at the + start of matching, but this doesn't work because atomic groups and + assertions can cause a value to be set that should later be unset. + Example: matching /(?>(a))b|(a)c/ against "ac". This sets group 1 as + part of the atomic group, but this is not on the final matching path, + so must be unset when 2 is set. (If there is no group 2, there is no + problem, because offset_top will then be 2, indicating no capture.) */ + + if (offset > offset_top) + { + register PCRE2_SIZE *iptr = mb->ovector + offset_top; + register PCRE2_SIZE *iend = mb->ovector + offset; + while (iptr < iend) *iptr++ = PCRE2_UNSET; + } + + /* Now make the extraction */ + + mb->ovector[offset] = mb->ovector[mb->offset_end - number]; + mb->ovector[offset+1] = eptr - mb->start_subject; + if (offset_top <= offset) offset_top = offset + 2; + } + } + + /* OP_KETRPOS is a possessive repeating ket. Remember the current position, + and return the MATCH_KETRPOS. This makes it possible to do the repeats one + at a time from the outer level, thus saving stack. This must precede the + empty string test - in this case that test is done at the outer level. */ + + if (*ecode == OP_KETRPOS) + { + mb->start_match_ptr = mstart; /* In case \K reset it */ + mb->end_match_ptr = eptr; + mb->end_offset_top = offset_top; + if (eptr > mb->last_used_ptr) mb->last_used_ptr = eptr; + RRETURN(MATCH_KETRPOS); + } + + /* For an ordinary non-repeating ket, just continue at this level. This + also happens for a repeating ket if no characters were matched in the + group. This is the forcible breaking of infinite loops as implemented in + Perl 5.005. For a non-repeating atomic group that includes captures, + establish a backup point by processing the rest of the pattern at a lower + level. If this results in a NOMATCH return, pass MATCH_ONCE back to the + original OP_ONCE level, thereby bypassing intermediate backup points, but + resetting any captures that happened along the way. */ + + if (*ecode == OP_KET || eptr == saved_eptr) + { + if (*prev == OP_ONCE) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, eptrb, RM12); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->once_target = prev; /* Level at which to change to MATCH_NOMATCH */ + RRETURN(MATCH_ONCE); + } + ecode += 1 + LINK_SIZE; /* Carry on at this level */ + break; + } + + /* The normal repeating kets try the rest of the pattern or restart from + the preceding bracket, in the appropriate order. In the second case, we can + use tail recursion to avoid using another stack frame, unless we have an + an atomic group or an unlimited repeat of a group that can match an empty + string. */ + + if (*ecode == OP_KETRMIN) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, eptrb, RM7); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (*prev == OP_ONCE) + { + RMATCH(eptr, prev, offset_top, mb, eptrb, RM8); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->once_target = prev; /* Level at which to change to MATCH_NOMATCH */ + RRETURN(MATCH_ONCE); + } + if (*prev >= OP_SBRA) /* Could match an empty string */ + { + RMATCH(eptr, prev, offset_top, mb, eptrb, RM50); + RRETURN(rrc); + } + ecode = prev; + goto TAIL_RECURSE; + } + else /* OP_KETRMAX */ + { + RMATCH(eptr, prev, offset_top, mb, eptrb, RM13); + if (rrc == MATCH_ONCE && mb->once_target == prev) rrc = MATCH_NOMATCH; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (*prev == OP_ONCE) + { + RMATCH(eptr, ecode + 1 + LINK_SIZE, offset_top, mb, eptrb, RM9); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + mb->once_target = prev; + RRETURN(MATCH_ONCE); + } + ecode += 1 + LINK_SIZE; + goto TAIL_RECURSE; + } + /* Control never gets here */ + + /* Not multiline mode: start of subject assertion, unless notbol. */ + + case OP_CIRC: + if ((mb->moptions & PCRE2_NOTBOL) != 0 && eptr == mb->start_subject) + RRETURN(MATCH_NOMATCH); + + /* Start of subject assertion */ + + case OP_SOD: + if (eptr != mb->start_subject) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Multiline mode: start of subject unless notbol, or after any newline + except for one at the very end, unless PCRE2_ALT_CIRCUMFLEX is set. */ + + case OP_CIRCM: + if ((mb->moptions & PCRE2_NOTBOL) != 0 && eptr == mb->start_subject) + RRETURN(MATCH_NOMATCH); + if (eptr != mb->start_subject && + ((eptr == mb->end_subject && + (mb->poptions & PCRE2_ALT_CIRCUMFLEX) == 0) || + !WAS_NEWLINE(eptr))) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Start of match assertion */ + + case OP_SOM: + if (eptr != mb->start_subject + mb->start_offset) RRETURN(MATCH_NOMATCH); + ecode++; + break; + + /* Reset the start of match point */ + + case OP_SET_SOM: + mstart = eptr; + ecode++; + break; + + /* Multiline mode: assert before any newline, or before end of subject + unless noteol is set. */ + + case OP_DOLLM: + if (eptr < mb->end_subject) + { + if (!IS_NEWLINE(eptr)) + { + if (mb->partial != 0 && + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + UCHAR21TEST(eptr) == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + RRETURN(MATCH_NOMATCH); + } + } + else + { + if ((mb->moptions & PCRE2_NOTEOL) != 0) RRETURN(MATCH_NOMATCH); + SCHECK_PARTIAL(); + } + ecode++; + break; + + /* Not multiline mode: assert before a terminating newline or before end of + subject unless noteol is set. */ + + case OP_DOLL: + if ((mb->moptions & PCRE2_NOTEOL) != 0) RRETURN(MATCH_NOMATCH); + if ((mb->poptions & PCRE2_DOLLAR_ENDONLY) == 0) goto ASSERT_NL_OR_EOS; + + /* ... else fall through for endonly */ + + /* End of subject assertion (\z) */ + + case OP_EOD: + if (eptr < mb->end_subject) RRETURN(MATCH_NOMATCH); + SCHECK_PARTIAL(); + ecode++; + break; + + /* End of subject or ending \n assertion (\Z) */ + + case OP_EODN: + ASSERT_NL_OR_EOS: + if (eptr < mb->end_subject && + (!IS_NEWLINE(eptr) || eptr != mb->end_subject - mb->nllen)) + { + if (mb->partial != 0 && + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + UCHAR21TEST(eptr) == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + RRETURN(MATCH_NOMATCH); + } + + /* Either at end of string or \n before end. */ + + SCHECK_PARTIAL(); + ecode++; + break; + + /* Word boundary assertions */ + + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + { + + /* Find out if the previous and current characters are "word" characters. + It takes a bit more work in UTF-8 mode. Characters > 255 are assumed to + be "non-word" characters. Remember the earliest consulted character for + partial matching. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + /* Get status of previous character */ + + if (eptr == mb->start_subject) prev_is_word = FALSE; else + { + PCRE2_SPTR lastptr = eptr - 1; + BACKCHAR(lastptr); + if (lastptr < mb->start_used_ptr) mb->start_used_ptr = lastptr; + GETCHAR(c, lastptr); + if ((mb->poptions & PCRE2_UCP) != 0) + { + if (c == '_') prev_is_word = TRUE; else + { + int cat = UCD_CATEGORY(c); + prev_is_word = (cat == ucp_L || cat == ucp_N); + } + } + else + prev_is_word = c < 256 && (mb->ctypes[c] & ctype_word) != 0; + } + + /* Get status of next character */ + + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + cur_is_word = FALSE; + } + else + { + PCRE2_SPTR nextptr = eptr + 1; + FORWARDCHARTEST(nextptr, mb->end_subject); + if (nextptr > mb->last_used_ptr) mb->last_used_ptr = nextptr; + GETCHAR(c, eptr); + if ((mb->poptions & PCRE2_UCP) != 0) + { + if (c == '_') cur_is_word = TRUE; else + { + int cat = UCD_CATEGORY(c); + cur_is_word = (cat == ucp_L || cat == ucp_N); + } + } + else + cur_is_word = c < 256 && (mb->ctypes[c] & ctype_word) != 0; + } + } + else +#endif /* SUPPORT UTF */ + + /* Not in UTF-8 mode, but we may still have PCRE2_UCP set, and for + consistency with the behaviour of \w we do use it in this case. */ + + { + /* Get status of previous character */ + + if (eptr == mb->start_subject) prev_is_word = FALSE; else + { + if (eptr <= mb->start_used_ptr) mb->start_used_ptr = eptr - 1; +#ifdef SUPPORT_UNICODE + if ((mb->poptions & PCRE2_UCP) != 0) + { + c = eptr[-1]; + if (c == '_') prev_is_word = TRUE; else + { + int cat = UCD_CATEGORY(c); + prev_is_word = (cat == ucp_L || cat == ucp_N); + } + } + else +#endif + prev_is_word = MAX_255(eptr[-1]) + && ((mb->ctypes[eptr[-1]] & ctype_word) != 0); + } + + /* Get status of next character */ + + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + cur_is_word = FALSE; + } + else + { + if (eptr >= mb->last_used_ptr) mb->last_used_ptr = eptr + 1; +#ifdef SUPPORT_UNICODE + if ((mb->poptions & PCRE2_UCP) != 0) + { + c = *eptr; + if (c == '_') cur_is_word = TRUE; else + { + int cat = UCD_CATEGORY(c); + cur_is_word = (cat == ucp_L || cat == ucp_N); + } + } + else +#endif + cur_is_word = MAX_255(*eptr) + && ((mb->ctypes[*eptr] & ctype_word) != 0); + } + } + + /* Now see if the situation is what we want */ + + if ((*ecode++ == OP_WORD_BOUNDARY)? + cur_is_word == prev_is_word : cur_is_word != prev_is_word) + RRETURN(MATCH_NOMATCH); + } + break; + + /* Match any single character type except newline; have to take care with + CRLF newlines and partial matching. */ + + case OP_ANY: + if (IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); + if (mb->partial != 0 && + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + UCHAR21TEST(eptr) == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + + /* Fall through */ + + /* Match any single character whatsoever. */ + + case OP_ALLANY: + if (eptr >= mb->end_subject) /* DO NOT merge the eptr++ here; it must */ + { /* not be updated before SCHECK_PARTIAL. */ + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr++; +#ifdef SUPPORT_UNICODE + if (utf) ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); +#endif + ecode++; + break; + + /* Match a single code unit, even in UTF-8 mode. This opcode really does + match any code unit, even newline. (It really should be called ANYCODEUNIT, + of course - the byte name is from pre-16 bit days.) */ + + case OP_ANYBYTE: + if (eptr >= mb->end_subject) /* DO NOT merge the eptr++ here; it must */ + { /* not be updated before SCHECK_PARTIAL. */ + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr++; + ecode++; + break; + + case OP_NOT_DIGIT: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c < 256 && +#endif + (mb->ctypes[c] & ctype_digit) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_DIGIT: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c > 255 || +#endif + (mb->ctypes[c] & ctype_digit) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_NOT_WHITESPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c < 256 && +#endif + (mb->ctypes[c] & ctype_space) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_WHITESPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c > 255 || +#endif + (mb->ctypes[c] & ctype_space) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_NOT_WORDCHAR: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c < 256 && +#endif + (mb->ctypes[c] & ctype_word) != 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_WORDCHAR: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ( +#ifdef SUPPORT_WIDE_CHARS + c > 255 || +#endif + (mb->ctypes[c] & ctype_word) == 0 + ) + RRETURN(MATCH_NOMATCH); + ecode++; + break; + + case OP_ANYNL: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + + case CHAR_CR: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + } + else if (UCHAR21TEST(eptr) == CHAR_LF) eptr++; + break; + + case CHAR_LF: + break; + + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) RRETURN(MATCH_NOMATCH); + break; + } + ecode++; + break; + + case OP_NOT_HSPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + HSPACE_CASES: RRETURN(MATCH_NOMATCH); /* Byte and multibyte cases */ + default: break; + } + ecode++; + break; + + case OP_HSPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + HSPACE_CASES: break; /* Byte and multibyte cases */ + default: RRETURN(MATCH_NOMATCH); + } + ecode++; + break; + + case OP_NOT_VSPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + VSPACE_CASES: RRETURN(MATCH_NOMATCH); + default: break; + } + ecode++; + break; + + case OP_VSPACE: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + VSPACE_CASES: break; + default: RRETURN(MATCH_NOMATCH); + } + ecode++; + break; + +#ifdef SUPPORT_UNICODE + /* Check the next character by Unicode property. We will get here only + if the support is in the binary; otherwise a compile-time error occurs. */ + + case OP_PROP: + case OP_NOTPROP: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + { + const uint32_t *cp; + const ucd_record *prop = GET_UCD(c); + + switch(ecode[1]) + { + case PT_ANY: + if (op == OP_NOTPROP) RRETURN(MATCH_NOMATCH); + break; + + case PT_LAMP: + if ((prop->chartype == ucp_Lu || + prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt) == (op == OP_NOTPROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_GC: + if ((ecode[2] != PRIV(ucp_gentype)[prop->chartype]) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_PC: + if ((ecode[2] != prop->chartype) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_SC: + if ((ecode[2] != prop->script) == (op == OP_PROP)) + RRETURN(MATCH_NOMATCH); + break; + + /* These are specials */ + + case PT_ALNUM: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N) == (op == OP_NOTPROP)) + RRETURN(MATCH_NOMATCH); + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + if (op == OP_NOTPROP) RRETURN(MATCH_NOMATCH); + break; + + default: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_Z) == + (op == OP_NOTPROP)) RRETURN(MATCH_NOMATCH); + break; + } + break; + + case PT_WORD: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || + c == CHAR_UNDERSCORE) == (op == OP_NOTPROP)) + RRETURN(MATCH_NOMATCH); + break; + + case PT_CLIST: + cp = PRIV(ucd_caseless_sets) + ecode[2]; + for (;;) + { + if (c < *cp) + { if (op == OP_PROP) { RRETURN(MATCH_NOMATCH); } else break; } + if (c == *cp++) + { if (op == OP_PROP) break; else { RRETURN(MATCH_NOMATCH); } } + } + break; + + case PT_UCNC: + if ((c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000) == (op == OP_NOTPROP)) + RRETURN(MATCH_NOMATCH); + break; + + /* This should never occur */ + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + + ecode += 3; + } + break; + + /* Match an extended Unicode sequence. We will get here only if the support + is in the binary; otherwise a compile-time error occurs. */ + + case OP_EXTUNI: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + else + { + int lgb, rgb; + GETCHARINCTEST(c, eptr); + lgb = UCD_GRAPHBREAK(c); + while (eptr < mb->end_subject) + { + int len = 1; + if (!utf) c = *eptr; else { GETCHARLEN(c, eptr, len); } + rgb = UCD_GRAPHBREAK(c); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + lgb = rgb; + eptr += len; + } + } + CHECK_PARTIAL(); + ecode++; + break; +#endif /* SUPPORT_UNICODE */ + + + /* Match a back reference, possibly repeatedly. Look past the end of the + item to see if there is repeat information following. + + The OP_REF and OP_REFI opcodes are used for a reference to a numbered group + or to a non-duplicated named group. For a duplicated named group, OP_DNREF + and OP_DNREFI are used. In this case we must scan the list of groups to + which the name refers, and use the first one that is set. */ + + case OP_DNREF: + case OP_DNREFI: + caseless = op == OP_DNREFI; + { + int count = GET2(ecode, 1+IMM2_SIZE); + PCRE2_SPTR slot = mb->name_table + GET2(ecode, 1) * mb->name_entry_size; + ecode += 1 + 2*IMM2_SIZE; + + /* Initializing 'offset' avoids a compiler warning in the REF_REPEAT + code. */ + + offset = 0; + while (count-- > 0) + { + offset = GET2(slot, 0) << 1; + if (offset < offset_top && mb->ovector[offset] != PCRE2_UNSET) break; + slot += mb->name_entry_size; + } + } + goto REF_REPEAT; + + case OP_REF: + case OP_REFI: + caseless = op == OP_REFI; + offset = GET2(ecode, 1) << 1; /* Doubled ref number */ + ecode += 1 + IMM2_SIZE; + + /* Set up for repetition, or handle the non-repeated case */ + + REF_REPEAT: + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + c = *ecode++ - OP_CRSTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + minimize = (*ecode == OP_CRMINRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 1 + IMM2_SIZE); + if (max == 0) max = INT_MAX; + ecode += 1 + 2 * IMM2_SIZE; + break; + + default: /* No repeat follows */ + { + int rc = match_ref(offset, offset_top, eptr, mb, caseless, &length); + if (rc != 0) + { + if (rc > 0) eptr = mb->end_subject; /* Partial match */ + CHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + } + eptr += length; + continue; /* With the main loop */ + } + + /* Handle repeated back references. If a set group has length zero, just + continue with the main loop, because it matches however many times. For an + unset reference, if the minimum is zero, we can also just continue. We an + also continue if PCRE2_MATCH_UNSET_BACKREF is set, because this makes unset + group be have as a zero-length group. For any other unset cases, carrying + on will result in NOMATCH. */ + + if (offset < offset_top && mb->ovector[offset] != PCRE2_UNSET) + { + if (mb->ovector[offset] == mb->ovector[offset + 1]) continue; + } + else /* Group is not set */ + { + if (min == 0 || (mb->poptions & PCRE2_MATCH_UNSET_BACKREF) != 0) + continue; + } + + /* First, ensure the minimum number of matches are present. We get back + the length of the reference string explicitly rather than passing the + address of eptr, so that eptr can be a register variable. */ + + for (i = 1; i <= min; i++) + { + PCRE2_SIZE slength; + int rc = match_ref(offset, offset_top, eptr, mb, caseless, &slength); + if (rc != 0) + { + if (rc > 0) eptr = mb->end_subject; /* Partial match */ + CHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr += slength; + } + + /* If min = max, continue at the same level without recursion. + They are not both allowed to be zero. */ + + if (min == max) continue; + + /* If minimizing, keep trying and advancing the pointer */ + + if (minimize) + { + for (fi = min;; fi++) + { + int rc; + PCRE2_SIZE slength; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM14); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + rc = match_ref(offset, offset_top, eptr, mb, caseless, &slength); + if (rc != 0) + { + if (rc > 0) eptr = mb->end_subject; /* Partial match */ + CHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr += slength; + } + /* Control never gets here */ + } + + /* If maximizing, find the longest string and work backwards, as long as + the matched lengths for each iteration are the same. */ + + else + { + BOOL samelengths = TRUE; + pp = eptr; + length = mb->ovector[offset+1] - mb->ovector[offset]; + + for (i = min; i < max; i++) + { + PCRE2_SIZE slength; + int rc = match_ref(offset, offset_top, eptr, mb, caseless, &slength); + + if (rc != 0) + { + /* Can't use CHECK_PARTIAL because we don't want to update eptr in + the soft partial matching case. */ + + if (rc > 0 && mb->partial != 0 && + mb->end_subject > mb->start_used_ptr) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + break; + } + + if (slength != length) samelengths = FALSE; + eptr += slength; + } + + /* If the length matched for each repetition is the same as the length of + the captured group, we can easily work backwards. This is the normal + case. However, in caseless UTF-8 mode there are pairs of case-equivalent + characters whose lengths (in terms of code units) differ. However, this + is very rare, so we handle it by re-matching fewer and fewer times. */ + + if (samelengths) + { + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM15); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr -= length; + } + } + + /* The rare case of non-matching lengths. Re-scan the repetition for each + iteration. We know that match_ref() will succeed every time. */ + + else + { + max = i; + for (;;) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM68); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr == pp) break; /* Failed after minimal repetition */ + eptr = pp; + max--; + for (i = min; i < max; i++) + { + PCRE2_SIZE slength; + (void)match_ref(offset, offset_top, eptr, mb, caseless, &slength); + eptr += slength; + } + } + } + + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + /* Match a bit-mapped character class, possibly repeatedly. This op code is + used when all the characters in the class have values in the range 0-255, + and either the matching is caseful, or the characters are in the range + 0-127 when UTF-8 processing is enabled. The only difference between + OP_CLASS and OP_NCLASS occurs when a data character outside the range is + encountered. + + First, look past the end of the item to see if there is repeat information + following. Then obey similar code to character type repeats - written out + again for speed. */ + + case OP_NCLASS: + case OP_CLASS: + { + /* The data variable is saved across frames, so the byte map needs to + be stored there. */ +#define BYTE_MAP ((uint8_t *)data) + data = ecode + 1; /* Save for matching */ + ecode += 1 + (32 / sizeof(PCRE2_UCHAR)); /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSPLUS: + case OP_CRPOSQUERY: + c = *ecode++ - OP_CRSTAR; + if (c < OP_CRPOSSTAR - OP_CRSTAR) minimize = (c & 1) != 0; + else possessive = TRUE; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + minimize = (*ecode == OP_CRMINRANGE); + possessive = (*ecode == OP_CRPOSRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 1 + IMM2_SIZE); + if (max == 0) max = INT_MAX; + ecode += 1 + 2 * IMM2_SIZE; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF mode */ + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + c = *eptr++; +#if PCRE2_CODE_UNIT_WIDTH != 8 + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else +#endif + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM16); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM17); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + c = *eptr++; +#if PCRE2_CODE_UNIT_WIDTH != 8 + if (c > 255) + { + if (op == OP_CLASS) RRETURN(MATCH_NOMATCH); + } + else +#endif + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UNICODE + if (utf) + { + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c > 255) + { + if (op == OP_CLASS) break; + } + else + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) break; + eptr += len; + } + + if (possessive) continue; /* No backtracking */ + + for (;;) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM18); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ + BACKCHAR(eptr); + } + } + else +#endif + /* Not UTF mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + c = *eptr; +#if PCRE2_CODE_UNIT_WIDTH != 8 + if (c > 255) + { + if (op == OP_CLASS) break; + } + else +#endif + if ((BYTE_MAP[c/8] & (1 << (c&7))) == 0) break; + eptr++; + } + + if (possessive) continue; /* No backtracking */ + + while (eptr >= pp) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM19); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + + RRETURN(MATCH_NOMATCH); + } +#undef BYTE_MAP + } + /* Control never gets here */ + + + /* Match an extended character class. In the 8-bit library, this opcode is + encountered only when UTF-8 mode mode is supported. In the 16-bit and + 32-bit libraries, codepoints greater than 255 may be encountered even when + UTF is not supported. */ + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + { + data = ecode + 1 + LINK_SIZE; /* Save for matching */ + ecode += GET(ecode, 1); /* Advance past the item */ + + switch (*ecode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSPLUS: + case OP_CRPOSQUERY: + c = *ecode++ - OP_CRSTAR; + if (c < OP_CRPOSSTAR - OP_CRSTAR) minimize = (c & 1) != 0; + else possessive = TRUE; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + minimize = (*ecode == OP_CRMINRANGE); + possessive = (*ecode == OP_CRPOSRANGE); + min = GET2(ecode, 1); + max = GET2(ecode, 1 + IMM2_SIZE); + if (max == 0) max = INT_MAX; + ecode += 1 + 2 * IMM2_SIZE; + break; + + default: /* No repeat follows */ + min = max = 1; + break; + } + + /* First, ensure the minimum number of matches are present. */ + + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if (!PRIV(xclass)(c, data, utf)) RRETURN(MATCH_NOMATCH); + } + + /* If max == min we can continue with the main loop without the + need to recurse. */ + + if (min == max) continue; + + /* If minimizing, keep testing the rest of the expression and advancing + the pointer while it matches the class. */ + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM20); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if (!PRIV(xclass)(c, data, utf)) RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + + /* If maximizing, find the longest possible run, then work backwards. */ + + else + { + pp = eptr; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } +#ifdef SUPPORT_UNICODE + GETCHARLENTEST(c, eptr, len); +#else + c = *eptr; +#endif + if (!PRIV(xclass)(c, data, utf)) break; + eptr += len; + } + + if (possessive) continue; /* No backtracking */ + + for(;;) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM21); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (eptr-- == pp) break; /* Stop if tried at original pos */ +#ifdef SUPPORT_UNICODE + if (utf) BACKCHAR(eptr); +#endif + } + RRETURN(MATCH_NOMATCH); + } + + /* Control never gets here */ + } +#endif /* End of XCLASS */ + + /* Match a single character, casefully */ + + case OP_CHAR: +#ifdef SUPPORT_UNICODE + if (utf) + { + length = 1; + ecode++; + GETCHARLEN(fc, ecode, length); + if (length > (PCRE2_SIZE)(mb->end_subject - eptr)) + { + CHECK_PARTIAL(); /* Not SCHECK_PARTIAL() */ + RRETURN(MATCH_NOMATCH); + } + for (; length > 0; length--) + { + if (*ecode++ != UCHAR21INC(eptr)) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF mode */ + { + if (mb->end_subject - eptr < 1) + { + SCHECK_PARTIAL(); /* This one can use SCHECK_PARTIAL() */ + RRETURN(MATCH_NOMATCH); + } + if (ecode[1] != *eptr++) RRETURN(MATCH_NOMATCH); + ecode += 2; + } + break; + + /* Match a single character, caselessly. If we are at the end of the + subject, give up immediately. */ + + case OP_CHARI: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + +#ifdef SUPPORT_UNICODE + if (utf) + { + length = 1; + ecode++; + GETCHARLEN(fc, ecode, length); + + /* If the pattern character's value is < 128, we have only one byte, and + we know that its other case must also be one byte long, so we can use the + fast lookup table. We know that there is at least one byte left in the + subject. */ + + if (fc < 128) + { + uint32_t cc = UCHAR21(eptr); + if (mb->lcc[fc] != TABLE_GET(cc, mb->lcc, cc)) RRETURN(MATCH_NOMATCH); + ecode++; + eptr++; + } + + /* Otherwise we must pick up the subject character. Note that we cannot + use the value of "length" to check for sufficient bytes left, because the + other case of the character may have more or fewer bytes. */ + + else + { + uint32_t dc; + GETCHARINC(dc, eptr); + ecode += length; + + /* If we have Unicode property support, we can use it to test the other + case of the character, if there is one. */ + + if (fc != dc) + { +#ifdef SUPPORT_UNICODE + if (dc != UCD_OTHERCASE(fc)) +#endif + RRETURN(MATCH_NOMATCH); + } + } + } + else +#endif /* SUPPORT_UNICODE */ + + /* Not UTF mode */ + { + if (TABLE_GET(ecode[1], mb->lcc, ecode[1]) + != TABLE_GET(*eptr, mb->lcc, *eptr)) RRETURN(MATCH_NOMATCH); + eptr++; + ecode += 2; + } + break; + + /* Match a single character repeatedly. */ + + case OP_EXACT: + case OP_EXACTI: + min = max = GET2(ecode, 1); + ecode += 1 + IMM2_SIZE; + goto REPEATCHAR; + + case OP_POSUPTO: + case OP_POSUPTOI: + possessive = TRUE; + /* Fall through */ + + case OP_UPTO: + case OP_UPTOI: + case OP_MINUPTO: + case OP_MINUPTOI: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_MINUPTO || *ecode == OP_MINUPTOI; + ecode += 1 + IMM2_SIZE; + goto REPEATCHAR; + + case OP_POSSTAR: + case OP_POSSTARI: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATCHAR; + + case OP_POSPLUS: + case OP_POSPLUSI: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATCHAR; + + case OP_POSQUERY: + case OP_POSQUERYI: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATCHAR; + + case OP_STAR: + case OP_STARI: + case OP_MINSTAR: + case OP_MINSTARI: + case OP_PLUS: + case OP_PLUSI: + case OP_MINPLUS: + case OP_MINPLUSI: + case OP_QUERY: + case OP_QUERYI: + case OP_MINQUERY: + case OP_MINQUERYI: + c = *ecode++ - ((op < OP_STARI)? OP_STAR : OP_STARI); + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-character matches. We first check + for the minimum number of characters. If the minimum equals the maximum, we + are done. Otherwise, if minimizing, check the rest of the pattern for a + match; if there isn't one, advance up to the maximum, one character at a + time. + + If maximizing, advance up to the maximum number of matching characters, + until eptr is past the end of the maximum run. If possessive, we are + then done (no backing up). Otherwise, match at this position; anything + other than no match is immediately returned. For nomatch, back up one + character, unless we are matching \R and the last thing matched was + \r\n, in which case, back up two bytes. When we reach the first optional + character position, we can save stack by doing a tail recurse. + + The various UTF/non-UTF and caseful/caseless cases are handled separately, + for speed. */ + + REPEATCHAR: +#ifdef SUPPORT_UNICODE + if (utf) + { + length = 1; + charptr = ecode; + GETCHARLEN(fc, ecode, length); + ecode += length; + + /* Handle multibyte character matching specially here. There is + support for caseless matching if UCP support is present. */ + + if (length > 1) + { + uint32_t othercase; + if (op >= OP_STARI && /* Caseless */ + (othercase = UCD_OTHERCASE(fc)) != fc) + oclength = PRIV(ord2utf)(othercase, occhars); + else oclength = 0; + + for (i = 1; i <= min; i++) + { + if (eptr <= mb->end_subject - length && + memcmp(eptr, charptr, CU2BYTES(length)) == 0) eptr += length; + else if (oclength > 0 && + eptr <= mb->end_subject - oclength && + memcmp(eptr, occhars, CU2BYTES(oclength)) == 0) eptr += oclength; + else + { + CHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + } + + if (min == max) continue; + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM22); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr <= mb->end_subject - length && + memcmp(eptr, charptr, CU2BYTES(length)) == 0) eptr += length; + else if (oclength > 0 && + eptr <= mb->end_subject - oclength && + memcmp(eptr, occhars, CU2BYTES(oclength)) == 0) eptr += oclength; + else + { + CHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + if (eptr <= mb->end_subject - length && + memcmp(eptr, charptr, CU2BYTES(length)) == 0) eptr += length; + else if (oclength > 0 && + eptr <= mb->end_subject - oclength && + memcmp(eptr, occhars, CU2BYTES(oclength)) == 0) eptr += oclength; + else + { + CHECK_PARTIAL(); + break; + } + } + + if (possessive) continue; /* No backtracking */ + + /* After \C in UTF mode, pp might be in the middle of a Unicode + character. Use <= pp to ensure backtracking doesn't go too far. */ + + for(;;) + { + if (eptr <= pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM23); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + BACKCHAR(eptr); + } + } + /* Control never gets here */ + } + + /* If the length of a UTF-8 character is 1, we fall through here, and + obey the code as for non-UTF-8 characters below, though in this case the + value of fc will always be < 128. */ + } + else +#endif /* SUPPORT_UNICODE */ + + /* When not in UTF-8 mode, load a single-byte character. */ + fc = *ecode++; + + /* The value of fc at this point is always one character, though we may + or may not be in UTF mode. The code is duplicated for the caseless and + caseful cases, for speed, since matching characters is likely to be quite + common. First, ensure the minimum number of matches are present. If min = + max, continue at the same level without recursing. Otherwise, if + minimizing, keep trying the rest of the expression and advancing one + matching character if failing, up to the maximum. Alternatively, if + maximizing, find the maximum number of characters and work backwards. */ + + if (op >= OP_STARI) /* Caseless */ + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + /* fc must be < 128 if UTF is enabled. */ + foc = mb->fcc[fc]; +#else +#ifdef SUPPORT_UNICODE + if (utf && fc > 127) + foc = UCD_OTHERCASE(fc); + else +#endif /* SUPPORT_UNICODE */ + foc = TABLE_GET(fc, mb->fcc, fc); +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + + for (i = 1; i <= min; i++) + { + uint32_t cc; /* Faster than PCRE2_UCHAR */ + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21TEST(eptr); + if (fc != cc && foc != cc) RRETURN(MATCH_NOMATCH); + eptr++; + } + if (min == max) continue; + if (minimize) + { + for (fi = min;; fi++) + { + uint32_t cc; /* Faster than PCRE2_UCHAR */ + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM24); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21TEST(eptr); + if (fc != cc && foc != cc) RRETURN(MATCH_NOMATCH); + eptr++; + } + /* Control never gets here */ + } + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + uint32_t cc; /* Faster than PCRE2_UCHAR */ + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + cc = UCHAR21TEST(eptr); + if (fc != cc && foc != cc) break; + eptr++; + } + if (possessive) continue; /* No backtracking */ + for (;;) + { + if (eptr == pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM25); + eptr--; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + } + /* Control never gets here */ + } + } + + /* Caseful comparisons (includes all multi-byte characters) */ + + else + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc != UCHAR21INCTEST(eptr)) RRETURN(MATCH_NOMATCH); + } + + if (min == max) continue; + + if (minimize) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM26); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc != UCHAR21INCTEST(eptr)) RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + } + else /* Maximize */ + { + pp = eptr; + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (fc != UCHAR21TEST(eptr)) break; + eptr++; + } + if (possessive) continue; /* No backtracking */ + for (;;) + { + if (eptr == pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM27); + eptr--; + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + } + /* Control never gets here */ + } + } + /* Control never gets here */ + + /* Match a negated single one-byte character. The character we are + checking can be multibyte. */ + + case OP_NOT: + case OP_NOTI: + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t ch, och; + + ecode++; + GETCHARINC(ch, ecode); + GETCHARINC(c, eptr); + + if (op == OP_NOT) + { + if (ch == c) RRETURN(MATCH_NOMATCH); + } + else + { + if (ch > 127) + och = UCD_OTHERCASE(ch); + else + och = TABLE_GET(ch, mb->fcc, ch); + if (ch == c || och == c) RRETURN(MATCH_NOMATCH); + } + } + else +#endif /* SUPPORT_UNICODE */ + { + register uint32_t ch = ecode[1]; + c = *eptr++; + if (ch == c || (op == OP_NOTI && TABLE_GET(ch, mb->fcc, ch) == c)) + RRETURN(MATCH_NOMATCH); + ecode += 2; + } + break; + + /* Match a negated single one-byte character repeatedly. This is almost a + repeat of the code for a repeated single character, but I haven't found a + nice way of commoning these up that doesn't require a test of the + positive/negative option for each character match. Maybe that wouldn't add + very much to the time taken, but character matching *is* what this is all + about... */ + + case OP_NOTEXACT: + case OP_NOTEXACTI: + min = max = GET2(ecode, 1); + ecode += 1 + IMM2_SIZE; + goto REPEATNOTCHAR; + + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_NOTMINUPTO || *ecode == OP_NOTMINUPTOI; + ecode += 1 + IMM2_SIZE; + goto REPEATNOTCHAR; + + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATNOTCHAR; + + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + possessive = TRUE; + min = 0; + max = GET2(ecode, 1); + ecode += 1 + IMM2_SIZE; + goto REPEATNOTCHAR; + + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + c = *ecode++ - ((op >= OP_NOTSTARI)? OP_NOTSTARI: OP_NOTSTAR); + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single-byte matches. */ + + REPEATNOTCHAR: + GETCHARINCTEST(fc, ecode); + + /* The code is duplicated for the caseless and caseful cases, for speed, + since matching characters is likely to be quite common. First, ensure the + minimum number of matches are present. If min = max, continue at the same + level without recursing. Otherwise, if minimizing, keep trying the rest of + the expression and advancing one matching character if failing, up to the + maximum. Alternatively, if maximizing, find the maximum number of + characters and work backwards. */ + + if (op >= OP_NOTSTARI) /* Caseless */ + { +#ifdef SUPPORT_UNICODE + if (utf && fc > 127) + foc = UCD_OTHERCASE(fc); + else +#endif /* SUPPORT_UNICODE */ + foc = TABLE_GET(fc, mb->fcc, fc); + +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(d, eptr); + if (fc == d || (uint32_t)foc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif /* SUPPORT_UNICODE */ + /* Not UTF mode */ + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc == *eptr || foc == *eptr) RRETURN(MATCH_NOMATCH); + eptr++; + } + } + + if (min == max) continue; + + if (minimize) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM28); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(d, eptr); + if (fc == d || (uint32_t)foc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif /*SUPPORT_UNICODE */ + /* Not UTF mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM29); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc == *eptr || foc == *eptr) RRETURN(MATCH_NOMATCH); + eptr++; + } + } + /* Control never gets here */ + } + + /* Maximize case */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(d, eptr, len); + if (fc == d || (uint32_t)foc == d) break; + eptr += len; + } + if (possessive) continue; /* No backtracking */ + + /* After \C in UTF mode, pp might be in the middle of a Unicode + character. Use <= pp to ensure backtracking doesn't go too far. */ + + for(;;) + { + if (eptr <= pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM30); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + BACKCHAR(eptr); + } + } + else +#endif /* SUPPORT_UNICODE */ + /* Not UTF mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (fc == *eptr || foc == *eptr) break; + eptr++; + } + if (possessive) continue; /* No backtracking */ + for (;;) + { + if (eptr == pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM31); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + /* Control never gets here */ + } + } + + /* Caseful comparisons */ + + else + { +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(d, eptr); + if (fc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF mode */ + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc == *eptr++) RRETURN(MATCH_NOMATCH); + } + } + + if (min == max) continue; + + if (minimize) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM32); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(d, eptr); + if (fc == d) RRETURN(MATCH_NOMATCH); + } + } + else +#endif + /* Not UTF mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM33); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (fc == *eptr++) RRETURN(MATCH_NOMATCH); + } + } + /* Control never gets here */ + } + + /* Maximize case */ + + else + { + pp = eptr; + +#ifdef SUPPORT_UNICODE + if (utf) + { + register uint32_t d; + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(d, eptr, len); + if (fc == d) break; + eptr += len; + } + if (possessive) continue; /* No backtracking */ + + /* After \C in UTF mode, pp might be in the middle of a Unicode + character. Use <= pp to ensure backtracking doesn't go too far. */ + + for(;;) + { + if (eptr <= pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM34); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + BACKCHAR(eptr); + } + } + else +#endif + /* Not UTF mode */ + { + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (fc == *eptr) break; + eptr++; + } + if (possessive) continue; /* No backtracking */ + for (;;) + { + if (eptr == pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM35); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + } + } + /* Control never gets here */ + } + } + /* Control never gets here */ + + /* Match a single character type repeatedly; several different opcodes + share code. This is very similar to the code for single characters, but we + repeat it in the interests of efficiency. */ + + case OP_TYPEEXACT: + min = max = GET2(ecode, 1); + minimize = TRUE; + ecode += 1 + IMM2_SIZE; + goto REPEATTYPE; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + min = 0; + max = GET2(ecode, 1); + minimize = *ecode == OP_TYPEMINUPTO; + ecode += 1 + IMM2_SIZE; + goto REPEATTYPE; + + case OP_TYPEPOSSTAR: + possessive = TRUE; + min = 0; + max = INT_MAX; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSPLUS: + possessive = TRUE; + min = 1; + max = INT_MAX; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSQUERY: + possessive = TRUE; + min = 0; + max = 1; + ecode++; + goto REPEATTYPE; + + case OP_TYPEPOSUPTO: + possessive = TRUE; + min = 0; + max = GET2(ecode, 1); + ecode += 1 + IMM2_SIZE; + goto REPEATTYPE; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + c = *ecode++ - OP_TYPESTAR; + minimize = (c & 1) != 0; + min = rep_min[c]; /* Pick up values from tables; */ + max = rep_max[c]; /* zero for max => infinity */ + if (max == 0) max = INT_MAX; + + /* Common code for all repeated single character type matches. Note that + in UTF-8 mode, '.' matches a character of any length, but for the other + character types, the valid characters are all one-byte long. */ + + REPEATTYPE: + ctype = *ecode++; /* Code for the character type */ + +#ifdef SUPPORT_UNICODE + if (ctype == OP_PROP || ctype == OP_NOTPROP) + { + prop_fail_result = ctype == OP_NOTPROP; + prop_type = *ecode++; + prop_value = *ecode++; + } + else prop_type = -1; +#endif + + /* First, ensure the minimum number of matches are present. Use inline + code for maximizing the speed, and do the type test once at the start + (i.e. keep it out of the loop). Separate the UTF-8 code completely as that + is tidier. Also separate the UCP code, which can be the same for both UTF-8 + and single-bytes. */ + + if (min > 0) + { +#ifdef SUPPORT_UNICODE + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + } + break; + + case PT_LAMP: + for (i = 1; i <= min; i++) + { + int chartype; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + chartype = UCD_CHARTYPE(c); + if ((chartype == ucp_Lu || + chartype == ucp_Ll || + chartype == ucp_Lt) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_GC: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_PC: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_SC: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_ALNUM: + for (i = 1; i <= min; i++) + { + int category; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + category = UCD_CATEGORY(c); + if ((category == ucp_L || category == ucp_N) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + break; + + default: + if ((UCD_CATEGORY(c) == ucp_Z) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + break; + } + } + break; + + case PT_WORD: + for (i = 1; i <= min; i++) + { + int category; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + category = UCD_CATEGORY(c); + if ((category == ucp_L || category == ucp_N || c == CHAR_UNDERSCORE) + == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + case PT_CLIST: + for (i = 1; i <= min; i++) + { + const uint32_t *cp; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + cp = PRIV(ucd_caseless_sets) + prop_value; + for (;;) + { + if (c < *cp) + { if (prop_fail_result) break; else { RRETURN(MATCH_NOMATCH); } } + if (c == *cp++) + { if (prop_fail_result) { RRETURN(MATCH_NOMATCH); } else break; } + } + } + break; + + case PT_UCNC: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + break; + + /* This should not occur */ + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + } + + /* Match extended Unicode sequences. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + else + { + int lgb, rgb; + GETCHARINCTEST(c, eptr); + lgb = UCD_GRAPHBREAK(c); + while (eptr < mb->end_subject) + { + int len = 1; + if (!utf) c = *eptr; else { GETCHARLEN(c, eptr, len); } + rgb = UCD_GRAPHBREAK(c); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + lgb = rgb; + eptr += len; + } + } + CHECK_PARTIAL(); + } + } + + else +#endif /* SUPPORT_UNICODE */ + +/* Handle all other cases when the coding is UTF-8 */ + +#ifdef SUPPORT_UNICODE + if (utf) switch(ctype) + { + case OP_ANY: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); + if (mb->partial != 0 && + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + UCHAR21(eptr) == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + break; + + case OP_ALLANY: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + break; + + case OP_ANYBYTE: + if (eptr > mb->end_subject - min) RRETURN(MATCH_NOMATCH); + eptr += min; + break; + + case OP_ANYNL: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + + case CHAR_CR: + if (eptr < mb->end_subject && UCHAR21(eptr) == CHAR_LF) eptr++; + break; + + case CHAR_LF: + break; + + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) RRETURN(MATCH_NOMATCH); + break; + } + } + break; + + case OP_NOT_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + switch(c) + { + HSPACE_CASES: RRETURN(MATCH_NOMATCH); /* Byte and multibyte cases */ + default: break; + } + } + break; + + case OP_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + switch(c) + { + HSPACE_CASES: break; /* Byte and multibyte cases */ + default: RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_NOT_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + switch(c) + { + VSPACE_CASES: RRETURN(MATCH_NOMATCH); + default: break; + } + } + break; + + case OP_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + switch(c) + { + VSPACE_CASES: break; + default: RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINC(c, eptr); + if (c < 128 && (mb->ctypes[c] & ctype_digit) != 0) + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + { + uint32_t cc; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21(eptr); + if (cc >= 128 || (mb->ctypes[cc] & ctype_digit) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + { + uint32_t cc; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21(eptr); + if (cc < 128 && (mb->ctypes[cc] & ctype_space) != 0) + RRETURN(MATCH_NOMATCH); + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + { + uint32_t cc; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21(eptr); + if (cc >= 128 || (mb->ctypes[cc] & ctype_space) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + { + uint32_t cc; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21(eptr); + if (cc < 128 && (mb->ctypes[cc] & ctype_word) != 0) + RRETURN(MATCH_NOMATCH); + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + { + uint32_t cc; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + cc = UCHAR21(eptr); + if (cc >= 128 || (mb->ctypes[cc] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + /* No need to skip more bytes - we know it's a 1-byte character */ + } + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } /* End switch(ctype) */ + + else +#endif /* SUPPORT_UNICODE */ + + /* Code for the non-UTF-8 case for minimum matching of operators other + than OP_PROP and OP_NOTPROP. */ + + switch(ctype) + { + case OP_ANY: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (IS_NEWLINE(eptr)) RRETURN(MATCH_NOMATCH); + if (mb->partial != 0 && + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + *eptr == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + eptr++; + } + break; + + case OP_ALLANY: + if (eptr > mb->end_subject - min) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr += min; + break; + + case OP_ANYBYTE: + if (eptr > mb->end_subject - min) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + eptr += min; + break; + + case OP_ANYNL: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + + case CHAR_CR: + if (eptr < mb->end_subject && *eptr == CHAR_LF) eptr++; + break; + + case CHAR_LF: + break; + + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#if PCRE2_CODE_UNIT_WIDTH != 8 + case 0x2028: + case 0x2029: +#endif + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) RRETURN(MATCH_NOMATCH); + break; + } + } + break; + + case OP_NOT_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + switch(*eptr++) + { + default: break; + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + RRETURN(MATCH_NOMATCH); + } + } + break; + + case OP_HSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + break; + } + } + break; + + case OP_NOT_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + switch(*eptr++) + { + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + RRETURN(MATCH_NOMATCH); + default: break; + } + } + break; + + case OP_VSPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + switch(*eptr++) + { + default: RRETURN(MATCH_NOMATCH); + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + break; + } + } + break; + + case OP_NOT_DIGIT: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_digit) != 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + case OP_DIGIT: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_digit) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + case OP_NOT_WHITESPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_space) != 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + case OP_WHITESPACE: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_space) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + case OP_NOT_WORDCHAR: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_word) != 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + case OP_WORDCHAR: + for (i = 1; i <= min; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + eptr++; + } + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + } + + /* If min = max, continue at the same level without recursing */ + + if (min == max) continue; + + /* If minimizing, we have to test the rest of the pattern before each + subsequent match. Again, separate the UTF-8 case for speed, and also + separate the UCP cases. */ + + if (minimize) + { +#ifdef SUPPORT_UNICODE + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM36); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_LAMP: + for (fi = min;; fi++) + { + int chartype; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM37); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + chartype = UCD_CHARTYPE(c); + if ((chartype == ucp_Lu || + chartype == ucp_Ll || + chartype == ucp_Lt) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_GC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM38); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_PC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM39); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_SC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM40); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_ALNUM: + for (fi = min;; fi++) + { + int category; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM59); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + category = UCD_CATEGORY(c); + if ((category == ucp_L || category == ucp_N) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM61); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + if (prop_fail_result) RRETURN(MATCH_NOMATCH); + break; + + default: + if ((UCD_CATEGORY(c) == ucp_Z) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + break; + } + } + /* Control never gets here */ + + case PT_WORD: + for (fi = min;; fi++) + { + int category; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM62); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + category = UCD_CATEGORY(c); + if ((category == ucp_L || + category == ucp_N || + c == CHAR_UNDERSCORE) + == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + case PT_CLIST: + for (fi = min;; fi++) + { + const uint32_t *cp; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM67); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + cp = PRIV(ucd_caseless_sets) + prop_value; + for (;;) + { + if (c < *cp) + { if (prop_fail_result) break; else { RRETURN(MATCH_NOMATCH); } } + if (c == *cp++) + { if (prop_fail_result) { RRETURN(MATCH_NOMATCH); } else break; } + } + } + /* Control never gets here */ + + case PT_UCNC: + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM60); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + GETCHARINCTEST(c, eptr); + if ((c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000) == prop_fail_result) + RRETURN(MATCH_NOMATCH); + } + /* Control never gets here */ + + /* This should never occur */ + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + } + + /* Match extended Unicode sequences. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM41); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + else + { + int lgb, rgb; + GETCHARINCTEST(c, eptr); + lgb = UCD_GRAPHBREAK(c); + while (eptr < mb->end_subject) + { + int len = 1; + if (!utf) c = *eptr; else { GETCHARLEN(c, eptr, len); } + rgb = UCD_GRAPHBREAK(c); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + lgb = rgb; + eptr += len; + } + } + CHECK_PARTIAL(); + } + } + else +#endif /* SUPPORT_UNICODE */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM42); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (ctype == OP_ANY && IS_NEWLINE(eptr)) + RRETURN(MATCH_NOMATCH); + GETCHARINC(c, eptr); + switch(ctype) + { + case OP_ANY: /* This is the non-NL case */ + if (mb->partial != 0 && /* Take care with CRLF partial */ + eptr >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + break; + + case OP_ALLANY: + case OP_ANYBYTE: + break; + + case OP_ANYNL: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case CHAR_CR: + if (eptr < mb->end_subject && UCHAR21(eptr) == CHAR_LF) eptr++; + break; + + case CHAR_LF: + break; + + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#ifndef EBCDIC + case 0x2028: + case 0x2029: +#endif /* Not EBCDIC */ + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) RRETURN(MATCH_NOMATCH); + break; + } + break; + + case OP_NOT_HSPACE: + switch(c) + { + HSPACE_CASES: RRETURN(MATCH_NOMATCH); + default: break; + } + break; + + case OP_HSPACE: + switch(c) + { + HSPACE_CASES: break; + default: RRETURN(MATCH_NOMATCH); + } + break; + + case OP_NOT_VSPACE: + switch(c) + { + VSPACE_CASES: RRETURN(MATCH_NOMATCH); + default: break; + } + break; + + case OP_VSPACE: + switch(c) + { + VSPACE_CASES: break; + default: RRETURN(MATCH_NOMATCH); + } + break; + + case OP_NOT_DIGIT: + if (c < 256 && (mb->ctypes[c] & ctype_digit) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_DIGIT: + if (c >= 256 || (mb->ctypes[c] & ctype_digit) == 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WHITESPACE: + if (c < 256 && (mb->ctypes[c] & ctype_space) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_WHITESPACE: + if (c >= 256 || (mb->ctypes[c] & ctype_space) == 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WORDCHAR: + if (c < 256 && (mb->ctypes[c] & ctype_word) != 0) + RRETURN(MATCH_NOMATCH); + break; + + case OP_WORDCHAR: + if (c >= 256 || (mb->ctypes[c] & ctype_word) == 0) + RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + } + } + else +#endif + /* Not UTF mode */ + { + for (fi = min;; fi++) + { + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM43); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + if (fi >= max) RRETURN(MATCH_NOMATCH); + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + RRETURN(MATCH_NOMATCH); + } + if (ctype == OP_ANY && IS_NEWLINE(eptr)) + RRETURN(MATCH_NOMATCH); + c = *eptr++; + switch(ctype) + { + case OP_ANY: /* This is the non-NL case */ + if (mb->partial != 0 && /* Take care with CRLF partial */ + eptr >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + c == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + break; + + case OP_ALLANY: + case OP_ANYBYTE: + break; + + case OP_ANYNL: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + case CHAR_CR: + if (eptr < mb->end_subject && *eptr == CHAR_LF) eptr++; + break; + + case CHAR_LF: + break; + + case CHAR_VT: + case CHAR_FF: + case CHAR_NEL: +#if PCRE2_CODE_UNIT_WIDTH != 8 + case 0x2028: + case 0x2029: +#endif + if (mb->bsr_convention == PCRE2_BSR_ANYCRLF) RRETURN(MATCH_NOMATCH); + break; + } + break; + + case OP_NOT_HSPACE: + switch(c) + { + default: break; + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_HSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + break; + } + break; + + case OP_NOT_VSPACE: + switch(c) + { + default: break; + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + RRETURN(MATCH_NOMATCH); + } + break; + + case OP_VSPACE: + switch(c) + { + default: RRETURN(MATCH_NOMATCH); + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + break; + } + break; + + case OP_NOT_DIGIT: + if (MAX_255(c) && (mb->ctypes[c] & ctype_digit) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_DIGIT: + if (!MAX_255(c) || (mb->ctypes[c] & ctype_digit) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WHITESPACE: + if (MAX_255(c) && (mb->ctypes[c] & ctype_space) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_WHITESPACE: + if (!MAX_255(c) || (mb->ctypes[c] & ctype_space) == 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_NOT_WORDCHAR: + if (MAX_255(c) && (mb->ctypes[c] & ctype_word) != 0) RRETURN(MATCH_NOMATCH); + break; + + case OP_WORDCHAR: + if (!MAX_255(c) || (mb->ctypes[c] & ctype_word) == 0) RRETURN(MATCH_NOMATCH); + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + } + } + /* Control never gets here */ + } + + /* If maximizing, it is worth using inline code for speed, doing the type + test once at the start (i.e. keep it out of the loop). Again, keep the + UTF-8 and UCP stuff separate. */ + + else + { + pp = eptr; /* Remember where we started */ + +#ifdef SUPPORT_UNICODE + if (prop_type >= 0) + { + switch(prop_type) + { + case PT_ANY: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + if (prop_fail_result) break; + eptr+= len; + } + break; + + case PT_LAMP: + for (i = min; i < max; i++) + { + int chartype; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + chartype = UCD_CHARTYPE(c); + if ((chartype == ucp_Lu || + chartype == ucp_Ll || + chartype == ucp_Lt) == prop_fail_result) + break; + eptr+= len; + } + break; + + case PT_GC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + if ((UCD_CATEGORY(c) == prop_value) == prop_fail_result) break; + eptr+= len; + } + break; + + case PT_PC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + if ((UCD_CHARTYPE(c) == prop_value) == prop_fail_result) break; + eptr+= len; + } + break; + + case PT_SC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + if ((UCD_SCRIPT(c) == prop_value) == prop_fail_result) break; + eptr+= len; + } + break; + + case PT_ALNUM: + for (i = min; i < max; i++) + { + int category; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + category = UCD_CATEGORY(c); + if ((category == ucp_L || category == ucp_N) == prop_fail_result) + break; + eptr+= len; + } + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + if (prop_fail_result) goto ENDLOOP99; /* Break the loop */ + break; + + default: + if ((UCD_CATEGORY(c) == ucp_Z) == prop_fail_result) + goto ENDLOOP99; /* Break the loop */ + break; + } + eptr+= len; + } + ENDLOOP99: + break; + + case PT_WORD: + for (i = min; i < max; i++) + { + int category; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + category = UCD_CATEGORY(c); + if ((category == ucp_L || category == ucp_N || + c == CHAR_UNDERSCORE) == prop_fail_result) + break; + eptr+= len; + } + break; + + case PT_CLIST: + for (i = min; i < max; i++) + { + const uint32_t *cp; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + cp = PRIV(ucd_caseless_sets) + prop_value; + for (;;) + { + if (c < *cp) + { if (prop_fail_result) break; else goto GOT_MAX; } + if (c == *cp++) + { if (prop_fail_result) goto GOT_MAX; else break; } + } + eptr += len; + } + GOT_MAX: + break; + + case PT_UCNC: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLENTEST(c, eptr, len); + if ((c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT || (c >= 0xa0 && c <= 0xd7ff) || + c >= 0xe000) == prop_fail_result) + break; + eptr += len; + } + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; /* No backtracking */ + + /* After \C in UTF mode, pp might be in the middle of a Unicode + character. Use <= pp to ensure backtracking doesn't go too far. */ + + for(;;) + { + if (eptr <= pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM44); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + if (utf) BACKCHAR(eptr); + } + } + + /* Match extended Unicode grapheme clusters. We will get here only if the + support is in the binary; otherwise a compile-time error occurs. */ + + else if (ctype == OP_EXTUNI) + { + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + else + { + int lgb, rgb; + GETCHARINCTEST(c, eptr); + lgb = UCD_GRAPHBREAK(c); + while (eptr < mb->end_subject) + { + int len = 1; + if (!utf) c = *eptr; else { GETCHARLEN(c, eptr, len); } + rgb = UCD_GRAPHBREAK(c); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + lgb = rgb; + eptr += len; + } + } + CHECK_PARTIAL(); + } + + /* eptr is now past the end of the maximum run */ + + if (possessive) continue; /* No backtracking */ + + /* We use <= pp rather than == pp to detect the start of the run while + backtracking because the use of \C in UTF mode can cause BACKCHAR to + move back past pp. This is just palliative; the use of \C in UTF mode + is fraught with danger. */ + + for(;;) + { + int lgb, rgb; + PCRE2_SPTR fptr; + + if (eptr <= pp) goto TAIL_RECURSE; /* At start of char run */ + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM45); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + + /* Backtracking over an extended grapheme cluster involves inspecting + the previous two characters (if present) to see if a break is + permitted between them. */ + + eptr--; + if (!utf) c = *eptr; else + { + BACKCHAR(eptr); + GETCHAR(c, eptr); + } + rgb = UCD_GRAPHBREAK(c); + + for (;;) + { + if (eptr <= pp) goto TAIL_RECURSE; /* At start of char run */ + fptr = eptr - 1; + if (!utf) c = *fptr; else + { + BACKCHAR(fptr); + GETCHAR(c, fptr); + } + lgb = UCD_GRAPHBREAK(c); + if ((PRIV(ucp_gbtable)[lgb] & (1 << rgb)) == 0) break; + eptr = fptr; + rgb = lgb; + } + } + } + + else +#endif /* SUPPORT_UNICODE */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + switch(ctype) + { + case OP_ANY: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (IS_NEWLINE(eptr)) break; + if (mb->partial != 0 && /* Take care with CRLF partial */ + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + UCHAR21(eptr) == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + break; + + case OP_ALLANY: + if (max < INT_MAX) + { + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + eptr++; + ACROSSCHAR(eptr < mb->end_subject, *eptr, eptr++); + } + } + else + { + eptr = mb->end_subject; /* Unlimited UTF-8 repeat */ + SCHECK_PARTIAL(); + } + break; + + /* The byte case is the same as non-UTF8 */ + + case OP_ANYBYTE: + c = max - min; + if (c > (uint32_t)(mb->end_subject - eptr)) + { + eptr = mb->end_subject; + SCHECK_PARTIAL(); + } + else eptr += c; + break; + + case OP_ANYNL: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c == CHAR_CR) + { + if (++eptr >= mb->end_subject) break; + if (UCHAR21(eptr) == CHAR_LF) eptr++; + } + else + { + if (c != CHAR_LF && + (mb->bsr_convention == PCRE2_BSR_ANYCRLF || + (c != CHAR_VT && c != CHAR_FF && c != CHAR_NEL +#ifndef EBCDIC + && c != 0x2028 && c != 0x2029 +#endif /* Not EBCDIC */ + ))) + break; + eptr += len; + } + } + break; + + case OP_NOT_HSPACE: + case OP_HSPACE: + for (i = min; i < max; i++) + { + BOOL gotspace; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + switch(c) + { + HSPACE_CASES: gotspace = TRUE; break; + default: gotspace = FALSE; break; + } + if (gotspace == (ctype == OP_NOT_HSPACE)) break; + eptr += len; + } + break; + + case OP_NOT_VSPACE: + case OP_VSPACE: + for (i = min; i < max; i++) + { + BOOL gotspace; + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + switch(c) + { + VSPACE_CASES: gotspace = TRUE; break; + default: gotspace = FALSE; break; + } + if (gotspace == (ctype == OP_NOT_VSPACE)) break; + eptr += len; + } + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c < 256 && (mb->ctypes[c] & ctype_digit) != 0) break; + eptr+= len; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c >= 256 ||(mb->ctypes[c] & ctype_digit) == 0) break; + eptr+= len; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c < 256 && (mb->ctypes[c] & ctype_space) != 0) break; + eptr+= len; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c >= 256 ||(mb->ctypes[c] & ctype_space) == 0) break; + eptr+= len; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c < 256 && (mb->ctypes[c] & ctype_word) != 0) break; + eptr+= len; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + int len = 1; + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + GETCHARLEN(c, eptr, len); + if (c >= 256 || (mb->ctypes[c] & ctype_word) == 0) break; + eptr+= len; + } + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + + if (possessive) continue; /* No backtracking */ + + /* After \C in UTF mode, pp might be in the middle of a Unicode + character. Use <= pp to ensure backtracking doesn't go too far. */ + + for(;;) + { + if (eptr <= pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM46); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + BACKCHAR(eptr); + if (ctype == OP_ANYNL && eptr > pp && UCHAR21(eptr) == CHAR_NL && + UCHAR21(eptr - 1) == CHAR_CR) eptr--; + } + } + else +#endif /* SUPPORT_UNICODE */ + /* Not UTF mode */ + { + switch(ctype) + { + case OP_ANY: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (IS_NEWLINE(eptr)) break; + if (mb->partial != 0 && /* Take care with CRLF partial */ + eptr + 1 >= mb->end_subject && + NLBLOCK->nltype == NLTYPE_FIXED && + NLBLOCK->nllen == 2 && + *eptr == NLBLOCK->nl[0]) + { + mb->hitend = TRUE; + if (mb->partial > 1) RRETURN(PCRE2_ERROR_PARTIAL); + } + eptr++; + } + break; + + case OP_ALLANY: + case OP_ANYBYTE: + c = max - min; + if (c > (uint32_t)(mb->end_subject - eptr)) + { + eptr = mb->end_subject; + SCHECK_PARTIAL(); + } + else eptr += c; + break; + + case OP_ANYNL: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + c = *eptr; + if (c == CHAR_CR) + { + if (++eptr >= mb->end_subject) break; + if (*eptr == CHAR_LF) eptr++; + } + else + { + if (c != CHAR_LF && (mb->bsr_convention == PCRE2_BSR_ANYCRLF || + (c != CHAR_VT && c != CHAR_FF && c != CHAR_NEL +#if PCRE2_CODE_UNIT_WIDTH != 8 + && c != 0x2028 && c != 0x2029 +#endif + ))) break; + eptr++; + } + } + break; + + case OP_NOT_HSPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + switch(*eptr) + { + default: eptr++; break; + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + goto ENDLOOP00; + } + } + ENDLOOP00: + break; + + case OP_HSPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + switch(*eptr) + { + default: goto ENDLOOP01; + HSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + HSPACE_MULTIBYTE_CASES: +#endif + eptr++; break; + } + } + ENDLOOP01: + break; + + case OP_NOT_VSPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + switch(*eptr) + { + default: eptr++; break; + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + goto ENDLOOP02; + } + } + ENDLOOP02: + break; + + case OP_VSPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + switch(*eptr) + { + default: goto ENDLOOP03; + VSPACE_BYTE_CASES: +#if PCRE2_CODE_UNIT_WIDTH != 8 + VSPACE_MULTIBYTE_CASES: +#endif + eptr++; break; + } + } + ENDLOOP03: + break; + + case OP_NOT_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_digit) != 0) break; + eptr++; + } + break; + + case OP_DIGIT: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_digit) == 0) break; + eptr++; + } + break; + + case OP_NOT_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_space) != 0) break; + eptr++; + } + break; + + case OP_WHITESPACE: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_space) == 0) break; + eptr++; + } + break; + + case OP_NOT_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (MAX_255(*eptr) && (mb->ctypes[*eptr] & ctype_word) != 0) break; + eptr++; + } + break; + + case OP_WORDCHAR: + for (i = min; i < max; i++) + { + if (eptr >= mb->end_subject) + { + SCHECK_PARTIAL(); + break; + } + if (!MAX_255(*eptr) || (mb->ctypes[*eptr] & ctype_word) == 0) break; + eptr++; + } + break; + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + + if (possessive) continue; /* No backtracking */ + for (;;) + { + if (eptr == pp) goto TAIL_RECURSE; + RMATCH(eptr, ecode, offset_top, mb, eptrb, RM47); + if (rrc != MATCH_NOMATCH) RRETURN(rrc); + eptr--; + if (ctype == OP_ANYNL && eptr > pp && *eptr == CHAR_LF && + eptr[-1] == CHAR_CR) eptr--; + } + } + + /* Control never gets here */ + } + + /* There's been some horrible disaster. Arrival here can only mean there is + something seriously wrong in the code above or the OP_xxx definitions. */ + + default: + RRETURN(PCRE2_ERROR_INTERNAL); + } + + /* Do not stick any code in here without much thought; it is assumed + that "continue" in the code above comes out to here to repeat the main + loop. */ + + } /* End of main loop */ +/* Control never reaches here */ + + +/* When compiling to use the heap rather than the stack for recursive calls to +match(), the RRETURN() macro jumps here. The number that is saved in +frame->Xwhere indicates which label we actually want to return to. */ + +#ifdef HEAP_MATCH_RECURSE +#define LBL(val) case val: goto L_RM##val; +HEAP_RETURN: +switch (frame->Xwhere) + { + LBL( 1) LBL( 2) LBL( 3) LBL( 4) LBL( 5) LBL( 6) LBL( 7) LBL( 8) + LBL( 9) LBL(10) LBL(11) LBL(12) LBL(13) LBL(14) LBL(15) LBL(17) + LBL(19) LBL(24) LBL(25) LBL(26) LBL(27) LBL(29) LBL(31) LBL(33) + LBL(35) LBL(43) LBL(47) LBL(48) LBL(49) LBL(50) LBL(51) LBL(52) + LBL(53) LBL(54) LBL(55) LBL(56) LBL(57) LBL(58) LBL(63) LBL(64) + LBL(65) LBL(66) LBL(68) +#ifdef SUPPORT_WIDE_CHARS + LBL(20) LBL(21) +#endif +#ifdef SUPPORT_UNICODE + LBL(16) LBL(18) + LBL(22) LBL(23) LBL(28) LBL(30) + LBL(32) LBL(34) LBL(42) LBL(46) + LBL(36) LBL(37) LBL(38) LBL(39) LBL(40) LBL(41) LBL(44) LBL(45) + LBL(59) LBL(60) LBL(61) LBL(62) LBL(67) +#endif /* SUPPORT_UNICODE */ + default: + return PCRE2_ERROR_INTERNAL; + } +#undef LBL +#endif /* HEAP_MATCH_RECURSE */ +} + + +/*************************************************************************** +**************************************************************************** + RECURSION IN THE match() FUNCTION + +Undefine all the macros that were defined above to handle this. */ + +#ifdef HEAP_MATCH_RECURSE +#undef eptr +#undef ecode +#undef mstart +#undef offset_top +#undef eptrb +#undef flags + +#undef callpat +#undef charptr +#undef data +#undef next_ecode +#undef pp +#undef prev +#undef saved_eptr + +#undef new_recursive + +#undef cur_is_word +#undef condition +#undef prev_is_word + +#undef ctype +#undef length +#undef max +#undef min +#undef number +#undef offset +#undef op +#undef save_capture_last +#undef save_offset1 +#undef save_offset2 +#undef save_offset3 + +#undef newptrb +#endif /* HEAP_MATCH_RECURSE */ + +/* These two are defined as macros in both cases */ + +#undef fc +#undef fi + +/*************************************************************************** +***************************************************************************/ + + +#ifdef HEAP_MATCH_RECURSE +/************************************************* +* Release allocated heap frames * +*************************************************/ + +/* This function releases all the allocated frames. The base frame is on the +machine stack, and so must not be freed. + +Argument: + frame_base the address of the base frame + mb the match block + +Returns: nothing +*/ + +static void +release_match_heapframes (heapframe *frame_base, match_block *mb) +{ +heapframe *nextframe = frame_base->Xnextframe; +while (nextframe != NULL) + { + heapframe *oldframe = nextframe; + nextframe = nextframe->Xnextframe; + mb->stack_memctl.free(oldframe, mb->stack_memctl.memory_data); + } +} +#endif /* HEAP_MATCH_RECURSE */ + + + +/************************************************* +* Match a Regular Expression * +*************************************************/ + +/* This function applies a compiled pattern to a subject string and picks out +portions of the string if it matches. Two elements in the vector are set for +each substring: the offsets to the start and end of the substring. + +Arguments: + code points to the compiled expression + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + match_data points to a match_data block + mcontext points a PCRE2 context + +Returns: > 0 => success; value is the number of ovector pairs filled + = 0 => success, but ovector is not big enough + -1 => failed to match (PCRE2_ERROR_NOMATCH) + -2 => partial match (PCRE2_ERROR_PARTIAL) + < -2 => some kind of unexpected problem +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_match(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, + PCRE2_SIZE start_offset, uint32_t options, pcre2_match_data *match_data, + pcre2_match_context *mcontext) +{ +int rc; +int ocount; + +const uint8_t *start_bits = NULL; + +const pcre2_real_code *re = (const pcre2_real_code *)code; + +BOOL anchored; +BOOL firstline; +BOOL has_first_cu = FALSE; +BOOL has_req_cu = FALSE; +BOOL startline; +BOOL using_temporary_offsets = FALSE; +BOOL utf; + +PCRE2_UCHAR first_cu = 0; +PCRE2_UCHAR first_cu2 = 0; +PCRE2_UCHAR req_cu = 0; +PCRE2_UCHAR req_cu2 = 0; + +PCRE2_SPTR bumpalong_limit; +PCRE2_SPTR end_subject; +PCRE2_SPTR start_match = subject + start_offset; +PCRE2_SPTR req_cu_ptr = start_match - 1; +PCRE2_SPTR start_partial = NULL; +PCRE2_SPTR match_partial = NULL; + +/* We need to have mb pointing to a match block, because the IS_NEWLINE macro +is used below, and it expects NLBLOCK to be defined as a pointer. */ + +match_block actual_match_block; +match_block *mb = &actual_match_block; + +#ifdef HEAP_MATCH_RECURSE +heapframe frame_zero; +frame_zero.Xprevframe = NULL; /* Marks the top level */ +frame_zero.Xnextframe = NULL; /* None are allocated yet */ +mb->match_frames_base = &frame_zero; +#endif + +/* A length equal to PCRE2_ZERO_TERMINATED implies a zero-terminated +subject string. */ + +if (length == PCRE2_ZERO_TERMINATED) length = PRIV(strlen)(subject); +end_subject = subject + length; + +/* Plausibility checks */ + +if ((options & ~PUBLIC_MATCH_OPTIONS) != 0) return PCRE2_ERROR_BADOPTION; +if (code == NULL || subject == NULL || match_data == NULL) + return PCRE2_ERROR_NULL; +if (start_offset > length) return PCRE2_ERROR_BADOFFSET; + +/* Check that the first field in the block is the magic number. */ + +if (re->magic_number != MAGIC_NUMBER) return PCRE2_ERROR_BADMAGIC; + +/* Check the code unit width. */ + +if ((re->flags & PCRE2_MODE_MASK) != PCRE2_CODE_UNIT_WIDTH/8) + return PCRE2_ERROR_BADMODE; + +/* PCRE2_NOTEMPTY and PCRE2_NOTEMPTY_ATSTART are match-time flags in the +options variable for this function. Users of PCRE2 who are not calling the +function directly would like to have a way of setting these flags, in the same +way that they can set pcre2_compile() flags like PCRE2_NO_AUTOPOSSESS with +constructions like (*NO_AUTOPOSSESS). To enable this, (*NOTEMPTY) and +(*NOTEMPTY_ATSTART) set bits in the pattern's "flag" function which can now be +transferred to the options for this function. The bits are guaranteed to be +adjacent, but do not have the same values. This bit of Boolean trickery assumes +that the match-time bits are not more significant than the flag bits. If by +accident this is not the case, a compile-time division by zero error will +occur. */ + +#define FF (PCRE2_NOTEMPTY_SET|PCRE2_NE_ATST_SET) +#define OO (PCRE2_NOTEMPTY|PCRE2_NOTEMPTY_ATSTART) +options |= (re->flags & FF) / ((FF & (~FF+1)) / (OO & (~OO+1))); +#undef FF +#undef OO + +/* A NULL match context means "use a default context" */ + +if (mcontext == NULL) + mcontext = (pcre2_match_context *)(&PRIV(default_match_context)); + +/* These two settings are used in the code for checking a UTF string that +follows immediately afterwards. Other values in the mb block are used only +during interpretive pcre_match() processing, not when the JIT support is in +use, so they are set up later. */ + +utf = (re->overall_options & PCRE2_UTF) != 0; +mb->partial = ((options & PCRE2_PARTIAL_HARD) != 0)? 2 : + ((options & PCRE2_PARTIAL_SOFT) != 0)? 1 : 0; + +/* Check a UTF string for validity if required. For 8-bit and 16-bit strings, +we must also check that a starting offset does not point into the middle of a +multiunit character. We check only the portion of the subject that is going to +be inspected during matching - from the offset minus the maximum back reference +to the given length. This saves time when a small part of a large subject is +being matched by the use of a starting offset. Note that the maximum lookbehind +is a number of characters, not code units. */ + +#ifdef SUPPORT_UNICODE +if (utf && (options & PCRE2_NO_UTF_CHECK) == 0) + { + PCRE2_SPTR check_subject = start_match; /* start_match includes offset */ + + if (start_offset > 0) + { +#if PCRE2_CODE_UNIT_WIDTH != 32 + unsigned int i; + if (start_match < end_subject && NOT_FIRSTCU(*start_match)) + return PCRE2_ERROR_BADUTFOFFSET; + for (i = re->max_lookbehind; i > 0 && check_subject > subject; i--) + { + check_subject--; + while (check_subject > subject && +#if PCRE2_CODE_UNIT_WIDTH == 8 + (*check_subject & 0xc0) == 0x80) +#else /* 16-bit */ + (*check_subject & 0xfc00) == 0xdc00) +#endif /* PCRE2_CODE_UNIT_WIDTH == 8 */ + check_subject--; + } +#else + /* In the 32-bit library, one code unit equals one character. However, + we cannot just subtract the lookbehind and then compare pointers, because + a very large lookbehind could create an invalid pointer. */ + + if (start_offset >= re->max_lookbehind) + check_subject -= re->max_lookbehind; + else + check_subject = subject; +#endif /* PCRE2_CODE_UNIT_WIDTH != 32 */ + } + + /* Validate the relevant portion of the subject. After an error, adjust the + offset to be an absolute offset in the whole string. */ + + match_data->rc = PRIV(valid_utf)(check_subject, + length - (check_subject - subject), &(match_data->startchar)); + if (match_data->rc != 0) + { + match_data->startchar += check_subject - subject; + return match_data->rc; + } + } +#endif /* SUPPORT_UNICODE */ + +/* It is an error to set an offset limit without setting the flag at compile +time. */ + +if (mcontext->offset_limit != PCRE2_UNSET && + (re->overall_options & PCRE2_USE_OFFSET_LIMIT) == 0) + return PCRE2_ERROR_BADOFFSETLIMIT; + +/* If the pattern was successfully studied with JIT support, run the JIT +executable instead of the rest of this function. Most options must be set at +compile time for the JIT code to be usable. Fallback to the normal code path if +an unsupported option is set or if JIT returns BADOPTION (which means that the +selected normal or partial matching mode was not compiled). */ + +#ifdef SUPPORT_JIT +if (re->executable_jit != NULL && (options & ~PUBLIC_JIT_MATCH_OPTIONS) == 0) + { + rc = pcre2_jit_match(code, subject, length, start_offset, options, + match_data, mcontext); + if (rc != PCRE2_ERROR_JIT_BADOPTION) return rc; + } +#endif + +/* Carry on with non-JIT matching. */ + +anchored = ((re->overall_options | options) & PCRE2_ANCHORED) != 0; +firstline = (re->overall_options & PCRE2_FIRSTLINE) != 0; +startline = (re->flags & PCRE2_STARTLINE) != 0; +bumpalong_limit = (mcontext->offset_limit == PCRE2_UNSET)? + end_subject : subject + mcontext->offset_limit; + +/* Fill in the fields in the match block. */ + +mb->callout = mcontext->callout; +mb->callout_data = mcontext->callout_data; +mb->memctl = mcontext->memctl; +#ifdef HEAP_MATCH_RECURSE +mb->stack_memctl = mcontext->stack_memctl; +#endif + +mb->start_subject = subject; +mb->start_offset = start_offset; +mb->end_subject = end_subject; +mb->hasthen = (re->flags & PCRE2_HASTHEN) != 0; + +mb->moptions = options; /* Match options */ +mb->poptions = re->overall_options; /* Pattern options */ + +mb->ignore_skip_arg = 0; +mb->mark = mb->nomatch_mark = NULL; /* In case never set */ +mb->recursive = NULL; /* No recursion at top level */ +mb->ovecsave_chain = NULL; /* No ovecsave blocks yet */ +mb->hitend = FALSE; + +/* The name table is needed for finding all the numbers associated with a +given name, for condition testing. The code follows the name table. */ + +mb->name_table = (PCRE2_UCHAR *)((uint8_t *)re + sizeof(pcre2_real_code)); +mb->name_count = re->name_count; +mb->name_entry_size = re->name_entry_size; +mb->start_code = mb->name_table + re->name_count * re->name_entry_size; + +/* Limits set in the pattern override the match context only if they are +smaller. */ + +mb->match_limit = (mcontext->match_limit < re->limit_match)? + mcontext->match_limit : re->limit_match; +mb->match_limit_recursion = (mcontext->recursion_limit < re->limit_recursion)? + mcontext->recursion_limit : re->limit_recursion; + +/* Pointers to the individual character tables */ + +mb->lcc = re->tables + lcc_offset; +mb->fcc = re->tables + fcc_offset; +mb->ctypes = re->tables + ctypes_offset; + +/* Process the \R and newline settings. */ + +mb->bsr_convention = re->bsr_convention; +mb->nltype = NLTYPE_FIXED; +switch(re->newline_convention) + { + case PCRE2_NEWLINE_CR: + mb->nllen = 1; + mb->nl[0] = CHAR_CR; + break; + + case PCRE2_NEWLINE_LF: + mb->nllen = 1; + mb->nl[0] = CHAR_NL; + break; + + case PCRE2_NEWLINE_CRLF: + mb->nllen = 2; + mb->nl[0] = CHAR_CR; + mb->nl[1] = CHAR_NL; + break; + + case PCRE2_NEWLINE_ANY: + mb->nltype = NLTYPE_ANY; + break; + + case PCRE2_NEWLINE_ANYCRLF: + mb->nltype = NLTYPE_ANYCRLF; + break; + + default: return PCRE2_ERROR_INTERNAL; + } + +/* If the expression has got more back references than the offsets supplied can +hold, we get a temporary chunk of memory to use during the matching. Otherwise, +we can use the vector supplied. The size of the ovector is three times the +value in the oveccount field. Two-thirds of it is pairs for storing matching +offsets, and the top third is working space. */ + +if (re->top_backref >= match_data->oveccount) + { + ocount = re->top_backref * 3 + 3; + mb->ovector = (PCRE2_SIZE *)(mb->memctl.malloc(ocount * sizeof(PCRE2_SIZE), + mb->memctl.memory_data)); + if (mb->ovector == NULL) return PCRE2_ERROR_NOMEMORY; + using_temporary_offsets = TRUE; + } +else + { + ocount = 3 * match_data->oveccount; + mb->ovector = match_data->ovector; + } + +mb->offset_end = ocount; +mb->offset_max = (2*ocount)/3; + +/* Reset the working variable associated with each extraction. These should +never be used unless previously set, but they get saved and restored, and so we +initialize them to avoid reading uninitialized locations. Also, unset the +offsets for the matched string. This is really just for tidiness with callouts, +in case they inspect these fields. */ + +if (ocount > 0) + { + register PCRE2_SIZE *iptr = mb->ovector + ocount; + register PCRE2_SIZE *iend = iptr - re->top_bracket; + if (iend < mb->ovector + 2) iend = mb->ovector + 2; + while (--iptr >= iend) *iptr = PCRE2_UNSET; + mb->ovector[0] = mb->ovector[1] = PCRE2_UNSET; + } + +/* Set up the first code unit to match, if available. The first_codeunit value +is never set for an anchored regular expression, but the anchoring may be +forced at run time, so we have to test for anchoring. The first code unit may +be unset for an unanchored pattern, of course. If there's no first code unit +there may be a bitmap of possible first characters. */ + +if (!anchored) + { + if ((re->flags & PCRE2_FIRSTSET) != 0) + { + has_first_cu = TRUE; + first_cu = first_cu2 = (PCRE2_UCHAR)(re->first_codeunit); + if ((re->flags & PCRE2_FIRSTCASELESS) != 0) + { + first_cu2 = TABLE_GET(first_cu, mb->fcc, first_cu); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (utf && first_cu > 127) first_cu2 = UCD_OTHERCASE(first_cu); +#endif + } + } + else + if (!startline && (re->flags & PCRE2_FIRSTMAPSET) != 0) + start_bits = re->start_bitmap; + } + +/* For anchored or unanchored matches, there may be a "last known required +character" set. */ + +if ((re->flags & PCRE2_LASTSET) != 0) + { + has_req_cu = TRUE; + req_cu = req_cu2 = (PCRE2_UCHAR)(re->last_codeunit); + if ((re->flags & PCRE2_LASTCASELESS) != 0) + { + req_cu2 = TABLE_GET(req_cu, mb->fcc, req_cu); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH != 8 + if (utf && req_cu > 127) req_cu2 = UCD_OTHERCASE(req_cu); +#endif + } + } + + +/* ==========================================================================*/ + +/* Loop for handling unanchored repeated matching attempts; for anchored regexs +the loop runs just once. */ + +for(;;) + { + PCRE2_SPTR new_start_match; + mb->capture_last = 0; + + /* ----------------- Start of match optimizations ---------------- */ + + /* There are some optimizations that avoid running the match if a known + starting point is not found, or if a known later code unit is not present. + However, there is an option (settable at compile time) that disables these, + for testing and for ensuring that all callouts do actually occur. */ + + if ((re->overall_options & PCRE2_NO_START_OPTIMIZE) == 0) + { + PCRE2_SPTR save_end_subject = end_subject; + + /* If firstline is TRUE, the start of the match is constrained to the first + line of a multiline string. That is, the match must be before or at the + first newline. Implement this by temporarily adjusting end_subject so that + we stop the optimization scans at a newline. If the match fails at the + newline, later code breaks this loop. */ + + if (firstline) + { + PCRE2_SPTR t = start_match; +#ifdef SUPPORT_UNICODE + if (utf) + { + while (t < mb->end_subject && !IS_NEWLINE(t)) + { + t++; + ACROSSCHAR(t < end_subject, *t, t++); + } + } + else +#endif + while (t < mb->end_subject && !IS_NEWLINE(t)) t++; + end_subject = t; + } + + /* Advance to a unique first code unit if there is one. In 8-bit mode, the + use of memchr() gives a big speed up. */ + + if (has_first_cu) + { + PCRE2_UCHAR smc; + if (first_cu != first_cu2) + while (start_match < end_subject && + (smc = UCHAR21TEST(start_match)) != first_cu && smc != first_cu2) + start_match++; + else + { +#if PCRE2_CODE_UNIT_WIDTH != 8 + while (start_match < end_subject && UCHAR21TEST(start_match) != first_cu) + start_match++; +#else + start_match = memchr(start_match, first_cu, end_subject - start_match); + if (start_match == NULL) start_match = end_subject; +#endif + } + } + + /* Or to just after a linebreak for a multiline match */ + + else if (startline) + { + if (start_match > mb->start_subject + start_offset) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + while (start_match < end_subject && !WAS_NEWLINE(start_match)) + { + start_match++; + ACROSSCHAR(start_match < end_subject, *start_match, + start_match++); + } + } + else +#endif + while (start_match < end_subject && !WAS_NEWLINE(start_match)) + start_match++; + + /* If we have just passed a CR and the newline option is ANY or + ANYCRLF, and we are now at a LF, advance the match position by one more + code unit. */ + + if (start_match[-1] == CHAR_CR && + (mb->nltype == NLTYPE_ANY || mb->nltype == NLTYPE_ANYCRLF) && + start_match < end_subject && + UCHAR21TEST(start_match) == CHAR_NL) + start_match++; + } + } + + /* Or to a non-unique first code unit if any have been identified. The + bitmap contains only 256 bits. When code units are 16 or 32 bits wide, all + code units greater than 254 set the 255 bit. */ + + else if (start_bits != NULL) + { + while (start_match < end_subject) + { + register uint32_t c = UCHAR21TEST(start_match); +#if PCRE2_CODE_UNIT_WIDTH != 8 + if (c > 255) c = 255; +#endif + if ((start_bits[c/8] & (1 << (c&7))) != 0) break; + start_match++; + } + } + + /* Restore fudged end_subject */ + + end_subject = save_end_subject; + + /* The following two optimizations are disabled for partial matching. */ + + if (!mb->partial) + { + /* The minimum matching length is a lower bound; no actual string of that + length may actually match the pattern. Although the value is, strictly, + in characters, we treat it as code units to avoid spending too much time + in this optimization. */ + + if (end_subject - start_match < re->minlength) + { + rc = MATCH_NOMATCH; + break; + } + + /* If req_cu is set, we know that that code unit must appear in the + subject for the match to succeed. If the first code unit is set, req_cu + must be later in the subject; otherwise the test starts at the match + point. This optimization can save a huge amount of backtracking in + patterns with nested unlimited repeats that aren't going to match. + Writing separate code for cased/caseless versions makes it go faster, as + does using an autoincrement and backing off on a match. + + HOWEVER: when the subject string is very, very long, searching to its end + can take a long time, and give bad performance on quite ordinary + patterns. This showed up when somebody was matching something like + /^\d+C/ on a 32-megabyte string... so we don't do this when the string is + sufficiently long. */ + + if (has_req_cu && end_subject - start_match < REQ_CU_MAX) + { + register PCRE2_SPTR p = start_match + (has_first_cu? 1:0); + + /* We don't need to repeat the search if we haven't yet reached the + place we found it at last time. */ + + if (p > req_cu_ptr) + { + if (req_cu != req_cu2) + { + while (p < end_subject) + { + register uint32_t pp = UCHAR21INCTEST(p); + if (pp == req_cu || pp == req_cu2) { p--; break; } + } + } + else + { + while (p < end_subject) + { + if (UCHAR21INCTEST(p) == req_cu) { p--; break; } + } + } + + /* If we can't find the required code unit, break the matching loop, + forcing a match failure. */ + + if (p >= end_subject) + { + rc = MATCH_NOMATCH; + break; + } + + /* If we have found the required code unit, save the point where we + found it, so that we don't search again next time round the loop if + the start hasn't passed this code unit yet. */ + + req_cu_ptr = p; + } + } + } + } + + /* ------------ End of start of match optimizations ------------ */ + + /* Give no match if we have passed the bumpalong limit. */ + + if (start_match > bumpalong_limit) + { + rc = MATCH_NOMATCH; + break; + } + + /* OK, we can now run the match. If "hitend" is set afterwards, remember the + first starting point for which a partial match was found. */ + + mb->start_match_ptr = start_match; + mb->start_used_ptr = start_match; + mb->last_used_ptr = start_match; + mb->match_call_count = 0; + mb->match_function_type = 0; + mb->end_offset_top = 0; + mb->skip_arg_count = 0; + rc = match(start_match, mb->start_code, start_match, 2, mb, NULL, 0); + + if (mb->hitend && start_partial == NULL) + { + start_partial = mb->start_used_ptr; + match_partial = start_match; + } + + switch(rc) + { + /* If MATCH_SKIP_ARG reaches this level it means that a MARK that matched + the SKIP's arg was not found. In this circumstance, Perl ignores the SKIP + entirely. The only way we can do that is to re-do the match at the same + point, with a flag to force SKIP with an argument to be ignored. Just + treating this case as NOMATCH does not work because it does not check other + alternatives in patterns such as A(*SKIP:A)B|AC when the subject is AC. */ + + case MATCH_SKIP_ARG: + new_start_match = start_match; + mb->ignore_skip_arg = mb->skip_arg_count; + break; + + /* SKIP passes back the next starting point explicitly, but if it is no + greater than the match we have just done, treat it as NOMATCH. */ + + case MATCH_SKIP: + if (mb->start_match_ptr > start_match) + { + new_start_match = mb->start_match_ptr; + break; + } + /* Fall through */ + + /* NOMATCH and PRUNE advance by one character. THEN at this level acts + exactly like PRUNE. Unset ignore SKIP-with-argument. */ + + case MATCH_NOMATCH: + case MATCH_PRUNE: + case MATCH_THEN: + mb->ignore_skip_arg = 0; + new_start_match = start_match + 1; +#ifdef SUPPORT_UNICODE + if (utf) + ACROSSCHAR(new_start_match < end_subject, *new_start_match, + new_start_match++); +#endif + break; + + /* COMMIT disables the bumpalong, but otherwise behaves as NOMATCH. */ + + case MATCH_COMMIT: + rc = MATCH_NOMATCH; + goto ENDLOOP; + + /* Any other return is either a match, or some kind of error. */ + + default: + goto ENDLOOP; + } + + /* Control reaches here for the various types of "no match at this point" + result. Reset the code to MATCH_NOMATCH for subsequent checking. */ + + rc = MATCH_NOMATCH; + + /* If PCRE2_FIRSTLINE is set, the match must happen before or at the first + newline in the subject (though it may continue over the newline). Therefore, + if we have just failed to match, starting at a newline, do not continue. */ + + if (firstline && IS_NEWLINE(start_match)) break; + + /* Advance to new matching position */ + + start_match = new_start_match; + + /* Break the loop if the pattern is anchored or if we have passed the end of + the subject. */ + + if (anchored || start_match > end_subject) break; + + /* If we have just passed a CR and we are now at a LF, and the pattern does + not contain any explicit matches for \r or \n, and the newline option is CRLF + or ANY or ANYCRLF, advance the match position by one more code unit. In + normal matching start_match will aways be greater than the first position at + this stage, but a failed *SKIP can cause a return at the same point, which is + why the first test exists. */ + + if (start_match > subject + start_offset && + start_match[-1] == CHAR_CR && + start_match < end_subject && + *start_match == CHAR_NL && + (re->flags & PCRE2_HASCRORLF) == 0 && + (mb->nltype == NLTYPE_ANY || + mb->nltype == NLTYPE_ANYCRLF || + mb->nllen == 2)) + start_match++; + + mb->mark = NULL; /* Reset for start of next match attempt */ + } /* End of for(;;) "bumpalong" loop */ + +/* ==========================================================================*/ + +/* When we reach here, one of the stopping conditions is true: + +(1) The match succeeded, either completely, or partially; + +(2) The pattern is anchored or the match was failed by (*COMMIT); + +(3) We are past the end of the subject or the bumpalong limit; + +(4) PCRE2_FIRSTLINE is set and we have failed to match at a newline, because + this option requests that a match occur at or before the first newline in + the subject. + +(5) Some kind of error occurred. + +*/ + +ENDLOOP: + +#ifdef HEAP_MATCH_RECURSE +release_match_heapframes(&frame_zero, mb); +#endif + +/* Release any frames that were saved from recursions. */ + +while (mb->ovecsave_chain != NULL) + { + ovecsave_frame *this = mb->ovecsave_chain; + mb->ovecsave_chain = this->next; + mb->memctl.free(this, mb->memctl.memory_data); + } + +/* Fill in fields that are always returned in the match data. */ + +match_data->code = re; +match_data->subject = subject; +match_data->mark = mb->mark; +match_data->matchedby = PCRE2_MATCHEDBY_INTERPRETER; + +/* Handle a fully successful match. */ + +if (rc == MATCH_MATCH || rc == MATCH_ACCEPT) + { + uint32_t arg_offset_max = 2 * match_data->oveccount; + + /* When the offset vector is big enough to deal with any backreferences, + captured substring offsets will already be set up. In the case where we had + to get some local memory to hold offsets for backreference processing, copy + those that we can. In this case there need not be overflow if certain parts + of the pattern were not used, even though there are more capturing + parentheses than vector slots. */ + + if (using_temporary_offsets) + { + if (arg_offset_max >= 4) + { + memcpy(match_data->ovector + 2, mb->ovector + 2, + (arg_offset_max - 2) * sizeof(PCRE2_SIZE)); + } + if (mb->end_offset_top > arg_offset_max) mb->capture_last |= OVFLBIT; + mb->memctl.free(mb->ovector, mb->memctl.memory_data); + } + + /* Set the return code to the number of captured strings, or 0 if there were + too many to fit into the ovector. */ + + match_data->rc = ((mb->capture_last & OVFLBIT) != 0)? + 0 : mb->end_offset_top/2; + + /* If there is space in the offset vector, set any pairs that follow the + highest-numbered captured string but are less than the number of capturing + groups in the pattern (and are within the ovector) to PCRE2_UNSET. It is + documented that this happens. In earlier versions, the whole set of potential + capturing offsets was initialized each time round the loop, but this is + handled differently now. "Gaps" are set to PCRE2_UNSET dynamically instead + (this fixed a bug). Thus, it is only those at the end that need setting here. + We can't just mark them all unset at the start of the whole thing because + they may get set in one branch that is not the final matching branch. */ + + if (mb->end_offset_top/2 <= re->top_bracket) + { + register PCRE2_SIZE *iptr, *iend; + int resetcount = re->top_bracket + 1; + if (resetcount > match_data->oveccount) resetcount = match_data->oveccount; + iptr = match_data->ovector + mb->end_offset_top; + iend = match_data->ovector + 2 * resetcount; + while (iptr < iend) *iptr++ = PCRE2_UNSET; + } + + /* If there is space, set up the whole thing as substring 0. The value of + mb->start_match_ptr might be modified if \K was encountered on the success + matching path. */ + + if (match_data->oveccount < 1) rc = 0; else + { + match_data->ovector[0] = mb->start_match_ptr - mb->start_subject; + match_data->ovector[1] = mb->end_match_ptr - mb->start_subject; + } + + /* Set the remaining returned values */ + + match_data->startchar = start_match - subject; + match_data->leftchar = mb->start_used_ptr - subject; + match_data->rightchar = ((mb->last_used_ptr > mb->end_match_ptr)? + mb->last_used_ptr : mb->end_match_ptr) - subject; + return match_data->rc; + } + +/* Control gets here if there has been a partial match, an error, or if the +overall match attempt has failed at all permitted starting positions. Any mark +data is in the nomatch_mark field. */ + +match_data->mark = mb->nomatch_mark; + +/* For anything other than nomatch or partial match, just return the code. */ + +if (rc != MATCH_NOMATCH && rc != PCRE2_ERROR_PARTIAL) + match_data->rc = rc; + +/* Else handle a partial match. */ + +else if (match_partial != NULL) + { + if (match_data->oveccount > 0) + { + match_data->ovector[0] = match_partial - subject; + match_data->ovector[1] = end_subject - subject; + } + match_data->startchar = match_partial - subject; + match_data->leftchar = start_partial - subject; + match_data->rightchar = end_subject - subject; + match_data->rc = PCRE2_ERROR_PARTIAL; + } + +/* Else this is the classic nomatch case. */ + +else match_data->rc = PCRE2_ERROR_NOMATCH; + +/* Free any temporary offsets. */ + +if (using_temporary_offsets) + mb->memctl.free(mb->ovector, mb->memctl.memory_data); +return match_data->rc; +} + +/* End of pcre2_match.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2_match_data.c b/ProcessHacker/pcre/pcre2_match_data.c new file mode 100644 index 0000000..8b3ffcd --- /dev/null +++ b/ProcessHacker/pcre/pcre2_match_data.c @@ -0,0 +1,147 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + + +/************************************************* +* Create a match data block given ovector size * +*************************************************/ + +/* A minimum of 1 is imposed on the number of ovector triplets. */ + +PCRE2_EXP_DEFN pcre2_match_data * PCRE2_CALL_CONVENTION +pcre2_match_data_create(uint32_t oveccount, pcre2_general_context *gcontext) +{ +pcre2_match_data *yield; +if (oveccount < 1) oveccount = 1; +yield = PRIV(memctl_malloc)( + sizeof(pcre2_match_data) + 3*oveccount*sizeof(PCRE2_SIZE), + (pcre2_memctl *)gcontext); +if (yield == NULL) return NULL; +yield->oveccount = oveccount; +return yield; +} + + + +/************************************************* +* Create a match data block using pattern data * +*************************************************/ + +/* If no context is supplied, use the memory allocator from the code. */ + +PCRE2_EXP_DEFN pcre2_match_data * PCRE2_CALL_CONVENTION +pcre2_match_data_create_from_pattern(const pcre2_code *code, + pcre2_general_context *gcontext) +{ +if (gcontext == NULL) gcontext = (pcre2_general_context *)code; +return pcre2_match_data_create(((pcre2_real_code *)code)->top_bracket + 1, + gcontext); +} + + + +/************************************************* +* Free a match data block * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_match_data_free(pcre2_match_data *match_data) +{ +if (match_data != NULL) + match_data->memctl.free(match_data, match_data->memctl.memory_data); +} + + + +/************************************************* +* Get last mark in match * +*************************************************/ + +PCRE2_EXP_DEFN PCRE2_SPTR PCRE2_CALL_CONVENTION +pcre2_get_mark(pcre2_match_data *match_data) +{ +return match_data->mark; +} + + + +/************************************************* +* Get pointer to ovector * +*************************************************/ + +PCRE2_EXP_DEFN PCRE2_SIZE * PCRE2_CALL_CONVENTION +pcre2_get_ovector_pointer(pcre2_match_data *match_data) +{ +return match_data->ovector; +} + + + +/************************************************* +* Get number of ovector slots * +*************************************************/ + +PCRE2_EXP_DEFN uint32_t PCRE2_CALL_CONVENTION +pcre2_get_ovector_count(pcre2_match_data *match_data) +{ +return match_data->oveccount; +} + + + +/************************************************* +* Get starting code unit in match * +*************************************************/ + +PCRE2_EXP_DEFN PCRE2_SIZE PCRE2_CALL_CONVENTION +pcre2_get_startchar(pcre2_match_data *match_data) +{ +return match_data->startchar; +} + +/* End of pcre2_match_data.c */ diff --git a/ProcessHacker/pcre/pcre2_newline.c b/ProcessHacker/pcre/pcre2_newline.c new file mode 100644 index 0000000..2aab03a --- /dev/null +++ b/ProcessHacker/pcre/pcre2_newline.c @@ -0,0 +1,243 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module contains internal functions for testing newlines when more than +one kind of newline is to be recognized. When a newline is found, its length is +returned. In principle, we could implement several newline "types", each +referring to a different set of newline characters. At present, PCRE2 supports +only NLTYPE_FIXED, which gets handled without these functions, NLTYPE_ANYCRLF, +and NLTYPE_ANY. The full list of Unicode newline characters is taken from +http://unicode.org/unicode/reports/tr18/. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + + +/************************************************* +* Check for newline at given position * +*************************************************/ + +/* This function is called only via the IS_NEWLINE macro, which does so only +when the newline type is NLTYPE_ANY or NLTYPE_ANYCRLF. The case of a fixed +newline (NLTYPE_FIXED) is handled inline. It is guaranteed that the code unit +pointed to by ptr is less than the end of the string. + +Arguments: + ptr pointer to possible newline + type the newline type + endptr pointer to the end of the string + lenptr where to return the length + utf TRUE if in utf mode + +Returns: TRUE or FALSE +*/ + +BOOL +PRIV(is_newline)(PCRE2_SPTR ptr, uint32_t type, PCRE2_SPTR endptr, + uint32_t *lenptr, BOOL utf) +{ +uint32_t c; + +#ifdef SUPPORT_UNICODE +if (utf) { GETCHAR(c, ptr); } else c = *ptr; +#else +(void)utf; +c = *ptr; +#endif /* SUPPORT_UNICODE */ + +if (type == NLTYPE_ANYCRLF) switch(c) + { + case CHAR_LF: + *lenptr = 1; + return TRUE; + + case CHAR_CR: + *lenptr = (ptr < endptr - 1 && ptr[1] == CHAR_LF)? 2 : 1; + return TRUE; + + default: + return FALSE; + } + +/* NLTYPE_ANY */ + +else switch(c) + { +#ifdef EBCDIC + case CHAR_NEL: +#endif + case CHAR_LF: + case CHAR_VT: + case CHAR_FF: + *lenptr = 1; + return TRUE; + + case CHAR_CR: + *lenptr = (ptr < endptr - 1 && ptr[1] == CHAR_LF)? 2 : 1; + return TRUE; + +#ifndef EBCDIC +#if PCRE2_CODE_UNIT_WIDTH == 8 + case CHAR_NEL: + *lenptr = utf? 2 : 1; + return TRUE; + + case 0x2028: /* LS */ + case 0x2029: /* PS */ + *lenptr = 3; + return TRUE; + +#else /* 16-bit or 32-bit code units */ + case CHAR_NEL: + case 0x2028: /* LS */ + case 0x2029: /* PS */ + *lenptr = 1; + return TRUE; +#endif +#endif /* Not EBCDIC */ + + default: + return FALSE; + } +} + + + +/************************************************* +* Check for newline at previous position * +*************************************************/ + +/* This function is called only via the WAS_NEWLINE macro, which does so only +when the newline type is NLTYPE_ANY or NLTYPE_ANYCRLF. The case of a fixed +newline (NLTYPE_FIXED) is handled inline. It is guaranteed that the initial +value of ptr is greater than the start of the string that is being processed. + +Arguments: + ptr pointer to possible newline + type the newline type + startptr pointer to the start of the string + lenptr where to return the length + utf TRUE if in utf mode + +Returns: TRUE or FALSE +*/ + +BOOL +PRIV(was_newline)(PCRE2_SPTR ptr, uint32_t type, PCRE2_SPTR startptr, + uint32_t *lenptr, BOOL utf) +{ +uint32_t c; +ptr--; + +#ifdef SUPPORT_UNICODE +if (utf) + { + BACKCHAR(ptr); + GETCHAR(c, ptr); + } +else c = *ptr; +#else +(void)utf; +c = *ptr; +#endif /* SUPPORT_UNICODE */ + +if (type == NLTYPE_ANYCRLF) switch(c) + { + case CHAR_LF: + *lenptr = (ptr > startptr && ptr[-1] == CHAR_CR)? 2 : 1; + return TRUE; + + case CHAR_CR: + *lenptr = 1; + return TRUE; + + default: + return FALSE; + } + +/* NLTYPE_ANY */ + +else switch(c) + { + case CHAR_LF: + *lenptr = (ptr > startptr && ptr[-1] == CHAR_CR)? 2 : 1; + return TRUE; + +#ifdef EBCDIC + case CHAR_NEL: +#endif + case CHAR_VT: + case CHAR_FF: + case CHAR_CR: + *lenptr = 1; + return TRUE; + +#ifndef EBCDIC +#if PCRE2_CODE_UNIT_WIDTH == 8 + case CHAR_NEL: + *lenptr = utf? 2 : 1; + return TRUE; + + case 0x2028: /* LS */ + case 0x2029: /* PS */ + *lenptr = 3; + return TRUE; + +#else /* 16-bit or 32-bit code units */ + case CHAR_NEL: + case 0x2028: /* LS */ + case 0x2029: /* PS */ + *lenptr = 1; + return TRUE; +#endif +#endif /* Not EBCDIC */ + + default: + return FALSE; + } +} + +/* End of pcre2_newline.c */ diff --git a/ProcessHacker/pcre/pcre2_ord2utf.c b/ProcessHacker/pcre/pcre2_ord2utf.c new file mode 100644 index 0000000..482e30a --- /dev/null +++ b/ProcessHacker/pcre/pcre2_ord2utf.c @@ -0,0 +1,120 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This file contains a function that converts a Unicode character code point +into a UTF string. The behaviour is different for each code unit width. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + +/* If SUPPORT_UNICODE is not defined, this function will never be called. +Supply a dummy function because some compilers do not like empty source +modules. */ + +#ifndef SUPPORT_UNICODE +unsigned int +PRIV(ord2utf)(uint32_t cvalue, PCRE2_UCHAR *buffer) +{ +(void)(cvalue); +(void)(buffer); +return 0; +} +#else /* SUPPORT_UNICODE */ + + +/************************************************* +* Convert code point to UTF * +*************************************************/ + +/* +Arguments: + cvalue the character value + buffer pointer to buffer for result + +Returns: number of code units placed in the buffer +*/ + +unsigned int +PRIV(ord2utf)(uint32_t cvalue, PCRE2_UCHAR *buffer) +{ +/* Convert to UTF-8 */ + +#if PCRE2_CODE_UNIT_WIDTH == 8 +register int i, j; +for (i = 0; i < PRIV(utf8_table1_size); i++) + if ((int)cvalue <= PRIV(utf8_table1)[i]) break; +buffer += i; +for (j = i; j > 0; j--) + { + *buffer-- = 0x80 | (cvalue & 0x3f); + cvalue >>= 6; + } +*buffer = PRIV(utf8_table2)[i] | cvalue; +return i + 1; + +/* Convert to UTF-16 */ + +#elif PCRE2_CODE_UNIT_WIDTH == 16 +if (cvalue <= 0xffff) + { + *buffer = (PCRE2_UCHAR)cvalue; + return 1; + } +cvalue -= 0x10000; +*buffer++ = 0xd800 | (cvalue >> 10); +*buffer = 0xdc00 | (cvalue & 0x3ff); +return 2; + +/* Convert to UTF-32 */ + +#else +*buffer = (PCRE2_UCHAR)cvalue; +return 1; +#endif +} +#endif /* SUPPORT_UNICODE */ + +/* End of pcre_ord2utf.c */ diff --git a/ProcessHacker/pcre/pcre2_pattern_info.c b/ProcessHacker/pcre/pcre2_pattern_info.c new file mode 100644 index 0000000..39c1162 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_pattern_info.c @@ -0,0 +1,410 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + +/************************************************* +* Return info about compiled pattern * +*************************************************/ + +/* +Arguments: + code points to compiled code + what what information is required + where where to put the information; if NULL, return length + +Returns: 0 when data returned + > 0 when length requested + < 0 on error or unset value +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_pattern_info(const pcre2_code *code, uint32_t what, void *where) +{ +const pcre2_real_code *re = (pcre2_real_code *)code; + +if (where == NULL) /* Requests field length */ + { + switch(what) + { + case PCRE2_INFO_ALLOPTIONS: + case PCRE2_INFO_ARGOPTIONS: + case PCRE2_INFO_BACKREFMAX: + case PCRE2_INFO_BSR: + case PCRE2_INFO_CAPTURECOUNT: + case PCRE2_INFO_FIRSTCODETYPE: + case PCRE2_INFO_FIRSTCODEUNIT: + case PCRE2_INFO_HASBACKSLASHC: + case PCRE2_INFO_HASCRORLF: + case PCRE2_INFO_JCHANGED: + case PCRE2_INFO_LASTCODETYPE: + case PCRE2_INFO_LASTCODEUNIT: + case PCRE2_INFO_MATCHEMPTY: + case PCRE2_INFO_MATCHLIMIT: + case PCRE2_INFO_MAXLOOKBEHIND: + case PCRE2_INFO_MINLENGTH: + case PCRE2_INFO_NAMEENTRYSIZE: + case PCRE2_INFO_NAMECOUNT: + case PCRE2_INFO_NEWLINE: + case PCRE2_INFO_RECURSIONLIMIT: + return sizeof(uint32_t); + + case PCRE2_INFO_FIRSTBITMAP: + return sizeof(const uint8_t *); + + case PCRE2_INFO_JITSIZE: + case PCRE2_INFO_SIZE: + return sizeof(size_t); + + case PCRE2_INFO_NAMETABLE: + return sizeof(PCRE2_SPTR); + } + } + +if (re == NULL) return PCRE2_ERROR_NULL; + +/* Check that the first field in the block is the magic number. If it is not, +return with PCRE2_ERROR_BADMAGIC. */ + +if (re->magic_number != MAGIC_NUMBER) return PCRE2_ERROR_BADMAGIC; + +/* Check that this pattern was compiled in the correct bit mode */ + +if ((re->flags & (PCRE2_CODE_UNIT_WIDTH/8)) == 0) return PCRE2_ERROR_BADMODE; + +switch(what) + { + case PCRE2_INFO_ALLOPTIONS: + *((uint32_t *)where) = re->overall_options; + break; + + case PCRE2_INFO_ARGOPTIONS: + *((uint32_t *)where) = re->compile_options; + break; + + case PCRE2_INFO_BACKREFMAX: + *((uint32_t *)where) = re->top_backref; + break; + + case PCRE2_INFO_BSR: + *((uint32_t *)where) = re->bsr_convention; + break; + + case PCRE2_INFO_CAPTURECOUNT: + *((uint32_t *)where) = re->top_bracket; + break; + + case PCRE2_INFO_FIRSTCODETYPE: + *((uint32_t *)where) = ((re->flags & PCRE2_FIRSTSET) != 0)? 1 : + ((re->flags & PCRE2_STARTLINE) != 0)? 2 : 0; + break; + + case PCRE2_INFO_FIRSTCODEUNIT: + *((uint32_t *)where) = ((re->flags & PCRE2_FIRSTSET) != 0)? + re->first_codeunit : 0; + break; + + case PCRE2_INFO_FIRSTBITMAP: + *((const uint8_t **)where) = ((re->flags & PCRE2_FIRSTMAPSET) != 0)? + &(re->start_bitmap[0]) : NULL; + break; + + case PCRE2_INFO_HASBACKSLASHC: + *((uint32_t *)where) = (re->flags & PCRE2_HASBKC) != 0; + break; + + case PCRE2_INFO_HASCRORLF: + *((uint32_t *)where) = (re->flags & PCRE2_HASCRORLF) != 0; + break; + + case PCRE2_INFO_JCHANGED: + *((uint32_t *)where) = (re->flags & PCRE2_JCHANGED) != 0; + break; + + case PCRE2_INFO_JITSIZE: +#ifdef SUPPORT_JIT + *((size_t *)where) = (re->executable_jit != NULL)? + PRIV(jit_get_size)(re->executable_jit) : 0; +#else + *((size_t *)where) = 0; +#endif + break; + + case PCRE2_INFO_LASTCODETYPE: + *((uint32_t *)where) = ((re->flags & PCRE2_LASTSET) != 0)? 1 : 0; + break; + + case PCRE2_INFO_LASTCODEUNIT: + *((uint32_t *)where) = ((re->flags & PCRE2_LASTSET) != 0)? + re->last_codeunit : 0; + break; + + case PCRE2_INFO_MATCHEMPTY: + *((uint32_t *)where) = (re->flags & PCRE2_MATCH_EMPTY) != 0; + break; + + case PCRE2_INFO_MATCHLIMIT: + *((uint32_t *)where) = re->limit_match; + if (re->limit_match == UINT32_MAX) return PCRE2_ERROR_UNSET; + break; + + case PCRE2_INFO_MAXLOOKBEHIND: + *((uint32_t *)where) = re->max_lookbehind; + break; + + case PCRE2_INFO_MINLENGTH: + *((uint32_t *)where) = re->minlength; + break; + + case PCRE2_INFO_NAMEENTRYSIZE: + *((uint32_t *)where) = re->name_entry_size; + break; + + case PCRE2_INFO_NAMECOUNT: + *((uint32_t *)where) = re->name_count; + break; + + case PCRE2_INFO_NAMETABLE: + *((PCRE2_SPTR *)where) = (PCRE2_SPTR)((char *)re + sizeof(pcre2_real_code)); + break; + + case PCRE2_INFO_NEWLINE: + *((uint32_t *)where) = re->newline_convention; + break; + + case PCRE2_INFO_RECURSIONLIMIT: + *((uint32_t *)where) = re->limit_recursion; + if (re->limit_recursion == UINT32_MAX) return PCRE2_ERROR_UNSET; + break; + + case PCRE2_INFO_SIZE: + *((size_t *)where) = re->blocksize; + break; + + default: return PCRE2_ERROR_BADOPTION; + } + +return 0; +} + + + +/************************************************* +* Callout enumerator * +*************************************************/ + +/* +Arguments: + code points to compiled code + callback function called for each callout block + callout_data user data passed to the callback + +Returns: 0 when successfully completed + < 0 on local error + != 0 for callback error +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_callout_enumerate(const pcre2_code *code, + int (*callback)(pcre2_callout_enumerate_block *, void *), void *callout_data) +{ +pcre2_real_code *re = (pcre2_real_code *)code; +pcre2_callout_enumerate_block cb; +PCRE2_SPTR cc; +#ifdef SUPPORT_UNICODE +BOOL utf = (re->overall_options & PCRE2_UTF) != 0; +#endif + +if (re == NULL) return PCRE2_ERROR_NULL; + +/* Check that the first field in the block is the magic number. If it is not, +return with PCRE2_ERROR_BADMAGIC. */ + +if (re->magic_number != MAGIC_NUMBER) return PCRE2_ERROR_BADMAGIC; + +/* Check that this pattern was compiled in the correct bit mode */ + +if ((re->flags & (PCRE2_CODE_UNIT_WIDTH/8)) == 0) return PCRE2_ERROR_BADMODE; + +cb.version = 0; +cc = (PCRE2_SPTR)((uint8_t *)re + sizeof(pcre2_real_code)) + + re->name_count * re->name_entry_size; + +while (TRUE) + { + int rc; + switch (*cc) + { + case OP_END: + return 0; + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_STAR: + case OP_MINSTAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_QUERY: + case OP_MINQUERY: + case OP_UPTO: + case OP_MINUPTO: + case OP_EXACT: + case OP_POSSTAR: + case OP_POSPLUS: + case OP_POSQUERY: + case OP_POSUPTO: + case OP_STARI: + case OP_MINSTARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_UPTOI: + case OP_MINUPTOI: + case OP_EXACTI: + case OP_POSSTARI: + case OP_POSPLUSI: + case OP_POSQUERYI: + case OP_POSUPTOI: + case OP_NOTSTAR: + case OP_NOTMINSTAR: + case OP_NOTPLUS: + case OP_NOTMINPLUS: + case OP_NOTQUERY: + case OP_NOTMINQUERY: + case OP_NOTUPTO: + case OP_NOTMINUPTO: + case OP_NOTEXACT: + case OP_NOTPOSSTAR: + case OP_NOTPOSPLUS: + case OP_NOTPOSQUERY: + case OP_NOTPOSUPTO: + case OP_NOTSTARI: + case OP_NOTMINSTARI: + case OP_NOTPLUSI: + case OP_NOTMINPLUSI: + case OP_NOTQUERYI: + case OP_NOTMINQUERYI: + case OP_NOTUPTOI: + case OP_NOTMINUPTOI: + case OP_NOTEXACTI: + case OP_NOTPOSSTARI: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERYI: + case OP_NOTPOSUPTOI: + cc += PRIV(OP_lengths)[*cc]; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEEXACT: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSPLUS: + case OP_TYPEPOSQUERY: + case OP_TYPEPOSUPTO: + cc += PRIV(OP_lengths)[*cc]; +#ifdef SUPPORT_UNICODE + if (cc[-1] == OP_PROP || cc[-1] == OP_NOTPROP) cc += 2; +#endif + break; + +#if defined SUPPORT_UNICODE || PCRE2_CODE_UNIT_WIDTH != 8 + case OP_XCLASS: + cc += GET(cc, 1); + break; +#endif + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + cc += PRIV(OP_lengths)[*cc] + cc[1]; + break; + + case OP_CALLOUT: + cb.pattern_position = GET(cc, 1); + cb.next_item_length = GET(cc, 1 + LINK_SIZE); + cb.callout_number = cc[1 + 2*LINK_SIZE]; + cb.callout_string_offset = 0; + cb.callout_string_length = 0; + cb.callout_string = NULL; + rc = callback(&cb, callout_data); + if (rc != 0) return rc; + cc += PRIV(OP_lengths)[*cc]; + break; + + case OP_CALLOUT_STR: + cb.pattern_position = GET(cc, 1); + cb.next_item_length = GET(cc, 1 + LINK_SIZE); + cb.callout_number = 0; + cb.callout_string_offset = GET(cc, 1 + 3*LINK_SIZE); + cb.callout_string_length = + GET(cc, 1 + 2*LINK_SIZE) - (1 + 4*LINK_SIZE) - 2; + cb.callout_string = cc + (1 + 4*LINK_SIZE) + 1; + rc = callback(&cb, callout_data); + if (rc != 0) return rc; + cc += GET(cc, 1 + 2*LINK_SIZE); + break; + + default: + cc += PRIV(OP_lengths)[*cc]; + break; + } + } +} + +/* End of pcre2_pattern_info.c */ diff --git a/ProcessHacker/pcre/pcre2_serialize.c b/ProcessHacker/pcre/pcre2_serialize.c new file mode 100644 index 0000000..d787b25 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_serialize.c @@ -0,0 +1,258 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains functions for serializing and deserializing +a sequence of compiled codes. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "pcre2_internal.h" + +/* Magic number to provide a small check against being handed junk. */ + +#define SERIALIZED_DATA_MAGIC 0x50523253u + +/* Deserialization is limited to the current PCRE version and +character width. */ + +#define SERIALIZED_DATA_VERSION \ + ((PCRE2_MAJOR) | ((PCRE2_MINOR) << 16)) + +#define SERIALIZED_DATA_CONFIG \ + (sizeof(PCRE2_UCHAR) | ((sizeof(void*)) << 8) | ((sizeof(PCRE2_SIZE)) << 16)) + + + +/************************************************* +* Serialize compiled patterns * +*************************************************/ + +PCRE2_EXP_DEFN int32_t PCRE2_CALL_CONVENTION +pcre2_serialize_encode(const pcre2_code **codes, int32_t number_of_codes, + uint8_t **serialized_bytes, PCRE2_SIZE *serialized_size, + pcre2_general_context *gcontext) +{ +uint8_t *bytes; +uint8_t *dst_bytes; +int32_t i; +PCRE2_SIZE total_size; +const pcre2_real_code *re; +const uint8_t *tables; +pcre2_serialized_data *data; + +const pcre2_memctl *memctl = (gcontext != NULL) ? + &gcontext->memctl : &PRIV(default_compile_context).memctl; + +if (codes == NULL || serialized_bytes == NULL || serialized_size == NULL) + return PCRE2_ERROR_NULL; + +if (number_of_codes <= 0) return PCRE2_ERROR_BADDATA; + +/* Compute total size. */ +total_size = sizeof(pcre2_serialized_data) + tables_length; +tables = NULL; + +for (i = 0; i < number_of_codes; i++) + { + if (codes[i] == NULL) return PCRE2_ERROR_NULL; + re = (const pcre2_real_code *)(codes[i]); + if (re->magic_number != MAGIC_NUMBER) return PCRE2_ERROR_BADMAGIC; + if (tables == NULL) + tables = re->tables; + else if (tables != re->tables) + return PCRE2_ERROR_MIXEDTABLES; + total_size += re->blocksize; + } + +/* Initialize the byte stream. */ +bytes = memctl->malloc(total_size + sizeof(pcre2_memctl), memctl->memory_data); +if (bytes == NULL) return PCRE2_ERROR_NOMEMORY; + +/* The controller is stored as a hidden parameter. */ +memcpy(bytes, memctl, sizeof(pcre2_memctl)); +bytes += sizeof(pcre2_memctl); + +data = (pcre2_serialized_data *)bytes; +data->magic = SERIALIZED_DATA_MAGIC; +data->version = SERIALIZED_DATA_VERSION; +data->config = SERIALIZED_DATA_CONFIG; +data->number_of_codes = number_of_codes; + +/* Copy all compiled code data. */ +dst_bytes = bytes + sizeof(pcre2_serialized_data); +memcpy(dst_bytes, tables, tables_length); +dst_bytes += tables_length; + +for (i = 0; i < number_of_codes; i++) + { + re = (const pcre2_real_code *)(codes[i]); + memcpy(dst_bytes, (char *)re, re->blocksize); + dst_bytes += re->blocksize; + } + +*serialized_bytes = bytes; +*serialized_size = total_size; +return number_of_codes; +} + + +/************************************************* +* Deserialize compiled patterns * +*************************************************/ + +PCRE2_EXP_DEFN int32_t PCRE2_CALL_CONVENTION +pcre2_serialize_decode(pcre2_code **codes, int32_t number_of_codes, + const uint8_t *bytes, pcre2_general_context *gcontext) +{ +const pcre2_serialized_data *data = (const pcre2_serialized_data *)bytes; +const pcre2_memctl *memctl = (gcontext != NULL) ? + &gcontext->memctl : &PRIV(default_compile_context).memctl; + +const uint8_t *src_bytes; +pcre2_real_code *dst_re; +uint8_t *tables; +int32_t i, j; + +/* Sanity checks. */ + +if (data == NULL || codes == NULL) return PCRE2_ERROR_NULL; +if (number_of_codes <= 0) return PCRE2_ERROR_BADDATA; +if (data->magic != SERIALIZED_DATA_MAGIC) return PCRE2_ERROR_BADMAGIC; +if (data->version != SERIALIZED_DATA_VERSION) return PCRE2_ERROR_BADMODE; +if (data->config != SERIALIZED_DATA_CONFIG) return PCRE2_ERROR_BADMODE; + +if (number_of_codes > data->number_of_codes) + number_of_codes = data->number_of_codes; + +src_bytes = bytes + sizeof(pcre2_serialized_data); + +/* Decode tables. The reference count for the tables is stored immediately +following them. */ + +tables = memctl->malloc(tables_length + sizeof(PCRE2_SIZE), memctl->memory_data); +if (tables == NULL) return PCRE2_ERROR_NOMEMORY; + +memcpy(tables, src_bytes, tables_length); +*(PCRE2_SIZE *)(tables + tables_length) = number_of_codes; +src_bytes += tables_length; + +/* Decode the byte stream. We must not try to read the size from the compiled +code block in the stream, because it might be unaligned, which causes errors on +hardware such as Sparc-64 that doesn't like unaligned memory accesses. The type +of the blocksize field is given its own name to ensure that it is the same here +as in the block. */ + +for (i = 0; i < number_of_codes; i++) + { + CODE_BLOCKSIZE_TYPE blocksize; + memcpy(&blocksize, src_bytes + offsetof(pcre2_real_code, blocksize), + sizeof(CODE_BLOCKSIZE_TYPE)); + + /* The allocator provided by gcontext replaces the original one. */ + + dst_re = (pcre2_real_code *)PRIV(memctl_malloc)(blocksize, + (pcre2_memctl *)gcontext); + if (dst_re == NULL) + { + memctl->free(tables, memctl->memory_data); + for (j = 0; j < i; j++) + { + memctl->free(codes[j], memctl->memory_data); + codes[j] = NULL; + } + return PCRE2_ERROR_NOMEMORY; + } + + /* The new allocator must be preserved. */ + + memcpy(((uint8_t *)dst_re) + sizeof(pcre2_memctl), + src_bytes + sizeof(pcre2_memctl), blocksize - sizeof(pcre2_memctl)); + + /* At the moment only one table is supported. */ + + dst_re->tables = tables; + dst_re->executable_jit = NULL; + dst_re->flags |= PCRE2_DEREF_TABLES; + + codes[i] = dst_re; + src_bytes += blocksize; + } + +return number_of_codes; +} + + +/************************************************* +* Get the number of serialized patterns * +*************************************************/ + +PCRE2_EXP_DEFN int32_t PCRE2_CALL_CONVENTION +pcre2_serialize_get_number_of_codes(const uint8_t *bytes) +{ +const pcre2_serialized_data *data = (const pcre2_serialized_data *)bytes; + +if (data == NULL) return PCRE2_ERROR_NULL; +if (data->magic != SERIALIZED_DATA_MAGIC) return PCRE2_ERROR_BADMAGIC; +if (data->version != SERIALIZED_DATA_VERSION) return PCRE2_ERROR_BADMODE; +if (data->config != SERIALIZED_DATA_CONFIG) return PCRE2_ERROR_BADMODE; + +return data->number_of_codes; +} + + +/************************************************* +* Free the allocated stream * +*************************************************/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_serialize_free(uint8_t *bytes) +{ +if (bytes != NULL) + { + pcre2_memctl *memctl = (pcre2_memctl *)(bytes - sizeof(pcre2_memctl)); + memctl->free(memctl, memctl->memory_data); + } +} + +/* End of pcre2_serialize.c */ diff --git a/ProcessHacker/pcre/pcre2_string_utils.c b/ProcessHacker/pcre/pcre2_string_utils.c new file mode 100644 index 0000000..feaa098 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_string_utils.c @@ -0,0 +1,201 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains internal functions for comparing and finding the length +of strings. These are used instead of strcmp() etc because the standard +functions work only on 8-bit data. */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + +/************************************************* +* Compare two zero-terminated PCRE2 strings * +*************************************************/ + +/* +Arguments: + str1 first string + str2 second string + +Returns: 0, 1, or -1 +*/ + +int +PRIV(strcmp)(PCRE2_SPTR str1, PCRE2_SPTR str2) +{ +PCRE2_UCHAR c1, c2; +while (*str1 != '\0' || *str2 != '\0') + { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) return ((c1 > c2) << 1) - 1; + } +return 0; +} + + +/************************************************* +* Compare zero-terminated PCRE2 & 8-bit strings * +*************************************************/ + +/* As the 8-bit string is almost always a literal, its type is specified as +const char *. + +Arguments: + str1 first string + str2 second string + +Returns: 0, 1, or -1 +*/ + +int +PRIV(strcmp_c8)(PCRE2_SPTR str1, const char *str2) +{ +PCRE2_UCHAR c1, c2; +while (*str1 != '\0' || *str2 != '\0') + { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) return ((c1 > c2) << 1) - 1; + } +return 0; +} + + +/************************************************* +* Compare two PCRE2 strings, given a length * +*************************************************/ + +/* +Arguments: + str1 first string + str2 second string + len the length + +Returns: 0, 1, or -1 +*/ + +int +PRIV(strncmp)(PCRE2_SPTR str1, PCRE2_SPTR str2, size_t len) +{ +PCRE2_UCHAR c1, c2; +for (; len > 0; len--) + { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) return ((c1 > c2) << 1) - 1; + } +return 0; +} + + +/************************************************* +* Compare PCRE2 string to 8-bit string by length * +*************************************************/ + +/* As the 8-bit string is almost always a literal, its type is specified as +const char *. + +Arguments: + str1 first string + str2 second string + len the length + +Returns: 0, 1, or -1 +*/ + +int +PRIV(strncmp_c8)(PCRE2_SPTR str1, const char *str2, size_t len) +{ +PCRE2_UCHAR c1, c2; +for (; len > 0; len--) + { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) return ((c1 > c2) << 1) - 1; + } +return 0; +} + + +/************************************************* +* Find the length of a PCRE2 string * +*************************************************/ + +/* +Argument: the string +Returns: the length +*/ + +PCRE2_SIZE +PRIV(strlen)(PCRE2_SPTR str) +{ +PCRE2_SIZE c = 0; +while (*str++ != 0) c++; +return c; +} + + +/************************************************* +* Copy 8-bit 0-terminated string to PCRE2 string * +*************************************************/ + +/* Arguments: + str1 buffer to receive the string + str2 8-bit string to be copied + +Returns: the number of code units used (excluding trailing zero) +*/ + +PCRE2_SIZE +PRIV(strcpy_c8)(PCRE2_UCHAR *str1, const char *str2) +{ +PCRE2_UCHAR *t = str1; +while (*str2 != 0) *t++ = *str2++; +*t = 0; +return t - str1; +} + +/* End of pcre2_string_utils.c */ diff --git a/ProcessHacker/pcre/pcre2_study.c b/ProcessHacker/pcre/pcre2_study.c new file mode 100644 index 0000000..d66763f --- /dev/null +++ b/ProcessHacker/pcre/pcre2_study.c @@ -0,0 +1,1575 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains functions for scanning a compiled pattern and +collecting data (e.g. minimum matching length). */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "pcre2_internal.h" + + +/* Set a bit in the starting code unit bit map. */ + +#define SET_BIT(c) re->start_bitmap[(c)/8] |= (1 << ((c)&7)) + +/* Returns from set_start_bits() */ + +enum { SSB_FAIL, SSB_DONE, SSB_CONTINUE, SSB_UNKNOWN }; + + +/************************************************* +* Find the minimum subject length for a group * +*************************************************/ + +/* Scan a parenthesized group and compute the minimum length of subject that +is needed to match it. This is a lower bound; it does not mean there is a +string of that length that matches. In UTF mode, the result is in characters +rather than code units. The field in a compiled pattern for storing the minimum +length is 16-bits long (on the grounds that anything longer than that is +pathological), so we give up when we reach that amount. This also means that +integer overflow for really crazy patterns cannot happen. + +Arguments: + re compiled pattern block + code pointer to start of group (the bracket) + startcode pointer to start of the whole pattern's code + utf UTF flag + recurses chain of recurse_check to catch mutual recursion + countptr pointer to call count (to catch over complexity) + +Returns: the minimum length + -1 \C in UTF-8 mode + or (*ACCEPT) + or pattern too complicated + or back reference to duplicate name/number + -2 internal error (missing capturing bracket) + -3 internal error (opcode not listed) +*/ + +static int +find_minlength(const pcre2_real_code *re, PCRE2_SPTR code, + PCRE2_SPTR startcode, BOOL utf, recurse_check *recurses, int *countptr) +{ +int length = -1; +int prev_cap_recno = -1; +int prev_cap_d = 0; +int prev_recurse_recno = -1; +int prev_recurse_d = 0; +uint32_t once_fudge = 0; +BOOL had_recurse = FALSE; +BOOL dupcapused = (re->flags & PCRE2_DUPCAPUSED) != 0; +recurse_check this_recurse; +register int branchlength = 0; +register PCRE2_UCHAR *cc = (PCRE2_UCHAR *)code + 1 + LINK_SIZE; + +/* If this is a "could be empty" group, its minimum length is 0. */ + +if (*code >= OP_SBRA && *code <= OP_SCOND) return 0; + +/* Skip over capturing bracket number */ + +if (*code == OP_CBRA || *code == OP_CBRAPOS) cc += IMM2_SIZE; + +/* A large and/or complex regex can take too long to process. */ + +if ((*countptr)++ > 1000) return -1; + +/* Scan along the opcodes for this branch. If we get to the end of the branch, +check the length against that of the other branches. If the accumulated length +passes 16-bits, stop. */ + +for (;;) + { + int d, min, recno; + PCRE2_UCHAR *cs, *ce; + register PCRE2_UCHAR op = *cc; + + if (branchlength >= UINT16_MAX) return UINT16_MAX; + + switch (op) + { + case OP_COND: + case OP_SCOND: + + /* If there is only one branch in a condition, the implied branch has zero + length, so we don't add anything. This covers the DEFINE "condition" + automatically. If there are two branches we can treat it the same as any + other non-capturing subpattern. */ + + cs = cc + GET(cc, 1); + if (*cs != OP_ALT) + { + cc = cs + 1 + LINK_SIZE; + break; + } + goto PROCESS_NON_CAPTURE; + + /* There's a special case of OP_ONCE, when it is wrapped round an + OP_RECURSE. We'd like to process the latter at this level so that + remembering the value works for repeated cases. So we do nothing, but + set a fudge value to skip over the OP_KET after the recurse. */ + + case OP_ONCE: + if (cc[1+LINK_SIZE] == OP_RECURSE && cc[2*(1+LINK_SIZE)] == OP_KET) + { + once_fudge = 1 + LINK_SIZE; + cc += 1 + LINK_SIZE; + break; + } + /* Fall through */ + + case OP_ONCE_NC: + case OP_BRA: + case OP_SBRA: + case OP_BRAPOS: + case OP_SBRAPOS: + PROCESS_NON_CAPTURE: + d = find_minlength(re, cc, startcode, utf, recurses, countptr); + if (d < 0) return d; + branchlength += d; + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* To save time for repeated capturing subpatterns, we remember the + length of the previous one. Unfortunately we can't do the same for + the unnumbered ones above. Nor can we do this if (?| is present in the + pattern because captures with the same number are not then identical. */ + + case OP_CBRA: + case OP_SCBRA: + case OP_CBRAPOS: + case OP_SCBRAPOS: + recno = dupcapused? prev_cap_recno - 1 : (int)GET2(cc, 1+LINK_SIZE); + if (recno != prev_cap_recno) + { + prev_cap_recno = recno; + prev_cap_d = find_minlength(re, cc, startcode, utf, recurses, countptr); + if (prev_cap_d < 0) return prev_cap_d; + } + branchlength += prev_cap_d; + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* ACCEPT makes things far too complicated; we have to give up. */ + + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + return -1; + + /* Reached end of a branch; if it's a ket it is the end of a nested + call. If it's ALT it is an alternation in a nested call. If it is END it's + the end of the outer call. All can be handled by the same code. If an + ACCEPT was previously encountered, use the length that was in force at that + time, and pass back the shortest ACCEPT length. */ + + case OP_ALT: + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_KETRPOS: + case OP_END: + if (length < 0 || (!had_recurse && branchlength < length)) + length = branchlength; + if (op != OP_ALT) return length; + cc += 1 + LINK_SIZE; + branchlength = 0; + had_recurse = FALSE; + break; + + /* Skip over assertive subpatterns */ + + case OP_ASSERT: + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + do cc += GET(cc, 1); while (*cc == OP_ALT); + /* Fall through */ + + /* Skip over things that don't match chars */ + + case OP_REVERSE: + case OP_CREF: + case OP_DNCREF: + case OP_RREF: + case OP_DNRREF: + case OP_FALSE: + case OP_TRUE: + case OP_CALLOUT: + case OP_SOD: + case OP_SOM: + case OP_EOD: + case OP_EODN: + case OP_CIRC: + case OP_CIRCM: + case OP_DOLL: + case OP_DOLLM: + case OP_NOT_WORD_BOUNDARY: + case OP_WORD_BOUNDARY: + cc += PRIV(OP_lengths)[*cc]; + break; + + case OP_CALLOUT_STR: + cc += GET(cc, 1 + 2*LINK_SIZE); + break; + + /* Skip over a subpattern that has a {0} or {0,x} quantifier */ + + case OP_BRAZERO: + case OP_BRAMINZERO: + case OP_BRAPOSZERO: + case OP_SKIPZERO: + cc += PRIV(OP_lengths)[*cc]; + do cc += GET(cc, 1); while (*cc == OP_ALT); + cc += 1 + LINK_SIZE; + break; + + /* Handle literal characters and + repetitions */ + + case OP_CHAR: + case OP_CHARI: + case OP_NOT: + case OP_NOTI: + case OP_PLUS: + case OP_PLUSI: + case OP_MINPLUS: + case OP_MINPLUSI: + case OP_POSPLUS: + case OP_POSPLUSI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + branchlength++; + cc += 2; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + branchlength++; + cc += (cc[1] == OP_PROP || cc[1] == OP_NOTPROP)? 4 : 2; + break; + + /* Handle exact repetitions. The count is already in characters, but we + may need to skip over a multibyte character in UTF mode. */ + + case OP_EXACT: + case OP_EXACTI: + case OP_NOTEXACT: + case OP_NOTEXACTI: + branchlength += GET2(cc,1); + cc += 2 + IMM2_SIZE; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + case OP_TYPEEXACT: + branchlength += GET2(cc,1); + cc += 2 + IMM2_SIZE + ((cc[1 + IMM2_SIZE] == OP_PROP + || cc[1 + IMM2_SIZE] == OP_NOTPROP)? 2 : 0); + break; + + /* Handle single-char non-literal matchers */ + + case OP_PROP: + case OP_NOTPROP: + cc += 2; + /* Fall through */ + + case OP_NOT_DIGIT: + case OP_DIGIT: + case OP_NOT_WHITESPACE: + case OP_WHITESPACE: + case OP_NOT_WORDCHAR: + case OP_WORDCHAR: + case OP_ANY: + case OP_ALLANY: + case OP_EXTUNI: + case OP_HSPACE: + case OP_NOT_HSPACE: + case OP_VSPACE: + case OP_NOT_VSPACE: + branchlength++; + cc++; + break; + + /* "Any newline" might match two characters, but it also might match just + one. */ + + case OP_ANYNL: + branchlength += 1; + cc++; + break; + + /* The single-byte matcher means we can't proceed in UTF mode. (In + non-UTF mode \C will actually be turned into OP_ALLANY, so won't ever + appear, but leave the code, just in case.) */ + + case OP_ANYBYTE: +#ifdef SUPPORT_UNICODE + if (utf) return -1; +#endif + branchlength++; + cc++; + break; + + /* For repeated character types, we have to test for \p and \P, which have + an extra two bytes of parameters. */ + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSSTAR: + case OP_TYPEPOSQUERY: + if (cc[1] == OP_PROP || cc[1] == OP_NOTPROP) cc += 2; + cc += PRIV(OP_lengths)[op]; + break; + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + if (cc[1 + IMM2_SIZE] == OP_PROP + || cc[1 + IMM2_SIZE] == OP_NOTPROP) cc += 2; + cc += PRIV(OP_lengths)[op]; + break; + + /* Check a class for variable quantification */ + + case OP_CLASS: + case OP_NCLASS: +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + /* The original code caused an unsigned overflow in 64 bit systems, + so now we use a conditional statement. */ + if (op == OP_XCLASS) + cc += GET(cc, 1); + else + cc += PRIV(OP_lengths)[OP_CLASS]; +#else + cc += PRIV(OP_lengths)[OP_CLASS]; +#endif + + switch (*cc) + { + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + branchlength++; + /* Fall through */ + + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSQUERY: + cc++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + branchlength += GET2(cc,1); + cc += 1 + 2 * IMM2_SIZE; + break; + + default: + branchlength++; + break; + } + break; + + /* Backreferences and subroutine calls (OP_RECURSE) are treated in the same + way: we find the minimum length for the subpattern. A recursion + (backreference or subroutine) causes an a flag to be set that causes the + length of this branch to be ignored. The logic is that a recursion can only + make sense if there is another alternative that stops the recursing. That + will provide the minimum length (when no recursion happens). + + If PCRE2_MATCH_UNSET_BACKREF is set, a backreference to an unset bracket + matches an empty string (by default it causes a matching failure), so in + that case we must set the minimum length to zero. */ + + /* Duplicate named pattern back reference. We cannot reliably find a length + for this if duplicate numbers are present in the pattern. */ + + case OP_DNREF: + case OP_DNREFI: + if (dupcapused) return -1; + if ((re->overall_options & PCRE2_MATCH_UNSET_BACKREF) == 0) + { + int count = GET2(cc, 1+IMM2_SIZE); + PCRE2_UCHAR *slot = + (PCRE2_UCHAR *)((uint8_t *)re + sizeof(pcre2_real_code)) + + GET2(cc, 1) * re->name_entry_size; + + d = INT_MAX; + + /* Scan all groups with the same name */ + + while (count-- > 0) + { + ce = cs = (PCRE2_UCHAR *)PRIV(find_bracket)(startcode, utf, GET2(slot, 0)); + if (cs == NULL) return -2; + do ce += GET(ce, 1); while (*ce == OP_ALT); + if (cc > cs && cc < ce) /* Simple recursion */ + { + d = 0; + had_recurse = TRUE; + break; + } + else + { + recurse_check *r = recurses; + for (r = recurses; r != NULL; r = r->prev) if (r->group == cs) break; + if (r != NULL) /* Mutual recursion */ + { + d = 0; + had_recurse = TRUE; + break; + } + else + { + int dd; + this_recurse.prev = recurses; + this_recurse.group = cs; + dd = find_minlength(re, cs, startcode, utf, &this_recurse, countptr); + if (dd < d) d = dd; + } + } + slot += re->name_entry_size; + } + } + else d = 0; + cc += 1 + 2*IMM2_SIZE; + goto REPEAT_BACK_REFERENCE; + + /* Single back reference. We cannot find a length for this if duplicate + numbers are present in the pattern. */ + + case OP_REF: + case OP_REFI: + if (dupcapused) return -1; + if ((re->overall_options & PCRE2_MATCH_UNSET_BACKREF) == 0) + { + ce = cs = (PCRE2_UCHAR *)PRIV(find_bracket)(startcode, utf, GET2(cc, 1)); + if (cs == NULL) return -2; + do ce += GET(ce, 1); while (*ce == OP_ALT); + if (cc > cs && cc < ce) /* Simple recursion */ + { + d = 0; + had_recurse = TRUE; + } + else + { + recurse_check *r = recurses; + for (r = recurses; r != NULL; r = r->prev) if (r->group == cs) break; + if (r != NULL) /* Mutual recursion */ + { + d = 0; + had_recurse = TRUE; + } + else + { + this_recurse.prev = recurses; + this_recurse.group = cs; + d = find_minlength(re, cs, startcode, utf, &this_recurse, countptr); + } + } + } + else d = 0; + cc += 1 + IMM2_SIZE; + + /* Handle repeated back references */ + + REPEAT_BACK_REFERENCE: + switch (*cc) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSQUERY: + min = 0; + cc++; + break; + + case OP_CRPLUS: + case OP_CRMINPLUS: + case OP_CRPOSPLUS: + min = 1; + cc++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + min = GET2(cc, 1); + cc += 1 + 2 * IMM2_SIZE; + break; + + default: + min = 1; + break; + } + + /* Take care not to overflow: (1) min and d are ints, so check that their + product is not greater than INT_MAX. (2) branchlength is limited to + UINT16_MAX (checked at the top of the loop). */ + + if ((d > 0 && (INT_MAX/d) < min) || UINT16_MAX - branchlength < min*d) + branchlength = UINT16_MAX; + else branchlength += min * d; + break; + + /* Recursion always refers to the first occurrence of a subpattern with a + given number. Therefore, we can always make use of caching, even when the + pattern contains multiple subpatterns with the same number. */ + + case OP_RECURSE: + cs = ce = (PCRE2_UCHAR *)startcode + GET(cc, 1); + recno = GET2(cs, 1+LINK_SIZE); + if (recno == prev_recurse_recno) + { + branchlength += prev_recurse_d; + } + else + { + do ce += GET(ce, 1); while (*ce == OP_ALT); + if (cc > cs && cc < ce) /* Simple recursion */ + had_recurse = TRUE; + else + { + recurse_check *r = recurses; + for (r = recurses; r != NULL; r = r->prev) if (r->group == cs) break; + if (r != NULL) /* Mutual recursion */ + had_recurse = TRUE; + else + { + this_recurse.prev = recurses; + this_recurse.group = cs; + prev_recurse_d = find_minlength(re, cs, startcode, utf, &this_recurse, + countptr); + if (prev_recurse_d < 0) return prev_recurse_d; + prev_recurse_recno = recno; + branchlength += prev_recurse_d; + } + } + } + cc += 1 + LINK_SIZE + once_fudge; + once_fudge = 0; + break; + + /* Anything else does not or need not match a character. We can get the + item's length from the table, but for those that can match zero occurrences + of a character, we must take special action for UTF-8 characters. As it + happens, the "NOT" versions of these opcodes are used at present only for + ASCII characters, so they could be omitted from this list. However, in + future that may change, so we include them here so as not to leave a + gotcha for a future maintainer. */ + + case OP_UPTO: + case OP_UPTOI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_MINUPTO: + case OP_MINUPTOI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + case OP_POSUPTO: + case OP_POSUPTOI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + + case OP_STAR: + case OP_STARI: + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_MINSTAR: + case OP_MINSTARI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_POSSTAR: + case OP_POSSTARI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + + case OP_QUERY: + case OP_QUERYI: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_MINQUERY: + case OP_MINQUERYI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + case OP_POSQUERY: + case OP_POSQUERYI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + + cc += PRIV(OP_lengths)[op]; +#ifdef SUPPORT_UNICODE + if (utf && HAS_EXTRALEN(cc[-1])) cc += GET_EXTRALEN(cc[-1]); +#endif + break; + + /* Skip these, but we need to add in the name length. */ + + case OP_MARK: + case OP_PRUNE_ARG: + case OP_SKIP_ARG: + case OP_THEN_ARG: + cc += PRIV(OP_lengths)[op] + cc[1]; + break; + + /* The remaining opcodes are just skipped over. */ + + case OP_CLOSE: + case OP_COMMIT: + case OP_FAIL: + case OP_PRUNE: + case OP_SET_SOM: + case OP_SKIP: + case OP_THEN: + cc += PRIV(OP_lengths)[op]; + break; + + /* This should not occur: we list all opcodes explicitly so that when + new ones get added they are properly considered. */ + + default: + return -3; + } + } +/* Control never gets here */ +} + + + +/************************************************* +* Set a bit and maybe its alternate case * +*************************************************/ + +/* Given a character, set its first code unit's bit in the table, and also the +corresponding bit for the other version of a letter if we are caseless. + +Arguments: + re points to the regex block + p points to the first code unit of the character + caseless TRUE if caseless + utf TRUE for UTF mode + +Returns: pointer after the character +*/ + +static PCRE2_SPTR +set_table_bit(pcre2_real_code *re, PCRE2_SPTR p, BOOL caseless, BOOL utf) +{ +uint32_t c = *p++; /* First code unit */ +(void)utf; /* Stop compiler warning when UTF not supported */ + +/* In 16-bit and 32-bit modes, code units greater than 0xff set the bit for +0xff. */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 +if (c > 0xff) SET_BIT(0xff); else +#endif + +SET_BIT(c); + +/* In UTF-8 or UTF-16 mode, pick up the remaining code units in order to find +the end of the character, even when caseless. */ + +#ifdef SUPPORT_UNICODE +if (utf) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + if (c >= 0xc0) GETUTF8INC(c, p); +#elif PCRE2_CODE_UNIT_WIDTH == 16 + if ((c & 0xfc00) == 0xd800) GETUTF16INC(c, p); +#endif + } +#endif /* SUPPORT_UNICODE */ + +/* If caseless, handle the other case of the character. */ + +if (caseless) + { + if (utf) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + PCRE2_UCHAR buff[6]; + c = UCD_OTHERCASE(c); + (void)PRIV(ord2utf)(c, buff); + SET_BIT(buff[0]); +#else /* 16-bit or 32-bit mode */ + c = UCD_OTHERCASE(c); + if (c > 0xff) SET_BIT(0xff); else SET_BIT(c); +#endif + } + + /* Not UTF */ + + else if (MAX_255(c)) SET_BIT(re->tables[fcc_offset + c]); + } + +return p; +} + + + +/************************************************* +* Set bits for a positive character type * +*************************************************/ + +/* This function sets starting bits for a character type. In UTF-8 mode, we can +only do a direct setting for bytes less than 128, as otherwise there can be +confusion with bytes in the middle of UTF-8 characters. In a "traditional" +environment, the tables will only recognize ASCII characters anyway, but in at +least one Windows environment, some higher bytes bits were set in the tables. +So we deal with that case by considering the UTF-8 encoding. + +Arguments: + re the regex block + cbit type the type of character wanted + table_limit 32 for non-UTF-8; 16 for UTF-8 + +Returns: nothing +*/ + +static void +set_type_bits(pcre2_real_code *re, int cbit_type, unsigned int table_limit) +{ +register uint32_t c; +for (c = 0; c < table_limit; c++) + re->start_bitmap[c] |= re->tables[c+cbits_offset+cbit_type]; +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +if (table_limit == 32) return; +for (c = 128; c < 256; c++) + { + if ((re->tables[cbits_offset + c/8] & (1 << (c&7))) != 0) + { + PCRE2_UCHAR buff[6]; + (void)PRIV(ord2utf)(c, buff); + SET_BIT(buff[0]); + } + } +#endif /* UTF-8 */ +} + + +/************************************************* +* Set bits for a negative character type * +*************************************************/ + +/* This function sets starting bits for a negative character type such as \D. +In UTF-8 mode, we can only do a direct setting for bytes less than 128, as +otherwise there can be confusion with bytes in the middle of UTF-8 characters. +Unlike in the positive case, where we can set appropriate starting bits for +specific high-valued UTF-8 characters, in this case we have to set the bits for +all high-valued characters. The lowest is 0xc2, but we overkill by starting at +0xc0 (192) for simplicity. + +Arguments: + re the regex block + cbit type the type of character wanted + table_limit 32 for non-UTF-8; 16 for UTF-8 + +Returns: nothing +*/ + +static void +set_nottype_bits(pcre2_real_code *re, int cbit_type, unsigned int table_limit) +{ +register uint32_t c; +for (c = 0; c < table_limit; c++) + re->start_bitmap[c] |= ~(re->tables[c+cbits_offset+cbit_type]); +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +if (table_limit != 32) for (c = 24; c < 32; c++) re->start_bitmap[c] = 0xff; +#endif +} + + + +/************************************************* +* Create bitmap of starting bytes * +*************************************************/ + +/* This function scans a compiled unanchored expression recursively and +attempts to build a bitmap of the set of possible starting code units whose +values are less than 256. In 16-bit and 32-bit mode, values above 255 all cause +the 255 bit to be set. When calling set[_not]_type_bits() in UTF-8 (sic) mode +we pass a value of 16 rather than 32 as the final argument. (See comments in +those functions for the reason.) + +The SSB_CONTINUE return is useful for parenthesized groups in patterns such as +(a*)b where the group provides some optional starting code units but scanning +must continue at the outer level to find at least one mandatory code unit. At +the outermost level, this function fails unless the result is SSB_DONE. + +Arguments: + re points to the compiled regex block + code points to an expression + utf TRUE if in UTF mode + +Returns: SSB_FAIL => Failed to find any starting code units + SSB_DONE => Found mandatory starting code units + SSB_CONTINUE => Found optional starting code units + SSB_UNKNOWN => Hit an unrecognized opcode +*/ + +static int +set_start_bits(pcre2_real_code *re, PCRE2_SPTR code, BOOL utf) +{ +register uint32_t c; +int yield = SSB_DONE; + +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 +int table_limit = utf? 16:32; +#else +int table_limit = 32; +#endif + +do + { + BOOL try_next = TRUE; + PCRE2_SPTR tcode = code + 1 + LINK_SIZE; + + if (*code == OP_CBRA || *code == OP_SCBRA || + *code == OP_CBRAPOS || *code == OP_SCBRAPOS) tcode += IMM2_SIZE; + + while (try_next) /* Loop for items in this branch */ + { + int rc; + uint8_t *classmap = NULL; + + switch(*tcode) + { + /* If we reach something we don't understand, it means a new opcode has + been created that hasn't been added to this function. Hopefully this + problem will be discovered during testing. */ + + default: + return SSB_UNKNOWN; + + /* Fail for a valid opcode that implies no starting bits. */ + + case OP_ACCEPT: + case OP_ASSERT_ACCEPT: + case OP_ALLANY: + case OP_ANY: + case OP_ANYBYTE: + case OP_CIRC: + case OP_CIRCM: + case OP_CLOSE: + case OP_COMMIT: + case OP_COND: + case OP_CREF: + case OP_FALSE: + case OP_TRUE: + case OP_DNCREF: + case OP_DNREF: + case OP_DNREFI: + case OP_DNRREF: + case OP_DOLL: + case OP_DOLLM: + case OP_END: + case OP_EOD: + case OP_EODN: + case OP_EXTUNI: + case OP_FAIL: + case OP_MARK: + case OP_NOT: + case OP_NOTEXACT: + case OP_NOTEXACTI: + case OP_NOTI: + case OP_NOTMINPLUS: + case OP_NOTMINPLUSI: + case OP_NOTMINQUERY: + case OP_NOTMINQUERYI: + case OP_NOTMINSTAR: + case OP_NOTMINSTARI: + case OP_NOTMINUPTO: + case OP_NOTMINUPTOI: + case OP_NOTPLUS: + case OP_NOTPLUSI: + case OP_NOTPOSPLUS: + case OP_NOTPOSPLUSI: + case OP_NOTPOSQUERY: + case OP_NOTPOSQUERYI: + case OP_NOTPOSSTAR: + case OP_NOTPOSSTARI: + case OP_NOTPOSUPTO: + case OP_NOTPOSUPTOI: + case OP_NOTPROP: + case OP_NOTQUERY: + case OP_NOTQUERYI: + case OP_NOTSTAR: + case OP_NOTSTARI: + case OP_NOTUPTO: + case OP_NOTUPTOI: + case OP_NOT_HSPACE: + case OP_NOT_VSPACE: + case OP_PRUNE: + case OP_PRUNE_ARG: + case OP_RECURSE: + case OP_REF: + case OP_REFI: + case OP_REVERSE: + case OP_RREF: + case OP_SCOND: + case OP_SET_SOM: + case OP_SKIP: + case OP_SKIP_ARG: + case OP_SOD: + case OP_SOM: + case OP_THEN: + case OP_THEN_ARG: + return SSB_FAIL; + + /* A "real" property test implies no starting bits, but the fake property + PT_CLIST identifies a list of characters. These lists are short, as they + are used for characters with more than one "other case", so there is no + point in recognizing them for OP_NOTPROP. */ + + case OP_PROP: + if (tcode[1] != PT_CLIST) return SSB_FAIL; + { + const uint32_t *p = PRIV(ucd_caseless_sets) + tcode[2]; + while ((c = *p++) < NOTACHAR) + { +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (utf) + { + PCRE2_UCHAR buff[6]; + (void)PRIV(ord2utf)(c, buff); + c = buff[0]; + } +#endif + if (c > 0xff) SET_BIT(0xff); else SET_BIT(c); + } + } + try_next = FALSE; + break; + + /* We can ignore word boundary tests. */ + + case OP_WORD_BOUNDARY: + case OP_NOT_WORD_BOUNDARY: + tcode++; + break; + + /* If we hit a bracket or a positive lookahead assertion, recurse to set + bits from within the subpattern. If it can't find anything, we have to + give up. If it finds some mandatory character(s), we are done for this + branch. Otherwise, carry on scanning after the subpattern. */ + + case OP_BRA: + case OP_SBRA: + case OP_CBRA: + case OP_SCBRA: + case OP_BRAPOS: + case OP_SBRAPOS: + case OP_CBRAPOS: + case OP_SCBRAPOS: + case OP_ONCE: + case OP_ONCE_NC: + case OP_ASSERT: + rc = set_start_bits(re, tcode, utf); + if (rc == SSB_FAIL || rc == SSB_UNKNOWN) return rc; + if (rc == SSB_DONE) try_next = FALSE; else + { + do tcode += GET(tcode, 1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + } + break; + + /* If we hit ALT or KET, it means we haven't found anything mandatory in + this branch, though we might have found something optional. For ALT, we + continue with the next alternative, but we have to arrange that the final + result from subpattern is SSB_CONTINUE rather than SSB_DONE. For KET, + return SSB_CONTINUE: if this is the top level, that indicates failure, + but after a nested subpattern, it causes scanning to continue. */ + + case OP_ALT: + yield = SSB_CONTINUE; + try_next = FALSE; + break; + + case OP_KET: + case OP_KETRMAX: + case OP_KETRMIN: + case OP_KETRPOS: + return SSB_CONTINUE; + + /* Skip over callout */ + + case OP_CALLOUT: + tcode += PRIV(OP_lengths)[OP_CALLOUT]; + break; + + case OP_CALLOUT_STR: + tcode += GET(tcode, 1 + 2*LINK_SIZE); + break; + + /* Skip over lookbehind and negative lookahead assertions */ + + case OP_ASSERT_NOT: + case OP_ASSERTBACK: + case OP_ASSERTBACK_NOT: + do tcode += GET(tcode, 1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + break; + + /* BRAZERO does the bracket, but carries on. */ + + case OP_BRAZERO: + case OP_BRAMINZERO: + case OP_BRAPOSZERO: + rc = set_start_bits(re, ++tcode, utf); + if (rc == SSB_FAIL || rc == SSB_UNKNOWN) return rc; + do tcode += GET(tcode,1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + break; + + /* SKIPZERO skips the bracket. */ + + case OP_SKIPZERO: + tcode++; + do tcode += GET(tcode,1); while (*tcode == OP_ALT); + tcode += 1 + LINK_SIZE; + break; + + /* Single-char * or ? sets the bit and tries the next item */ + + case OP_STAR: + case OP_MINSTAR: + case OP_POSSTAR: + case OP_QUERY: + case OP_MINQUERY: + case OP_POSQUERY: + tcode = set_table_bit(re, tcode + 1, FALSE, utf); + break; + + case OP_STARI: + case OP_MINSTARI: + case OP_POSSTARI: + case OP_QUERYI: + case OP_MINQUERYI: + case OP_POSQUERYI: + tcode = set_table_bit(re, tcode + 1, TRUE, utf); + break; + + /* Single-char upto sets the bit and tries the next */ + + case OP_UPTO: + case OP_MINUPTO: + case OP_POSUPTO: + tcode = set_table_bit(re, tcode + 1 + IMM2_SIZE, FALSE, utf); + break; + + case OP_UPTOI: + case OP_MINUPTOI: + case OP_POSUPTOI: + tcode = set_table_bit(re, tcode + 1 + IMM2_SIZE, TRUE, utf); + break; + + /* At least one single char sets the bit and stops */ + + case OP_EXACT: + tcode += IMM2_SIZE; + /* Fall through */ + case OP_CHAR: + case OP_PLUS: + case OP_MINPLUS: + case OP_POSPLUS: + (void)set_table_bit(re, tcode + 1, FALSE, utf); + try_next = FALSE; + break; + + case OP_EXACTI: + tcode += IMM2_SIZE; + /* Fall through */ + case OP_CHARI: + case OP_PLUSI: + case OP_MINPLUSI: + case OP_POSPLUSI: + (void)set_table_bit(re, tcode + 1, TRUE, utf); + try_next = FALSE; + break; + + /* Special spacing and line-terminating items. These recognize specific + lists of characters. The difference between VSPACE and ANYNL is that the + latter can match the two-character CRLF sequence, but that is not + relevant for finding the first character, so their code here is + identical. */ + + case OP_HSPACE: + SET_BIT(CHAR_HT); + SET_BIT(CHAR_SPACE); + + /* For the 16-bit and 32-bit libraries (which can never be EBCDIC), set + the bits for 0xA0 and for code units >= 255, independently of UTF. */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 + SET_BIT(0xA0); + SET_BIT(0xFF); +#else + /* For the 8-bit library in UTF-8 mode, set the bits for the first code + units of horizontal space characters. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + SET_BIT(0xC2); /* For U+00A0 */ + SET_BIT(0xE1); /* For U+1680, U+180E */ + SET_BIT(0xE2); /* For U+2000 - U+200A, U+202F, U+205F */ + SET_BIT(0xE3); /* For U+3000 */ + } + else +#endif + /* For the 8-bit library not in UTF-8 mode, set the bit for 0xA0, unless + the code is EBCDIC. */ + { +#ifndef EBCDIC + SET_BIT(0xA0); +#endif /* Not EBCDIC */ + } +#endif /* 8-bit support */ + + try_next = FALSE; + break; + + case OP_ANYNL: + case OP_VSPACE: + SET_BIT(CHAR_LF); + SET_BIT(CHAR_VT); + SET_BIT(CHAR_FF); + SET_BIT(CHAR_CR); + + /* For the 16-bit and 32-bit libraries (which can never be EBCDIC), set + the bits for NEL and for code units >= 255, independently of UTF. */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 + SET_BIT(CHAR_NEL); + SET_BIT(0xFF); +#else + /* For the 8-bit library in UTF-8 mode, set the bits for the first code + units of vertical space characters. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + SET_BIT(0xC2); /* For U+0085 (NEL) */ + SET_BIT(0xE2); /* For U+2028, U+2029 */ + } + else +#endif + /* For the 8-bit library not in UTF-8 mode, set the bit for NEL. */ + { + SET_BIT(CHAR_NEL); + } +#endif /* 8-bit support */ + + try_next = FALSE; + break; + + /* Single character types set the bits and stop. Note that if PCRE2_UCP + is set, we do not see these op codes because \d etc are converted to + properties. Therefore, these apply in the case when only characters less + than 256 are recognized to match the types. */ + + case OP_NOT_DIGIT: + set_nottype_bits(re, cbit_digit, table_limit); + try_next = FALSE; + break; + + case OP_DIGIT: + set_type_bits(re, cbit_digit, table_limit); + try_next = FALSE; + break; + + case OP_NOT_WHITESPACE: + set_nottype_bits(re, cbit_space, table_limit); + try_next = FALSE; + break; + + case OP_WHITESPACE: + set_type_bits(re, cbit_space, table_limit); + try_next = FALSE; + break; + + case OP_NOT_WORDCHAR: + set_nottype_bits(re, cbit_word, table_limit); + try_next = FALSE; + break; + + case OP_WORDCHAR: + set_type_bits(re, cbit_word, table_limit); + try_next = FALSE; + break; + + /* One or more character type fudges the pointer and restarts, knowing + it will hit a single character type and stop there. */ + + case OP_TYPEPLUS: + case OP_TYPEMINPLUS: + case OP_TYPEPOSPLUS: + tcode++; + break; + + case OP_TYPEEXACT: + tcode += 1 + IMM2_SIZE; + break; + + /* Zero or more repeats of character types set the bits and then + try again. */ + + case OP_TYPEUPTO: + case OP_TYPEMINUPTO: + case OP_TYPEPOSUPTO: + tcode += IMM2_SIZE; /* Fall through */ + + case OP_TYPESTAR: + case OP_TYPEMINSTAR: + case OP_TYPEPOSSTAR: + case OP_TYPEQUERY: + case OP_TYPEMINQUERY: + case OP_TYPEPOSQUERY: + switch(tcode[1]) + { + default: + case OP_ANY: + case OP_ALLANY: + return SSB_FAIL; + + case OP_HSPACE: + SET_BIT(CHAR_HT); + SET_BIT(CHAR_SPACE); + + /* For the 16-bit and 32-bit libraries (which can never be EBCDIC), set + the bits for 0xA0 and for code units >= 255, independently of UTF. */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 + SET_BIT(0xA0); + SET_BIT(0xFF); +#else + /* For the 8-bit library in UTF-8 mode, set the bits for the first code + units of horizontal space characters. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + SET_BIT(0xC2); /* For U+00A0 */ + SET_BIT(0xE1); /* For U+1680, U+180E */ + SET_BIT(0xE2); /* For U+2000 - U+200A, U+202F, U+205F */ + SET_BIT(0xE3); /* For U+3000 */ + } + else +#endif + /* For the 8-bit library not in UTF-8 mode, set the bit for 0xA0, unless + the code is EBCDIC. */ + { +#ifndef EBCDIC + SET_BIT(0xA0); +#endif /* Not EBCDIC */ + } +#endif /* 8-bit support */ + break; + + case OP_ANYNL: + case OP_VSPACE: + SET_BIT(CHAR_LF); + SET_BIT(CHAR_VT); + SET_BIT(CHAR_FF); + SET_BIT(CHAR_CR); + + /* For the 16-bit and 32-bit libraries (which can never be EBCDIC), set + the bits for NEL and for code units >= 255, independently of UTF. */ + +#if PCRE2_CODE_UNIT_WIDTH != 8 + SET_BIT(CHAR_NEL); + SET_BIT(0xFF); +#else + /* For the 8-bit library in UTF-8 mode, set the bits for the first code + units of vertical space characters. */ + +#ifdef SUPPORT_UNICODE + if (utf) + { + SET_BIT(0xC2); /* For U+0085 (NEL) */ + SET_BIT(0xE2); /* For U+2028, U+2029 */ + } + else +#endif + /* For the 8-bit library not in UTF-8 mode, set the bit for NEL. */ + { + SET_BIT(CHAR_NEL); + } +#endif /* 8-bit support */ + break; + + case OP_NOT_DIGIT: + set_nottype_bits(re, cbit_digit, table_limit); + break; + + case OP_DIGIT: + set_type_bits(re, cbit_digit, table_limit); + break; + + case OP_NOT_WHITESPACE: + set_nottype_bits(re, cbit_space, table_limit); + break; + + case OP_WHITESPACE: + set_type_bits(re, cbit_space, table_limit); + break; + + case OP_NOT_WORDCHAR: + set_nottype_bits(re, cbit_word, table_limit); + break; + + case OP_WORDCHAR: + set_type_bits(re, cbit_word, table_limit); + break; + } + + tcode += 2; + break; + + /* Extended class: if there are any property checks, or if this is a + negative XCLASS without a map, give up. If there are no property checks, + there must be wide characters on the XCLASS list, because otherwise an + XCLASS would not have been created. This means that code points >= 255 + are always potential starters. */ + +#ifdef SUPPORT_WIDE_CHARS + case OP_XCLASS: + if ((tcode[1 + LINK_SIZE] & XCL_HASPROP) != 0 || + (tcode[1 + LINK_SIZE] & (XCL_MAP|XCL_NOT)) == XCL_NOT) + return SSB_FAIL; + + /* We have a positive XCLASS or a negative one without a map. Set up the + map pointer if there is one, and fall through. */ + + classmap = ((tcode[1 + LINK_SIZE] & XCL_MAP) == 0)? NULL : + (uint8_t *)(tcode + 1 + LINK_SIZE + 1); +#endif + + /* Enter here for a negative non-XCLASS. In the 8-bit library, if we are + in UTF mode, any byte with a value >= 0xc4 is a potentially valid starter + because it starts a character with a value > 255. In 8-bit non-UTF mode, + there is no difference between CLASS and NCLASS. In all other wide + character modes, set the 0xFF bit to indicate code units >= 255. */ + + case OP_NCLASS: +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (utf) + { + re->start_bitmap[24] |= 0xf0; /* Bits for 0xc4 - 0xc8 */ + memset(re->start_bitmap+25, 0xff, 7); /* Bits for 0xc9 - 0xff */ + } +#elif PCRE2_CODE_UNIT_WIDTH != 8 + SET_BIT(0xFF); /* For characters >= 255 */ +#endif + /* Fall through */ + + /* Enter here for a positive non-XCLASS. If we have fallen through from + an XCLASS, classmap will already be set; just advance the code pointer. + Otherwise, set up classmap for a a non-XCLASS and advance past it. */ + + case OP_CLASS: + if (*tcode == OP_XCLASS) tcode += GET(tcode, 1); else + { + classmap = (uint8_t *)(++tcode); + tcode += 32 / sizeof(PCRE2_UCHAR); + } + + /* When wide characters are supported, classmap may be NULL. In UTF-8 + (sic) mode, the bits in a class bit map correspond to character values, + not to byte values. However, the bit map we are constructing is for byte + values. So we have to do a conversion for characters whose code point is + greater than 127. In fact, there are only two possible starting bytes for + characters in the range 128 - 255. */ + + if (classmap != NULL) + { +#if defined SUPPORT_UNICODE && PCRE2_CODE_UNIT_WIDTH == 8 + if (utf) + { + for (c = 0; c < 16; c++) re->start_bitmap[c] |= classmap[c]; + for (c = 128; c < 256; c++) + { + if ((classmap[c/8] && (1 << (c&7))) != 0) + { + int d = (c >> 6) | 0xc0; /* Set bit for this starter */ + re->start_bitmap[d/8] |= (1 << (d&7)); /* and then skip on to the */ + c = (c & 0xc0) + 0x40 - 1; /* next relevant character. */ + } + } + } + else +#endif + /* In all modes except UTF-8, the two bit maps are compatible. */ + + { + for (c = 0; c < 32; c++) re->start_bitmap[c] |= classmap[c]; + } + } + + /* Act on what follows the class. For a zero minimum repeat, continue; + otherwise stop processing. */ + + switch (*tcode) + { + case OP_CRSTAR: + case OP_CRMINSTAR: + case OP_CRQUERY: + case OP_CRMINQUERY: + case OP_CRPOSSTAR: + case OP_CRPOSQUERY: + tcode++; + break; + + case OP_CRRANGE: + case OP_CRMINRANGE: + case OP_CRPOSRANGE: + if (GET2(tcode, 1) == 0) tcode += 1 + 2 * IMM2_SIZE; + else try_next = FALSE; + break; + + default: + try_next = FALSE; + break; + } + break; /* End of class handling case */ + } /* End of switch for opcodes */ + } /* End of try_next loop */ + + code += GET(code, 1); /* Advance to next branch */ + } +while (*code == OP_ALT); + +return yield; +} + + + +/************************************************* +* Study a compiled expression * +*************************************************/ + +/* This function is handed a compiled expression that it must study to produce +information that will speed up the matching. + +Argument: points to the compiled expression +Returns: 0 normally; non-zero should never normally occur + 1 unknown opcode in set_start_bits + 2 missing capturing bracket + 3 unknown opcode in find_minlength +*/ + +int +PRIV(study)(pcre2_real_code *re) +{ +int min; +int count = 0; +PCRE2_UCHAR *code; +BOOL utf = (re->overall_options & PCRE2_UTF) != 0; + +/* Find start of compiled code */ + +code = (PCRE2_UCHAR *)((uint8_t *)re + sizeof(pcre2_real_code)) + + re->name_entry_size * re->name_count; + +/* For an anchored pattern, or an unanchored pattern that has a first code +unit, or a multiline pattern that matches only at "line start", there is no +point in seeking a list of starting code units. */ + +if ((re->overall_options & PCRE2_ANCHORED) == 0 && + (re->flags & (PCRE2_FIRSTSET|PCRE2_STARTLINE)) == 0) + { + int rc = set_start_bits(re, code, utf); + if (rc == SSB_UNKNOWN) return 1; + if (rc == SSB_DONE) re->flags |= PCRE2_FIRSTMAPSET; + } + +/* Find the minimum length of subject string. If it can match an empty string, +the minimum length is already known. */ + +if ((re->flags & PCRE2_MATCH_EMPTY) == 0) + { + switch(min = find_minlength(re, code, code, utf, NULL, &count)) + { + case -1: /* \C in UTF mode or (*ACCEPT) or over-complex regex */ + break; /* Leave minlength unchanged (will be zero) */ + + case -2: + return 2; /* missing capturing bracket */ + + case -3: + return 3; /* unrecognized opcode */ + + default: + if (min > UINT16_MAX) min = UINT16_MAX; + re->minlength = min; + break; + } + } + +return 0; +} + +/* End of pcre2_study.c */ diff --git a/ProcessHacker/pcre/pcre2_substitute.c b/ProcessHacker/pcre/pcre2_substitute.c new file mode 100644 index 0000000..a5d0c84 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_substitute.c @@ -0,0 +1,850 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + +#define PTR_STACK_SIZE 20 + +#define SUBSTITUTE_OPTIONS \ + (PCRE2_SUBSTITUTE_EXTENDED|PCRE2_SUBSTITUTE_GLOBAL| \ + PCRE2_SUBSTITUTE_OVERFLOW_LENGTH|PCRE2_SUBSTITUTE_UNKNOWN_UNSET| \ + PCRE2_SUBSTITUTE_UNSET_EMPTY) + + + +/************************************************* +* Find end of substitute text * +*************************************************/ + +/* In extended mode, we recognize ${name:+set text:unset text} and similar +constructions. This requires the identification of unescaped : and } +characters. This function scans for such. It must deal with nested ${ +constructions. The pointer to the text is updated, either to the required end +character, or to where an error was detected. + +Arguments: + code points to the compiled expression (for options) + ptrptr points to the pointer to the start of the text (updated) + ptrend end of the whole string + last TRUE if the last expected string (only } recognized) + +Returns: 0 on success + negative error code on failure +*/ + +static int +find_text_end(const pcre2_code *code, PCRE2_SPTR *ptrptr, PCRE2_SPTR ptrend, + BOOL last) +{ +int rc = 0; +uint32_t nestlevel = 0; +BOOL literal = FALSE; +PCRE2_SPTR ptr = *ptrptr; + +for (; ptr < ptrend; ptr++) + { + if (literal) + { + if (ptr[0] == CHAR_BACKSLASH && ptr < ptrend - 1 && ptr[1] == CHAR_E) + { + literal = FALSE; + ptr += 1; + } + } + + else if (*ptr == CHAR_RIGHT_CURLY_BRACKET) + { + if (nestlevel == 0) goto EXIT; + nestlevel--; + } + + else if (*ptr == CHAR_COLON && !last && nestlevel == 0) goto EXIT; + + else if (*ptr == CHAR_DOLLAR_SIGN) + { + if (ptr < ptrend - 1 && ptr[1] == CHAR_LEFT_CURLY_BRACKET) + { + nestlevel++; + ptr += 1; + } + } + + else if (*ptr == CHAR_BACKSLASH) + { + int erc; + int errorcode = 0; + uint32_t ch; + + if (ptr < ptrend - 1) switch (ptr[1]) + { + case CHAR_L: + case CHAR_l: + case CHAR_U: + case CHAR_u: + ptr += 1; + continue; + } + + erc = PRIV(check_escape)(&ptr, ptrend, &ch, &errorcode, + code->overall_options, FALSE, NULL); + if (errorcode != 0) + { + rc = errorcode; + goto EXIT; + } + + switch(erc) + { + case 0: /* Data character */ + case ESC_E: /* Isolated \E is ignored */ + break; + + case ESC_Q: + literal = TRUE; + break; + + default: + rc = PCRE2_ERROR_BADREPESCAPE; + goto EXIT; + } + } + } + +rc = PCRE2_ERROR_REPMISSINGBRACE; /* Terminator not found */ + +EXIT: +*ptrptr = ptr; +return rc; +} + + + +/************************************************* +* Match and substitute * +*************************************************/ + +/* This function applies a compiled re to a subject string and creates a new +string with substitutions. The first 7 arguments are the same as for +pcre2_match(). Either string length may be PCRE2_ZERO_TERMINATED. + +Arguments: + code points to the compiled expression + subject points to the subject string + length length of subject string (may contain binary zeros) + start_offset where to start in the subject string + options option bits + match_data points to a match_data block, or is NULL + context points a PCRE2 context + replacement points to the replacement string + rlength length of replacement string + buffer where to put the substituted string + blength points to length of buffer; updated to length of string + +Returns: >= 0 number of substitutions made + < 0 an error code + PCRE2_ERROR_BADREPLACEMENT means invalid use of $ +*/ + +/* This macro checks for space in the buffer before copying into it. On +overflow, either give an error immediately, or keep on, accumulating the +length. */ + +#define CHECKMEMCPY(from,length) \ + if (!overflowed && lengthleft < length) \ + { \ + if ((suboptions & PCRE2_SUBSTITUTE_OVERFLOW_LENGTH) == 0) goto NOROOM; \ + overflowed = TRUE; \ + extra_needed = length - lengthleft; \ + } \ + else if (overflowed) \ + { \ + extra_needed += length; \ + } \ + else \ + { \ + memcpy(buffer + buff_offset, from, CU2BYTES(length)); \ + buff_offset += length; \ + lengthleft -= length; \ + } + +/* Here's the function */ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substitute(const pcre2_code *code, PCRE2_SPTR subject, PCRE2_SIZE length, + PCRE2_SIZE start_offset, uint32_t options, pcre2_match_data *match_data, + pcre2_match_context *mcontext, PCRE2_SPTR replacement, PCRE2_SIZE rlength, + PCRE2_UCHAR *buffer, PCRE2_SIZE *blength) +{ +int rc; +int subs; +int forcecase = 0; +int forcecasereset = 0; +uint32_t ovector_count; +uint32_t goptions = 0; +uint32_t suboptions; +BOOL match_data_created = FALSE; +BOOL literal = FALSE; +BOOL overflowed = FALSE; +#ifdef SUPPORT_UNICODE +BOOL utf = (code->overall_options & PCRE2_UTF) != 0; +#endif +PCRE2_UCHAR temp[6]; +PCRE2_SPTR ptr; +PCRE2_SPTR repend; +PCRE2_SIZE extra_needed = 0; +PCRE2_SIZE buff_offset, buff_length, lengthleft, fraglength; +PCRE2_SIZE *ovector; + +buff_offset = 0; +lengthleft = buff_length = *blength; +*blength = PCRE2_UNSET; + +/* Partial matching is not valid. */ + +if ((options & (PCRE2_PARTIAL_HARD|PCRE2_PARTIAL_SOFT)) != 0) + return PCRE2_ERROR_BADOPTION; + +/* If no match data block is provided, create one. */ + +if (match_data == NULL) + { + pcre2_general_context *gcontext = (mcontext == NULL)? + (pcre2_general_context *)code : + (pcre2_general_context *)mcontext; + match_data = pcre2_match_data_create_from_pattern(code, gcontext); + if (match_data == NULL) return PCRE2_ERROR_NOMEMORY; + match_data_created = TRUE; + } +ovector = pcre2_get_ovector_pointer(match_data); +ovector_count = pcre2_get_ovector_count(match_data); + +/* Find lengths of zero-terminated strings and the end of the replacement. */ + +if (length == PCRE2_ZERO_TERMINATED) length = PRIV(strlen)(subject); +if (rlength == PCRE2_ZERO_TERMINATED) rlength = PRIV(strlen)(replacement); +repend = replacement + rlength; + +/* Check UTF replacement string if necessary. */ + +#ifdef SUPPORT_UNICODE +if (utf && (options & PCRE2_NO_UTF_CHECK) == 0) + { + rc = PRIV(valid_utf)(replacement, rlength, &(match_data->rightchar)); + if (rc != 0) + { + match_data->leftchar = 0; + goto EXIT; + } + } +#endif /* SUPPORT_UNICODE */ + +/* Save the substitute options and remove them from the match options. */ + +suboptions = options & SUBSTITUTE_OPTIONS; +options &= ~SUBSTITUTE_OPTIONS; + +/* Copy up to the start offset */ + +CHECKMEMCPY(subject, start_offset); + +/* Loop for global substituting. */ + +subs = 0; +do + { + PCRE2_SPTR ptrstack[PTR_STACK_SIZE]; + uint32_t ptrstackptr = 0; + + rc = pcre2_match(code, subject, length, start_offset, options|goptions, + match_data, mcontext); + +#ifdef SUPPORT_UNICODE + if (utf) options |= PCRE2_NO_UTF_CHECK; /* Only need to check once */ +#endif + + /* Any error other than no match returns the error code. No match when not + doing the special after-empty-match global rematch, or when at the end of the + subject, breaks the global loop. Otherwise, advance the starting point by one + character, copying it to the output, and try again. */ + + if (rc < 0) + { + PCRE2_SIZE save_start; + + if (rc != PCRE2_ERROR_NOMATCH) goto EXIT; + if (goptions == 0 || start_offset >= length) break; + + /* Advance by one code point. Then, if CRLF is a valid newline sequence and + we have advanced into the middle of it, advance one more code point. In + other words, do not start in the middle of CRLF, even if CR and LF on their + own are valid newlines. */ + + save_start = start_offset++; + if (subject[start_offset-1] == CHAR_CR && + code->newline_convention != PCRE2_NEWLINE_CR && + code->newline_convention != PCRE2_NEWLINE_LF && + start_offset < length && + subject[start_offset] == CHAR_LF) + start_offset++; + + /* Otherwise, in UTF mode, advance past any secondary code points. */ + + else if ((code->overall_options & PCRE2_UTF) != 0) + { +#if PCRE2_CODE_UNIT_WIDTH == 8 + while (start_offset < length && (subject[start_offset] & 0xc0) == 0x80) + start_offset++; +#elif PCRE2_CODE_UNIT_WIDTH == 16 + while (start_offset < length && + (subject[start_offset] & 0xfc00) == 0xdc00) + start_offset++; +#endif + } + + /* Copy what we have advanced past, reset the special global options, and + continue to the next match. */ + + fraglength = start_offset - save_start; + CHECKMEMCPY(subject + save_start, fraglength); + goptions = 0; + continue; + } + + /* Handle a successful match. Matches that use \K to end before they start + are not supported. */ + + if (ovector[1] < ovector[0]) + { + rc = PCRE2_ERROR_BADSUBSPATTERN; + goto EXIT; + } + + /* Count substitutions with a paranoid check for integer overflow; surely no + real call to this function would ever hit this! */ + + if (subs == INT_MAX) + { + rc = PCRE2_ERROR_TOOMANYREPLACE; + goto EXIT; + } + subs++; + + /* Copy the text leading up to the match. */ + + if (rc == 0) rc = ovector_count; + fraglength = ovector[0] - start_offset; + CHECKMEMCPY(subject + start_offset, fraglength); + + /* Process the replacement string. Literal mode is set by \Q, but only in + extended mode when backslashes are being interpreted. In extended mode we + must handle nested substrings that are to be reprocessed. */ + + ptr = replacement; + for (;;) + { + uint32_t ch; + unsigned int chlen; + + /* If at the end of a nested substring, pop the stack. */ + + if (ptr >= repend) + { + if (ptrstackptr <= 0) break; /* End of replacement string */ + repend = ptrstack[--ptrstackptr]; + ptr = ptrstack[--ptrstackptr]; + continue; + } + + /* Handle the next character */ + + if (literal) + { + if (ptr[0] == CHAR_BACKSLASH && ptr < repend - 1 && ptr[1] == CHAR_E) + { + literal = FALSE; + ptr += 2; + continue; + } + goto LOADLITERAL; + } + + /* Not in literal mode. */ + + if (*ptr == CHAR_DOLLAR_SIGN) + { + int group, n; + uint32_t special = 0; + BOOL inparens; + BOOL star; + PCRE2_SIZE sublength; + PCRE2_SPTR text1_start = NULL; + PCRE2_SPTR text1_end = NULL; + PCRE2_SPTR text2_start = NULL; + PCRE2_SPTR text2_end = NULL; + PCRE2_UCHAR next; + PCRE2_UCHAR name[33]; + + if (++ptr >= repend) goto BAD; + if ((next = *ptr) == CHAR_DOLLAR_SIGN) goto LOADLITERAL; + + group = -1; + n = 0; + inparens = FALSE; + star = FALSE; + + if (next == CHAR_LEFT_CURLY_BRACKET) + { + if (++ptr >= repend) goto BAD; + next = *ptr; + inparens = TRUE; + } + + if (next == CHAR_ASTERISK) + { + if (++ptr >= repend) goto BAD; + next = *ptr; + star = TRUE; + } + + if (!star && next >= CHAR_0 && next <= CHAR_9) + { + group = next - CHAR_0; + while (++ptr < repend) + { + next = *ptr; + if (next < CHAR_0 || next > CHAR_9) break; + group = group * 10 + next - CHAR_0; + + /* A check for a number greater than the hightest captured group + is sufficient here; no need for a separate overflow check. If unknown + groups are to be treated as unset, just skip over any remaining + digits and carry on. */ + + if (group > code->top_bracket) + { + if ((suboptions & PCRE2_SUBSTITUTE_UNKNOWN_UNSET) != 0) + { + while (++ptr < repend && *ptr >= CHAR_0 && *ptr <= CHAR_9); + break; + } + else + { + rc = PCRE2_ERROR_NOSUBSTRING; + goto PTREXIT; + } + } + } + } + else + { + const uint8_t *ctypes = code->tables + ctypes_offset; + while (MAX_255(next) && (ctypes[next] & ctype_word) != 0) + { + name[n++] = next; + if (n > 32) goto BAD; + if (++ptr >= repend) break; + next = *ptr; + } + if (n == 0) goto BAD; + name[n] = 0; + } + + /* In extended mode we recognize ${name:+set text:unset text} and + ${name:-default text}. */ + + if (inparens) + { + if ((suboptions & PCRE2_SUBSTITUTE_EXTENDED) != 0 && + !star && ptr < repend - 2 && next == CHAR_COLON) + { + special = *(++ptr); + if (special != CHAR_PLUS && special != CHAR_MINUS) + { + rc = PCRE2_ERROR_BADSUBSTITUTION; + goto PTREXIT; + } + + text1_start = ++ptr; + rc = find_text_end(code, &ptr, repend, special == CHAR_MINUS); + if (rc != 0) goto PTREXIT; + text1_end = ptr; + + if (special == CHAR_PLUS && *ptr == CHAR_COLON) + { + text2_start = ++ptr; + rc = find_text_end(code, &ptr, repend, TRUE); + if (rc != 0) goto PTREXIT; + text2_end = ptr; + } + } + + else + { + if (ptr >= repend || *ptr != CHAR_RIGHT_CURLY_BRACKET) + { + rc = PCRE2_ERROR_REPMISSINGBRACE; + goto PTREXIT; + } + } + + ptr++; + } + + /* Have found a syntactically correct group number or name, or *name. + Only *MARK is currently recognized. */ + + if (star) + { + if (PRIV(strcmp_c8)(name, STRING_MARK) == 0) + { + PCRE2_SPTR mark = pcre2_get_mark(match_data); + if (mark != NULL) + { + PCRE2_SPTR mark_start = mark; + while (*mark != 0) mark++; + fraglength = mark - mark_start; + CHECKMEMCPY(mark_start, fraglength); + } + } + else goto BAD; + } + + /* Substitute the contents of a group. We don't use substring_copy + functions any more, in order to support case forcing. */ + + else + { + PCRE2_SPTR subptr, subptrend; + + /* Find a number for a named group. In case there are duplicate names, + search for the first one that is set. If the name is not found when + PCRE2_SUBSTITUTE_UNKNOWN_EMPTY is set, set the group number to a + non-existent group. */ + + if (group < 0) + { + PCRE2_SPTR first, last, entry; + rc = pcre2_substring_nametable_scan(code, name, &first, &last); + if (rc == PCRE2_ERROR_NOSUBSTRING && + (suboptions & PCRE2_SUBSTITUTE_UNKNOWN_UNSET) != 0) + { + group = code->top_bracket + 1; + } + else + { + if (rc < 0) goto PTREXIT; + for (entry = first; entry <= last; entry += rc) + { + uint32_t ng = GET2(entry, 0); + if (ng < ovector_count) + { + if (group < 0) group = ng; /* First in ovector */ + if (ovector[ng*2] != PCRE2_UNSET) + { + group = ng; /* First that is set */ + break; + } + } + } + + /* If group is still negative, it means we did not find a group + that is in the ovector. Just set the first group. */ + + if (group < 0) group = GET2(first, 0); + } + } + + /* We now have a group that is identified by number. Find the length of + the captured string. If a group in a non-special substitution is unset + when PCRE2_SUBSTITUTE_UNSET_EMPTY is set, substitute nothing. */ + + rc = pcre2_substring_length_bynumber(match_data, group, &sublength); + if (rc < 0) + { + if (rc == PCRE2_ERROR_NOSUBSTRING && + (suboptions & PCRE2_SUBSTITUTE_UNKNOWN_UNSET) != 0) + { + rc = PCRE2_ERROR_UNSET; + } + if (rc != PCRE2_ERROR_UNSET) goto PTREXIT; /* Non-unset errors */ + if (special == 0) /* Plain substitution */ + { + if ((suboptions & PCRE2_SUBSTITUTE_UNSET_EMPTY) != 0) continue; + goto PTREXIT; /* Else error */ + } + } + + /* If special is '+' we have a 'set' and possibly an 'unset' text, + both of which are reprocessed when used. If special is '-' we have a + default text for when the group is unset; it must be reprocessed. */ + + if (special != 0) + { + if (special == CHAR_MINUS) + { + if (rc == 0) goto LITERAL_SUBSTITUTE; + text2_start = text1_start; + text2_end = text1_end; + } + + if (ptrstackptr >= PTR_STACK_SIZE) goto BAD; + ptrstack[ptrstackptr++] = ptr; + ptrstack[ptrstackptr++] = repend; + + if (rc == 0) + { + ptr = text1_start; + repend = text1_end; + } + else + { + ptr = text2_start; + repend = text2_end; + } + continue; + } + + /* Otherwise we have a literal substitution of a group's contents. */ + + LITERAL_SUBSTITUTE: + subptr = subject + ovector[group*2]; + subptrend = subject + ovector[group*2 + 1]; + + /* Substitute a literal string, possibly forcing alphabetic case. */ + + while (subptr < subptrend) + { + GETCHARINCTEST(ch, subptr); + if (forcecase != 0) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + uint32_t type = UCD_CHARTYPE(ch); + if (PRIV(ucp_gentype)[type] == ucp_L && + type != ((forcecase > 0)? ucp_Lu : ucp_Ll)) + ch = UCD_OTHERCASE(ch); + } + else +#endif + { + if (((code->tables + cbits_offset + + ((forcecase > 0)? cbit_upper:cbit_lower) + )[ch/8] & (1 << (ch%8))) == 0) + ch = (code->tables + fcc_offset)[ch]; + } + forcecase = forcecasereset; + } + +#ifdef SUPPORT_UNICODE + if (utf) chlen = PRIV(ord2utf)(ch, temp); else +#endif + { + temp[0] = ch; + chlen = 1; + } + CHECKMEMCPY(temp, chlen); + } + } + } + + /* Handle an escape sequence in extended mode. We can use check_escape() + to process \Q, \E, \c, \o, \x and \ followed by non-alphanumerics, but + the case-forcing escapes are not supported in pcre2_compile() so must be + recognized here. */ + + else if ((suboptions & PCRE2_SUBSTITUTE_EXTENDED) != 0 && + *ptr == CHAR_BACKSLASH) + { + int errorcode = 0; + + if (ptr < repend - 1) switch (ptr[1]) + { + case CHAR_L: + forcecase = forcecasereset = -1; + ptr += 2; + continue; + + case CHAR_l: + forcecase = -1; + forcecasereset = 0; + ptr += 2; + continue; + + case CHAR_U: + forcecase = forcecasereset = 1; + ptr += 2; + continue; + + case CHAR_u: + forcecase = 1; + forcecasereset = 0; + ptr += 2; + continue; + + default: + break; + } + + rc = PRIV(check_escape)(&ptr, repend, &ch, &errorcode, + code->overall_options, FALSE, NULL); + if (errorcode != 0) goto BADESCAPE; + ptr++; + + switch(rc) + { + case ESC_E: + forcecase = forcecasereset = 0; + continue; + + case ESC_Q: + literal = TRUE; + continue; + + case 0: /* Data character */ + goto LITERAL; + + default: + goto BADESCAPE; + } + } + + /* Handle a literal code unit */ + + else + { + LOADLITERAL: + GETCHARINCTEST(ch, ptr); /* Get character value, increment pointer */ + + LITERAL: + if (forcecase != 0) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + uint32_t type = UCD_CHARTYPE(ch); + if (PRIV(ucp_gentype)[type] == ucp_L && + type != ((forcecase > 0)? ucp_Lu : ucp_Ll)) + ch = UCD_OTHERCASE(ch); + } + else +#endif + { + if (((code->tables + cbits_offset + + ((forcecase > 0)? cbit_upper:cbit_lower) + )[ch/8] & (1 << (ch%8))) == 0) + ch = (code->tables + fcc_offset)[ch]; + } + forcecase = forcecasereset; + } + +#ifdef SUPPORT_UNICODE + if (utf) chlen = PRIV(ord2utf)(ch, temp); else +#endif + { + temp[0] = ch; + chlen = 1; + } + CHECKMEMCPY(temp, chlen); + } /* End handling a literal code unit */ + } /* End of loop for scanning the replacement. */ + + /* The replacement has been copied to the output. Update the start offset to + point to the rest of the subject string. If we matched an empty string, + do the magic for global matches. */ + + start_offset = ovector[1]; + goptions = (ovector[0] != ovector[1])? 0 : + PCRE2_ANCHORED|PCRE2_NOTEMPTY_ATSTART; + } while ((suboptions & PCRE2_SUBSTITUTE_GLOBAL) != 0); /* Repeat "do" loop */ + +/* Copy the rest of the subject. */ + +fraglength = length - start_offset; +CHECKMEMCPY(subject + start_offset, fraglength); +temp[0] = 0; +CHECKMEMCPY(temp , 1); + +/* If overflowed is set it means the PCRE2_SUBSTITUTE_OVERFLOW_LENGTH is set, +and matching has carried on after a full buffer, in order to compute the length +needed. Otherwise, an overflow generates an immediate error return. */ + +if (overflowed) + { + rc = PCRE2_ERROR_NOMEMORY; + *blength = buff_length + extra_needed; + } + +/* After a successful execution, return the number of substitutions and set the +length of buffer used, excluding the trailing zero. */ + +else + { + rc = subs; + *blength = buff_offset - 1; + } + +EXIT: +if (match_data_created) pcre2_match_data_free(match_data); + else match_data->rc = rc; +return rc; + +NOROOM: +rc = PCRE2_ERROR_NOMEMORY; +goto EXIT; + +BAD: +rc = PCRE2_ERROR_BADREPLACEMENT; +goto PTREXIT; + +BADESCAPE: +rc = PCRE2_ERROR_BADREPESCAPE; + +PTREXIT: +*blength = (PCRE2_SIZE)(ptr - replacement); +goto EXIT; +} + +/* End of pcre2_substitute.c */ diff --git a/ProcessHacker/pcre/pcre2_substring.c b/ProcessHacker/pcre/pcre2_substring.c new file mode 100644 index 0000000..3b8e82c --- /dev/null +++ b/ProcessHacker/pcre/pcre2_substring.c @@ -0,0 +1,536 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pcre2_internal.h" + + + +/************************************************* +* Copy named captured string to given buffer * +*************************************************/ + +/* This function copies a single captured substring into a given buffer, +identifying it by name. If the regex permits duplicate names, the first +substring that is set is chosen. + +Arguments: + match_data points to the match data + stringname the name of the required substring + buffer where to put the substring + sizeptr the size of the buffer, updated to the size of the substring + +Returns: if successful: zero + if not successful, a negative error code: + (1) an error from nametable_scan() + (2) an error from copy_bynumber() + (3) PCRE2_ERROR_UNAVAILABLE: no group is in ovector + (4) PCRE2_ERROR_UNSET: all named groups in ovector are unset +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_copy_byname(pcre2_match_data *match_data, PCRE2_SPTR stringname, + PCRE2_UCHAR *buffer, PCRE2_SIZE *sizeptr) +{ +PCRE2_SPTR first, last, entry; +int failrc, entrysize; +if (match_data->matchedby == PCRE2_MATCHEDBY_DFA_INTERPRETER) + return PCRE2_ERROR_DFA_UFUNC; +entrysize = pcre2_substring_nametable_scan(match_data->code, stringname, + &first, &last); +if (entrysize < 0) return entrysize; +failrc = PCRE2_ERROR_UNAVAILABLE; +for (entry = first; entry <= last; entry += entrysize) + { + uint32_t n = GET2(entry, 0); + if (n < match_data->oveccount) + { + if (match_data->ovector[n*2] != PCRE2_UNSET) + return pcre2_substring_copy_bynumber(match_data, n, buffer, sizeptr); + failrc = PCRE2_ERROR_UNSET; + } + } +return failrc; +} + + + +/************************************************* +* Copy numbered captured string to given buffer * +*************************************************/ + +/* This function copies a single captured substring into a given buffer, +identifying it by number. + +Arguments: + match_data points to the match data + stringnumber the number of the required substring + buffer where to put the substring + sizeptr the size of the buffer, updated to the size of the substring + +Returns: if successful: 0 + if not successful, a negative error code: + PCRE2_ERROR_NOMEMORY: buffer too small + PCRE2_ERROR_NOSUBSTRING: no such substring + PCRE2_ERROR_UNAVAILABLE: ovector too small + PCRE2_ERROR_UNSET: substring is not set +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_copy_bynumber(pcre2_match_data *match_data, + uint32_t stringnumber, PCRE2_UCHAR *buffer, PCRE2_SIZE *sizeptr) +{ +int rc; +PCRE2_SIZE size; +rc = pcre2_substring_length_bynumber(match_data, stringnumber, &size); +if (rc < 0) return rc; +if (size + 1 > *sizeptr) return PCRE2_ERROR_NOMEMORY; +memcpy(buffer, match_data->subject + match_data->ovector[stringnumber*2], + CU2BYTES(size)); +buffer[size] = 0; +*sizeptr = size; +return 0; +} + + + +/************************************************* +* Extract named captured string * +*************************************************/ + +/* This function copies a single captured substring, identified by name, into +new memory. If the regex permits duplicate names, the first substring that is +set is chosen. + +Arguments: + match_data pointer to match_data + stringname the name of the required substring + stringptr where to put the pointer to the new memory + sizeptr where to put the length of the substring + +Returns: if successful: zero + if not successful, a negative value: + (1) an error from nametable_scan() + (2) an error from get_bynumber() + (3) PCRE2_ERROR_UNAVAILABLE: no group is in ovector + (4) PCRE2_ERROR_UNSET: all named groups in ovector are unset +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_get_byname(pcre2_match_data *match_data, + PCRE2_SPTR stringname, PCRE2_UCHAR **stringptr, PCRE2_SIZE *sizeptr) +{ +PCRE2_SPTR first, last, entry; +int failrc, entrysize; +if (match_data->matchedby == PCRE2_MATCHEDBY_DFA_INTERPRETER) + return PCRE2_ERROR_DFA_UFUNC; +entrysize = pcre2_substring_nametable_scan(match_data->code, stringname, + &first, &last); +if (entrysize < 0) return entrysize; +failrc = PCRE2_ERROR_UNAVAILABLE; +for (entry = first; entry <= last; entry += entrysize) + { + uint32_t n = GET2(entry, 0); + if (n < match_data->oveccount) + { + if (match_data->ovector[n*2] != PCRE2_UNSET) + return pcre2_substring_get_bynumber(match_data, n, stringptr, sizeptr); + failrc = PCRE2_ERROR_UNSET; + } + } +return failrc; +} + + + +/************************************************* +* Extract captured string to new memory * +*************************************************/ + +/* This function copies a single captured substring into a piece of new +memory. + +Arguments: + match_data points to match data + stringnumber the number of the required substring + stringptr where to put a pointer to the new memory + sizeptr where to put the size of the substring + +Returns: if successful: 0 + if not successful, a negative error code: + PCRE2_ERROR_NOMEMORY: failed to get memory + PCRE2_ERROR_NOSUBSTRING: no such substring + PCRE2_ERROR_UNAVAILABLE: ovector too small + PCRE2_ERROR_UNSET: substring is not set +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_get_bynumber(pcre2_match_data *match_data, + uint32_t stringnumber, PCRE2_UCHAR **stringptr, PCRE2_SIZE *sizeptr) +{ +int rc; +PCRE2_SIZE size; +PCRE2_UCHAR *yield; +rc = pcre2_substring_length_bynumber(match_data, stringnumber, &size); +if (rc < 0) return rc; +yield = PRIV(memctl_malloc)(sizeof(pcre2_memctl) + + (size + 1)*PCRE2_CODE_UNIT_WIDTH, (pcre2_memctl *)match_data); +if (yield == NULL) return PCRE2_ERROR_NOMEMORY; +yield = (PCRE2_UCHAR *)(((char *)yield) + sizeof(pcre2_memctl)); +memcpy(yield, match_data->subject + match_data->ovector[stringnumber*2], + CU2BYTES(size)); +yield[size] = 0; +*stringptr = yield; +*sizeptr = size; +return 0; +} + + + +/************************************************* +* Free memory obtained by get_substring * +*************************************************/ + +/* +Argument: the result of a previous pcre2_substring_get_byxxx() +Returns: nothing +*/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_substring_free(PCRE2_UCHAR *string) +{ +pcre2_memctl *memctl = (pcre2_memctl *)((char *)string - sizeof(pcre2_memctl)); +memctl->free(memctl, memctl->memory_data); +} + + + +/************************************************* +* Get length of a named substring * +*************************************************/ + +/* This function returns the length of a named captured substring. If the regex +permits duplicate names, the first substring that is set is chosen. + +Arguments: + match_data pointer to match data + stringname the name of the required substring + sizeptr where to put the length + +Returns: 0 if successful, else a negative error number +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_length_byname(pcre2_match_data *match_data, + PCRE2_SPTR stringname, PCRE2_SIZE *sizeptr) +{ +PCRE2_SPTR first, last, entry; +int failrc, entrysize; +if (match_data->matchedby == PCRE2_MATCHEDBY_DFA_INTERPRETER) + return PCRE2_ERROR_DFA_UFUNC; +entrysize = pcre2_substring_nametable_scan(match_data->code, stringname, + &first, &last); +if (entrysize < 0) return entrysize; +failrc = PCRE2_ERROR_UNAVAILABLE; +for (entry = first; entry <= last; entry += entrysize) + { + uint32_t n = GET2(entry, 0); + if (n < match_data->oveccount) + { + if (match_data->ovector[n*2] != PCRE2_UNSET) + return pcre2_substring_length_bynumber(match_data, n, sizeptr); + failrc = PCRE2_ERROR_UNSET; + } + } +return failrc; +} + + + +/************************************************* +* Get length of a numbered substring * +*************************************************/ + +/* This function returns the length of a captured substring. If the start is +beyond the end (which can happen when \K is used in an assertion), it sets the +length to zero. + +Arguments: + match_data pointer to match data + stringnumber the number of the required substring + sizeptr where to put the length, if not NULL + +Returns: if successful: 0 + if not successful, a negative error code: + PCRE2_ERROR_NOSUBSTRING: no such substring + PCRE2_ERROR_UNAVAILABLE: ovector is too small + PCRE2_ERROR_UNSET: substring is not set +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_length_bynumber(pcre2_match_data *match_data, + uint32_t stringnumber, PCRE2_SIZE *sizeptr) +{ +PCRE2_SIZE left, right; +int count = match_data->rc; +if (count == PCRE2_ERROR_PARTIAL) + { + if (stringnumber > 0) return PCRE2_ERROR_PARTIAL; + count = 0; + } +else if (count < 0) return count; /* Match failed */ + +if (match_data->matchedby != PCRE2_MATCHEDBY_DFA_INTERPRETER) + { + if (stringnumber > match_data->code->top_bracket) + return PCRE2_ERROR_NOSUBSTRING; + if (stringnumber >= match_data->oveccount) + return PCRE2_ERROR_UNAVAILABLE; + if (match_data->ovector[stringnumber*2] == PCRE2_UNSET) + return PCRE2_ERROR_UNSET; + } +else /* Matched using pcre2_dfa_match() */ + { + if (stringnumber >= match_data->oveccount) return PCRE2_ERROR_UNAVAILABLE; + if (count != 0 && stringnumber >= (uint32_t)count) return PCRE2_ERROR_UNSET; + } + +left = match_data->ovector[stringnumber*2]; +right = match_data->ovector[stringnumber*2+1]; +if (sizeptr != NULL) *sizeptr = (left > right)? 0 : right - left; +return 0; +} + + + +/************************************************* +* Extract all captured strings to new memory * +*************************************************/ + +/* This function gets one chunk of memory and builds a list of pointers and all +the captured substrings in it. A NULL pointer is put on the end of the list. +The substrings are zero-terminated, but also, if the final argument is +non-NULL, a list of lengths is also returned. This allows binary data to be +handled. + +Arguments: + match_data points to the match data + listptr set to point to the list of pointers + lengthsptr set to point to the list of lengths (may be NULL) + +Returns: if successful: 0 + if not successful, a negative error code: + PCRE2_ERROR_NOMEMORY: failed to get memory, + or a match failure code +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_list_get(pcre2_match_data *match_data, PCRE2_UCHAR ***listptr, + PCRE2_SIZE **lengthsptr) +{ +int i, count, count2; +PCRE2_SIZE size; +PCRE2_SIZE *lensp; +pcre2_memctl *memp; +PCRE2_UCHAR **listp; +PCRE2_UCHAR *sp; +PCRE2_SIZE *ovector; + +if ((count = match_data->rc) < 0) return count; /* Match failed */ +if (count == 0) count = match_data->oveccount; /* Ovector too small */ + +count2 = 2*count; +ovector = match_data->ovector; +size = sizeof(pcre2_memctl) + sizeof(PCRE2_UCHAR *); /* For final NULL */ +if (lengthsptr != NULL) size += sizeof(PCRE2_SIZE) * count; /* For lengths */ + +for (i = 0; i < count2; i += 2) + { + size += sizeof(PCRE2_UCHAR *) + CU2BYTES(1); + if (ovector[i+1] > ovector[i]) size += CU2BYTES(ovector[i+1] - ovector[i]); + } + +memp = PRIV(memctl_malloc)(size, (pcre2_memctl *)match_data); +if (memp == NULL) return PCRE2_ERROR_NOMEMORY; + +*listptr = listp = (PCRE2_UCHAR **)((char *)memp + sizeof(pcre2_memctl)); +lensp = (PCRE2_SIZE *)((char *)listp + sizeof(PCRE2_UCHAR *) * (count + 1)); + +if (lengthsptr == NULL) + { + sp = (PCRE2_UCHAR *)lensp; + lensp = NULL; + } +else + { + *lengthsptr = lensp; + sp = (PCRE2_UCHAR *)((char *)lensp + sizeof(PCRE2_SIZE) * count); + } + +for (i = 0; i < count2; i += 2) + { + size = (ovector[i+1] > ovector[i])? (ovector[i+1] - ovector[i]) : 0; + memcpy(sp, match_data->subject + ovector[i], CU2BYTES(size)); + *listp++ = sp; + if (lensp != NULL) *lensp++ = size; + sp += size; + *sp++ = 0; + } + +*listp = NULL; +return 0; +} + + + +/************************************************* +* Free memory obtained by substring_list_get * +*************************************************/ + +/* +Argument: the result of a previous pcre2_substring_list_get() +Returns: nothing +*/ + +PCRE2_EXP_DEFN void PCRE2_CALL_CONVENTION +pcre2_substring_list_free(PCRE2_SPTR *list) +{ +pcre2_memctl *memctl = (pcre2_memctl *)((char *)list - sizeof(pcre2_memctl)); +memctl->free(memctl, memctl->memory_data); +} + + + +/************************************************* +* Find (multiple) entries for named string * +*************************************************/ + +/* This function scans the nametable for a given name, using binary chop. It +returns either two pointers to the entries in the table, or, if no pointers are +given, the number of a unique group with the given name. If duplicate names are +permitted, and the name is not unique, an error is generated. + +Arguments: + code the compiled regex + stringname the name whose entries required + firstptr where to put the pointer to the first entry + lastptr where to put the pointer to the last entry + +Returns: PCRE2_ERROR_NOSUBSTRING if the name is not found + otherwise, if firstptr and lastptr are NULL: + a group number for a unique substring + else PCRE2_ERROR_NOUNIQUESUBSTRING + otherwise: + the length of each entry, having set firstptr and lastptr +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_nametable_scan(const pcre2_code *code, PCRE2_SPTR stringname, + PCRE2_SPTR *firstptr, PCRE2_SPTR *lastptr) +{ +uint16_t bot = 0; +uint16_t top = code->name_count; +uint16_t entrysize = code->name_entry_size; +PCRE2_SPTR nametable = (PCRE2_SPTR)((char *)code + sizeof(pcre2_real_code)); + +while (top > bot) + { + uint16_t mid = (top + bot) / 2; + PCRE2_SPTR entry = nametable + entrysize*mid; + int c = PRIV(strcmp)(stringname, entry + IMM2_SIZE); + if (c == 0) + { + PCRE2_SPTR first; + PCRE2_SPTR last; + PCRE2_SPTR lastentry; + lastentry = nametable + entrysize * (code->name_count - 1); + first = last = entry; + while (first > nametable) + { + if (PRIV(strcmp)(stringname, (first - entrysize + IMM2_SIZE)) != 0) break; + first -= entrysize; + } + while (last < lastentry) + { + if (PRIV(strcmp)(stringname, (last + entrysize + IMM2_SIZE)) != 0) break; + last += entrysize; + } + if (firstptr == NULL) return (first == last)? + (int)GET2(entry, 0) : PCRE2_ERROR_NOUNIQUESUBSTRING; + *firstptr = first; + *lastptr = last; + return entrysize; + } + if (c > 0) bot = mid + 1; else top = mid; + } + +return PCRE2_ERROR_NOSUBSTRING; +} + + +/************************************************* +* Find number for named string * +*************************************************/ + +/* This function is a convenience wrapper for pcre2_substring_nametable_scan() +when it is known that names are unique. If there are duplicate names, it is not +defined which number is returned. + +Arguments: + code the compiled regex + stringname the name whose number is required + +Returns: the number of the named parenthesis, or a negative number + PCRE2_ERROR_NOSUBSTRING if not found + PCRE2_ERROR_NOUNIQUESUBSTRING if not unique +*/ + +PCRE2_EXP_DEFN int PCRE2_CALL_CONVENTION +pcre2_substring_number_from_name(const pcre2_code *code, + PCRE2_SPTR stringname) +{ +return pcre2_substring_nametable_scan(code, stringname, NULL, NULL); +} + +/* End of pcre2_substring.c */ diff --git a/ProcessHacker/pcre/pcre2_tables.c b/ProcessHacker/pcre/pcre2_tables.c new file mode 100644 index 0000000..a9e6bc0 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_tables.c @@ -0,0 +1,766 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains some fixed tables that are used by more than one of the +PCRE code modules. The tables are also #included by the pcre2test program, +which uses macros to change their names from _pcre2_xxx to xxxx, thereby +avoiding name clashes with the library. In this case, PCRE2_PCRE2TEST is +defined. */ + +#ifndef PCRE2_PCRE2TEST /* We're compiling the library */ +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "pcre2_internal.h" +#endif /* PCRE2_PCRE2TEST */ + + +/* Table of sizes for the fixed-length opcodes. It's defined in a macro so that +the definition is next to the definition of the opcodes in pcre2_internal.h. +This is mode-dependent, so is skipped when this file is included by pcre2test. */ + +#ifndef PCRE2_PCRE2TEST +const uint8_t PRIV(OP_lengths)[] = { OP_LENGTHS }; +#endif + +/* Tables of horizontal and vertical whitespace characters, suitable for +adding to classes. */ + +const uint32_t PRIV(hspace_list)[] = { HSPACE_LIST }; +const uint32_t PRIV(vspace_list)[] = { VSPACE_LIST }; + +/* These tables are the pairs of delimiters that are valid for callout string +arguments. For each starting delimiter there must be a matching ending +delimiter, which in fact is different only for bracket-like delimiters. */ + +const uint32_t PRIV(callout_start_delims)[] = { + CHAR_GRAVE_ACCENT, CHAR_APOSTROPHE, CHAR_QUOTATION_MARK, + CHAR_CIRCUMFLEX_ACCENT, CHAR_PERCENT_SIGN, CHAR_NUMBER_SIGN, + CHAR_DOLLAR_SIGN, CHAR_LEFT_CURLY_BRACKET, 0 }; + +const uint32_t PRIV(callout_end_delims[]) = { + CHAR_GRAVE_ACCENT, CHAR_APOSTROPHE, CHAR_QUOTATION_MARK, + CHAR_CIRCUMFLEX_ACCENT, CHAR_PERCENT_SIGN, CHAR_NUMBER_SIGN, + CHAR_DOLLAR_SIGN, CHAR_RIGHT_CURLY_BRACKET, 0 }; + + +/************************************************* +* Tables for UTF-8 support * +*************************************************/ + +/* These tables are required by pcre2test in 16- or 32-bit mode, as well +as for the library in 8-bit mode, because pcre2test uses UTF-8 internally for +handling wide characters. */ + +#if defined PCRE2_PCRE2TEST || \ + (defined SUPPORT_UNICODE && \ + defined PCRE2_CODE_UNIT_WIDTH && \ + PCRE2_CODE_UNIT_WIDTH == 8) + +/* These are the breakpoints for different numbers of bytes in a UTF-8 +character. */ + +const int PRIV(utf8_table1)[] = + { 0x7f, 0x7ff, 0xffff, 0x1fffff, 0x3ffffff, 0x7fffffff}; + +const int PRIV(utf8_table1_size) = sizeof(PRIV(utf8_table1)) / sizeof(int); + +/* These are the indicator bits and the mask for the data bits to set in the +first byte of a character, indexed by the number of additional bytes. */ + +const int PRIV(utf8_table2)[] = { 0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc}; +const int PRIV(utf8_table3)[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01}; + +/* Table of the number of extra bytes, indexed by the first byte masked with +0x3f. The highest number for a valid UTF-8 first byte is in fact 0x3d. */ + +const uint8_t PRIV(utf8_table4)[] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; + +#endif /* UTF-8 support needed */ + + +#ifdef SUPPORT_UNICODE + +/* Table to translate from particular type value to the general value. */ + +const uint32_t PRIV(ucp_gentype)[] = { + ucp_C, ucp_C, ucp_C, ucp_C, ucp_C, /* Cc, Cf, Cn, Co, Cs */ + ucp_L, ucp_L, ucp_L, ucp_L, ucp_L, /* Ll, Lu, Lm, Lo, Lt */ + ucp_M, ucp_M, ucp_M, /* Mc, Me, Mn */ + ucp_N, ucp_N, ucp_N, /* Nd, Nl, No */ + ucp_P, ucp_P, ucp_P, ucp_P, ucp_P, /* Pc, Pd, Pe, Pf, Pi */ + ucp_P, ucp_P, /* Ps, Po */ + ucp_S, ucp_S, ucp_S, ucp_S, /* Sc, Sk, Sm, So */ + ucp_Z, ucp_Z, ucp_Z /* Zl, Zp, Zs */ +}; + +/* This table encodes the rules for finding the end of an extended grapheme +cluster. Every code point has a grapheme break property which is one of the +ucp_gbXX values defined in pcre2_ucp.h. The 2-dimensional table is indexed by +the properties of two adjacent code points. The left property selects a word +from the table, and the right property selects a bit from that word like this: + + PRIV(ucp_gbtable)[left-property] & (1 << right-property) + +The value is non-zero if a grapheme break is NOT permitted between the relevant +two code points. The breaking rules are as follows: + +1. Break at the start and end of text (pretty obviously). + +2. Do not break between a CR and LF; otherwise, break before and after + controls. + +3. Do not break Hangul syllable sequences, the rules for which are: + + L may be followed by L, V, LV or LVT + LV or V may be followed by V or T + LVT or T may be followed by T + +4. Do not break before extending characters. + +The next two rules are only for extended grapheme clusters (but that's what we +are implementing). + +5. Do not break before SpacingMarks. + +6. Do not break after Prepend characters. + +7. Otherwise, break everywhere. +*/ + +const uint32_t PRIV(ucp_gbtable)[] = { + (1< 0x10ffff is not permitted +PCRE2_ERROR_UTF8_ERR14 3-byte character with value 0xd800-0xdfff is not permitted +PCRE2_ERROR_UTF8_ERR15 Overlong 2-byte sequence +PCRE2_ERROR_UTF8_ERR16 Overlong 3-byte sequence +PCRE2_ERROR_UTF8_ERR17 Overlong 4-byte sequence +PCRE2_ERROR_UTF8_ERR18 Overlong 5-byte sequence (won't ever occur) +PCRE2_ERROR_UTF8_ERR19 Overlong 6-byte sequence (won't ever occur) +PCRE2_ERROR_UTF8_ERR20 Isolated 0x80 byte (not within UTF-8 character) +PCRE2_ERROR_UTF8_ERR21 Byte with the illegal value 0xfe or 0xff +*/ + +for (p = string; length > 0; p++) + { + register uint32_t ab, d; + + c = *p; + length--; + + if (c < 128) continue; /* ASCII character */ + + if (c < 0xc0) /* Isolated 10xx xxxx byte */ + { + *erroroffset = (int)(p - string); + return PCRE2_ERROR_UTF8_ERR20; + } + + if (c >= 0xfe) /* Invalid 0xfe or 0xff bytes */ + { + *erroroffset = (int)(p - string); + return PCRE2_ERROR_UTF8_ERR21; + } + + ab = PRIV(utf8_table4)[c & 0x3f]; /* Number of additional bytes (1-5) */ + if (length < ab) /* Missing bytes */ + { + *erroroffset = (int)(p - string); + switch(ab - length) + { + case 1: return PCRE2_ERROR_UTF8_ERR1; + case 2: return PCRE2_ERROR_UTF8_ERR2; + case 3: return PCRE2_ERROR_UTF8_ERR3; + case 4: return PCRE2_ERROR_UTF8_ERR4; + case 5: return PCRE2_ERROR_UTF8_ERR5; + } + } + length -= ab; /* Length remaining */ + + /* Check top bits in the second byte */ + + if (((d = *(++p)) & 0xc0) != 0x80) + { + *erroroffset = (int)(p - string) - 1; + return PCRE2_ERROR_UTF8_ERR6; + } + + /* For each length, check that the remaining bytes start with the 0x80 bit + set and not the 0x40 bit. Then check for an overlong sequence, and for the + excluded range 0xd800 to 0xdfff. */ + + switch (ab) + { + /* 2-byte character. No further bytes to check for 0x80. Check first byte + for for xx00 000x (overlong sequence). */ + + case 1: if ((c & 0x3e) == 0) + { + *erroroffset = (int)(p - string) - 1; + return PCRE2_ERROR_UTF8_ERR15; + } + break; + + /* 3-byte character. Check third byte for 0x80. Then check first 2 bytes + for 1110 0000, xx0x xxxx (overlong sequence) or + 1110 1101, 1010 xxxx (0xd800 - 0xdfff) */ + + case 2: + if ((*(++p) & 0xc0) != 0x80) /* Third byte */ + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR7; + } + if (c == 0xe0 && (d & 0x20) == 0) + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR16; + } + if (c == 0xed && d >= 0xa0) + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR14; + } + break; + + /* 4-byte character. Check 3rd and 4th bytes for 0x80. Then check first 2 + bytes for for 1111 0000, xx00 xxxx (overlong sequence), then check for a + character greater than 0x0010ffff (f4 8f bf bf) */ + + case 3: + if ((*(++p) & 0xc0) != 0x80) /* Third byte */ + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR7; + } + if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */ + { + *erroroffset = (int)(p - string) - 3; + return PCRE2_ERROR_UTF8_ERR8; + } + if (c == 0xf0 && (d & 0x30) == 0) + { + *erroroffset = (int)(p - string) - 3; + return PCRE2_ERROR_UTF8_ERR17; + } + if (c > 0xf4 || (c == 0xf4 && d > 0x8f)) + { + *erroroffset = (int)(p - string) - 3; + return PCRE2_ERROR_UTF8_ERR13; + } + break; + + /* 5-byte and 6-byte characters are not allowed by RFC 3629, and will be + rejected by the length test below. However, we do the appropriate tests + here so that overlong sequences get diagnosed, and also in case there is + ever an option for handling these larger code points. */ + + /* 5-byte character. Check 3rd, 4th, and 5th bytes for 0x80. Then check for + 1111 1000, xx00 0xxx */ + + case 4: + if ((*(++p) & 0xc0) != 0x80) /* Third byte */ + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR7; + } + if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */ + { + *erroroffset = (int)(p - string) - 3; + return PCRE2_ERROR_UTF8_ERR8; + } + if ((*(++p) & 0xc0) != 0x80) /* Fifth byte */ + { + *erroroffset = (int)(p - string) - 4; + return PCRE2_ERROR_UTF8_ERR9; + } + if (c == 0xf8 && (d & 0x38) == 0) + { + *erroroffset = (int)(p - string) - 4; + return PCRE2_ERROR_UTF8_ERR18; + } + break; + + /* 6-byte character. Check 3rd-6th bytes for 0x80. Then check for + 1111 1100, xx00 00xx. */ + + case 5: + if ((*(++p) & 0xc0) != 0x80) /* Third byte */ + { + *erroroffset = (int)(p - string) - 2; + return PCRE2_ERROR_UTF8_ERR7; + } + if ((*(++p) & 0xc0) != 0x80) /* Fourth byte */ + { + *erroroffset = (int)(p - string) - 3; + return PCRE2_ERROR_UTF8_ERR8; + } + if ((*(++p) & 0xc0) != 0x80) /* Fifth byte */ + { + *erroroffset = (int)(p - string) - 4; + return PCRE2_ERROR_UTF8_ERR9; + } + if ((*(++p) & 0xc0) != 0x80) /* Sixth byte */ + { + *erroroffset = (int)(p - string) - 5; + return PCRE2_ERROR_UTF8_ERR10; + } + if (c == 0xfc && (d & 0x3c) == 0) + { + *erroroffset = (int)(p - string) - 5; + return PCRE2_ERROR_UTF8_ERR19; + } + break; + } + + /* Character is valid under RFC 2279, but 4-byte and 5-byte characters are + excluded by RFC 3629. The pointer p is currently at the last byte of the + character. */ + + if (ab > 3) + { + *erroroffset = (int)(p - string) - ab; + return (ab == 4)? PCRE2_ERROR_UTF8_ERR11 : PCRE2_ERROR_UTF8_ERR12; + } + } +return 0; + + +/* ----------------- Check a UTF-16 string ----------------- */ + +#elif PCRE2_CODE_UNIT_WIDTH == 16 + +/* There's not so much work, nor so many errors, for UTF-16. +PCRE2_ERROR_UTF16_ERR1 Missing low surrogate at the end of the string +PCRE2_ERROR_UTF16_ERR2 Invalid low surrogate +PCRE2_ERROR_UTF16_ERR3 Isolated low surrogate +*/ + +for (p = string; length > 0; p++) + { + c = *p; + length--; + + if ((c & 0xf800) != 0xd800) + { + /* Normal UTF-16 code point. Neither high nor low surrogate. */ + } + else if ((c & 0x0400) == 0) + { + /* High surrogate. Must be a followed by a low surrogate. */ + if (length == 0) + { + *erroroffset = p - string; + return PCRE2_ERROR_UTF16_ERR1; + } + p++; + length--; + if ((*p & 0xfc00) != 0xdc00) + { + *erroroffset = p - string; + return PCRE2_ERROR_UTF16_ERR2; + } + } + else + { + /* Isolated low surrogate. Always an error. */ + *erroroffset = p - string; + return PCRE2_ERROR_UTF16_ERR3; + } + } +return 0; + + + +/* ----------------- Check a UTF-32 string ----------------- */ + +#else + +/* There is very little to do for a UTF-32 string. +PCRE2_ERROR_UTF32_ERR1 Surrogate character +PCRE2_ERROR_UTF32_ERR2 Character > 0x10ffff +*/ + +for (p = string; length > 0; length--, p++) + { + c = *p; + if ((c & 0xfffff800u) != 0xd800u) + { + /* Normal UTF-32 code point. Neither high nor low surrogate. */ + if (c > 0x10ffffu) + { + *erroroffset = p - string; + return PCRE2_ERROR_UTF32_ERR2; + } + } + else + { + /* A surrogate */ + *erroroffset = p - string; + return PCRE2_ERROR_UTF32_ERR1; + } + } +return 0; +#endif /* CODE_UNIT_WIDTH */ +} +#endif /* SUPPORT_UNICODE */ + +/* End of pcre2_valid_utf.c */ diff --git a/ProcessHacker/pcre/pcre2_xclass.c b/ProcessHacker/pcre/pcre2_xclass.c new file mode 100644 index 0000000..ccff770 --- /dev/null +++ b/ProcessHacker/pcre/pcre2_xclass.c @@ -0,0 +1,271 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +/* This module contains an internal function that is used to match an extended +class. It is used by pcre2_auto_possessify() and by both pcre2_match() and +pcre2_def_match(). */ + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "pcre2_internal.h" + +/************************************************* +* Match character against an XCLASS * +*************************************************/ + +/* This function is called to match a character against an extended class that +might contain codepoints above 255 and/or Unicode properties. + +Arguments: + c the character + data points to the flag code unit of the XCLASS data + utf TRUE if in UTF mode + +Returns: TRUE if character matches, else FALSE +*/ + +BOOL +PRIV(xclass)(uint32_t c, PCRE2_SPTR data, BOOL utf) +{ +PCRE2_UCHAR t; +BOOL negated = (*data & XCL_NOT) != 0; + +#if PCRE2_CODE_UNIT_WIDTH == 8 +/* In 8 bit mode, this must always be TRUE. Help the compiler to know that. */ +utf = TRUE; +#endif + +/* Code points < 256 are matched against a bitmap, if one is present. If not, +we still carry on, because there may be ranges that start below 256 in the +additional data. */ + +if (c < 256) + { + if ((*data & XCL_HASPROP) == 0) + { + if ((*data & XCL_MAP) == 0) return negated; + return (((uint8_t *)(data + 1))[c/8] & (1 << (c&7))) != 0; + } + if ((*data & XCL_MAP) != 0 && + (((uint8_t *)(data + 1))[c/8] & (1 << (c&7))) != 0) + return !negated; /* char found */ + } + +/* First skip the bit map if present. Then match against the list of Unicode +properties or large chars or ranges that end with a large char. We won't ever +encounter XCL_PROP or XCL_NOTPROP when UTF support is not compiled. */ + +if ((*data++ & XCL_MAP) != 0) data += 32 / sizeof(PCRE2_UCHAR); + +while ((t = *data++) != XCL_END) + { + uint32_t x, y; + if (t == XCL_SINGLE) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + GETCHARINC(x, data); /* macro generates multiple statements */ + } + else +#endif + x = *data++; + if (c == x) return !negated; + } + else if (t == XCL_RANGE) + { +#ifdef SUPPORT_UNICODE + if (utf) + { + GETCHARINC(x, data); /* macro generates multiple statements */ + GETCHARINC(y, data); /* macro generates multiple statements */ + } + else +#endif + { + x = *data++; + y = *data++; + } + if (c >= x && c <= y) return !negated; + } + +#ifdef SUPPORT_UNICODE + else /* XCL_PROP & XCL_NOTPROP */ + { + const ucd_record *prop = GET_UCD(c); + BOOL isprop = t == XCL_PROP; + + switch(*data) + { + case PT_ANY: + if (isprop) return !negated; + break; + + case PT_LAMP: + if ((prop->chartype == ucp_Lu || prop->chartype == ucp_Ll || + prop->chartype == ucp_Lt) == isprop) return !negated; + break; + + case PT_GC: + if ((data[1] == PRIV(ucp_gentype)[prop->chartype]) == isprop) + return !negated; + break; + + case PT_PC: + if ((data[1] == prop->chartype) == isprop) return !negated; + break; + + case PT_SC: + if ((data[1] == prop->script) == isprop) return !negated; + break; + + case PT_ALNUM: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N) == isprop) + return !negated; + break; + + /* Perl space used to exclude VT, but from Perl 5.18 it is included, + which means that Perl space and POSIX space are now identical. PCRE + was changed at release 8.34. */ + + case PT_SPACE: /* Perl space */ + case PT_PXSPACE: /* POSIX space */ + switch(c) + { + HSPACE_CASES: + VSPACE_CASES: + if (isprop) return !negated; + break; + + default: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_Z) == isprop) + return !negated; + break; + } + break; + + case PT_WORD: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_L || + PRIV(ucp_gentype)[prop->chartype] == ucp_N || c == CHAR_UNDERSCORE) + == isprop) + return !negated; + break; + + case PT_UCNC: + if (c < 0xa0) + { + if ((c == CHAR_DOLLAR_SIGN || c == CHAR_COMMERCIAL_AT || + c == CHAR_GRAVE_ACCENT) == isprop) + return !negated; + } + else + { + if ((c < 0xd800 || c > 0xdfff) == isprop) + return !negated; + } + break; + + /* The following three properties can occur only in an XCLASS, as there + is no \p or \P coding for them. */ + + /* Graphic character. Implement this as not Z (space or separator) and + not C (other), except for Cf (format) with a few exceptions. This seems + to be what Perl does. The exceptional characters are: + + U+061C Arabic Letter Mark + U+180E Mongolian Vowel Separator + U+2066 - U+2069 Various "isolate"s + */ + + case PT_PXGRAPH: + if ((PRIV(ucp_gentype)[prop->chartype] != ucp_Z && + (PRIV(ucp_gentype)[prop->chartype] != ucp_C || + (prop->chartype == ucp_Cf && + c != 0x061c && c != 0x180e && (c < 0x2066 || c > 0x2069)) + )) == isprop) + return !negated; + break; + + /* Printable character: same as graphic, with the addition of Zs, i.e. + not Zl and not Zp, and U+180E. */ + + case PT_PXPRINT: + if ((prop->chartype != ucp_Zl && + prop->chartype != ucp_Zp && + (PRIV(ucp_gentype)[prop->chartype] != ucp_C || + (prop->chartype == ucp_Cf && + c != 0x061c && (c < 0x2066 || c > 0x2069)) + )) == isprop) + return !negated; + break; + + /* Punctuation: all Unicode punctuation, plus ASCII characters that + Unicode treats as symbols rather than punctuation, for Perl + compatibility (these are $+<=>^`|~). */ + + case PT_PXPUNCT: + if ((PRIV(ucp_gentype)[prop->chartype] == ucp_P || + (c < 128 && PRIV(ucp_gentype)[prop->chartype] == ucp_S)) == isprop) + return !negated; + break; + + /* This should never occur, but compilers may mutter if there is no + default. */ + + default: + return FALSE; + } + + data += 2; + } +#else + (void)utf; /* Avoid compiler warning */ +#endif /* SUPPORT_UNICODE */ + } + +return negated; /* char did not match */ +} + +/* End of pcre2_xclass.c */ diff --git a/ProcessHacker/pcre/pcre2posix.c b/ProcessHacker/pcre/pcre2posix.c new file mode 100644 index 0000000..230b8ad --- /dev/null +++ b/ProcessHacker/pcre/pcre2posix.c @@ -0,0 +1,341 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* This module is a wrapper that provides a POSIX API to the underlying PCRE2 +functions. */ + +// dmex: Disable warnings +#pragma warning(push) +#pragma warning(disable : 4996) +#pragma warning(disable : 4267) + +#define HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +/* Ensure that the PCRE2POSIX_EXP_xxx macros are set appropriately for +compiling these functions. This must come before including pcre2posix.h, where +they are set for an application (using these functions) if they have not +previously been set. */ + +#if defined(_WIN32) && !defined(PCRE2_STATIC) +# define PCRE2POSIX_EXP_DECL extern __declspec(dllexport) +# define PCRE2POSIX_EXP_DEFN __declspec(dllexport) +#endif + +/* We include pcre2.h before pcre2_internal.h so that the PCRE2 library +functions are declared as "import" for Windows by defining PCRE2_EXP_DECL as +"import". This is needed even though pcre2_internal.h itself includes pcre2.h, +because it does so after it has set PCRE2_EXP_DECL to "export" if it is not +already set. */ + +#include "pcre2.h" +#include "pcre2_internal.h" +#include "pcre2posix.h" + +/* Table to translate PCRE2 compile time error codes into POSIX error codes. +Only a few PCRE2 errors with a value greater than 23 turn into special POSIX +codes: most go to REG_BADPAT. The second table lists, in pairs, those that +don't. */ + +static const int eint1[] = { + 0, /* No error */ + REG_EESCAPE, /* \ at end of pattern */ + REG_EESCAPE, /* \c at end of pattern */ + REG_EESCAPE, /* unrecognized character follows \ */ + REG_BADBR, /* numbers out of order in {} quantifier */ + /* 5 */ + REG_BADBR, /* number too big in {} quantifier */ + REG_EBRACK, /* missing terminating ] for character class */ + REG_ECTYPE, /* invalid escape sequence in character class */ + REG_ERANGE, /* range out of order in character class */ + REG_BADRPT, /* nothing to repeat */ + /* 10 */ + REG_ASSERT, /* internal error: unexpected repeat */ + REG_BADPAT, /* unrecognized character after (? or (?- */ + REG_BADPAT, /* POSIX named classes are supported only within a class */ + REG_BADPAT, /* POSIX collating elements are not supported */ + REG_EPAREN, /* missing ) */ + /* 15 */ + REG_ESUBREG, /* reference to non-existent subpattern */ + REG_INVARG, /* pattern passed as NULL */ + REG_INVARG, /* unknown compile-time option bit(s) */ + REG_EPAREN, /* missing ) after (?# comment */ + REG_ESIZE, /* parentheses nested too deeply */ + /* 20 */ + REG_ESIZE, /* regular expression too large */ + REG_ESPACE, /* failed to get memory */ + REG_EPAREN, /* unmatched closing parenthesis */ + REG_ASSERT /* internal error: code overflow */ + }; + +static const int eint2[] = { + 30, REG_ECTYPE, /* unknown POSIX class name */ + 32, REG_INVARG, /* this version of PCRE2 does not have Unicode support */ + 37, REG_EESCAPE, /* PCRE2 does not support \L, \l, \N{name}, \U, or \u */ + 56, REG_INVARG, /* internal error: unknown newline setting */ +}; + +/* Table of texts corresponding to POSIX error codes */ + +static const char *const pstring[] = { + "", /* Dummy for value 0 */ + "internal error", /* REG_ASSERT */ + "invalid repeat counts in {}", /* BADBR */ + "pattern error", /* BADPAT */ + "? * + invalid", /* BADRPT */ + "unbalanced {}", /* EBRACE */ + "unbalanced []", /* EBRACK */ + "collation error - not relevant", /* ECOLLATE */ + "bad class", /* ECTYPE */ + "bad escape sequence", /* EESCAPE */ + "empty expression", /* EMPTY */ + "unbalanced ()", /* EPAREN */ + "bad range inside []", /* ERANGE */ + "expression too big", /* ESIZE */ + "failed to get memory", /* ESPACE */ + "bad back reference", /* ESUBREG */ + "bad argument", /* INVARG */ + "match failed" /* NOMATCH */ +}; + + + + +/************************************************* +* Translate error code to string * +*************************************************/ + +PCRE2POSIX_EXP_DEFN size_t PCRE2_CALL_CONVENTION +regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) +{ +int used; +const char *message; + +message = (errcode <= 0 || errcode >= (int)(sizeof(pstring)/sizeof(char *)))? + "unknown error code" : pstring[errcode]; + +if (preg != NULL && (int)preg->re_erroffset != -1) + { + used = snprintf(errbuf, errbuf_size, "%s at offset %-6d", message, + (int)preg->re_erroffset); + } +else + { + used = snprintf(errbuf, errbuf_size, "%s", message); + } + +return used + 1; +} + + + + +/************************************************* +* Free store held by a regex * +*************************************************/ + +PCRE2POSIX_EXP_DEFN void PCRE2_CALL_CONVENTION +regfree(regex_t *preg) +{ +pcre2_match_data_free(preg->re_match_data); +pcre2_code_free(preg->re_pcre2_code); +} + + + + +/************************************************* +* Compile a regular expression * +*************************************************/ + +/* +Arguments: + preg points to a structure for recording the compiled expression + pattern the pattern to compile + cflags compilation flags + +Returns: 0 on success + various non-zero codes on failure +*/ + +PCRE2POSIX_EXP_DEFN int PCRE2_CALL_CONVENTION +regcomp(regex_t *preg, const char *pattern, int cflags) +{ +PCRE2_SIZE erroffset; +int errorcode; +int options = 0; +int re_nsub = 0; + +if ((cflags & REG_ICASE) != 0) options |= PCRE2_CASELESS; +if ((cflags & REG_NEWLINE) != 0) options |= PCRE2_MULTILINE; +if ((cflags & REG_DOTALL) != 0) options |= PCRE2_DOTALL; +if ((cflags & REG_NOSUB) != 0) options |= PCRE2_NO_AUTO_CAPTURE; +if ((cflags & REG_UTF) != 0) options |= PCRE2_UTF; +if ((cflags & REG_UCP) != 0) options |= PCRE2_UCP; +if ((cflags & REG_UNGREEDY) != 0) options |= PCRE2_UNGREEDY; + +preg->re_pcre2_code = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, + options, &errorcode, &erroffset, NULL); +preg->re_erroffset = erroffset; + +if (preg->re_pcre2_code == NULL) + { + unsigned int i; + + /* A negative value is a UTF error; otherwise all error codes are greater + than COMPILE_ERROR_BASE, but check, just in case. */ + + if (errorcode < COMPILE_ERROR_BASE) return REG_BADPAT; + errorcode -= COMPILE_ERROR_BASE; + + if (errorcode < (int)(sizeof(eint1)/sizeof(const int))) + return eint1[errorcode]; + for (i = 0; i < sizeof(eint2)/(2*sizeof(const int)); i += 2) + if (errorcode == eint2[i]) return eint2[i+1]; + return REG_BADPAT; + } + +(void)pcre2_pattern_info((const pcre2_code *)preg->re_pcre2_code, + PCRE2_INFO_CAPTURECOUNT, &re_nsub); +preg->re_nsub = (size_t)re_nsub; +if ((options & PCRE2_NO_AUTO_CAPTURE) != 0) re_nsub = -1; +preg->re_match_data = pcre2_match_data_create(re_nsub + 1, NULL); + +if (preg->re_match_data == NULL) + { + pcre2_code_free(preg->re_pcre2_code); + return REG_ESPACE; + } + +return 0; +} + + + +/************************************************* +* Match a regular expression * +*************************************************/ + +/* A suitable match_data block, large enough to hold all possible captures, was +obtained when the pattern was compiled, to save having to allocate and free it +for each match. If REG_NOSUB was specified at compile time, the +PCRE_NO_AUTO_CAPTURE flag will be set. When this is the case, the nmatch and +pmatch arguments are ignored, and the only result is yes/no/error. */ + +PCRE2POSIX_EXP_DEFN int PCRE2_CALL_CONVENTION +regexec(const regex_t *preg, const char *string, size_t nmatch, + regmatch_t pmatch[], int eflags) +{ +int rc, so, eo; +int options = 0; +pcre2_match_data *md = (pcre2_match_data *)preg->re_match_data; + +if ((eflags & REG_NOTBOL) != 0) options |= PCRE2_NOTBOL; +if ((eflags & REG_NOTEOL) != 0) options |= PCRE2_NOTEOL; +if ((eflags & REG_NOTEMPTY) != 0) options |= PCRE2_NOTEMPTY; + +((regex_t *)preg)->re_erroffset = (size_t)(-1); /* Only has meaning after compile */ + +/* When no string data is being returned, or no vector has been passed in which +to put it, ensure that nmatch is zero. */ + +if ((((pcre2_real_code *)(preg->re_pcre2_code))->compile_options & + PCRE2_NO_AUTO_CAPTURE) != 0 || pmatch == NULL) nmatch = 0; + +/* REG_STARTEND is a BSD extension, to allow for non-NUL-terminated strings. +The man page from OS X says "REG_STARTEND affects only the location of the +string, not how it is matched". That is why the "so" value is used to bump the +start location rather than being passed as a PCRE2 "starting offset". */ + +if ((eflags & REG_STARTEND) != 0) + { + if (pmatch == NULL) return REG_INVARG; + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + } +else + { + so = 0; + eo = (int)strlen(string); + } + +rc = pcre2_match((const pcre2_code *)preg->re_pcre2_code, + (PCRE2_SPTR)string + so, (eo - so), 0, options, md, NULL); + +/* Successful match */ + +if (rc >= 0) + { + size_t i; + if ((size_t)rc > nmatch) rc = (int)nmatch; + for (i = 0; i < (size_t)rc; i++) + { + pmatch[i].rm_so = md->ovector[i*2]; + pmatch[i].rm_eo = md->ovector[i*2+1]; + } + for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; + return 0; + } + +/* Unsuccessful match */ + +if (rc <= PCRE2_ERROR_UTF8_ERR1 && rc >= PCRE2_ERROR_UTF8_ERR21) + return REG_INVARG; + +switch(rc) + { + default: return REG_ASSERT; + case PCRE2_ERROR_BADMODE: return REG_INVARG; + case PCRE2_ERROR_BADMAGIC: return REG_INVARG; + case PCRE2_ERROR_BADOPTION: return REG_INVARG; + case PCRE2_ERROR_BADUTFOFFSET: return REG_INVARG; + case PCRE2_ERROR_MATCHLIMIT: return REG_ESPACE; + case PCRE2_ERROR_NOMATCH: return REG_NOMATCH; + case PCRE2_ERROR_NOMEMORY: return REG_ESPACE; + case PCRE2_ERROR_NULL: return REG_INVARG; + } +} + +/* End of pcre2posix.c */ +#pragma warning(pop) \ No newline at end of file diff --git a/ProcessHacker/pcre/pcre2posix.h b/ProcessHacker/pcre/pcre2posix.h new file mode 100644 index 0000000..44a2fd8 --- /dev/null +++ b/ProcessHacker/pcre/pcre2posix.h @@ -0,0 +1,146 @@ +/************************************************* +* Perl-Compatible Regular Expressions * +*************************************************/ + +/* PCRE2 is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + + Written by Philip Hazel + Original API code Copyright (c) 1997-2012 University of Cambridge + New API code Copyright (c) 2016 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + + +/* Have to include stdlib.h in order to ensure that size_t is defined. */ + +#include + +/* Allow for C++ users */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Options, mostly defined by POSIX, but with some extras. */ + +#define REG_ICASE 0x0001 /* Maps to PCRE2_CASELESS */ +#define REG_NEWLINE 0x0002 /* Maps to PCRE2_MULTILINE */ +#define REG_NOTBOL 0x0004 /* Maps to PCRE2_NOTBOL */ +#define REG_NOTEOL 0x0008 /* Maps to PCRE2_NOTEOL */ +#define REG_DOTALL 0x0010 /* NOT defined by POSIX; maps to PCRE2_DOTALL */ +#define REG_NOSUB 0x0020 /* Maps to PCRE2_NO_AUTO_CAPTURE */ +#define REG_UTF 0x0040 /* NOT defined by POSIX; maps to PCRE2_UTF */ +#define REG_STARTEND 0x0080 /* BSD feature: pass subject string by so,eo */ +#define REG_NOTEMPTY 0x0100 /* NOT defined by POSIX; maps to PCRE2_NOTEMPTY */ +#define REG_UNGREEDY 0x0200 /* NOT defined by POSIX; maps to PCRE2_UNGREEDY */ +#define REG_UCP 0x0400 /* NOT defined by POSIX; maps to PCRE2_UCP */ + +/* This is not used by PCRE2, but by defining it we make it easier +to slot PCRE2 into existing programs that make POSIX calls. */ + +#define REG_EXTENDED 0 + +/* Error values. Not all these are relevant or used by the wrapper. */ + +enum { + REG_ASSERT = 1, /* internal error ? */ + REG_BADBR, /* invalid repeat counts in {} */ + REG_BADPAT, /* pattern error */ + REG_BADRPT, /* ? * + invalid */ + REG_EBRACE, /* unbalanced {} */ + REG_EBRACK, /* unbalanced [] */ + REG_ECOLLATE, /* collation error - not relevant */ + REG_ECTYPE, /* bad class */ + REG_EESCAPE, /* bad escape sequence */ + REG_EMPTY, /* empty expression */ + REG_EPAREN, /* unbalanced () */ + REG_ERANGE, /* bad range inside [] */ + REG_ESIZE, /* expression too big */ + REG_ESPACE, /* failed to get memory */ + REG_ESUBREG, /* bad back reference */ + REG_INVARG, /* bad argument */ + REG_NOMATCH /* match failed */ +}; + + +/* The structure representing a compiled regular expression. */ + +typedef struct { + void *re_pcre2_code; + void *re_match_data; + size_t re_nsub; + size_t re_erroffset; +} regex_t; + +/* The structure in which a captured offset is returned. */ + +typedef int regoff_t; + +typedef struct { + regoff_t rm_so; + regoff_t rm_eo; +} regmatch_t; + +/* When an application links to a PCRE2 DLL in Windows, the symbols that are +imported have to be identified as such. When building PCRE2, the appropriate +export settings are needed, and are set in pcre2posix.c before including this +file. */ + +#if defined(_WIN32) && !defined(PCRE2_STATIC) && !defined(PCRE2POSIX_EXP_DECL) +# define PCRE2POSIX_EXP_DECL extern __declspec(dllimport) +# define PCRE2POSIX_EXP_DEFN __declspec(dllimport) +#endif + +/* By default, we use the standard "extern" declarations. */ + +#ifndef PCRE2POSIX_EXP_DECL +# ifdef __cplusplus +# define PCRE2POSIX_EXP_DECL extern "C" +# define PCRE2POSIX_EXP_DEFN extern "C" +# else +# define PCRE2POSIX_EXP_DECL extern +# define PCRE2POSIX_EXP_DEFN extern +# endif +#endif + +/* The functions */ + +PCRE2POSIX_EXP_DECL int regcomp(regex_t *, const char *, int); +PCRE2POSIX_EXP_DECL int regexec(const regex_t *, const char *, size_t, + regmatch_t *, int); +PCRE2POSIX_EXP_DECL size_t regerror(int, const regex_t *, char *, size_t); +PCRE2POSIX_EXP_DECL void regfree(regex_t *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +/* End of pcre2posix.h */ diff --git a/ProcessHacker/phsvc/clapi.c b/ProcessHacker/phsvc/clapi.c new file mode 100644 index 0000000..5a5cfb5 --- /dev/null +++ b/ProcessHacker/phsvc/clapi.c @@ -0,0 +1,1217 @@ +/* + * Process Hacker - + * phsvc client + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +HANDLE PhSvcClPortHandle; +PVOID PhSvcClPortHeap; +HANDLE PhSvcClServerProcessId; + +NTSTATUS PhSvcConnectToServer( + _In_ PUNICODE_STRING PortName, + _In_opt_ SIZE_T PortSectionSize + ) +{ + NTSTATUS status; + HANDLE sectionHandle; + LARGE_INTEGER sectionSize; + PORT_VIEW clientView; + REMOTE_PORT_VIEW serverView; + SECURITY_QUALITY_OF_SERVICE securityQos; + ULONG maxMessageLength; + PHSVC_API_CONNECTINFO connectInfo; + ULONG connectInfoLength; + + if (PhSvcClPortHandle) + return STATUS_ADDRESS_ALREADY_EXISTS; + + if (PortSectionSize == 0) + PortSectionSize = 512 * 1024; + + // Create the port section and connect to the port. + + sectionSize.QuadPart = PortSectionSize; + status = NtCreateSection( + §ionHandle, + SECTION_ALL_ACCESS, + NULL, + §ionSize, + PAGE_READWRITE, + SEC_COMMIT, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + clientView.Length = sizeof(PORT_VIEW); + clientView.SectionHandle = sectionHandle; + clientView.SectionOffset = 0; + clientView.ViewSize = PortSectionSize; + clientView.ViewBase = NULL; + clientView.ViewRemoteBase = NULL; + + serverView.Length = sizeof(REMOTE_PORT_VIEW); + serverView.ViewSize = 0; + serverView.ViewBase = NULL; + + securityQos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); + securityQos.ImpersonationLevel = SecurityImpersonation; + securityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; + securityQos.EffectiveOnly = TRUE; + + connectInfoLength = sizeof(PHSVC_API_CONNECTINFO); + + status = NtConnectPort( + &PhSvcClPortHandle, + PortName, + &securityQos, + &clientView, + &serverView, + &maxMessageLength, + &connectInfo, + &connectInfoLength + ); + NtClose(sectionHandle); + + if (!NT_SUCCESS(status)) + return status; + + PhSvcClServerProcessId = UlongToHandle(connectInfo.ServerProcessId); + + // Create the port heap. + + PhSvcClPortHeap = RtlCreateHeap( + HEAP_CLASS_1, + clientView.ViewBase, + clientView.ViewSize, + PAGE_SIZE, + NULL, + NULL + ); + + if (!PhSvcClPortHeap) + { + NtClose(PhSvcClPortHandle); + return STATUS_INSUFFICIENT_RESOURCES; + } + + return status; +} + +VOID PhSvcDisconnectFromServer( + VOID + ) +{ + if (PhSvcClPortHeap) + { + RtlDestroyHeap(PhSvcClPortHeap); + PhSvcClPortHeap = NULL; + } + + if (PhSvcClPortHandle) + { + NtClose(PhSvcClPortHandle); + PhSvcClPortHandle = NULL; + } + + PhSvcClServerProcessId = NULL; +} + +PVOID PhSvcpAllocateHeap( + _In_ SIZE_T Size, + _Out_ PULONG Offset + ) +{ + PVOID memory; + + if (!PhSvcClPortHeap) + return NULL; + + memory = RtlAllocateHeap(PhSvcClPortHeap, 0, Size); + + if (!memory) + return NULL; + + *Offset = (ULONG)((ULONG_PTR)memory - (ULONG_PTR)PhSvcClPortHeap); + + return memory; +} + +VOID PhSvcpFreeHeap( + _In_ PVOID Memory + ) +{ + if (!PhSvcClPortHeap) + return; + + RtlFreeHeap(PhSvcClPortHeap, 0, Memory); +} + +PVOID PhSvcpCreateString( + _In_opt_ PVOID String, + _In_ SIZE_T Length, + _Out_ PPH_RELATIVE_STRINGREF StringRef + ) +{ + PVOID memory; + SIZE_T length; + ULONG offset; + + if (Length != -1) + length = Length; + else + length = PhCountStringZ(String) * sizeof(WCHAR); + + if (length > MAXULONG32) + return NULL; + + memory = PhSvcpAllocateHeap(length, &offset); + + if (!memory) + return NULL; + + if (String) + memcpy(memory, String, length); + + StringRef->Length = (ULONG)length; + StringRef->Offset = offset; + + return memory; +} + +NTSTATUS PhSvcpCallServer( + _Inout_ PPHSVC_API_MSG Message + ) +{ + NTSTATUS status; + + Message->h.u1.s1.DataLength = sizeof(PHSVC_API_MSG) - FIELD_OFFSET(PHSVC_API_MSG, p); + Message->h.u1.s1.TotalLength = sizeof(PHSVC_API_MSG); + Message->h.u2.ZeroInit = 0; + + status = NtRequestWaitReplyPort(PhSvcClPortHandle, &Message->h, &Message->h); + + if (!NT_SUCCESS(status)) + return status; + + return Message->p.ReturnStatus; +} + +NTSTATUS PhSvcCallPlugin( + _In_ PPH_STRINGREF ApiId, + _In_reads_bytes_opt_(InLength) PVOID InBuffer, + _In_ ULONG InLength, + _Out_writes_bytes_opt_(OutLength) PVOID OutBuffer, + _In_ ULONG OutLength + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID apiId = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + if (InLength > sizeof(m.p.u.Plugin.i.Data)) + return STATUS_BUFFER_OVERFLOW; + + m.p.ApiNumber = PhSvcPluginApiNumber; + + if (!(apiId = PhSvcpCreateString(ApiId->Buffer, ApiId->Length, &m.p.u.Plugin.i.ApiId))) + return STATUS_NO_MEMORY; + + if (InLength != 0) + memcpy(m.p.u.Plugin.i.Data, InBuffer, InLength); + + status = PhSvcpCallServer(&m); + + if (OutLength != 0) + memcpy(OutBuffer, m.p.u.Plugin.o.Data, min(OutLength, sizeof(m.p.u.Plugin.o.Data))); + + if (apiId) + PhSvcpFreeHeap(apiId); + + return status; +} + +NTSTATUS PhSvcpCallExecuteRunAsCommand( + _In_ PHSVC_API_NUMBER ApiNumber, + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID userName = NULL; + PVOID password = NULL; + ULONG passwordLength; + PVOID currentDirectory = NULL; + PVOID commandLine = NULL; + PVOID fileName = NULL; + PVOID desktopName = NULL; + PVOID serviceName = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = ApiNumber; + + m.p.u.ExecuteRunAsCommand.i.ProcessId = Parameters->ProcessId; + m.p.u.ExecuteRunAsCommand.i.LogonType = Parameters->LogonType; + m.p.u.ExecuteRunAsCommand.i.SessionId = Parameters->SessionId; + m.p.u.ExecuteRunAsCommand.i.UseLinkedToken = Parameters->UseLinkedToken; + + status = STATUS_NO_MEMORY; + + if (Parameters->UserName && !(userName = PhSvcpCreateString(Parameters->UserName, -1, &m.p.u.ExecuteRunAsCommand.i.UserName))) + goto CleanupExit; + + if (Parameters->Password) + { + if (!(password = PhSvcpCreateString(Parameters->Password, -1, &m.p.u.ExecuteRunAsCommand.i.Password))) + goto CleanupExit; + + passwordLength = m.p.u.ExecuteRunAsCommand.i.Password.Length; + } + + if (Parameters->CurrentDirectory && !(currentDirectory = PhSvcpCreateString(Parameters->CurrentDirectory, -1, &m.p.u.ExecuteRunAsCommand.i.CurrentDirectory))) + goto CleanupExit; + if (Parameters->CommandLine && !(commandLine = PhSvcpCreateString(Parameters->CommandLine, -1, &m.p.u.ExecuteRunAsCommand.i.CommandLine))) + goto CleanupExit; + if (Parameters->FileName && !(fileName = PhSvcpCreateString(Parameters->FileName, -1, &m.p.u.ExecuteRunAsCommand.i.FileName))) + goto CleanupExit; + if (Parameters->DesktopName && !(desktopName = PhSvcpCreateString(Parameters->DesktopName, -1, &m.p.u.ExecuteRunAsCommand.i.DesktopName))) + goto CleanupExit; + if (Parameters->ServiceName && !(serviceName = PhSvcpCreateString(Parameters->ServiceName, -1, &m.p.u.ExecuteRunAsCommand.i.ServiceName))) + goto CleanupExit; + + status = PhSvcpCallServer(&m); + +CleanupExit: + if (serviceName) PhSvcpFreeHeap(serviceName); + if (desktopName) PhSvcpFreeHeap(desktopName); + if (fileName) PhSvcpFreeHeap(fileName); + if (commandLine) PhSvcpFreeHeap(commandLine); + if (currentDirectory) PhSvcpFreeHeap(currentDirectory); + + if (password) + { + RtlSecureZeroMemory(password, passwordLength); + PhSvcpFreeHeap(password); + } + + if (userName) PhSvcpFreeHeap(userName); + + return status; +} + +NTSTATUS PhSvcCallExecuteRunAsCommand( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + return PhSvcpCallExecuteRunAsCommand(PhSvcExecuteRunAsCommandApiNumber, Parameters); +} + +NTSTATUS PhSvcCallUnloadDriver( + _In_opt_ PVOID BaseAddress, + _In_opt_ PWSTR Name + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID name = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcUnloadDriverApiNumber; + + m.p.u.UnloadDriver.i.BaseAddress = BaseAddress; + + if (Name) + { + name = PhSvcpCreateString(Name, -1, &m.p.u.UnloadDriver.i.Name); + + if (!name) + return STATUS_NO_MEMORY; + } + + status = PhSvcpCallServer(&m); + + if (name) + PhSvcpFreeHeap(name); + + return status; +} + +NTSTATUS PhSvcCallControlProcess( + _In_ HANDLE ProcessId, + _In_ PHSVC_API_CONTROLPROCESS_COMMAND Command, + _In_ ULONG Argument + ) +{ + PHSVC_API_MSG m; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcControlProcessApiNumber; + m.p.u.ControlProcess.i.ProcessId = ProcessId; + m.p.u.ControlProcess.i.Command = Command; + m.p.u.ControlProcess.i.Argument = Argument; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallControlService( + _In_ PWSTR ServiceName, + _In_ PHSVC_API_CONTROLSERVICE_COMMAND Command + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID serviceName; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcControlServiceApiNumber; + + serviceName = PhSvcpCreateString(ServiceName, -1, &m.p.u.ControlService.i.ServiceName); + m.p.u.ControlService.i.Command = Command; + + if (serviceName) + { + status = PhSvcpCallServer(&m); + } + else + { + status = STATUS_NO_MEMORY; + } + + if (serviceName) + PhSvcpFreeHeap(serviceName); + + return status; +} + +NTSTATUS PhSvcCallCreateService( + _In_ PWSTR ServiceName, + _In_opt_ PWSTR DisplayName, + _In_ ULONG ServiceType, + _In_ ULONG StartType, + _In_ ULONG ErrorControl, + _In_opt_ PWSTR BinaryPathName, + _In_opt_ PWSTR LoadOrderGroup, + _Out_opt_ PULONG TagId, + _In_opt_ PWSTR Dependencies, + _In_opt_ PWSTR ServiceStartName, + _In_opt_ PWSTR Password + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID serviceName = NULL; + PVOID displayName = NULL; + PVOID binaryPathName = NULL; + PVOID loadOrderGroup = NULL; + PVOID dependencies = NULL; + PVOID serviceStartName = NULL; + PVOID password = NULL; + ULONG passwordLength; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcCreateServiceApiNumber; + + m.p.u.CreateService.i.ServiceType = ServiceType; + m.p.u.CreateService.i.StartType = StartType; + m.p.u.CreateService.i.ErrorControl = ErrorControl; + m.p.u.CreateService.i.TagIdSpecified = TagId != NULL; + + status = STATUS_NO_MEMORY; + + if (!(serviceName = PhSvcpCreateString(ServiceName, -1, &m.p.u.CreateService.i.ServiceName))) + goto CleanupExit; + if (DisplayName && !(displayName = PhSvcpCreateString(DisplayName, -1, &m.p.u.CreateService.i.DisplayName))) + goto CleanupExit; + if (BinaryPathName && !(binaryPathName = PhSvcpCreateString(BinaryPathName, -1, &m.p.u.CreateService.i.BinaryPathName))) + goto CleanupExit; + if (LoadOrderGroup && !(loadOrderGroup = PhSvcpCreateString(LoadOrderGroup, -1, &m.p.u.CreateService.i.LoadOrderGroup))) + goto CleanupExit; + + if (Dependencies) + { + SIZE_T dependenciesLength; + SIZE_T partCount; + PWSTR part; + + dependenciesLength = sizeof(WCHAR); + part = Dependencies; + + do + { + partCount = PhCountStringZ(part) + 1; + part += partCount; + dependenciesLength += partCount * sizeof(WCHAR); + } while (partCount != 1); // stop at empty dependency part + + if (!(dependencies = PhSvcpCreateString(Dependencies, dependenciesLength, &m.p.u.CreateService.i.Dependencies))) + goto CleanupExit; + } + + if (ServiceStartName && !(serviceStartName = PhSvcpCreateString(ServiceStartName, -1, &m.p.u.CreateService.i.ServiceStartName))) + goto CleanupExit; + + if (Password) + { + if (!(password = PhSvcpCreateString(Password, -1, &m.p.u.CreateService.i.Password))) + goto CleanupExit; + + passwordLength = m.p.u.CreateService.i.Password.Length; + } + + status = PhSvcpCallServer(&m); + + if (NT_SUCCESS(status)) + { + if (TagId) + *TagId = m.p.u.CreateService.o.TagId; + } + +CleanupExit: + if (password) + { + RtlSecureZeroMemory(password, passwordLength); + PhSvcpFreeHeap(password); + } + + if (serviceStartName) PhSvcpFreeHeap(serviceStartName); + if (dependencies) PhSvcpFreeHeap(dependencies); + if (loadOrderGroup) PhSvcpFreeHeap(loadOrderGroup); + if (binaryPathName) PhSvcpFreeHeap(binaryPathName); + if (displayName) PhSvcpFreeHeap(displayName); + if (serviceName) PhSvcpFreeHeap(serviceName); + + return status; +} + +NTSTATUS PhSvcCallChangeServiceConfig( + _In_ PWSTR ServiceName, + _In_ ULONG ServiceType, + _In_ ULONG StartType, + _In_ ULONG ErrorControl, + _In_opt_ PWSTR BinaryPathName, + _In_opt_ PWSTR LoadOrderGroup, + _Out_opt_ PULONG TagId, + _In_opt_ PWSTR Dependencies, + _In_opt_ PWSTR ServiceStartName, + _In_opt_ PWSTR Password, + _In_opt_ PWSTR DisplayName + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID serviceName = NULL; + PVOID binaryPathName = NULL; + PVOID loadOrderGroup = NULL; + PVOID dependencies = NULL; + PVOID serviceStartName = NULL; + PVOID password = NULL; + ULONG passwordLength; + PVOID displayName = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcChangeServiceConfigApiNumber; + + m.p.u.ChangeServiceConfig.i.ServiceType = ServiceType; + m.p.u.ChangeServiceConfig.i.StartType = StartType; + m.p.u.ChangeServiceConfig.i.ErrorControl = ErrorControl; + m.p.u.ChangeServiceConfig.i.TagIdSpecified = TagId != NULL; + + status = STATUS_NO_MEMORY; + + if (!(serviceName = PhSvcpCreateString(ServiceName, -1, &m.p.u.ChangeServiceConfig.i.ServiceName))) + goto CleanupExit; + if (BinaryPathName && !(binaryPathName = PhSvcpCreateString(BinaryPathName, -1, &m.p.u.ChangeServiceConfig.i.BinaryPathName))) + goto CleanupExit; + if (LoadOrderGroup && !(loadOrderGroup = PhSvcpCreateString(LoadOrderGroup, -1, &m.p.u.ChangeServiceConfig.i.LoadOrderGroup))) + goto CleanupExit; + + if (Dependencies) + { + SIZE_T dependenciesLength; + SIZE_T partCount; + PWSTR part; + + dependenciesLength = sizeof(WCHAR); + part = Dependencies; + + do + { + partCount = PhCountStringZ(part) + 1; + part += partCount; + dependenciesLength += partCount * sizeof(WCHAR); + } while (partCount != 1); // stop at empty dependency part + + if (!(dependencies = PhSvcpCreateString(Dependencies, dependenciesLength, &m.p.u.ChangeServiceConfig.i.Dependencies))) + goto CleanupExit; + } + + if (ServiceStartName && !(serviceStartName = PhSvcpCreateString(ServiceStartName, -1, &m.p.u.ChangeServiceConfig.i.ServiceStartName))) + goto CleanupExit; + + if (Password) + { + if (!(password = PhSvcpCreateString(Password, -1, &m.p.u.ChangeServiceConfig.i.Password))) + goto CleanupExit; + + passwordLength = m.p.u.ChangeServiceConfig.i.Password.Length; + } + + if (DisplayName && !(displayName = PhSvcpCreateString(DisplayName, -1, &m.p.u.ChangeServiceConfig.i.DisplayName))) + goto CleanupExit; + + status = PhSvcpCallServer(&m); + + if (NT_SUCCESS(status)) + { + if (TagId) + *TagId = m.p.u.ChangeServiceConfig.o.TagId; + } + +CleanupExit: + if (displayName) PhSvcpFreeHeap(displayName); + + if (password) + { + RtlSecureZeroMemory(password, passwordLength); + PhSvcpFreeHeap(password); + } + + if (serviceStartName) PhSvcpFreeHeap(serviceStartName); + if (dependencies) PhSvcpFreeHeap(dependencies); + if (loadOrderGroup) PhSvcpFreeHeap(loadOrderGroup); + if (binaryPathName) PhSvcpFreeHeap(binaryPathName); + if (serviceName) PhSvcpFreeHeap(serviceName); + + return status; +} + +PVOID PhSvcpPackRoot( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_ PVOID Buffer, + _In_ SIZE_T Length + ) +{ + return PhAppendBytesBuilderEx(BytesBuilder, Buffer, Length, sizeof(ULONG_PTR), NULL); +} + +VOID PhSvcpPackBuffer_V( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _Inout_ PVOID *PointerInBytesBuilder, + _In_ SIZE_T Length, + _In_ SIZE_T Alignment, + _In_ ULONG NumberOfPointersToRebase, + _In_ va_list ArgPtr + ) +{ + va_list argptr; + ULONG_PTR oldBase; + SIZE_T oldLength; + ULONG_PTR newBase; + SIZE_T offset; + ULONG i; + PVOID *pointer; + + oldBase = (ULONG_PTR)BytesBuilder->Bytes->Buffer; + oldLength = BytesBuilder->Bytes->Length; + assert((ULONG_PTR)PointerInBytesBuilder >= oldBase && (ULONG_PTR)PointerInBytesBuilder + sizeof(PVOID) <= oldBase + oldLength); + + if (!*PointerInBytesBuilder) + return; + + PhAppendBytesBuilderEx(BytesBuilder, *PointerInBytesBuilder, Length, Alignment, &offset); + newBase = (ULONG_PTR)BytesBuilder->Bytes->Buffer; + + PointerInBytesBuilder = (PVOID *)((ULONG_PTR)PointerInBytesBuilder - oldBase + newBase); + *PointerInBytesBuilder = (PVOID)offset; + + argptr = ArgPtr; + + for (i = 0; i < NumberOfPointersToRebase; i++) + { + pointer = va_arg(argptr, PVOID *); + assert(!*pointer || ((ULONG_PTR)*pointer >= oldBase && (ULONG_PTR)*pointer + sizeof(PVOID) <= oldBase + oldLength)); + + if (*pointer) + *pointer = (PVOID)((ULONG_PTR)*pointer - oldBase + newBase); + } +} + +VOID PhSvcpPackBuffer( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _Inout_ PVOID *PointerInBytesBuilder, + _In_ SIZE_T Length, + _In_ SIZE_T Alignment, + _In_ ULONG NumberOfPointersToRebase, + ... + ) +{ + va_list argptr; + + va_start(argptr, NumberOfPointersToRebase); + PhSvcpPackBuffer_V(BytesBuilder, PointerInBytesBuilder, Length, Alignment, NumberOfPointersToRebase, argptr); +} + +SIZE_T PhSvcpBufferLengthStringZ( + _In_opt_ PWSTR String, + _In_ BOOLEAN Multi + ) +{ + SIZE_T length = 0; + + if (String) + { + if (Multi) + { + PWSTR part = String; + SIZE_T partCount; + + while (TRUE) + { + partCount = PhCountStringZ(part); + length += (partCount + 1) * sizeof(WCHAR); + + if (partCount == 0) + break; + + part += partCount + 1; + } + } + else + { + length = (PhCountStringZ(String) + 1) * sizeof(WCHAR); + } + } + + return length; +} + +NTSTATUS PhSvcCallChangeServiceConfig2( + _In_ PWSTR ServiceName, + _In_ ULONG InfoLevel, + _In_ PVOID Info + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID serviceName = NULL; + PVOID info = NULL; + PH_BYTES_BUILDER bb; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcChangeServiceConfig2ApiNumber; + + m.p.u.ChangeServiceConfig2.i.InfoLevel = InfoLevel; + + if (serviceName = PhSvcpCreateString(ServiceName, -1, &m.p.u.ChangeServiceConfig2.i.ServiceName)) + { + switch (InfoLevel) + { + case SERVICE_CONFIG_FAILURE_ACTIONS: + { + LPSERVICE_FAILURE_ACTIONS failureActions = Info; + LPSERVICE_FAILURE_ACTIONS packedFailureActions; + + PhInitializeBytesBuilder(&bb, 200); + packedFailureActions = PhSvcpPackRoot(&bb, failureActions, sizeof(SERVICE_FAILURE_ACTIONS)); + PhSvcpPackBuffer(&bb, &packedFailureActions->lpRebootMsg, PhSvcpBufferLengthStringZ(failureActions->lpRebootMsg, FALSE), sizeof(WCHAR), + 1, &packedFailureActions); + PhSvcpPackBuffer(&bb, &packedFailureActions->lpCommand, PhSvcpBufferLengthStringZ(failureActions->lpCommand, FALSE), sizeof(WCHAR), + 1, &packedFailureActions); + + if (failureActions->cActions != 0 && failureActions->lpsaActions) + { + PhSvcpPackBuffer(&bb, &packedFailureActions->lpsaActions, failureActions->cActions * sizeof(SC_ACTION), __alignof(SC_ACTION), + 1, &packedFailureActions); + } + + info = PhSvcpCreateString(bb.Bytes->Buffer, bb.Bytes->Length, &m.p.u.ChangeServiceConfig2.i.Info); + PhDeleteBytesBuilder(&bb); + } + break; + case SERVICE_CONFIG_DELAYED_AUTO_START_INFO: + info = PhSvcpCreateString(Info, sizeof(SERVICE_DELAYED_AUTO_START_INFO), &m.p.u.ChangeServiceConfig2.i.Info); + break; + case SERVICE_CONFIG_FAILURE_ACTIONS_FLAG: + info = PhSvcpCreateString(Info, sizeof(SERVICE_FAILURE_ACTIONS_FLAG), &m.p.u.ChangeServiceConfig2.i.Info); + break; + case SERVICE_CONFIG_SERVICE_SID_INFO: + info = PhSvcpCreateString(Info, sizeof(SERVICE_SID_INFO), &m.p.u.ChangeServiceConfig2.i.Info); + break; + case SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO: + { + LPSERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo = Info; + LPSERVICE_REQUIRED_PRIVILEGES_INFO packedRequiredPrivilegesInfo; + + PhInitializeBytesBuilder(&bb, 100); + packedRequiredPrivilegesInfo = PhSvcpPackRoot(&bb, requiredPrivilegesInfo, sizeof(SERVICE_REQUIRED_PRIVILEGES_INFO)); + PhSvcpPackBuffer(&bb, &packedRequiredPrivilegesInfo->pmszRequiredPrivileges, PhSvcpBufferLengthStringZ(requiredPrivilegesInfo->pmszRequiredPrivileges, TRUE), sizeof(WCHAR), + 1, &packedRequiredPrivilegesInfo); + + info = PhSvcpCreateString(bb.Bytes->Buffer, bb.Bytes->Length, &m.p.u.ChangeServiceConfig2.i.Info); + PhDeleteBytesBuilder(&bb); + } + break; + case SERVICE_CONFIG_PRESHUTDOWN_INFO: + info = PhSvcpCreateString(Info, sizeof(SERVICE_PRESHUTDOWN_INFO), &m.p.u.ChangeServiceConfig2.i.Info); + break; + case SERVICE_CONFIG_TRIGGER_INFO: + { + PSERVICE_TRIGGER_INFO triggerInfo = Info; + PSERVICE_TRIGGER_INFO packedTriggerInfo; + ULONG i; + PSERVICE_TRIGGER packedTrigger; + ULONG j; + PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM packedDataItem; + ULONG alignment; + + PhInitializeBytesBuilder(&bb, 400); + packedTriggerInfo = PhSvcpPackRoot(&bb, triggerInfo, sizeof(SERVICE_TRIGGER_INFO)); + + if (triggerInfo->cTriggers != 0 && triggerInfo->pTriggers) + { + PhSvcpPackBuffer(&bb, &packedTriggerInfo->pTriggers, triggerInfo->cTriggers * sizeof(SERVICE_TRIGGER), __alignof(SERVICE_TRIGGER), + 1, &packedTriggerInfo); + + for (i = 0; i < triggerInfo->cTriggers; i++) + { + packedTrigger = PhOffsetBytesBuilder(&bb, (SIZE_T)packedTriggerInfo->pTriggers + i * sizeof(SERVICE_TRIGGER)); + + PhSvcpPackBuffer(&bb, &packedTrigger->pTriggerSubtype, sizeof(GUID), __alignof(GUID), + 2, &packedTriggerInfo, &packedTrigger); + + if (packedTrigger->cDataItems != 0 && packedTrigger->pDataItems) + { + PhSvcpPackBuffer(&bb, &packedTrigger->pDataItems, packedTrigger->cDataItems * sizeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM), __alignof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM), + 2, &packedTriggerInfo, &packedTrigger); + + for (j = 0; j < packedTrigger->cDataItems; j++) + { + packedDataItem = PhOffsetBytesBuilder(&bb, (SIZE_T)packedTrigger->pDataItems + j * sizeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM)); + alignment = 1; + + switch (packedDataItem->dwDataType) + { + case SERVICE_TRIGGER_DATA_TYPE_BINARY: + case SERVICE_TRIGGER_DATA_TYPE_LEVEL: + alignment = sizeof(CHAR); + break; + case SERVICE_TRIGGER_DATA_TYPE_STRING: + alignment = sizeof(WCHAR); + break; + case SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY: + case SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL: + alignment = sizeof(ULONG64); + break; + } + + PhSvcpPackBuffer(&bb, &packedDataItem->pData, packedDataItem->cbData, alignment, + 3, &packedTriggerInfo, &packedTrigger, &packedDataItem); + } + } + } + } + + info = PhSvcpCreateString(bb.Bytes->Buffer, bb.Bytes->Length, &m.p.u.ChangeServiceConfig2.i.Info); + PhDeleteBytesBuilder(&bb); + } + break; + case SERVICE_CONFIG_LAUNCH_PROTECTED: + info = PhSvcpCreateString(Info, sizeof(SERVICE_LAUNCH_PROTECTED_INFO), &m.p.u.ChangeServiceConfig2.i.Info); + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + } + + if (serviceName && info) + { + status = PhSvcpCallServer(&m); + } + else + { + status = STATUS_NO_MEMORY; + } + + if (info) + PhSvcpFreeHeap(info); + if (serviceName) + PhSvcpFreeHeap(serviceName); + + return status; +} + +NTSTATUS PhSvcCallSetTcpEntry( + _In_ PVOID TcpRow + ) +{ + PHSVC_API_MSG m; + struct + { + DWORD dwState; + DWORD dwLocalAddr; + DWORD dwLocalPort; + DWORD dwRemoteAddr; + DWORD dwRemotePort; + } *tcpRow = TcpRow; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcSetTcpEntryApiNumber; + + m.p.u.SetTcpEntry.i.State = tcpRow->dwState; + m.p.u.SetTcpEntry.i.LocalAddress = tcpRow->dwLocalAddr; + m.p.u.SetTcpEntry.i.LocalPort = tcpRow->dwLocalPort; + m.p.u.SetTcpEntry.i.RemoteAddress = tcpRow->dwRemoteAddr; + m.p.u.SetTcpEntry.i.RemotePort = tcpRow->dwRemotePort; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallControlThread( + _In_ HANDLE ThreadId, + _In_ PHSVC_API_CONTROLTHREAD_COMMAND Command, + _In_ ULONG Argument + ) +{ + PHSVC_API_MSG m; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcControlThreadApiNumber; + m.p.u.ControlThread.i.ThreadId = ThreadId; + m.p.u.ControlThread.i.Command = Command; + m.p.u.ControlThread.i.Argument = Argument; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallAddAccountRight( + _In_ PSID AccountSid, + _In_ PUNICODE_STRING UserRight + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID accountSid = NULL; + PVOID userRight = NULL; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcAddAccountRightApiNumber; + + status = STATUS_NO_MEMORY; + + if (!(accountSid = PhSvcpCreateString(AccountSid, RtlLengthSid(AccountSid), &m.p.u.AddAccountRight.i.AccountSid))) + goto CleanupExit; + if (!(userRight = PhSvcpCreateString(UserRight->Buffer, UserRight->Length, &m.p.u.AddAccountRight.i.UserRight))) + goto CleanupExit; + + status = PhSvcpCallServer(&m); + +CleanupExit: + if (userRight) PhSvcpFreeHeap(userRight); + if (accountSid) PhSvcpFreeHeap(accountSid); + + return status; +} + +NTSTATUS PhSvcCallInvokeRunAsService( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + return PhSvcpCallExecuteRunAsCommand(PhSvcInvokeRunAsServiceApiNumber, Parameters); +} + +NTSTATUS PhSvcCallIssueMemoryListCommand( + _In_ SYSTEM_MEMORY_LIST_COMMAND Command + ) +{ + PHSVC_API_MSG m; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcIssueMemoryListCommandApiNumber; + m.p.u.IssueMemoryListCommand.i.Command = Command; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallPostMessage( + _In_opt_ HWND hWnd, + _In_ UINT Msg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PHSVC_API_MSG m; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcPostMessageApiNumber; + m.p.u.PostMessage.i.hWnd = hWnd; + m.p.u.PostMessage.i.Msg = Msg; + m.p.u.PostMessage.i.wParam = wParam; + m.p.u.PostMessage.i.lParam = lParam; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallSendMessage( + _In_opt_ HWND hWnd, + _In_ UINT Msg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PHSVC_API_MSG m; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcSendMessageApiNumber; + m.p.u.PostMessage.i.hWnd = hWnd; + m.p.u.PostMessage.i.Msg = Msg; + m.p.u.PostMessage.i.wParam = wParam; + m.p.u.PostMessage.i.lParam = lParam; + + return PhSvcpCallServer(&m); +} + +NTSTATUS PhSvcCallCreateProcessIgnoreIfeoDebugger( + _In_ PWSTR FileName + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID fileName = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcCreateProcessIgnoreIfeoDebuggerApiNumber; + fileName = PhSvcpCreateString(FileName, -1, &m.p.u.CreateProcessIgnoreIfeoDebugger.i.FileName); + + if (!fileName) + return STATUS_NO_MEMORY; + + status = PhSvcpCallServer(&m); + + if (fileName) + PhSvcpFreeHeap(fileName); + + return status; +} + +PSECURITY_DESCRIPTOR PhpAbsoluteToSelfRelativeSD( + _In_ PSECURITY_DESCRIPTOR AbsoluteSecurityDescriptor, + _Out_ PULONG BufferSize + ) +{ + NTSTATUS status; + ULONG bufferSize = 0; + PSECURITY_DESCRIPTOR selfRelativeSecurityDescriptor; + + status = RtlAbsoluteToSelfRelativeSD(AbsoluteSecurityDescriptor, NULL, &bufferSize); + + if (status != STATUS_BUFFER_TOO_SMALL) + return NULL; + + selfRelativeSecurityDescriptor = PhAllocate(bufferSize); + status = RtlAbsoluteToSelfRelativeSD(AbsoluteSecurityDescriptor, selfRelativeSecurityDescriptor, &bufferSize); + + if (!NT_SUCCESS(status)) + { + PhFree(selfRelativeSecurityDescriptor); + return NULL; + } + + *BufferSize = bufferSize; + + return selfRelativeSecurityDescriptor; +} + +NTSTATUS PhSvcCallSetServiceSecurity( + _In_ PWSTR ServiceName, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PSECURITY_DESCRIPTOR selfRelativeSecurityDescriptor = NULL; + ULONG bufferSize; + PVOID serviceName = NULL; + PVOID copiedSelfRelativeSecurityDescriptor = NULL; + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + selfRelativeSecurityDescriptor = PhpAbsoluteToSelfRelativeSD(SecurityDescriptor, &bufferSize); + + if (!selfRelativeSecurityDescriptor) + { + status = STATUS_BAD_DESCRIPTOR_FORMAT; + goto CleanupExit; + } + + m.p.ApiNumber = PhSvcSetServiceSecurityApiNumber; + m.p.u.SetServiceSecurity.i.SecurityInformation = SecurityInformation; + status = STATUS_NO_MEMORY; + + if (!(serviceName = PhSvcpCreateString(ServiceName, -1, &m.p.u.SetServiceSecurity.i.ServiceName))) + goto CleanupExit; + if (!(copiedSelfRelativeSecurityDescriptor = PhSvcpCreateString(selfRelativeSecurityDescriptor, bufferSize, &m.p.u.SetServiceSecurity.i.SecurityDescriptor))) + goto CleanupExit; + + status = PhSvcpCallServer(&m); + +CleanupExit: + if (selfRelativeSecurityDescriptor) PhFree(selfRelativeSecurityDescriptor); + if (serviceName) PhSvcpFreeHeap(serviceName); + if (copiedSelfRelativeSecurityDescriptor) PhSvcpFreeHeap(copiedSelfRelativeSecurityDescriptor); + + return status; +} + +NTSTATUS PhSvcCallLoadDbgHelp( + _In_ PWSTR DbgHelpPath + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + PVOID dbgHelpPath = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + m.p.ApiNumber = PhSvcLoadDbgHelpApiNumber; + dbgHelpPath = PhSvcpCreateString(DbgHelpPath, -1, &m.p.u.LoadDbgHelp.i.DbgHelpPath); + + if (!dbgHelpPath) + return STATUS_NO_MEMORY; + + status = PhSvcpCallServer(&m); + + if (dbgHelpPath) + PhSvcpFreeHeap(dbgHelpPath); + + return status; +} + +NTSTATUS PhSvcCallWriteMiniDumpProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE FileHandle, + _In_ ULONG DumpType + ) +{ + NTSTATUS status; + PHSVC_API_MSG m; + HANDLE serverHandle = NULL; + HANDLE remoteProcessHandle = NULL; + HANDLE remoteFileHandle = NULL; + + memset(&m, 0, sizeof(PHSVC_API_MSG)); + + if (!PhSvcClPortHandle) + return STATUS_PORT_DISCONNECTED; + + // For typical uses of this function, the client has more privileges than the server. + // We therefore duplicate our handles into the server's process. + + m.p.ApiNumber = PhSvcWriteMiniDumpProcessApiNumber; + + if (!NT_SUCCESS(status = PhOpenProcess(&serverHandle, PROCESS_DUP_HANDLE, PhSvcClServerProcessId))) + { + goto CleanupExit; + } + + if (!NT_SUCCESS(status = NtDuplicateObject(NtCurrentProcess(), ProcessHandle, serverHandle, &remoteProcessHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, 0))) + { + goto CleanupExit; + } + + if (!NT_SUCCESS(status = NtDuplicateObject(NtCurrentProcess(), FileHandle, serverHandle, &remoteFileHandle, + FILE_GENERIC_WRITE, 0, 0))) + { + goto CleanupExit; + } + + m.p.u.WriteMiniDumpProcess.i.LocalProcessHandle = HandleToUlong(remoteProcessHandle); + m.p.u.WriteMiniDumpProcess.i.ProcessId = HandleToUlong(ProcessId); + m.p.u.WriteMiniDumpProcess.i.LocalFileHandle = HandleToUlong(remoteFileHandle); + m.p.u.WriteMiniDumpProcess.i.DumpType = DumpType; + + status = PhSvcpCallServer(&m); + +CleanupExit: + if (serverHandle) + { + if (remoteProcessHandle) + NtDuplicateObject(serverHandle, remoteProcessHandle, NULL, NULL, 0, 0, DUPLICATE_CLOSE_SOURCE); + if (remoteFileHandle) + NtDuplicateObject(serverHandle, remoteFileHandle, NULL, NULL, 0, 0, DUPLICATE_CLOSE_SOURCE); + + NtClose(serverHandle); + } + + return status; +} diff --git a/ProcessHacker/phsvc/svcapi.c b/ProcessHacker/phsvc/svcapi.c new file mode 100644 index 0000000..95d7646 --- /dev/null +++ b/ProcessHacker/phsvc/svcapi.c @@ -0,0 +1,1442 @@ +/* + * Process Hacker - + * server API + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS +{ + PPH_STRING UserName; + PPH_STRING Password; + PPH_STRING CurrentDirectory; + PPH_STRING CommandLine; + PPH_STRING FileName; + PPH_STRING DesktopName; + PPH_STRING ServiceName; +} PHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS, *PPHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS; + +PPHSVC_API_PROCEDURE PhSvcApiCallTable[] = +{ + PhSvcApiPlugin, + PhSvcApiExecuteRunAsCommand, + PhSvcApiUnloadDriver, + PhSvcApiControlProcess, + PhSvcApiControlService, + PhSvcApiCreateService, + PhSvcApiChangeServiceConfig, + PhSvcApiChangeServiceConfig2, + PhSvcApiSetTcpEntry, + PhSvcApiControlThread, + PhSvcApiAddAccountRight, + PhSvcApiInvokeRunAsService, + PhSvcApiIssueMemoryListCommand, + PhSvcApiPostMessage, + PhSvcApiSendMessage, + PhSvcApiCreateProcessIgnoreIfeoDebugger, + PhSvcApiSetServiceSecurity, + PhSvcApiLoadDbgHelp, + PhSvcApiWriteMiniDumpProcess +}; +C_ASSERT(sizeof(PhSvcApiCallTable) / sizeof(PPHSVC_API_PROCEDURE) == PhSvcMaximumApiNumber - 1); + +NTSTATUS PhSvcApiInitialization( + VOID + ) +{ + return STATUS_SUCCESS; +} + +VOID PhSvcDispatchApiCall( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload, + _Out_ PHANDLE ReplyPortHandle + ) +{ + NTSTATUS status; + + if ( + Payload->ApiNumber == 0 || + (ULONG)Payload->ApiNumber >= (ULONG)PhSvcMaximumApiNumber || + !PhSvcApiCallTable[Payload->ApiNumber - 1] + ) + { + Payload->ReturnStatus = STATUS_INVALID_SYSTEM_SERVICE; + *ReplyPortHandle = Client->PortHandle; + return; + } + + status = PhSvcApiCallTable[Payload->ApiNumber - 1](Client, Payload); + Payload->ReturnStatus = status; + + *ReplyPortHandle = Client->PortHandle; +} + +PVOID PhSvcValidateString( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ ULONG Alignment + ) +{ + PPHSVC_CLIENT client = PhSvcGetCurrentClient(); + PVOID address; + + address = (PCHAR)client->ClientViewBase + String->Offset; + + if ((ULONG_PTR)address + String->Length < (ULONG_PTR)address || + (ULONG_PTR)address < (ULONG_PTR)client->ClientViewBase || + (ULONG_PTR)address + String->Length > (ULONG_PTR)client->ClientViewLimit) + { + return NULL; + } + + if ((ULONG_PTR)address & (Alignment - 1)) + { + return NULL; + } + + return address; +} + +NTSTATUS PhSvcProbeBuffer( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ ULONG Alignment, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *Pointer + ) +{ + PVOID address; + + if (String->Offset != 0) + { + address = PhSvcValidateString(String, Alignment); + + if (!address) + return STATUS_ACCESS_VIOLATION; + + *Pointer = address; + } + else + { + if (!AllowNull) + return STATUS_ACCESS_VIOLATION; + + *Pointer = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcCaptureBuffer( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PVOID *CapturedBuffer + ) +{ + PVOID address; + PVOID buffer; + + if (String->Offset != 0) + { + address = PhSvcValidateString(String, 1); + + if (!address) + return STATUS_ACCESS_VIOLATION; + + buffer = PhAllocateSafe(String->Length); + + if (!buffer) + return STATUS_NO_MEMORY; + + memcpy(buffer, address, String->Length); + *CapturedBuffer = buffer; + } + else + { + if (!AllowNull) + return STATUS_ACCESS_VIOLATION; + + *CapturedBuffer = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcCaptureString( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PPH_STRING *CapturedString + ) +{ + PVOID address; + + if (String->Length & 1) + return STATUS_INVALID_BUFFER_SIZE; + if (String->Length > 0xfffe) + return STATUS_INVALID_BUFFER_SIZE; + + if (String->Offset != 0) + { + address = PhSvcValidateString(String, sizeof(WCHAR)); + + if (!address) + return STATUS_ACCESS_VIOLATION; + + if (String->Length != 0) + *CapturedString = PhCreateStringEx(address, String->Length); + else + *CapturedString = PhReferenceEmptyString(); + } + else + { + if (!AllowNull) + return STATUS_ACCESS_VIOLATION; + + *CapturedString = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcCaptureSid( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _Out_ PSID *CapturedSid + ) +{ + NTSTATUS status; + PSID sid; + + if (!NT_SUCCESS(status = PhSvcCaptureBuffer(String, AllowNull, &sid))) + return status; + + if (sid) + { + if (String->Length < (ULONG)FIELD_OFFSET(struct _SID, IdentifierAuthority) || + String->Length < RtlLengthRequiredSid(((struct _SID *)sid)->SubAuthorityCount) || + !RtlValidSid(sid)) + { + PhFree(sid); + return STATUS_INVALID_SID; + } + + *CapturedSid = sid; + } + else + { + *CapturedSid = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcCaptureSecurityDescriptor( + _In_ PPH_RELATIVE_STRINGREF String, + _In_ BOOLEAN AllowNull, + _In_ SECURITY_INFORMATION RequiredInformation, + _Out_ PSECURITY_DESCRIPTOR *CapturedSecurityDescriptor + ) +{ + NTSTATUS status; + PSECURITY_DESCRIPTOR securityDescriptor; + ULONG bufferSize; + + if (!NT_SUCCESS(status = PhSvcCaptureBuffer(String, AllowNull, &securityDescriptor))) + return status; + + if (securityDescriptor) + { + if (!RtlValidRelativeSecurityDescriptor(securityDescriptor, String->Length, RequiredInformation)) + { + PhFree(securityDescriptor); + return STATUS_INVALID_SECURITY_DESCR; + } + + bufferSize = String->Length; + status = RtlSelfRelativeToAbsoluteSD2(securityDescriptor, &bufferSize); + + if (status == STATUS_BUFFER_TOO_SMALL) + { + PVOID newBuffer; + + newBuffer = PhAllocate(bufferSize); + memcpy(newBuffer, securityDescriptor, String->Length); + PhFree(securityDescriptor); + securityDescriptor = newBuffer; + + status = RtlSelfRelativeToAbsoluteSD2(securityDescriptor, &bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(securityDescriptor); + return status; + } + + *CapturedSecurityDescriptor = securityDescriptor; + } + else + { + *CapturedSecurityDescriptor = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcApiDefault( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS PhSvcApiPlugin( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING apiId; + PPH_PLUGIN plugin; + PH_STRINGREF pluginName; + PH_PLUGIN_PHSVC_REQUEST request; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.Plugin.i.ApiId, FALSE, &apiId))) + { + PH_AUTO(apiId); + + if (PhPluginsEnabled && + PhEmParseCompoundId(&apiId->sr, &pluginName, &request.SubId) && + (plugin = PhFindPlugin2(&pluginName))) + { + request.ReturnStatus = STATUS_NOT_IMPLEMENTED; + request.InBuffer = Payload->u.Plugin.i.Data; + request.InLength = sizeof(Payload->u.Plugin.i.Data); + request.OutBuffer = Payload->u.Plugin.o.Data; + request.OutLength = sizeof(Payload->u.Plugin.o.Data); + + request.ProbeBuffer = PhSvcProbeBuffer; + request.CaptureBuffer = PhSvcCaptureBuffer; + request.CaptureString = PhSvcCaptureString; + + PhInvokeCallback(PhGetPluginCallback(plugin, PluginCallbackPhSvcRequest), &request); + status = request.ReturnStatus; + } + else + { + status = STATUS_NOT_FOUND; + } + } + + return status; +} + +NTSTATUS PhSvcpCaptureRunAsServiceParameters( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload, + _Out_ PPH_RUNAS_SERVICE_PARAMETERS Parameters, + _Out_ PPHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS CapturedParameters + ) +{ + NTSTATUS status; + + memset(CapturedParameters, 0, sizeof(PHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS)); + + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.UserName, TRUE, &CapturedParameters->UserName))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.Password, TRUE, &CapturedParameters->Password))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.CurrentDirectory, TRUE, &CapturedParameters->CurrentDirectory))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.CommandLine, TRUE, &CapturedParameters->CommandLine))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.FileName, TRUE, &CapturedParameters->FileName))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.DesktopName, TRUE, &CapturedParameters->DesktopName))) + return status; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ExecuteRunAsCommand.i.ServiceName, TRUE, &CapturedParameters->ServiceName))) + return status; + + Parameters->ProcessId = Payload->u.ExecuteRunAsCommand.i.ProcessId; + Parameters->UserName = PhGetString(CapturedParameters->UserName); + Parameters->Password = PhGetString(CapturedParameters->Password); + Parameters->LogonType = Payload->u.ExecuteRunAsCommand.i.LogonType; + Parameters->SessionId = Payload->u.ExecuteRunAsCommand.i.SessionId; + Parameters->CurrentDirectory = PhGetString(CapturedParameters->CurrentDirectory); + Parameters->CommandLine = PhGetString(CapturedParameters->CommandLine); + Parameters->FileName = PhGetString(CapturedParameters->FileName); + Parameters->DesktopName = PhGetString(CapturedParameters->DesktopName); + Parameters->UseLinkedToken = Payload->u.ExecuteRunAsCommand.i.UseLinkedToken; + Parameters->ServiceName = PhGetString(CapturedParameters->ServiceName); + + return status; +} + +VOID PhSvcpReleaseRunAsServiceParameters( + _In_ PPHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS CapturedParameters + ) +{ + if (CapturedParameters->UserName) + PhDereferenceObject(CapturedParameters->UserName); + + if (CapturedParameters->Password) + { + RtlSecureZeroMemory(CapturedParameters->Password->Buffer, CapturedParameters->Password->Length); + PhDereferenceObject(CapturedParameters->Password); + } + + if (CapturedParameters->CurrentDirectory) + PhDereferenceObject(CapturedParameters->CurrentDirectory); + if (CapturedParameters->CommandLine) + PhDereferenceObject(CapturedParameters->CommandLine); + if (CapturedParameters->FileName) + PhDereferenceObject(CapturedParameters->FileName); + if (CapturedParameters->DesktopName) + PhDereferenceObject(CapturedParameters->DesktopName); + if (CapturedParameters->ServiceName) + PhDereferenceObject(CapturedParameters->ServiceName); +} + +NTSTATUS PhSvcpValidateRunAsServiceParameters( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + if ((!Parameters->UserName || !Parameters->Password) && !Parameters->ProcessId) + return STATUS_INVALID_PARAMETER_MIX; + if (!Parameters->FileName && !Parameters->CommandLine) + return STATUS_INVALID_PARAMETER_MIX; + if (!Parameters->ServiceName) + return STATUS_INVALID_PARAMETER; + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcApiExecuteRunAsCommand( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PH_RUNAS_SERVICE_PARAMETERS parameters; + PHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS capturedParameters; + + if (NT_SUCCESS(status = PhSvcpCaptureRunAsServiceParameters(Client, Payload, ¶meters, &capturedParameters))) + { + if (NT_SUCCESS(status = PhSvcpValidateRunAsServiceParameters(¶meters))) + { + status = PhExecuteRunAsCommand(¶meters); + } + } + + PhSvcpReleaseRunAsServiceParameters(&capturedParameters); + + return status; +} + +NTSTATUS PhSvcApiUnloadDriver( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING name; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.UnloadDriver.i.Name, TRUE, &name))) + { + status = PhUnloadDriver(Payload->u.UnloadDriver.i.BaseAddress, PhGetString(name)); + PhClearReference(&name); + } + + return status; +} + +NTSTATUS PhSvcApiControlProcess( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + HANDLE processId; + HANDLE processHandle; + + processId = Payload->u.ControlProcess.i.ProcessId; + + switch (Payload->u.ControlProcess.i.Command) + { + case PhSvcControlProcessTerminate: + if (NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_TERMINATE, processId))) + { + status = PhTerminateProcess(processHandle, 1); // see notes in PhUiTerminateProcesses + NtClose(processHandle); + } + break; + case PhSvcControlProcessSuspend: + if (NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_SUSPEND_RESUME, processId))) + { + status = NtSuspendProcess(processHandle); + NtClose(processHandle); + } + break; + case PhSvcControlProcessResume: + if (NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_SUSPEND_RESUME, processId))) + { + status = NtResumeProcess(processHandle); + NtClose(processHandle); + } + break; + case PhSvcControlProcessPriority: + if (processId != SYSTEM_PROCESS_ID) + { + if (NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_SET_INFORMATION, processId))) + { + PROCESS_PRIORITY_CLASS priorityClass; + + priorityClass.Foreground = FALSE; + priorityClass.PriorityClass = (UCHAR)Payload->u.ControlProcess.i.Argument; + status = NtSetInformationProcess(processHandle, ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS)); + + NtClose(processHandle); + } + } + else + { + status = STATUS_UNSUCCESSFUL; + } + break; + case PhSvcControlProcessIoPriority: + if (processId != SYSTEM_PROCESS_ID) + { + if (NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_SET_INFORMATION, processId))) + { + status = PhSetProcessIoPriority(processHandle, Payload->u.ControlProcess.i.Argument); + NtClose(processHandle); + } + } + else + { + status = STATUS_UNSUCCESSFUL; + } + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + return status; +} + +NTSTATUS PhSvcApiControlService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING serviceName; + SC_HANDLE serviceHandle; + SERVICE_STATUS serviceStatus; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ControlService.i.ServiceName, FALSE, &serviceName))) + { + PH_AUTO(serviceName); + + switch (Payload->u.ControlService.i.Command) + { + case PhSvcControlServiceStart: + if (serviceHandle = PhOpenService( + serviceName->Buffer, + SERVICE_START + )) + { + if (!StartService(serviceHandle, 0, NULL)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + break; + case PhSvcControlServiceContinue: + if (serviceHandle = PhOpenService( + serviceName->Buffer, + SERVICE_PAUSE_CONTINUE + )) + { + if (!ControlService(serviceHandle, SERVICE_CONTROL_CONTINUE, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + break; + case PhSvcControlServicePause: + if (serviceHandle = PhOpenService( + serviceName->Buffer, + SERVICE_PAUSE_CONTINUE + )) + { + if (!ControlService(serviceHandle, SERVICE_CONTROL_PAUSE, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + break; + case PhSvcControlServiceStop: + if (serviceHandle = PhOpenService( + serviceName->Buffer, + SERVICE_STOP + )) + { + if (!ControlService(serviceHandle, SERVICE_CONTROL_STOP, &serviceStatus)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + break; + case PhSvcControlServiceDelete: + if (serviceHandle = PhOpenService( + serviceName->Buffer, + DELETE + )) + { + if (!DeleteService(serviceHandle)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + } + + return status; +} + +NTSTATUS PhSvcApiCreateService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING serviceName = NULL; + PPH_STRING displayName = NULL; + PPH_STRING binaryPathName = NULL; + PPH_STRING loadOrderGroup = NULL; + PPH_STRING dependencies = NULL; + PPH_STRING serviceStartName = NULL; + PPH_STRING password = NULL; + ULONG tagId = 0; + SC_HANDLE scManagerHandle; + SC_HANDLE serviceHandle; + + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.ServiceName, FALSE, &serviceName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.DisplayName, TRUE, &displayName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.BinaryPathName, TRUE, &binaryPathName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.LoadOrderGroup, TRUE, &loadOrderGroup))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.Dependencies, TRUE, &dependencies))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.ServiceStartName, TRUE, &serviceStartName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateService.i.Password, TRUE, &password))) + goto CleanupExit; + + if (scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) + { + if (serviceHandle = CreateService( + scManagerHandle, + serviceName->Buffer, + PhGetString(displayName), + SERVICE_CHANGE_CONFIG, + Payload->u.CreateService.i.ServiceType, + Payload->u.CreateService.i.StartType, + Payload->u.CreateService.i.ErrorControl, + PhGetString(binaryPathName), + PhGetString(loadOrderGroup), + Payload->u.CreateService.i.TagIdSpecified ? &tagId : NULL, + PhGetString(dependencies), + PhGetString(serviceStartName), + PhGetString(password) + )) + { + Payload->u.CreateService.o.TagId = tagId; + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + + CloseServiceHandle(scManagerHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + +CleanupExit: + if (password) + { + RtlSecureZeroMemory(password->Buffer, password->Length); + PhDereferenceObject(password); + } + + PhClearReference(&serviceStartName); + PhClearReference(&dependencies); + PhClearReference(&loadOrderGroup); + PhClearReference(&binaryPathName); + PhClearReference(&displayName); + PhClearReference(&serviceName); + + return status; +} + +NTSTATUS PhSvcApiChangeServiceConfig( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING serviceName = NULL; + PPH_STRING binaryPathName = NULL; + PPH_STRING loadOrderGroup = NULL; + PPH_STRING dependencies = NULL; + PPH_STRING serviceStartName = NULL; + PPH_STRING password = NULL; + PPH_STRING displayName = NULL; + ULONG tagId = 0; + SC_HANDLE serviceHandle; + + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.ServiceName, FALSE, &serviceName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.BinaryPathName, TRUE, &binaryPathName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.LoadOrderGroup, TRUE, &loadOrderGroup))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.Dependencies, TRUE, &dependencies))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.ServiceStartName, TRUE, &serviceStartName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.Password, TRUE, &password))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig.i.DisplayName, TRUE, &displayName))) + goto CleanupExit; + + if (serviceHandle = PhOpenService(serviceName->Buffer, SERVICE_CHANGE_CONFIG)) + { + if (ChangeServiceConfig( + serviceHandle, + Payload->u.ChangeServiceConfig.i.ServiceType, + Payload->u.ChangeServiceConfig.i.StartType, + Payload->u.ChangeServiceConfig.i.ErrorControl, + PhGetString(binaryPathName), + PhGetString(loadOrderGroup), + Payload->u.ChangeServiceConfig.i.TagIdSpecified ? &tagId : NULL, + PhGetString(dependencies), + PhGetString(serviceStartName), + PhGetString(password), + PhGetString(displayName) + )) + { + Payload->u.ChangeServiceConfig.o.TagId = tagId; + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + +CleanupExit: + PhClearReference(&displayName); + + if (password) + { + RtlSecureZeroMemory(password->Buffer, password->Length); + PhDereferenceObject(password); + } + + PhClearReference(&serviceStartName); + PhClearReference(&dependencies); + PhClearReference(&loadOrderGroup); + PhClearReference(&binaryPathName); + PhClearReference(&serviceName); + + return status; +} + +NTSTATUS PhSvcpUnpackRoot( + _In_ PPH_RELATIVE_STRINGREF PackedData, + _In_ PVOID CapturedBuffer, + _In_ SIZE_T Length, + _Out_ PVOID *ValidatedBuffer + ) +{ + if (Length > PackedData->Length) + return STATUS_ACCESS_VIOLATION; + + *ValidatedBuffer = CapturedBuffer; + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcpUnpackBuffer( + _In_ PPH_RELATIVE_STRINGREF PackedData, + _In_ PVOID CapturedBuffer, + _In_ PVOID *OffsetInBuffer, + _In_ SIZE_T Length, + _In_ ULONG Alignment, + _In_ BOOLEAN AllowNull + ) +{ + SIZE_T offset; + + offset = (SIZE_T)*OffsetInBuffer; + + if (offset == 0) + { + if (AllowNull) + return STATUS_SUCCESS; + else + return STATUS_ACCESS_VIOLATION; + } + + if (offset + Length < offset) + return STATUS_ACCESS_VIOLATION; + if (offset + Length > PackedData->Length) + return STATUS_ACCESS_VIOLATION; + if (offset & (Alignment - 1)) + return STATUS_DATATYPE_MISALIGNMENT; + + *OffsetInBuffer = (PVOID)((ULONG_PTR)CapturedBuffer + offset); + + return STATUS_SUCCESS; +} + +NTSTATUS PhSvcpUnpackStringZ( + _In_ PPH_RELATIVE_STRINGREF PackedData, + _In_ PVOID CapturedBuffer, + _In_ PVOID *OffsetInBuffer, + _In_ BOOLEAN Multi, + _In_ BOOLEAN AllowNull + ) +{ + SIZE_T offset; + PWCHAR start; + PWCHAR end; + PH_STRINGREF remainingPart; + PH_STRINGREF firstPart; + + offset = (SIZE_T)*OffsetInBuffer; + + if (offset == 0) + { + if (AllowNull) + return STATUS_SUCCESS; + else + return STATUS_ACCESS_VIOLATION; + } + + if (offset >= PackedData->Length) + return STATUS_ACCESS_VIOLATION; + if (offset & 1) + return STATUS_DATATYPE_MISALIGNMENT; + + start = (PWCHAR)((ULONG_PTR)CapturedBuffer + offset); + end = (PWCHAR)((ULONG_PTR)CapturedBuffer + (PackedData->Length & -2)); + remainingPart.Buffer = start; + remainingPart.Length = (end - start) * sizeof(WCHAR); + + if (Multi) + { + SIZE_T validatedLength = 0; + + while (PhSplitStringRefAtChar(&remainingPart, 0, &firstPart, &remainingPart)) + { + validatedLength += firstPart.Length + sizeof(WCHAR); + + if (firstPart.Length == 0) + { + *OffsetInBuffer = start; + + return STATUS_SUCCESS; + } + } + } + else + { + if (PhSplitStringRefAtChar(&remainingPart, 0, &firstPart, &remainingPart)) + { + *OffsetInBuffer = start; + + return STATUS_SUCCESS; + } + } + + return STATUS_ACCESS_VIOLATION; +} + +NTSTATUS PhSvcApiChangeServiceConfig2( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING serviceName = NULL; + PVOID info = NULL; + SC_HANDLE serviceHandle = NULL; + PH_RELATIVE_STRINGREF packedData; + PVOID unpackedInfo = NULL; + ACCESS_MASK desiredAccess = SERVICE_CHANGE_CONFIG; + + if (!NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.ChangeServiceConfig2.i.ServiceName, FALSE, &serviceName))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcCaptureBuffer(&Payload->u.ChangeServiceConfig2.i.Info, FALSE, &info))) + goto CleanupExit; + + packedData = Payload->u.ChangeServiceConfig2.i.Info; + + switch (Payload->u.ChangeServiceConfig2.i.InfoLevel) + { + case SERVICE_CONFIG_FAILURE_ACTIONS: + { + LPSERVICE_FAILURE_ACTIONS failureActions; + + if (!NT_SUCCESS(status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_FAILURE_ACTIONS), &failureActions))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackStringZ(&packedData, info, &failureActions->lpRebootMsg, FALSE, TRUE))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackStringZ(&packedData, info, &failureActions->lpCommand, FALSE, TRUE))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackBuffer(&packedData, info, &failureActions->lpsaActions, failureActions->cActions * sizeof(SC_ACTION), __alignof(SC_ACTION), TRUE))) + goto CleanupExit; + + if (failureActions->lpsaActions) + { + ULONG i; + + for (i = 0; i < failureActions->cActions; i++) + { + if (failureActions->lpsaActions[i].Type == SC_ACTION_RESTART) + { + desiredAccess |= SERVICE_START; + break; + } + } + } + + unpackedInfo = failureActions; + } + break; + case SERVICE_CONFIG_DELAYED_AUTO_START_INFO: + status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_DELAYED_AUTO_START_INFO), &unpackedInfo); + break; + case SERVICE_CONFIG_FAILURE_ACTIONS_FLAG: + status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_FAILURE_ACTIONS_FLAG), &unpackedInfo); + break; + case SERVICE_CONFIG_SERVICE_SID_INFO: + status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_SID_INFO), &unpackedInfo); + break; + case SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO: + { + LPSERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo; + + if (!NT_SUCCESS(status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_REQUIRED_PRIVILEGES_INFO), &requiredPrivilegesInfo))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackStringZ(&packedData, info, &requiredPrivilegesInfo->pmszRequiredPrivileges, TRUE, FALSE))) + goto CleanupExit; + + unpackedInfo = requiredPrivilegesInfo; + } + break; + case SERVICE_CONFIG_PRESHUTDOWN_INFO: + status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_PRESHUTDOWN_INFO), &unpackedInfo); + break; + case SERVICE_CONFIG_TRIGGER_INFO: + { + PSERVICE_TRIGGER_INFO triggerInfo; + ULONG i; + PSERVICE_TRIGGER trigger; + ULONG j; + PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM dataItem; + ULONG alignment; + + if (!NT_SUCCESS(status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_TRIGGER_INFO), &triggerInfo))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackBuffer(&packedData, info, &triggerInfo->pTriggers, triggerInfo->cTriggers * sizeof(SERVICE_TRIGGER), __alignof(SERVICE_TRIGGER), TRUE))) + goto CleanupExit; + + if (triggerInfo->pTriggers) + { + for (i = 0; i < triggerInfo->cTriggers; i++) + { + trigger = &triggerInfo->pTriggers[i]; + + if (!NT_SUCCESS(status = PhSvcpUnpackBuffer(&packedData, info, &trigger->pTriggerSubtype, sizeof(GUID), __alignof(GUID), TRUE))) + goto CleanupExit; + if (!NT_SUCCESS(status = PhSvcpUnpackBuffer(&packedData, info, &trigger->pDataItems, trigger->cDataItems * sizeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM), __alignof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM), TRUE))) + goto CleanupExit; + + if (trigger->pDataItems) + { + for (j = 0; j < trigger->cDataItems; j++) + { + dataItem = &trigger->pDataItems[j]; + alignment = 1; + + switch (dataItem->dwDataType) + { + case SERVICE_TRIGGER_DATA_TYPE_BINARY: + case SERVICE_TRIGGER_DATA_TYPE_LEVEL: + alignment = sizeof(CHAR); + break; + case SERVICE_TRIGGER_DATA_TYPE_STRING: + alignment = sizeof(WCHAR); + break; + case SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY: + case SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL: + alignment = sizeof(ULONG64); + break; + } + + if (!NT_SUCCESS(status = PhSvcpUnpackBuffer(&packedData, info, &dataItem->pData, dataItem->cbData, alignment, FALSE))) + goto CleanupExit; + } + } + } + } + + unpackedInfo = triggerInfo; + } + break; + case SERVICE_CONFIG_LAUNCH_PROTECTED: + status = PhSvcpUnpackRoot(&packedData, info, sizeof(SERVICE_LAUNCH_PROTECTED_INFO), &unpackedInfo); + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + if (NT_SUCCESS(status)) + { + assert(unpackedInfo); + + if (!(serviceHandle = PhOpenService(serviceName->Buffer, desiredAccess))) + { + status = PhGetLastWin32ErrorAsNtStatus(); + goto CleanupExit; + } + + if (!ChangeServiceConfig2( + serviceHandle, + Payload->u.ChangeServiceConfig2.i.InfoLevel, + unpackedInfo + )) + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + } + +CleanupExit: + if (serviceHandle) + CloseServiceHandle(serviceHandle); + if (info) + PhFree(info); + if (serviceName) + PhDereferenceObject(serviceName); + + return status; +} + +NTSTATUS PhSvcApiSetTcpEntry( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + static PVOID setTcpEntry = NULL; + + ULONG (__stdcall *localSetTcpEntry)(PVOID TcpRow); + struct + { + DWORD dwState; + DWORD dwLocalAddr; + DWORD dwLocalPort; + DWORD dwRemoteAddr; + DWORD dwRemotePort; + } tcpRow; + ULONG result; + + localSetTcpEntry = setTcpEntry; + + if (!localSetTcpEntry) + { + HMODULE iphlpapiModule; + + iphlpapiModule = LoadLibrary(L"iphlpapi.dll"); + + if (iphlpapiModule) + { + localSetTcpEntry = (PVOID)GetProcAddress(iphlpapiModule, "SetTcpEntry"); + + if (localSetTcpEntry) + { + if (_InterlockedExchangePointer(&setTcpEntry, localSetTcpEntry) != NULL) + { + // Another thread got the address of SetTcpEntry already. + // Decrement the reference count of iphlpapi.dll. + FreeLibrary(iphlpapiModule); + } + } + } + } + + if (!localSetTcpEntry) + return STATUS_NOT_SUPPORTED; + + tcpRow.dwState = Payload->u.SetTcpEntry.i.State; + tcpRow.dwLocalAddr = Payload->u.SetTcpEntry.i.LocalAddress; + tcpRow.dwLocalPort = Payload->u.SetTcpEntry.i.LocalPort; + tcpRow.dwRemoteAddr = Payload->u.SetTcpEntry.i.RemoteAddress; + tcpRow.dwRemotePort = Payload->u.SetTcpEntry.i.RemotePort; + result = localSetTcpEntry(&tcpRow); + + return NTSTATUS_FROM_WIN32(result); +} + +NTSTATUS PhSvcApiControlThread( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + HANDLE threadId; + HANDLE threadHandle; + + threadId = Payload->u.ControlThread.i.ThreadId; + + switch (Payload->u.ControlThread.i.Command) + { + case PhSvcControlThreadTerminate: + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_TERMINATE, threadId))) + { + status = NtTerminateThread(threadHandle, STATUS_SUCCESS); + NtClose(threadHandle); + } + break; + case PhSvcControlThreadSuspend: + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_SUSPEND_RESUME, threadId))) + { + status = NtSuspendThread(threadHandle, NULL); + NtClose(threadHandle); + } + break; + case PhSvcControlThreadResume: + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_SUSPEND_RESUME, threadId))) + { + status = NtResumeThread(threadHandle, NULL); + NtClose(threadHandle); + } + break; + case PhSvcControlThreadIoPriority: + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_SET_INFORMATION, threadId))) + { + status = PhSetThreadIoPriority(threadHandle, Payload->u.ControlThread.i.Argument); + NtClose(threadHandle); + } + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + return status; +} + +NTSTATUS PhSvcApiAddAccountRight( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PSID accountSid; + PPH_STRING userRight; + LSA_HANDLE policyHandle; + UNICODE_STRING userRightUs; + + if (NT_SUCCESS(status = PhSvcCaptureSid(&Payload->u.AddAccountRight.i.AccountSid, FALSE, &accountSid))) + { + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.AddAccountRight.i.UserRight, FALSE, &userRight))) + { + PH_AUTO(userRight); + + if (NT_SUCCESS(status = PhOpenLsaPolicy(&policyHandle, POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, NULL))) + { + PhStringRefToUnicodeString(&userRight->sr, &userRightUs); + status = LsaAddAccountRights(policyHandle, accountSid, &userRightUs, 1); + LsaClose(policyHandle); + } + } + + PhFree(accountSid); + } + + return status; +} + +NTSTATUS PhSvcApiInvokeRunAsService( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PH_RUNAS_SERVICE_PARAMETERS parameters; + PHSVCP_CAPTURED_RUNAS_SERVICE_PARAMETERS capturedParameters; + + if (NT_SUCCESS(status = PhSvcpCaptureRunAsServiceParameters(Client, Payload, ¶meters, &capturedParameters))) + { + if (NT_SUCCESS(status = PhSvcpValidateRunAsServiceParameters(¶meters))) + { + status = PhInvokeRunAsService(¶meters); + } + } + + PhSvcpReleaseRunAsServiceParameters(&capturedParameters); + + return status; +} + +NTSTATUS PhSvcApiIssueMemoryListCommand( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + + status = NtSetSystemInformation( + SystemMemoryListInformation, + &Payload->u.IssueMemoryListCommand.i.Command, + sizeof(SYSTEM_MEMORY_LIST_COMMAND) + ); + + return status; +} + +NTSTATUS PhSvcApiPostMessage( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + if (PostMessage( + Payload->u.PostMessage.i.hWnd, + Payload->u.PostMessage.i.Msg, + Payload->u.PostMessage.i.wParam, + Payload->u.PostMessage.i.lParam + )) + { + return STATUS_SUCCESS; + } + else + { + return PhGetLastWin32ErrorAsNtStatus(); + } +} + +NTSTATUS PhSvcApiSendMessage( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + if (SendMessage( + Payload->u.PostMessage.i.hWnd, + Payload->u.PostMessage.i.Msg, + Payload->u.PostMessage.i.wParam, + Payload->u.PostMessage.i.lParam + )) + { + return STATUS_SUCCESS; + } + else + { + return PhGetLastWin32ErrorAsNtStatus(); + } +} + +NTSTATUS PhSvcApiCreateProcessIgnoreIfeoDebugger( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING fileName; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.CreateProcessIgnoreIfeoDebugger.i.FileName, FALSE, &fileName))) + { + PH_AUTO(fileName); + + if (!PhCreateProcessIgnoreIfeoDebugger(fileName->Buffer)) + status = STATUS_UNSUCCESSFUL; + } + + return status; +} + +NTSTATUS PhSvcApiSetServiceSecurity( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + NTSTATUS status; + PPH_STRING serviceName; + PSECURITY_DESCRIPTOR securityDescriptor; + ACCESS_MASK desiredAccess; + SC_HANDLE serviceHandle; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.SetServiceSecurity.i.ServiceName, FALSE, &serviceName))) + { + PH_AUTO(serviceName); + + if (NT_SUCCESS(status = PhSvcCaptureSecurityDescriptor(&Payload->u.SetServiceSecurity.i.SecurityDescriptor, FALSE, 0, &securityDescriptor))) + { + desiredAccess = 0; + + if ((Payload->u.SetServiceSecurity.i.SecurityInformation & OWNER_SECURITY_INFORMATION) || + (Payload->u.SetServiceSecurity.i.SecurityInformation & GROUP_SECURITY_INFORMATION)) + { + desiredAccess |= WRITE_OWNER; + } + + if (Payload->u.SetServiceSecurity.i.SecurityInformation & DACL_SECURITY_INFORMATION) + { + desiredAccess |= WRITE_DAC; + } + + if (Payload->u.SetServiceSecurity.i.SecurityInformation & SACL_SECURITY_INFORMATION) + { + desiredAccess |= ACCESS_SYSTEM_SECURITY; + } + + if (serviceHandle = PhOpenService(serviceName->Buffer, desiredAccess)) + { + status = PhSetSeObjectSecurity( + serviceHandle, + SE_SERVICE, + Payload->u.SetServiceSecurity.i.SecurityInformation, + securityDescriptor + ); + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + + PhFree(securityDescriptor); + } + } + + return status; +} + +NTSTATUS PhSvcApiLoadDbgHelp( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + static BOOLEAN alreadyLoaded; + + NTSTATUS status; + PPH_STRING dbgHelpPath; + + if (alreadyLoaded) + return STATUS_SOME_NOT_MAPPED; + + if (NT_SUCCESS(status = PhSvcCaptureString(&Payload->u.LoadDbgHelp.i.DbgHelpPath, FALSE, &dbgHelpPath))) + { + PH_AUTO(dbgHelpPath); + PhLoadDbgHelpFromPath(dbgHelpPath->Buffer); + alreadyLoaded = TRUE; + } + + return status; +} + +NTSTATUS PhSvcApiWriteMiniDumpProcess( + _In_ PPHSVC_CLIENT Client, + _Inout_ PPHSVC_API_PAYLOAD Payload + ) +{ + if (PhWriteMiniDumpProcess( + UlongToHandle(Payload->u.WriteMiniDumpProcess.i.LocalProcessHandle), + UlongToHandle(Payload->u.WriteMiniDumpProcess.i.ProcessId), + UlongToHandle(Payload->u.WriteMiniDumpProcess.i.LocalFileHandle), + Payload->u.WriteMiniDumpProcess.i.DumpType, + NULL, + NULL, + NULL + )) + { + return STATUS_SUCCESS; + } + else + { + ULONG error; + + error = GetLastError(); + + if (error == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER)) + return STATUS_INVALID_PARAMETER; + else + return STATUS_UNSUCCESSFUL; + } +} diff --git a/ProcessHacker/phsvc/svcapiport.c b/ProcessHacker/phsvc/svcapiport.c new file mode 100644 index 0000000..c4b3043 --- /dev/null +++ b/ProcessHacker/phsvc/svcapiport.c @@ -0,0 +1,318 @@ +/* + * Process Hacker - + * server API port + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +NTSTATUS PhSvcApiRequestThreadStart( + _In_ PVOID Parameter + ); + +extern HANDLE PhSvcTimeoutStandbyEventHandle; +extern HANDLE PhSvcTimeoutCancelEventHandle; + +ULONG PhSvcApiThreadContextTlsIndex; +HANDLE PhSvcApiPortHandle; +ULONG PhSvcApiNumberOfClients = 0; + +NTSTATUS PhSvcApiPortInitialization( + _In_ PUNICODE_STRING PortName + ) +{ + static SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; + + NTSTATUS status; + OBJECT_ATTRIBUTES objectAttributes; + PSECURITY_DESCRIPTOR securityDescriptor; + ULONG sdAllocationLength; + UCHAR administratorsSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; + PSID administratorsSid; + PACL dacl; + ULONG i; + HANDLE threadHandle; + + // Create the API port. + + administratorsSid = (PSID)administratorsSidBuffer; + RtlInitializeSid(administratorsSid, &ntAuthority, 2); + *RtlSubAuthoritySid(administratorsSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; + *RtlSubAuthoritySid(administratorsSid, 1) = DOMAIN_ALIAS_RID_ADMINS; + + sdAllocationLength = SECURITY_DESCRIPTOR_MIN_LENGTH + + (ULONG)sizeof(ACL) + + (ULONG)sizeof(ACCESS_ALLOWED_ACE) + + RtlLengthSid(administratorsSid) + + (ULONG)sizeof(ACCESS_ALLOWED_ACE) + + RtlLengthSid(&PhSeEveryoneSid); + + securityDescriptor = PhAllocate(sdAllocationLength); + dacl = (PACL)((PCHAR)securityDescriptor + SECURITY_DESCRIPTOR_MIN_LENGTH); + + RtlCreateSecurityDescriptor(securityDescriptor, SECURITY_DESCRIPTOR_REVISION); + RtlCreateAcl(dacl, sdAllocationLength - SECURITY_DESCRIPTOR_MIN_LENGTH, ACL_REVISION); + RtlAddAccessAllowedAce(dacl, ACL_REVISION, PORT_ALL_ACCESS, administratorsSid); + RtlAddAccessAllowedAce(dacl, ACL_REVISION, PORT_CONNECT, &PhSeEveryoneSid); + RtlSetDaclSecurityDescriptor(securityDescriptor, TRUE, dacl, FALSE); + + InitializeObjectAttributes( + &objectAttributes, + PortName, + OBJ_CASE_INSENSITIVE, + NULL, + securityDescriptor + ); + + status = NtCreatePort( + &PhSvcApiPortHandle, + &objectAttributes, + sizeof(PHSVC_API_CONNECTINFO), + PhIsExecutingInWow64() ? sizeof(PHSVC_API_MSG64) : sizeof(PHSVC_API_MSG), + 0 + ); + PhFree(securityDescriptor); + + if (!NT_SUCCESS(status)) + return status; + + // Start the API threads. + + PhSvcApiThreadContextTlsIndex = TlsAlloc(); + + for (i = 0; i < 2; i++) + { + threadHandle = PhCreateThread(0, PhSvcApiRequestThreadStart, NULL); + + if (threadHandle) + NtClose(threadHandle); + } + + return status; +} + +PPHSVC_THREAD_CONTEXT PhSvcGetCurrentThreadContext( + VOID + ) +{ + return (PPHSVC_THREAD_CONTEXT)TlsGetValue(PhSvcApiThreadContextTlsIndex); +} + +NTSTATUS PhSvcApiRequestThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + NTSTATUS status; + PHSVC_THREAD_CONTEXT threadContext; + HANDLE portHandle; + PVOID portContext; + SIZE_T messageSize; + PPORT_MESSAGE receiveMessage; + PPORT_MESSAGE replyMessage; + CSHORT messageType; + PPHSVC_CLIENT client; + PPHSVC_API_PAYLOAD payload; + + PhInitializeAutoPool(&autoPool); + + threadContext.CurrentClient = NULL; + threadContext.OldClient = NULL; + + TlsSetValue(PhSvcApiThreadContextTlsIndex, &threadContext); + + portHandle = PhSvcApiPortHandle; + messageSize = PhIsExecutingInWow64() ? sizeof(PHSVC_API_MSG64) : sizeof(PHSVC_API_MSG); + receiveMessage = PhAllocate(messageSize); + replyMessage = NULL; + + while (TRUE) + { + status = NtReplyWaitReceivePort( + portHandle, + &portContext, + replyMessage, + receiveMessage + ); + + portHandle = PhSvcApiPortHandle; + replyMessage = NULL; + + if (!NT_SUCCESS(status)) + { + // Client probably died. + continue; + } + + messageType = receiveMessage->u2.s2.Type; + + if (messageType == LPC_CONNECTION_REQUEST) + { + PhSvcHandleConnectionRequest(receiveMessage); + continue; + } + + if (!portContext) + continue; + + client = portContext; + threadContext.CurrentClient = client; + PhWaitForEvent(&client->ReadyEvent, NULL); + + if (messageType == LPC_REQUEST) + { + if (PhIsExecutingInWow64()) + payload = &((PPHSVC_API_MSG64)receiveMessage)->p; + else + payload = &((PPHSVC_API_MSG)receiveMessage)->p; + + PhSvcDispatchApiCall(client, payload, &portHandle); + replyMessage = receiveMessage; + } + else if (messageType == LPC_PORT_CLOSED) + { + PhDereferenceObject(client); + + if (_InterlockedDecrement(&PhSvcApiNumberOfClients) == 0) + { + NtSetEvent(PhSvcTimeoutStandbyEventHandle, NULL); + } + } + + assert(!threadContext.OldClient); + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); +} + +VOID PhSvcHandleConnectionRequest( + _In_ PPORT_MESSAGE PortMessage + ) +{ + NTSTATUS status; + PPHSVC_API_MSG message; + PPHSVC_API_MSG64 message64; + CLIENT_ID clientId; + PPHSVC_CLIENT client; + HANDLE portHandle; + REMOTE_PORT_VIEW clientView; + REMOTE_PORT_VIEW64 clientView64; + PREMOTE_PORT_VIEW actualClientView; + + message = (PPHSVC_API_MSG)PortMessage; + message64 = (PPHSVC_API_MSG64)PortMessage; + + if (PhIsExecutingInWow64()) + { + clientId.UniqueProcess = (HANDLE)message64->h.ClientId.UniqueProcess; + clientId.UniqueThread = (HANDLE)message64->h.ClientId.UniqueThread; + } + else + { + PPH_STRING referenceFileName; + PPH_STRING remoteFileName; + + clientId = message->h.ClientId; + + // Make sure that the remote process is Process Hacker itself and not some other program. + + referenceFileName = NULL; + PhGetProcessImageFileNameByProcessId(NtCurrentProcessId(), &referenceFileName); + PH_AUTO(referenceFileName); + + remoteFileName = NULL; + PhGetProcessImageFileNameByProcessId(NtCurrentProcessId(), &remoteFileName); + PH_AUTO(remoteFileName); + + if (!referenceFileName || !remoteFileName || !PhEqualString(referenceFileName, remoteFileName, TRUE)) + { + NtAcceptConnectPort(&portHandle, NULL, PortMessage, FALSE, NULL, NULL); + return; + } + } + + client = PhSvcCreateClient(&clientId); + + if (!client) + { + NtAcceptConnectPort(&portHandle, NULL, PortMessage, FALSE, NULL, NULL); + return; + } + + if (PhIsExecutingInWow64()) + { + message64->p.ConnectInfo.ServerProcessId = HandleToUlong(NtCurrentProcessId()); + + clientView64.Length = sizeof(REMOTE_PORT_VIEW64); + clientView64.ViewSize = 0; + clientView64.ViewBase = 0; + actualClientView = (PREMOTE_PORT_VIEW)&clientView64; + } + else + { + message->p.ConnectInfo.ServerProcessId = HandleToUlong(NtCurrentProcessId()); + + clientView.Length = sizeof(REMOTE_PORT_VIEW); + clientView.ViewSize = 0; + clientView.ViewBase = NULL; + actualClientView = &clientView; + } + + status = NtAcceptConnectPort( + &portHandle, + client, + PortMessage, + TRUE, + NULL, + actualClientView + ); + + if (!NT_SUCCESS(status)) + { + PhDereferenceObject(client); + return; + } + + // IMPORTANT: Since Vista, NtCompleteConnectPort does not do anything and simply returns STATUS_SUCCESS. + // We will call it anyway (for completeness), but we need to use an event to ensure that other threads don't try + // to process requests before we have finished setting up the client object. + + client->PortHandle = portHandle; + + if (PhIsExecutingInWow64()) + { + client->ClientViewBase = (PVOID)clientView64.ViewBase; + client->ClientViewLimit = (PCHAR)clientView64.ViewBase + (ULONG)clientView64.ViewSize; + } + else + { + client->ClientViewBase = clientView.ViewBase; + client->ClientViewLimit = (PCHAR)clientView.ViewBase + clientView.ViewSize; + } + + NtCompleteConnectPort(portHandle); + PhSetEvent(&client->ReadyEvent); + + if (_InterlockedIncrement(&PhSvcApiNumberOfClients) == 1) + { + NtSetEvent(PhSvcTimeoutCancelEventHandle, NULL); + } +} diff --git a/ProcessHacker/phsvc/svcclient.c b/ProcessHacker/phsvc/svcclient.c new file mode 100644 index 0000000..8b6fe4c --- /dev/null +++ b/ProcessHacker/phsvc/svcclient.c @@ -0,0 +1,159 @@ +/* + * Process Hacker - + * server client + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +VOID NTAPI PhSvcpClientDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +PPH_OBJECT_TYPE PhSvcClientType; +LIST_ENTRY PhSvcClientListHead; +PH_QUEUED_LOCK PhSvcClientListLock = PH_QUEUED_LOCK_INIT; + +NTSTATUS PhSvcClientInitialization( + VOID + ) +{ + PhSvcClientType = PhCreateObjectType(L"Client", 0, PhSvcpClientDeleteProcedure); + InitializeListHead(&PhSvcClientListHead); + + return STATUS_SUCCESS; +} + +PPHSVC_CLIENT PhSvcCreateClient( + _In_opt_ PCLIENT_ID ClientId + ) +{ + PPHSVC_CLIENT client; + + client = PhCreateObject(sizeof(PHSVC_CLIENT), PhSvcClientType); + memset(client, 0, sizeof(PHSVC_CLIENT)); + PhInitializeEvent(&client->ReadyEvent); + + if (ClientId) + client->ClientId = *ClientId; + + PhAcquireQueuedLockExclusive(&PhSvcClientListLock); + InsertTailList(&PhSvcClientListHead, &client->ListEntry); + PhReleaseQueuedLockExclusive(&PhSvcClientListLock); + + return client; +} + +VOID NTAPI PhSvcpClientDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPHSVC_CLIENT client = Object; + + PhAcquireQueuedLockExclusive(&PhSvcClientListLock); + RemoveEntryList(&client->ListEntry); + PhReleaseQueuedLockExclusive(&PhSvcClientListLock); + + if (client->PortHandle) + NtClose(client->PortHandle); +} + +PPHSVC_CLIENT PhSvcReferenceClientByClientId( + _In_ PCLIENT_ID ClientId + ) +{ + PLIST_ENTRY listEntry; + PPHSVC_CLIENT client = NULL; + + PhAcquireQueuedLockShared(&PhSvcClientListLock); + + listEntry = PhSvcClientListHead.Flink; + + while (listEntry != &PhSvcClientListHead) + { + client = CONTAINING_RECORD(listEntry, PHSVC_CLIENT, ListEntry); + + if (ClientId->UniqueThread) + { + if ( + client->ClientId.UniqueProcess == ClientId->UniqueProcess && + client->ClientId.UniqueThread == ClientId->UniqueThread + ) + { + break; + } + } + else + { + if (client->ClientId.UniqueProcess == ClientId->UniqueProcess) + break; + } + + client = NULL; + + listEntry = listEntry->Flink; + } + + if (client) + { + if (!PhReferenceObjectSafe(client)) + client = NULL; + } + + PhReleaseQueuedLockShared(&PhSvcClientListLock); + + return client; +} + +PPHSVC_CLIENT PhSvcGetCurrentClient( + VOID + ) +{ + return PhSvcGetCurrentThreadContext()->CurrentClient; +} + +BOOLEAN PhSvcAttachClient( + _In_ PPHSVC_CLIENT Client + ) +{ + PPHSVC_THREAD_CONTEXT threadContext = PhSvcGetCurrentThreadContext(); + + if (threadContext->OldClient) + return FALSE; + + PhReferenceObject(Client); + threadContext->OldClient = threadContext->CurrentClient; + threadContext->CurrentClient = Client; + + return TRUE; +} + +VOID PhSvcDetachClient( + _In_ PPHSVC_CLIENT Client + ) +{ + PPHSVC_THREAD_CONTEXT threadContext = PhSvcGetCurrentThreadContext(); + + PhDereferenceObject(threadContext->CurrentClient); + threadContext->CurrentClient = threadContext->OldClient; + threadContext->OldClient = NULL; +} diff --git a/ProcessHacker/phsvc/svcmain.c b/ProcessHacker/phsvc/svcmain.c new file mode 100644 index 0000000..660c55c --- /dev/null +++ b/ProcessHacker/phsvc/svcmain.c @@ -0,0 +1,111 @@ +/* + * Process Hacker - + * server + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +HANDLE PhSvcTimeoutStandbyEventHandle; +HANDLE PhSvcTimeoutCancelEventHandle; + +NTSTATUS PhSvcMain( + _In_opt_ PUNICODE_STRING PortName, + _In_opt_ PLARGE_INTEGER Timeout, + _Inout_opt_ PPHSVC_STOP Stop + ) +{ + NTSTATUS status; + UNICODE_STRING portName; + LARGE_INTEGER timeout; + + if (!PortName) + { + if (PhIsExecutingInWow64()) + RtlInitUnicodeString(&portName, PHSVC_WOW64_PORT_NAME); + else + RtlInitUnicodeString(&portName, PHSVC_PORT_NAME); + + PortName = &portName; + } + + if (!Timeout) + { + timeout.QuadPart = -15 * PH_TIMEOUT_SEC; + Timeout = &timeout; + } + + if (!NT_SUCCESS(status = PhSvcClientInitialization())) + return status; + if (!NT_SUCCESS(status = PhSvcApiInitialization())) + return status; + if (!NT_SUCCESS(status = PhSvcApiPortInitialization(PortName))) + return status; + + if (!NT_SUCCESS(status = NtCreateEvent(&PhSvcTimeoutStandbyEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, TRUE))) + return status; + + if (!NT_SUCCESS(status = NtCreateEvent(&PhSvcTimeoutCancelEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) + { + NtClose(PhSvcTimeoutStandbyEventHandle); + return status; + } + + if (Stop) + { + Stop->Event1 = PhSvcTimeoutStandbyEventHandle; + Stop->Event2 = PhSvcTimeoutCancelEventHandle; + MemoryBarrier(); + + if (Stop->Stop) + return STATUS_SUCCESS; + } + + while (TRUE) + { + NtWaitForSingleObject(PhSvcTimeoutStandbyEventHandle, FALSE, NULL); + + if (Stop && Stop->Stop) + break; + + status = NtWaitForSingleObject(PhSvcTimeoutCancelEventHandle, FALSE, Timeout); + + if (Stop && Stop->Stop) + break; + if (status == STATUS_TIMEOUT) + break; + + // A client connected, so we wait on the standby event again. + } + + return status; +} + +VOID PhSvcStop( + _Inout_ PPHSVC_STOP Stop + ) +{ + Stop->Stop = TRUE; + + if (Stop->Event1) + NtSetEvent(Stop->Event1, NULL); + if (Stop->Event2) + NtSetEvent(Stop->Event2, NULL); +} diff --git a/ProcessHacker/plugin.c b/ProcessHacker/plugin.c new file mode 100644 index 0000000..8664158 --- /dev/null +++ b/ProcessHacker/plugin.c @@ -0,0 +1,1049 @@ +/* + * Process Hacker - + * plugin support + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PHP_PLUGIN_LOAD_ERROR +{ + PPH_STRING FileName; + PPH_STRING ErrorMessage; +} PHP_PLUGIN_LOAD_ERROR, *PPHP_PLUGIN_LOAD_ERROR; + +typedef struct _PHP_PLUGIN_MENU_HOOK +{ + PPH_PLUGIN Plugin; + PVOID Context; +} PHP_PLUGIN_MENU_HOOK, *PPHP_PLUGIN_MENU_HOOK; + +INT NTAPI PhpPluginsCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +BOOLEAN PhLoadPlugin( + _In_ PPH_STRING FileName + ); + +VOID PhpExecuteCallbackForAllPlugins( + _In_ PH_PLUGIN_CALLBACK Callback, + _In_ BOOLEAN StartupParameters + ); + +PH_AVL_TREE PhPluginsByName = PH_AVL_TREE_INIT(PhpPluginsCompareFunction); + +static PH_CALLBACK GeneralCallbacks[GeneralCallbackMaximum]; +static PPH_STRING PluginsDirectory; +static PPH_LIST LoadErrors; +static ULONG NextPluginId = IDPLUGINS + 1; + +VOID PhPluginsInitialization( + VOID + ) +{ + ULONG i; + + for (i = 0; i < GeneralCallbackMaximum; i++) + PhInitializeCallback(&GeneralCallbacks[i]); + + if (WindowsVersion <= WINDOWS_XP) + { + PH_STRINGREF extendedTools = PH_STRINGREF_INIT(L"ExtendedTools.dll"); + + // HACK and violation of abstraction. + // Always disable ExtendedTools on XP to avoid the annoying error message. + PhSetPluginDisabled(&extendedTools, TRUE); + } +} + +INT NTAPI PhpPluginsCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PPH_PLUGIN plugin1 = CONTAINING_RECORD(Links1, PH_PLUGIN, Links); + PPH_PLUGIN plugin2 = CONTAINING_RECORD(Links2, PH_PLUGIN, Links); + + return PhCompareStringRef(&plugin1->Name, &plugin2->Name, FALSE); +} + +BOOLEAN PhpLocateDisabledPlugin( + _In_ PPH_STRING List, + _In_ PPH_STRINGREF BaseName, + _Out_opt_ PULONG FoundIndex + ) +{ + PH_STRINGREF namePart; + PH_STRINGREF remainingPart; + + remainingPart = List->sr; + + while (remainingPart.Length != 0) + { + PhSplitStringRefAtChar(&remainingPart, '|', &namePart, &remainingPart); + + if (PhEqualStringRef(&namePart, BaseName, TRUE)) + { + if (FoundIndex) + *FoundIndex = (ULONG)(namePart.Buffer - List->Buffer); + + return TRUE; + } + } + + return FALSE; +} + +BOOLEAN PhIsPluginDisabled( + _In_ PPH_STRINGREF BaseName + ) +{ + BOOLEAN found; + PPH_STRING disabled; + + disabled = PhGetStringSetting(L"DisabledPlugins"); + found = PhpLocateDisabledPlugin(disabled, BaseName, NULL); + PhDereferenceObject(disabled); + + return found; +} + +VOID PhSetPluginDisabled( + _In_ PPH_STRINGREF BaseName, + _In_ BOOLEAN Disable + ) +{ + BOOLEAN found; + PPH_STRING disabled; + ULONG foundIndex; + PPH_STRING newDisabled; + + disabled = PhGetStringSetting(L"DisabledPlugins"); + + found = PhpLocateDisabledPlugin(disabled, BaseName, &foundIndex); + + if (Disable && !found) + { + // We need to add the plugin to the disabled list. + + if (disabled->Length != 0) + { + // We have other disabled plugins. Append a pipe character followed by the plugin name. + newDisabled = PhCreateStringEx(NULL, disabled->Length + sizeof(WCHAR) + BaseName->Length); + memcpy(newDisabled->Buffer, disabled->Buffer, disabled->Length); + newDisabled->Buffer[disabled->Length / 2] = '|'; + memcpy(&newDisabled->Buffer[disabled->Length / 2 + 1], BaseName->Buffer, BaseName->Length); + PhSetStringSetting2(L"DisabledPlugins", &newDisabled->sr); + PhDereferenceObject(newDisabled); + } + else + { + // This is the first disabled plugin. + PhSetStringSetting2(L"DisabledPlugins", BaseName); + } + } + else if (!Disable && found) + { + ULONG removeCount; + + // We need to remove the plugin from the disabled list. + + removeCount = (ULONG)BaseName->Length / 2; + + if (foundIndex + (ULONG)BaseName->Length / 2 < (ULONG)disabled->Length / 2) + { + // Remove the following pipe character as well. + removeCount++; + } + else if (foundIndex != 0) + { + // Remove the preceding pipe character as well. + foundIndex--; + removeCount++; + } + + newDisabled = PhCreateStringEx(NULL, disabled->Length - removeCount * sizeof(WCHAR)); + memcpy(newDisabled->Buffer, disabled->Buffer, foundIndex * sizeof(WCHAR)); + memcpy(&newDisabled->Buffer[foundIndex], &disabled->Buffer[foundIndex + removeCount], + disabled->Length - removeCount * sizeof(WCHAR) - foundIndex * sizeof(WCHAR)); + PhSetStringSetting2(L"DisabledPlugins", &newDisabled->sr); + PhDereferenceObject(newDisabled); + } + + PhDereferenceObject(disabled); +} + +static BOOLEAN EnumPluginsDirectoryCallback( + _In_ PFILE_DIRECTORY_INFORMATION Information, + _In_opt_ PVOID Context + ) +{ + PH_STRINGREF baseName; + PPH_STRING fileName; + + baseName.Buffer = Information->FileName; + baseName.Length = Information->FileNameLength; + + if (PhEndsWithStringRef2(&baseName, L".dll", TRUE)) + { + if (!PhIsPluginDisabled(&baseName)) + { + fileName = PhCreateStringEx(NULL, PluginsDirectory->Length + Information->FileNameLength); + memcpy(fileName->Buffer, PluginsDirectory->Buffer, PluginsDirectory->Length); + memcpy(&fileName->Buffer[PluginsDirectory->Length / 2], Information->FileName, Information->FileNameLength); + + PhLoadPlugin(fileName); + + PhDereferenceObject(fileName); + } + } + + return TRUE; +} + +/** + * Loads plugins from the default plugins directory. + */ +VOID PhLoadPlugins( + VOID + ) +{ + HANDLE pluginsDirectoryHandle; + PPH_STRING pluginsDirectory; + + pluginsDirectory = PhGetStringSetting(L"PluginsDirectory"); + + if (RtlDetermineDosPathNameType_U(pluginsDirectory->Buffer) == RtlPathTypeRelative) + { + // Not absolute. Make sure it is. + PluginsDirectory = PhConcatStrings(4, PhApplicationDirectory->Buffer, L"\\", pluginsDirectory->Buffer, L"\\"); + PhDereferenceObject(pluginsDirectory); + } + else + { + PluginsDirectory = pluginsDirectory; + } + + if (NT_SUCCESS(PhCreateFileWin32( + &pluginsDirectoryHandle, + PluginsDirectory->Buffer, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + UNICODE_STRING pattern = RTL_CONSTANT_STRING(L"*.dll"); + + PhEnumDirectoryFile(pluginsDirectoryHandle, &pattern, EnumPluginsDirectoryCallback, NULL); + NtClose(pluginsDirectoryHandle); + } + + // Handle load errors. + // In certain startup modes we want to ignore all plugin load errors. + if (LoadErrors && LoadErrors->Count != 0 && !PhStartupParameters.PhSvc) + { + PH_STRING_BUILDER sb; + ULONG i; + PPHP_PLUGIN_LOAD_ERROR loadError; + PPH_STRING baseName; + + PhInitializeStringBuilder(&sb, 100); + PhAppendStringBuilder2(&sb, L"Unable to load the following plugin(s):\n\n"); + + for (i = 0; i < LoadErrors->Count; i++) + { + loadError = LoadErrors->Items[i]; + baseName = PhGetBaseName(loadError->FileName); + PhAppendFormatStringBuilder(&sb, L"%s: %s\n", + baseName->Buffer, PhGetStringOrDefault(loadError->ErrorMessage, L"An unknown error occurred.")); + PhDereferenceObject(baseName); + } + + PhAppendStringBuilder2(&sb, L"\nDo you want to disable the above plugin(s)?"); + + if (PhShowMessage( + NULL, + MB_ICONERROR | MB_YESNO, + sb.String->Buffer + ) == IDYES) + { + ULONG i; + + for (i = 0; i < LoadErrors->Count; i++) + { + loadError = LoadErrors->Items[i]; + baseName = PhGetBaseName(loadError->FileName); + PhSetPluginDisabled(&baseName->sr, TRUE); + PhDereferenceObject(baseName); + } + } + + PhDeleteStringBuilder(&sb); + } + + // When we loaded settings before, we didn't know about plugin settings, so they + // went into the ignored settings list. Now that they've had a chance to add + // settings, we should scan the ignored settings list and move the settings to + // the right places. + if (PhSettingsFileName) + PhConvertIgnoredSettings(); + + PhpExecuteCallbackForAllPlugins(PluginCallbackLoad, TRUE); +} + +/** + * Notifies all plugins that the program is shutting down. + */ +VOID PhUnloadPlugins( + VOID + ) +{ + PhpExecuteCallbackForAllPlugins(PluginCallbackUnload, FALSE); +} + +/** + * Loads a plugin. + * + * \param FileName The full file name of the plugin. + */ +BOOLEAN PhLoadPlugin( + _In_ PPH_STRING FileName + ) +{ + BOOLEAN success; + PPH_STRING fileName; + PPH_STRING errorMessage; + + fileName = PhGetFullPath(FileName->Buffer, NULL); + + if (!fileName) + PhSetReference(&fileName, FileName); + + success = TRUE; + + if (!LoadLibrary(fileName->Buffer)) + { + success = FALSE; + errorMessage = PhGetWin32Message(GetLastError()); + } + + if (!success) + { + PPHP_PLUGIN_LOAD_ERROR loadError; + + loadError = PhAllocate(sizeof(PHP_PLUGIN_LOAD_ERROR)); + PhSetReference(&loadError->FileName, fileName); + PhSetReference(&loadError->ErrorMessage, errorMessage); + + if (!LoadErrors) + LoadErrors = PhCreateList(2); + + PhAddItemList(LoadErrors, loadError); + + if (errorMessage) + PhDereferenceObject(errorMessage); + } + + PhDereferenceObject(fileName); + + return success; +} + +VOID PhpExecuteCallbackForAllPlugins( + _In_ PH_PLUGIN_CALLBACK Callback, + _In_ BOOLEAN StartupParameters + ) +{ + PPH_AVL_LINKS links; + + for (links = PhMinimumElementAvlTree(&PhPluginsByName); links; links = PhSuccessorElementAvlTree(links)) + { + PPH_PLUGIN plugin = CONTAINING_RECORD(links, PH_PLUGIN, Links); + PPH_LIST parameters = NULL; + + // Find relevant startup parameters for this plugin. + if (StartupParameters && PhStartupParameters.PluginParameters) + { + ULONG i; + + for (i = 0; i < PhStartupParameters.PluginParameters->Count; i++) + { + PPH_STRING string = PhStartupParameters.PluginParameters->Items[i]; + PH_STRINGREF pluginName; + PH_STRINGREF parameter; + + if (PhSplitStringRefAtChar(&string->sr, ':', &pluginName, ¶meter) && + PhEqualStringRef(&pluginName, &plugin->Name, FALSE) && + parameter.Length != 0) + { + if (!parameters) + parameters = PhCreateList(3); + + PhAddItemList(parameters, PhCreateString2(¶meter)); + } + } + } + + PhInvokeCallback(PhGetPluginCallback(plugin, Callback), parameters); + + if (parameters) + { + PhDereferenceObjects(parameters->Items, parameters->Count); + PhDereferenceObject(parameters); + } + } +} + +BOOLEAN PhpValidatePluginName( + _In_ PPH_STRINGREF Name + ) +{ + SIZE_T i; + PWSTR buffer; + SIZE_T count; + + buffer = Name->Buffer; + count = Name->Length / sizeof(WCHAR); + + for (i = 0; i < count; i++) + { + if (!iswalnum(buffer[i]) && buffer[i] != ' ' && buffer[i] != '.' && buffer[i] != '_') + { + return FALSE; + } + } + + return TRUE; +} + +/** + * Registers a plugin with the host. + * + * \param Name A unique identifier for the plugin. The function fails + * if another plugin has already been registered with the same name. The + * name must only contain alphanumeric characters, spaces, dots and + * underscores. + * \param DllBase The base address of the plugin DLL. This is passed + * to the DllMain function. + * \param Information A variable which receives a pointer to the + * plugin's additional information block. This should be filled in after + * the function returns. + * + * \return A pointer to the plugin instance structure, or NULL if the + * function failed. + */ +PPH_PLUGIN PhRegisterPlugin( + _In_ PWSTR Name, + _In_ PVOID DllBase, + _Out_opt_ PPH_PLUGIN_INFORMATION *Information + ) +{ + PPH_PLUGIN plugin; + PH_STRINGREF pluginName; + PPH_AVL_LINKS existingLinks; + ULONG i; + PPH_STRING fileName; + + PhInitializeStringRefLongHint(&pluginName, Name); + + if (!PhpValidatePluginName(&pluginName)) + return NULL; + + fileName = PhGetDllFileName(DllBase, NULL); + + if (!fileName) + return NULL; + + plugin = PhAllocate(sizeof(PH_PLUGIN)); + memset(plugin, 0, sizeof(PH_PLUGIN)); + + plugin->Name = pluginName; + plugin->DllBase = DllBase; + + plugin->FileName = fileName; + + existingLinks = PhAddElementAvlTree(&PhPluginsByName, &plugin->Links); + + if (existingLinks) + { + // Another plugin has already been registered with the same name. + PhFree(plugin); + return NULL; + } + + for (i = 0; i < PluginCallbackMaximum; i++) + PhInitializeCallback(&plugin->Callbacks[i]); + + PhEmInitializeAppContext(&plugin->AppContext, &pluginName); + + if (Information) + *Information = &plugin->Information; + + return plugin; +} + +/** + * Locates a plugin instance structure. + * + * \param Name The name of the plugin. + * + * \return A plugin instance structure, or NULL if the plugin was not found. + */ +PPH_PLUGIN PhFindPlugin( + _In_ PWSTR Name + ) +{ + PH_STRINGREF name; + + PhInitializeStringRefLongHint(&name, Name); + + return PhFindPlugin2(&name); +} + +/** + * Locates a plugin instance structure. + * + * \param Name The name of the plugin. + * + * \return A plugin instance structure, or NULL if the plugin was not found. + */ +PPH_PLUGIN PhFindPlugin2( + _In_ PPH_STRINGREF Name + ) +{ + PPH_AVL_LINKS links; + PH_PLUGIN lookupPlugin; + + lookupPlugin.Name = *Name; + links = PhFindElementAvlTree(&PhPluginsByName, &lookupPlugin.Links); + + if (links) + return CONTAINING_RECORD(links, PH_PLUGIN, Links); + else + return NULL; +} + +/** + * Gets a pointer to a plugin's additional information block. + * + * \param Plugin The plugin instance structure. + * + * \return The plugin's additional information block. + */ +PPH_PLUGIN_INFORMATION PhGetPluginInformation( + _In_ PPH_PLUGIN Plugin + ) +{ + return &Plugin->Information; +} + +/** + * Retrieves a pointer to a plugin callback. + * + * \param Plugin A plugin instance structure. + * \param Callback The type of callback. + * + * \remarks The program invokes plugin callbacks for notifications + * specific to a plugin. + */ +PPH_CALLBACK PhGetPluginCallback( + _In_ PPH_PLUGIN Plugin, + _In_ PH_PLUGIN_CALLBACK Callback + ) +{ + if (Callback >= PluginCallbackMaximum) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + return &Plugin->Callbacks[Callback]; +} + +/** + * Retrieves a pointer to a general callback. + * + * \param Callback The type of callback. + * + * \remarks The program invokes general callbacks for system-wide + * notifications. + */ +PPH_CALLBACK PhGetGeneralCallback( + _In_ PH_GENERAL_CALLBACK Callback + ) +{ + if (Callback >= GeneralCallbackMaximum) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + return &GeneralCallbacks[Callback]; +} + +/** + * Reserves unique GUI identifiers. + * + * \param Count The number of identifiers to reserve. + * + * \return The start of the reserved range. + * + * \remarks The identifiers reserved by this function are + * guaranteed to be unique throughout the program. + */ +ULONG PhPluginReserveIds( + _In_ ULONG Count + ) +{ + ULONG nextPluginId; + + nextPluginId = NextPluginId; + NextPluginId += Count; + + return nextPluginId; +} + +/** + * Adds a menu item to the program's main menu. This function is + * deprecated. Use \c GeneralCallbackMainMenuInitializing instead. + * + * \param Plugin A plugin instance structure. + * \param Location A handle to the parent menu, or one of the following: + * \li \c PH_MENU_ITEM_LOCATION_VIEW The "View" menu. + * \li \c PH_MENU_ITEM_LOCATION_TOOLS The "Tools" menu. + * \param InsertAfter The text of the menu item to insert the + * new menu item after. The search is a case-insensitive prefix search + * that ignores prefix characters (ampersands). + * \param Id An identifier for the menu item. This should be unique + * within the plugin. + * \param Text The text of the menu item. + * \param Context A user-defined value for the menu item. + * + * \return TRUE if the function succeeded, otherwise FALSE. + * + * \remarks The \ref PluginCallbackMenuItem callback is invoked when + * the menu item is chosen, and the \ref PH_PLUGIN_MENU_ITEM structure + * will contain the \a Id and \a Context values passed to this function. + */ +ULONG_PTR PhPluginAddMenuItem( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG_PTR Location, + _In_opt_ PWSTR InsertAfter, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ PVOID Context + ) +{ + PH_ADDMENUITEM addMenuItem; + + addMenuItem.Plugin = Plugin; + addMenuItem.InsertAfter = InsertAfter; + addMenuItem.Text = Text; + addMenuItem.Context = Context; + + if (Location < 0x1000) + { + addMenuItem.Location = (ULONG)Location; + } + else + { + return 0; + } + + addMenuItem.Flags = Id & PH_MENU_ITEM_VALID_FLAGS; + Id &= ~PH_MENU_ITEM_VALID_FLAGS; + addMenuItem.Id = Id; + + return ProcessHacker_AddMenuItem(PhMainWndHandle, &addMenuItem); +} + +/** + * Retrieves current system statistics. + */ +VOID PhPluginGetSystemStatistics( + _Out_ PPH_PLUGIN_SYSTEM_STATISTICS Statistics + ) +{ + Statistics->Performance = &PhPerfInformation; + + Statistics->NumberOfProcesses = PhTotalProcesses; + Statistics->NumberOfThreads = PhTotalThreads; + Statistics->NumberOfHandles = PhTotalHandles; + + Statistics->CpuKernelUsage = PhCpuKernelUsage; + Statistics->CpuUserUsage = PhCpuUserUsage; + + Statistics->IoReadDelta = PhIoReadDelta; + Statistics->IoWriteDelta = PhIoWriteDelta; + Statistics->IoOtherDelta = PhIoOtherDelta; + + Statistics->CommitPages = PhGetItemCircularBuffer_ULONG(&PhCommitHistory, 0); + Statistics->PhysicalPages = PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, 0); + + Statistics->MaxCpuProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&PhMaxCpuHistory, 0)); + Statistics->MaxIoProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&PhMaxIoHistory, 0)); + + Statistics->CpuKernelHistory = &PhCpuKernelHistory; + Statistics->CpuUserHistory = &PhCpuUserHistory; + Statistics->CpusKernelHistory = &PhCpusKernelHistory; + Statistics->CpusUserHistory = &PhCpusUserHistory; + Statistics->IoReadHistory = &PhIoReadHistory; + Statistics->IoWriteHistory = &PhIoWriteHistory; + Statistics->IoOtherHistory = &PhIoOtherHistory; + Statistics->CommitHistory = &PhCommitHistory; + Statistics->PhysicalHistory = &PhPhysicalHistory; + Statistics->MaxCpuHistory = &PhMaxCpuHistory; + Statistics->MaxIoHistory = &PhMaxIoHistory; +#ifdef PH_RECORD_MAX_USAGE + Statistics->MaxCpuUsageHistory = &PhMaxCpuUsageHistory; + Statistics->MaxIoReadOtherHistory = &PhMaxIoReadOtherHistory; + Statistics->MaxIoWriteHistory = &PhMaxIoWriteHistory; +#else + Statistics->MaxCpuUsageHistory = NULL; + Statistics->MaxIoReadOtherHistory = NULL; + Statistics->MaxIoWriteHistory = NULL; +#endif +} + +static VOID NTAPI PhpPluginEMenuItemDeleteFunction( + _In_ PPH_EMENU_ITEM Item + ) +{ + PPH_PLUGIN_MENU_ITEM pluginMenuItem; + + pluginMenuItem = Item->Context; + + if (pluginMenuItem->DeleteFunction) + pluginMenuItem->DeleteFunction(pluginMenuItem); + + PhFree(pluginMenuItem); +} + +/** + * Creates a menu item. + * + * \param Plugin A plugin instance structure. + * \param Flags A combination of flags. + * \param Id An identifier for the menu item. This should be unique + * within the plugin. + * \param Text The text of the menu item. + * \param Context A user-defined value for the menu item. + * + * \return A menu item object. This can then be inserted into another + * menu using PhInsertEMenuItem(). + * + * \remarks The \ref PluginCallbackMenuItem callback is invoked when + * the menu item is chosen, and the \ref PH_PLUGIN_MENU_ITEM structure + * will contain the \a Id and \a Context values passed to this function. + */ +PPH_EMENU_ITEM PhPluginCreateEMenuItem( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG Flags, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ PVOID Context + ) +{ + PPH_EMENU_ITEM item; + PPH_PLUGIN_MENU_ITEM pluginMenuItem; + + item = PhCreateEMenuItem(Flags, ID_PLUGIN_MENU_ITEM, Text, NULL, NULL); + + pluginMenuItem = PhAllocate(sizeof(PH_PLUGIN_MENU_ITEM)); + memset(pluginMenuItem, 0, sizeof(PH_PLUGIN_MENU_ITEM)); + pluginMenuItem->Plugin = Plugin; + pluginMenuItem->Id = Id; + pluginMenuItem->Context = Context; + + item->Context = pluginMenuItem; + item->DeleteFunction = PhpPluginEMenuItemDeleteFunction; + + return item; +} + +/** + * Adds a menu hook. + * + * \param MenuInfo The plugin menu information structure. + * \param Plugin A plugin instance structure. + * \param Context A user-defined value that is later accessible from the callback. + * + * \remarks The \ref PluginCallbackMenuHook callback is invoked when any menu item + * from the menu is chosen. + */ +BOOLEAN PhPluginAddMenuHook( + _Inout_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_ PPH_PLUGIN Plugin, + _In_opt_ PVOID Context + ) +{ + PPHP_PLUGIN_MENU_HOOK hook; + + if (MenuInfo->Flags & PH_PLUGIN_MENU_DISALLOW_HOOKS) + return FALSE; + + if (!MenuInfo->PluginHookList) + MenuInfo->PluginHookList = PH_AUTO(PhCreateList(2)); + + hook = PH_AUTO(PhCreateAlloc(sizeof(PHP_PLUGIN_MENU_HOOK))); + hook->Plugin = Plugin; + hook->Context = Context; + PhAddItemList(MenuInfo->PluginHookList, hook); + + return TRUE; +} + +/** + * Initializes a plugin menu information structure. + * + * \param MenuInfo The structure to initialize. + * \param Menu The menu being shown. + * \param OwnerWindow The window that owns the menu. + * \param Flags Additional flags. + * + * \remarks This function is reserved for internal use. + */ +VOID PhPluginInitializeMenuInfo( + _Out_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_opt_ PPH_EMENU Menu, + _In_ HWND OwnerWindow, + _In_ ULONG Flags + ) +{ + memset(MenuInfo, 0, sizeof(PH_PLUGIN_MENU_INFORMATION)); + MenuInfo->Menu = Menu; + MenuInfo->OwnerWindow = OwnerWindow; + MenuInfo->Flags = Flags; +} + +/** + * Triggers a plugin menu item. + * + * \param MenuInfo The plugin menu information structure. + * \param Item The menu item chosen by the user. + * + * \remarks This function is reserved for internal use. + */ +BOOLEAN PhPluginTriggerEMenuItem( + _In_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_ PPH_EMENU_ITEM Item + ) +{ + PPH_PLUGIN_MENU_ITEM pluginMenuItem; + ULONG i; + PPHP_PLUGIN_MENU_HOOK hook; + PH_PLUGIN_MENU_HOOK_INFORMATION menuHookInfo; + + if (MenuInfo->PluginHookList) + { + for (i = 0; i < MenuInfo->PluginHookList->Count; i++) + { + hook = MenuInfo->PluginHookList->Items[i]; + menuHookInfo.MenuInfo = MenuInfo; + menuHookInfo.SelectedItem = Item; + menuHookInfo.Context = hook->Context; + menuHookInfo.Handled = FALSE; + PhInvokeCallback(PhGetPluginCallback(hook->Plugin, PluginCallbackMenuHook), &menuHookInfo); + + if (menuHookInfo.Handled) + return TRUE; + } + } + + if (Item->Id != ID_PLUGIN_MENU_ITEM) + return FALSE; + + pluginMenuItem = Item->Context; + + pluginMenuItem->OwnerWindow = MenuInfo->OwnerWindow; + + PhInvokeCallback(PhGetPluginCallback(pluginMenuItem->Plugin, PluginCallbackMenuItem), pluginMenuItem); + + return TRUE; +} + +/** + * Adds a column to a tree new control. + * + * \param Plugin A plugin instance structure. + * \param CmData The CmData value from the \ref PH_PLUGIN_TREENEW_INFORMATION + * structure. + * \param Column The column properties. + * \param SubId An identifier for the column. This should be unique within the + * plugin. + * \param Context A user-defined value. + * \param SortFunction The sort function for the column. + */ +BOOLEAN PhPluginAddTreeNewColumn( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID CmData, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_opt_ PPH_PLUGIN_TREENEW_SORT_FUNCTION SortFunction + ) +{ + return !!PhCmCreateColumn( + CmData, + Column, + Plugin, + SubId, + Context, + SortFunction + ); +} + +/** + * Sets the object extension size and callbacks for an object type. + * + * \param Plugin A plugin instance structure. + * \param ObjectType The type of object for which the extension is being registered. + * \param ExtensionSize The size of the extension, in bytes. + * \param CreateCallback The object creation callback. + * \param DeleteCallback The object deletion callback. + */ +VOID PhPluginSetObjectExtension( + _In_ PPH_PLUGIN Plugin, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ ULONG ExtensionSize, + _In_opt_ PPH_EM_OBJECT_CALLBACK CreateCallback, + _In_opt_ PPH_EM_OBJECT_CALLBACK DeleteCallback + ) +{ + PhEmSetObjectExtension( + &Plugin->AppContext, + ObjectType, + ExtensionSize, + CreateCallback, + DeleteCallback + ); +} + +/** + * Gets the object extension for an object. + * + * \param Plugin A plugin instance structure. + * \param Object The object. + * \param ObjectType The type of object for which an extension has been registered. + */ +PVOID PhPluginGetObjectExtension( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType + ) +{ + return PhEmGetObjectExtension( + &Plugin->AppContext, + ObjectType, + Object + ); +} + +/** + * Creates a notification icon. + * + * \param Plugin A plugin instance structure. + * \param SubId An identifier for the column. This should be unique within the + * plugin. + * \param Context A user-defined value. + * \param Text A string describing the notification icon. + * \param Flags A combination of flags. + * \li \c PH_NF_ICON_UNAVAILABLE The notification icon is currently unavailable. + * \param RegistrationData A \ref PH_NF_ICON_REGISTRATION_DATA structure that + * contains registration information. + */ +struct _PH_NF_ICON *PhPluginRegisterIcon( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG SubId, + _In_opt_ PVOID Context, + _In_ PWSTR Text, + _In_ ULONG Flags, + _In_ struct _PH_NF_ICON_REGISTRATION_DATA *RegistrationData + ) +{ + return PhNfRegisterIcon( + Plugin, + SubId, + Context, + Text, + Flags, + RegistrationData->UpdateCallback, + RegistrationData->MessageCallback + ); +} + +/** + * Allows a plugin to receive all treenew messages, not just column-related ones. + * + * \param Plugin A plugin instance structure. + * \param CmData The CmData value from the \ref PH_PLUGIN_TREENEW_INFORMATION + * structure. + */ +VOID PhPluginEnableTreeNewNotify( + _In_ PPH_PLUGIN Plugin, + _In_ PVOID CmData + ) +{ + PhCmSetNotifyPlugin(CmData, Plugin); +} + +BOOLEAN PhPluginQueryPhSvc( + _Out_ PPH_PLUGIN_PHSVC_CLIENT Client + ) +{ + if (!PhSvcClServerProcessId) + return FALSE; + + Client->ServerProcessId = PhSvcClServerProcessId; + Client->FreeHeap = PhSvcpFreeHeap; + Client->CreateString = PhSvcpCreateString; + + return TRUE; +} + +NTSTATUS PhPluginCallPhSvc( + _In_ PPH_PLUGIN Plugin, + _In_ ULONG SubId, + _In_reads_bytes_opt_(InLength) PVOID InBuffer, + _In_ ULONG InLength, + _Out_writes_bytes_opt_(OutLength) PVOID OutBuffer, + _In_ ULONG OutLength + ) +{ + NTSTATUS status; + PPH_STRING apiId; + PH_FORMAT format[4]; + + PhInitFormatC(&format[0], '+'); + PhInitFormatSR(&format[1], Plugin->Name); + PhInitFormatC(&format[2], '+'); + PhInitFormatU(&format[3], SubId); + apiId = PhFormat(format, 4, 50); + + status = PhSvcCallPlugin(&apiId->sr, InBuffer, InLength, OutBuffer, OutLength); + PhDereferenceObject(apiId); + + return status; +} diff --git a/ProcessHacker/plugman.c b/ProcessHacker/plugman.c new file mode 100644 index 0000000..e065d74 --- /dev/null +++ b/ProcessHacker/plugman.c @@ -0,0 +1,443 @@ +/* + * Process Hacker - + * plugins + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +#define IS_PLUGIN_LOADED(Plugin) (!!(Plugin)->AppContext.AppName.Buffer) +#define STR_OR_DEFAULT(String, Default) ((String) ? (String) : (Default)) + +static HWND PluginsLv; +static PPH_PLUGIN SelectedPlugin; +static PPH_LIST DisabledPluginInstances; // fake PH_PLUGIN structures for disabled plugins +static PPH_HASHTABLE DisabledPluginLookup; // list of all disabled plugins (including fake structures) by PH_PLUGIN address + +INT_PTR CALLBACK PhpPluginsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowPluginsDialog( + _In_ HWND ParentWindowHandle + ) +{ + if (PhPluginsEnabled) + { + DialogBox( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PLUGINS), + ParentWindowHandle, + PhpPluginsDlgProc + ); + } + else + { + PhShowInformation(ParentWindowHandle, + L"Plugins are not enabled. To use plugins enable them in Options and restart Process Hacker."); + } +} + +PWSTR PhpGetPluginBaseName( + _In_ PPH_PLUGIN Plugin + ) +{ + if (Plugin->FileName) + { + PH_STRINGREF pathNamePart; + PH_STRINGREF baseNamePart; + + if (PhSplitStringRefAtLastChar(&Plugin->FileName->sr, '\\', &pathNamePart, &baseNamePart)) + return baseNamePart.Buffer; + else + return Plugin->FileName->Buffer; + } + else + { + // Fake disabled plugin. + return Plugin->Name.Buffer; + } +} + +PWSTR PhpGetPluginDisableButtonText( + _In_ PWSTR BaseName + ) +{ + PH_STRINGREF baseName; + + PhInitializeStringRefLongHint(&baseName, BaseName); + + if (PhIsPluginDisabled(&baseName)) + return L"Enable"; + else + return L"Disable"; +} + +VOID PhpRefreshPluginDetails( + _In_ HWND hwndDlg + ) +{ + PPH_STRING fileName; + PH_IMAGE_VERSION_INFO versionInfo; + + if (SelectedPlugin && SelectedPlugin->FileName) // if there's no FileName, then it's a fake disabled plugin instance + { + fileName = SelectedPlugin->FileName; + + SetDlgItemText(hwndDlg, IDC_NAME, SelectedPlugin->Information.DisplayName ? SelectedPlugin->Information.DisplayName : L"(unnamed)"); + SetDlgItemText(hwndDlg, IDC_INTERNALNAME, SelectedPlugin->Name.Buffer); + SetDlgItemText(hwndDlg, IDC_AUTHOR, SelectedPlugin->Information.Author); + SetDlgItemText(hwndDlg, IDC_FILENAME, fileName->Buffer); + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, SelectedPlugin->Information.Description); + SetDlgItemText(hwndDlg, IDC_URL, SelectedPlugin->Information.Url); + + if (PhInitializeImageVersionInfo(&versionInfo, fileName->Buffer)) + { + SetDlgItemText(hwndDlg, IDC_VERSION, PhGetStringOrDefault(versionInfo.FileVersion, L"Unknown")); + PhDeleteImageVersionInfo(&versionInfo); + } + else + { + SetDlgItemText(hwndDlg, IDC_VERSION, L"Unknown"); + } + + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENURL), SelectedPlugin->Information.Url ? SW_SHOW : SW_HIDE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DISABLE), TRUE); + SetDlgItemText(hwndDlg, IDC_DISABLE, PhpGetPluginDisableButtonText(PhpGetPluginBaseName(SelectedPlugin))); + EnableWindow(GetDlgItem(hwndDlg, IDC_OPTIONS), SelectedPlugin->Information.HasOptions); + } + else + { + SetDlgItemText(hwndDlg, IDC_NAME, L"N/A"); + SetDlgItemText(hwndDlg, IDC_VERSION, L"N/A"); + SetDlgItemText(hwndDlg, IDC_INTERNALNAME, L"N/A"); + SetDlgItemText(hwndDlg, IDC_AUTHOR, L"N/A"); + SetDlgItemText(hwndDlg, IDC_URL, L"N/A"); + SetDlgItemText(hwndDlg, IDC_FILENAME, L"N/A"); + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, L"N/A"); + + ShowWindow(GetDlgItem(hwndDlg, IDC_OPENURL), SW_HIDE); + + if (SelectedPlugin) + { + // This is a disabled plugin. + EnableWindow(GetDlgItem(hwndDlg, IDC_DISABLE), TRUE); + SetDlgItemText(hwndDlg, IDC_DISABLE, PhpGetPluginDisableButtonText(SelectedPlugin->Name.Buffer)); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DISABLE), FALSE); + SetDlgItemText(hwndDlg, IDC_DISABLE, L"Disable"); + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_OPTIONS), FALSE); + } +} + +BOOLEAN PhpIsPluginLoadedByBaseName( + _In_ PPH_STRINGREF BaseName + ) +{ + PPH_AVL_LINKS links; + + // Extremely inefficient code follows. + // TODO: Make this better. + + for (links = PhMinimumElementAvlTree(&PhPluginsByName); links; links = PhSuccessorElementAvlTree(links)) + { + PPH_PLUGIN plugin = CONTAINING_RECORD(links, PH_PLUGIN, Links); + PH_STRINGREF pluginBaseName; + + PhInitializeStringRefLongHint(&pluginBaseName, PhpGetPluginBaseName(plugin)); + + if (PhEqualStringRef(&pluginBaseName, BaseName, TRUE)) + return TRUE; + } + + return FALSE; +} + +PPH_PLUGIN PhpCreateDisabledPlugin( + _In_ PPH_STRINGREF BaseName + ) +{ + PPH_PLUGIN plugin; + + plugin = PhAllocate(sizeof(PH_PLUGIN)); + memset(plugin, 0, sizeof(PH_PLUGIN)); + + plugin->Name.Length = BaseName->Length; + plugin->Name.Buffer = PhAllocate(BaseName->Length + sizeof(WCHAR)); + memcpy(plugin->Name.Buffer, BaseName->Buffer, BaseName->Length); + plugin->Name.Buffer[BaseName->Length / 2] = 0; + + return plugin; +} + +VOID PhpFreeDisabledPlugin( + _In_ PPH_PLUGIN Plugin + ) +{ + PhFree(Plugin->Name.Buffer); + PhFree(Plugin); +} + +VOID PhpAddDisabledPlugins( + VOID + ) +{ + PPH_STRING disabled; + PH_STRINGREF remainingPart; + PH_STRINGREF part; + PPH_PLUGIN disabledPlugin; + PPH_STRING displayText; + INT lvItemIndex; + + disabled = PhGetStringSetting(L"DisabledPlugins"); + remainingPart = disabled->sr; + + while (remainingPart.Length != 0) + { + PhSplitStringRefAtChar(&remainingPart, '|', &part, &remainingPart); + + if (part.Length != 0) + { + if (!PhpIsPluginLoadedByBaseName(&part)) + { + disabledPlugin = PhpCreateDisabledPlugin(&part); + PhAddItemList(DisabledPluginInstances, disabledPlugin); + PhAddItemSimpleHashtable(DisabledPluginLookup, disabledPlugin, NULL); + + displayText = PhCreateString2(&part); + lvItemIndex = PhAddListViewItem(PluginsLv, MAXINT, displayText->Buffer, disabledPlugin); + PhDereferenceObject(displayText); + } + } + } + + PhDereferenceObject(disabled); +} + +VOID PhpUpdateDisabledPlugin( + _In_ HWND hwndDlg, + _In_ INT ItemIndex, + _In_ PPH_PLUGIN Plugin, + _In_ BOOLEAN NewDisabledState + ) +{ + if (NewDisabledState) + { + PhAddItemSimpleHashtable(DisabledPluginLookup, Plugin, NULL); + } + else + { + PhRemoveItemSimpleHashtable(DisabledPluginLookup, Plugin); + } + + if (!IS_PLUGIN_LOADED(Plugin)) + { + assert(!NewDisabledState); + ListView_DeleteItem(PluginsLv, ItemIndex); + } + + InvalidateRect(PluginsLv, NULL, TRUE); + + ShowWindow(GetDlgItem(hwndDlg, IDC_INSTRUCTION), SW_SHOW); +} + +static COLORREF PhpPluginColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN plugin = Param; + + if (PhFindItemSimpleHashtable(DisabledPluginLookup, plugin)) + return RGB(0x77, 0x77, 0x77); // fake disabled plugin + + return GetSysColor(COLOR_WINDOW); +} + +INT_PTR CALLBACK PhpPluginsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_AVL_LINKS links; + + PhCenterWindow(hwndDlg, PhMainWndHandle); + + PluginsLv = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(PluginsLv, FALSE, TRUE); + PhSetControlTheme(PluginsLv, L"explorer"); + PhAddListViewColumn(PluginsLv, 0, 0, 0, LVCFMT_LEFT, 280, L"Name"); + PhAddListViewColumn(PluginsLv, 1, 1, 1, LVCFMT_LEFT, 100, L"Author"); + PhSetExtendedListView(PluginsLv); + ExtendedListView_SetItemColorFunction(PluginsLv, PhpPluginColorFunction); + + DisabledPluginLookup = PhCreateSimpleHashtable(10); + + for (links = PhMinimumElementAvlTree(&PhPluginsByName); links; links = PhSuccessorElementAvlTree(links)) + { + PPH_PLUGIN plugin = CONTAINING_RECORD(links, PH_PLUGIN, Links); + INT lvItemIndex; + PH_STRINGREF baseNameSr; + + lvItemIndex = PhAddListViewItem(PluginsLv, MAXINT, plugin->Information.DisplayName ? plugin->Information.DisplayName : plugin->Name.Buffer, plugin); + + if (plugin->Information.Author) + PhSetListViewSubItem(PluginsLv, lvItemIndex, 1, plugin->Information.Author); + + PhInitializeStringRefLongHint(&baseNameSr, PhpGetPluginBaseName(plugin)); + + if (PhIsPluginDisabled(&baseNameSr)) + PhAddItemSimpleHashtable(DisabledPluginLookup, plugin, NULL); + } + + DisabledPluginInstances = PhCreateList(10); + PhpAddDisabledPlugins(); + + ExtendedListView_SortItems(PluginsLv); + + SelectedPlugin = NULL; + PhpRefreshPluginDetails(hwndDlg); + } + break; + case WM_DESTROY: + { + ULONG i; + + for (i = 0; i < DisabledPluginInstances->Count; i++) + PhpFreeDisabledPlugin(DisabledPluginInstances->Items[i]); + + PhDereferenceObject(DisabledPluginInstances); + PhDereferenceObject(DisabledPluginLookup); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_DISABLE: + { + if (SelectedPlugin) + { + PWSTR baseName; + PH_STRINGREF baseNameRef; + BOOLEAN newDisabledState; + + baseName = PhpGetPluginBaseName(SelectedPlugin); + PhInitializeStringRef(&baseNameRef, baseName); + newDisabledState = !PhIsPluginDisabled(&baseNameRef); + PhSetPluginDisabled(&baseNameRef, newDisabledState); + PhpUpdateDisabledPlugin(hwndDlg, PhFindListViewItemByFlags(PluginsLv, -1, LVNI_SELECTED), SelectedPlugin, newDisabledState); + + SetDlgItemText(hwndDlg, IDC_DISABLE, PhpGetPluginDisableButtonText(baseName)); + } + } + break; + case IDC_OPTIONS: + { + if (SelectedPlugin && IS_PLUGIN_LOADED(SelectedPlugin)) + { + PhInvokeCallback(PhGetPluginCallback(SelectedPlugin, PluginCallbackShowOptions), hwndDlg); + } + } + break; + case IDC_CLEANUP: + { + if (PhShowMessage(hwndDlg, MB_ICONQUESTION | MB_YESNO, + L"Do you want to clean up unused plugin settings?") == IDYES) + { + PhClearIgnoredSettings(); + } + } + break; + case IDC_OPENURL: + { + NOTHING; + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == PluginsLv) + { + if (ListView_GetSelectedCount(PluginsLv) == 1) + SelectedPlugin = PhGetSelectedListViewItemParam(PluginsLv); + else + SelectedPlugin = NULL; + + PhpRefreshPluginDetails(hwndDlg); + } + } + break; + case NM_CLICK: + { + if (header->hwndFrom == GetDlgItem(hwndDlg, IDC_OPENURL)) + { + if (SelectedPlugin && IS_PLUGIN_LOADED(SelectedPlugin)) + PhShellExecute(hwndDlg, SelectedPlugin->Information.Url, NULL); + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == PluginsLv) + { + if (SelectedPlugin && IS_PLUGIN_LOADED(SelectedPlugin)) + { + PhInvokeCallback(PhGetPluginCallback(SelectedPlugin, PluginCallbackShowOptions), hwndDlg); + } + } + } + break; + } + } + break; + } + + REFLECT_MESSAGE_DLG(hwndDlg, PluginsLv, uMsg, wParam, lParam); + + return FALSE; +} diff --git a/ProcessHacker/procgrp.c b/ProcessHacker/procgrp.c new file mode 100644 index 0000000..23a06bb --- /dev/null +++ b/ProcessHacker/procgrp.c @@ -0,0 +1,342 @@ +/* + * Process Hacker - + * process grouping + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +typedef struct _PHP_PROCESS_DATA +{ + PPH_PROCESS_NODE Process; + LIST_ENTRY ListEntry; + HWND WindowHandle; +} PHP_PROCESS_DATA, *PPHP_PROCESS_DATA; + +PPH_LIST PhpCreateProcessDataList( + _In_ PPH_LIST Processes + ) +{ + PPH_LIST processDataList; + ULONG i; + + processDataList = PhCreateList(Processes->Count); + + for (i = 0; i < Processes->Count; i++) + { + PPH_PROCESS_NODE process = Processes->Items[i]; + PPHP_PROCESS_DATA processData; + + if (PH_IS_FAKE_PROCESS_ID(process->ProcessId) || process->ProcessId == SYSTEM_IDLE_PROCESS_ID) + continue; + + processData = PhAllocate(sizeof(PHP_PROCESS_DATA)); + memset(processData, 0, sizeof(PHP_PROCESS_DATA)); + processData->Process = process; + PhAddItemList(processDataList, processData); + } + + return processDataList; +} + +VOID PhpDestroyProcessDataList( + _In_ PPH_LIST List + ) +{ + ULONG i; + + for (i = 0; i < List->Count; i++) + { + PPHP_PROCESS_DATA processData = List->Items[i]; + PhFree(processData); + } + + PhDereferenceObject(List); +} + +VOID PhpProcessDataListToLinkedList( + _In_ PPH_LIST List, + _Out_ PLIST_ENTRY ListHead + ) +{ + ULONG i; + + InitializeListHead(ListHead); + + for (i = 0; i < List->Count; i++) + { + PPHP_PROCESS_DATA processData = List->Items[i]; + InsertTailList(ListHead, &processData->ListEntry); + } +} + +VOID PhpProcessDataListToHashtable( + _In_ PPH_LIST List, + _Out_ PPH_HASHTABLE *Hashtable + ) +{ + PPH_HASHTABLE hashtable; + ULONG i; + + hashtable = PhCreateSimpleHashtable(List->Count); + + for (i = 0; i < List->Count; i++) + { + PPHP_PROCESS_DATA processData = List->Items[i]; + PhAddItemSimpleHashtable(hashtable, processData->Process->ProcessId, processData); + } + + *Hashtable = hashtable; +} + +typedef struct _QUERY_WINDOWS_CONTEXT +{ + PPH_HASHTABLE ProcessDataHashtable; +} QUERY_WINDOWS_CONTEXT, *PQUERY_WINDOWS_CONTEXT; + +BOOL CALLBACK PhpQueryWindowsEnumWindowsProc( + _In_ HWND hwnd, + _In_ LPARAM lParam + ) +{ + PQUERY_WINDOWS_CONTEXT context = (PQUERY_WINDOWS_CONTEXT)lParam; + ULONG processId; + PPHP_PROCESS_DATA processData; + HWND parentWindow; + + if (!IsWindowVisible(hwnd)) + return TRUE; + + GetWindowThreadProcessId(hwnd, &processId); + processData = PhFindItemSimpleHashtable2(context->ProcessDataHashtable, UlongToHandle(processId)); + + if (!processData || processData->WindowHandle) + return TRUE; + + if (!((parentWindow = GetParent(hwnd)) && IsWindowVisible(parentWindow)) && // Skip windows with a visible parent + PhGetWindowTextEx(hwnd, PH_GET_WINDOW_TEXT_INTERNAL | PH_GET_WINDOW_TEXT_LENGTH_ONLY, NULL) != 0) // Skip windows with no title + { + processData->WindowHandle = hwnd; + } + + return TRUE; +} + +PPH_STRING PhpGetRelevantFileName( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Flags + ) +{ + if (Flags & PH_GROUP_PROCESSES_FILE_PATH) + return ProcessItem->FileName; + else + return ProcessItem->ProcessName; +} + +BOOLEAN PhpEqualFileNameAndUserName( + _In_ PPH_STRING FileName, + _In_ PPH_STRING UserName, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Flags + ) +{ + PPH_STRING otherFileName; + PPH_STRING otherUserName; + + otherFileName = PhpGetRelevantFileName(ProcessItem, Flags); + otherUserName = ProcessItem->UserName; + + return + otherFileName && PhEqualString(otherFileName, FileName, TRUE) && + otherUserName && PhEqualString(otherUserName, UserName, TRUE); +} + +PPHP_PROCESS_DATA PhpFindGroupRoot( + _In_ PPHP_PROCESS_DATA ProcessData, + _In_ PPH_HASHTABLE ProcessDataHashtable, + _In_ ULONG Flags + ) +{ + PPH_PROCESS_NODE root; + PPHP_PROCESS_DATA rootProcessData; + PPH_PROCESS_NODE parent; + PPHP_PROCESS_DATA processData; + PPH_STRING fileName; + PPH_STRING userName; + + root = ProcessData->Process; + rootProcessData = ProcessData; + fileName = PhpGetRelevantFileName(ProcessData->Process->ProcessItem, Flags); + userName = ProcessData->Process->ProcessItem->UserName; + + if (ProcessData->WindowHandle) + return rootProcessData; + + while (parent = root->Parent) + { + if ((processData = PhFindItemSimpleHashtable2(ProcessDataHashtable, parent->ProcessId)) && + PhpEqualFileNameAndUserName(fileName, userName, parent->ProcessItem, Flags)) + { + root = parent; + rootProcessData = processData; + + if (processData->WindowHandle) + break; + } + else + { + break; + } + } + + return rootProcessData; +} + +VOID PhpAddGroupMember( + _In_ PPHP_PROCESS_DATA ProcessData, + _Inout_ PPH_LIST List + ) +{ + PhReferenceObject(ProcessData->Process->ProcessItem); + PhAddItemList(List, ProcessData->Process->ProcessItem); + RemoveEntryList(&ProcessData->ListEntry); +} + +VOID PhpAddGroupMembersFromRoot( + _In_ PPHP_PROCESS_DATA ProcessData, + _Inout_ PPH_LIST List, + _In_ PPH_HASHTABLE ProcessDataHashtable, + _In_ ULONG Flags + ) +{ + PPH_STRING fileName; + PPH_STRING userName; + ULONG i; + + PhpAddGroupMember(ProcessData, List); + fileName = PhpGetRelevantFileName(ProcessData->Process->ProcessItem, Flags); + userName = ProcessData->Process->ProcessItem->UserName; + + for (i = 0; i < ProcessData->Process->Children->Count; i++) + { + PPH_PROCESS_NODE node = ProcessData->Process->Children->Items[i]; + PPHP_PROCESS_DATA processData; + + if ((processData = PhFindItemSimpleHashtable2(ProcessDataHashtable, node->ProcessId)) && + PhpEqualFileNameAndUserName(fileName, userName, node->ProcessItem, Flags) && + node->ProcessItem->UserName && PhEqualString(node->ProcessItem->UserName, userName, TRUE) && + !processData->WindowHandle) + { + PhpAddGroupMembersFromRoot(processData, List, ProcessDataHashtable, Flags); + } + } +} + +PPH_LIST PhCreateProcessGroupList( + _In_opt_ PPH_SORT_LIST_FUNCTION SortListFunction, + _In_opt_ PVOID Context, + _In_ ULONG MaximumGroups, + _In_ ULONG Flags + ) +{ + PPH_LIST processList; + PPH_LIST processDataList; + LIST_ENTRY processDataListHead; // We will be removing things from this list as we group processes together + PPH_HASHTABLE processDataHashtable; // Process ID to process data hashtable + QUERY_WINDOWS_CONTEXT queryWindowsContext; + PPH_LIST processGroupList; + + // We group together processes that share a common ancestor and have the same file name, where + // the ancestor must have a visible window and all other processes in the group do not have a + // visible window. All processes in the group must have the same user name. All ancestors up to + // the lowest common ancestor must have the same file name and user name. + // + // The current algorithm is greedy and may not detect groups that have many processes, each with + // a small usage amount. + + processList = PhDuplicateProcessNodeList(); + + if (SortListFunction) + SortListFunction(processList, Context); + + processDataList = PhpCreateProcessDataList(processList); + PhDereferenceObject(processList); + PhpProcessDataListToLinkedList(processDataList, &processDataListHead); + PhpProcessDataListToHashtable(processDataList, &processDataHashtable); + + queryWindowsContext.ProcessDataHashtable = processDataHashtable; + PhEnumChildWindows(NULL, 0x800, PhpQueryWindowsEnumWindowsProc, (LPARAM)&queryWindowsContext); + + processGroupList = PhCreateList(10); + + while (processDataListHead.Flink != &processDataListHead && processGroupList->Count < MaximumGroups) + { + PPHP_PROCESS_DATA processData = CONTAINING_RECORD(processDataListHead.Flink, PHP_PROCESS_DATA, ListEntry); + PPH_PROCESS_GROUP processGroup; + PPH_STRING fileName; + PPH_STRING userName; + + processGroup = PhAllocate(sizeof(PH_PROCESS_GROUP)); + processGroup->Processes = PhCreateList(4); + fileName = PhpGetRelevantFileName(processData->Process->ProcessItem, Flags); + userName = processData->Process->ProcessItem->UserName; + + if (!fileName || !userName || (Flags & PH_GROUP_PROCESSES_DONT_GROUP)) + { + processGroup->Representative = processData->Process->ProcessItem; + PhpAddGroupMember(processData, processGroup->Processes); + } + else + { + processData = PhpFindGroupRoot(processData, processDataHashtable, Flags); + processGroup->Representative = processData->Process->ProcessItem; + PhpAddGroupMembersFromRoot(processData, processGroup->Processes, processDataHashtable, Flags); + } + + processGroup->WindowHandle = processData->WindowHandle; + + PhAddItemList(processGroupList, processGroup); + } + + PhDereferenceObject(processDataHashtable); + PhpDestroyProcessDataList(processDataList); + + return processGroupList; +} + +VOID PhFreeProcessGroupList( + _In_ PPH_LIST List + ) +{ + ULONG i; + + for (i = 0; i < List->Count; i++) + { + PPH_PROCESS_GROUP processGroup = List->Items[i]; + + PhDereferenceObjects(processGroup->Processes->Items, processGroup->Processes->Count); + PhDereferenceObject(processGroup->Processes); + PhFree(processGroup); + } + + PhDereferenceObject(List); +} diff --git a/ProcessHacker/procmtgn.c b/ProcessHacker/procmtgn.c new file mode 100644 index 0000000..9116619 --- /dev/null +++ b/ProcessHacker/procmtgn.c @@ -0,0 +1,361 @@ +/* + * Process Hacker - + * process mitigation information + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +NTSTATUS PhpCopyProcessMitigationPolicy( + _Inout_ PNTSTATUS Status, + _In_ HANDLE ProcessHandle, + _In_ PROCESS_MITIGATION_POLICY Policy, + _In_ SIZE_T Offset, + _In_ SIZE_T Size, + _Out_writes_bytes_(Size) PVOID Destination + ) +{ + NTSTATUS status; + PROCESS_MITIGATION_POLICY_INFORMATION policyInfo; + + policyInfo.Policy = Policy; + status = NtQueryInformationProcess( + ProcessHandle, + ProcessMitigationPolicy, + &policyInfo, + sizeof(PROCESS_MITIGATION_POLICY_INFORMATION), + NULL + ); + + if (!NT_SUCCESS(status)) + { + if (*Status == STATUS_NONE_MAPPED) + *Status = status; + return status; + } + + memcpy(Destination, (PCHAR)&policyInfo + Offset, Size); + *Status = STATUS_SUCCESS; + + return status; +} + +NTSTATUS PhGetProcessMitigationPolicy( + _In_ HANDLE ProcessHandle, + _Out_ PPH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION Information + ) +{ + NTSTATUS status = STATUS_NONE_MAPPED; + NTSTATUS subStatus; +#ifdef _WIN64 + BOOLEAN isWow64; +#endif + ULONG depStatus; + + memset(Information, 0, sizeof(PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION)); + +#ifdef _WIN64 + if (NT_SUCCESS(subStatus = PhGetProcessIsWow64(ProcessHandle, &isWow64)) && !isWow64) + { + depStatus = PH_PROCESS_DEP_ENABLED | PH_PROCESS_DEP_PERMANENT; + } + else + { +#endif + subStatus = PhGetProcessDepStatus(ProcessHandle, &depStatus); +#ifdef _WIN64 + } +#endif + + if (NT_SUCCESS(subStatus)) + { + status = STATUS_SUCCESS; + Information->DEPPolicy.Enable = !!(depStatus & PH_PROCESS_DEP_ENABLED); + Information->DEPPolicy.DisableAtlThunkEmulation = !!(depStatus & PH_PROCESS_DEP_ATL_THUNK_EMULATION_DISABLED); + Information->DEPPolicy.Permanent = !!(depStatus & PH_PROCESS_DEP_PERMANENT); + Information->Pointers[ProcessDEPPolicy] = &Information->DEPPolicy; + } + else if (status == STATUS_NONE_MAPPED) + { + status = subStatus; + } + +#define COPY_PROCESS_MITIGATION_POLICY(PolicyName, StructName) \ + if (NT_SUCCESS(PhpCopyProcessMitigationPolicy(&status, ProcessHandle, Process##PolicyName##Policy, \ + FIELD_OFFSET(PROCESS_MITIGATION_POLICY_INFORMATION, PolicyName##Policy), \ + sizeof(StructName), \ + &Information->PolicyName##Policy))) \ + { \ + Information->Pointers[Process##PolicyName##Policy] = &Information->PolicyName##Policy; \ + } + + COPY_PROCESS_MITIGATION_POLICY(ASLR, PROCESS_MITIGATION_ASLR_POLICY); + COPY_PROCESS_MITIGATION_POLICY(DynamicCode, PROCESS_MITIGATION_DYNAMIC_CODE_POLICY); + COPY_PROCESS_MITIGATION_POLICY(StrictHandleCheck, PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY); + COPY_PROCESS_MITIGATION_POLICY(SystemCallDisable, PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY); + COPY_PROCESS_MITIGATION_POLICY(ExtensionPointDisable, PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY); + COPY_PROCESS_MITIGATION_POLICY(ControlFlowGuard, PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY); + COPY_PROCESS_MITIGATION_POLICY(Signature, PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY); + COPY_PROCESS_MITIGATION_POLICY(FontDisable, PROCESS_MITIGATION_FONT_DISABLE_POLICY); + COPY_PROCESS_MITIGATION_POLICY(ImageLoad, PROCESS_MITIGATION_IMAGE_LOAD_POLICY); + + return status; +} + +BOOLEAN PhDescribeProcessMitigationPolicy( + _In_ PROCESS_MITIGATION_POLICY Policy, + _In_ PVOID Data, + _Out_opt_ PPH_STRING *ShortDescription, + _Out_opt_ PPH_STRING *LongDescription + ) +{ + BOOLEAN result = FALSE; + PH_STRING_BUILDER sb; + + switch (Policy) + { + case ProcessDEPPolicy: + { + PPROCESS_MITIGATION_DEP_POLICY data = Data; + + if (data->Enable) + { + if (ShortDescription) + { + PhInitializeStringBuilder(&sb, 20); + PhAppendStringBuilder2(&sb, L"DEP"); + if (data->Permanent) PhAppendStringBuilder2(&sb, L" (permanent)"); + *ShortDescription = PhFinalStringBuilderString(&sb); + } + + if (LongDescription) + { + PhInitializeStringBuilder(&sb, 50); + PhAppendFormatStringBuilder(&sb, L"Data Execution Prevention (DEP) is%s enabled for this process.\r\n", data->Permanent ? L" permanently" : L""); + if (data->DisableAtlThunkEmulation) PhAppendStringBuilder2(&sb, L"ATL thunk emulation is disabled.\r\n"); + *LongDescription = PhFinalStringBuilderString(&sb); + } + + result = TRUE; + } + } + break; + case ProcessASLRPolicy: + { + PPROCESS_MITIGATION_ASLR_POLICY data = Data; + + if (data->EnableBottomUpRandomization || data->EnableForceRelocateImages || data->EnableHighEntropy) + { + if (ShortDescription) + { + PhInitializeStringBuilder(&sb, 20); + PhAppendStringBuilder2(&sb, L"ASLR"); + + if (data->EnableHighEntropy || data->EnableForceRelocateImages) + { + PhAppendStringBuilder2(&sb, L" ("); + if (data->EnableHighEntropy) PhAppendStringBuilder2(&sb, L"high entropy, "); + if (data->EnableForceRelocateImages) PhAppendStringBuilder2(&sb, L"force relocate, "); + if (PhEndsWithStringRef2(&sb.String->sr, L", ", FALSE)) PhRemoveEndStringBuilder(&sb, 2); + PhAppendCharStringBuilder(&sb, ')'); + } + + *ShortDescription = PhFinalStringBuilderString(&sb); + } + + if (LongDescription) + { + PhInitializeStringBuilder(&sb, 100); + PhAppendStringBuilder2(&sb, L"Address Space Layout Randomization is enabled for this process.\r\n"); + if (data->EnableHighEntropy) PhAppendStringBuilder2(&sb, L"High entropy randomization is enabled.\r\n"); + if (data->EnableForceRelocateImages) PhAppendStringBuilder2(&sb, L"All images are being forcibly relocated (regardless of whether they support ASLR).\r\n"); + if (data->DisallowStrippedImages) PhAppendStringBuilder2(&sb, L"Images with stripped relocation data are disallowed.\r\n"); + *LongDescription = PhFinalStringBuilderString(&sb); + } + + result = TRUE; + } + } + break; + case ProcessDynamicCodePolicy: + { + PPROCESS_MITIGATION_DYNAMIC_CODE_POLICY data = Data; + + if (data->ProhibitDynamicCode) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"Dynamic code prohibited"); + + if (LongDescription) + *LongDescription = PhCreateString(L"Dynamically loaded code is not allowed to execute.\r\n"); + + result = TRUE; + } + } + break; + case ProcessStrictHandleCheckPolicy: + { + PPROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY data = Data; + + if (data->RaiseExceptionOnInvalidHandleReference) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"Strict handle checks"); + + if (LongDescription) + *LongDescription = PhCreateString(L"An exception is raised when an invalid handle is used by the process.\r\n"); + + result = TRUE; + } + } + break; + case ProcessSystemCallDisablePolicy: + { + PPROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY data = Data; + + if (data->DisallowWin32kSystemCalls) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"Win32k system calls disabled"); + + if (LongDescription) + *LongDescription = PhCreateString(L"Win32k (GDI/USER) system calls are not allowed.\r\n"); + + result = TRUE; + } + } + break; + case ProcessExtensionPointDisablePolicy: + { + PPROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY data = Data; + + if (data->DisableExtensionPoints) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"Extension points disabled"); + + if (LongDescription) + *LongDescription = PhCreateString(L"Legacy extension point DLLs cannot be loaded into the process.\r\n"); + + result = TRUE; + } + } + break; + case ProcessControlFlowGuardPolicy: + { + PPROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY data = Data; + + if (data->EnableControlFlowGuard) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"CF Guard"); + + if (LongDescription) + *LongDescription = PhCreateString(L"Control Flow Guard (CFG) is enabled for the process.\r\n"); + + result = TRUE; + } + } + break; + case ProcessSignaturePolicy: + { + PPROCESS_MITIGATION_BINARY_SIGNATURE_POLICY data = Data; + + if (data->MicrosoftSignedOnly || data->StoreSignedOnly) + { + if (ShortDescription) + { + PhInitializeStringBuilder(&sb, 50); + PhAppendStringBuilder2(&sb, L"Signatures restricted ("); + if (data->MicrosoftSignedOnly) PhAppendStringBuilder2(&sb, L"Microsoft only, "); + if (data->StoreSignedOnly) PhAppendStringBuilder2(&sb, L"Store only, "); + if (PhEndsWithStringRef2(&sb.String->sr, L", ", FALSE)) PhRemoveEndStringBuilder(&sb, 2); + PhAppendCharStringBuilder(&sb, ')'); + + *ShortDescription = PhFinalStringBuilderString(&sb); + } + + if (LongDescription) + { + PhInitializeStringBuilder(&sb, 100); + PhAppendStringBuilder2(&sb, L"Image signature restrictions are enabled for this process.\r\n"); + if (data->MicrosoftSignedOnly) PhAppendStringBuilder2(&sb, L"Only Microsoft signatures are allowed.\r\n"); + if (data->StoreSignedOnly) PhAppendStringBuilder2(&sb, L"Only Windows Store signatures are allowed.\r\n"); + if (data->MitigationOptIn) PhAppendStringBuilder2(&sb, L"This is an opt-in restriction.\r\n"); + *LongDescription = PhFinalStringBuilderString(&sb); + } + + result = TRUE; + } + } + break; + case ProcessFontDisablePolicy: + { + PPROCESS_MITIGATION_FONT_DISABLE_POLICY data = Data; + + if (data->DisableNonSystemFonts) + { + if (ShortDescription) + *ShortDescription = PhCreateString(L"Non-system fonts disabled"); + + if (LongDescription) + *LongDescription = PhCreateString(L"Non-system fonts cannot be used in this process.\r\n"); + + result = TRUE; + } + } + break; + case ProcessImageLoadPolicy: + { + PPROCESS_MITIGATION_IMAGE_LOAD_POLICY data = Data; + + if (data->NoRemoteImages || data->NoLowMandatoryLabelImages) + { + if (ShortDescription) + { + PhInitializeStringBuilder(&sb, 50); + PhAppendStringBuilder2(&sb, L"Images restricted ("); + if (data->NoRemoteImages) PhAppendStringBuilder2(&sb, L"remote images, "); + if (data->NoLowMandatoryLabelImages) PhAppendStringBuilder2(&sb, L"low mandatory label images, "); + if (PhEndsWithStringRef2(&sb.String->sr, L", ", FALSE)) PhRemoveEndStringBuilder(&sb, 2); + PhAppendCharStringBuilder(&sb, ')'); + + *ShortDescription = PhFinalStringBuilderString(&sb); + } + + if (LongDescription) + { + PhInitializeStringBuilder(&sb, 50); + if (data->NoRemoteImages) PhAppendStringBuilder2(&sb, L"Remotely located images cannot be loaded into the process.\r\n"); + if (data->NoLowMandatoryLabelImages) PhAppendStringBuilder2(&sb, L"Images with a Low mandatory label cannot be loaded into the process.\r\n"); + + *LongDescription = PhFinalStringBuilderString(&sb); + } + + result = TRUE; + } + } + break; + default: + result = FALSE; + } + + return result; +} diff --git a/ProcessHacker/procprp.c b/ProcessHacker/procprp.c new file mode 100644 index 0000000..d4383e6 --- /dev/null +++ b/ProcessHacker/procprp.c @@ -0,0 +1,722 @@ +/* + * Process Hacker - + * Process properties + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +PPH_OBJECT_TYPE PhpProcessPropContextType; +PPH_OBJECT_TYPE PhpProcessPropPageContextType; +PH_STRINGREF PhpLoadingText = PH_STRINGREF_INIT(L"Loading..."); + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +BOOLEAN PhProcessPropInitialization( + VOID + ) +{ + PhpProcessPropContextType = PhCreateObjectType(L"ProcessPropContext", 0, PhpProcessPropContextDeleteProcedure); + PhpProcessPropPageContextType = PhCreateObjectType(L"ProcessPropPageContext", 0, PhpProcessPropPageContextDeleteProcedure); + + return TRUE; +} + +PPH_PROCESS_PROPCONTEXT PhCreateProcessPropContext( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PPH_PROCESS_PROPCONTEXT propContext; + PROPSHEETHEADER propSheetHeader; + + propContext = PhCreateObject(sizeof(PH_PROCESS_PROPCONTEXT), PhpProcessPropContextType); + memset(propContext, 0, sizeof(PH_PROCESS_PROPCONTEXT)); + + propContext->PropSheetPages = PhAllocate(sizeof(HPROPSHEETPAGE) * PH_PROCESS_PROPCONTEXT_MAXPAGES); + + if (!PH_IS_FAKE_PROCESS_ID(ProcessItem->ProcessId)) + { + propContext->Title = PhFormatString( + L"%s (%u)", + ProcessItem->ProcessName->Buffer, + HandleToUlong(ProcessItem->ProcessId) + ); + } + else + { + PhSetReference(&propContext->Title, ProcessItem->ProcessName); + } + + memset(&propSheetHeader, 0, sizeof(PROPSHEETHEADER)); + propSheetHeader.dwSize = sizeof(PROPSHEETHEADER); + propSheetHeader.dwFlags = + PSH_MODELESS | + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE | + PSH_USECALLBACK | + PSH_USEHICON; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.hIcon = ProcessItem->SmallIcon; + propSheetHeader.pszCaption = propContext->Title->Buffer; + propSheetHeader.pfnCallback = PhpPropSheetProc; + + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = propContext->PropSheetPages; + + if (PhCsForceNoParent) + propSheetHeader.hwndParent = NULL; + + memcpy(&propContext->PropSheetHeader, &propSheetHeader, sizeof(PROPSHEETHEADER)); + + PhSetReference(&propContext->ProcessItem, ProcessItem); + PhInitializeEvent(&propContext->CreatedEvent); + + return propContext; +} + +VOID NTAPI PhpProcessPropContextDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_PROCESS_PROPCONTEXT propContext = (PPH_PROCESS_PROPCONTEXT)Object; + + PhFree(propContext->PropSheetPages); + PhDereferenceObject(propContext->Title); + PhDereferenceObject(propContext->ProcessItem); +} + +VOID PhRefreshProcessPropContext( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext + ) +{ + PropContext->PropSheetHeader.hIcon = PropContext->ProcessItem->SmallIcon; +} + +VOID PhSetSelectThreadIdProcessPropContext( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ HANDLE ThreadId + ) +{ + PropContext->SelectThreadId = ThreadId; +} + +INT CALLBACK PhpPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ) +{ +#define PROPSHEET_ADD_STYLE (WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME); + + switch (uMsg) + { + case PSCB_PRECREATE: + { + if (lParam) + { + if (((DLGTEMPLATEEX *)lParam)->signature == 0xffff) + { + ((DLGTEMPLATEEX *)lParam)->style |= PROPSHEET_ADD_STYLE; + } + else + { + ((DLGTEMPLATE *)lParam)->style |= PROPSHEET_ADD_STYLE; + } + } + } + break; + case PSCB_INITIALIZED: + { + PPH_PROCESS_PROPSHEETCONTEXT propSheetContext; + + propSheetContext = PhAllocate(sizeof(PH_PROCESS_PROPSHEETCONTEXT)); + memset(propSheetContext, 0, sizeof(PH_PROCESS_PROPSHEETCONTEXT)); + + PhInitializeLayoutManager(&propSheetContext->LayoutManager, hwndDlg); + + propSheetContext->OldWndProc = (WNDPROC)GetWindowLongPtr(hwndDlg, GWLP_WNDPROC); + SetWindowLongPtr(hwndDlg, GWLP_WNDPROC, (LONG_PTR)PhpPropSheetWndProc); + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)propSheetContext); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 290; + rect.bottom = 320; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + } + break; + } + + return 0; +} + +PPH_PROCESS_PROPSHEETCONTEXT PhpGetPropSheetContext( + _In_ HWND hwnd + ) +{ + return (PPH_PROCESS_PROPSHEETCONTEXT)GetProp(hwnd, PhMakeContextAtom()); +} + +LRESULT CALLBACK PhpPropSheetWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_PROCESS_PROPSHEETCONTEXT propSheetContext = PhpGetPropSheetContext(hwnd); + WNDPROC oldWndProc = propSheetContext->OldWndProc; + + switch (uMsg) + { + case WM_DESTROY: + { + HWND tabControl; + TCITEM tabItem; + WCHAR text[128]; + + // Save the window position and size. + + PhSaveWindowPlacementToSetting(L"ProcPropPosition", L"ProcPropSize", hwnd); + + // Save the selected tab. + + tabControl = PropSheet_GetTabControl(hwnd); + + tabItem.mask = TCIF_TEXT; + tabItem.pszText = text; + tabItem.cchTextMax = sizeof(text) / 2 - 1; + + if (TabCtrl_GetItem(tabControl, TabCtrl_GetCurSel(tabControl), &tabItem)) + { + PhSetStringSetting(L"ProcPropPage", text); + } + } + break; + case WM_NCDESTROY: + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + PhDeleteLayoutManager(&propSheetContext->LayoutManager); + + RemoveProp(hwnd, PhMakeContextAtom()); + PhFree(propSheetContext); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDOK: + // Prevent the OK button from working (even though + // it's already hidden). This prevents the Enter + // key from closing the dialog box. + return 0; + } + } + break; + case WM_SIZE: + { + if (!IsIconic(hwnd)) + { + PhLayoutManagerLayout(&propSheetContext->LayoutManager); + } + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +BOOLEAN PhpInitializePropSheetLayoutStage1( + _In_ HWND hwnd + ) +{ + PPH_PROCESS_PROPSHEETCONTEXT propSheetContext = PhpGetPropSheetContext(hwnd); + + if (!propSheetContext->LayoutInitialized) + { + HWND tabControlHandle; + PPH_LAYOUT_ITEM tabControlItem; + PPH_LAYOUT_ITEM tabPageItem; + + tabControlHandle = PropSheet_GetTabControl(hwnd); + tabControlItem = PhAddLayoutItem(&propSheetContext->LayoutManager, tabControlHandle, + NULL, PH_ANCHOR_ALL | PH_LAYOUT_IMMEDIATE_RESIZE); + tabPageItem = PhAddLayoutItem(&propSheetContext->LayoutManager, tabControlHandle, + NULL, PH_LAYOUT_TAB_CONTROL); // dummy item to fix multiline tab control + + propSheetContext->TabPageItem = tabPageItem; + + PhAddLayoutItem(&propSheetContext->LayoutManager, GetDlgItem(hwnd, IDCANCEL), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + // Hide the OK button. + ShowWindow(GetDlgItem(hwnd, IDOK), SW_HIDE); + // Set the Cancel button's text to "Close". + SetDlgItemText(hwnd, IDCANCEL, L"Close"); + + propSheetContext->LayoutInitialized = TRUE; + + return TRUE; + } + + return FALSE; +} + +VOID PhpInitializePropSheetLayoutStage2( + _In_ HWND hwnd + ) +{ + PH_RECTANGLE windowRectangle; + + windowRectangle.Position = PhGetIntegerPairSetting(L"ProcPropPosition"); + windowRectangle.Size = PhGetScalableIntegerPairSetting(L"ProcPropSize", TRUE).Pair; + + if (windowRectangle.Size.X < MinimumSize.right) + windowRectangle.Size.X = MinimumSize.right; + if (windowRectangle.Size.Y < MinimumSize.bottom) + windowRectangle.Size.Y = MinimumSize.bottom; + + PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); + + MoveWindow(hwnd, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + + // Implement cascading by saving an offsetted rectangle. + windowRectangle.Left += 20; + windowRectangle.Top += 20; + + PhSetIntegerPairSetting(L"ProcPropPosition", windowRectangle.Position); +} + +BOOLEAN PhAddProcessPropPage( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ _Assume_refs_(1) PPH_PROCESS_PROPPAGECONTEXT PropPageContext + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + + if (PropContext->PropSheetHeader.nPages == PH_PROCESS_PROPCONTEXT_MAXPAGES) + return FALSE; + + propSheetPageHandle = CreatePropertySheetPage( + &PropPageContext->PropSheetPage + ); + // CreatePropertySheetPage would have sent PSPCB_ADDREF, + // which would have added a reference. + PhDereferenceObject(PropPageContext); + + PropPageContext->PropContext = PropContext; + PhReferenceObject(PropContext); + + PropContext->PropSheetPages[PropContext->PropSheetHeader.nPages] = + propSheetPageHandle; + PropContext->PropSheetHeader.nPages++; + + return TRUE; +} + +BOOLEAN PhAddProcessPropPage2( + _Inout_ PPH_PROCESS_PROPCONTEXT PropContext, + _In_ HPROPSHEETPAGE PropSheetPageHandle + ) +{ + if (PropContext->PropSheetHeader.nPages == PH_PROCESS_PROPCONTEXT_MAXPAGES) + return FALSE; + + PropContext->PropSheetPages[PropContext->PropSheetHeader.nPages] = + PropSheetPageHandle; + PropContext->PropSheetHeader.nPages++; + + return TRUE; +} + +PPH_PROCESS_PROPPAGECONTEXT PhCreateProcessPropPageContext( + _In_ LPCWSTR Template, + _In_ DLGPROC DlgProc, + _In_opt_ PVOID Context + ) +{ + return PhCreateProcessPropPageContextEx(NULL, Template, DlgProc, Context); +} + +PPH_PROCESS_PROPPAGECONTEXT PhCreateProcessPropPageContextEx( + _In_opt_ PVOID InstanceHandle, + _In_ LPCWSTR Template, + _In_ DLGPROC DlgProc, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + + propPageContext = PhCreateObject(sizeof(PH_PROCESS_PROPPAGECONTEXT), PhpProcessPropPageContextType); + memset(propPageContext, 0, sizeof(PH_PROCESS_PROPPAGECONTEXT)); + + propPageContext->PropSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propPageContext->PropSheetPage.dwFlags = + PSP_USECALLBACK; + propPageContext->PropSheetPage.hInstance = InstanceHandle; + propPageContext->PropSheetPage.pszTemplate = Template; + propPageContext->PropSheetPage.pfnDlgProc = DlgProc; + propPageContext->PropSheetPage.lParam = (LPARAM)propPageContext; + propPageContext->PropSheetPage.pfnCallback = PhpStandardPropPageProc; + + propPageContext->Context = Context; + + return propPageContext; +} + +VOID NTAPI PhpProcessPropPageContextDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_PROCESS_PROPPAGECONTEXT propPageContext = (PPH_PROCESS_PROPPAGECONTEXT)Object; + + if (propPageContext->PropContext) + PhDereferenceObject(propPageContext->PropContext); +} + +INT CALLBACK PhpStandardPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + + propPageContext = (PPH_PROCESS_PROPPAGECONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + PhReferenceObject(propPageContext); + else if (uMsg == PSPCB_RELEASE) + PhDereferenceObject(propPageContext); + + return 1; +} + +BOOLEAN PhPropPageDlgProcHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam, + _Out_ LPPROPSHEETPAGE *PropSheetPage, + _Out_ PPH_PROCESS_PROPPAGECONTEXT *PropPageContext, + _Out_ PPH_PROCESS_ITEM *ProcessItem + ) +{ + return PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, PropSheetPage, PropPageContext, ProcessItem); +} + +VOID PhPropPageDlgProcDestroy( + _In_ HWND hwndDlg + ) +{ + PhpPropPageDlgProcDestroy(hwndDlg); +} + +PPH_LAYOUT_ITEM PhAddPropPageLayoutItem( + _In_ HWND hwnd, + _In_ HWND Handle, + _In_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor + ) +{ + HWND parent; + PPH_PROCESS_PROPSHEETCONTEXT propSheetContext; + PPH_LAYOUT_MANAGER layoutManager; + PPH_LAYOUT_ITEM realParentItem; + BOOLEAN doLayoutStage2; + PPH_LAYOUT_ITEM item; + + parent = GetParent(hwnd); + propSheetContext = PhpGetPropSheetContext(parent); + layoutManager = &propSheetContext->LayoutManager; + + doLayoutStage2 = PhpInitializePropSheetLayoutStage1(parent); + + if (ParentItem != PH_PROP_PAGE_TAB_CONTROL_PARENT) + realParentItem = ParentItem; + else + realParentItem = propSheetContext->TabPageItem; + + // Use the HACK if the control is a direct child of the dialog. + if (ParentItem && ParentItem != PH_PROP_PAGE_TAB_CONTROL_PARENT && + // We detect if ParentItem is the layout item for the dialog + // by looking at its parent. + (ParentItem->ParentItem == &layoutManager->RootItem || + (ParentItem->ParentItem->Anchor & PH_LAYOUT_TAB_CONTROL))) + { + RECT dialogRect; + RECT dialogSize; + RECT margin; + + // MAKE SURE THESE NUMBERS ARE CORRECT. + dialogSize.right = 260; + dialogSize.bottom = 260; + MapDialogRect(hwnd, &dialogSize); + + // Get the original dialog rectangle. + GetWindowRect(hwnd, &dialogRect); + dialogRect.right = dialogRect.left + dialogSize.right; + dialogRect.bottom = dialogRect.top + dialogSize.bottom; + + // Calculate the margin from the original rectangle. + GetWindowRect(Handle, &margin); + margin = PhMapRect(margin, dialogRect); + PhConvertRect(&margin, &dialogRect); + + item = PhAddLayoutItemEx(layoutManager, Handle, realParentItem, Anchor, margin); + } + else + { + item = PhAddLayoutItem(layoutManager, Handle, realParentItem, Anchor); + } + + if (doLayoutStage2) + PhpInitializePropSheetLayoutStage2(parent); + + return item; +} + +VOID PhDoPropPageLayout( + _In_ HWND hwnd + ) +{ + HWND parent; + PPH_PROCESS_PROPSHEETCONTEXT propSheetContext; + + parent = GetParent(hwnd); + propSheetContext = PhpGetPropSheetContext(parent); + PhLayoutManagerLayout(&propSheetContext->LayoutManager); +} + +NTSTATUS PhpProcessPropertiesThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + PPH_PROCESS_PROPCONTEXT PropContext = (PPH_PROCESS_PROPCONTEXT)Parameter; + PPH_PROCESS_PROPPAGECONTEXT newPage; + PPH_STRING startPage; + HWND hwnd; + BOOL result; + MSG message; + + PhInitializeAutoPool(&autoPool); + + // Wait for stage 1 to be processed. + PhWaitForEvent(&PropContext->ProcessItem->Stage1Event, NULL); + // Refresh the icon which may have been updated due to + // stage 1. + PhRefreshProcessPropContext(PropContext); + + // Add the pages... + + // General + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCGENERAL), + PhpProcessGeneralDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Statistics + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCSTATISTICS), + PhpProcessStatisticsDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Performance + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCPERFORMANCE), + PhpProcessPerformanceDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Threads + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCTHREADS), + PhpProcessThreadsDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Token + PhAddProcessPropPage2( + PropContext, + PhCreateTokenPage(PhpOpenProcessTokenForPage, (PVOID)PropContext->ProcessItem->ProcessId, PhpProcessTokenHookProc) + ); + + // Modules + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCMODULES), + PhpProcessModulesDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Memory + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCMEMORY), + PhpProcessMemoryDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Environment + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCENVIRONMENT), + PhpProcessEnvironmentDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Handles + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCHANDLES), + PhpProcessHandlesDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + + // Job + if ( + PropContext->ProcessItem->IsInJob && + // There's no way the job page can function without KPH since it needs + // to open a handle to the job. + KphIsConnected() + ) + { + PhAddProcessPropPage2( + PropContext, + PhCreateJobPage(PhpOpenProcessJobForPage, (PVOID)PropContext->ProcessItem->ProcessId, PhpProcessJobHookProc) + ); + } + + // Services + if (PropContext->ProcessItem->ServiceList && PropContext->ProcessItem->ServiceList->Count != 0) + { + newPage = PhCreateProcessPropPageContext( + MAKEINTRESOURCE(IDD_PROCSERVICES), + PhpProcessServicesDlgProc, + NULL + ); + PhAddProcessPropPage(PropContext, newPage); + } + + // Plugin-supplied pages + if (PhPluginsEnabled) + { + PH_PLUGIN_PROCESS_PROPCONTEXT pluginProcessPropContext; + + pluginProcessPropContext.PropContext = PropContext; + pluginProcessPropContext.ProcessItem = PropContext->ProcessItem; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackProcessPropertiesInitializing), &pluginProcessPropContext); + } + + // Create the property sheet + + if (PropContext->SelectThreadId) + PhSetStringSetting(L"ProcPropPage", L"Threads"); + + startPage = PhGetStringSetting(L"ProcPropPage"); + PropContext->PropSheetHeader.dwFlags |= PSH_USEPSTARTPAGE; + PropContext->PropSheetHeader.pStartPage = startPage->Buffer; + + hwnd = (HWND)PropertySheet(&PropContext->PropSheetHeader); + + PhDereferenceObject(startPage); + + PropContext->WindowHandle = hwnd; + PhSetEvent(&PropContext->CreatedEvent); + + // Main event loop + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!PropSheet_IsDialogMessage(hwnd, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + + if (!PropSheet_GetCurrentPageHwnd(hwnd)) + break; + } + + DestroyWindow(hwnd); + PhDereferenceObject(PropContext); + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +BOOLEAN PhShowProcessProperties( + _In_ PPH_PROCESS_PROPCONTEXT Context + ) +{ + HANDLE threadHandle; + + PhReferenceObject(Context); + threadHandle = PhCreateThread(0, PhpProcessPropertiesThreadStart, Context); + + if (threadHandle) + { + NtClose(threadHandle); + return TRUE; + } + else + { + PhDereferenceObject(Context); + return FALSE; + } +} diff --git a/ProcessHacker/procprv.c b/ProcessHacker/procprv.c new file mode 100644 index 0000000..3d03dbc --- /dev/null +++ b/ProcessHacker/procprv.c @@ -0,0 +1,2958 @@ +/* + * Process Hacker - + * process provider + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This provider module handles the collection of process information and system-wide statistics. A + * list of all running processes is kept and periodically scanned to detect new and terminated + * processes. + * + * The retrieval of certain information is delayed in order to improve performance. This includes + * things such as file icons, version information, digital signature verification, and packed + * executable detection. These requests are handed to worker threads which then post back + * information to a S-list. + * + * Also contained in this module is the storage of process records, which contain static information + * about processes. Unlike process items which are removed as soon as their corresponding process + * exits, process records remain as long as they are needed by the statistics system, and more + * specifically the max. CPU and I/O history buffers. In PH 1.x, a new formatted string was created + * at each update containing information about the maximum-usage process within that interval. Here + * we use a much more storage-efficient method, where the raw maximum-usage PIDs are stored for each + * interval, and the process record list is searched when the name of a process is needed. + * + * The process record list is stored as a list of records sorted by process creation time. If two or + * more processes have the same creation time, they are added to a doubly-linked list. This + * structure allows for fast searching in the typical scenario where we know the PID of a process + * and a specific time in which the process was running. In this case a binary search is used and + * then the list is traversed backwards until the process is found. Binary search is similarly used + * for insertion and removal. + * + * On Windows 7 and above, CPU usage can be calculated from cycle time. However, cycle time cannot + * be split into kernel/user components, and cycle time is not available for DPCs and Interrupts + * separately (only a "system" cycle time). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROCESS_ID_BUCKETS 64 +#define PROCESS_ID_TO_BUCKET_INDEX(ProcessId) ((HandleToUlong(ProcessId) / 4) & (PROCESS_ID_BUCKETS - 1)) + +typedef struct _PH_PROCESS_QUERY_DATA +{ + SLIST_ENTRY ListEntry; + ULONG Stage; + PPH_PROCESS_ITEM ProcessItem; +} PH_PROCESS_QUERY_DATA, *PPH_PROCESS_QUERY_DATA; + +typedef struct _PH_PROCESS_QUERY_S1_DATA +{ + PH_PROCESS_QUERY_DATA Header; + + PPH_STRING CommandLine; + + HICON SmallIcon; + HICON LargeIcon; + PH_IMAGE_VERSION_INFO VersionInfo; + + TOKEN_ELEVATION_TYPE ElevationType; + BOOLEAN IsElevated; + MANDATORY_LEVEL IntegrityLevel; + PWSTR IntegrityString; + + PPH_STRING JobName; + BOOLEAN IsInJob; + BOOLEAN IsInSignificantJob; + + HANDLE ConsoleHostProcessId; + PPH_STRING PackageFullName; + + BOOLEAN IsDotNet; + BOOLEAN IsWow64; + BOOLEAN IsWow64Valid; +} PH_PROCESS_QUERY_S1_DATA, *PPH_PROCESS_QUERY_S1_DATA; + +typedef struct _PH_PROCESS_QUERY_S2_DATA +{ + PH_PROCESS_QUERY_DATA Header; + + VERIFY_RESULT VerifyResult; + PPH_STRING VerifySignerName; + + BOOLEAN IsPacked; + ULONG ImportFunctions; + ULONG ImportModules; +} PH_PROCESS_QUERY_S2_DATA, *PPH_PROCESS_QUERY_S2_DATA; + +typedef struct _PH_VERIFY_CACHE_ENTRY +{ + PH_AVL_LINKS Links; + + PPH_STRING FileName; + VERIFY_RESULT VerifyResult; + PPH_STRING VerifySignerName; +} PH_VERIFY_CACHE_ENTRY, *PPH_VERIFY_CACHE_ENTRY; + +typedef struct _PH_SID_FULL_NAME_CACHE_ENTRY +{ + PSID Sid; + PPH_STRING FullName; +} PH_SID_FULL_NAME_CACHE_ENTRY, *PPH_SID_FULL_NAME_CACHE_ENTRY; + +VOID NTAPI PhpProcessItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +INT NTAPI PhpVerifyCacheCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +VOID PhpQueueProcessQueryStage1( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +VOID PhpQueueProcessQueryStage2( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +PPH_PROCESS_RECORD PhpCreateProcessRecord( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +VOID PhpAddProcessRecord( + _Inout_ PPH_PROCESS_RECORD ProcessRecord + ); + +VOID PhpRemoveProcessRecord( + _Inout_ PPH_PROCESS_RECORD ProcessRecord + ); + +PPH_OBJECT_TYPE PhProcessItemType; + +PPH_HASH_ENTRY PhProcessHashSet[256] = PH_HASH_SET_INIT; +ULONG PhProcessHashSetCount = 0; +PH_QUEUED_LOCK PhProcessHashSetLock = PH_QUEUED_LOCK_INIT; + +SLIST_HEADER PhProcessQueryDataListHead; + +PHAPPAPI PH_CALLBACK_DECLARE(PhProcessAddedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhProcessModifiedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhProcessRemovedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhProcessesUpdatedEvent); + +PPH_LIST PhProcessRecordList; +PH_QUEUED_LOCK PhProcessRecordListLock = PH_QUEUED_LOCK_INIT; + +ULONG PhStatisticsSampleCount = 512; +BOOLEAN PhEnableProcessQueryStage2 = FALSE; +BOOLEAN PhEnablePurgeProcessRecords = TRUE; +BOOLEAN PhEnableCycleCpuUsage = TRUE; + +PVOID PhProcessInformation; // only can be used if running on same thread as process provider +SYSTEM_PERFORMANCE_INFORMATION PhPerfInformation; +PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PhCpuInformation; +SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION PhCpuTotals; +ULONG PhTotalProcesses; +ULONG PhTotalThreads; +ULONG PhTotalHandles; + +SYSTEM_PROCESS_INFORMATION PhDpcsProcessInformation; +SYSTEM_PROCESS_INFORMATION PhInterruptsProcessInformation; + +ULONG64 PhCpuTotalCycleDelta; // real cycle time delta for this period +PLARGE_INTEGER PhCpuIdleCycleTime; // cycle time for Idle +PLARGE_INTEGER PhCpuSystemCycleTime; // cycle time for DPCs and Interrupts +PH_UINT64_DELTA PhCpuIdleCycleDelta; +PH_UINT64_DELTA PhCpuSystemCycleDelta; +//PPH_UINT64_DELTA PhCpusIdleCycleDelta; + +FLOAT PhCpuKernelUsage; +FLOAT PhCpuUserUsage; +PFLOAT PhCpusKernelUsage; +PFLOAT PhCpusUserUsage; + +PH_UINT64_DELTA PhCpuKernelDelta; +PH_UINT64_DELTA PhCpuUserDelta; +PH_UINT64_DELTA PhCpuIdleDelta; + +PPH_UINT64_DELTA PhCpusKernelDelta; +PPH_UINT64_DELTA PhCpusUserDelta; +PPH_UINT64_DELTA PhCpusIdleDelta; + +PH_UINT64_DELTA PhIoReadDelta; +PH_UINT64_DELTA PhIoWriteDelta; +PH_UINT64_DELTA PhIoOtherDelta; + +static BOOLEAN PhProcessStatisticsInitialized = FALSE; +static ULONG PhTimeSequenceNumber = 0; +static PH_CIRCULAR_BUFFER_ULONG PhTimeHistory; + +PH_CIRCULAR_BUFFER_FLOAT PhCpuKernelHistory; +PH_CIRCULAR_BUFFER_FLOAT PhCpuUserHistory; +//PH_CIRCULAR_BUFFER_FLOAT PhCpuOtherHistory; + +PPH_CIRCULAR_BUFFER_FLOAT PhCpusKernelHistory; +PPH_CIRCULAR_BUFFER_FLOAT PhCpusUserHistory; +//PPH_CIRCULAR_BUFFER_FLOAT PhCpusOtherHistory; + +PH_CIRCULAR_BUFFER_ULONG64 PhIoReadHistory; +PH_CIRCULAR_BUFFER_ULONG64 PhIoWriteHistory; +PH_CIRCULAR_BUFFER_ULONG64 PhIoOtherHistory; + +PH_CIRCULAR_BUFFER_ULONG PhCommitHistory; +PH_CIRCULAR_BUFFER_ULONG PhPhysicalHistory; + +PH_CIRCULAR_BUFFER_ULONG PhMaxCpuHistory; // ID of max. CPU process +PH_CIRCULAR_BUFFER_ULONG PhMaxIoHistory; // ID of max. I/O process +#ifdef PH_RECORD_MAX_USAGE +PH_CIRCULAR_BUFFER_FLOAT PhMaxCpuUsageHistory; +PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoReadOtherHistory; +PH_CIRCULAR_BUFFER_ULONG64 PhMaxIoWriteHistory; +#endif + +static PTS_ALL_PROCESSES_INFO PhpTsProcesses = NULL; +static ULONG PhpTsNumberOfProcesses; + +#ifdef PH_ENABLE_VERIFY_CACHE +static PH_AVL_TREE PhpVerifyCacheSet = PH_AVL_TREE_INIT(PhpVerifyCacheCompareFunction); +static PH_QUEUED_LOCK PhpVerifyCacheLock = PH_QUEUED_LOCK_INIT; +#endif + +static PPH_HASHTABLE PhpSidFullNameCacheHashtable; + +BOOLEAN PhProcessProviderInitialization( + VOID + ) +{ + PFLOAT usageBuffer; + PPH_UINT64_DELTA deltaBuffer; + PPH_CIRCULAR_BUFFER_FLOAT historyBuffer; + + PhProcessItemType = PhCreateObjectType(L"ProcessItem", 0, PhpProcessItemDeleteProcedure); + + RtlInitializeSListHead(&PhProcessQueryDataListHead); + + PhProcessRecordList = PhCreateList(40); + + RtlInitUnicodeString( + &PhDpcsProcessInformation.ImageName, + L"DPCs" + ); + PhDpcsProcessInformation.UniqueProcessId = DPCS_PROCESS_ID; + PhDpcsProcessInformation.InheritedFromUniqueProcessId = SYSTEM_IDLE_PROCESS_ID; + + RtlInitUnicodeString( + &PhInterruptsProcessInformation.ImageName, + L"Interrupts" + ); + PhInterruptsProcessInformation.UniqueProcessId = INTERRUPTS_PROCESS_ID; + PhInterruptsProcessInformation.InheritedFromUniqueProcessId = SYSTEM_IDLE_PROCESS_ID; + + PhCpuInformation = PhAllocate( + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors + ); + + PhCpuIdleCycleTime = PhAllocate( + sizeof(LARGE_INTEGER) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors + ); + PhCpuSystemCycleTime = PhAllocate( + sizeof(LARGE_INTEGER) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors + ); + + usageBuffer = PhAllocate( + sizeof(FLOAT) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors * + 2 + ); + deltaBuffer = PhAllocate( + sizeof(PH_UINT64_DELTA) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors * + 3 // 4 for PhCpusIdleCycleDelta + ); + historyBuffer = PhAllocate( + sizeof(PH_CIRCULAR_BUFFER_FLOAT) * + (ULONG)PhSystemBasicInformation.NumberOfProcessors * + 2 + ); + + PhCpusKernelUsage = usageBuffer; + PhCpusUserUsage = PhCpusKernelUsage + (ULONG)PhSystemBasicInformation.NumberOfProcessors; + + PhCpusKernelDelta = deltaBuffer; + PhCpusUserDelta = PhCpusKernelDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors; + PhCpusIdleDelta = PhCpusUserDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors; + //PhCpusIdleCycleDelta = PhCpusIdleDelta + (ULONG)PhSystemBasicInformation.NumberOfProcessors; + + PhCpusKernelHistory = historyBuffer; + PhCpusUserHistory = PhCpusKernelHistory + (ULONG)PhSystemBasicInformation.NumberOfProcessors; + + memset(deltaBuffer, 0, sizeof(PH_UINT64_DELTA) * (ULONG)PhSystemBasicInformation.NumberOfProcessors); + + return TRUE; +} + +PPH_STRING PhGetClientIdName( + _In_ PCLIENT_ID ClientId + ) +{ + PPH_STRING name; + PPH_PROCESS_ITEM processItem; + + processItem = PhReferenceProcessItem(ClientId->UniqueProcess); + + if (processItem) + { + name = PhGetClientIdNameEx(ClientId, processItem->ProcessName); + PhDereferenceObject(processItem); + } + else + { + name = PhGetClientIdNameEx(ClientId, NULL); + } + + return name; +} + +PPH_STRING PhGetClientIdNameEx( + _In_ PCLIENT_ID ClientId, + _In_opt_ PPH_STRING ProcessName + ) +{ + PPH_STRING name; + PH_FORMAT format[5]; + + if (ClientId->UniqueThread) + { + if (ProcessName) + { + PhInitFormatSR(&format[0], ProcessName->sr); + PhInitFormatS(&format[1], L" ("); + PhInitFormatIU(&format[2], (ULONG_PTR)ClientId->UniqueProcess); + PhInitFormatS(&format[3], L"): "); + PhInitFormatIU(&format[4], (ULONG_PTR)ClientId->UniqueThread); + + name = PhFormat(format, 5, ProcessName->Length + 16 * sizeof(WCHAR)); + } + else + { + PhInitFormatS(&format[0], L"Non-existent process ("); + PhInitFormatIU(&format[1], (ULONG_PTR)ClientId->UniqueProcess); + PhInitFormatS(&format[2], L"): "); + PhInitFormatIU(&format[3], (ULONG_PTR)ClientId->UniqueThread); + + name = PhFormat(format, 4, 0); + } + } + else + { + if (ProcessName) + { + PhInitFormatSR(&format[0], ProcessName->sr); + PhInitFormatS(&format[1], L" ("); + PhInitFormatIU(&format[2], (ULONG_PTR)ClientId->UniqueProcess); + PhInitFormatC(&format[3], ')'); + + name = PhFormat(format, 4, 0); + } + else + { + PhInitFormatS(&format[0], L"Non-existent process ("); + PhInitFormatIU(&format[1], (ULONG_PTR)ClientId->UniqueProcess); + PhInitFormatC(&format[2], ')'); + + name = PhFormat(format, 3, 0); + } + } + + return name; +} + +PWSTR PhGetProcessPriorityClassString( + _In_ ULONG PriorityClass + ) +{ + switch (PriorityClass) + { + case PROCESS_PRIORITY_CLASS_REALTIME: + return L"Real time"; + case PROCESS_PRIORITY_CLASS_HIGH: + return L"High"; + case PROCESS_PRIORITY_CLASS_ABOVE_NORMAL: + return L"Above normal"; + case PROCESS_PRIORITY_CLASS_NORMAL: + return L"Normal"; + case PROCESS_PRIORITY_CLASS_BELOW_NORMAL: + return L"Below normal"; + case PROCESS_PRIORITY_CLASS_IDLE: + return L"Idle"; + default: + return L"Unknown"; + } +} + +/** + * Creates a process item. + */ +PPH_PROCESS_ITEM PhCreateProcessItem( + _In_ HANDLE ProcessId + ) +{ + PPH_PROCESS_ITEM processItem; + + processItem = PhCreateObject( + PhEmGetObjectSize(EmProcessItemType, sizeof(PH_PROCESS_ITEM)), + PhProcessItemType + ); + memset(processItem, 0, sizeof(PH_PROCESS_ITEM)); + PhInitializeEvent(&processItem->Stage1Event); + PhInitializeQueuedLock(&processItem->ServiceListLock); + + processItem->ProcessId = ProcessId; + + if (!PH_IS_FAKE_PROCESS_ID(ProcessId)) + PhPrintUInt32(processItem->ProcessIdString, HandleToUlong(ProcessId)); + + // Create the statistics buffers. + PhInitializeCircularBuffer_FLOAT(&processItem->CpuKernelHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_FLOAT(&processItem->CpuUserHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&processItem->IoReadHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&processItem->IoWriteHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&processItem->IoOtherHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, PhStatisticsSampleCount); + //PhInitializeCircularBuffer_SIZE_T(&processItem->WorkingSetHistory, PhStatisticsSampleCount); + + PhEmCallObjectOperation(EmProcessItemType, processItem, EmObjectCreate); + + return processItem; +} + +VOID PhpProcessItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Object; + ULONG i; + + PhEmCallObjectOperation(EmProcessItemType, processItem, EmObjectDelete); + + PhDeleteCircularBuffer_FLOAT(&processItem->CpuKernelHistory); + PhDeleteCircularBuffer_FLOAT(&processItem->CpuUserHistory); + PhDeleteCircularBuffer_ULONG64(&processItem->IoReadHistory); + PhDeleteCircularBuffer_ULONG64(&processItem->IoWriteHistory); + PhDeleteCircularBuffer_ULONG64(&processItem->IoOtherHistory); + PhDeleteCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory); + //PhDeleteCircularBuffer_SIZE_T(&processItem->WorkingSetHistory); + + if (processItem->ServiceList) + { + PPH_SERVICE_ITEM serviceItem; + + i = 0; + + while (PhEnumPointerList(processItem->ServiceList, &i, &serviceItem)) + PhDereferenceObject(serviceItem); + + PhDereferenceObject(processItem->ServiceList); + } + + if (processItem->ProcessName) PhDereferenceObject(processItem->ProcessName); + if (processItem->FileName) PhDereferenceObject(processItem->FileName); + if (processItem->CommandLine) PhDereferenceObject(processItem->CommandLine); + if (processItem->SmallIcon) DestroyIcon(processItem->SmallIcon); + if (processItem->LargeIcon) DestroyIcon(processItem->LargeIcon); + PhDeleteImageVersionInfo(&processItem->VersionInfo); + if (processItem->UserName) PhDereferenceObject(processItem->UserName); + if (processItem->JobName) PhDereferenceObject(processItem->JobName); + if (processItem->VerifySignerName) PhDereferenceObject(processItem->VerifySignerName); + if (processItem->PackageFullName) PhDereferenceObject(processItem->PackageFullName); + + if (processItem->QueryHandle) NtClose(processItem->QueryHandle); + + if (processItem->Record) PhDereferenceProcessRecord(processItem->Record); +} + +FORCEINLINE BOOLEAN PhCompareProcessItem( + _In_ PPH_PROCESS_ITEM Value1, + _In_ PPH_PROCESS_ITEM Value2 + ) +{ + return Value1->ProcessId == Value2->ProcessId; +} + +FORCEINLINE ULONG PhHashProcessItem( + _In_ PPH_PROCESS_ITEM Value + ) +{ + return HandleToUlong(Value->ProcessId) / 4; +} + +/** + * Finds a process item in the hash set. + * + * \param ProcessId The process ID of the process item. + * + * \remarks The hash set must be locked before calling this function. The reference count of the + * found process item is not incremented. + */ +PPH_PROCESS_ITEM PhpLookupProcessItem( + _In_ HANDLE ProcessId + ) +{ + PH_PROCESS_ITEM lookupProcessItem; + PPH_HASH_ENTRY entry; + PPH_PROCESS_ITEM processItem; + + lookupProcessItem.ProcessId = ProcessId; + entry = PhFindEntryHashSet( + PhProcessHashSet, + PH_HASH_SET_SIZE(PhProcessHashSet), + PhHashProcessItem(&lookupProcessItem) + ); + + for (; entry; entry = entry->Next) + { + processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry); + + if (PhCompareProcessItem(&lookupProcessItem, processItem)) + return processItem; + } + + return NULL; +} + +/** + * Finds and references a process item. + * + * \param ProcessId The process ID of the process item. + * + * \return The found process item. + */ +PPH_PROCESS_ITEM PhReferenceProcessItem( + _In_ HANDLE ProcessId + ) +{ + PPH_PROCESS_ITEM processItem; + + PhAcquireQueuedLockShared(&PhProcessHashSetLock); + + processItem = PhpLookupProcessItem(ProcessId); + + if (processItem) + PhReferenceObject(processItem); + + PhReleaseQueuedLockShared(&PhProcessHashSetLock); + + return processItem; +} + +/** + * Enumerates the process items. + * + * \param ProcessItems A variable which receives an array of pointers to process items. You must + * free the buffer with PhFree() when you no longer need it. + * \param NumberOfProcessItems A variable which receives the number of process items returned in + * \a ProcessItems. + */ +VOID PhEnumProcessItems( + _Out_opt_ PPH_PROCESS_ITEM **ProcessItems, + _Out_ PULONG NumberOfProcessItems + ) +{ + PPH_PROCESS_ITEM *processItems; + ULONG numberOfProcessItems; + ULONG count = 0; + ULONG i; + PPH_HASH_ENTRY entry; + PPH_PROCESS_ITEM processItem; + + if (!ProcessItems) + { + *NumberOfProcessItems = PhProcessHashSetCount; + return; + } + + PhAcquireQueuedLockShared(&PhProcessHashSetLock); + + numberOfProcessItems = PhProcessHashSetCount; + processItems = PhAllocate(sizeof(PPH_PROCESS_ITEM) * numberOfProcessItems); + + for (i = 0; i < PH_HASH_SET_SIZE(PhProcessHashSet); i++) + { + for (entry = PhProcessHashSet[i]; entry; entry = entry->Next) + { + processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry); + PhReferenceObject(processItem); + processItems[count++] = processItem; + } + } + + PhReleaseQueuedLockShared(&PhProcessHashSetLock); + + *ProcessItems = processItems; + *NumberOfProcessItems = numberOfProcessItems; +} + +VOID PhpAddProcessItem( + _In_ _Assume_refs_(1) PPH_PROCESS_ITEM ProcessItem + ) +{ + PhAddEntryHashSet( + PhProcessHashSet, + PH_HASH_SET_SIZE(PhProcessHashSet), + &ProcessItem->HashEntry, + PhHashProcessItem(ProcessItem) + ); + PhProcessHashSetCount++; +} + +VOID PhpRemoveProcessItem( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PhRemoveEntryHashSet(PhProcessHashSet, PH_HASH_SET_SIZE(PhProcessHashSet), &ProcessItem->HashEntry); + PhProcessHashSetCount--; + PhDereferenceObject(ProcessItem); +} + +INT NTAPI PhpVerifyCacheCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PPH_VERIFY_CACHE_ENTRY entry1 = CONTAINING_RECORD(Links1, PH_VERIFY_CACHE_ENTRY, Links); + PPH_VERIFY_CACHE_ENTRY entry2 = CONTAINING_RECORD(Links2, PH_VERIFY_CACHE_ENTRY, Links); + + return PhCompareString(entry1->FileName, entry2->FileName, TRUE); +} + +VERIFY_RESULT PhVerifyFileWithAdditionalCatalog( + _In_ PPH_VERIFY_FILE_INFO Information, + _In_opt_ PWSTR PackageFullName, + _Out_opt_ PPH_STRING *SignerName + ) +{ + static PH_STRINGREF codeIntegrityFileName = PH_STRINGREF_INIT(L"\\AppxMetadata\\CodeIntegrity.cat"); + + VERIFY_RESULT result; + PPH_STRING additionalCatalogFileName = NULL; + PCERT_CONTEXT *signatures; + ULONG numberOfSignatures; + + if (PackageFullName) + { + PACKAGE_ID *packageId; + PPH_STRING packagePath; + + if (packageId = PhPackageIdFromFullName(PackageFullName)) + { + if (packagePath = PhGetPackagePath(packageId)) + { + additionalCatalogFileName = PhConcatStringRef2(&packagePath->sr, &codeIntegrityFileName); + PhDereferenceObject(packagePath); + } + + PhFree(packageId); + } + } + + if (additionalCatalogFileName) + { + Information->NumberOfCatalogFileNames = 1; + Information->CatalogFileNames = &additionalCatalogFileName->Buffer; + } + + if (!NT_SUCCESS(PhVerifyFileEx(Information, &result, &signatures, &numberOfSignatures))) + { + result = VrNoSignature; + signatures = NULL; + numberOfSignatures = 0; + } + + if (additionalCatalogFileName) + PhDereferenceObject(additionalCatalogFileName); + + if (SignerName) + { + if (numberOfSignatures != 0) + *SignerName = PhGetSignerNameFromCertificate(signatures[0]); + else + *SignerName = NULL; + } + + PhFreeVerifySignatures(signatures, numberOfSignatures); + + return result; +} + +/** + * Verifies a file's digital signature, using a cached result if possible. + * + * \param FileName A file name. + * \param ProcessItem An associated process item. + * \param SignerName A variable which receives a pointer to a string containing the signer name. You + * must free the string using PhDereferenceObject() when you no longer need it. Note that the signer + * name may be NULL if it is not valid. + * \param CachedOnly Specify TRUE to fail the function when no cached result exists. + * + * \return A VERIFY_RESULT value. + */ +VERIFY_RESULT PhVerifyFileCached( + _In_ PPH_STRING FileName, + _In_opt_ PWSTR PackageFullName, + _Out_opt_ PPH_STRING *SignerName, + _In_ BOOLEAN CachedOnly + ) +{ +#ifdef PH_ENABLE_VERIFY_CACHE + PPH_AVL_LINKS links; + PPH_VERIFY_CACHE_ENTRY entry; + PH_VERIFY_CACHE_ENTRY lookupEntry; + + lookupEntry.FileName = FileName; + + PhAcquireQueuedLockShared(&PhpVerifyCacheLock); + links = PhFindElementAvlTree(&PhpVerifyCacheSet, &lookupEntry.Links); + PhReleaseQueuedLockShared(&PhpVerifyCacheLock); + + if (links) + { + entry = CONTAINING_RECORD(links, PH_VERIFY_CACHE_ENTRY, Links); + + if (SignerName) + PhSetReference(SignerName, entry->VerifySignerName); + + return entry->VerifyResult; + } + else + { + VERIFY_RESULT result; + PPH_STRING signerName; + + if (!CachedOnly) + { + PH_VERIFY_FILE_INFO info; + + memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO)); + info.FileName = FileName->Buffer; + info.Flags = PH_VERIFY_PREVENT_NETWORK_ACCESS; + result = PhVerifyFileWithAdditionalCatalog(&info, PackageFullName, &signerName); + + if (result != VrTrusted) + PhClearReference(&signerName); + } + else + { + result = VrUnknown; + signerName = NULL; + } + + if (result != VrUnknown) + { + entry = PhAllocate(sizeof(PH_VERIFY_CACHE_ENTRY)); + entry->FileName = FileName; + entry->VerifyResult = result; + entry->VerifySignerName = signerName; + + PhAcquireQueuedLockExclusive(&PhpVerifyCacheLock); + links = PhAddElementAvlTree(&PhpVerifyCacheSet, &entry->Links); + PhReleaseQueuedLockExclusive(&PhpVerifyCacheLock); + + if (!links) + { + // We successfully added the cache entry. Add references. + + PhReferenceObject(entry->FileName); + + if (entry->VerifySignerName) + PhReferenceObject(entry->VerifySignerName); + } + else + { + // Entry already exists. + PhFree(entry); + } + } + + if (SignerName) + { + *SignerName = signerName; + } + else + { + if (signerName) + PhDereferenceObject(signerName); + } + + return result; + } +#else + VERIFY_RESULT result; + PPH_STRING signerName; + PH_VERIFY_FILE_INFO info; + + memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO)); + info.FileName = FileName->Buffer; + info.Flags = PH_VERIFY_PREVENT_NETWORK_ACCESS; + result = PhVerifyFileWithAdditionalCatalog(&info, PackageFullName, &signerName); + + if (result != VrTrusted) + PhClearReference(&signerName); + + if (SignerName) + { + *SignerName = signerName; + } + else + { + if (signerName) + PhDereferenceObject(signerName); + } + + return result; +#endif +} + +BOOLEAN PhpSidFullNameCacheHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_SID_FULL_NAME_CACHE_ENTRY entry1 = Entry1; + PPH_SID_FULL_NAME_CACHE_ENTRY entry2 = Entry2; + + return RtlEqualSid(entry1->Sid, entry2->Sid); +} + +ULONG PhpSidFullNameCacheHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPH_SID_FULL_NAME_CACHE_ENTRY entry = Entry; + + return PhHashBytes(entry->Sid, RtlLengthSid(entry->Sid)); +} + +PPH_STRING PhpGetSidFullNameCached( + _In_ PSID Sid + ) +{ + PPH_STRING fullName; + PH_SID_FULL_NAME_CACHE_ENTRY newEntry; + + if (PhpSidFullNameCacheHashtable) + { + PPH_SID_FULL_NAME_CACHE_ENTRY entry; + PH_SID_FULL_NAME_CACHE_ENTRY lookupEntry; + + lookupEntry.Sid = Sid; + entry = PhFindEntryHashtable(PhpSidFullNameCacheHashtable, &lookupEntry); + + if (entry) + return PhReferenceObject(entry->FullName); + } + + fullName = PhGetSidFullName(Sid, TRUE, NULL); + + if (!fullName) + return NULL; + + if (!PhpSidFullNameCacheHashtable) + { + PhpSidFullNameCacheHashtable = PhCreateHashtable( + sizeof(PH_SID_FULL_NAME_CACHE_ENTRY), + PhpSidFullNameCacheHashtableEqualFunction, + PhpSidFullNameCacheHashtableHashFunction, + 16 + ); + } + + newEntry.Sid = PhAllocateCopy(Sid, RtlLengthSid(Sid)); + newEntry.FullName = PhReferenceObject(fullName); + PhAddEntryHashtable(PhpSidFullNameCacheHashtable, &newEntry); + + return fullName; +} + +VOID PhpFlushSidFullNameCache( + VOID + ) +{ + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_SID_FULL_NAME_CACHE_ENTRY entry; + + if (!PhpSidFullNameCacheHashtable) + return; + + PhBeginEnumHashtable(PhpSidFullNameCacheHashtable, &enumContext); + + while (entry = PhNextEnumHashtable(&enumContext)) + { + PhFree(entry->Sid); + PhDereferenceObject(entry->FullName); + } + + PhClearReference(&PhpSidFullNameCacheHashtable); +} + +VOID PhpProcessQueryStage1( + _Inout_ PPH_PROCESS_QUERY_S1_DATA Data + ) +{ + NTSTATUS status; + PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem; + HANDLE processId = processItem->ProcessId; + HANDLE processHandleLimited = NULL; + + PhOpenProcess(&processHandleLimited, ProcessQueryAccess, processId); + + if (processItem->FileName) + { + // Small icon, large icon. + if (ExtractIconEx( + processItem->FileName->Buffer, + 0, + &Data->LargeIcon, + &Data->SmallIcon, + 1 + ) == 0) + { + Data->LargeIcon = NULL; + Data->SmallIcon = NULL; + } + + // Version info. + PhInitializeImageVersionInfo(&Data->VersionInfo, processItem->FileName->Buffer); + } + + // Use the default EXE icon if we didn't get the file's icon. + { + if (!Data->SmallIcon || !Data->LargeIcon) + { + if (Data->SmallIcon) + { + DestroyIcon(Data->SmallIcon); + Data->SmallIcon = NULL; + } + else if (Data->LargeIcon) + { + DestroyIcon(Data->LargeIcon); + Data->LargeIcon = NULL; + } + + PhGetStockApplicationIcon(&Data->SmallIcon, &Data->LargeIcon); + Data->SmallIcon = CopyIcon(Data->SmallIcon); + Data->LargeIcon = CopyIcon(Data->LargeIcon); + } + } + +#ifdef _WIN64 + // WOW64 + if (processHandleLimited) + { + if (NT_SUCCESS(PhGetProcessIsWow64(processHandleLimited, &Data->IsWow64))) + Data->IsWow64Valid = TRUE; + } +#else + Data->IsWow64Valid = TRUE; +#endif + + // Command line, .NET + { + HANDLE processHandle; + BOOLEAN queryAccess = FALSE; + + status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_VM_READ, + processId + ); + + if (!NT_SUCCESS(status) && WindowsVersion >= WINDOWS_8_1) + { + queryAccess = TRUE; + status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + processId + ); + } + + if (NT_SUCCESS(status)) + { + BOOLEAN isDotNet = FALSE; + PPH_STRING commandLine; + ULONG i; + + if (NT_SUCCESS(status = PhGetProcessCommandLine(processHandle, &commandLine))) + { + // Some command lines (e.g. from taskeng.exe) have nulls in them. Since Windows + // can't display them, we'll replace them with spaces. + for (i = 0; i < (ULONG)commandLine->Length / 2; i++) + { + if (commandLine->Buffer[i] == 0) + commandLine->Buffer[i] = ' '; + } + } + + if (NT_SUCCESS(status)) + { + Data->CommandLine = commandLine; + } + + if (!queryAccess) + { + PhGetProcessIsDotNetEx( + processId, + processHandle, +#ifdef _WIN64 + PH_CLR_NO_WOW64_CHECK | (Data->IsWow64 ? PH_CLR_KNOWN_IS_WOW64 : 0), +#else + 0, +#endif + &isDotNet, + NULL + ); + Data->IsDotNet = isDotNet; + } + + NtClose(processHandle); + } + } + + // Token information + if (processHandleLimited) + { + if (WINDOWS_HAS_UAC) + { + HANDLE tokenHandle; + + status = PhOpenProcessToken(processHandleLimited, TOKEN_QUERY, &tokenHandle); + + if (NT_SUCCESS(status)) + { + // Elevation + if (NT_SUCCESS(PhGetTokenElevationType( + tokenHandle, + &Data->ElevationType + ))) + { + Data->IsElevated = Data->ElevationType == TokenElevationTypeFull; + } + + // Integrity + PhGetTokenIntegrityLevel( + tokenHandle, + &Data->IntegrityLevel, + &Data->IntegrityString + ); + + NtClose(tokenHandle); + } + } + } + + // Job + if (processHandleLimited) + { + if (KphIsConnected()) + { + HANDLE jobHandle = NULL; + + status = KphOpenProcessJob( + processHandleLimited, + JOB_OBJECT_QUERY, + &jobHandle + ); + + if (NT_SUCCESS(status) && status != STATUS_PROCESS_NOT_IN_JOB && jobHandle) + { + JOBOBJECT_BASIC_LIMIT_INFORMATION basicLimits; + + Data->IsInJob = TRUE; + + PhGetHandleInformation( + NtCurrentProcess(), + jobHandle, + -1, + NULL, + NULL, + NULL, + &Data->JobName + ); + + // Process Explorer only recognizes processes as being in jobs if they don't have + // the silent-breakaway-OK limit as their only limit. Emulate this behaviour. + if (NT_SUCCESS(PhGetJobBasicLimits(jobHandle, &basicLimits))) + { + Data->IsInSignificantJob = + basicLimits.LimitFlags != JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; + } + + NtClose(jobHandle); + } + } + else + { + // KProcessHacker is not available. We can determine if the process is in a job, but we + // can't get a handle to the job. + + status = NtIsProcessInJob(processHandleLimited, NULL); + + if (NT_SUCCESS(status)) + Data->IsInJob = status == STATUS_PROCESS_IN_JOB; + } + } + + // Console host process + if (processHandleLimited && WINDOWS_HAS_CONSOLE_HOST) + { + PhGetProcessConsoleHostProcessId(processHandleLimited, &Data->ConsoleHostProcessId); + } + + // Package full name + if (processHandleLimited && WINDOWS_HAS_IMMERSIVE) + { + Data->PackageFullName = PhGetProcessPackageFullName(processHandleLimited); + } + + if (processHandleLimited) + NtClose(processHandleLimited); + + PhpQueueProcessQueryStage2(processItem); +} + +VOID PhpProcessQueryStage2( + _Inout_ PPH_PROCESS_QUERY_S2_DATA Data + ) +{ + NTSTATUS status; + PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem; + + if (PhEnableProcessQueryStage2 && processItem->FileName) + { + PPH_STRING packageFullName = NULL; + + if (processItem->QueryHandle) + packageFullName = PhGetProcessPackageFullName(processItem->QueryHandle); + + Data->VerifyResult = PhVerifyFileCached( + processItem->FileName, + PhGetString(packageFullName), + &Data->VerifySignerName, + FALSE + ); + + if (packageFullName) + PhDereferenceObject(packageFullName); + + status = PhIsExecutablePacked( + processItem->FileName->Buffer, + &Data->IsPacked, + &Data->ImportModules, + &Data->ImportFunctions + ); + + // If we got an image-related error, the image is packed. + if ( + status == STATUS_INVALID_IMAGE_NOT_MZ || + status == STATUS_INVALID_IMAGE_FORMAT || + status == STATUS_ACCESS_VIOLATION + ) + { + Data->IsPacked = TRUE; + Data->ImportModules = -1; + Data->ImportFunctions = -1; + } + } +} + +NTSTATUS PhpProcessQueryStage1Worker( + _In_ PVOID Parameter + ) +{ + PPH_PROCESS_QUERY_S1_DATA data; + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter; + + data = PhAllocate(sizeof(PH_PROCESS_QUERY_S1_DATA)); + memset(data, 0, sizeof(PH_PROCESS_QUERY_S1_DATA)); + data->Header.Stage = 1; + data->Header.ProcessItem = processItem; + + PhpProcessQueryStage1(data); + + RtlInterlockedPushEntrySList(&PhProcessQueryDataListHead, &data->Header.ListEntry); + + return STATUS_SUCCESS; +} + +NTSTATUS PhpProcessQueryStage2Worker( + _In_ PVOID Parameter + ) +{ + PPH_PROCESS_QUERY_S2_DATA data; + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)Parameter; + + data = PhAllocate(sizeof(PH_PROCESS_QUERY_S2_DATA)); + memset(data, 0, sizeof(PH_PROCESS_QUERY_S2_DATA)); + data->Header.Stage = 2; + data->Header.ProcessItem = processItem; + + PhpProcessQueryStage2(data); + + RtlInterlockedPushEntrySList(&PhProcessQueryDataListHead, &data->Header.ListEntry); + + return STATUS_SUCCESS; +} + +VOID PhpQueueProcessQueryStage1( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PH_WORK_QUEUE_ENVIRONMENT environment; + + // Ref: dereferenced when the provider update function removes the item from the queue. + PhReferenceObject(ProcessItem); + + PhInitializeWorkQueueEnvironment(&environment); + environment.BasePriority = THREAD_PRIORITY_BELOW_NORMAL; + + PhQueueItemWorkQueueEx(PhGetGlobalWorkQueue(), PhpProcessQueryStage1Worker, ProcessItem, + NULL, &environment); +} + +VOID PhpQueueProcessQueryStage2( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PH_WORK_QUEUE_ENVIRONMENT environment; + + if (!PhEnableProcessQueryStage2) + return; + + PhReferenceObject(ProcessItem); + + PhInitializeWorkQueueEnvironment(&environment); + environment.BasePriority = THREAD_PRIORITY_BELOW_NORMAL; + environment.IoPriority = IoPriorityVeryLow; + environment.PagePriority = MEMORY_PRIORITY_VERY_LOW; + + PhQueueItemWorkQueueEx(PhGetGlobalWorkQueue(), PhpProcessQueryStage2Worker, ProcessItem, + NULL, &environment); +} + +VOID PhpFillProcessItemStage1( + _In_ PPH_PROCESS_QUERY_S1_DATA Data + ) +{ + PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem; + + processItem->CommandLine = Data->CommandLine; + processItem->SmallIcon = Data->SmallIcon; + processItem->LargeIcon = Data->LargeIcon; + memcpy(&processItem->VersionInfo, &Data->VersionInfo, sizeof(PH_IMAGE_VERSION_INFO)); + processItem->ElevationType = Data->ElevationType; + processItem->IntegrityLevel = Data->IntegrityLevel; + processItem->IntegrityString = Data->IntegrityString; + processItem->JobName = Data->JobName; + processItem->ConsoleHostProcessId = Data->ConsoleHostProcessId; + processItem->PackageFullName = Data->PackageFullName; + processItem->IsDotNet = Data->IsDotNet; + processItem->IsElevated = Data->IsElevated; + processItem->IsInJob = Data->IsInJob; + processItem->IsInSignificantJob = Data->IsInSignificantJob; + processItem->IsWow64 = Data->IsWow64; + processItem->IsWow64Valid = Data->IsWow64Valid; + + PhSwapReference(&processItem->Record->CommandLine, processItem->CommandLine); +} + +VOID PhpFillProcessItemStage2( + _In_ PPH_PROCESS_QUERY_S2_DATA Data + ) +{ + PPH_PROCESS_ITEM processItem = Data->Header.ProcessItem; + + processItem->VerifyResult = Data->VerifyResult; + processItem->VerifySignerName = Data->VerifySignerName; + processItem->IsPacked = Data->IsPacked; + processItem->ImportFunctions = Data->ImportFunctions; + processItem->ImportModules = Data->ImportModules; +} + +VOID PhpFillProcessItem( + _Inout_ PPH_PROCESS_ITEM ProcessItem, + _In_ PSYSTEM_PROCESS_INFORMATION Process + ) +{ + NTSTATUS status; + HANDLE processHandle = NULL; + + ProcessItem->ParentProcessId = Process->InheritedFromUniqueProcessId; + ProcessItem->SessionId = Process->SessionId; + ProcessItem->CreateTime = Process->CreateTime; + + if (ProcessItem->ProcessId != SYSTEM_IDLE_PROCESS_ID) + ProcessItem->ProcessName = PhCreateStringFromUnicodeString(&Process->ImageName); + else + ProcessItem->ProcessName = PhCreateString(SYSTEM_IDLE_PROCESS_NAME); + + PhPrintUInt32(ProcessItem->ParentProcessIdString, HandleToUlong(ProcessItem->ParentProcessId)); + PhPrintUInt32(ProcessItem->SessionIdString, ProcessItem->SessionId); + + PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessItem->ProcessId); + + // Process information + { + // If we're dealing with System (PID 4), we need to get the + // kernel file name. Otherwise, get the image file name. + + if (ProcessItem->ProcessId != SYSTEM_PROCESS_ID) + { + PPH_STRING fileName; + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (processHandle) + { + PhGetProcessImageFileNameWin32(processHandle, &ProcessItem->FileName); + } + else + { + if (NT_SUCCESS(PhGetProcessImageFileNameByProcessId(ProcessItem->ProcessId, &fileName))) + { + ProcessItem->FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + } + } + } + else + { + if (processHandle && NT_SUCCESS(PhGetProcessImageFileName(processHandle, &fileName))) + { + ProcessItem->FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + } + } + } + else + { + PPH_STRING fileName; + + fileName = PhGetKernelFileName(); + + if (fileName) + { + ProcessItem->FileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + } + } + } + + // Token-related information + if ( + processHandle && + ProcessItem->ProcessId != SYSTEM_PROCESS_ID // Token of System process can't be opened sometimes + ) + { + HANDLE tokenHandle; + + status = PhOpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle); + + if (NT_SUCCESS(status)) + { + // User name + { + PTOKEN_USER user; + + status = PhGetTokenUser(tokenHandle, &user); + + if (NT_SUCCESS(status)) + { + ProcessItem->UserName = PhpGetSidFullNameCached(user->User.Sid); + PhFree(user); + } + } + + NtClose(tokenHandle); + } + } + else + { + if ( + ProcessItem->ProcessId == SYSTEM_IDLE_PROCESS_ID || + ProcessItem->ProcessId == SYSTEM_PROCESS_ID // System token can't be opened on XP + ) + { + PhSetReference(&ProcessItem->UserName, PhLocalSystemName); + } + } + + if (!ProcessItem->UserName && WindowsVersion <= WINDOWS_XP) + { + // In some cases we can get the user SID using WTS (only works on XP and below). + + if (!PhpTsProcesses) + { + WinStationGetAllProcesses( + NULL, + 0, + &PhpTsNumberOfProcesses, + &PhpTsProcesses + ); + } + + if (PhpTsProcesses) + { + ULONG i; + + for (i = 0; i < PhpTsNumberOfProcesses; i++) + { + if (UlongToHandle(PhpTsProcesses[i].pTsProcessInfo->UniqueProcessId) == ProcessItem->ProcessId) + { + ProcessItem->UserName = PhpGetSidFullNameCached(PhpTsProcesses[i].pSid); + break; + } + } + } + } + + NtClose(processHandle); +} + +FORCEINLINE VOID PhpUpdateDynamicInfoProcessItem( + _Inout_ PPH_PROCESS_ITEM ProcessItem, + _In_ PSYSTEM_PROCESS_INFORMATION Process + ) +{ + ProcessItem->BasePriority = Process->BasePriority; + + if (ProcessItem->QueryHandle) + { + PROCESS_PRIORITY_CLASS priorityClass; + + if (NT_SUCCESS(NtQueryInformationProcess( + ProcessItem->QueryHandle, + ProcessPriorityClass, + &priorityClass, + sizeof(PROCESS_PRIORITY_CLASS), + NULL + ))) + { + ProcessItem->PriorityClass = priorityClass.PriorityClass; + } + } + else + { + ProcessItem->PriorityClass = 0; + } + + ProcessItem->KernelTime = Process->KernelTime; + ProcessItem->UserTime = Process->UserTime; + ProcessItem->NumberOfHandles = Process->HandleCount; + ProcessItem->NumberOfThreads = Process->NumberOfThreads; + ProcessItem->WorkingSetPrivateSize = (SIZE_T)Process->WorkingSetPrivateSize.QuadPart; + ProcessItem->PeakNumberOfThreads = Process->NumberOfThreadsHighWatermark; + ProcessItem->HardFaultCount = Process->HardFaultCount; + + // Update VM and I/O counters. + ProcessItem->VmCounters = *(PVM_COUNTERS_EX)&Process->PeakVirtualSize; + ProcessItem->IoCounters = *(PIO_COUNTERS)&Process->ReadOperationCount; +} + +VOID PhpUpdatePerfInformation( + VOID + ) +{ + NtQuerySystemInformation( + SystemPerformanceInformation, + &PhPerfInformation, + sizeof(SYSTEM_PERFORMANCE_INFORMATION), + NULL + ); + + PhUpdateDelta(&PhIoReadDelta, PhPerfInformation.IoReadTransferCount.QuadPart); + PhUpdateDelta(&PhIoWriteDelta, PhPerfInformation.IoWriteTransferCount.QuadPart); + PhUpdateDelta(&PhIoOtherDelta, PhPerfInformation.IoOtherTransferCount.QuadPart); +} + +VOID PhpUpdateCpuInformation( + _In_ BOOLEAN SetCpuUsage, + _Out_ PULONG64 TotalTime + ) +{ + ULONG i; + ULONG64 totalTime; + + NtQuerySystemInformation( + SystemProcessorPerformanceInformation, + PhCpuInformation, + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * (ULONG)PhSystemBasicInformation.NumberOfProcessors, + NULL + ); + + // Zero the CPU totals. + memset(&PhCpuTotals, 0, sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)); + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION cpuInfo = + &PhCpuInformation[i]; + + // KernelTime includes IdleTime. + cpuInfo->KernelTime.QuadPart -= cpuInfo->IdleTime.QuadPart; + + PhCpuTotals.DpcTime.QuadPart += cpuInfo->DpcTime.QuadPart; + PhCpuTotals.IdleTime.QuadPart += cpuInfo->IdleTime.QuadPart; + PhCpuTotals.InterruptCount += cpuInfo->InterruptCount; + PhCpuTotals.InterruptTime.QuadPart += cpuInfo->InterruptTime.QuadPart; + PhCpuTotals.KernelTime.QuadPart += cpuInfo->KernelTime.QuadPart; + PhCpuTotals.UserTime.QuadPart += cpuInfo->UserTime.QuadPart; + + PhUpdateDelta(&PhCpusKernelDelta[i], cpuInfo->KernelTime.QuadPart); + PhUpdateDelta(&PhCpusUserDelta[i], cpuInfo->UserTime.QuadPart); + PhUpdateDelta(&PhCpusIdleDelta[i], cpuInfo->IdleTime.QuadPart); + + if (SetCpuUsage) + { + totalTime = PhCpusKernelDelta[i].Delta + PhCpusUserDelta[i].Delta + PhCpusIdleDelta[i].Delta; + + if (totalTime != 0) + { + PhCpusKernelUsage[i] = (FLOAT)PhCpusKernelDelta[i].Delta / totalTime; + PhCpusUserUsage[i] = (FLOAT)PhCpusUserDelta[i].Delta / totalTime; + } + else + { + PhCpusKernelUsage[i] = 0; + PhCpusUserUsage[i] = 0; + } + } + } + + PhUpdateDelta(&PhCpuKernelDelta, PhCpuTotals.KernelTime.QuadPart); + PhUpdateDelta(&PhCpuUserDelta, PhCpuTotals.UserTime.QuadPart); + PhUpdateDelta(&PhCpuIdleDelta, PhCpuTotals.IdleTime.QuadPart); + + totalTime = PhCpuKernelDelta.Delta + PhCpuUserDelta.Delta + PhCpuIdleDelta.Delta; + + if (SetCpuUsage) + { + if (totalTime != 0) + { + PhCpuKernelUsage = (FLOAT)PhCpuKernelDelta.Delta / totalTime; + PhCpuUserUsage = (FLOAT)PhCpuUserDelta.Delta / totalTime; + } + else + { + PhCpuKernelUsage = 0; + PhCpuUserUsage = 0; + } + } + + *TotalTime = totalTime; +} + +VOID PhpUpdateCpuCycleInformation( + _Out_ PULONG64 IdleCycleTime + ) +{ + ULONG i; + ULONG64 total; + + // Idle + + // We need to query this separately because the idle cycle time in SYSTEM_PROCESS_INFORMATION + // doesn't give us data for individual processors. + + NtQuerySystemInformation( + SystemProcessorIdleCycleTimeInformation, + PhCpuIdleCycleTime, + sizeof(LARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors, + NULL + ); + + total = 0; + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + //PhUpdateDelta(&PhCpusIdleCycleDelta[i], PhCpuIdleCycleTime[i].QuadPart); + total += PhCpuIdleCycleTime[i].QuadPart; + } + + PhUpdateDelta(&PhCpuIdleCycleDelta, total); + *IdleCycleTime = PhCpuIdleCycleDelta.Delta; + + // System + + NtQuerySystemInformation( + SystemProcessorCycleTimeInformation, + PhCpuSystemCycleTime, + sizeof(LARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors, + NULL + ); + + total = 0; + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + total += PhCpuSystemCycleTime[i].QuadPart; + } + + PhUpdateDelta(&PhCpuSystemCycleDelta, total); +} + +VOID PhpUpdateCpuCycleUsageInformation( + _In_ ULONG64 TotalCycleTime, + _In_ ULONG64 IdleCycleTime + ) +{ + ULONG i; + FLOAT baseCpuUsage; + FLOAT totalTimeDelta; + ULONG64 totalTime; + + // Cycle time is not only lacking for kernel/user components, but also for individual + // processors. We can get the total idle cycle time for individual processors but + // without knowing the total cycle time for individual processors, this information + // is useless. + // + // We'll start by calculating the total CPU usage, then we'll calculate the kernel/user + // components. In the event that the corresponding CPU time deltas are zero, we'll split + // the CPU usage evenly across the kernel/user components. CPU usage for individual + // processors is left untouched, because it's too difficult to provide an estimate. + // + // Let I_1, I_2, ..., I_n be the idle cycle times and T_1, T_2, ..., T_n be the + // total cycle times. Let I'_1, I'_2, ..., I'_n be the idle CPU times and T'_1, T'_2, ..., + // T'_n be the total CPU times. + // We know all I'_n, T'_n and I_n, but we only know sigma(T). The "real" total CPU usage is + // sigma(I)/sigma(T), and the "real" individual CPU usage is I_n/T_n. The problem is that + // we don't know T_n; we only know sigma(T). Hence we need to find values i_1, i_2, ..., i_n + // and t_1, t_2, ..., t_n such that: + // sigma(i)/sigma(t) ~= sigma(I)/sigma(T), and + // i_n/t_n ~= I_n/T_n + // + // Solution 1: Set i_n = I_n and t_n = sigma(T)*T'_n/sigma(T'). Then: + // sigma(i)/sigma(t) = sigma(I)/(sigma(T)*sigma(T')/sigma(T')) = sigma(I)/sigma(T), and + // i_n/t_n = I_n/T'_n*sigma(T')/sigma(T) ~= I_n/T_n since I_n/T'_n ~= I_n/T_n and sigma(T')/sigma(T) ~= 1. + // However, it is not guaranteed that i_n/t_n <= 1, which may lead to CPU usages over 100% being displayed. + // + // Solution 2: Set i_n = I'_n and t_n = T'_n. Then: + // sigma(i)/sigma(t) = sigma(I')/sigma(T') ~= sigma(I)/sigma(T) since I'_n ~= I_n and T'_n ~= T_n. + // i_n/t_n = I'_n/T'_n ~= I_n/T_n as above. + // Not scaling at all is currently the best solution, since it's fast, simple and guarantees that i_n/t_n <= 1. + + baseCpuUsage = 1 - (FLOAT)IdleCycleTime / TotalCycleTime; + totalTimeDelta = (FLOAT)(PhCpuKernelDelta.Delta + PhCpuUserDelta.Delta); + + if (totalTimeDelta != 0) + { + PhCpuKernelUsage = baseCpuUsage * ((FLOAT)PhCpuKernelDelta.Delta / totalTimeDelta); + PhCpuUserUsage = baseCpuUsage * ((FLOAT)PhCpuUserDelta.Delta / totalTimeDelta); + } + else + { + PhCpuKernelUsage = baseCpuUsage / 2; + PhCpuUserUsage = baseCpuUsage / 2; + } + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + totalTime = PhCpusKernelDelta[i].Delta + PhCpusUserDelta[i].Delta + PhCpusIdleDelta[i].Delta; + + if (totalTime != 0) + { + PhCpusKernelUsage[i] = (FLOAT)PhCpusKernelDelta[i].Delta / totalTime; + PhCpusUserUsage[i] = (FLOAT)PhCpusUserDelta[i].Delta / totalTime; + } + else + { + PhCpusKernelUsage[i] = 0; + PhCpusUserUsage[i] = 0; + } + } +} + +VOID PhpInitializeProcessStatistics( + VOID + ) +{ + ULONG i; + + PhInitializeCircularBuffer_ULONG(&PhTimeHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_FLOAT(&PhCpuKernelHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_FLOAT(&PhCpuUserHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&PhIoReadHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&PhIoWriteHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&PhIoOtherHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG(&PhCommitHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG(&PhPhysicalHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG(&PhMaxCpuHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG(&PhMaxIoHistory, PhStatisticsSampleCount); +#ifdef PH_RECORD_MAX_USAGE + PhInitializeCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, PhStatisticsSampleCount); + PhInitializeCircularBuffer_ULONG64(&PhMaxIoWriteHistory, PhStatisticsSampleCount); +#endif + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + PhInitializeCircularBuffer_FLOAT(&PhCpusKernelHistory[i], PhStatisticsSampleCount); + PhInitializeCircularBuffer_FLOAT(&PhCpusUserHistory[i], PhStatisticsSampleCount); + } +} + +VOID PhpUpdateSystemHistory( + VOID + ) +{ + ULONG i; + LARGE_INTEGER systemTime; + ULONG secondsSince1980; + + // CPU + PhAddItemCircularBuffer_FLOAT(&PhCpuKernelHistory, PhCpuKernelUsage); + PhAddItemCircularBuffer_FLOAT(&PhCpuUserHistory, PhCpuUserUsage); + + // CPUs + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + { + PhAddItemCircularBuffer_FLOAT(&PhCpusKernelHistory[i], PhCpusKernelUsage[i]); + PhAddItemCircularBuffer_FLOAT(&PhCpusUserHistory[i], PhCpusUserUsage[i]); + } + + // I/O + PhAddItemCircularBuffer_ULONG64(&PhIoReadHistory, PhIoReadDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&PhIoWriteHistory, PhIoWriteDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&PhIoOtherHistory, PhIoOtherDelta.Delta); + + // Memory + PhAddItemCircularBuffer_ULONG(&PhCommitHistory, PhPerfInformation.CommittedPages); + PhAddItemCircularBuffer_ULONG(&PhPhysicalHistory, + PhSystemBasicInformation.NumberOfPhysicalPages - PhPerfInformation.AvailablePages + ); + + // Time + PhQuerySystemTime(&systemTime); + RtlTimeToSecondsSince1980(&systemTime, &secondsSince1980); + PhAddItemCircularBuffer_ULONG(&PhTimeHistory, secondsSince1980); +} + +/** + * Retrieves a time value recorded by the statistics system. + * + * \param ProcessItem A process item to synchronize with, or NULL if no synchronization is + * necessary. + * \param Index The history index. + * \param Time A variable which receives the time at \a Index. + * + * \return TRUE if the function succeeded, otherwise FALSE if \a ProcessItem was specified and + * \a Index is too far into the past for that process item. + */ +BOOLEAN PhGetStatisticsTime( + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Index, + _Out_ PLARGE_INTEGER Time + ) +{ + ULONG secondsSince1980; + ULONG index; + LARGE_INTEGER time; + + if (ProcessItem) + { + // The sequence number is used to synchronize statistics when a process exits, since that + // process' history is not updated anymore. + index = PhTimeSequenceNumber - ProcessItem->SequenceNumber + Index; + + if (index >= PhTimeHistory.Count) + { + // The data point is too far into the past. + return FALSE; + } + } + else + { + // Assume the index is valid. + index = Index; + } + + secondsSince1980 = PhGetItemCircularBuffer_ULONG(&PhTimeHistory, index); + RtlSecondsSince1980ToTime(secondsSince1980, &time); + + *Time = time; + + return TRUE; +} + +PPH_STRING PhGetStatisticsTimeString( + _In_opt_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Index + ) +{ + LARGE_INTEGER time; + SYSTEMTIME systemTime; + + if (PhGetStatisticsTime(ProcessItem, Index, &time)) + { + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + + return PhFormatDateTime(&systemTime); + } + else + { + return PhCreateString(L"Unknown time"); + } +} + +VOID PhFlushProcessQueryData( + _In_ BOOLEAN SendModifiedEvent + ) +{ + PSLIST_ENTRY entry; + PPH_PROCESS_QUERY_DATA data; + BOOLEAN processed; + + if (!RtlFirstEntrySList(&PhProcessQueryDataListHead)) + return; + + entry = RtlInterlockedFlushSList(&PhProcessQueryDataListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_PROCESS_QUERY_DATA, ListEntry); + entry = entry->Next; + processed = FALSE; + + if (data->Stage == 1) + { + PhpFillProcessItemStage1((PPH_PROCESS_QUERY_S1_DATA)data); + PhSetEvent(&data->ProcessItem->Stage1Event); + processed = TRUE; + } + else if (data->Stage == 2) + { + PhpFillProcessItemStage2((PPH_PROCESS_QUERY_S2_DATA)data); + processed = TRUE; + } + + if (processed) + { + // Invoke the modified event only if the main provider has sent the added event already. + if (SendModifiedEvent && data->ProcessItem->AddedEventSent) + { + // Since this may be executing on a thread other than the main provider thread, we + // need to check whether the process has been removed already. If we don't do this + // then users may get a modified event after a removed event for the same process, + // which will lead to very bad things happening. + PhAcquireQueuedLockExclusive(&data->ProcessItem->RemoveLock); + if (!(data->ProcessItem->State & PH_PROCESS_ITEM_REMOVED)) + PhInvokeCallback(&PhProcessModifiedEvent, data->ProcessItem); + PhReleaseQueuedLockExclusive(&data->ProcessItem->RemoveLock); + } + else + { + data->ProcessItem->JustProcessed = 1; + } + } + + PhDereferenceObject(data->ProcessItem); + PhFree(data); + } +} + +VOID PhpGetProcessThreadInformation( + _In_ PSYSTEM_PROCESS_INFORMATION Process, + _Out_opt_ PBOOLEAN IsSuspended, + _Out_opt_ PBOOLEAN IsPartiallySuspended, + _Out_opt_ PULONG ContextSwitches + ) +{ + ULONG i; + BOOLEAN isSuspended; + BOOLEAN isPartiallySuspended; + ULONG contextSwitches; + + isSuspended = PH_IS_REAL_PROCESS_ID(Process->UniqueProcessId); + isPartiallySuspended = FALSE; + contextSwitches = 0; + + for (i = 0; i < Process->NumberOfThreads; i++) + { + if (Process->Threads[i].ThreadState != Waiting || + Process->Threads[i].WaitReason != Suspended) + { + isSuspended = FALSE; + } + else + { + isPartiallySuspended = TRUE; + } + + contextSwitches += Process->Threads[i].ContextSwitches; + } + + if (IsSuspended) + *IsSuspended = isSuspended; + if (IsPartiallySuspended) + *IsPartiallySuspended = isPartiallySuspended; + if (ContextSwitches) + *ContextSwitches = contextSwitches; +} + +VOID PhProcessProviderUpdate( + _In_ PVOID Object + ) +{ + static ULONG runCount = 0; + static PSYSTEM_PROCESS_INFORMATION pidBuckets[PROCESS_ID_BUCKETS]; + + // Note about locking: + // + // Since this is the only function that is allowed to modify the process hashtable, locking is + // not needed for shared accesses. However, exclusive accesses need locking. + + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + ULONG bucketIndex; + + BOOLEAN isCycleCpuUsageEnabled = FALSE; + + ULONG64 sysTotalTime; // total time for this update period + ULONG64 sysTotalCycleTime = 0; // total cycle time for this update period + ULONG64 sysIdleCycleTime = 0; // total idle cycle time for this update period + FLOAT maxCpuValue = 0; + PPH_PROCESS_ITEM maxCpuProcessItem = NULL; + ULONG64 maxIoValue = 0; + PPH_PROCESS_ITEM maxIoProcessItem = NULL; + + // Pre-update tasks + + if (runCount % 5 == 0) + { + PhUpdateDosDevicePrefixes(); + } + + if (runCount % 512 == 0) // yes, a very long time + { + if (PhEnablePurgeProcessRecords) + PhPurgeProcessRecords(); + } + + isCycleCpuUsageEnabled = WindowsVersion >= WINDOWS_7 && PhEnableCycleCpuUsage; + + if (!PhProcessStatisticsInitialized) + { + PhpInitializeProcessStatistics(); + PhProcessStatisticsInitialized = TRUE; + } + + PhpUpdatePerfInformation(); + + if (isCycleCpuUsageEnabled) + { + PhpUpdateCpuInformation(FALSE, &sysTotalTime); + PhpUpdateCpuCycleInformation(&sysIdleCycleTime); + } + else + { + PhpUpdateCpuInformation(TRUE, &sysTotalTime); + } + + if (runCount != 0) + { + PhTimeSequenceNumber++; + } + + // Get the process list. + + PhTotalProcesses = 0; + PhTotalThreads = 0; + PhTotalHandles = 0; + + if (!NT_SUCCESS(PhEnumProcesses(&processes))) + return; + + // Notes on cycle-based CPU usage: + // + // Cycle-based CPU usage is a bit tricky to calculate because we cannot get the total number of + // cycles consumed by all processes since system startup - we can only get total number of + // cycles per process. This means there are two ways to calculate the system-wide cycle time + // delta: + // + // 1. Each update, sum the cycle times of all processes, and calculate the system-wide delta + // from this. Process Explorer seems to do this. + // 2. Each update, calculate the cycle time delta for each individual process, and sum these + // deltas to create the system-wide delta. We use this here. + // + // The first method is simpler but has a problem when a process exits and its cycle time is no + // longer counted in the system-wide total. This may cause the delta to be negative and all + // other calculations to become invalid. Process Explorer simply ignored this fact and treated + // the system-wide delta as unsigned (and therefore huge when negative), leading to all CPU + // usages being displayed as "< 0.01". + // + // The second method is used here, but the adjustments must be done before the main new/modified + // pass. We need take into account new, existing and terminated processes. + + // Create the PID hash set. This contains the process information structures returned by + // PhEnumProcesses, distinct from the process item hash set. Note that we use the + // UniqueProcessKey field as the next node pointer to avoid having to allocate extra memory. + + memset(pidBuckets, 0, sizeof(pidBuckets)); + + process = PH_FIRST_PROCESS(processes); + + do + { + PhTotalProcesses++; + PhTotalThreads += process->NumberOfThreads; + PhTotalHandles += process->HandleCount; + + if (process->UniqueProcessId == SYSTEM_IDLE_PROCESS_ID) + { + process->CycleTime = PhCpuIdleCycleDelta.Value; + process->KernelTime = PhCpuTotals.IdleTime; + } + + bucketIndex = PROCESS_ID_TO_BUCKET_INDEX(process->UniqueProcessId); + process->UniqueProcessKey = (ULONG_PTR)pidBuckets[bucketIndex]; + pidBuckets[bucketIndex] = process; + + if (isCycleCpuUsageEnabled) + { + PPH_PROCESS_ITEM processItem; + + if ((processItem = PhpLookupProcessItem(process->UniqueProcessId)) && processItem->CreateTime.QuadPart == process->CreateTime.QuadPart) + sysTotalCycleTime += process->CycleTime - processItem->CycleTimeDelta.Value; // existing process + else + sysTotalCycleTime += process->CycleTime; // new process + } + } while (process = PH_NEXT_PROCESS(process)); + + // Add the fake processes to the PID list. + // + // On Windows 7 the two fake processes are merged into "Interrupts" since we can only get cycle + // time information both DPCs and Interrupts combined. + + if (isCycleCpuUsageEnabled) + { + PhInterruptsProcessInformation.KernelTime.QuadPart = PhCpuTotals.DpcTime.QuadPart + PhCpuTotals.InterruptTime.QuadPart; + PhInterruptsProcessInformation.CycleTime = PhCpuSystemCycleDelta.Value; + sysTotalCycleTime += PhCpuSystemCycleDelta.Delta; + } + else + { + PhDpcsProcessInformation.KernelTime = PhCpuTotals.DpcTime; + PhInterruptsProcessInformation.KernelTime = PhCpuTotals.InterruptTime; + } + + // Look for dead processes. + { + PPH_LIST processesToRemove = NULL; + ULONG i; + PPH_HASH_ENTRY entry; + PPH_PROCESS_ITEM processItem; + PSYSTEM_PROCESS_INFORMATION processEntry; + + for (i = 0; i < PH_HASH_SET_SIZE(PhProcessHashSet); i++) + { + for (entry = PhProcessHashSet[i]; entry; entry = entry->Next) + { + processItem = CONTAINING_RECORD(entry, PH_PROCESS_ITEM, HashEntry); + + // Check if the process still exists. Note that we take into account PID re-use by + // checking CreateTime as well. + + if (processItem->ProcessId == DPCS_PROCESS_ID) + { + processEntry = &PhDpcsProcessInformation; + } + else if (processItem->ProcessId == INTERRUPTS_PROCESS_ID) + { + processEntry = &PhInterruptsProcessInformation; + } + else + { + processEntry = pidBuckets[PROCESS_ID_TO_BUCKET_INDEX(processItem->ProcessId)]; + + while (processEntry && processEntry->UniqueProcessId != processItem->ProcessId) + processEntry = (PSYSTEM_PROCESS_INFORMATION)processEntry->UniqueProcessKey; + } + + if (!processEntry || processEntry->CreateTime.QuadPart != processItem->CreateTime.QuadPart) + { + LARGE_INTEGER exitTime; + + processItem->State |= PH_PROCESS_ITEM_REMOVED; + exitTime.QuadPart = 0; + + if (processItem->QueryHandle) + { + KERNEL_USER_TIMES times; + ULONG64 finalCycleTime; + + if (NT_SUCCESS(PhGetProcessTimes(processItem->QueryHandle, ×))) + { + exitTime = times.ExitTime; + } + + if (isCycleCpuUsageEnabled) + { + if (NT_SUCCESS(PhGetProcessCycleTime(processItem->QueryHandle, &finalCycleTime))) + { + // Adjust deltas for the terminated process because this doesn't get + // picked up anywhere else. + // + // Note that if we don't have sufficient access to the process, the + // worst that will happen is that the CPU usages of other processes + // will get inflated. (See above; if we were using the first + // technique, we could get negative deltas, which is much worse.) + sysTotalCycleTime += finalCycleTime - processItem->CycleTimeDelta.Value; + } + } + } + + // If we don't have a valid exit time, use the current time. + if (exitTime.QuadPart == 0) + PhQuerySystemTime(&exitTime); + + processItem->Record->Flags |= PH_PROCESS_RECORD_DEAD; + processItem->Record->ExitTime = exitTime; + + // Raise the process removed event. + // See PhFlushProcessQueryData for why we need to lock here. + PhAcquireQueuedLockExclusive(&processItem->RemoveLock); + PhInvokeCallback(&PhProcessRemovedEvent, processItem); + PhReleaseQueuedLockExclusive(&processItem->RemoveLock); + + if (!processesToRemove) + processesToRemove = PhCreateList(2); + + PhAddItemList(processesToRemove, processItem); + } + } + } + + // Lock only if we have something to do. + if (processesToRemove) + { + PhAcquireQueuedLockExclusive(&PhProcessHashSetLock); + + for (i = 0; i < processesToRemove->Count; i++) + { + PhpRemoveProcessItem((PPH_PROCESS_ITEM)processesToRemove->Items[i]); + } + + PhReleaseQueuedLockExclusive(&PhProcessHashSetLock); + PhDereferenceObject(processesToRemove); + } + } + + // Go through the queued process query data. + PhFlushProcessQueryData(FALSE); + + if (sysTotalTime == 0) + sysTotalTime = -1; // max. value + if (sysTotalCycleTime == 0) + sysTotalCycleTime = -1; + + PhCpuTotalCycleDelta = sysTotalCycleTime; + + // Look for new processes and update existing ones. + process = PH_FIRST_PROCESS(processes); + + while (process) + { + PPH_PROCESS_ITEM processItem; + + processItem = PhpLookupProcessItem(process->UniqueProcessId); + + if (!processItem) + { + PPH_PROCESS_RECORD processRecord; + BOOLEAN isSuspended; + BOOLEAN isPartiallySuspended; + ULONG contextSwitches; + + // Create the process item and fill in basic information. + processItem = PhCreateProcessItem(process->UniqueProcessId); + PhpFillProcessItem(processItem, process); + processItem->SequenceNumber = PhTimeSequenceNumber; + + processRecord = PhpCreateProcessRecord(processItem); + PhpAddProcessRecord(processRecord); + processItem->Record = processRecord; + + // Open a handle to the process for later usage. + // + // Don't try to do this if the process has no threads. On Windows 8.1, processes without + // threads are probably reflected processes which will not terminate if we have a handle + // open. + if (process->NumberOfThreads != 0) + { + PhOpenProcess(&processItem->QueryHandle, PROCESS_QUERY_INFORMATION, processItem->ProcessId); + + if (WINDOWS_HAS_LIMITED_ACCESS && !processItem->QueryHandle) + PhOpenProcess(&processItem->QueryHandle, PROCESS_QUERY_LIMITED_INFORMATION, processItem->ProcessId); + } + + PhpGetProcessThreadInformation(process, &isSuspended, &isPartiallySuspended, &contextSwitches); + PhpUpdateDynamicInfoProcessItem(processItem, process); + + // Initialize the deltas. + PhUpdateDelta(&processItem->CpuKernelDelta, process->KernelTime.QuadPart); + PhUpdateDelta(&processItem->CpuUserDelta, process->UserTime.QuadPart); + PhUpdateDelta(&processItem->IoReadDelta, process->ReadTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoWriteDelta, process->WriteTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoOtherDelta, process->OtherTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoReadCountDelta, process->ReadOperationCount.QuadPart); + PhUpdateDelta(&processItem->IoWriteCountDelta, process->WriteOperationCount.QuadPart); + PhUpdateDelta(&processItem->IoOtherCountDelta, process->OtherOperationCount.QuadPart); + PhUpdateDelta(&processItem->ContextSwitchesDelta, contextSwitches); + PhUpdateDelta(&processItem->PageFaultsDelta, process->PageFaultCount); + PhUpdateDelta(&processItem->CycleTimeDelta, process->CycleTime); + PhUpdateDelta(&processItem->PrivateBytesDelta, process->PagefileUsage); + + processItem->IsSuspended = isSuspended; + processItem->IsPartiallySuspended = isPartiallySuspended; + + // If this is the first run of the provider, queue the + // process query tasks. Otherwise, perform stage 1 + // processing now and queue stage 2 processing. + if (runCount > 0) + { + PH_PROCESS_QUERY_S1_DATA data; + + memset(&data, 0, sizeof(PH_PROCESS_QUERY_S1_DATA)); + data.Header.Stage = 1; + data.Header.ProcessItem = processItem; + PhpProcessQueryStage1(&data); + PhpFillProcessItemStage1(&data); + PhSetEvent(&processItem->Stage1Event); + } + else + { + PhpQueueProcessQueryStage1(processItem); + } + + // Add pending service items to the process item. + PhUpdateProcessItemServices(processItem); + + // Add the process item to the hashtable. + PhAcquireQueuedLockExclusive(&PhProcessHashSetLock); + PhpAddProcessItem(processItem); + PhReleaseQueuedLockExclusive(&PhProcessHashSetLock); + + // Raise the process added event. + PhInvokeCallback(&PhProcessAddedEvent, processItem); + processItem->AddedEventSent = TRUE; + + // (Ref: for the process item being in the hashtable.) + // Instead of referencing then dereferencing we simply don't do anything. + // Dereferenced in PhpRemoveProcessItem. + } + else + { + BOOLEAN modified = FALSE; + BOOLEAN isSuspended; + BOOLEAN isPartiallySuspended; + ULONG contextSwitches; + FLOAT newCpuUsage; + FLOAT kernelCpuUsage; + FLOAT userCpuUsage; + + PhpGetProcessThreadInformation(process, &isSuspended, &isPartiallySuspended, &contextSwitches); + PhpUpdateDynamicInfoProcessItem(processItem, process); + + // Update the deltas. + PhUpdateDelta(&processItem->CpuKernelDelta, process->KernelTime.QuadPart); + PhUpdateDelta(&processItem->CpuUserDelta, process->UserTime.QuadPart); + PhUpdateDelta(&processItem->IoReadDelta, process->ReadTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoWriteDelta, process->WriteTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoOtherDelta, process->OtherTransferCount.QuadPart); + PhUpdateDelta(&processItem->IoReadCountDelta, process->ReadOperationCount.QuadPart); + PhUpdateDelta(&processItem->IoWriteCountDelta, process->WriteOperationCount.QuadPart); + PhUpdateDelta(&processItem->IoOtherCountDelta, process->OtherOperationCount.QuadPart); + PhUpdateDelta(&processItem->ContextSwitchesDelta, contextSwitches); + PhUpdateDelta(&processItem->PageFaultsDelta, process->PageFaultCount); + PhUpdateDelta(&processItem->CycleTimeDelta, process->CycleTime); + PhUpdateDelta(&processItem->PrivateBytesDelta, process->PagefileUsage); + + processItem->SequenceNumber++; + PhAddItemCircularBuffer_ULONG64(&processItem->IoReadHistory, processItem->IoReadDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, processItem->IoWriteDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, processItem->IoOtherDelta.Delta); + + PhAddItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, processItem->VmCounters.PagefileUsage); + //PhAddItemCircularBuffer_SIZE_T(&processItem->WorkingSetHistory, processItem->VmCounters.WorkingSetSize); + + if (InterlockedExchange(&processItem->JustProcessed, 0) != 0) + modified = TRUE; + + if (isCycleCpuUsageEnabled) + { + FLOAT totalDelta; + + newCpuUsage = (FLOAT)processItem->CycleTimeDelta.Delta / sysTotalCycleTime; + + // Calculate the kernel/user CPU usage based on the kernel/user time. If the kernel + // and user deltas are both zero, we'll just have to use an estimate. Currently, we + // split the CPU usage evenly across the kernel and user components, except when the + // total user time is zero, in which case we assign it all to the kernel component. + + totalDelta = (FLOAT)(processItem->CpuKernelDelta.Delta + processItem->CpuUserDelta.Delta); + + if (totalDelta != 0) + { + kernelCpuUsage = newCpuUsage * ((FLOAT)processItem->CpuKernelDelta.Delta / totalDelta); + userCpuUsage = newCpuUsage * ((FLOAT)processItem->CpuUserDelta.Delta / totalDelta); + } + else + { + if (processItem->UserTime.QuadPart != 0) + { + kernelCpuUsage = newCpuUsage / 2; + userCpuUsage = newCpuUsage / 2; + } + else + { + kernelCpuUsage = newCpuUsage; + userCpuUsage = 0; + } + } + } + else + { + kernelCpuUsage = (FLOAT)processItem->CpuKernelDelta.Delta / sysTotalTime; + userCpuUsage = (FLOAT)processItem->CpuUserDelta.Delta / sysTotalTime; + newCpuUsage = kernelCpuUsage + userCpuUsage; + } + + processItem->CpuUsage = newCpuUsage; + processItem->CpuKernelUsage = kernelCpuUsage; + processItem->CpuUserUsage = userCpuUsage; + + PhAddItemCircularBuffer_FLOAT(&processItem->CpuKernelHistory, kernelCpuUsage); + PhAddItemCircularBuffer_FLOAT(&processItem->CpuUserHistory, userCpuUsage); + + // Max. values + + if (processItem->ProcessId != NULL) + { + if (maxCpuValue < newCpuUsage) + { + maxCpuValue = newCpuUsage; + maxCpuProcessItem = processItem; + } + + // I/O for Other is not included because it is too generic. + if (maxIoValue < processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta) + { + maxIoValue = processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta; + maxIoProcessItem = processItem; + } + } + + // Debugged + if (processItem->QueryHandle) + { + BOOLEAN isBeingDebugged; + + if (NT_SUCCESS(PhGetProcessIsBeingDebugged( + processItem->QueryHandle, + &isBeingDebugged + )) && processItem->IsBeingDebugged != isBeingDebugged) + { + processItem->IsBeingDebugged = isBeingDebugged; + modified = TRUE; + } + } + + // Suspended + if (processItem->IsSuspended != isSuspended) + { + processItem->IsSuspended = isSuspended; + modified = TRUE; + } + + processItem->IsPartiallySuspended = isPartiallySuspended; + + // .NET + if (processItem->UpdateIsDotNet) + { + BOOLEAN isDotNet; + + if (NT_SUCCESS(PhGetProcessIsDotNet(processItem->ProcessId, &isDotNet))) + { + processItem->IsDotNet = isDotNet; + modified = TRUE; + } + + processItem->UpdateIsDotNet = FALSE; + } + + // Immersive + if (processItem->QueryHandle && IsImmersiveProcess_I) + { + BOOLEAN isImmersive; + + isImmersive = !!IsImmersiveProcess_I(processItem->QueryHandle); + + if (processItem->IsImmersive != isImmersive) + { + processItem->IsImmersive = isImmersive; + modified = TRUE; + } + } + + if (modified) + { + PhInvokeCallback(&PhProcessModifiedEvent, processItem); + } + + // No reference added by PhpLookupProcessItem. + } + + // Trick ourselves into thinking that the fake processes + // are on the list. + if (process == &PhInterruptsProcessInformation) + { + process = NULL; + } + else if (process == &PhDpcsProcessInformation) + { + process = &PhInterruptsProcessInformation; + } + else + { + process = PH_NEXT_PROCESS(process); + + if (process == NULL) + { + if (isCycleCpuUsageEnabled) + process = &PhInterruptsProcessInformation; + else + process = &PhDpcsProcessInformation; + } + } + } + + if (PhProcessInformation) + PhFree(PhProcessInformation); + + PhProcessInformation = processes; + + if (PhpTsProcesses) + { + WinStationFreeGAPMemory(0, PhpTsProcesses, PhpTsNumberOfProcesses); + PhpTsProcesses = NULL; + } + + PhpFlushSidFullNameCache(); + + // History cannot be updated on the first run because the deltas are invalid. For example, the + // I/O "deltas" will be huge because they are currently the raw accumulated values. + if (runCount != 0) + { + if (isCycleCpuUsageEnabled) + PhpUpdateCpuCycleUsageInformation(sysTotalCycleTime, sysIdleCycleTime); + + PhpUpdateSystemHistory(); + + // Note that we need to add a reference to the records of these processes, to make it + // possible for others to get the name of a max. CPU or I/O process. + + if (maxCpuProcessItem) + { + PhAddItemCircularBuffer_ULONG(&PhMaxCpuHistory, HandleToUlong(maxCpuProcessItem->ProcessId)); +#ifdef PH_RECORD_MAX_USAGE + PhAddItemCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, maxCpuProcessItem->CpuUsage); +#endif + + if (!(maxCpuProcessItem->Record->Flags & PH_PROCESS_RECORD_STAT_REF)) + { + PhReferenceProcessRecord(maxCpuProcessItem->Record); + maxCpuProcessItem->Record->Flags |= PH_PROCESS_RECORD_STAT_REF; + } + } + else + { + PhAddItemCircularBuffer_ULONG(&PhMaxCpuHistory, PtrToUlong(NULL)); +#ifdef PH_RECORD_MAX_USAGE + PhAddItemCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, 0); +#endif + } + + if (maxIoProcessItem) + { + PhAddItemCircularBuffer_ULONG(&PhMaxIoHistory, HandleToUlong(maxIoProcessItem->ProcessId)); +#ifdef PH_RECORD_MAX_USAGE + PhAddItemCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, + maxIoProcessItem->IoReadDelta.Delta + maxIoProcessItem->IoOtherDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&PhMaxIoWriteHistory, maxIoProcessItem->IoWriteDelta.Delta); +#endif + + if (!(maxIoProcessItem->Record->Flags & PH_PROCESS_RECORD_STAT_REF)) + { + PhReferenceProcessRecord(maxIoProcessItem->Record); + maxIoProcessItem->Record->Flags |= PH_PROCESS_RECORD_STAT_REF; + } + } + else + { + PhAddItemCircularBuffer_ULONG(&PhMaxIoHistory, PtrToUlong(NULL)); +#ifdef PH_RECORD_MAX_USAGE + PhAddItemCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, 0); + PhAddItemCircularBuffer_ULONG64(&PhMaxIoWriteHistory, 0); +#endif + } + } + + PhInvokeCallback(&PhProcessesUpdatedEvent, NULL); + runCount++; +} + +PPH_PROCESS_RECORD PhpCreateProcessRecord( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PPH_PROCESS_RECORD processRecord; + + processRecord = PhAllocate(sizeof(PH_PROCESS_RECORD)); + memset(processRecord, 0, sizeof(PH_PROCESS_RECORD)); + + InitializeListHead(&processRecord->ListEntry); + processRecord->RefCount = 1; + + processRecord->ProcessId = ProcessItem->ProcessId; + processRecord->ParentProcessId = ProcessItem->ParentProcessId; + processRecord->SessionId = ProcessItem->SessionId; + processRecord->CreateTime = ProcessItem->CreateTime; + + PhSetReference(&processRecord->ProcessName, ProcessItem->ProcessName); + PhSetReference(&processRecord->FileName, ProcessItem->FileName); + PhSetReference(&processRecord->CommandLine, ProcessItem->CommandLine); + //PhSetReference(&processRecord->UserName, ProcessItem->UserName); + + return processRecord; +} + +PPH_PROCESS_RECORD PhpSearchProcessRecordList( + _In_ PLARGE_INTEGER Time, + _Out_opt_ PULONG Index, + _Out_opt_ PULONG InsertIndex + ) +{ + PPH_PROCESS_RECORD processRecord; + LONG low; + LONG high; + LONG i; + BOOLEAN found = FALSE; + INT comparison; + + if (PhProcessRecordList->Count == 0) + { + if (Index) + *Index = 0; + if (InsertIndex) + *InsertIndex = 0; + + return NULL; + } + + low = 0; + high = PhProcessRecordList->Count - 1; + + // Do a binary search. + do + { + i = (low + high) / 2; + processRecord = (PPH_PROCESS_RECORD)PhProcessRecordList->Items[i]; + + comparison = uint64cmp(Time->QuadPart, processRecord->CreateTime.QuadPart); + + if (comparison == 0) + { + found = TRUE; + break; + } + else if (comparison < 0) + { + high = i - 1; + } + else + { + low = i + 1; + } + } while (low <= high); + + if (Index) + *Index = i; + + if (found) + { + return processRecord; + } + else + { + if (InsertIndex) + *InsertIndex = i + (comparison > 0); + + return NULL; + } +} + +VOID PhpAddProcessRecord( + _Inout_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + PPH_PROCESS_RECORD processRecord; + ULONG insertIndex; + + PhAcquireQueuedLockExclusive(&PhProcessRecordListLock); + + processRecord = PhpSearchProcessRecordList(&ProcessRecord->CreateTime, NULL, &insertIndex); + + if (!processRecord) + { + // Insert the process record, keeping the list sorted. + PhInsertItemList(PhProcessRecordList, insertIndex, ProcessRecord); + } + else + { + // Link the process record with the existing one that we found. + InsertTailList(&processRecord->ListEntry, &ProcessRecord->ListEntry); + } + + PhReleaseQueuedLockExclusive(&PhProcessRecordListLock); +} + +VOID PhpRemoveProcessRecord( + _Inout_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + ULONG i; + PPH_PROCESS_RECORD headProcessRecord; + + PhAcquireQueuedLockExclusive(&PhProcessRecordListLock); + + headProcessRecord = PhpSearchProcessRecordList(&ProcessRecord->CreateTime, &i, NULL); + assert(headProcessRecord); + + // Unlink the record from the list. + RemoveEntryList(&ProcessRecord->ListEntry); + + if (ProcessRecord == headProcessRecord) + { + // Remove the slot completely, or choose a new head record. + if (IsListEmpty(&headProcessRecord->ListEntry)) + PhRemoveItemList(PhProcessRecordList, i); + else + PhProcessRecordList->Items[i] = CONTAINING_RECORD(headProcessRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry); + } + + PhReleaseQueuedLockExclusive(&PhProcessRecordListLock); +} + +VOID PhReferenceProcessRecord( + _In_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + _InterlockedIncrement(&ProcessRecord->RefCount); +} + +BOOLEAN PhReferenceProcessRecordSafe( + _In_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + return _InterlockedIncrementNoZero(&ProcessRecord->RefCount); +} + +VOID PhReferenceProcessRecordForStatistics( + _In_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + if (!(ProcessRecord->Flags & PH_PROCESS_RECORD_STAT_REF)) + { + PhReferenceProcessRecord(ProcessRecord); + ProcessRecord->Flags |= PH_PROCESS_RECORD_STAT_REF; + } +} + +VOID PhDereferenceProcessRecord( + _In_ PPH_PROCESS_RECORD ProcessRecord + ) +{ + if (_InterlockedDecrement(&ProcessRecord->RefCount) == 0) + { + PhpRemoveProcessRecord(ProcessRecord); + + PhDereferenceObject(ProcessRecord->ProcessName); + if (ProcessRecord->FileName) PhDereferenceObject(ProcessRecord->FileName); + if (ProcessRecord->CommandLine) PhDereferenceObject(ProcessRecord->CommandLine); + /*if (ProcessRecord->UserName) PhDereferenceObject(ProcessRecord->UserName);*/ + PhFree(ProcessRecord); + } +} + +PPH_PROCESS_RECORD PhpFindProcessRecord( + _In_ PPH_PROCESS_RECORD ProcessRecord, + _In_ HANDLE ProcessId + ) +{ + PPH_PROCESS_RECORD startProcessRecord; + + startProcessRecord = ProcessRecord; + + do + { + if (ProcessRecord->ProcessId == ProcessId) + return ProcessRecord; + + ProcessRecord = CONTAINING_RECORD(ProcessRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry); + } while (ProcessRecord != startProcessRecord); + + return NULL; +} + +/** + * Finds a process record. + * + * \param ProcessId The ID of the process. + * \param Time A time in which the process was active. + * + * \return The newest record older than \a Time, or NULL + * if the record could not be found. You must call + * PhDereferenceProcessRecord() when you no longer need + * the record. + */ +PPH_PROCESS_RECORD PhFindProcessRecord( + _In_opt_ HANDLE ProcessId, + _In_ PLARGE_INTEGER Time + ) +{ + PPH_PROCESS_RECORD processRecord; + ULONG i; + BOOLEAN found; + + if (PhProcessRecordList->Count == 0) + return NULL; + + PhAcquireQueuedLockShared(&PhProcessRecordListLock); + + processRecord = PhpSearchProcessRecordList(Time, &i, NULL); + + if (!processRecord) + { + // This is expected. Now we search backwards to find the newest matching element older than + // the given time. + + found = FALSE; + + while (TRUE) + { + processRecord = (PPH_PROCESS_RECORD)PhProcessRecordList->Items[i]; + + if (processRecord->CreateTime.QuadPart < Time->QuadPart) + { + if (ProcessId) + { + processRecord = PhpFindProcessRecord(processRecord, ProcessId); + + if (processRecord) + found = TRUE; + } + else + { + found = TRUE; + } + + if (found) + break; + } + + if (i == 0) + break; + + i--; + } + } + else + { + if (ProcessId) + { + processRecord = PhpFindProcessRecord(processRecord, ProcessId); + + if (processRecord) + found = TRUE; + else + found = FALSE; + } + else + { + found = TRUE; + } + } + + if (found) + { + // The record might have had its last reference just cleared but it hasn't been removed from + // the list yet. + if (!PhReferenceProcessRecordSafe(processRecord)) + found = FALSE; + } + + PhReleaseQueuedLockShared(&PhProcessRecordListLock); + + if (found) + return processRecord; + else + return NULL; +} + +/** + * Deletes unused process records. + */ +VOID PhPurgeProcessRecords( + VOID + ) +{ + PPH_PROCESS_RECORD processRecord; + PPH_PROCESS_RECORD startProcessRecord; + ULONG i; + LARGE_INTEGER threshold; + PPH_LIST derefList = NULL; + + if (PhProcessRecordList->Count == 0) + return; + + // Get the oldest statistics time. + PhGetStatisticsTime(NULL, PhTimeHistory.Count - 1, &threshold); + + PhAcquireQueuedLockShared(&PhProcessRecordListLock); + + for (i = 0; i < PhProcessRecordList->Count; i++) + { + processRecord = PhProcessRecordList->Items[i]; + startProcessRecord = processRecord; + + do + { + ULONG requiredFlags; + + requiredFlags = PH_PROCESS_RECORD_DEAD | PH_PROCESS_RECORD_STAT_REF; + + if ((processRecord->Flags & requiredFlags) == requiredFlags) + { + // Check if the process exit time is before the oldest statistics time. If so we can + // dereference the process record. + if (processRecord->ExitTime.QuadPart < threshold.QuadPart) + { + // Clear the stat ref bit; this is to make sure we don't try to dereference the + // record twice (e.g. if someone else currently holds a reference to the record + // and it doesn't get removed immediately). + processRecord->Flags &= ~PH_PROCESS_RECORD_STAT_REF; + + if (!derefList) + derefList = PhCreateList(2); + + PhAddItemList(derefList, processRecord); + } + } + + processRecord = CONTAINING_RECORD(processRecord->ListEntry.Flink, PH_PROCESS_RECORD, ListEntry); + } while (processRecord != startProcessRecord); + } + + PhReleaseQueuedLockShared(&PhProcessRecordListLock); + + if (derefList) + { + for (i = 0; i < derefList->Count; i++) + { + PhDereferenceProcessRecord(derefList->Items[i]); + } + + PhDereferenceObject(derefList); + } +} + +PPH_PROCESS_ITEM PhReferenceProcessItemForParent( + _In_ HANDLE ParentProcessId, + _In_ HANDLE ProcessId, + _In_ PLARGE_INTEGER CreateTime + ) +{ + PPH_PROCESS_ITEM processItem; + + if (ParentProcessId == ProcessId) // for cases where the parent PID = PID (e.g. System Idle Process) + return NULL; + + PhAcquireQueuedLockShared(&PhProcessHashSetLock); + + processItem = PhpLookupProcessItem(ParentProcessId); + + // We make sure that the process item we found is actually the parent process - its start time + // must not be larger than the supplied time. + if (processItem && processItem->CreateTime.QuadPart <= CreateTime->QuadPart) + PhReferenceObject(processItem); + else + processItem = NULL; + + PhReleaseQueuedLockShared(&PhProcessHashSetLock); + + return processItem; +} + +PPH_PROCESS_ITEM PhReferenceProcessItemForRecord( + _In_ PPH_PROCESS_RECORD Record + ) +{ + PPH_PROCESS_ITEM processItem; + + PhAcquireQueuedLockShared(&PhProcessHashSetLock); + + processItem = PhpLookupProcessItem(Record->ProcessId); + + if (processItem && processItem->CreateTime.QuadPart == Record->CreateTime.QuadPart) + PhReferenceObject(processItem); + else + processItem = NULL; + + PhReleaseQueuedLockShared(&PhProcessHashSetLock); + + return processItem; +} diff --git a/ProcessHacker/procrec.c b/ProcessHacker/procrec.c new file mode 100644 index 0000000..6793cd6 --- /dev/null +++ b/ProcessHacker/procrec.c @@ -0,0 +1,260 @@ +/* + * Process Hacker - + * process record properties + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +typedef struct _PROCESS_RECORD_CONTEXT +{ + PPH_PROCESS_RECORD Record; + HICON FileIcon; +} PROCESS_RECORD_CONTEXT, *PPROCESS_RECORD_CONTEXT; + +INT_PTR CALLBACK PhpProcessRecordDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowProcessRecordDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_RECORD Record + ) +{ + PROCESS_RECORD_CONTEXT context; + + context.Record = Record; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PROCRECORD), + ParentWindowHandle, + PhpProcessRecordDlgProc, + (LPARAM)&context + ); +} + +PPH_STRING PhpaGetRelativeTimeString( + _In_ PLARGE_INTEGER Time + ) +{ + LARGE_INTEGER time; + LARGE_INTEGER currentTime; + SYSTEMTIME timeFields; + PPH_STRING timeRelativeString; + PPH_STRING timeString; + + time = *Time; + PhQuerySystemTime(¤tTime); + timeRelativeString = PH_AUTO(PhFormatTimeSpanRelative(currentTime.QuadPart - time.QuadPart)); + + PhLargeIntegerToLocalSystemTime(&timeFields, &time); + timeString = PhaFormatDateTime(&timeFields); + + return PhaFormatString(L"%s ago (%s)", timeRelativeString->Buffer, timeString->Buffer); +} + +FORCEINLINE PWSTR PhpGetStringOrNa( + _In_ PPH_STRING String + ) +{ + if (String) + return String->Buffer; + else + return L"N/A"; +} + +INT_PTR CALLBACK PhpProcessRecordDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPROCESS_RECORD_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PPROCESS_RECORD_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + else + { + context = (PPROCESS_RECORD_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PH_IMAGE_VERSION_INFO versionInfo; + BOOLEAN versionInfoInitialized; + PPH_STRING processNameString; + PPH_PROCESS_ITEM processItem; + + if (!PH_IS_FAKE_PROCESS_ID(context->Record->ProcessId)) + { + processNameString = PhaFormatString(L"%s (%u)", + context->Record->ProcessName->Buffer, HandleToUlong(context->Record->ProcessId)); + } + else + { + processNameString = context->Record->ProcessName; + } + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDOK), TRUE); + SetWindowText(hwndDlg, processNameString->Buffer); + + SetDlgItemText(hwndDlg, IDC_PROCESSNAME, processNameString->Buffer); + + if (processItem = PhReferenceProcessItemForRecord(context->Record)) + { + PPH_PROCESS_ITEM parentProcess; + + if (parentProcess = PhReferenceProcessItemForParent( + processItem->ParentProcessId, + processItem->ProcessId, + &processItem->CreateTime + )) + { + CLIENT_ID clientId; + + clientId.UniqueProcess = parentProcess->ProcessId; + clientId.UniqueThread = NULL; + + SetDlgItemText(hwndDlg, IDC_PARENT, + PH_AUTO_T(PH_STRING, PhGetClientIdNameEx(&clientId, parentProcess->ProcessName))->Buffer); + + PhDereferenceObject(parentProcess); + } + else + { + SetDlgItemText(hwndDlg, IDC_PARENT, PhaFormatString(L"Non-existent process (%u)", + HandleToUlong(context->Record->ParentProcessId))->Buffer); + } + + PhDereferenceObject(processItem); + } + else + { + SetDlgItemText(hwndDlg, IDC_PARENT, PhaFormatString(L"Unknown process (%u)", + HandleToUlong(context->Record->ParentProcessId))->Buffer); + + EnableWindow(GetDlgItem(hwndDlg, IDC_PROPERTIES), FALSE); + } + + memset(&versionInfo, 0, sizeof(PH_IMAGE_VERSION_INFO)); + versionInfoInitialized = FALSE; + + if (context->Record->FileName) + { + if (PhInitializeImageVersionInfo(&versionInfo, context->Record->FileName->Buffer)) + versionInfoInitialized = TRUE; + } + + context->FileIcon = PhGetFileShellIcon(PhGetString(context->Record->FileName), L".exe", TRUE); + + SendMessage(GetDlgItem(hwndDlg, IDC_OPENFILENAME), BM_SETIMAGE, IMAGE_ICON, + (LPARAM)PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_FOLDER))); + SendMessage(GetDlgItem(hwndDlg, IDC_FILEICON), STM_SETICON, + (WPARAM)context->FileIcon, 0); + + SetDlgItemText(hwndDlg, IDC_NAME, PhpGetStringOrNa(versionInfo.FileDescription)); + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, PhpGetStringOrNa(versionInfo.CompanyName)); + SetDlgItemText(hwndDlg, IDC_VERSION, PhpGetStringOrNa(versionInfo.FileVersion)); + SetDlgItemText(hwndDlg, IDC_FILENAME, PhpGetStringOrNa(context->Record->FileName)); + + if (versionInfoInitialized) + PhDeleteImageVersionInfo(&versionInfo); + + if (!context->Record->FileName) + EnableWindow(GetDlgItem(hwndDlg, IDC_OPENFILENAME), FALSE); + + SetDlgItemText(hwndDlg, IDC_CMDLINE, PhpGetStringOrNa(context->Record->CommandLine)); + + if (context->Record->CreateTime.QuadPart != 0) + SetDlgItemText(hwndDlg, IDC_STARTED, PhpaGetRelativeTimeString(&context->Record->CreateTime)->Buffer); + else + SetDlgItemText(hwndDlg, IDC_STARTED, L"N/A"); + + if (context->Record->ExitTime.QuadPart != 0) + SetDlgItemText(hwndDlg, IDC_TERMINATED, PhpaGetRelativeTimeString(&context->Record->ExitTime)->Buffer); + else + SetDlgItemText(hwndDlg, IDC_TERMINATED, L"N/A"); + + SetDlgItemInt(hwndDlg, IDC_SESSIONID, context->Record->SessionId, FALSE); + } + break; + case WM_DESTROY: + { + if (context->FileIcon) + DestroyIcon(context->FileIcon); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + { + EndDialog(hwndDlg, IDOK); + } + break; + case IDC_OPENFILENAME: + { + if (context->Record->FileName) + PhShellExploreFile(hwndDlg, context->Record->FileName->Buffer); + } + break; + case IDC_PROPERTIES: + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItemForRecord(context->Record)) + { + ProcessHacker_ShowProcessProperties(PhMainWndHandle, processItem); + PhDereferenceObject(processItem); + } + else + { + PhShowError(hwndDlg, L"The process has already terminated; only the process record is available."); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/proctree.c b/ProcessHacker/proctree.c new file mode 100644 index 0000000..d4bc426 --- /dev/null +++ b/ProcessHacker/proctree.c @@ -0,0 +1,3504 @@ +/* + * Process Hacker - + * process tree list + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The process tree list manages a list of tree nodes and handles callback + * events generated by the underlying treenew control. Retrieval of + * certain types of process information is also performed here, on the + * GUI thread (see PH_PROCESS_NODE.ValidMask). This is done for columns + * that require data not supplied by the process provider. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum _PHP_AGGREGATE_TYPE +{ + AggregateTypeFloat, + AggregateTypeInt32, + AggregateTypeInt64, + AggregateTypeIntPtr +} PHP_AGGREGATE_TYPE; + +typedef enum _PHP_AGGREGATE_LOCATION +{ + AggregateLocationProcessNode, + AggregateLocationProcessItem +} PHP_AGGREGATE_LOCATION; + +VOID PhpRemoveProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ); + +VOID PhpUpdateNeedCyclesInformation( + VOID + ); + +VOID PhpUpdateProcessNodeCycles( + _Inout_ PPH_PROCESS_NODE ProcessNode + ); + +LONG PhpProcessTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpProcessTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +static HWND ProcessTreeListHandle; +static ULONG ProcessTreeListSortColumn; +static PH_SORT_ORDER ProcessTreeListSortOrder; +static PH_CM_MANAGER ProcessTreeListCm; + +static PPH_HASH_ENTRY ProcessNodeHashSet[256] = PH_HASH_SET_INIT; // hashtable of all nodes +static PPH_LIST ProcessNodeList; // list of all nodes, used when sorting is enabled +static PPH_LIST ProcessNodeRootList; // list of root nodes + +BOOLEAN PhProcessTreeListStateHighlighting = TRUE; +static PPH_POINTER_LIST ProcessNodeStateList = NULL; // list of nodes which need to be processed + +static PH_TN_FILTER_SUPPORT FilterSupport; +static BOOLEAN NeedCyclesInformation = FALSE; + +static HDC GraphContext = NULL; +static ULONG GraphContextWidth = 0; +static ULONG GraphContextHeight = 0; +static HBITMAP GraphOldBitmap; +static HBITMAP GraphBitmap = NULL; +static PVOID GraphBits = NULL; + +VOID PhProcessTreeListInitialization( + VOID + ) +{ + ProcessNodeList = PhCreateList(40); + ProcessNodeRootList = PhCreateList(10); +} + +VOID PhInitializeProcessTreeList( + _In_ HWND hwnd + ) +{ + ProcessTreeListHandle = hwnd; + PhSetControlTheme(ProcessTreeListHandle, L"explorer"); + TreeNew_SetExtendedFlags(hwnd, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT); + SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT); + + TreeNew_SetCallback(hwnd, PhpProcessTreeNewCallback, NULL); + + TreeNew_SetMaxId(hwnd, PHPRTLC_MAXIMUM - 1); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHPRTLC_NAME, TRUE, L"Name", 200, PH_ALIGN_LEFT, -2, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_PID, TRUE, L"PID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CPU, TRUE, L"CPU", 45, PH_ALIGN_RIGHT, 1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOTOTALRATE, TRUE, L"I/O total rate", 70, PH_ALIGN_RIGHT, 2, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEBYTES, TRUE, L"Private bytes", 70, PH_ALIGN_RIGHT, 3, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_USERNAME, TRUE, L"User name", 140, PH_ALIGN_LEFT, 4, DT_PATH_ELLIPSIS); + PhAddTreeNewColumn(hwnd, PHPRTLC_DESCRIPTION, TRUE, L"Description", 180, PH_ALIGN_LEFT, 5, 0); + + // Customizable columns (1) + PhAddTreeNewColumn(hwnd, PHPRTLC_COMPANYNAME, FALSE, L"Company name", 180, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_VERSION, FALSE, L"Version", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_FILENAME, FALSE, L"File name", 180, PH_ALIGN_LEFT, -1, DT_PATH_ELLIPSIS); + PhAddTreeNewColumn(hwnd, PHPRTLC_COMMANDLINE, FALSE, L"Command line", 180, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKPRIVATEBYTES, FALSE, L"Peak private bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_WORKINGSET, FALSE, L"Working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKWORKINGSET, FALSE, L"Peak working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEWS, FALSE, L"Private WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_SHAREDWS, FALSE, L"Shared WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_SHAREABLEWS, FALSE, L"Shareable WS", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_VIRTUALSIZE, FALSE, L"Virtual size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKVIRTUALSIZE, FALSE, L"Peak virtual size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEFAULTS, FALSE, L"Page faults", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_SESSIONID, FALSE, L"Session ID", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIORITYCLASS, FALSE, L"Priority class", 100, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_BASEPRIORITY, FALSE, L"Base priority", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + + // Customizable columns (2) + PhAddTreeNewColumnEx(hwnd, PHPRTLC_THREADS, FALSE, L"Threads", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_HANDLES, FALSE, L"Handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_GDIHANDLES, FALSE, L"GDI handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_USERHANDLES, FALSE, L"USER handles", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IORORATE, FALSE, L"I/O read+other rate", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRATE, FALSE, L"I/O write rate", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_INTEGRITY, FALSE, L"Integrity", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOPRIORITY, FALSE, L"I/O priority", 70, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEPRIORITY, FALSE, L"Page priority", 45, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_STARTTIME, FALSE, L"Start time", 100, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_TOTALCPUTIME, FALSE, L"Total CPU time", 90, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_KERNELCPUTIME, FALSE, L"Kernel CPU time", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_USERCPUTIME, FALSE, L"User CPU time", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_VERIFICATIONSTATUS, FALSE, L"Verification status", 70, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_VERIFIEDSIGNER, FALSE, L"Verified signer", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_ASLR, FALSE, L"ASLR", 50, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_RELATIVESTARTTIME, FALSE, L"Relative start time", 180, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_BITS, FALSE, L"Bits", 50, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_ELEVATION, FALSE, L"Elevation", 60, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_WINDOWTITLE, FALSE, L"Window title", 120, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_WINDOWSTATUS, FALSE, L"Window status", 60, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CYCLES, FALSE, L"Cycles", 110, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CYCLESDELTA, FALSE, L"Cycles delta", 90, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx2(hwnd, PHPRTLC_CPUHISTORY, FALSE, L"CPU history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING); + PhAddTreeNewColumnEx2(hwnd, PHPRTLC_PRIVATEBYTESHISTORY, FALSE, L"Private bytes history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING); + PhAddTreeNewColumnEx2(hwnd, PHPRTLC_IOHISTORY, FALSE, L"I/O history", 100, PH_ALIGN_LEFT, -1, 0, TN_COLUMN_FLAG_CUSTOMDRAW | TN_COLUMN_FLAG_SORTDESCENDING); + PhAddTreeNewColumn(hwnd, PHPRTLC_DEP, FALSE, L"DEP", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_VIRTUALIZED, FALSE, L"Virtualized", 80, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CONTEXTSWITCHES, FALSE, L"Context switches", 100, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CONTEXTSWITCHESDELTA, FALSE, L"Context switches delta", 80, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEFAULTSDELTA, FALSE, L"Page faults delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + + // I/O group columns + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADS, FALSE, L"I/O reads", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITES, FALSE, L"I/O writes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHER, FALSE, L"I/O other", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADBYTES, FALSE, L"I/O read bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITEBYTES, FALSE, L"I/O write bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHERBYTES, FALSE, L"I/O other bytes", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOREADSDELTA, FALSE, L"I/O reads delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOWRITESDELTA, FALSE, L"I/O writes delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_IOOTHERDELTA, FALSE, L"I/O other delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + + // Customizable columns (3) + PhAddTreeNewColumn(hwnd, PHPRTLC_OSCONTEXT, FALSE, L"OS context", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PAGEDPOOL, FALSE, L"Paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKPAGEDPOOL, FALSE, L"Peak paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_NONPAGEDPOOL, FALSE, L"Non-paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PEAKNONPAGEDPOOL, FALSE, L"Peak non-paged pool", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_MINIMUMWORKINGSET, FALSE, L"Minimum working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_MAXIMUMWORKINGSET, FALSE, L"Maximum working set", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_PRIVATEBYTESDELTA, FALSE, L"Private bytes delta", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHPRTLC_SUBSYSTEM, FALSE, L"Subsystem", 110, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_PACKAGENAME, FALSE, L"Package name", 160, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_APPID, FALSE, L"App ID", 160, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHPRTLC_DPIAWARENESS, FALSE, L"DPI awareness", 110, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_CFGUARD, FALSE, L"CF Guard", 70, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_TIMESTAMP, FALSE, L"Time stamp", 140, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_FILEMODIFIEDTIME, FALSE, L"File modified time", 140, PH_ALIGN_LEFT, -1, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, PHPRTLC_FILESIZE, FALSE, L"File size", 70, PH_ALIGN_RIGHT, -1, DT_RIGHT, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetTriState(hwnd, TRUE); + TreeNew_SetSort(hwnd, 0, NoSortOrder); + + PhCmInitializeManager(&ProcessTreeListCm, hwnd, PHPRTLC_MAXIMUM, PhpProcessTreeNewPostSortFunction); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = hwnd; + treeNewInfo.CmData = &ProcessTreeListCm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), &treeNewInfo); + } + + PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, ProcessNodeList); +} + +VOID PhLoadSettingsProcessTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"ProcessTreeListColumns"); + sortSettings = PhGetStringSetting(L"ProcessTreeListSort"); + PhCmLoadSettingsEx(ProcessTreeListHandle, &ProcessTreeListCm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); + + if (PhGetIntegerSetting(L"EnableInstantTooltips")) + { + SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_INITIAL, 0); + } + + PhpUpdateNeedCyclesInformation(); +} + +VOID PhSaveSettingsProcessTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(ProcessTreeListHandle, &ProcessTreeListCm, 0, &sortSettings); + PhSetStringSetting2(L"ProcessTreeListColumns", &settings->sr); + PhSetStringSetting2(L"ProcessTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhReloadSettingsProcessTreeList( + VOID + ) +{ + SendMessage(TreeNew_GetTooltips(ProcessTreeListHandle), TTM_SETDELAYTIME, TTDT_INITIAL, + PhGetIntegerSetting(L"EnableInstantTooltips") ? 0 : -1); +} + +struct _PH_TN_FILTER_SUPPORT *PhGetFilterSupportProcessTreeList( + VOID + ) +{ + return &FilterSupport; +} + +FORCEINLINE BOOLEAN PhCompareProcessNode( + _In_ PPH_PROCESS_NODE Value1, + _In_ PPH_PROCESS_NODE Value2 + ) +{ + return Value1->ProcessId == Value2->ProcessId; +} + +FORCEINLINE ULONG PhHashProcessNode( + _In_ PPH_PROCESS_NODE Value + ) +{ + return HandleToUlong(Value->ProcessId) / 4; +} + +FORCEINLINE BOOLEAN PhpValidateParentCreateTime( + _In_ PPH_PROCESS_NODE Child, + _In_ PPH_PROCESS_NODE Parent + ) +{ + return + PH_IS_FAKE_PROCESS_ID(Child->ProcessId) || + Parent->ProcessItem->CreateTime.QuadPart <= Child->ProcessItem->CreateTime.QuadPart; +} + +PPH_PROCESS_NODE PhAddProcessNode( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG RunId + ) +{ + PPH_PROCESS_NODE processNode; + PPH_PROCESS_NODE parentNode; + ULONG i; + + processNode = PhAllocate(PhEmGetObjectSize(EmProcessNodeType, sizeof(PH_PROCESS_NODE))); + memset(processNode, 0, sizeof(PH_PROCESS_NODE)); + PhInitializeTreeNewNode(&processNode->Node); + + if (PhProcessTreeListStateHighlighting && RunId != 1) + { + PhChangeShStateTn( + &processNode->Node, + &processNode->ShState, + &ProcessNodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + processNode->ProcessId = ProcessItem->ProcessId; + processNode->ProcessItem = ProcessItem; + PhReferenceObject(ProcessItem); + + memset(processNode->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM); + processNode->Node.TextCache = processNode->TextCache; + processNode->Node.TextCacheSize = PHPRTLC_MAXIMUM; + + processNode->Children = PhCreateList(1); + + // Find this process' parent and add the process to it if we found it. + if ( + (parentNode = PhFindProcessNode(ProcessItem->ParentProcessId)) && + PhpValidateParentCreateTime(processNode, parentNode) + ) + { + PhAddItemList(parentNode->Children, processNode); + processNode->Parent = parentNode; + } + else + { + // No parent, add to root list. + processNode->Parent = NULL; + PhAddItemList(ProcessNodeRootList, processNode); + } + + // Find this process' children and move them to this node. + + for (i = 0; i < ProcessNodeRootList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeRootList->Items[i]; + + if ( + node != processNode && // for cases where the parent PID = PID (e.g. System Idle Process) + node->ProcessItem->ParentProcessId == ProcessItem->ProcessId && + PhpValidateParentCreateTime(node, processNode) + ) + { + node->Parent = processNode; + PhAddItemList(processNode->Children, node); + } + } + + for (i = 0; i < processNode->Children->Count; i++) + { + PhRemoveItemList( + ProcessNodeRootList, + PhFindItemList(ProcessNodeRootList, processNode->Children->Items[i]) + ); + } + + PhAddEntryHashSet( + ProcessNodeHashSet, + PH_HASH_SET_SIZE(ProcessNodeHashSet), + &processNode->HashEntry, + PhHashProcessNode(processNode) + ); + PhAddItemList(ProcessNodeList, processNode); + + if (PhCsCollapseServicesOnStart) + { + static PH_STRINGREF servicesBaseName = PH_STRINGREF_INIT(L"\\services.exe"); + static BOOLEAN servicesFound = FALSE; + static PPH_STRING servicesFileName = NULL; + + if (!servicesFound) + { + if (!servicesFileName) + { + PPH_STRING systemDirectory; + + systemDirectory = PhGetSystemDirectory(); + servicesFileName = PhConcatStringRef2(&systemDirectory->sr, &servicesBaseName); + PhDereferenceObject(systemDirectory); + } + + // If this process is services.exe, collapse the node and free the string. + if ( + ProcessItem->FileName && + PhEqualString(ProcessItem->FileName, servicesFileName, TRUE) + ) + { + processNode->Node.Expanded = FALSE; + PhDereferenceObject(servicesFileName); + servicesFileName = NULL; + servicesFound = TRUE; + } + } + } + + if (WindowsVersion >= WINDOWS_7 && PhEnableCycleCpuUsage && ProcessItem->ProcessId == INTERRUPTS_PROCESS_ID) + PhInitializeStringRef(&processNode->DescriptionText, L"Interrupts and DPCs"); + + if (FilterSupport.FilterList) + processNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &processNode->Node); + + PhEmCallObjectOperation(EmProcessNodeType, processNode, EmObjectCreate); + + TreeNew_NodesStructured(ProcessTreeListHandle); + + return processNode; +} + +PPH_PROCESS_NODE PhFindProcessNode( + _In_ HANDLE ProcessId + ) +{ + PH_PROCESS_NODE lookupNode; + PPH_HASH_ENTRY entry; + PPH_PROCESS_NODE node; + + lookupNode.ProcessId = ProcessId; + entry = PhFindEntryHashSet( + ProcessNodeHashSet, + PH_HASH_SET_SIZE(ProcessNodeHashSet), + PhHashProcessNode(&lookupNode) + ); + + for (; entry; entry = entry->Next) + { + node = CONTAINING_RECORD(entry, PH_PROCESS_NODE, HashEntry); + + if (PhCompareProcessNode(&lookupNode, node)) + return node; + } + + return NULL; +} + +VOID PhRemoveProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashSet(ProcessNodeHashSet, PH_HASH_SET_SIZE(ProcessNodeHashSet), &ProcessNode->HashEntry); + + if (PhProcessTreeListStateHighlighting) + { + PhChangeShStateTn( + &ProcessNode->Node, + &ProcessNode->ShState, + &ProcessNodeStateList, + RemovingItemState, + PhCsColorRemoved, + ProcessTreeListHandle + ); + } + else + { + PhpRemoveProcessNode(ProcessNode); + } +} + +VOID PhpRemoveProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ) +{ + ULONG index; + ULONG i; + + PhEmCallObjectOperation(EmProcessNodeType, ProcessNode, EmObjectDelete); + + if (ProcessNode->Parent) + { + // Remove the node from its parent. + + if ((index = PhFindItemList(ProcessNode->Parent->Children, ProcessNode)) != -1) + PhRemoveItemList(ProcessNode->Parent->Children, index); + } + else + { + // Remove the node from the root list. + + if ((index = PhFindItemList(ProcessNodeRootList, ProcessNode)) != -1) + PhRemoveItemList(ProcessNodeRootList, index); + } + + // Move the node's children to the root list. + for (i = 0; i < ProcessNode->Children->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNode->Children->Items[i]; + + node->Parent = NULL; + PhAddItemList(ProcessNodeRootList, node); + } + + // Remove from list and cleanup. + + if ((index = PhFindItemList(ProcessNodeList, ProcessNode)) != -1) + PhRemoveItemList(ProcessNodeList, index); + + PhDereferenceObject(ProcessNode->Children); + + PhClearReference(&ProcessNode->WindowText); + PhClearReference(&ProcessNode->AppIdText); + + PhClearReference(&ProcessNode->TooltipText); + + PhClearReference(&ProcessNode->IoTotalRateText); + PhClearReference(&ProcessNode->PrivateBytesText); + PhClearReference(&ProcessNode->PeakPrivateBytesText); + PhClearReference(&ProcessNode->WorkingSetText); + PhClearReference(&ProcessNode->PeakWorkingSetText); + PhClearReference(&ProcessNode->PrivateWsText); + PhClearReference(&ProcessNode->SharedWsText); + PhClearReference(&ProcessNode->ShareableWsText); + PhClearReference(&ProcessNode->VirtualSizeText); + PhClearReference(&ProcessNode->PeakVirtualSizeText); + PhClearReference(&ProcessNode->PageFaultsText); + PhClearReference(&ProcessNode->IoRoRateText); + PhClearReference(&ProcessNode->IoWRateText); + PhClearReference(&ProcessNode->StartTimeText); + PhClearReference(&ProcessNode->TotalCpuTimeText); + PhClearReference(&ProcessNode->KernelCpuTimeText); + PhClearReference(&ProcessNode->UserCpuTimeText); + PhClearReference(&ProcessNode->RelativeStartTimeText); + PhClearReference(&ProcessNode->WindowTitleText); + PhClearReference(&ProcessNode->CyclesText); + PhClearReference(&ProcessNode->CyclesDeltaText); + PhClearReference(&ProcessNode->ContextSwitchesText); + PhClearReference(&ProcessNode->ContextSwitchesDeltaText); + PhClearReference(&ProcessNode->PageFaultsDeltaText); + + for (i = 0; i < PHPRTLC_IOGROUP_COUNT; i++) + PhClearReference(&ProcessNode->IoGroupText[i]); + + PhClearReference(&ProcessNode->PagedPoolText); + PhClearReference(&ProcessNode->PeakPagedPoolText); + PhClearReference(&ProcessNode->NonPagedPoolText); + PhClearReference(&ProcessNode->PeakNonPagedPoolText); + PhClearReference(&ProcessNode->MinimumWorkingSetText); + PhClearReference(&ProcessNode->MaximumWorkingSetText); + PhClearReference(&ProcessNode->PrivateBytesDeltaText); + PhClearReference(&ProcessNode->TimeStampText); + PhClearReference(&ProcessNode->FileModifiedTimeText); + PhClearReference(&ProcessNode->FileSizeText); + + PhDeleteGraphBuffers(&ProcessNode->CpuGraphBuffers); + PhDeleteGraphBuffers(&ProcessNode->PrivateGraphBuffers); + PhDeleteGraphBuffers(&ProcessNode->IoGraphBuffers); + + PhDereferenceObject(ProcessNode->ProcessItem); + + PhFree(ProcessNode); + + TreeNew_NodesStructured(ProcessTreeListHandle); +} + +VOID PhUpdateProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ) +{ + memset(ProcessNode->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM); + + if (ProcessNode->TooltipText) + { + PhDereferenceObject(ProcessNode->TooltipText); + ProcessNode->TooltipText = NULL; + } + + PhInvalidateTreeNewNode(&ProcessNode->Node, TN_CACHE_COLOR | TN_CACHE_ICON); + TreeNew_InvalidateNode(ProcessTreeListHandle, &ProcessNode->Node); +} + +VOID PhTickProcessNodes( + VOID + ) +{ + ULONG i; + PH_TREENEW_VIEW_PARTS viewParts; + BOOLEAN fullyInvalidated; + RECT rect; + + // Text invalidation, node updates + + for (i = 0; i < ProcessNodeList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeList->Items[i]; + + // The name and PID never change, so we don't invalidate that. + memset(&node->TextCache[2], 0, sizeof(PH_STRINGREF) * (PHPRTLC_MAXIMUM - 2)); + node->ValidMask &= PHPN_OSCONTEXT | PHPN_IMAGE | PHPN_DPIAWARENESS; // Items that always remain valid + + // Invalidate graph buffers. + node->CpuGraphBuffers.Valid = FALSE; + node->PrivateGraphBuffers.Valid = FALSE; + node->IoGraphBuffers.Valid = FALSE; + + // Updates cycles if necessary. + if (NeedCyclesInformation) + PhpUpdateProcessNodeCycles(node); + } + + fullyInvalidated = FALSE; + + if (ProcessTreeListSortOrder != NoSortOrder) + { + // Force a rebuild to sort the items. + TreeNew_NodesStructured(ProcessTreeListHandle); + fullyInvalidated = TRUE; + } + + // State highlighting + PH_TICK_SH_STATE_TN(PH_PROCESS_NODE, ShState, ProcessNodeStateList, PhpRemoveProcessNode, PhCsHighlightingDuration, ProcessTreeListHandle, TRUE, &fullyInvalidated); + + if (!fullyInvalidated) + { + // The first column doesn't need to be invalidated because the process name never changes, and + // icon changes are handled by the modified event. This small optimization can save more than + // 10 million cycles per update (on my machine). + TreeNew_GetViewParts(ProcessTreeListHandle, &viewParts); + rect.left = viewParts.NormalLeft; + rect.top = viewParts.HeaderHeight; + rect.right = viewParts.ClientRect.right - viewParts.VScrollWidth; + rect.bottom = viewParts.ClientRect.bottom; + InvalidateRect(ProcessTreeListHandle, &rect, FALSE); + } +} + +static VOID PhpNeedGraphContext( + _In_ HDC hdc, + _In_ ULONG Width, + _In_ ULONG Height + ) +{ + BITMAPINFOHEADER header; + + // If we already have a graph context and it's the right size, then return immediately. + if (GraphContextWidth == Width && GraphContextHeight == Height) + return; + + if (GraphContext) + { + // The original bitmap must be selected back into the context, otherwise + // the bitmap can't be deleted. + SelectObject(GraphContext, GraphBitmap); + DeleteObject(GraphBitmap); + DeleteDC(GraphContext); + + GraphContext = NULL; + GraphBitmap = NULL; + GraphBits = NULL; + } + + GraphContext = CreateCompatibleDC(hdc); + + memset(&header, 0, sizeof(BITMAPINFOHEADER)); + header.biSize = sizeof(BITMAPINFOHEADER); + header.biWidth = Width; + header.biHeight = Height; + header.biPlanes = 1; + header.biBitCount = 32; + GraphBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &GraphBits, NULL, 0); + GraphOldBitmap = SelectObject(GraphContext, GraphBitmap); +} + +static BOOLEAN PhpFormatInt32GroupDigits( + _In_ ULONG Value, + _Out_writes_bytes_(BufferLength) PWCHAR Buffer, + _In_ ULONG BufferLength, + _Out_opt_ PPH_STRINGREF String + ) +{ + PH_FORMAT format; + SIZE_T returnLength; + + PhInitFormatU(&format, Value); + format.Type |= FormatGroupDigits; + + if (PhFormatToBuffer(&format, 1, Buffer, BufferLength, &returnLength)) + { + if (String) + { + String->Buffer = Buffer; + String->Length = returnLength - sizeof(WCHAR); + } + + return TRUE; + } + else + { + return FALSE; + } +} + +FORCEINLINE PVOID PhpFieldForAggregate( + _In_ PPH_PROCESS_NODE ProcessNode, + _In_ PHP_AGGREGATE_LOCATION Location, + _In_ SIZE_T FieldOffset + ) +{ + PVOID object; + + switch (Location) + { + case AggregateLocationProcessNode: + object = ProcessNode; + break; + case AggregateLocationProcessItem: + object = ProcessNode->ProcessItem; + break; + default: + PhRaiseStatus(STATUS_INVALID_PARAMETER); + } + + return PTR_ADD_OFFSET(object, FieldOffset); +} + +FORCEINLINE VOID PhpAccumulateField( + _Inout_ PVOID Accumulator, + _In_ PVOID Value, + _In_ PHP_AGGREGATE_TYPE Type + ) +{ + switch (Type) + { + case AggregateTypeFloat: + *(PFLOAT)Accumulator += *(PFLOAT)Value; + break; + case AggregateTypeInt32: + *(PULONG)Accumulator += *(PULONG)Value; + break; + case AggregateTypeInt64: + *(PULONG64)Accumulator += *(PULONG64)Value; + break; + case AggregateTypeIntPtr: + *(PULONG_PTR)Accumulator += *(PULONG_PTR)Value; + break; + } +} + +static VOID PhpAggregateField( + _In_ PPH_PROCESS_NODE ProcessNode, + _In_ PHP_AGGREGATE_TYPE Type, + _In_ PHP_AGGREGATE_LOCATION Location, + _In_ SIZE_T FieldOffset, + _Inout_ PVOID AggregatedValue + ) +{ + ULONG i; + + PhpAccumulateField(AggregatedValue, PhpFieldForAggregate(ProcessNode, Location, FieldOffset), Type); + + for (i = 0; i < ProcessNode->Children->Count; i++) + { + PhpAggregateField(ProcessNode->Children->Items[i], Type, Location, FieldOffset, AggregatedValue); + } +} + +static VOID PhpAggregateFieldIfNeeded( + _In_ PPH_PROCESS_NODE ProcessNode, + _In_ PHP_AGGREGATE_TYPE Type, + _In_ PHP_AGGREGATE_LOCATION Location, + _In_ SIZE_T FieldOffset, + _Inout_ PVOID AggregatedValue + ) +{ + if (!PhCsPropagateCpuUsage || ProcessNode->Node.Expanded || ProcessTreeListSortOrder != NoSortOrder) + { + PhpAccumulateField(AggregatedValue, PhpFieldForAggregate(ProcessNode, Location, FieldOffset), Type); + } + else + { + PhpAggregateField(ProcessNode, Type, Location, FieldOffset, AggregatedValue); + } +} + +static VOID PhpUpdateProcessNodeWsCounters( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_WSCOUNTERS)) + { + BOOLEAN success = FALSE; + HANDLE processHandle; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION, + ProcessNode->ProcessItem->ProcessId + ))) + { + if (NT_SUCCESS(PhGetProcessWsCounters( + processHandle, + &ProcessNode->WsCounters + ))) + success = TRUE; + + NtClose(processHandle); + } + + if (!success) + memset(&ProcessNode->WsCounters, 0, sizeof(PH_PROCESS_WS_COUNTERS)); + + ProcessNode->ValidMask |= PHPN_WSCOUNTERS; + } +} + +static VOID PhpUpdateProcessNodeGdiUserHandles( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_GDIUSERHANDLES)) + { + if (ProcessNode->ProcessItem->QueryHandle) + { + ProcessNode->GdiHandles = GetGuiResources(ProcessNode->ProcessItem->QueryHandle, GR_GDIOBJECTS); + ProcessNode->UserHandles = GetGuiResources(ProcessNode->ProcessItem->QueryHandle, GR_USEROBJECTS); + } + else + { + ProcessNode->GdiHandles = 0; + ProcessNode->UserHandles = 0; + } + + ProcessNode->ValidMask |= PHPN_GDIUSERHANDLES; + } +} + +static VOID PhpUpdateProcessNodeIoPagePriority( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_IOPAGEPRIORITY)) + { + if (ProcessNode->ProcessItem->QueryHandle) + { + if (!NT_SUCCESS(PhGetProcessIoPriority(ProcessNode->ProcessItem->QueryHandle, &ProcessNode->IoPriority))) + ProcessNode->IoPriority = -1; + if (!NT_SUCCESS(PhGetProcessPagePriority(ProcessNode->ProcessItem->QueryHandle, &ProcessNode->PagePriority))) + ProcessNode->PagePriority = -1; + } + else + { + ProcessNode->IoPriority = -1; + ProcessNode->PagePriority = -1; + } + + ProcessNode->ValidMask |= PHPN_IOPAGEPRIORITY; + } +} + +static VOID PhpUpdateProcessNodeWindow( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_WINDOW)) + { + ProcessNode->WindowHandle = PhGetProcessMainWindow(ProcessNode->ProcessId, ProcessNode->ProcessItem->QueryHandle); + + PhClearReference(&ProcessNode->WindowText); + + if (ProcessNode->WindowHandle) + { + PhGetWindowTextEx(ProcessNode->WindowHandle, PH_GET_WINDOW_TEXT_INTERNAL, &ProcessNode->WindowText); + ProcessNode->WindowHung = !!IsHungAppWindow(ProcessNode->WindowHandle); + } + + ProcessNode->ValidMask |= PHPN_WINDOW; + } +} + +static VOID PhpUpdateProcessNodeDepStatus( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_DEPSTATUS)) + { + HANDLE processHandle; + ULONG depStatus; + + depStatus = 0; + +#ifdef _WIN64 + if (ProcessNode->ProcessItem->IsWow64) +#else + if (TRUE) +#endif + { + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION, + ProcessNode->ProcessItem->ProcessId + ))) + { + PhGetProcessDepStatus(processHandle, &depStatus); + NtClose(processHandle); + } + } + else + { + if (ProcessNode->ProcessItem->QueryHandle) + depStatus = PH_PROCESS_DEP_ENABLED | PH_PROCESS_DEP_PERMANENT; + } + + ProcessNode->DepStatus = depStatus; + + ProcessNode->ValidMask |= PHPN_DEPSTATUS; + } +} + +static VOID PhpUpdateProcessNodeToken( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_TOKEN)) + { + HANDLE tokenHandle; + + ProcessNode->VirtualizationAllowed = FALSE; + ProcessNode->VirtualizationEnabled = FALSE; + + if (WINDOWS_HAS_UAC && ProcessNode->ProcessItem->QueryHandle) + { + if (NT_SUCCESS(PhOpenProcessToken( + ProcessNode->ProcessItem->QueryHandle, + TOKEN_QUERY, + &tokenHandle + ))) + { + if (NT_SUCCESS(PhGetTokenIsVirtualizationAllowed(tokenHandle, &ProcessNode->VirtualizationAllowed)) && + ProcessNode->VirtualizationAllowed) + { + if (!NT_SUCCESS(PhGetTokenIsVirtualizationEnabled(tokenHandle, &ProcessNode->VirtualizationEnabled))) + { + ProcessNode->VirtualizationAllowed = FALSE; // display N/A on error + } + } + + NtClose(tokenHandle); + } + } + + ProcessNode->ValidMask |= PHPN_TOKEN; + } +} + +static VOID PhpUpdateProcessOsContext( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_OSCONTEXT)) + { + HANDLE processHandle; + + if (WindowsVersion >= WINDOWS_7) + { + if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId))) + { + if (NT_SUCCESS(PhGetProcessSwitchContext(processHandle, &ProcessNode->OsContextGuid))) + { + if (memcmp(&ProcessNode->OsContextGuid, &WINTHRESHOLD_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_10; + else if (memcmp(&ProcessNode->OsContextGuid, &WINBLUE_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_8_1; + else if (memcmp(&ProcessNode->OsContextGuid, &WIN8_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_8; + else if (memcmp(&ProcessNode->OsContextGuid, &WIN7_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_7; + else if (memcmp(&ProcessNode->OsContextGuid, &VISTA_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_VISTA; + else if (memcmp(&ProcessNode->OsContextGuid, &XP_CONTEXT_GUID, sizeof(GUID)) == 0) + ProcessNode->OsContextVersion = WINDOWS_XP; + } + + NtClose(processHandle); + } + } + + ProcessNode->ValidMask |= PHPN_OSCONTEXT; + } +} + +static VOID PhpUpdateProcessNodeQuotaLimits( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_QUOTALIMITS)) + { + QUOTA_LIMITS quotaLimits; + + if (ProcessNode->ProcessItem->QueryHandle && NT_SUCCESS(NtQueryInformationProcess( + ProcessNode->ProcessItem->QueryHandle, + ProcessQuotaLimits, + "aLimits, + sizeof(QUOTA_LIMITS), + NULL + ))) + { + ProcessNode->MinimumWorkingSetSize = quotaLimits.MinimumWorkingSetSize; + ProcessNode->MaximumWorkingSetSize = quotaLimits.MaximumWorkingSetSize; + } + else + { + ProcessNode->MinimumWorkingSetSize = 0; + ProcessNode->MaximumWorkingSetSize = 0; + } + + ProcessNode->ValidMask |= PHPN_QUOTALIMITS; + } +} + +static VOID PhpUpdateProcessNodeImage( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_IMAGE)) + { + HANDLE processHandle; + PROCESS_BASIC_INFORMATION basicInfo; + PVOID imageBaseAddress; + PH_REMOTE_MAPPED_IMAGE mappedImage; + + if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId))) + { + if (NT_SUCCESS(PhGetProcessBasicInformation(processHandle, &basicInfo))) + { + if (NT_SUCCESS(NtReadVirtualMemory( + processHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ImageBaseAddress)), + &imageBaseAddress, + sizeof(PVOID), + NULL + ))) + { + if (NT_SUCCESS(PhLoadRemoteMappedImage(processHandle, imageBaseAddress, &mappedImage))) + { + ProcessNode->ImageTimeDateStamp = mappedImage.NtHeaders->FileHeader.TimeDateStamp; + ProcessNode->ImageCharacteristics = mappedImage.NtHeaders->FileHeader.Characteristics; + + if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + ProcessNode->ImageSubsystem = ((PIMAGE_OPTIONAL_HEADER32)&mappedImage.NtHeaders->OptionalHeader)->Subsystem; + ProcessNode->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER32)&mappedImage.NtHeaders->OptionalHeader)->DllCharacteristics; + } + else if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + { + ProcessNode->ImageSubsystem = ((PIMAGE_OPTIONAL_HEADER64)&mappedImage.NtHeaders->OptionalHeader)->Subsystem; + ProcessNode->ImageDllCharacteristics = ((PIMAGE_OPTIONAL_HEADER64)&mappedImage.NtHeaders->OptionalHeader)->DllCharacteristics; + } + + PhUnloadRemoteMappedImage(&mappedImage); + } + } + } + + NtClose(processHandle); + } + + ProcessNode->ValidMask |= PHPN_IMAGE; + } +} + +static VOID PhpUpdateProcessNodeAppId( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_APPID)) + { + HANDLE processHandle; + ULONG windowFlags; + PPH_STRING windowTitle; + + PhClearReference(&ProcessNode->AppIdText); + + if (!NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessNode->ProcessId))) + { + if (WindowsVersion >= WINDOWS_7) + { + if (!NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess, ProcessNode->ProcessId))) + goto Done; + } + else + { + goto Done; + } + } + + if (NT_SUCCESS(PhGetProcessWindowTitle( + processHandle, + &windowFlags, + &windowTitle + ))) + { + if (windowFlags & STARTF_TITLEISAPPID) + ProcessNode->AppIdText = windowTitle; + else + PhDereferenceObject(windowTitle); + } + + NtClose(processHandle); + +Done: + ProcessNode->ValidMask |= PHPN_APPID; + } +} + +static VOID PhpUpdateProcessNodeDpiAwareness( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static BOOL (WINAPI *getProcessDpiAwarenessInternal)( + _In_ HANDLE hprocess, + _Out_ ULONG *value + ); + + if (PhBeginInitOnce(&initOnce)) + { + getProcessDpiAwarenessInternal = PhGetModuleProcAddress(L"user32.dll", "GetProcessDpiAwarenessInternal"); + PhEndInitOnce(&initOnce); + } + + if (!getProcessDpiAwarenessInternal) + return; + + if (!(ProcessNode->ValidMask & PHPN_DPIAWARENESS)) + { + if (ProcessNode->ProcessItem->QueryHandle) + { + ULONG dpiAwareness; + + if (getProcessDpiAwarenessInternal(ProcessNode->ProcessItem->QueryHandle, &dpiAwareness)) + ProcessNode->DpiAwareness = dpiAwareness + 1; + } + + ProcessNode->ValidMask |= PHPN_DPIAWARENESS; + } +} + +static VOID PhpUpdateProcessNodeFileAttributes( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (!(ProcessNode->ValidMask & PHPN_FILEATTRIBUTES)) + { + FILE_NETWORK_OPEN_INFORMATION networkOpenInfo; + + if (ProcessNode->ProcessItem->FileName && NT_SUCCESS(PhQueryFullAttributesFileWin32( + ProcessNode->ProcessItem->FileName->Buffer, + &networkOpenInfo + ))) + { + ProcessNode->FileLastWriteTime = networkOpenInfo.LastWriteTime; + ProcessNode->FileEndOfFile = networkOpenInfo.EndOfFile; + } + else + { + ProcessNode->FileEndOfFile.QuadPart = -1; + } + + ProcessNode->ValidMask |= PHPN_FILEATTRIBUTES; + } +} + +static VOID PhpUpdateNeedCyclesInformation( + VOID + ) +{ + PH_TREENEW_COLUMN column; + + NeedCyclesInformation = FALSE; + + // Before Windows Vista, there is no cycle time measurement. + // On Windows 7 and above, cycle time information is available directly from the process item. + // We only need to query cycle time separately for Windows Vista. + if (WindowsVersion != WINDOWS_VISTA) + return; + + TreeNew_GetColumn(ProcessTreeListHandle, PHPRTLC_CYCLES, &column); + + if (column.Visible) + { + NeedCyclesInformation = TRUE; + return; + } + + TreeNew_GetColumn(ProcessTreeListHandle, PHPRTLC_CYCLESDELTA, &column); + + if (column.Visible) + { + NeedCyclesInformation = TRUE; + return; + } +} + +static VOID PhpUpdateProcessNodeCycles( + _Inout_ PPH_PROCESS_NODE ProcessNode + ) +{ + if (ProcessNode->ProcessId == SYSTEM_IDLE_PROCESS_ID) + { + PULARGE_INTEGER idleThreadCycleTimes; + ULONG64 cycleTime; + ULONG i; + + // System Idle Process requires special treatment. + + idleThreadCycleTimes = PhAllocate( + sizeof(ULARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors + ); + + if (NT_SUCCESS(NtQuerySystemInformation( + SystemProcessorIdleCycleTimeInformation, + idleThreadCycleTimes, + sizeof(ULARGE_INTEGER) * (ULONG)PhSystemBasicInformation.NumberOfProcessors, + NULL + ))) + { + cycleTime = 0; + + for (i = 0; i < (ULONG)PhSystemBasicInformation.NumberOfProcessors; i++) + cycleTime += idleThreadCycleTimes[i].QuadPart; + + PhUpdateDelta(&ProcessNode->CyclesDelta, cycleTime); + } + + PhFree(idleThreadCycleTimes); + } + else if (ProcessNode->ProcessItem->QueryHandle) + { + ULONG64 cycleTime; + + if (NT_SUCCESS(PhGetProcessCycleTime(ProcessNode->ProcessItem->QueryHandle, &cycleTime))) + { + PhUpdateDelta(&ProcessNode->CyclesDelta, cycleTime); + } + } + + if (ProcessNode->CyclesDelta.Value != 0) + PhMoveReference(&ProcessNode->CyclesText, PhFormatUInt64(ProcessNode->CyclesDelta.Value, TRUE)); + else + PhClearReference(&ProcessNode->CyclesText); + + if (ProcessNode->CyclesDelta.Delta != 0) + PhMoveReference(&ProcessNode->CyclesDeltaText, PhFormatUInt64(ProcessNode->CyclesDelta.Delta, TRUE)); + else + PhClearReference(&ProcessNode->CyclesDeltaText); +} + +#define SORT_FUNCTION(Column) PhpProcessTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpProcessTreeNewCompare##Column( \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)_elem1; \ + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)_elem2; \ + PPH_PROCESS_ITEM processItem1 = node1->ProcessItem; \ + PPH_PROCESS_ITEM processItem2 = node2->ProcessItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = intptrcmp((LONG_PTR)processItem1->ProcessId, (LONG_PTR)processItem2->ProcessId); \ + \ + return PhModifySort(sortResult, ProcessTreeListSortOrder); \ +} + +LONG PhpProcessTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = intptrcmp((LONG_PTR)((PPH_PROCESS_NODE)Node1)->ProcessItem->ProcessId, (LONG_PTR)((PPH_PROCESS_NODE)Node2)->ProcessItem->ProcessId); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(Name) +{ + sortResult = PhCompareString(processItem1->ProcessName, processItem2->ProcessName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Pid) +{ + // Use signed int so DPCs and Interrupts are placed above System Idle Process. + sortResult = intptrcmp((LONG_PTR)processItem1->ProcessId, (LONG_PTR)processItem2->ProcessId); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Cpu) +{ + sortResult = singlecmp(processItem1->CpuUsage, processItem2->CpuUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoTotalRate) +{ + sortResult = uint64cmp( + processItem1->IoReadDelta.Delta + processItem1->IoWriteDelta.Delta + processItem1->IoOtherDelta.Delta, + processItem2->IoReadDelta.Delta + processItem2->IoWriteDelta.Delta + processItem2->IoOtherDelta.Delta + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PrivateBytes) +{ + sortResult = uintptrcmp(processItem1->VmCounters.PagefileUsage, processItem2->VmCounters.PagefileUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(UserName) +{ + sortResult = PhCompareStringWithNull(processItem1->UserName, processItem2->UserName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Description) +{ + PH_STRINGREF sr1; + PH_STRINGREF sr2; + + sr1 = processItem1->VersionInfo.FileDescription ? processItem1->VersionInfo.FileDescription->sr : node1->DescriptionText; + sr2 = processItem2->VersionInfo.FileDescription ? processItem2->VersionInfo.FileDescription->sr : node2->DescriptionText; + + sortResult = PhCompareStringRef(&sr1, &sr2, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CompanyName) +{ + sortResult = PhCompareStringWithNull( + processItem1->VersionInfo.CompanyName, + processItem2->VersionInfo.CompanyName, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Version) +{ + sortResult = PhCompareStringWithNull( + processItem1->VersionInfo.FileVersion, + processItem2->VersionInfo.FileVersion, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileName) +{ + sortResult = PhCompareStringWithNull( + processItem1->FileName, + processItem2->FileName, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CommandLine) +{ + sortResult = PhCompareStringWithNull( + processItem1->CommandLine, + processItem2->CommandLine, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PeakPrivateBytes) +{ + sortResult = uintptrcmp(processItem1->VmCounters.PeakPagefileUsage, processItem2->VmCounters.PeakPagefileUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(WorkingSet) +{ + sortResult = uintptrcmp(processItem1->VmCounters.WorkingSetSize, processItem2->VmCounters.WorkingSetSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PeakWorkingSet) +{ + sortResult = uintptrcmp(processItem1->VmCounters.PeakWorkingSetSize, processItem2->VmCounters.PeakWorkingSetSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PrivateWs) +{ + PhpUpdateProcessNodeWsCounters(node1); + PhpUpdateProcessNodeWsCounters(node2); + sortResult = uintptrcmp(node1->WsCounters.NumberOfPrivatePages, node2->WsCounters.NumberOfPrivatePages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PrivateWsWin7) +{ + sortResult = uintptrcmp(processItem1->WorkingSetPrivateSize, processItem2->WorkingSetPrivateSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(SharedWs) +{ + PhpUpdateProcessNodeWsCounters(node1); + PhpUpdateProcessNodeWsCounters(node2); + sortResult = uintptrcmp(node1->WsCounters.NumberOfSharedPages, node2->WsCounters.NumberOfSharedPages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ShareableWs) +{ + PhpUpdateProcessNodeWsCounters(node1); + PhpUpdateProcessNodeWsCounters(node2); + sortResult = uintptrcmp(node1->WsCounters.NumberOfShareablePages, node2->WsCounters.NumberOfShareablePages); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(VirtualSize) +{ + sortResult = uintptrcmp(processItem1->VmCounters.VirtualSize, processItem2->VmCounters.VirtualSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PeakVirtualSize) +{ + sortResult = uintptrcmp(processItem1->VmCounters.PeakVirtualSize, processItem2->VmCounters.PeakVirtualSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PageFaults) +{ + sortResult = uintcmp(processItem1->VmCounters.PageFaultCount, processItem2->VmCounters.PageFaultCount); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(SessionId) +{ + sortResult = uintcmp(processItem1->SessionId, processItem2->SessionId); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(BasePriority) +{ + sortResult = intcmp(processItem1->BasePriority, processItem2->BasePriority); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Threads) +{ + sortResult = uintcmp(processItem1->NumberOfThreads, processItem2->NumberOfThreads); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Handles) +{ + sortResult = uintcmp(processItem1->NumberOfHandles, processItem2->NumberOfHandles); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(GdiHandles) +{ + sortResult = uintcmp(node1->GdiHandles, node2->GdiHandles); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(UserHandles) +{ + sortResult = uintcmp(node1->UserHandles, node2->UserHandles); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoRoRate) +{ + sortResult = uint64cmp( + processItem1->IoReadDelta.Delta + processItem1->IoOtherDelta.Delta, + processItem2->IoReadDelta.Delta + processItem2->IoOtherDelta.Delta + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoWRate) +{ + sortResult = uint64cmp( + processItem1->IoWriteDelta.Delta, + processItem2->IoWriteDelta.Delta + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Integrity) +{ + sortResult = uintcmp(processItem1->IntegrityLevel, processItem2->IntegrityLevel); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoPriority) +{ + sortResult = uintcmp(node1->IoPriority, node2->IoPriority); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PagePriority) +{ + sortResult = uintcmp(node1->PagePriority, node2->PagePriority); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(StartTime) +{ + sortResult = int64cmp(processItem1->CreateTime.QuadPart, processItem2->CreateTime.QuadPart); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TotalCpuTime) +{ + sortResult = uint64cmp( + processItem1->KernelTime.QuadPart + processItem1->UserTime.QuadPart, + processItem2->KernelTime.QuadPart + processItem2->UserTime.QuadPart + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(KernelCpuTime) +{ + sortResult = uint64cmp( + processItem1->KernelTime.QuadPart, + processItem2->KernelTime.QuadPart + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(UserCpuTime) +{ + sortResult = uint64cmp( + processItem1->UserTime.QuadPart, + processItem2->UserTime.QuadPart + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(VerificationStatus) +{ + sortResult = intcmp(processItem1->VerifyResult, processItem2->VerifyResult); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(VerifiedSigner) +{ + sortResult = PhCompareStringWithNull( + processItem1->VerifySignerName, + processItem2->VerifySignerName, + TRUE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Aslr) +{ + PhpUpdateProcessNodeImage(node1); + PhpUpdateProcessNodeImage(node2); + sortResult = intcmp( + node1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, + node2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(RelativeStartTime) +{ + sortResult = -int64cmp(processItem1->CreateTime.QuadPart, processItem2->CreateTime.QuadPart); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Bits) +{ + sortResult = intcmp(processItem1->IsWow64Valid, processItem2->IsWow64Valid); + + if (sortResult == 0) + sortResult = intcmp(processItem1->IsWow64, processItem2->IsWow64); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Elevation) +{ + ULONG key1; + ULONG key2; + + switch (processItem1->ElevationType) + { + case TokenElevationTypeFull: + key1 = 2; + break; + case TokenElevationTypeLimited: + key1 = 1; + break; + default: + key1 = 0; + break; + } + + switch (processItem2->ElevationType) + { + case TokenElevationTypeFull: + key2 = 2; + break; + case TokenElevationTypeLimited: + key2 = 1; + break; + default: + key2 = 0; + break; + } + + sortResult = intcmp(key1, key2); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(WindowTitle) +{ + PhpUpdateProcessNodeWindow(node1); + PhpUpdateProcessNodeWindow(node2); + sortResult = PhCompareStringWithNull(node1->WindowText, node2->WindowText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(WindowStatus) +{ + PhpUpdateProcessNodeWindow(node1); + PhpUpdateProcessNodeWindow(node2); + sortResult = intcmp(node1->WindowHung, node2->WindowHung); + + // Make sure all processes with windows get grouped together. + if (sortResult == 0) + sortResult = intcmp(!!node1->WindowHandle, !!node2->WindowHandle); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Cycles) +{ + sortResult = uint64cmp(node1->CyclesDelta.Value, node2->CyclesDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CyclesWin7) +{ + sortResult = uint64cmp(processItem1->CycleTimeDelta.Value, processItem2->CycleTimeDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CyclesDelta) +{ + sortResult = uint64cmp(node1->CyclesDelta.Delta, node2->CyclesDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CyclesDeltaWin7) +{ + sortResult = uint64cmp(processItem1->CycleTimeDelta.Delta, processItem2->CycleTimeDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Dep) +{ + PhpUpdateProcessNodeDepStatus(node1); + PhpUpdateProcessNodeDepStatus(node2); + sortResult = uintcmp(node1->DepStatus, node2->DepStatus); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Virtualized) +{ + PhpUpdateProcessNodeToken(node1); + PhpUpdateProcessNodeToken(node2); + sortResult = intcmp(node1->VirtualizationEnabled, node2->VirtualizationEnabled); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ContextSwitches) +{ + sortResult = uintcmp(processItem1->ContextSwitchesDelta.Value, processItem2->ContextSwitchesDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ContextSwitchesDelta) +{ + sortResult = intcmp((LONG)processItem1->ContextSwitchesDelta.Delta, (LONG)processItem2->ContextSwitchesDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PageFaultsDelta) +{ + sortResult = uintcmp(processItem1->PageFaultsDelta.Delta, processItem2->PageFaultsDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoReads) +{ + sortResult = uint64cmp(processItem1->IoReadCountDelta.Value, processItem2->IoReadCountDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoWrites) +{ + sortResult = uint64cmp(processItem1->IoWriteCountDelta.Value, processItem2->IoWriteCountDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoOther) +{ + sortResult = uint64cmp(processItem1->IoOtherCountDelta.Value, processItem2->IoOtherCountDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoReadBytes) +{ + sortResult = uint64cmp(processItem1->IoReadDelta.Value, processItem2->IoReadDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoWriteBytes) +{ + sortResult = uint64cmp(processItem1->IoWriteDelta.Value, processItem2->IoWriteDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoOtherBytes) +{ + sortResult = uint64cmp(processItem1->IoOtherDelta.Value, processItem2->IoOtherDelta.Value); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoReadsDelta) +{ + sortResult = uint64cmp(processItem1->IoReadCountDelta.Delta, processItem2->IoReadCountDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoWritesDelta) +{ + sortResult = uint64cmp(processItem1->IoWriteCountDelta.Delta, processItem2->IoWriteCountDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoOtherDelta) +{ + sortResult = uint64cmp(processItem1->IoOtherCountDelta.Delta, processItem2->IoOtherCountDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(OsContext) +{ + PhpUpdateProcessOsContext(node1); + PhpUpdateProcessOsContext(node2); + sortResult = uintcmp(node1->OsContextVersion, node2->OsContextVersion); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PagedPool) +{ + sortResult = uintptrcmp(processItem1->VmCounters.QuotaPagedPoolUsage, processItem2->VmCounters.QuotaPagedPoolUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PeakPagedPool) +{ + sortResult = uintptrcmp(processItem1->VmCounters.QuotaPeakPagedPoolUsage, processItem2->VmCounters.QuotaPeakPagedPoolUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(NonPagedPool) +{ + sortResult = uintptrcmp(processItem1->VmCounters.QuotaNonPagedPoolUsage, processItem2->VmCounters.QuotaNonPagedPoolUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PeakNonPagedPool) +{ + sortResult = uintptrcmp(processItem1->VmCounters.QuotaPeakNonPagedPoolUsage, processItem2->VmCounters.QuotaPeakNonPagedPoolUsage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(MinimumWorkingSet) +{ + PhpUpdateProcessNodeQuotaLimits(node1); + PhpUpdateProcessNodeQuotaLimits(node2); + sortResult = uintptrcmp(node1->MinimumWorkingSetSize, node2->MinimumWorkingSetSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(MaximumWorkingSet) +{ + PhpUpdateProcessNodeQuotaLimits(node1); + PhpUpdateProcessNodeQuotaLimits(node2); + sortResult = uintptrcmp(node1->MaximumWorkingSetSize, node2->MaximumWorkingSetSize); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PrivateBytesDelta) +{ + sortResult = intptrcmp(processItem1->PrivateBytesDelta.Delta, processItem2->PrivateBytesDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Subsystem) +{ + PhpUpdateProcessNodeImage(node1); + PhpUpdateProcessNodeImage(node2); + sortResult = intcmp(node1->ImageSubsystem, node2->ImageSubsystem); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(PackageName) +{ + sortResult = PhCompareStringWithNull(processItem1->PackageFullName, processItem2->PackageFullName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(AppId) +{ + PhpUpdateProcessNodeAppId(node1); + PhpUpdateProcessNodeAppId(node2); + sortResult = PhCompareStringWithNull(node1->AppIdText, node2->AppIdText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(DpiAwareness) +{ + PhpUpdateProcessNodeDpiAwareness(node1); + PhpUpdateProcessNodeDpiAwareness(node2); + sortResult = uintcmp(node1->DpiAwareness, node2->DpiAwareness); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CfGuard) +{ + PhpUpdateProcessNodeImage(node1); + PhpUpdateProcessNodeImage(node2); + sortResult = intcmp( + node1->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF, + node2->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF + ); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TimeStamp) +{ + PhpUpdateProcessNodeImage(node1); + PhpUpdateProcessNodeImage(node2); + sortResult = uintcmp(node1->ImageTimeDateStamp, node2->ImageTimeDateStamp); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileModifiedTime) +{ + PhpUpdateProcessNodeFileAttributes(node1); + PhpUpdateProcessNodeFileAttributes(node2); + sortResult = int64cmp(node1->FileLastWriteTime.QuadPart, node2->FileLastWriteTime.QuadPart); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(FileSize) +{ + PhpUpdateProcessNodeFileAttributes(node1); + PhpUpdateProcessNodeFileAttributes(node2); + sortResult = int64cmp(node1->FileEndOfFile.QuadPart, node2->FileEndOfFile.QuadPart); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpProcessTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_NODE node; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &ProcessTreeListCm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + node = (PPH_PROCESS_NODE)getChildren->Node; + + if (ProcessTreeListSortOrder == NoSortOrder) + { + if (!node) + { + getChildren->Children = (PPH_TREENEW_NODE *)ProcessNodeRootList->Items; + getChildren->NumberOfChildren = ProcessNodeRootList->Count; + } + else + { + getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; + getChildren->NumberOfChildren = node->Children->Count; + } + } + else + { + if (!node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Name), + SORT_FUNCTION(Pid), + SORT_FUNCTION(Cpu), + SORT_FUNCTION(IoTotalRate), + SORT_FUNCTION(PrivateBytes), + SORT_FUNCTION(UserName), + SORT_FUNCTION(Description), + SORT_FUNCTION(CompanyName), + SORT_FUNCTION(Version), + SORT_FUNCTION(FileName), + SORT_FUNCTION(CommandLine), + SORT_FUNCTION(PeakPrivateBytes), + SORT_FUNCTION(WorkingSet), + SORT_FUNCTION(PeakWorkingSet), + SORT_FUNCTION(PrivateWs), + SORT_FUNCTION(SharedWs), + SORT_FUNCTION(ShareableWs), + SORT_FUNCTION(VirtualSize), + SORT_FUNCTION(PeakVirtualSize), + SORT_FUNCTION(PageFaults), + SORT_FUNCTION(SessionId), + SORT_FUNCTION(BasePriority), // Priority Class + SORT_FUNCTION(BasePriority), + SORT_FUNCTION(Threads), + SORT_FUNCTION(Handles), + SORT_FUNCTION(GdiHandles), + SORT_FUNCTION(UserHandles), + SORT_FUNCTION(IoRoRate), + SORT_FUNCTION(IoWRate), + SORT_FUNCTION(Integrity), + SORT_FUNCTION(IoPriority), + SORT_FUNCTION(PagePriority), + SORT_FUNCTION(StartTime), + SORT_FUNCTION(TotalCpuTime), + SORT_FUNCTION(KernelCpuTime), + SORT_FUNCTION(UserCpuTime), + SORT_FUNCTION(VerificationStatus), + SORT_FUNCTION(VerifiedSigner), + SORT_FUNCTION(Aslr), + SORT_FUNCTION(RelativeStartTime), + SORT_FUNCTION(Bits), + SORT_FUNCTION(Elevation), + SORT_FUNCTION(WindowTitle), + SORT_FUNCTION(WindowStatus), + SORT_FUNCTION(Cycles), + SORT_FUNCTION(CyclesDelta), + SORT_FUNCTION(Cpu), // CPU History + SORT_FUNCTION(PrivateBytes), // Private Bytes History + SORT_FUNCTION(IoTotalRate), // I/O History + SORT_FUNCTION(Dep), + SORT_FUNCTION(Virtualized), + SORT_FUNCTION(ContextSwitches), + SORT_FUNCTION(ContextSwitchesDelta), + SORT_FUNCTION(PageFaultsDelta), + SORT_FUNCTION(IoReads), + SORT_FUNCTION(IoWrites), + SORT_FUNCTION(IoOther), + SORT_FUNCTION(IoReadBytes), + SORT_FUNCTION(IoWriteBytes), + SORT_FUNCTION(IoOtherBytes), + SORT_FUNCTION(IoReadsDelta), + SORT_FUNCTION(IoWritesDelta), + SORT_FUNCTION(IoOtherDelta), + SORT_FUNCTION(OsContext), + SORT_FUNCTION(PagedPool), + SORT_FUNCTION(PeakPagedPool), + SORT_FUNCTION(NonPagedPool), + SORT_FUNCTION(PeakNonPagedPool), + SORT_FUNCTION(MinimumWorkingSet), + SORT_FUNCTION(MaximumWorkingSet), + SORT_FUNCTION(PrivateBytesDelta), + SORT_FUNCTION(Subsystem), + SORT_FUNCTION(PackageName), + SORT_FUNCTION(AppId), + SORT_FUNCTION(DpiAwareness), + SORT_FUNCTION(CfGuard), + SORT_FUNCTION(TimeStamp), + SORT_FUNCTION(FileModifiedTime), + SORT_FUNCTION(FileSize) + }; + static PH_INITONCE initOnce = PH_INITONCE_INIT; + int (__cdecl *sortFunction)(const void *, const void *); + + if (PhBeginInitOnce(&initOnce)) + { + if (WindowsVersion >= WINDOWS_7) + { + sortFunctions[PHPRTLC_PRIVATEWS] = SORT_FUNCTION(PrivateWsWin7); + sortFunctions[PHPRTLC_CYCLES] = SORT_FUNCTION(CyclesWin7); + sortFunctions[PHPRTLC_CYCLESDELTA] = SORT_FUNCTION(CyclesDeltaWin7); + } + + PhEndInitOnce(&initOnce); + } + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)ProcessNodeList->Items, + ProcessNodeList->Count, + ProcessTreeListSortColumn, + ProcessTreeListSortOrder, + &ProcessTreeListCm + )) + { + if (ProcessTreeListSortColumn < PHPRTLC_MAXIMUM) + sortFunction = sortFunctions[ProcessTreeListSortColumn]; + else + sortFunction = NULL; + + if (sortFunction) + { + qsort(ProcessNodeList->Items, ProcessNodeList->Count, sizeof(PVOID), sortFunction); + } + } + + getChildren->Children = (PPH_TREENEW_NODE *)ProcessNodeList->Items; + getChildren->NumberOfChildren = ProcessNodeList->Count; + } + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + node = (PPH_PROCESS_NODE)isLeaf->Node; + + if (ProcessTreeListSortOrder == NoSortOrder) + isLeaf->IsLeaf = node->Children->Count == 0; + else + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_PROCESS_ITEM processItem; + + node = (PPH_PROCESS_NODE)getCellText->Node; + processItem = node->ProcessItem; + + switch (getCellText->Id) + { + case PHPRTLC_NAME: + getCellText->Text = processItem->ProcessName->sr; + break; + case PHPRTLC_PID: + PhInitializeStringRefLongHint(&getCellText->Text, processItem->ProcessIdString); + break; + case PHPRTLC_CPU: + { + FLOAT cpuUsage = 0; + + PhpAggregateFieldIfNeeded(node, AggregateTypeFloat, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CpuUsage), &cpuUsage); + cpuUsage *= 100; + + if (cpuUsage >= 0.01) + { + PH_FORMAT format; + SIZE_T returnLength; + + PhInitFormatF(&format, cpuUsage, 2); + + if (PhFormatToBuffer(&format, 1, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength)) + { + getCellText->Text.Buffer = node->CpuUsageText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); // minus null terminator + } + } + else if (cpuUsage != 0 && PhCsShowCpuBelow001) + { + PH_FORMAT format[2]; + SIZE_T returnLength; + + PhInitFormatS(&format[0], L"< "); + PhInitFormatF(&format[1], 0.01, 2); + + if (PhFormatToBuffer(format, 2, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength)) + { + getCellText->Text.Buffer = node->CpuUsageText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); + } + } + } + break; + case PHPRTLC_IOTOTALRATE: + { + ULONG64 number = 0; + + if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value) // delta is wrong on first run of process provider + { + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoReadDelta.Delta), &number); + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoWriteDelta.Delta), &number); + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoOtherDelta.Delta), &number); + number *= 1000; + number /= PhCsUpdateInterval; + } + + if (number != 0) + { + PH_FORMAT format[2]; + + PhInitFormatSize(&format[0], number); + PhInitFormatS(&format[1], L"/s"); + PhMoveReference(&node->IoTotalRateText, PhFormat(format, 2, 0)); + getCellText->Text = node->IoTotalRateText->sr; + } + } + break; + case PHPRTLC_PRIVATEBYTES: + { + SIZE_T value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeIntPtr, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, VmCounters.PagefileUsage), &value); + PhMoveReference(&node->PrivateBytesText, PhFormatSize(value, -1)); + getCellText->Text = node->PrivateBytesText->sr; + } + break; + case PHPRTLC_USERNAME: + getCellText->Text = PhGetStringRef(processItem->UserName); + break; + case PHPRTLC_DESCRIPTION: + if (processItem->VersionInfo.FileDescription) + getCellText->Text = processItem->VersionInfo.FileDescription->sr; + else + getCellText->Text = node->DescriptionText; + break; + case PHPRTLC_COMPANYNAME: + getCellText->Text = PhGetStringRef(processItem->VersionInfo.CompanyName); + break; + case PHPRTLC_VERSION: + getCellText->Text = PhGetStringRef(processItem->VersionInfo.FileVersion); + break; + case PHPRTLC_FILENAME: + getCellText->Text = PhGetStringRef(processItem->FileName); + break; + case PHPRTLC_COMMANDLINE: + getCellText->Text = PhGetStringRef(processItem->CommandLine); + break; + case PHPRTLC_PEAKPRIVATEBYTES: + PhMoveReference(&node->PeakPrivateBytesText, PhFormatSize(processItem->VmCounters.PeakPagefileUsage, -1)); + getCellText->Text = node->PeakPrivateBytesText->sr; + break; + case PHPRTLC_WORKINGSET: + { + SIZE_T value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeIntPtr, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, VmCounters.WorkingSetSize), &value); + PhMoveReference(&node->WorkingSetText, PhFormatSize(value, -1)); + getCellText->Text = node->WorkingSetText->sr; + } + break; + case PHPRTLC_PEAKWORKINGSET: + PhMoveReference(&node->PeakWorkingSetText, PhFormatSize(processItem->VmCounters.PeakWorkingSetSize, -1)); + getCellText->Text = node->PeakWorkingSetText->sr; + break; + case PHPRTLC_PRIVATEWS: + if (WindowsVersion >= WINDOWS_7) + { + PhMoveReference(&node->PrivateWsText, PhFormatSize(processItem->WorkingSetPrivateSize, -1)); + } + else + { + PhpUpdateProcessNodeWsCounters(node); + PhMoveReference(&node->PrivateWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfPrivatePages * PAGE_SIZE, -1)); + } + getCellText->Text = node->PrivateWsText->sr; + break; + case PHPRTLC_SHAREDWS: + PhpUpdateProcessNodeWsCounters(node); + PhMoveReference(&node->SharedWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfSharedPages * PAGE_SIZE, -1)); + getCellText->Text = node->SharedWsText->sr; + break; + case PHPRTLC_SHAREABLEWS: + PhpUpdateProcessNodeWsCounters(node); + PhMoveReference(&node->ShareableWsText, PhFormatSize((ULONG64)node->WsCounters.NumberOfShareablePages * PAGE_SIZE, -1)); + getCellText->Text = node->ShareableWsText->sr; + break; + case PHPRTLC_VIRTUALSIZE: + PhMoveReference(&node->VirtualSizeText, PhFormatSize(processItem->VmCounters.VirtualSize, -1)); + getCellText->Text = node->VirtualSizeText->sr; + break; + case PHPRTLC_PEAKVIRTUALSIZE: + PhMoveReference(&node->PeakVirtualSizeText, PhFormatSize(processItem->VmCounters.PeakVirtualSize, -1)); + getCellText->Text = node->PeakVirtualSizeText->sr; + break; + case PHPRTLC_PAGEFAULTS: + PhMoveReference(&node->PageFaultsText, PhFormatUInt64(processItem->VmCounters.PageFaultCount, TRUE)); + getCellText->Text = node->PageFaultsText->sr; + break; + case PHPRTLC_SESSIONID: + PhInitializeStringRefLongHint(&getCellText->Text, processItem->SessionIdString); + break; + case PHPRTLC_PRIORITYCLASS: + PhInitializeStringRefLongHint(&getCellText->Text, PhGetProcessPriorityClassString(processItem->PriorityClass)); + break; + case PHPRTLC_BASEPRIORITY: + PhPrintInt32(node->BasePriorityText, processItem->BasePriority); + PhInitializeStringRefLongHint(&getCellText->Text, node->BasePriorityText); + break; + case PHPRTLC_THREADS: + { + ULONG value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeInt32, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, NumberOfThreads), &value); + PhpFormatInt32GroupDigits(value, node->ThreadsText, sizeof(node->ThreadsText), &getCellText->Text); + } + break; + case PHPRTLC_HANDLES: + { + ULONG value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeInt32, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, NumberOfHandles), &value); + PhpFormatInt32GroupDigits(value, node->HandlesText, sizeof(node->HandlesText), &getCellText->Text); + } + break; + case PHPRTLC_GDIHANDLES: + PhpUpdateProcessNodeGdiUserHandles(node); + PhpFormatInt32GroupDigits(node->GdiHandles, node->GdiHandlesText, sizeof(node->GdiHandlesText), &getCellText->Text); + break; + case PHPRTLC_USERHANDLES: + PhpUpdateProcessNodeGdiUserHandles(node); + PhpFormatInt32GroupDigits(node->UserHandles, node->UserHandlesText, sizeof(node->UserHandlesText), &getCellText->Text); + break; + case PHPRTLC_IORORATE: + { + ULONG64 number = 0; + + if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value) + { + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoReadDelta.Delta), &number); + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoOtherDelta.Delta), &number); + number *= 1000; + number /= PhCsUpdateInterval; + } + + if (number != 0) + { + PH_FORMAT format[2]; + + PhInitFormatSize(&format[0], number); + PhInitFormatS(&format[1], L"/s"); + PhMoveReference(&node->IoRoRateText, PhFormat(format, 2, 0)); + getCellText->Text = node->IoRoRateText->sr; + } + } + break; + case PHPRTLC_IOWRATE: + { + ULONG64 number = 0; + + if (processItem->IoReadDelta.Delta != processItem->IoReadDelta.Value) + { + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, IoWriteDelta.Delta), &number); + number *= 1000; + number /= PhCsUpdateInterval; + } + + if (number != 0) + { + PH_FORMAT format[2]; + + PhInitFormatSize(&format[0], number); + PhInitFormatS(&format[1], L"/s"); + PhMoveReference(&node->IoWRateText, PhFormat(format, 2, 0)); + getCellText->Text = node->IoWRateText->sr; + } + } + break; + case PHPRTLC_INTEGRITY: + if (processItem->IntegrityString) + PhInitializeStringRefLongHint(&getCellText->Text, processItem->IntegrityString); + break; + case PHPRTLC_IOPRIORITY: + PhpUpdateProcessNodeIoPagePriority(node); + if (node->IoPriority != -1 && node->IoPriority < MaxIoPriorityTypes) + PhInitializeStringRefLongHint(&getCellText->Text, PhIoPriorityHintNames[node->IoPriority]); + break; + case PHPRTLC_PAGEPRIORITY: + PhpUpdateProcessNodeIoPagePriority(node); + if (node->PagePriority != -1 && node->PagePriority <= MEMORY_PRIORITY_NORMAL) + PhInitializeStringRefLongHint(&getCellText->Text, PhPagePriorityNames[node->PagePriority]); + break; + case PHPRTLC_STARTTIME: + if (processItem->CreateTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &processItem->CreateTime); + PhMoveReference(&node->StartTimeText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->StartTimeText->sr; + } + break; + case PHPRTLC_TOTALCPUTIME: + PhMoveReference(&node->TotalCpuTimeText, PhFormatTimeSpan( + processItem->KernelTime.QuadPart + processItem->UserTime.QuadPart, + PH_TIMESPAN_HMSM + )); + getCellText->Text = node->TotalCpuTimeText->sr; + break; + case PHPRTLC_KERNELCPUTIME: + PhMoveReference(&node->KernelCpuTimeText, PhFormatTimeSpan( + processItem->KernelTime.QuadPart, + PH_TIMESPAN_HMSM + )); + getCellText->Text = node->KernelCpuTimeText->sr; + break; + case PHPRTLC_USERCPUTIME: + PhMoveReference(&node->UserCpuTimeText, PhFormatTimeSpan( + processItem->UserTime.QuadPart, + PH_TIMESPAN_HMSM + )); + getCellText->Text = node->UserCpuTimeText->sr; + break; + case PHPRTLC_VERIFICATIONSTATUS: + if (processItem->VerifyResult == VrTrusted) + PhInitializeStringRef(&getCellText->Text, L"Trusted"); + break; + case PHPRTLC_VERIFIEDSIGNER: + getCellText->Text = PhGetStringRef(processItem->VerifySignerName); + break; + case PHPRTLC_ASLR: + PhpUpdateProcessNodeImage(node); + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (node->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) + PhInitializeStringRef(&getCellText->Text, L"ASLR"); + } + else + { + PhInitializeStringRef(&getCellText->Text, L"N/A"); + } + break; + case PHPRTLC_RELATIVESTARTTIME: + { + if (processItem->CreateTime.QuadPart != 0) + { + LARGE_INTEGER currentTime; + PPH_STRING startTimeString; + + PhQuerySystemTime(¤tTime); + startTimeString = PhFormatTimeSpanRelative(currentTime.QuadPart - processItem->CreateTime.QuadPart); + PhMoveReference(&node->RelativeStartTimeText, PhConcatStrings2(startTimeString->Buffer, L" ago")); + PhDereferenceObject(startTimeString); + getCellText->Text = node->RelativeStartTimeText->sr; + } + } + break; + case PHPRTLC_BITS: +#ifdef _WIN64 + if (processItem->IsWow64Valid) + PhInitializeStringRef(&getCellText->Text, processItem->IsWow64 ? L"32" : L"64"); +#else + PhInitializeStringRef(&getCellText->Text, L"32"); +#endif + break; + case PHPRTLC_ELEVATION: + { + PWSTR type; + + if (WINDOWS_HAS_UAC) + { + switch (processItem->ElevationType) + { + case TokenElevationTypeDefault: + type = L"N/A"; + break; + case TokenElevationTypeLimited: + type = L"Limited"; + break; + case TokenElevationTypeFull: + type = L"Full"; + break; + default: + type = L"N/A"; + break; + } + } + else + { + type = L""; + } + + PhInitializeStringRefLongHint(&getCellText->Text, type); + } + break; + case PHPRTLC_WINDOWTITLE: + PhpUpdateProcessNodeWindow(node); + PhSwapReference(&node->WindowTitleText, node->WindowText); + getCellText->Text = PhGetStringRef(node->WindowTitleText); + break; + case PHPRTLC_WINDOWSTATUS: + PhpUpdateProcessNodeWindow(node); + + if (node->WindowHandle) + PhInitializeStringRef(&getCellText->Text, node->WindowHung ? L"Not responding" : L"Running"); + + break; + case PHPRTLC_CYCLES: + if (WindowsVersion >= WINDOWS_7) + { + ULONG64 value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CycleTimeDelta.Value), &value); + + if (value != 0) + { + PhMoveReference(&node->CyclesText, PhFormatUInt64(value, TRUE)); + getCellText->Text = node->CyclesText->sr; + } + } + else + { + getCellText->Text = PhGetStringRef(node->CyclesText); + } + break; + case PHPRTLC_CYCLESDELTA: + if (WindowsVersion >= WINDOWS_7) + { + ULONG64 value = 0; + PhpAggregateFieldIfNeeded(node, AggregateTypeInt64, AggregateLocationProcessItem, FIELD_OFFSET(PH_PROCESS_ITEM, CycleTimeDelta.Delta), &value); + + if (value != 0) + { + PhMoveReference(&node->CyclesDeltaText, PhFormatUInt64(value, TRUE)); + getCellText->Text = node->CyclesDeltaText->sr; + } + } + else + { + getCellText->Text = PhGetStringRef(node->CyclesDeltaText); + } + break; + case PHPRTLC_DEP: + PhpUpdateProcessNodeDepStatus(node); + + if (node->DepStatus & PH_PROCESS_DEP_ENABLED) + { + if (node->DepStatus & PH_PROCESS_DEP_PERMANENT) + PhInitializeStringRef(&getCellText->Text, L"DEP (permanent)"); + else + PhInitializeStringRef(&getCellText->Text, L"DEP"); + } + + break; + case PHPRTLC_VIRTUALIZED: + PhpUpdateProcessNodeToken(node); + + if (node->VirtualizationEnabled) + PhInitializeStringRef(&getCellText->Text, L"Virtualized"); + + break; + case PHPRTLC_CONTEXTSWITCHES: + if (processItem->ContextSwitchesDelta.Value != 0) + { + PhMoveReference(&node->ContextSwitchesText, PhFormatUInt64(processItem->ContextSwitchesDelta.Value, TRUE)); + getCellText->Text = node->ContextSwitchesText->sr; + } + break; + case PHPRTLC_CONTEXTSWITCHESDELTA: + if ((LONG)processItem->ContextSwitchesDelta.Delta > 0) // the delta may be negative if a thread exits - just don't show anything + { + PhMoveReference(&node->ContextSwitchesDeltaText, PhFormatUInt64(processItem->ContextSwitchesDelta.Delta, TRUE)); + getCellText->Text = node->ContextSwitchesDeltaText->sr; + } + break; + case PHPRTLC_PAGEFAULTSDELTA: + if (processItem->PageFaultsDelta.Delta != 0) + { + PhMoveReference(&node->PageFaultsDeltaText, PhFormatUInt64(processItem->PageFaultsDelta.Delta, TRUE)); + getCellText->Text = node->PageFaultsDeltaText->sr; + } + break; + case PHPRTLC_IOREADS: + if (processItem->IoReadCountDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[0], PhFormatUInt64(processItem->IoReadCountDelta.Value, TRUE)); + getCellText->Text = node->IoGroupText[0]->sr; + } + break; + case PHPRTLC_IOWRITES: + if (processItem->IoWriteCountDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[1], PhFormatUInt64(processItem->IoWriteCountDelta.Value, TRUE)); + getCellText->Text = node->IoGroupText[1]->sr; + } + break; + case PHPRTLC_IOOTHER: + if (processItem->IoOtherCountDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[2], PhFormatUInt64(processItem->IoOtherCountDelta.Value, TRUE)); + getCellText->Text = node->IoGroupText[2]->sr; + } + break; + case PHPRTLC_IOREADBYTES: + if (processItem->IoReadDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[3], PhFormatSize(processItem->IoReadDelta.Value, -1)); + getCellText->Text = node->IoGroupText[3]->sr; + } + break; + case PHPRTLC_IOWRITEBYTES: + if (processItem->IoWriteDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[4], PhFormatSize(processItem->IoWriteDelta.Value, -1)); + getCellText->Text = node->IoGroupText[4]->sr; + } + break; + case PHPRTLC_IOOTHERBYTES: + if (processItem->IoOtherDelta.Value != 0) + { + PhMoveReference(&node->IoGroupText[5], PhFormatSize(processItem->IoOtherDelta.Value, -1)); + getCellText->Text = node->IoGroupText[5]->sr; + } + break; + case PHPRTLC_IOREADSDELTA: + if (processItem->IoReadCountDelta.Delta != 0) + { + PhMoveReference(&node->IoGroupText[6], PhFormatUInt64(processItem->IoReadCountDelta.Delta, TRUE)); + getCellText->Text = node->IoGroupText[6]->sr; + } + break; + case PHPRTLC_IOWRITESDELTA: + if (processItem->IoWriteCountDelta.Delta != 0) + { + PhMoveReference(&node->IoGroupText[7], PhFormatUInt64(processItem->IoWriteCountDelta.Delta, TRUE)); + getCellText->Text = node->IoGroupText[7]->sr; + } + break; + case PHPRTLC_IOOTHERDELTA: + if (processItem->IoOtherCountDelta.Delta != 0) + { + PhMoveReference(&node->IoGroupText[8], PhFormatUInt64(processItem->IoOtherCountDelta.Delta, TRUE)); + getCellText->Text = node->IoGroupText[8]->sr; + } + break; + case PHPRTLC_OSCONTEXT: + PhpUpdateProcessOsContext(node); + + if (WindowsVersion >= WINDOWS_7) + { + switch (node->OsContextVersion) + { + case WINDOWS_10: + PhInitializeStringRef(&getCellText->Text, L"Windows 10"); + break; + case WINDOWS_8_1: + PhInitializeStringRef(&getCellText->Text, L"Windows 8.1"); + break; + case WINDOWS_8: + PhInitializeStringRef(&getCellText->Text, L"Windows 8"); + break; + case WINDOWS_7: + PhInitializeStringRef(&getCellText->Text, L"Windows 7"); + break; + case WINDOWS_VISTA: + PhInitializeStringRef(&getCellText->Text, L"Windows Vista"); + break; + case WINDOWS_XP: + PhInitializeStringRef(&getCellText->Text, L"Windows XP"); + break; + } + } + else + { + PhInitializeStringRef(&getCellText->Text, L"N/A"); + } + break; + case PHPRTLC_PAGEDPOOL: + PhMoveReference(&node->PagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPagedPoolUsage, -1)); + getCellText->Text = node->PagedPoolText->sr; + break; + case PHPRTLC_PEAKPAGEDPOOL: + PhMoveReference(&node->PeakPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPeakPagedPoolUsage, -1)); + getCellText->Text = node->PeakPagedPoolText->sr; + break; + case PHPRTLC_NONPAGEDPOOL: + PhMoveReference(&node->NonPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaNonPagedPoolUsage, -1)); + getCellText->Text = node->NonPagedPoolText->sr; + break; + case PHPRTLC_PEAKNONPAGEDPOOL: + PhMoveReference(&node->PeakNonPagedPoolText, PhFormatSize(processItem->VmCounters.QuotaPeakNonPagedPoolUsage, -1)); + getCellText->Text = node->PeakNonPagedPoolText->sr; + break; + case PHPRTLC_MINIMUMWORKINGSET: + PhpUpdateProcessNodeQuotaLimits(node); + PhMoveReference(&node->MinimumWorkingSetText, PhFormatSize(node->MinimumWorkingSetSize, -1)); + getCellText->Text = node->MinimumWorkingSetText->sr; + break; + case PHPRTLC_MAXIMUMWORKINGSET: + PhpUpdateProcessNodeQuotaLimits(node); + PhMoveReference(&node->MaximumWorkingSetText, PhFormatSize(node->MaximumWorkingSetSize, -1)); + getCellText->Text = node->MaximumWorkingSetText->sr; + break; + case PHPRTLC_PRIVATEBYTESDELTA: + { + LONG_PTR delta; + + delta = processItem->PrivateBytesDelta.Delta; + + if (delta != 0) + { + PH_FORMAT format[2]; + + if (delta > 0) + { + PhInitFormatC(&format[0], '+'); + } + else + { + PhInitFormatC(&format[0], '-'); + delta = -delta; + } + + format[1].Type = SizeFormatType | FormatUseRadix; + format[1].Radix = (UCHAR)PhMaxSizeUnit; + format[1].u.Size = delta; + + PhMoveReference(&node->PrivateBytesDeltaText, PhFormat(format, 2, 0)); + getCellText->Text = node->PrivateBytesDeltaText->sr; + } + } + break; + case PHPRTLC_SUBSYSTEM: + PhpUpdateProcessNodeImage(node); + + switch (node->ImageSubsystem) + { + case 0: + break; + case IMAGE_SUBSYSTEM_NATIVE: + PhInitializeStringRef(&getCellText->Text, L"Native"); + break; + case IMAGE_SUBSYSTEM_WINDOWS_GUI: + PhInitializeStringRef(&getCellText->Text, L"Windows"); + break; + case IMAGE_SUBSYSTEM_WINDOWS_CUI: + PhInitializeStringRef(&getCellText->Text, L"Windows console"); + break; + case IMAGE_SUBSYSTEM_OS2_CUI: + PhInitializeStringRef(&getCellText->Text, L"OS/2"); + break; + case IMAGE_SUBSYSTEM_POSIX_CUI: + PhInitializeStringRef(&getCellText->Text, L"POSIX"); + break; + default: + PhInitializeStringRef(&getCellText->Text, L"Unknown"); + break; + } + break; + case PHPRTLC_PACKAGENAME: + getCellText->Text = PhGetStringRef(processItem->PackageFullName); + break; + case PHPRTLC_APPID: + PhpUpdateProcessNodeAppId(node); + getCellText->Text = PhGetStringRef(node->AppIdText); + break; + case PHPRTLC_DPIAWARENESS: + PhpUpdateProcessNodeDpiAwareness(node); + + switch (node->DpiAwareness) + { + case 0: + break; + case 1: + PhInitializeStringRef(&getCellText->Text, L"Unaware"); + break; + case 2: + PhInitializeStringRef(&getCellText->Text, L"System aware"); + break; + case 3: + PhInitializeStringRef(&getCellText->Text, L"Per-monitor aware"); + break; + } + break; + case PHPRTLC_CFGUARD: + PhpUpdateProcessNodeImage(node); + + if (WindowsVersion >= WINDOWS_8_1) + { + if (node->ImageDllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF) + PhInitializeStringRef(&getCellText->Text, L"CF Guard"); + } + else + { + PhInitializeStringRef(&getCellText->Text, L"N/A"); + } + break; + case PHPRTLC_TIMESTAMP: + PhpUpdateProcessNodeImage(node); + + if (node->ImageTimeDateStamp != 0) + { + LARGE_INTEGER time; + SYSTEMTIME systemTime; + + RtlSecondsSince1970ToTime(node->ImageTimeDateStamp, &time); + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + + PhMoveReference(&node->TimeStampText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->TimeStampText->sr; + } + break; + case PHPRTLC_FILEMODIFIEDTIME: + PhpUpdateProcessNodeFileAttributes(node); + + if (node->FileLastWriteTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &node->FileLastWriteTime); + PhMoveReference(&node->FileModifiedTimeText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->FileModifiedTimeText->sr; + } + break; + case PHPRTLC_FILESIZE: + PhpUpdateProcessNodeFileAttributes(node); + + if (node->FileEndOfFile.QuadPart != -1) + { + PhMoveReference(&node->FileSizeText, PhFormatSize(node->FileEndOfFile.QuadPart, -1)); + getCellText->Text = node->FileSizeText->sr; + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + PPH_PROCESS_ITEM processItem; + + node = (PPH_PROCESS_NODE)getNodeColor->Node; + processItem = node->ProcessItem; + + if (PhPluginsEnabled) + { + PH_PLUGIN_GET_HIGHLIGHTING_COLOR getHighlightingColor; + + getHighlightingColor.Parameter = processItem; + getHighlightingColor.BackColor = RGB(0xff, 0xff, 0xff); + getHighlightingColor.Handled = FALSE; + getHighlightingColor.Cache = FALSE; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackGetProcessHighlightingColor), &getHighlightingColor); + + if (getHighlightingColor.Handled) + { + getNodeColor->BackColor = getHighlightingColor.BackColor; + getNodeColor->Flags = TN_AUTO_FORECOLOR; + + if (getHighlightingColor.Cache) + getNodeColor->Flags |= TN_CACHE; + + return TRUE; + } + } + + if (!processItem) + ; // Dummy + else if (PhCsUseColorDebuggedProcesses && processItem->IsBeingDebugged) + getNodeColor->BackColor = PhCsColorDebuggedProcesses; + else if (PhCsUseColorSuspended && processItem->IsSuspended) + getNodeColor->BackColor = PhCsColorSuspended; + else if (PhCsUseColorElevatedProcesses && processItem->IsElevated) + getNodeColor->BackColor = PhCsColorElevatedProcesses; + else if (PhCsUseColorImmersiveProcesses && processItem->IsImmersive) + getNodeColor->BackColor = PhCsColorImmersiveProcesses; + else if (PhCsUseColorDotNet && processItem->IsDotNet) + getNodeColor->BackColor = PhCsColorDotNet; + else if (PhCsUseColorPacked && processItem->IsPacked) + getNodeColor->BackColor = PhCsColorPacked; + else if (PhCsUseColorWow64Processes && processItem->IsWow64) + getNodeColor->BackColor = PhCsColorWow64Processes; + else if (PhCsUseColorJobProcesses && processItem->IsInSignificantJob) + getNodeColor->BackColor = PhCsColorJobProcesses; + else if (PhCsUseColorServiceProcesses && processItem->ServiceList && processItem->ServiceList->Count != 0) + getNodeColor->BackColor = PhCsColorServiceProcesses; + else if ( + PhCsUseColorSystemProcesses && + processItem->UserName && + PhEqualString(processItem->UserName, PhLocalSystemName, TRUE) + ) + getNodeColor->BackColor = PhCsColorSystemProcesses; + else if ( + PhCsUseColorOwnProcesses && + processItem->UserName && + PhCurrentUserName && + PhEqualString(processItem->UserName, PhCurrentUserName, TRUE) + ) + getNodeColor->BackColor = PhCsColorOwnProcesses; + + getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR; + } + return TRUE; + case TreeNewGetNodeIcon: + { + PPH_TREENEW_GET_NODE_ICON getNodeIcon = Parameter1; + + node = (PPH_PROCESS_NODE)getNodeIcon->Node; + + if (node->ProcessItem->SmallIcon) + { + getNodeIcon->Icon = node->ProcessItem->SmallIcon; + } + else + { + PhGetStockApplicationIcon(&getNodeIcon->Icon, NULL); + } + + getNodeIcon->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + ULONG tickCount; + + node = (PPH_PROCESS_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0) + return FALSE; + + tickCount = GetTickCount(); + + if ((LONG)(node->TooltipTextValidToTickCount - tickCount) < 0) + PhClearReference(&node->TooltipText); + if (!node->TooltipText) + node->TooltipText = PhGetProcessTooltipText(node->ProcessItem, &node->TooltipTextValidToTickCount); + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->MaximumWidth = -1; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewCustomDraw: + { + PPH_TREENEW_CUSTOM_DRAW customDraw = Parameter1; + PPH_PROCESS_ITEM processItem; + RECT rect; + PH_GRAPH_DRAW_INFO drawInfo; + + node = (PPH_PROCESS_NODE)customDraw->Node; + processItem = node->ProcessItem; + rect = customDraw->CellRect; + + if (rect.right - rect.left <= 1) + break; // nothing to draw + + // Generic graph pre-processing + switch (customDraw->Column->Id) + { + case PHPRTLC_CPUHISTORY: + case PHPRTLC_PRIVATEBYTESHISTORY: + case PHPRTLC_IOHISTORY: + memset(&drawInfo, 0, sizeof(PH_GRAPH_DRAW_INFO)); + drawInfo.Width = rect.right - rect.left - 1; // leave a small gap + drawInfo.Height = rect.bottom - rect.top - 1; // leave a small gap + drawInfo.Step = 2; + drawInfo.BackColor = RGB(0x00, 0x00, 0x00); + break; + } + + // Specific graph processing + switch (customDraw->Column->Id) + { + case PHPRTLC_CPUHISTORY: + { + drawInfo.Flags = PH_GRAPH_USE_LINE_2; + drawInfo.LineColor1 = PhCsColorCpuKernel; + drawInfo.LineColor2 = PhCsColorCpuUser; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorCpuKernel); + drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorCpuUser); + + PhGetDrawInfoGraphBuffers( + &node->CpuGraphBuffers, + &drawInfo, + processItem->CpuKernelHistory.Count + ); + + if (!node->CpuGraphBuffers.Valid) + { + PhCopyCircularBuffer_FLOAT(&processItem->CpuKernelHistory, + node->CpuGraphBuffers.Data1, drawInfo.LineDataCount); + PhCopyCircularBuffer_FLOAT(&processItem->CpuUserHistory, + node->CpuGraphBuffers.Data2, drawInfo.LineDataCount); + node->CpuGraphBuffers.Valid = TRUE; + } + } + break; + case PHPRTLC_PRIVATEBYTESHISTORY: + { + drawInfo.Flags = 0; + drawInfo.LineColor1 = PhCsColorPrivate; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorPrivate); + + PhGetDrawInfoGraphBuffers( + &node->PrivateGraphBuffers, + &drawInfo, + processItem->PrivateBytesHistory.Count + ); + + if (!node->PrivateGraphBuffers.Valid) + { + ULONG i; + FLOAT total; + FLOAT max; + + for (i = 0; i < drawInfo.LineDataCount; i++) + { + node->PrivateGraphBuffers.Data1[i] = + (FLOAT)PhGetItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, i); + } + + // This makes it easier for the user to see what processes are hogging memory. + // Scaling is still *not* consistent across all graphs. + total = (FLOAT)PhPerfInformation.CommittedPages * PAGE_SIZE / 4; // divide by 4 to make the scaling a bit better + max = (FLOAT)processItem->VmCounters.PeakPagefileUsage; + + if (max < total) + max = total; + + if (max != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + node->PrivateGraphBuffers.Data1, + max, + drawInfo.LineDataCount + ); + } + + node->PrivateGraphBuffers.Valid = TRUE; + } + } + break; + case PHPRTLC_IOHISTORY: + { + drawInfo.Flags = PH_GRAPH_USE_LINE_2; + drawInfo.LineColor1 = PhCsColorIoReadOther; + drawInfo.LineColor2 = PhCsColorIoWrite; + drawInfo.LineBackColor1 = PhHalveColorBrightness(PhCsColorIoReadOther); + drawInfo.LineBackColor2 = PhHalveColorBrightness(PhCsColorIoWrite); + + PhGetDrawInfoGraphBuffers( + &node->IoGraphBuffers, + &drawInfo, + processItem->IoReadHistory.Count + ); + + if (!node->IoGraphBuffers.Valid) + { + ULONG i; + FLOAT total; + FLOAT max = 0; + + for (i = 0; i < drawInfo.LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + node->IoGraphBuffers.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, i); + node->IoGraphBuffers.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + // Make the scaling a bit more consistent across the processes. + // It does *not* scale all graphs using the same maximum. + total = (FLOAT)(PhIoReadDelta.Delta + PhIoWriteDelta.Delta + PhIoOtherDelta.Delta); + + if (max < total) + max = total; + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + node->IoGraphBuffers.Data1, + max, + drawInfo.LineDataCount + ); + PhDivideSinglesBySingle( + node->IoGraphBuffers.Data2, + max, + drawInfo.LineDataCount + ); + } + + node->IoGraphBuffers.Valid = TRUE; + } + } + break; + } + + // Draw the graph. + switch (customDraw->Column->Id) + { + case PHPRTLC_CPUHISTORY: + case PHPRTLC_PRIVATEBYTESHISTORY: + case PHPRTLC_IOHISTORY: + PhpNeedGraphContext(customDraw->Dc, drawInfo.Width, drawInfo.Height); + + if (GraphBits) + { + PhDrawGraphDirect(GraphContext, GraphBits, &drawInfo); + BitBlt( + customDraw->Dc, + rect.left, + rect.top + 1, // + 1 for small gap + drawInfo.Width, + drawInfo.Height, + GraphContext, + 0, + 0, + SRCCOPY + ); + } + + break; + } + } + return TRUE; + case TreeNewColumnResized: + { + PPH_TREENEW_COLUMN column = Parameter1; + ULONG i; + + if (column->Id == PHPRTLC_CPUHISTORY || column->Id == PHPRTLC_IOHISTORY || column->Id == PHPRTLC_PRIVATEBYTESHISTORY) + { + for (i = 0; i < ProcessNodeList->Count; i++) + { + node = ProcessNodeList->Items[i]; + + if (column->Id == PHPRTLC_CPUHISTORY) + node->CpuGraphBuffers.Valid = FALSE; + if (column->Id == PHPRTLC_IOHISTORY) + node->IoGraphBuffers.Valid = FALSE; + if (column->Id == PHPRTLC_PRIVATEBYTESHISTORY) + node->PrivateGraphBuffers.Valid = FALSE; + } + } + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &ProcessTreeListSortColumn, &ProcessTreeListSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(ProcessTreeListHandle, 0, -1); + break; + case VK_DELETE: + if (GetKeyState(VK_SHIFT) >= 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_TERMINATE, 0); + else + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_TERMINATETREE, 0); + break; + case VK_RETURN: + if (GetKeyState(VK_CONTROL) >= 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_PROPERTIES, 0); + else + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_OPENFILELOCATION, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = NoSortOrder; + PhInitializeTreeNewColumnMenuEx(&data, PH_TN_COLUMN_MENU_SHOW_RESET_SORT); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + + if (data.ProcessedId == PH_TN_COLUMN_MENU_HIDE_COLUMN_ID || data.ProcessedId == PH_TN_COLUMN_MENU_CHOOSE_COLUMNS_ID) + PhpUpdateNeedCyclesInformation(); + + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(PhMainWndHandle, WM_COMMAND, ID_PROCESS_PROPERTIES, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + PhShowProcessContextMenu(contextMenu); + } + return TRUE; + case TreeNewNodeExpanding: + { + node = Parameter1; + + if (PhCsPropagateCpuUsage) + PhUpdateProcessNode(node); + } + return TRUE; + } + + return FALSE; +} + +PPH_PROCESS_ITEM PhGetSelectedProcessItem( + VOID + ) +{ + PPH_PROCESS_ITEM processItem = NULL; + ULONG i; + + for (i = 0; i < ProcessNodeList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeList->Items[i]; + + if (node->Node.Selected) + { + processItem = node->ProcessItem; + break; + } + } + + return processItem; +} + +VOID PhGetSelectedProcessItems( + _Out_ PPH_PROCESS_ITEM **Processes, + _Out_ PULONG NumberOfProcesses + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < ProcessNodeList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->ProcessItem); + } + + *NumberOfProcesses = (ULONG)array.Count; + *Processes = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllProcessNodes( + VOID + ) +{ + TreeNew_DeselectRange(ProcessTreeListHandle, 0, -1); +} + +VOID PhExpandAllProcessNodes( + _In_ BOOLEAN Expand + ) +{ + ULONG i; + BOOLEAN needsRestructure = FALSE; + + for (i = 0; i < ProcessNodeList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeList->Items[i]; + + if (node->Children->Count != 0 && node->Node.Expanded != Expand) + { + node->Node.Expanded = Expand; + needsRestructure = TRUE; + } + } + + if (needsRestructure) + TreeNew_NodesStructured(ProcessTreeListHandle); +} + +VOID PhInvalidateAllProcessNodes( + VOID + ) +{ + ULONG i; + + for (i = 0; i < ProcessNodeList->Count; i++) + { + PPH_PROCESS_NODE node = ProcessNodeList->Items[i]; + + memset(node->TextCache, 0, sizeof(PH_STRINGREF) * PHPRTLC_MAXIMUM); + PhInvalidateTreeNewNode(&node->Node, TN_CACHE_COLOR); + node->ValidMask = 0; + + // Invalidate graph buffers. + node->CpuGraphBuffers.Valid = FALSE; + node->PrivateGraphBuffers.Valid = FALSE; + node->IoGraphBuffers.Valid = FALSE; + } + + InvalidateRect(ProcessTreeListHandle, NULL, FALSE); +} + +VOID PhSelectAndEnsureVisibleProcessNode( + _In_ PPH_PROCESS_NODE ProcessNode + ) +{ + PhSelectAndEnsureVisibleProcessNodes(&ProcessNode, 1); +} + +VOID PhSelectAndEnsureVisibleProcessNodes( + _In_ PPH_PROCESS_NODE *ProcessNodes, + _In_ ULONG NumberOfProcessNodes + ) +{ + ULONG i; + PPH_PROCESS_NODE leader = NULL; + PPH_PROCESS_NODE node; + BOOLEAN needsRestructure = FALSE; + + PhDeselectAllProcessNodes(); + + for (i = 0; i < NumberOfProcessNodes; i++) + { + if (ProcessNodes[i]->Node.Visible) + { + leader = ProcessNodes[i]; + break; + } + } + + if (!leader) + return; + + // Expand recursively upwards, and select the nodes. + + for (i = 0; i < NumberOfProcessNodes; i++) + { + if (!ProcessNodes[i]->Node.Visible) + continue; + + node = ProcessNodes[i]->Parent; + + while (node) + { + if (!node->Node.Expanded) + needsRestructure = TRUE; + + node->Node.Expanded = TRUE; + node = node->Parent; + } + + ProcessNodes[i]->Node.Selected = TRUE; + } + + if (needsRestructure) + TreeNew_NodesStructured(ProcessTreeListHandle); + + TreeNew_SetFocusNode(ProcessTreeListHandle, &leader->Node); + TreeNew_SetMarkNode(ProcessTreeListHandle, &leader->Node); + TreeNew_EnsureVisible(ProcessTreeListHandle, &leader->Node); + TreeNew_InvalidateNode(ProcessTreeListHandle, &leader->Node); +} + +VOID PhpPopulateTableWithProcessNodes( + _In_ HWND TreeListHandle, + _In_ PPH_PROCESS_NODE Node, + _In_ ULONG Level, + _In_ PPH_STRING **Table, + _Inout_ PULONG Index, + _In_ PULONG DisplayToId, + _In_ ULONG Columns + ) +{ + ULONG i; + + for (i = 0; i < Columns; i++) + { + PH_TREENEW_GET_CELL_TEXT getCellText; + PPH_STRING text; + + getCellText.Node = &Node->Node; + getCellText.Id = DisplayToId[i]; + PhInitializeEmptyStringRef(&getCellText.Text); + TreeNew_GetCellText(TreeListHandle, &getCellText); + + if (i != 0) + { + text = PhaCreateStringEx(getCellText.Text.Buffer, getCellText.Text.Length); + } + else + { + // If this is the first column in the row, add some indentation. + text = PhaCreateStringEx( + NULL, + getCellText.Text.Length + Level * 2 * sizeof(WCHAR) + ); + wmemset(text->Buffer, ' ', Level * 2); + memcpy(&text->Buffer[Level * 2], getCellText.Text.Buffer, getCellText.Text.Length); + } + + Table[*Index][i] = text; + } + + (*Index)++; + + // Process the children. + for (i = 0; i < Node->Children->Count; i++) + { + PhpPopulateTableWithProcessNodes( + TreeListHandle, + Node->Children->Items[i], + Level + 1, + Table, + Index, + DisplayToId, + Columns + ); + } +} + +PPH_LIST PhGetProcessTreeListLines( + _In_ HWND TreeListHandle, + _In_ ULONG NumberOfNodes, + _In_ PPH_LIST RootNodes, + _In_ ULONG Mode + ) +{ + PH_AUTO_POOL autoPool; + PPH_LIST lines; + // The number of rows in the table (including +1 for the column headers). + ULONG rows; + // The number of columns. + ULONG columns; + // A column display index to ID map. + PULONG displayToId; + // A column display index to text map. + PWSTR *displayToText; + // The actual string table. + PPH_STRING **table; + ULONG i; + ULONG j; + + // Use a local auto-pool to make memory mangement a bit less painful. + PhInitializeAutoPool(&autoPool); + + rows = NumberOfNodes + 1; + + // Create the display index to ID map. + PhMapDisplayIndexTreeNew(TreeListHandle, &displayToId, &displayToText, &columns); + + PhaCreateTextTable(&table, rows, columns); + + // Populate the first row with the column headers. + for (i = 0; i < columns; i++) + { + table[0][i] = PhaCreateString(displayToText[i]); + } + + // Go through the nodes in the process tree and populate each cell of the table. + + j = 1; // index starts at one because the first row contains the column headers. + + for (i = 0; i < RootNodes->Count; i++) + { + PhpPopulateTableWithProcessNodes( + TreeListHandle, + RootNodes->Items[i], + 0, + table, + &j, + displayToId, + columns + ); + } + + PhFree(displayToId); + PhFree(displayToText); + + lines = PhaFormatTextTable(table, rows, columns, Mode); + + PhDeleteAutoPool(&autoPool); + + return lines; +} + +VOID PhCopyProcessTree( + VOID + ) +{ + PPH_STRING text; + + text = PhGetTreeNewText(ProcessTreeListHandle, 0); + PhSetClipboardString(ProcessTreeListHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID PhWriteProcessTree( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ) +{ + PPH_LIST lines; + ULONG i; + + lines = PhGetProcessTreeListLines( + ProcessTreeListHandle, + ProcessNodeList->Count, + ProcessNodeRootList, + Mode + ); + + for (i = 0; i < lines->Count; i++) + { + PPH_STRING line; + + line = lines->Items[i]; + PhWriteStringAsUtf8FileStream(FileStream, &line->sr); + PhDereferenceObject(line); + PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n"); + } + + PhDereferenceObject(lines); +} + +PPH_LIST PhDuplicateProcessNodeList( + VOID + ) +{ + PPH_LIST newList; + + newList = PhCreateList(ProcessNodeList->Count); + PhInsertItemsList(newList, 0, ProcessNodeList->Items, ProcessNodeList->Count); + + return newList; +} diff --git a/ProcessHacker/prpgenv.c b/ProcessHacker/prpgenv.c new file mode 100644 index 0000000..5129915 --- /dev/null +++ b/ProcessHacker/prpgenv.c @@ -0,0 +1,608 @@ +/* + * Process Hacker - + * Process properties: Environment page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +typedef struct _EDIT_ENV_DIALOG_CONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + PWSTR Name; + PWSTR Value; + BOOLEAN Refresh; + + PH_LAYOUT_MANAGER LayoutManager; + RECT MinimumSize; +} EDIT_ENV_DIALOG_CONTEXT, *PEDIT_ENV_DIALOG_CONTEXT; + +INT_PTR PhpShowEditEnvDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PWSTR Name, + _In_opt_ PWSTR Value, + _Out_opt_ PBOOLEAN Refresh + ); + +INT_PTR CALLBACK PhpEditEnvDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static VOID PhpClearEnvironmentItems( + _Inout_ PPH_ENVIRONMENT_CONTEXT Context + ) +{ + ULONG i; + PPH_ENVIRONMENT_ITEM item; + + for (i = 0; i < Context->Items.Count; i++) + { + item = PhItemArray(&Context->Items, i); + PhDereferenceObject(item->Name); + PhDereferenceObject(item->Value); + } + + PhClearArray(&Context->Items); +} + +static VOID PhpRefreshEnvironment( + _In_ HWND hwndDlg, + _Inout_ PPH_ENVIRONMENT_CONTEXT Context, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PVOID selectedIndex; + HANDLE processHandle; + PVOID environment; + ULONG environmentLength; + ULONG enumerationKey; + PH_ENVIRONMENT_VARIABLE variable; + + ExtendedListView_SetRedraw(Context->ListViewHandle, FALSE); + + selectedIndex = PhGetSelectedListViewItemParam(Context->ListViewHandle); + ListView_DeleteAllItems(Context->ListViewHandle); + PhpClearEnvironmentItems(Context); + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ProcessItem->ProcessId + ))) + { + ULONG flags; + + flags = 0; + +#ifdef _WIN64 + if (ProcessItem->IsWow64) + flags |= PH_GET_PROCESS_ENVIRONMENT_WOW64; +#endif + + if (NT_SUCCESS(PhGetProcessEnvironment( + processHandle, + flags, + &environment, + &environmentLength + ))) + { + enumerationKey = 0; + + while (PhEnumProcessEnvironmentVariables(environment, environmentLength, &enumerationKey, &variable)) + { + PH_ENVIRONMENT_ITEM item; + INT lvItemIndex; + + // Don't display pairs with no name. + if (variable.Name.Length == 0) + continue; + + // The strings are not guaranteed to be null-terminated, so we need to create some temporary strings. + item.Name = PhCreateString2(&variable.Name); + item.Value = PhCreateString2(&variable.Value); + + lvItemIndex = PhAddListViewItem(Context->ListViewHandle, MAXINT, item.Name->Buffer, + UlongToPtr((ULONG)Context->Items.Count + 1)); + PhSetListViewSubItem(Context->ListViewHandle, lvItemIndex, 1, item.Value->Buffer); + + PhAddItemArray(&Context->Items, &item); + } + + PhFreePage(environment); + } + + NtClose(processHandle); + } + + if (selectedIndex) + { + ListView_SetItemState(Context->ListViewHandle, PtrToUlong(selectedIndex) - 1, + LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + ListView_EnsureVisible(Context->ListViewHandle, PtrToUlong(selectedIndex) - 1, FALSE); + } + + ExtendedListView_SetRedraw(Context->ListViewHandle, TRUE); +} + +INT_PTR CALLBACK PhpProcessEnvironmentDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_ENVIRONMENT_CONTEXT environmentContext; + HWND lvHandle; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + environmentContext = propPageContext->Context; + + if (environmentContext) + lvHandle = environmentContext->ListViewHandle; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + environmentContext = propPageContext->Context = PhAllocate(sizeof(PH_ENVIRONMENT_CONTEXT)); + memset(environmentContext, 0, sizeof(PH_ENVIRONMENT_CONTEXT)); + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + environmentContext->ListViewHandle = lvHandle; + PhInitializeArray(&environmentContext->Items, sizeof(PH_ENVIRONMENT_ITEM), 100); + + PhSetListViewStyle(lvHandle, TRUE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 140, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 200, L"Value"); + + PhSetExtendedListView(lvHandle); + PhLoadListViewColumnsFromSetting(L"EnvironmentListViewColumns", lvHandle); + + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + PhpRefreshEnvironment(hwndDlg, environmentContext, processItem); + } + break; + case WM_DESTROY: + { + PhSaveListViewColumnsToSetting(L"EnvironmentListViewColumns", GetDlgItem(hwndDlg, IDC_LIST)); + + PhpClearEnvironmentItems(environmentContext); + PhDeleteArray(&environmentContext->Items); + + PhFree(environmentContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), + dialogItem, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_NEW), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_EDIT), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_DELETE), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_NEW: + { + BOOLEAN refresh; + + if (PhpShowEditEnvDialog(hwndDlg, processItem, L"", NULL, &refresh) == IDOK && + refresh) + { + PhpRefreshEnvironment(hwndDlg, environmentContext, processItem); + } + } + break; + case IDC_EDIT: + case ID_ENVIRONMENT_EDIT: + { + PVOID index = PhGetSelectedListViewItemParam(lvHandle); + + if (index) + { + PPH_ENVIRONMENT_ITEM item = PhItemArray(&environmentContext->Items, PtrToUlong(index) - 1); + BOOLEAN refresh; + + if (PhpShowEditEnvDialog(hwndDlg, processItem, item->Name->Buffer, + item->Value->Buffer, &refresh) == IDOK && refresh) + { + PhpRefreshEnvironment(hwndDlg, environmentContext, processItem); + } + } + } + break; + case IDC_DELETE: + case ID_ENVIRONMENT_DELETE: + { + NTSTATUS status; + PVOID *indices; + ULONG numberOfIndices; + HANDLE processHandle; + LARGE_INTEGER timeout; + ULONG i; + PPH_ENVIRONMENT_ITEM item; + + PhGetSelectedListViewItemParams(lvHandle, &indices, &numberOfIndices); + + if (numberOfIndices != 0 && PhShowConfirmMessage( + hwndDlg, + L"delete", + numberOfIndices != 1 ? L"the selected environment variables" : L"the selected environment variable", + NULL, + FALSE)) + { + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | + PROCESS_VM_READ | PROCESS_VM_WRITE, + processItem->ProcessId + ))) + { + timeout.QuadPart = -10 * PH_TIMEOUT_SEC; + + for (i = 0; i < numberOfIndices; i++) + { + item = PhItemArray(&environmentContext->Items, PtrToUlong(indices[i]) - 1); + PhSetEnvironmentVariableRemote(processHandle, &item->Name->sr, NULL, &timeout); + } + + NtClose(processHandle); + + PhpRefreshEnvironment(hwndDlg, environmentContext, processItem); + } + else + { + PhShowStatus(hwndDlg, L"Unable to open the process", status, 0); + } + } + + PhFree(indices); + } + break; + case ID_ENVIRONMENT_COPY: + { + PhCopyListView(lvHandle); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + PhHandleListViewNotifyBehaviors(lParam, lvHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == lvHandle) + { + ULONG selectedCount; + + selectedCount = ListView_GetSelectedCount(lvHandle); + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), selectedCount == 1); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), selectedCount >= 1); + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == lvHandle) + { + SendMessage(hwndDlg, WM_COMMAND, ID_ENVIRONMENT_EDIT, 0); + } + } + break; + case LVN_KEYDOWN: + { + if (header->hwndFrom == lvHandle) + { + LPNMLVKEYDOWN keyDown = (LPNMLVKEYDOWN)header; + + switch (keyDown->wVKey) + { + case VK_DELETE: + SendMessage(hwndDlg, WM_COMMAND, ID_ENVIRONMENT_DELETE, 0); + break; + } + } + } + break; + } + } + break; + case WM_CONTEXTMENU: + { + if ((HWND)wParam == lvHandle) + { + POINT point; + PVOID *indices; + ULONG numberOfIndices; + + point.x = (SHORT)LOWORD(lParam); + point.y = (SHORT)HIWORD(lParam); + + if (point.x == -1 && point.y == -1) + PhGetListViewContextMenuPoint((HWND)wParam, &point); + + PhGetSelectedListViewItemParams(lvHandle, &indices, &numberOfIndices); + + if (numberOfIndices != 0) + { + PPH_EMENU menu; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_ENVIRONMENT), 0); + PhSetFlagsEMenuItem(menu, ID_ENVIRONMENT_EDIT, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + if (numberOfIndices > 1) + PhEnableEMenuItem(menu, ID_ENVIRONMENT_EDIT, FALSE); + + PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + point.x, + point.y + ); + PhDestroyEMenu(menu); + } + + PhFree(indices); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhpEditEnvDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PEDIT_ENV_DIALOG_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PEDIT_ENV_DIALOG_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + else + { + context = (PEDIT_ENV_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_NAME), NULL, + PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_VALUE), NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDCANCEL), NULL, + PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhLayoutManagerLayout(&context->LayoutManager); + + context->MinimumSize.left = 0; + context->MinimumSize.top = 0; + context->MinimumSize.right = 200; + context->MinimumSize.bottom = 140; + MapDialogRect(hwndDlg, &context->MinimumSize); + + SetDlgItemText(hwndDlg, IDC_NAME, context->Name); + SetDlgItemText(hwndDlg, IDC_VALUE, context->Value ? context->Value : L""); + + if (context->Value) + { + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_VALUE), TRUE); + } + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + EndDialog(hwndDlg, IDCANCEL); + } + break; + case IDOK: + { + NTSTATUS status; + PPH_STRING name = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_NAME))); + PPH_STRING value = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_VALUE))); + HANDLE processHandle; + LARGE_INTEGER timeout; + + if (!PhIsNullOrEmptyString(name)) + { + if (!PhEqualString2(name, context->Name, FALSE) || + !context->Value || !PhEqualString2(value, context->Value, FALSE)) + { + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | + PROCESS_VM_READ | PROCESS_VM_WRITE, + context->ProcessItem->ProcessId + ))) + { + timeout.QuadPart = -10 * PH_TIMEOUT_SEC; + + // Delete the old environment variable if necessary. + if (!PhEqualStringZ(context->Name, L"", FALSE) && + !PhEqualString2(name, context->Name, FALSE)) + { + PH_STRINGREF nameSr; + + PhInitializeStringRefLongHint(&nameSr, context->Name); + PhSetEnvironmentVariableRemote( + processHandle, + &nameSr, + NULL, + &timeout + ); + } + + status = PhSetEnvironmentVariableRemote( + processHandle, + &name->sr, + &value->sr, + &timeout + ); + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus(hwndDlg, L"Unable to set the environment variable", status, 0); + break; + } + + context->Refresh = TRUE; + } + + EndDialog(hwndDlg, IDOK); + } + } + break; + case IDC_NAME: + { + if (HIWORD(wParam) == EN_CHANGE) + { + EnableWindow(GetDlgItem(hwndDlg, IDOK), GetWindowTextLength(GetDlgItem(hwndDlg, IDC_NAME)) > 0); + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, context->MinimumSize.right, context->MinimumSize.bottom); + } + break; + } + + return FALSE; +} + +INT_PTR PhpShowEditEnvDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PWSTR Name, + _In_opt_ PWSTR Value, + _Out_opt_ PBOOLEAN Refresh + ) +{ + INT_PTR result; + EDIT_ENV_DIALOG_CONTEXT context; + + memset(&context, 0, sizeof(EDIT_ENV_DIALOG_CONTEXT)); + context.ProcessItem = ProcessItem; + context.Name = Name; + context.Value = Value; + + result = DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_EDITENV), + ParentWindowHandle, + PhpEditEnvDlgProc, + (LPARAM)&context + ); + + if (Refresh) + *Refresh = context.Refresh; + + return result; +} diff --git a/ProcessHacker/prpggen.c b/ProcessHacker/prpggen.c new file mode 100644 index 0000000..e364418 --- /dev/null +++ b/ProcessHacker/prpggen.c @@ -0,0 +1,628 @@ +/* + * Process Hacker - + * Process properties: General page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PWSTR ProtectedSignerStrings[] = + { L"", L" (Authenticode)", L" (CodeGen)", L" (Antimalware)", L" (Lsa)", L" (Windows)", L" (WinTcb)" }; + +NTSTATUS PhpProcessGeneralOpenProcess( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + return PhOpenProcess(Handle, DesiredAccess, (HANDLE)Context); +} + +FORCEINLINE PWSTR PhpGetStringOrNa( + _In_ PPH_STRING String + ) +{ + if (String) + return String->Buffer; + else + return L"N/A"; +} + +VOID PhpUpdateProcessMitigationPolicies( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + NTSTATUS status; + HANDLE processHandle; + PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION information; + + SetDlgItemText(hwndDlg, IDC_MITIGATION, L"N/A"); + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION, + ProcessItem->ProcessId + ))) + { + if (NT_SUCCESS(status = PhGetProcessMitigationPolicy(processHandle, &information))) + { + PH_STRING_BUILDER sb; + PROCESS_MITIGATION_POLICY policy; + PPH_STRING shortDescription; + + PhInitializeStringBuilder(&sb, 100); + + for (policy = 0; policy < MaxProcessMitigationPolicy; policy++) + { + if (information.Pointers[policy] && PhDescribeProcessMitigationPolicy( + policy, + information.Pointers[policy], + &shortDescription, + NULL + )) + { + PhAppendStringBuilder(&sb, &shortDescription->sr); + PhAppendStringBuilder2(&sb, L"; "); + PhDereferenceObject(shortDescription); + } + } + + if (sb.String->Length != 0) + { + PhRemoveEndStringBuilder(&sb, 2); + SetDlgItemText(hwndDlg, IDC_MITIGATION, sb.String->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_MITIGATION, L"None"); + } + + PhDeleteStringBuilder(&sb); + } + + NtClose(processHandle); + } + + if (!NT_SUCCESS(status)) + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWMITIGATION), FALSE); +} + +INT_PTR CALLBACK PhpProcessGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + + if (!PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE processHandle = NULL; + PPH_STRING curDir = NULL; + PROCESS_BASIC_INFORMATION basicInfo; +#ifdef _WIN64 + PVOID peb32; +#endif + PPH_PROCESS_ITEM parentProcess; + CLIENT_ID clientId; + HICON folder; + HICON magnifier; + + folder = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_FOLDER)); + magnifier = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_MAGNIFIER)); + + SET_BUTTON_ICON(IDC_INSPECT, magnifier); + SET_BUTTON_ICON(IDC_OPENFILENAME, folder); + SET_BUTTON_ICON(IDC_VIEWCOMMANDLINE, magnifier); + SET_BUTTON_ICON(IDC_VIEWPARENTPROCESS, magnifier); + + // File + + SendMessage(GetDlgItem(hwndDlg, IDC_FILEICON), STM_SETICON, + (WPARAM)processItem->LargeIcon, 0); + + if (PH_IS_REAL_PROCESS_ID(processItem->ProcessId)) + { + SetDlgItemText(hwndDlg, IDC_NAME, PhpGetStringOrNa(processItem->VersionInfo.FileDescription)); + } + else + { + SetDlgItemText(hwndDlg, IDC_NAME, processItem->ProcessName->Buffer); + } + + SetDlgItemText(hwndDlg, IDC_VERSION, PhpGetStringOrNa(processItem->VersionInfo.FileVersion)); + SetDlgItemText(hwndDlg, IDC_FILENAME, PhpGetStringOrNa(processItem->FileName)); + + if (!processItem->FileName) + EnableWindow(GetDlgItem(hwndDlg, IDC_OPENFILENAME), FALSE); + + { + PPH_STRING inspectExecutables; + + inspectExecutables = PhaGetStringSetting(L"ProgramInspectExecutables"); + + if (!processItem->FileName || inspectExecutables->Length == 0) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_INSPECT), FALSE); + } + } + + if (processItem->VerifyResult == VrTrusted) + { + if (processItem->VerifySignerName) + { + SetDlgItemText(hwndDlg, IDC_COMPANYNAME_LINK, + PhaFormatString(L"(Verified) %s", processItem->VerifySignerName->Buffer)->Buffer); + ShowWindow(GetDlgItem(hwndDlg, IDC_COMPANYNAME), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_COMPANYNAME_LINK), SW_SHOW); + } + else + { + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, + PhaConcatStrings2( + L"(Verified) ", + PhGetStringOrEmpty(processItem->VersionInfo.CompanyName) + )->Buffer); + } + } + else if (processItem->VerifyResult != VrUnknown) + { + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, + PhaConcatStrings2( + L"(UNVERIFIED) ", + PhGetStringOrEmpty(processItem->VersionInfo.CompanyName) + )->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, + PhpGetStringOrNa(processItem->VersionInfo.CompanyName)); + } + + // Command Line + + SetDlgItemText(hwndDlg, IDC_CMDLINE, PhpGetStringOrNa(processItem->CommandLine)); + + if (!processItem->CommandLine) + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWCOMMANDLINE), FALSE); + + // Current Directory + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess | PROCESS_VM_READ, + processItem->ProcessId + ))) + { + PH_PEB_OFFSET pebOffset; + + pebOffset = PhpoCurrentDirectory; + +#ifdef _WIN64 + // Tell the function to get the WOW64 current directory, because that's the one that + // actually gets updated. + if (processItem->IsWow64) + pebOffset |= PhpoWow64; +#endif + + PhGetProcessPebString( + processHandle, + pebOffset, + &curDir + ); + PH_AUTO(curDir); + + NtClose(processHandle); + processHandle = NULL; + } + + SetDlgItemText(hwndDlg, IDC_CURDIR, PhpGetStringOrNa(curDir)); + + // Started + + if (processItem->CreateTime.QuadPart != 0) + { + LARGE_INTEGER startTime; + LARGE_INTEGER currentTime; + SYSTEMTIME startTimeFields; + PPH_STRING startTimeRelativeString; + PPH_STRING startTimeString; + + startTime = processItem->CreateTime; + PhQuerySystemTime(¤tTime); + startTimeRelativeString = PH_AUTO(PhFormatTimeSpanRelative(currentTime.QuadPart - startTime.QuadPart)); + + PhLargeIntegerToLocalSystemTime(&startTimeFields, &startTime); + startTimeString = PhaFormatDateTime(&startTimeFields); + + SetDlgItemText(hwndDlg, IDC_STARTED, + PhaFormatString(L"%s ago (%s)", startTimeRelativeString->Buffer, startTimeString->Buffer)->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_STARTED, L"N/A"); + } + + // Parent + + if (parentProcess = PhReferenceProcessItemForParent( + processItem->ParentProcessId, + processItem->ProcessId, + &processItem->CreateTime + )) + { + clientId.UniqueProcess = parentProcess->ProcessId; + clientId.UniqueThread = NULL; + + SetDlgItemText(hwndDlg, IDC_PARENTPROCESS, + PH_AUTO_T(PH_STRING, PhGetClientIdNameEx(&clientId, parentProcess->ProcessName))->Buffer); + + PhDereferenceObject(parentProcess); + } + else + { + SetDlgItemText(hwndDlg, IDC_PARENTPROCESS, + PhaFormatString(L"Non-existent process (%u)", HandleToUlong(processItem->ParentProcessId))->Buffer); + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWPARENTPROCESS), FALSE); + } + + // Mitigation policies + + PhpUpdateProcessMitigationPolicies(hwndDlg, processItem); + + // PEB address + + SetDlgItemText(hwndDlg, IDC_PEBADDRESS, L"N/A"); + + PhOpenProcess( + &processHandle, + ProcessQueryAccess, + processItem->ProcessId + ); + + if (processHandle) + { + PhGetProcessBasicInformation(processHandle, &basicInfo); +#ifdef _WIN64 + if (processItem->IsWow64) + { + PhGetProcessPeb32(processHandle, &peb32); + SetDlgItemText(hwndDlg, IDC_PEBADDRESS, + PhaFormatString(L"0x%Ix (32-bit: 0x%x)", basicInfo.PebBaseAddress, PtrToUlong(peb32))->Buffer); + } + else + { +#endif + + SetDlgItemText(hwndDlg, IDC_PEBADDRESS, + PhaFormatString(L"0x%Ix", basicInfo.PebBaseAddress)->Buffer); +#ifdef _WIN64 + } +#endif + } + + // Protection + + SetDlgItemText(hwndDlg, IDC_PROTECTION, L"N/A"); + + if (WINDOWS_HAS_LIMITED_ACCESS && processHandle) + { + if (WindowsVersion >= WINDOWS_8_1) + { + PS_PROTECTION protection; + + if (NT_SUCCESS(NtQueryInformationProcess( + processHandle, + ProcessProtectionInformation, + &protection, + sizeof(PS_PROTECTION), + NULL + ))) + { + PWSTR type; + PWSTR signer; + + switch (protection.Type) + { + case PsProtectedTypeNone: + type = L"None"; + break; + case PsProtectedTypeProtectedLight: + type = L"Light"; + break; + case PsProtectedTypeProtected: + type = L"Full"; + break; + default: + type = L"Unknown"; + break; + } + + if (protection.Signer < sizeof(ProtectedSignerStrings) / sizeof(PWSTR)) + signer = ProtectedSignerStrings[protection.Signer]; + else + signer = L""; + + SetDlgItemText(hwndDlg, IDC_PROTECTION, PhaConcatStrings2(type, signer)->Buffer); + } + } + else + { + PROCESS_EXTENDED_BASIC_INFORMATION extendedBasicInfo; + + if (NT_SUCCESS(PhGetProcessExtendedBasicInformation( + processHandle, + &extendedBasicInfo + ))) + { + SetDlgItemText(hwndDlg, IDC_PROTECTION, extendedBasicInfo.IsProtectedProcess ? L"Yes" : L"None"); + } + } + } + + if (processHandle) + NtClose(processHandle); + +#ifdef _WIN64 + if (processItem->IsWow64Valid) + { + if (processItem->IsWow64) + SetDlgItemText(hwndDlg, IDC_PROCESSTYPETEXT, L"32-bit"); + else + SetDlgItemText(hwndDlg, IDC_PROCESSTYPETEXT, L"64-bit"); + } + else + { + SetDlgItemText(hwndDlg, IDC_PROCESSTYPETEXT, L"N/A"); + } + + ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSTYPELABEL), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSTYPETEXT), SW_SHOW); +#endif + } + break; + case WM_DESTROY: + { + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_FILE), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_NAME), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_COMPANYNAME), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_COMPANYNAME_LINK), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_VERSION), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PROCESSTYPELABEL), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_TOP); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PROCESSTYPETEXT), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_TOP); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_FILENAME), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_INSPECT), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_OPENFILENAME), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_CMDLINE), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_VIEWCOMMANDLINE), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_CURDIR), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_STARTED), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PEBADDRESS), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PARENTPROCESS), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_VIEWPARENTPROCESS), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_MITIGATION), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_VIEWMITIGATION), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_TERMINATE), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_TOP); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PERMISSIONS), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_TOP); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PROCESS), + dialogItem, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case IDC_INSPECT: + { + if (processItem->FileName) + { + PhShellExecuteUserString( + hwndDlg, + L"ProgramInspectExecutables", + processItem->FileName->Buffer, + FALSE, + L"Make sure the PE Viewer executable file is present." + ); + } + } + break; + case IDC_OPENFILENAME: + { + if (processItem->FileName) + PhShellExploreFile(hwndDlg, processItem->FileName->Buffer); + } + break; + case IDC_VIEWCOMMANDLINE: + { + if (processItem->CommandLine) + PhShowInformationDialog(hwndDlg, processItem->CommandLine->Buffer, 0); + } + break; + case IDC_VIEWPARENTPROCESS: + { + PPH_PROCESS_ITEM parentProcessItem; + + if (parentProcessItem = PhReferenceProcessItem(processItem->ParentProcessId)) + { + ProcessHacker_ShowProcessProperties(PhMainWndHandle, parentProcessItem); + PhDereferenceObject(parentProcessItem); + } + else + { + PhShowError(hwndDlg, L"The process does not exist."); + } + } + break; + case IDC_VIEWMITIGATION: + { + NTSTATUS status; + HANDLE processHandle; + PH_PROCESS_MITIGATION_POLICY_ALL_INFORMATION information; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_QUERY_INFORMATION, + processItem->ProcessId + ))) + { + if (NT_SUCCESS(PhGetProcessMitigationPolicy(processHandle, &information))) + { + PhShowProcessMitigationPolicyDialog(hwndDlg, &information); + } + + NtClose(processHandle); + } + else + { + PhShowStatus(hwndDlg, L"Unable to open the process", status, 0); + } + } + break; + case IDC_TERMINATE: + { + PhUiTerminateProcesses( + hwndDlg, + &processItem, + 1 + ); + } + break; + case IDC_PERMISSIONS: + { + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + stdObjectSecurity.OpenObject = PhpProcessGeneralOpenProcess; + stdObjectSecurity.ObjectType = L"Process"; + stdObjectSecurity.Context = processItem->ProcessId; + + if (PhGetAccessEntries(L"Process", &accessEntries, &numberOfAccessEntries)) + { + PhEditSecurity( + hwndDlg, + processItem->ProcessName->Buffer, + PhStdGetObjectSecurity, + PhStdSetObjectSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case NM_CLICK: + { + switch (header->idFrom) + { + case IDC_COMPANYNAME_LINK: + { + if (processItem->FileName) + { + PH_VERIFY_FILE_INFO info; + + memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO)); + info.FileName = processItem->FileName->Buffer; + info.Flags = PH_VERIFY_VIEW_PROPERTIES; + info.hWnd = hwndDlg; + PhVerifyFileWithAdditionalCatalog( + &info, + PhGetString(processItem->PackageFullName), + NULL + ); + } + } + break; + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpghndl.c b/ProcessHacker/prpghndl.c new file mode 100644 index 0000000..9ff6584 --- /dev/null +++ b/ProcessHacker/prpghndl.c @@ -0,0 +1,558 @@ +/* + * Process Hacker - + * Process properties: Handles page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PH_STRINGREF EmptyHandlesText = PH_STRINGREF_INIT(L"There are no handles to display."); + +static VOID NTAPI HandleAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_HANDLES_CONTEXT handlesContext = (PPH_HANDLES_CONTEXT)Context; + + // Parameter contains a pointer to the added handle item. + PhReferenceObject(Parameter); + PhPushProviderEventQueue(&handlesContext->EventQueue, ProviderAddedEvent, Parameter, PhGetRunIdProvider(&handlesContext->ProviderRegistration)); +} + +static VOID NTAPI HandleModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_HANDLES_CONTEXT handlesContext = (PPH_HANDLES_CONTEXT)Context; + + PhPushProviderEventQueue(&handlesContext->EventQueue, ProviderModifiedEvent, Parameter, PhGetRunIdProvider(&handlesContext->ProviderRegistration)); +} + +static VOID NTAPI HandleRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_HANDLES_CONTEXT handlesContext = (PPH_HANDLES_CONTEXT)Context; + + PhPushProviderEventQueue(&handlesContext->EventQueue, ProviderRemovedEvent, Parameter, PhGetRunIdProvider(&handlesContext->ProviderRegistration)); +} + +static VOID NTAPI HandlesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_HANDLES_CONTEXT handlesContext = (PPH_HANDLES_CONTEXT)Context; + + PostMessage(handlesContext->WindowHandle, WM_PH_HANDLES_UPDATED, PhGetRunIdProvider(&handlesContext->ProviderRegistration), 0); +} + +VOID PhpInitializeHandleMenu( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ PPH_HANDLE_ITEM *Handles, + _In_ ULONG NumberOfHandles, + _Inout_ PPH_HANDLES_CONTEXT HandlesContext + ) +{ + PPH_EMENU_ITEM item; + + if (NumberOfHandles == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfHandles == 1) + { + PH_HANDLE_ITEM_INFO info; + + info.ProcessId = ProcessId; + info.Handle = Handles[0]->Handle; + info.TypeName = Handles[0]->TypeName; + info.BestObjectName = Handles[0]->BestObjectName; + PhInsertHandleObjectPropertiesEMenuItems(Menu, ID_HANDLE_COPY, TRUE, &info); + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + + PhEnableEMenuItem(Menu, ID_HANDLE_CLOSE, TRUE); + PhEnableEMenuItem(Menu, ID_HANDLE_COPY, TRUE); + } + + // Remove irrelevant menu items. + + if (!KphIsConnected()) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_HANDLE_PROTECTED)) + PhDestroyEMenuItem(item); + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_HANDLE_INHERIT)) + PhDestroyEMenuItem(item); + } + + // Protected, Inherit + if (NumberOfHandles == 1 && KphIsConnected()) + { + HandlesContext->SelectedHandleProtected = FALSE; + HandlesContext->SelectedHandleInherit = FALSE; + + if (Handles[0]->Attributes & OBJ_PROTECT_CLOSE) + { + HandlesContext->SelectedHandleProtected = TRUE; + PhSetFlagsEMenuItem(Menu, ID_HANDLE_PROTECTED, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (Handles[0]->Attributes & OBJ_INHERIT) + { + HandlesContext->SelectedHandleInherit = TRUE; + PhSetFlagsEMenuItem(Menu, ID_HANDLE_INHERIT, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + } +} + +VOID PhShowHandleContextMenu( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_HANDLES_CONTEXT Context, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PPH_HANDLE_ITEM *handles; + ULONG numberOfHandles; + + PhGetSelectedHandleItems(&Context->ListContext, &handles, &numberOfHandles); + + if (numberOfHandles != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_PLUGIN_MENU_INFORMATION menuInfo; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_HANDLE), 0); + PhSetFlagsEMenuItem(menu, ID_HANDLE_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhpInitializeHandleMenu(menu, ProcessItem->ProcessId, handles, numberOfHandles, Context); + PhInsertCopyCellEMenuItem(menu, ID_HANDLE_COPY, Context->ListContext.TreeNewHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, hwndDlg, 0); + menuInfo.u.Handle.ProcessId = ProcessItem->ProcessId; + menuInfo.u.Handle.Handles = handles; + menuInfo.u.Handle.NumberOfHandles = numberOfHandles; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackHandleMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(hwndDlg, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(handles); +} + +INT_PTR CALLBACK PhpProcessHandlesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_HANDLES_CONTEXT handlesContext; + HWND tnHandle; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + handlesContext = (PPH_HANDLES_CONTEXT)propPageContext->Context; + + if (handlesContext) + tnHandle = handlesContext->ListContext.TreeNewHandle; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + handlesContext = propPageContext->Context = + PhAllocate(PhEmGetObjectSize(EmHandlesContextType, sizeof(PH_HANDLES_CONTEXT))); + + handlesContext->Provider = PhCreateHandleProvider( + processItem->ProcessId + ); + PhRegisterProvider( + &PhSecondaryProviderThread, + PhHandleProviderUpdate, + handlesContext->Provider, + &handlesContext->ProviderRegistration + ); + PhRegisterCallback( + &handlesContext->Provider->HandleAddedEvent, + HandleAddedHandler, + handlesContext, + &handlesContext->AddedEventRegistration + ); + PhRegisterCallback( + &handlesContext->Provider->HandleModifiedEvent, + HandleModifiedHandler, + handlesContext, + &handlesContext->ModifiedEventRegistration + ); + PhRegisterCallback( + &handlesContext->Provider->HandleRemovedEvent, + HandleRemovedHandler, + handlesContext, + &handlesContext->RemovedEventRegistration + ); + PhRegisterCallback( + &handlesContext->Provider->UpdatedEvent, + HandlesUpdatedHandler, + handlesContext, + &handlesContext->UpdatedEventRegistration + ); + handlesContext->WindowHandle = hwndDlg; + + // Initialize the list. + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + BringWindowToTop(tnHandle); + PhInitializeHandleList(hwndDlg, tnHandle, &handlesContext->ListContext); + TreeNew_SetEmptyText(tnHandle, &PhpLoadingText, 0); + PhInitializeProviderEventQueue(&handlesContext->EventQueue, 100); + handlesContext->LastRunStatus = -1; + handlesContext->ErrorMessage = NULL; + + PhEmCallObjectOperation(EmHandlesContextType, handlesContext, EmObjectCreate); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &handlesContext->ListContext.Cm; + treeNewInfo.SystemContext = handlesContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackHandleTreeNewInitializing), &treeNewInfo); + } + + PhLoadSettingsHandleList(&handlesContext->ListContext); + + PhSetOptionsHandleList(&handlesContext->ListContext, !!PhGetIntegerSetting(L"HideUnnamedHandles")); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_HIDEUNNAMEDHANDLES), + handlesContext->ListContext.HideUnnamedHandles ? BST_CHECKED : BST_UNCHECKED); + + PhSetEnabledProvider(&handlesContext->ProviderRegistration, TRUE); + PhBoostProvider(&handlesContext->ProviderRegistration, NULL); + } + break; + case WM_DESTROY: + { + PhEmCallObjectOperation(EmHandlesContextType, handlesContext, EmObjectDelete); + + PhUnregisterCallback( + &handlesContext->Provider->HandleAddedEvent, + &handlesContext->AddedEventRegistration + ); + PhUnregisterCallback( + &handlesContext->Provider->HandleModifiedEvent, + &handlesContext->ModifiedEventRegistration + ); + PhUnregisterCallback( + &handlesContext->Provider->HandleRemovedEvent, + &handlesContext->RemovedEventRegistration + ); + PhUnregisterCallback( + &handlesContext->Provider->UpdatedEvent, + &handlesContext->UpdatedEventRegistration + ); + PhUnregisterProvider(&handlesContext->ProviderRegistration); + PhDereferenceObject(handlesContext->Provider); + PhDeleteProviderEventQueue(&handlesContext->EventQueue); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &handlesContext->ListContext.Cm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackHandleTreeNewUninitializing), &treeNewInfo); + } + + PhSaveSettingsHandleList(&handlesContext->ListContext); + PhDeleteHandleList(&handlesContext->ListContext); + + PhFree(handlesContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), + dialogItem, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case ID_SHOWCONTEXTMENU: + { + PhShowHandleContextMenu(hwndDlg, processItem, handlesContext, (PPH_TREENEW_CONTEXT_MENU)lParam); + } + break; + case ID_HANDLE_CLOSE: + { + PPH_HANDLE_ITEM *handles; + ULONG numberOfHandles; + + PhGetSelectedHandleItems(&handlesContext->ListContext, &handles, &numberOfHandles); + PhReferenceObjects(handles, numberOfHandles); + + if (PhUiCloseHandles(hwndDlg, processItem->ProcessId, handles, numberOfHandles, !!lParam)) + PhDeselectAllHandleNodes(&handlesContext->ListContext); + + PhDereferenceObjects(handles, numberOfHandles); + PhFree(handles); + } + break; + case ID_HANDLE_PROTECTED: + case ID_HANDLE_INHERIT: + { + PPH_HANDLE_ITEM handleItem = PhGetSelectedHandleItem(&handlesContext->ListContext); + + if (handleItem) + { + ULONG attributes = 0; + + // Re-create the attributes. + + if (handlesContext->SelectedHandleProtected) + attributes |= OBJ_PROTECT_CLOSE; + if (handlesContext->SelectedHandleInherit) + attributes |= OBJ_INHERIT; + + // Toggle the appropriate bit. + + if (id == ID_HANDLE_PROTECTED) + attributes ^= OBJ_PROTECT_CLOSE; + else if (id == ID_HANDLE_INHERIT) + attributes ^= OBJ_INHERIT; + + PhReferenceObject(handleItem); + PhUiSetAttributesHandle(hwndDlg, processItem->ProcessId, handleItem, attributes); + PhDereferenceObject(handleItem); + } + } + break; + case ID_HANDLE_OBJECTPROPERTIES1: + case ID_HANDLE_OBJECTPROPERTIES2: + { + PPH_HANDLE_ITEM handleItem = PhGetSelectedHandleItem(&handlesContext->ListContext); + + if (handleItem) + { + PH_HANDLE_ITEM_INFO info; + + info.ProcessId = processItem->ProcessId; + info.Handle = handleItem->Handle; + info.TypeName = handleItem->TypeName; + info.BestObjectName = handleItem->BestObjectName; + + if (id == ID_HANDLE_OBJECTPROPERTIES1) + PhShowHandleObjectProperties1(hwndDlg, &info); + else + PhShowHandleObjectProperties2(hwndDlg, &info); + } + } + break; + case ID_HANDLE_PROPERTIES: + { + PPH_HANDLE_ITEM handleItem = PhGetSelectedHandleItem(&handlesContext->ListContext); + + if (handleItem) + { + PhReferenceObject(handleItem); + PhShowHandleProperties(hwndDlg, processItem->ProcessId, handleItem); + PhDereferenceObject(handleItem); + } + } + break; + case ID_HANDLE_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(tnHandle, 0); + PhSetClipboardString(tnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + case IDC_HIDEUNNAMEDHANDLES: + { + BOOLEAN hide; + + hide = Button_GetCheck(GetDlgItem(hwndDlg, IDC_HIDEUNNAMEDHANDLES)) == BST_CHECKED; + PhSetOptionsHandleList(&handlesContext->ListContext, hide); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + PhSetEnabledProvider(&handlesContext->ProviderRegistration, TRUE); + break; + case PSN_KILLACTIVE: + PhSetEnabledProvider(&handlesContext->ProviderRegistration, FALSE); + break; + } + } + break; + case WM_PH_HANDLES_UPDATED: + { + ULONG upToRunId = (ULONG)wParam; + PPH_PROVIDER_EVENT events; + ULONG count; + ULONG i; + + events = PhFlushProviderEventQueue(&handlesContext->EventQueue, upToRunId, &count); + + if (events) + { + TreeNew_SetRedraw(tnHandle, FALSE); + + for (i = 0; i < count; i++) + { + PH_PROVIDER_EVENT_TYPE type = PH_PROVIDER_EVENT_TYPE(events[i]); + PPH_HANDLE_ITEM handleItem = PH_PROVIDER_EVENT_OBJECT(events[i]); + + switch (type) + { + case ProviderAddedEvent: + PhAddHandleNode(&handlesContext->ListContext, handleItem, events[i].RunId); + PhDereferenceObject(handleItem); + break; + case ProviderModifiedEvent: + PhUpdateHandleNode(&handlesContext->ListContext, PhFindHandleNode(&handlesContext->ListContext, handleItem->Handle)); + break; + case ProviderRemovedEvent: + PhRemoveHandleNode(&handlesContext->ListContext, PhFindHandleNode(&handlesContext->ListContext, handleItem->Handle)); + break; + } + } + + PhFree(events); + } + + PhTickHandleNodes(&handlesContext->ListContext); + + if (handlesContext->LastRunStatus != handlesContext->Provider->RunStatus) + { + NTSTATUS status; + PPH_STRING message; + + status = handlesContext->Provider->RunStatus; + handlesContext->LastRunStatus = status; + + if (!PH_IS_REAL_PROCESS_ID(processItem->ProcessId)) + status = STATUS_SUCCESS; + + if (NT_SUCCESS(status)) + { + TreeNew_SetEmptyText(tnHandle, &EmptyHandlesText, 0); + } + else + { + message = PhGetStatusMessage(status, 0); + PhMoveReference(&handlesContext->ErrorMessage, PhFormatString(L"Unable to query handle information:\n%s", PhGetStringOrDefault(message, L"Unknown error."))); + PhClearReference(&message); + TreeNew_SetEmptyText(tnHandle, &handlesContext->ErrorMessage->sr, 0); + } + + InvalidateRect(tnHandle, NULL, FALSE); + } + + if (count != 0) + TreeNew_SetRedraw(tnHandle, TRUE); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgjob.c b/ProcessHacker/prpgjob.c new file mode 100644 index 0000000..7311112 --- /dev/null +++ b/ProcessHacker/prpgjob.c @@ -0,0 +1,105 @@ +/* + * Process Hacker - + * Process properties: Job page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +NTSTATUS NTAPI PhpOpenProcessJobForPage( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + HANDLE processHandle; + HANDLE jobHandle = NULL; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + (HANDLE)Context + ))) + return status; + + status = KphOpenProcessJob(processHandle, DesiredAccess, &jobHandle); + NtClose(processHandle); + + if (NT_SUCCESS(status) && status != STATUS_PROCESS_NOT_IN_JOB && jobHandle) + { + *Handle = jobHandle; + } + else if (NT_SUCCESS(status)) + { + status = STATUS_UNSUCCESSFUL; + } + + return status; +} + +INT_PTR CALLBACK PhpProcessJobHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_SHOWWINDOW: + { + if (!GetProp(hwndDlg, PhMakeContextAtom())) // LayoutInitialized + { + PPH_LAYOUT_ITEM dialogItem; + + // This is a big violation of abstraction... + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_NAME), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_TERMINATE), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PROCESSES), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_ADD), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIMITS), + dialogItem, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_ADVANCED), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + PhDoPropPageLayout(hwndDlg); + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)TRUE); + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgmem.c b/ProcessHacker/prpgmem.c new file mode 100644 index 0000000..a349e62 --- /dev/null +++ b/ProcessHacker/prpgmem.c @@ -0,0 +1,554 @@ +/* + * Process Hacker - + * Process properties: Memory page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PH_STRINGREF EmptyMemoryText = PH_STRINGREF_INIT(L"There are no memory regions to display."); + +VOID PhpRefreshProcessMemoryList( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_PROPPAGECONTEXT PropPageContext + ) +{ + PPH_MEMORY_CONTEXT memoryContext = PropPageContext->Context; + + if (memoryContext->MemoryItemListValid) + { + PhDeleteMemoryItemList(&memoryContext->MemoryItemList); + memoryContext->MemoryItemListValid = FALSE; + } + + memoryContext->LastRunStatus = PhQueryMemoryItemList( + memoryContext->ProcessId, + PH_QUERY_MEMORY_REGION_TYPE | PH_QUERY_MEMORY_WS_COUNTERS, + &memoryContext->MemoryItemList + ); + + if (NT_SUCCESS(memoryContext->LastRunStatus)) + { + if (PhPluginsEnabled) + { + PH_PLUGIN_MEMORY_ITEM_LIST_CONTROL control; + + control.Type = PluginMemoryItemListInitialized; + control.u.Initialized.List = &memoryContext->MemoryItemList; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMemoryItemListControl), &control); + } + + memoryContext->MemoryItemListValid = TRUE; + TreeNew_SetEmptyText(memoryContext->ListContext.TreeNewHandle, &EmptyMemoryText, 0); + PhReplaceMemoryList(&memoryContext->ListContext, &memoryContext->MemoryItemList); + } + else + { + PPH_STRING message; + + message = PhGetStatusMessage(memoryContext->LastRunStatus, 0); + PhMoveReference(&memoryContext->ErrorMessage, PhFormatString(L"Unable to query memory information:\n%s", PhGetStringOrDefault(message, L"Unknown error."))); + PhClearReference(&message); + TreeNew_SetEmptyText(memoryContext->ListContext.TreeNewHandle, &memoryContext->ErrorMessage->sr, 0); + + PhReplaceMemoryList(&memoryContext->ListContext, NULL); + } +} + +VOID PhpInitializeMemoryMenu( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ PPH_MEMORY_NODE *MemoryNodes, + _In_ ULONG NumberOfMemoryNodes + ) +{ + if (NumberOfMemoryNodes == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfMemoryNodes == 1 && !MemoryNodes[0]->IsAllocationBase) + { + if (MemoryNodes[0]->MemoryItem->State & MEM_FREE) + { + PhEnableEMenuItem(Menu, ID_MEMORY_CHANGEPROTECTION, FALSE); + PhEnableEMenuItem(Menu, ID_MEMORY_FREE, FALSE); + PhEnableEMenuItem(Menu, ID_MEMORY_DECOMMIT, FALSE); + } + else if (MemoryNodes[0]->MemoryItem->Type & (MEM_MAPPED | MEM_IMAGE)) + { + PhEnableEMenuItem(Menu, ID_MEMORY_DECOMMIT, FALSE); + } + } + else + { + ULONG i; + ULONG numberOfAllocationBase = 0; + + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_MEMORY_COPY, TRUE); + + for (i = 0; i < NumberOfMemoryNodes; i++) + { + if (MemoryNodes[i]->IsAllocationBase) + numberOfAllocationBase++; + } + + if (numberOfAllocationBase == 0 || numberOfAllocationBase == NumberOfMemoryNodes) + PhEnableEMenuItem(Menu, ID_MEMORY_SAVE, TRUE); + } + + PhEnableEMenuItem(Menu, ID_MEMORY_READWRITEADDRESS, TRUE); +} + +VOID PhShowMemoryContextMenu( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_MEMORY_CONTEXT Context, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PPH_MEMORY_NODE *memoryNodes; + ULONG numberOfMemoryNodes; + + PhGetSelectedMemoryNodes(&Context->ListContext, &memoryNodes, &numberOfMemoryNodes); + + //if (numberOfMemoryNodes != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_PLUGIN_MENU_INFORMATION menuInfo; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MEMORY), 0); + PhSetFlagsEMenuItem(menu, ID_MEMORY_READWRITEMEMORY, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhpInitializeMemoryMenu(menu, ProcessItem->ProcessId, memoryNodes, numberOfMemoryNodes); + PhInsertCopyCellEMenuItem(menu, ID_MEMORY_COPY, Context->ListContext.TreeNewHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, hwndDlg, 0); + menuInfo.u.Memory.ProcessId = ProcessItem->ProcessId; + menuInfo.u.Memory.MemoryNodes = memoryNodes; + menuInfo.u.Memory.NumberOfMemoryNodes = numberOfMemoryNodes; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMemoryMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(hwndDlg, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(memoryNodes); +} + +INT_PTR CALLBACK PhpProcessMemoryDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_MEMORY_CONTEXT memoryContext; + HWND tnHandle; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + memoryContext = (PPH_MEMORY_CONTEXT)propPageContext->Context; + + if (memoryContext) + tnHandle = memoryContext->ListContext.TreeNewHandle; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + memoryContext = propPageContext->Context = + PhAllocate(PhEmGetObjectSize(EmMemoryContextType, sizeof(PH_MEMORY_CONTEXT))); + memset(memoryContext, 0, sizeof(PH_MEMORY_CONTEXT)); + memoryContext->ProcessId = processItem->ProcessId; + + // Initialize the list. + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + BringWindowToTop(tnHandle); + PhInitializeMemoryList(hwndDlg, tnHandle, &memoryContext->ListContext); + TreeNew_SetEmptyText(tnHandle, &PhpLoadingText, 0); + memoryContext->LastRunStatus = -1; + memoryContext->ErrorMessage = NULL; + + PhEmCallObjectOperation(EmMemoryContextType, memoryContext, EmObjectCreate); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &memoryContext->ListContext.Cm; + treeNewInfo.SystemContext = memoryContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMemoryTreeNewInitializing), &treeNewInfo); + } + + PhLoadSettingsMemoryList(&memoryContext->ListContext); + PhSetOptionsMemoryList(&memoryContext->ListContext, TRUE); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_HIDEFREEREGIONS), + memoryContext->ListContext.HideFreeRegions ? BST_CHECKED : BST_UNCHECKED); + + PhpRefreshProcessMemoryList(hwndDlg, propPageContext); + } + break; + case WM_DESTROY: + { + PhEmCallObjectOperation(EmMemoryContextType, memoryContext, EmObjectDelete); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &memoryContext->ListContext.Cm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackMemoryTreeNewUninitializing), &treeNewInfo); + } + + PhSaveSettingsMemoryList(&memoryContext->ListContext); + PhDeleteMemoryList(&memoryContext->ListContext); + + if (memoryContext->MemoryItemListValid) + PhDeleteMemoryItemList(&memoryContext->MemoryItemList); + + PhClearReference(&memoryContext->ErrorMessage); + PhFree(memoryContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_STRINGS), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_REFRESH), + dialogItem, PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, memoryContext->ListContext.TreeNewHandle, + dialogItem, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_SHOWCONTEXTMENU: + { + PhShowMemoryContextMenu(hwndDlg, processItem, memoryContext, (PPH_TREENEW_CONTEXT_MENU)lParam); + } + break; + case ID_MEMORY_READWRITEMEMORY: + { + PPH_MEMORY_NODE memoryNode = PhGetSelectedMemoryNode(&memoryContext->ListContext); + + if (memoryNode && !memoryNode->IsAllocationBase) + { + if (memoryNode->MemoryItem->State & MEM_COMMIT) + { + PPH_SHOWMEMORYEDITOR showMemoryEditor = PhAllocate(sizeof(PH_SHOWMEMORYEDITOR)); + + memset(showMemoryEditor, 0, sizeof(PH_SHOWMEMORYEDITOR)); + showMemoryEditor->ProcessId = processItem->ProcessId; + showMemoryEditor->BaseAddress = memoryNode->MemoryItem->BaseAddress; + showMemoryEditor->RegionSize = memoryNode->MemoryItem->RegionSize; + showMemoryEditor->SelectOffset = -1; + showMemoryEditor->SelectLength = 0; + ProcessHacker_ShowMemoryEditor(PhMainWndHandle, showMemoryEditor); + } + else + { + PhShowError(hwndDlg, L"Unable to edit the memory region because it is not committed."); + } + } + } + break; + case ID_MEMORY_SAVE: + { + NTSTATUS status; + HANDLE processHandle; + PPH_MEMORY_NODE *memoryNodes; + ULONG numberOfMemoryNodes; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_VM_READ, + processItem->ProcessId + ))) + { + PhShowStatus(hwndDlg, L"Unable to open the process", status, 0); + break; + } + + PhGetSelectedMemoryNodes(&memoryContext->ListContext, &memoryNodes, &numberOfMemoryNodes); + + if (numberOfMemoryNodes != 0) + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Binary files (*.bin)", L"*.bin" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateSaveFileDialog(); + + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, PhaConcatStrings2(processItem->ProcessName->Buffer, L".bin")->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + PPH_STRING fileName; + PPH_FILE_STREAM fileStream; + PVOID buffer; + ULONG i; + ULONG_PTR offset; + + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + + if (NT_SUCCESS(status = PhCreateFileStream( + &fileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + 0 + ))) + { + buffer = PhAllocatePage(PAGE_SIZE, NULL); + + // Go through each selected memory item and append the region contents + // to the file. + for (i = 0; i < numberOfMemoryNodes; i++) + { + PPH_MEMORY_NODE memoryNode = memoryNodes[i]; + PPH_MEMORY_ITEM memoryItem = memoryNode->MemoryItem; + + if (!memoryNode->IsAllocationBase && !(memoryItem->State & MEM_COMMIT)) + continue; + + for (offset = 0; offset < memoryItem->RegionSize; offset += PAGE_SIZE) + { + if (NT_SUCCESS(NtReadVirtualMemory( + processHandle, + PTR_ADD_OFFSET(memoryItem->BaseAddress, offset), + buffer, + PAGE_SIZE, + NULL + ))) + { + PhWriteFileStream(fileStream, buffer, PAGE_SIZE); + } + } + } + + PhFreePage(buffer); + + PhDereferenceObject(fileStream); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to create the file", status, 0); + } + + PhFreeFileDialog(fileDialog); + } + + PhFree(memoryNodes); + NtClose(processHandle); + } + break; + case ID_MEMORY_CHANGEPROTECTION: + { + PPH_MEMORY_NODE memoryNode = PhGetSelectedMemoryNode(&memoryContext->ListContext); + + if (memoryNode) + { + PhReferenceObject(memoryNode->MemoryItem); + + PhShowMemoryProtectDialog(hwndDlg, processItem, memoryNode->MemoryItem); + PhUpdateMemoryNode(&memoryContext->ListContext, memoryNode); + + PhDereferenceObject(memoryNode->MemoryItem); + } + } + break; + case ID_MEMORY_FREE: + { + PPH_MEMORY_NODE memoryNode = PhGetSelectedMemoryNode(&memoryContext->ListContext); + + if (memoryNode) + { + PhReferenceObject(memoryNode->MemoryItem); + PhUiFreeMemory(hwndDlg, processItem->ProcessId, memoryNode->MemoryItem, TRUE); + PhDereferenceObject(memoryNode->MemoryItem); + // TODO: somehow update the list + } + } + break; + case ID_MEMORY_DECOMMIT: + { + PPH_MEMORY_NODE memoryNode = PhGetSelectedMemoryNode(&memoryContext->ListContext); + + if (memoryNode) + { + PhReferenceObject(memoryNode->MemoryItem); + PhUiFreeMemory(hwndDlg, processItem->ProcessId, memoryNode->MemoryItem, FALSE); + PhDereferenceObject(memoryNode->MemoryItem); + } + } + break; + case ID_MEMORY_READWRITEADDRESS: + { + PPH_STRING selectedChoice = NULL; + + if (!memoryContext->MemoryItemListValid) + break; + + while (PhaChoiceDialog( + hwndDlg, + L"Read/Write Address", + L"Enter an address:", + NULL, + 0, + NULL, + PH_CHOICE_DIALOG_USER_CHOICE, + &selectedChoice, + NULL, + L"MemoryReadWriteAddressChoices" + )) + { + ULONG64 address64; + PVOID address; + + if (selectedChoice->Length == 0) + continue; + + if (PhStringToInteger64(&selectedChoice->sr, 0, &address64)) + { + PPH_MEMORY_ITEM memoryItem; + + address = (PVOID)address64; + memoryItem = PhLookupMemoryItemList(&memoryContext->MemoryItemList, address); + + if (memoryItem) + { + PPH_SHOWMEMORYEDITOR showMemoryEditor = PhAllocate(sizeof(PH_SHOWMEMORYEDITOR)); + + memset(showMemoryEditor, 0, sizeof(PH_SHOWMEMORYEDITOR)); + showMemoryEditor->ProcessId = processItem->ProcessId; + showMemoryEditor->BaseAddress = memoryItem->BaseAddress; + showMemoryEditor->RegionSize = memoryItem->RegionSize; + showMemoryEditor->SelectOffset = (ULONG)((ULONG_PTR)address - (ULONG_PTR)memoryItem->BaseAddress); + showMemoryEditor->SelectLength = 0; + ProcessHacker_ShowMemoryEditor(PhMainWndHandle, showMemoryEditor); + break; + } + else + { + PhShowError(hwndDlg, L"Unable to find the memory region for the selected address."); + } + } + } + } + break; + case ID_MEMORY_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(tnHandle, 0); + PhSetClipboardString(tnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + case IDC_HIDEFREEREGIONS: + { + BOOLEAN hide; + + hide = Button_GetCheck(GetDlgItem(hwndDlg, IDC_HIDEFREEREGIONS)) == BST_CHECKED; + PhSetOptionsMemoryList(&memoryContext->ListContext, hide); + } + break; + case IDC_STRINGS: + PhShowMemoryStringDialog(hwndDlg, processItem); + break; + case IDC_REFRESH: + PhpRefreshProcessMemoryList(hwndDlg, propPageContext); + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgmod.c b/ProcessHacker/prpgmod.c new file mode 100644 index 0000000..6a6b3ff --- /dev/null +++ b/ProcessHacker/prpgmod.c @@ -0,0 +1,503 @@ +/* + * Process Hacker - + * Process properties: Modules page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PH_STRINGREF EmptyModulesText = PH_STRINGREF_INIT(L"There are no modules to display."); + +static VOID NTAPI ModuleAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_MODULES_CONTEXT modulesContext = (PPH_MODULES_CONTEXT)Context; + + // Parameter contains a pointer to the added module item. + PhReferenceObject(Parameter); + PhPushProviderEventQueue(&modulesContext->EventQueue, ProviderAddedEvent, Parameter, PhGetRunIdProvider(&modulesContext->ProviderRegistration)); +} + +static VOID NTAPI ModuleModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_MODULES_CONTEXT modulesContext = (PPH_MODULES_CONTEXT)Context; + + PhPushProviderEventQueue(&modulesContext->EventQueue, ProviderModifiedEvent, Parameter, PhGetRunIdProvider(&modulesContext->ProviderRegistration)); +} + +static VOID NTAPI ModuleRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_MODULES_CONTEXT modulesContext = (PPH_MODULES_CONTEXT)Context; + + PhPushProviderEventQueue(&modulesContext->EventQueue, ProviderRemovedEvent, Parameter, PhGetRunIdProvider(&modulesContext->ProviderRegistration)); +} + +static VOID NTAPI ModulesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_MODULES_CONTEXT modulesContext = (PPH_MODULES_CONTEXT)Context; + + PostMessage(modulesContext->WindowHandle, WM_PH_MODULES_UPDATED, PhGetRunIdProvider(&modulesContext->ProviderRegistration), 0); +} + +VOID PhpInitializeModuleMenu( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ PPH_MODULE_ITEM *Modules, + _In_ ULONG NumberOfModules + ) +{ + PPH_EMENU_ITEM item; + PPH_STRING inspectExecutables; + + inspectExecutables = PhaGetStringSetting(L"ProgramInspectExecutables"); + + if (inspectExecutables->Length == 0) + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_MODULE_INSPECT)) + PhDestroyEMenuItem(item); + } + + if (NumberOfModules == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfModules == 1) + { + // Nothing + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_MODULE_COPY, TRUE); + } +} + +VOID PhShowModuleContextMenu( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_MODULES_CONTEXT Context, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PPH_MODULE_ITEM *modules; + ULONG numberOfModules; + + PhGetSelectedModuleItems(&Context->ListContext, &modules, &numberOfModules); + + if (numberOfModules != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_PLUGIN_MENU_INFORMATION menuInfo; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_MODULE), 0); + PhSetFlagsEMenuItem(menu, ID_MODULE_INSPECT, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhpInitializeModuleMenu(menu, ProcessItem->ProcessId, modules, numberOfModules); + PhInsertCopyCellEMenuItem(menu, ID_MODULE_COPY, Context->ListContext.TreeNewHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, hwndDlg, 0); + menuInfo.u.Module.ProcessId = ProcessItem->ProcessId; + menuInfo.u.Module.Modules = modules; + menuInfo.u.Module.NumberOfModules = numberOfModules; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackModuleMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(hwndDlg, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(modules); +} + +INT_PTR CALLBACK PhpProcessModulesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_MODULES_CONTEXT modulesContext; + HWND tnHandle; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + modulesContext = (PPH_MODULES_CONTEXT)propPageContext->Context; + + if (modulesContext) + tnHandle = modulesContext->ListContext.TreeNewHandle; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + // Lots of boilerplate code... + + modulesContext = propPageContext->Context = + PhAllocate(PhEmGetObjectSize(EmModulesContextType, sizeof(PH_MODULES_CONTEXT))); + + modulesContext->Provider = PhCreateModuleProvider( + processItem->ProcessId + ); + PhRegisterProvider( + &PhSecondaryProviderThread, + PhModuleProviderUpdate, + modulesContext->Provider, + &modulesContext->ProviderRegistration + ); + PhRegisterCallback( + &modulesContext->Provider->ModuleAddedEvent, + ModuleAddedHandler, + modulesContext, + &modulesContext->AddedEventRegistration + ); + PhRegisterCallback( + &modulesContext->Provider->ModuleModifiedEvent, + ModuleModifiedHandler, + modulesContext, + &modulesContext->ModifiedEventRegistration + ); + PhRegisterCallback( + &modulesContext->Provider->ModuleRemovedEvent, + ModuleRemovedHandler, + modulesContext, + &modulesContext->RemovedEventRegistration + ); + PhRegisterCallback( + &modulesContext->Provider->UpdatedEvent, + ModulesUpdatedHandler, + modulesContext, + &modulesContext->UpdatedEventRegistration + ); + modulesContext->WindowHandle = hwndDlg; + + // Initialize the list. + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + BringWindowToTop(tnHandle); + PhInitializeModuleList(hwndDlg, tnHandle, &modulesContext->ListContext); + TreeNew_SetEmptyText(tnHandle, &PhpLoadingText, 0); + PhInitializeProviderEventQueue(&modulesContext->EventQueue, 100); + modulesContext->LastRunStatus = -1; + modulesContext->ErrorMessage = NULL; + + PhEmCallObjectOperation(EmModulesContextType, modulesContext, EmObjectCreate); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &modulesContext->ListContext.Cm; + treeNewInfo.SystemContext = modulesContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackModuleTreeNewInitializing), &treeNewInfo); + } + + PhLoadSettingsModuleList(&modulesContext->ListContext); + + PhSetEnabledProvider(&modulesContext->ProviderRegistration, TRUE); + PhBoostProvider(&modulesContext->ProviderRegistration, NULL); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PhEmCallObjectOperation(EmModulesContextType, modulesContext, EmObjectDelete); + + PhUnregisterCallback( + &modulesContext->Provider->ModuleAddedEvent, + &modulesContext->AddedEventRegistration + ); + PhUnregisterCallback( + &modulesContext->Provider->ModuleModifiedEvent, + &modulesContext->ModifiedEventRegistration + ); + PhUnregisterCallback( + &modulesContext->Provider->ModuleRemovedEvent, + &modulesContext->RemovedEventRegistration + ); + PhUnregisterCallback( + &modulesContext->Provider->UpdatedEvent, + &modulesContext->UpdatedEventRegistration + ); + PhUnregisterProvider(&modulesContext->ProviderRegistration); + PhDereferenceObject(modulesContext->Provider); + PhDeleteProviderEventQueue(&modulesContext->EventQueue); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &modulesContext->ListContext.Cm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackModuleTreeNewUninitializing), &treeNewInfo); + } + + PhSaveSettingsModuleList(&modulesContext->ListContext); + PhDeleteModuleList(&modulesContext->ListContext); + + PhClearReference(&modulesContext->ErrorMessage); + PhFree(modulesContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, modulesContext->ListContext.TreeNewHandle, + dialogItem, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_SHOWCONTEXTMENU: + { + PhShowModuleContextMenu(hwndDlg, processItem, modulesContext, (PPH_TREENEW_CONTEXT_MENU)lParam); + } + break; + case ID_MODULE_UNLOAD: + { + PPH_MODULE_ITEM moduleItem = PhGetSelectedModuleItem(&modulesContext->ListContext); + + if (moduleItem) + { + PhReferenceObject(moduleItem); + + if (PhUiUnloadModule(hwndDlg, processItem->ProcessId, moduleItem)) + PhDeselectAllModuleNodes(&modulesContext->ListContext); + + PhDereferenceObject(moduleItem); + } + } + break; + case ID_MODULE_INSPECT: + { + PPH_MODULE_ITEM moduleItem = PhGetSelectedModuleItem(&modulesContext->ListContext); + + if (moduleItem) + { + PhShellExecuteUserString( + hwndDlg, + L"ProgramInspectExecutables", + moduleItem->FileName->Buffer, + FALSE, + L"Make sure the PE Viewer executable file is present." + ); + } + } + break; + case ID_MODULE_SEARCHONLINE: + { + PPH_MODULE_ITEM moduleItem = PhGetSelectedModuleItem(&modulesContext->ListContext); + + if (moduleItem) + { + PhSearchOnlineString(hwndDlg, moduleItem->Name->Buffer); + } + } + break; + case ID_MODULE_OPENFILELOCATION: + { + PPH_MODULE_ITEM moduleItem = PhGetSelectedModuleItem(&modulesContext->ListContext); + + if (moduleItem) + { + PhShellExploreFile(hwndDlg, moduleItem->FileName->Buffer); + } + } + break; + case ID_MODULE_PROPERTIES: + { + PPH_MODULE_ITEM moduleItem = PhGetSelectedModuleItem(&modulesContext->ListContext); + + if (moduleItem) + { + PhShellProperties(hwndDlg, moduleItem->FileName->Buffer); + } + } + break; + case ID_MODULE_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(tnHandle, 0); + PhSetClipboardString(tnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + PhSetEnabledProvider(&modulesContext->ProviderRegistration, TRUE); + break; + case PSN_KILLACTIVE: + PhSetEnabledProvider(&modulesContext->ProviderRegistration, FALSE); + break; + } + } + break; + case WM_PH_MODULES_UPDATED: + { + ULONG upToRunId = (ULONG)wParam; + PPH_PROVIDER_EVENT events; + ULONG count; + ULONG i; + + events = PhFlushProviderEventQueue(&modulesContext->EventQueue, upToRunId, &count); + + if (events) + { + TreeNew_SetRedraw(tnHandle, FALSE); + + for (i = 0; i < count; i++) + { + PH_PROVIDER_EVENT_TYPE type = PH_PROVIDER_EVENT_TYPE(events[i]); + PPH_MODULE_ITEM moduleItem = PH_PROVIDER_EVENT_OBJECT(events[i]); + + switch (type) + { + case ProviderAddedEvent: + PhAddModuleNode(&modulesContext->ListContext, moduleItem, events[i].RunId); + PhDereferenceObject(moduleItem); + break; + case ProviderModifiedEvent: + PhUpdateModuleNode(&modulesContext->ListContext, PhFindModuleNode(&modulesContext->ListContext, moduleItem)); + break; + case ProviderRemovedEvent: + PhRemoveModuleNode(&modulesContext->ListContext, PhFindModuleNode(&modulesContext->ListContext, moduleItem)); + break; + } + } + + PhFree(events); + } + + PhTickModuleNodes(&modulesContext->ListContext); + + if (modulesContext->LastRunStatus != modulesContext->Provider->RunStatus) + { + NTSTATUS status; + PPH_STRING message; + + status = modulesContext->Provider->RunStatus; + modulesContext->LastRunStatus = status; + + if (!PH_IS_REAL_PROCESS_ID(processItem->ProcessId)) + status = STATUS_SUCCESS; + + if (NT_SUCCESS(status)) + { + TreeNew_SetEmptyText(tnHandle, &EmptyModulesText, 0); + } + else + { + message = PhGetStatusMessage(status, 0); + PhMoveReference(&modulesContext->ErrorMessage, PhFormatString(L"Unable to query module information:\n%s", PhGetStringOrDefault(message, L"Unknown error."))); + PhClearReference(&message); + TreeNew_SetEmptyText(tnHandle, &modulesContext->ErrorMessage->sr, 0); + } + + InvalidateRect(tnHandle, NULL, FALSE); + } + + if (count != 0) + TreeNew_SetRedraw(tnHandle, TRUE); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgperf.c b/ProcessHacker/prpgperf.c new file mode 100644 index 0000000..51a37b9 --- /dev/null +++ b/ProcessHacker/prpgperf.c @@ -0,0 +1,483 @@ +/* + * Process Hacker - + * Process properties: Performance page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +static VOID NTAPI PerformanceUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PERFORMANCE_CONTEXT performanceContext = (PPH_PERFORMANCE_CONTEXT)Context; + + PostMessage(performanceContext->WindowHandle, WM_PH_PERFORMANCE_UPDATE, 0, 0); +} + +INT_PTR CALLBACK PhpProcessPerformanceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_PERFORMANCE_CONTEXT performanceContext; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + performanceContext = (PPH_PERFORMANCE_CONTEXT)propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + performanceContext = propPageContext->Context = + PhAllocate(sizeof(PH_PERFORMANCE_CONTEXT)); + + performanceContext->WindowHandle = hwndDlg; + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + PerformanceUpdateHandler, + performanceContext, + &performanceContext->ProcessesUpdatedRegistration + ); + + // We have already set the group boxes to have WS_EX_TRANSPARENT to fix + // the drawing issue that arises when using WS_CLIPCHILDREN. However + // in removing the flicker from the graphs the group boxes will now flicker. + // It's a good tradeoff since no one stares at the group boxes. + PhSetWindowStyle(hwndDlg, WS_CLIPCHILDREN, WS_CLIPCHILDREN); + + PhInitializeGraphState(&performanceContext->CpuGraphState); + PhInitializeGraphState(&performanceContext->PrivateGraphState); + PhInitializeGraphState(&performanceContext->IoGraphState); + + performanceContext->CpuGraphHandle = GetDlgItem(hwndDlg, IDC_CPU); + PhSetWindowStyle(performanceContext->CpuGraphHandle, WS_BORDER, WS_BORDER); + Graph_SetTooltip(performanceContext->CpuGraphHandle, TRUE); + BringWindowToTop(performanceContext->CpuGraphHandle); + + performanceContext->PrivateGraphHandle = GetDlgItem(hwndDlg, IDC_PRIVATEBYTES); + PhSetWindowStyle(performanceContext->PrivateGraphHandle, WS_BORDER, WS_BORDER); + Graph_SetTooltip(performanceContext->PrivateGraphHandle, TRUE); + BringWindowToTop(performanceContext->PrivateGraphHandle); + + performanceContext->IoGraphHandle = GetDlgItem(hwndDlg, IDC_IO); + PhSetWindowStyle(performanceContext->IoGraphHandle, WS_BORDER, WS_BORDER); + Graph_SetTooltip(performanceContext->IoGraphHandle, TRUE); + BringWindowToTop(performanceContext->IoGraphHandle); + } + break; + case WM_DESTROY: + { + PhDeleteGraphState(&performanceContext->CpuGraphState); + PhDeleteGraphState(&performanceContext->PrivateGraphState); + PhDeleteGraphState(&performanceContext->IoGraphState); + + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &performanceContext->ProcessesUpdatedRegistration + ); + PhFree(performanceContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + if (header->hwndFrom == performanceContext->CpuGraphHandle) + { + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorCpuKernel, PhCsColorCpuUser); + + PhGraphStateGetDrawInfo( + &performanceContext->CpuGraphState, + getDrawInfo, + processItem->CpuKernelHistory.Count + ); + + if (!performanceContext->CpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&processItem->CpuKernelHistory, + performanceContext->CpuGraphState.Data1, drawInfo->LineDataCount); + PhCopyCircularBuffer_FLOAT(&processItem->CpuUserHistory, + performanceContext->CpuGraphState.Data2, drawInfo->LineDataCount); + performanceContext->CpuGraphState.Valid = TRUE; + } + + if (PhCsGraphShowText) + { + HDC hdc; + + PhMoveReference(&performanceContext->CpuGraphState.Text, + PhFormatString(L"%.2f%%", + (processItem->CpuKernelUsage + processItem->CpuUserUsage) * 100 + )); + + hdc = Graph_GetBufferedContext(performanceContext->CpuGraphHandle); + PhSetGraphText(hdc, drawInfo, &performanceContext->CpuGraphState.Text->sr, + &PhNormalGraphTextMargin, &PhNormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + } + else if (header->hwndFrom == performanceContext->PrivateGraphHandle) + { + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorPrivate, 0); + + PhGraphStateGetDrawInfo( + &performanceContext->PrivateGraphState, + getDrawInfo, + processItem->PrivateBytesHistory.Count + ); + + if (!performanceContext->PrivateGraphState.Valid) + { + ULONG i; + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + performanceContext->PrivateGraphState.Data1[i] = + (FLOAT)PhGetItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, i); + } + + if (processItem->VmCounters.PeakPagefileUsage != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + performanceContext->PrivateGraphState.Data1, + (FLOAT)processItem->VmCounters.PeakPagefileUsage, + drawInfo->LineDataCount + ); + } + + performanceContext->PrivateGraphState.Valid = TRUE; + } + + if (PhCsGraphShowText) + { + HDC hdc; + + PhMoveReference(&performanceContext->PrivateGraphState.Text, + PhConcatStrings2( + L"Private bytes: ", + PhaFormatSize(processItem->VmCounters.PagefileUsage, -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(performanceContext->PrivateGraphHandle); + PhSetGraphText(hdc, drawInfo, &performanceContext->PrivateGraphState.Text->sr, + &PhNormalGraphTextMargin, &PhNormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + } + else if (header->hwndFrom == performanceContext->IoGraphHandle) + { + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorIoReadOther, PhCsColorIoWrite); + + PhGraphStateGetDrawInfo( + &performanceContext->IoGraphState, + getDrawInfo, + processItem->IoReadHistory.Count + ); + + if (!performanceContext->IoGraphState.Valid) + { + ULONG i; + FLOAT max = 0; + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + performanceContext->IoGraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, i); + performanceContext->IoGraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + performanceContext->IoGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + performanceContext->IoGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + performanceContext->IoGraphState.Valid = TRUE; + } + + if (PhCsGraphShowText) + { + HDC hdc; + + PhMoveReference(&performanceContext->IoGraphState.Text, + PhFormatString( + L"R+O: %s, W: %s", + PhaFormatSize(processItem->IoReadDelta.Delta + processItem->IoOtherDelta.Delta, -1)->Buffer, + PhaFormatSize(processItem->IoWriteDelta.Delta, -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(performanceContext->IoGraphHandle); + PhSetGraphText(hdc, drawInfo, &performanceContext->IoGraphState.Text->sr, + &PhNormalGraphTextMargin, &PhNormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)lParam; + + if ( + header->hwndFrom == performanceContext->CpuGraphHandle && + getTooltipText->Index < getTooltipText->TotalCount + ) + { + if (performanceContext->CpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT cpuKernel; + FLOAT cpuUser; + + cpuKernel = PhGetItemCircularBuffer_FLOAT(&processItem->CpuKernelHistory, getTooltipText->Index); + cpuUser = PhGetItemCircularBuffer_FLOAT(&processItem->CpuUserHistory, getTooltipText->Index); + + PhMoveReference(&performanceContext->CpuGraphState.TooltipText, PhFormatString( + L"%.2f%%\n%s", + (cpuKernel + cpuUser) * 100, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(processItem, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = performanceContext->CpuGraphState.TooltipText->sr; + } + else if ( + header->hwndFrom == performanceContext->PrivateGraphHandle && + getTooltipText->Index < getTooltipText->TotalCount + ) + { + if (performanceContext->PrivateGraphState.TooltipIndex != getTooltipText->Index) + { + SIZE_T privateBytes; + + privateBytes = PhGetItemCircularBuffer_SIZE_T(&processItem->PrivateBytesHistory, getTooltipText->Index); + + PhMoveReference(&performanceContext->PrivateGraphState.TooltipText, PhFormatString( + L"Private bytes: %s\n%s", + PhaFormatSize(privateBytes, -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(processItem, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = performanceContext->PrivateGraphState.TooltipText->sr; + } + else if ( + header->hwndFrom == performanceContext->IoGraphHandle && + getTooltipText->Index < getTooltipText->TotalCount + ) + { + if (performanceContext->IoGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 ioRead; + ULONG64 ioWrite; + ULONG64 ioOther; + + ioRead = PhGetItemCircularBuffer_ULONG64(&processItem->IoReadHistory, getTooltipText->Index); + ioWrite = PhGetItemCircularBuffer_ULONG64(&processItem->IoWriteHistory, getTooltipText->Index); + ioOther = PhGetItemCircularBuffer_ULONG64(&processItem->IoOtherHistory, getTooltipText->Index); + + PhMoveReference(&performanceContext->IoGraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\nO: %s\n%s", + PhaFormatSize(ioRead, -1)->Buffer, + PhaFormatSize(ioWrite, -1)->Buffer, + PhaFormatSize(ioOther, -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(processItem, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = performanceContext->IoGraphState.TooltipText->sr; + } + } + break; + } + } + break; + case WM_SIZE: + { + HDWP deferHandle; + HWND cpuGroupBox = GetDlgItem(hwndDlg, IDC_GROUPCPU); + HWND privateBytesGroupBox = GetDlgItem(hwndDlg, IDC_GROUPPRIVATEBYTES); + HWND ioGroupBox = GetDlgItem(hwndDlg, IDC_GROUPIO); + RECT clientRect; + RECT margin = { PH_SCALE_DPI(13), PH_SCALE_DPI(13), PH_SCALE_DPI(13), PH_SCALE_DPI(13) }; + RECT innerMargin = { PH_SCALE_DPI(10), PH_SCALE_DPI(20), PH_SCALE_DPI(10), PH_SCALE_DPI(10) }; + LONG between = PH_SCALE_DPI(3); + LONG width; + LONG height; + + performanceContext->CpuGraphState.Valid = FALSE; + performanceContext->CpuGraphState.TooltipIndex = -1; + performanceContext->PrivateGraphState.Valid = FALSE; + performanceContext->PrivateGraphState.TooltipIndex = -1; + performanceContext->IoGraphState.Valid = FALSE; + performanceContext->IoGraphState.TooltipIndex = -1; + + GetClientRect(hwndDlg, &clientRect); + width = clientRect.right - margin.left - margin.right; + height = (clientRect.bottom - margin.top - margin.bottom - between * 2) / 3; + + deferHandle = BeginDeferWindowPos(6); + + deferHandle = DeferWindowPos(deferHandle, cpuGroupBox, NULL, margin.left, margin.top, + width, height, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + performanceContext->CpuGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + innerMargin.top, + width - innerMargin.left - innerMargin.right, + height - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos(deferHandle, privateBytesGroupBox, NULL, margin.left, margin.top + height + between, + width, height, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + performanceContext->PrivateGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + height + between + innerMargin.top, + width - innerMargin.left - innerMargin.right, + height - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos(deferHandle, ioGroupBox, NULL, margin.left, margin.top + (height + between) * 2, + width, height, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + performanceContext->IoGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + (height + between) * 2 + innerMargin.top, + width - innerMargin.left - innerMargin.right, + height - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); + } + break; + case WM_PH_PERFORMANCE_UPDATE: + { + if (!(processItem->State & PH_PROCESS_ITEM_REMOVED)) + { + performanceContext->CpuGraphState.Valid = FALSE; + Graph_MoveGrid(performanceContext->CpuGraphHandle, 1); + Graph_Draw(performanceContext->CpuGraphHandle); + Graph_UpdateTooltip(performanceContext->CpuGraphHandle); + InvalidateRect(performanceContext->CpuGraphHandle, NULL, FALSE); + + performanceContext->PrivateGraphState.Valid = FALSE; + Graph_MoveGrid(performanceContext->PrivateGraphHandle, 1); + Graph_Draw(performanceContext->PrivateGraphHandle); + Graph_UpdateTooltip(performanceContext->PrivateGraphHandle); + InvalidateRect(performanceContext->PrivateGraphHandle, NULL, FALSE); + + performanceContext->IoGraphState.Valid = FALSE; + Graph_MoveGrid(performanceContext->IoGraphHandle, 1); + Graph_Draw(performanceContext->IoGraphHandle); + Graph_UpdateTooltip(performanceContext->IoGraphHandle); + InvalidateRect(performanceContext->IoGraphHandle, NULL, FALSE); + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgsrv.c b/ProcessHacker/prpgsrv.c new file mode 100644 index 0000000..1d6c19e --- /dev/null +++ b/ProcessHacker/prpgsrv.c @@ -0,0 +1,137 @@ +/* + * Process Hacker - + * Process properties: Services page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +static VOID PhpLayoutServiceListControl( + _In_ HWND hwndDlg, + _In_ HWND ServiceListHandle + ) +{ + RECT rect; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), &rect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&rect, 2); + + MoveWindow( + ServiceListHandle, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + TRUE + ); +} + +INT_PTR CALLBACK PhpProcessServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + + if (!PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_SERVICE_ITEM *services; + ULONG numberOfServices; + ULONG i; + HWND serviceListHandle; + + // Get a copy of the process' service list. + + PhAcquireQueuedLockShared(&processItem->ServiceListLock); + + numberOfServices = processItem->ServiceList->Count; + services = PhAllocate(numberOfServices * sizeof(PPH_SERVICE_ITEM)); + + { + ULONG enumerationKey = 0; + PPH_SERVICE_ITEM serviceItem; + + i = 0; + + while (PhEnumPointerList(processItem->ServiceList, &enumerationKey, &serviceItem)) + { + PhReferenceObject(serviceItem); + services[i++] = serviceItem; + } + } + + PhReleaseQueuedLockShared(&processItem->ServiceListLock); + + serviceListHandle = PhCreateServiceListControl( + hwndDlg, + services, + numberOfServices + ); + SendMessage(serviceListHandle, WM_PH_SET_LIST_VIEW_SETTINGS, 0, (LPARAM)L"ProcessServiceListViewColumns"); + ShowWindow(serviceListHandle, SW_SHOW); + + propPageContext->Context = serviceListHandle; + } + break; + case WM_DESTROY: + { + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), + dialogItem, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + PhpLayoutServiceListControl(hwndDlg, (HWND)propPageContext->Context); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_SIZE: + { + PhpLayoutServiceListControl(hwndDlg, (HWND)propPageContext->Context); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgstat.c b/ProcessHacker/prpgstat.c new file mode 100644 index 0000000..e3f3afa --- /dev/null +++ b/ProcessHacker/prpgstat.c @@ -0,0 +1,321 @@ +/* + * Process Hacker - + * Process properties: Statistics page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +static VOID NTAPI StatisticsUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_STATISTICS_CONTEXT statisticsContext = (PPH_STATISTICS_CONTEXT)Context; + + if (statisticsContext->Enabled) + PostMessage(statisticsContext->WindowHandle, WM_PH_STATISTICS_UPDATE, 0, 0); +} + +VOID PhpUpdateProcessStatistics( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_STATISTICS_CONTEXT Context + ) +{ + WCHAR timeSpan[PH_TIMESPAN_STR_LEN_1]; + + SetDlgItemInt(hwndDlg, IDC_ZPRIORITY_V, ProcessItem->BasePriority, TRUE); // priority + PhPrintTimeSpan(timeSpan, ProcessItem->KernelTime.QuadPart, PH_TIMESPAN_HMSM); // kernel time + SetDlgItemText(hwndDlg, IDC_ZKERNELTIME_V, timeSpan); + PhPrintTimeSpan(timeSpan, ProcessItem->UserTime.QuadPart, PH_TIMESPAN_HMSM); // user time + SetDlgItemText(hwndDlg, IDC_ZUSERTIME_V, timeSpan); + PhPrintTimeSpan(timeSpan, + ProcessItem->KernelTime.QuadPart + ProcessItem->UserTime.QuadPart, PH_TIMESPAN_HMSM); // total time + SetDlgItemText(hwndDlg, IDC_ZTOTALTIME_V, timeSpan); + + SetDlgItemText(hwndDlg, IDC_ZPRIVATEBYTES_V, + PhaFormatSize(ProcessItem->VmCounters.PagefileUsage, -1)->Buffer); // private bytes (same as PrivateUsage) + SetDlgItemText(hwndDlg, IDC_ZPEAKPRIVATEBYTES_V, + PhaFormatSize(ProcessItem->VmCounters.PeakPagefileUsage, -1)->Buffer); // peak private bytes + SetDlgItemText(hwndDlg, IDC_ZVIRTUALSIZE_V, + PhaFormatSize(ProcessItem->VmCounters.VirtualSize, -1)->Buffer); // virtual size + SetDlgItemText(hwndDlg, IDC_ZPEAKVIRTUALSIZE_V, + PhaFormatSize(ProcessItem->VmCounters.PeakVirtualSize, -1)->Buffer); // peak virtual size + SetDlgItemText(hwndDlg, IDC_ZPAGEFAULTS_V, + PhaFormatUInt64(ProcessItem->VmCounters.PageFaultCount, TRUE)->Buffer); // page faults + SetDlgItemText(hwndDlg, IDC_ZWORKINGSET_V, + PhaFormatSize(ProcessItem->VmCounters.WorkingSetSize, -1)->Buffer); // working set + SetDlgItemText(hwndDlg, IDC_ZPEAKWORKINGSET_V, + PhaFormatSize(ProcessItem->VmCounters.PeakWorkingSetSize, -1)->Buffer); // peak working set + + SetDlgItemText(hwndDlg, IDC_ZIOREADS_V, + PhaFormatUInt64(ProcessItem->IoCounters.ReadOperationCount, TRUE)->Buffer); // reads + SetDlgItemText(hwndDlg, IDC_ZIOREADBYTES_V, + PhaFormatSize(ProcessItem->IoCounters.ReadTransferCount, -1)->Buffer); // read bytes + SetDlgItemText(hwndDlg, IDC_ZIOWRITES_V, + PhaFormatUInt64(ProcessItem->IoCounters.WriteOperationCount, TRUE)->Buffer); // writes + SetDlgItemText(hwndDlg, IDC_ZIOWRITEBYTES_V, + PhaFormatSize(ProcessItem->IoCounters.WriteTransferCount, -1)->Buffer); // write bytes + SetDlgItemText(hwndDlg, IDC_ZIOOTHER_V, + PhaFormatUInt64(ProcessItem->IoCounters.OtherOperationCount, TRUE)->Buffer); // other + SetDlgItemText(hwndDlg, IDC_ZIOOTHERBYTES_V, + PhaFormatSize(ProcessItem->IoCounters.OtherTransferCount, -1)->Buffer); // read bytes + + SetDlgItemText(hwndDlg, IDC_ZHANDLES_V, + PhaFormatUInt64(ProcessItem->NumberOfHandles, TRUE)->Buffer); // handles + + // Optional information + if (!PH_IS_FAKE_PROCESS_ID(ProcessItem->ProcessId)) + { + PPH_STRING peakHandles = NULL; + PPH_STRING gdiHandles = NULL; + PPH_STRING userHandles = NULL; + PPH_STRING cycles = NULL; + ULONG pagePriority = -1; + IO_PRIORITY_HINT ioPriority = -1; + PPH_STRING privateWs = NULL; + PPH_STRING shareableWs = NULL; + PPH_STRING sharedWs = NULL; + BOOLEAN gotCycles = FALSE; + BOOLEAN gotWsCounters = FALSE; + + if (ProcessItem->QueryHandle) + { + ULONG64 cycleTime; + + if (WindowsVersion >= WINDOWS_7) + { + PROCESS_HANDLE_INFORMATION handleInfo; + + if (NT_SUCCESS(NtQueryInformationProcess( + ProcessItem->QueryHandle, + ProcessHandleCount, + &handleInfo, + sizeof(PROCESS_HANDLE_INFORMATION), + NULL + ))) + { + peakHandles = PhaFormatUInt64(handleInfo.HandleCountHighWatermark, TRUE); + } + } + + gdiHandles = PhaFormatUInt64(GetGuiResources(ProcessItem->QueryHandle, GR_GDIOBJECTS), TRUE); // GDI handles + userHandles = PhaFormatUInt64(GetGuiResources(ProcessItem->QueryHandle, GR_USEROBJECTS), TRUE); // USER handles + + if (WINDOWS_HAS_CYCLE_TIME && + NT_SUCCESS(PhGetProcessCycleTime(ProcessItem->QueryHandle, &cycleTime))) + { + cycles = PhaFormatUInt64(cycleTime, TRUE); + gotCycles = TRUE; + } + + if (WindowsVersion >= WINDOWS_VISTA) + { + PhGetProcessPagePriority(ProcessItem->QueryHandle, &pagePriority); + PhGetProcessIoPriority(ProcessItem->QueryHandle, &ioPriority); + } + } + + if (Context->ProcessHandle) + { + PH_PROCESS_WS_COUNTERS wsCounters; + + if (NT_SUCCESS(PhGetProcessWsCounters(Context->ProcessHandle, &wsCounters))) + { + privateWs = PhaFormatSize((ULONG64)wsCounters.NumberOfPrivatePages * PAGE_SIZE, -1); + shareableWs = PhaFormatSize((ULONG64)wsCounters.NumberOfShareablePages * PAGE_SIZE, -1); + sharedWs = PhaFormatSize((ULONG64)wsCounters.NumberOfSharedPages * PAGE_SIZE, -1); + gotWsCounters = TRUE; + } + } + + if (WindowsVersion >= WINDOWS_7) + { + if (!gotCycles) + cycles = PhaFormatUInt64(ProcessItem->CycleTimeDelta.Value, TRUE); + if (!gotWsCounters) + privateWs = PhaFormatSize(ProcessItem->WorkingSetPrivateSize, -1); + } + + if (WindowsVersion >= WINDOWS_7) + SetDlgItemText(hwndDlg, IDC_ZPEAKHANDLES_V, PhGetStringOrDefault(peakHandles, L"Unknown")); + else + SetDlgItemText(hwndDlg, IDC_ZPEAKHANDLES_V, L"N/A"); + + SetDlgItemText(hwndDlg, IDC_ZGDIHANDLES_V, PhGetStringOrDefault(gdiHandles, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_ZUSERHANDLES_V, PhGetStringOrDefault(userHandles, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_ZCYCLES_V, + PhGetStringOrDefault(cycles, WINDOWS_HAS_CYCLE_TIME ? L"Unknown" : L"N/A")); + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (pagePriority != -1 && pagePriority <= MEMORY_PRIORITY_NORMAL) + SetDlgItemText(hwndDlg, IDC_ZPAGEPRIORITY_V, PhPagePriorityNames[pagePriority]); + else + SetDlgItemText(hwndDlg, IDC_ZPAGEPRIORITY_V, L"Unknown"); + + if (ioPriority != -1 && ioPriority < MaxIoPriorityTypes) + SetDlgItemText(hwndDlg, IDC_ZIOPRIORITY_V, PhIoPriorityHintNames[ioPriority]); + else + SetDlgItemText(hwndDlg, IDC_ZIOPRIORITY_V, L"Unknown"); + } + else + { + SetDlgItemText(hwndDlg, IDC_ZPAGEPRIORITY_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZIOPRIORITY_V, L"N/A"); + } + + SetDlgItemText(hwndDlg, IDC_ZPRIVATEWS_V, PhGetStringOrDefault(privateWs, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_ZSHAREABLEWS_V, PhGetStringOrDefault(shareableWs, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_ZSHAREDWS_V, PhGetStringOrDefault(sharedWs, L"Unknown")); + } + else + { + SetDlgItemText(hwndDlg, IDC_ZPEAKHANDLES_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZGDIHANDLES_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZUSERHANDLES_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZCYCLES_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZPAGEPRIORITY_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZIOPRIORITY_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZPRIVATEWS_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZSHAREABLEWS_V, L"N/A"); + SetDlgItemText(hwndDlg, IDC_ZSHAREDWS_V, L"N/A"); + } +} + +INT_PTR CALLBACK PhpProcessStatisticsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_STATISTICS_CONTEXT statisticsContext; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + statisticsContext = (PPH_STATISTICS_CONTEXT)propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + statisticsContext = propPageContext->Context = + PhAllocate(sizeof(PH_STATISTICS_CONTEXT)); + + statisticsContext->WindowHandle = hwndDlg; + statisticsContext->Enabled = TRUE; + statisticsContext->ProcessHandle = NULL; + + // Try to open a process handle with PROCESS_QUERY_INFORMATION access for + // WS information. + PhOpenProcess( + &statisticsContext->ProcessHandle, + PROCESS_QUERY_INFORMATION, + processItem->ProcessId + ); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + StatisticsUpdateHandler, + statisticsContext, + &statisticsContext->ProcessesUpdatedRegistration + ); + + PhpUpdateProcessStatistics(hwndDlg, processItem, statisticsContext); + } + break; + case WM_DESTROY: + { + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &statisticsContext->ProcessesUpdatedRegistration + ); + + if (statisticsContext->ProcessHandle) + NtClose(statisticsContext->ProcessHandle); + + PhFree(statisticsContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_DETAILS: + { + PhShowHandleStatisticsDialog(hwndDlg, processItem->ProcessId); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + statisticsContext->Enabled = TRUE; + break; + case PSN_KILLACTIVE: + statisticsContext->Enabled = FALSE; + break; + } + } + break; + case WM_PH_STATISTICS_UPDATE: + { + PhpUpdateProcessStatistics(hwndDlg, processItem, statisticsContext); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgthrd.c b/ProcessHacker/prpgthrd.c new file mode 100644 index 0000000..1c60a0f --- /dev/null +++ b/ProcessHacker/prpgthrd.c @@ -0,0 +1,1113 @@ +/* + * Process Hacker - + * Process properties: Threads page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static PH_STRINGREF EmptyThreadsText = PH_STRINGREF_INIT(L"There are no threads to display."); + +static VOID NTAPI ThreadAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREADS_CONTEXT threadsContext = (PPH_THREADS_CONTEXT)Context; + + // Parameter contains a pointer to the added thread item. + PhReferenceObject(Parameter); + PhPushProviderEventQueue(&threadsContext->EventQueue, ProviderAddedEvent, Parameter, (ULONG)threadsContext->Provider->RunId); +} + +static VOID NTAPI ThreadModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREADS_CONTEXT threadsContext = (PPH_THREADS_CONTEXT)Context; + + PhPushProviderEventQueue(&threadsContext->EventQueue, ProviderModifiedEvent, Parameter, (ULONG)threadsContext->Provider->RunId); +} + +static VOID NTAPI ThreadRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREADS_CONTEXT threadsContext = (PPH_THREADS_CONTEXT)Context; + + PhPushProviderEventQueue(&threadsContext->EventQueue, ProviderRemovedEvent, Parameter, (ULONG)threadsContext->Provider->RunId); +} + +static VOID NTAPI ThreadsUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREADS_CONTEXT threadsContext = (PPH_THREADS_CONTEXT)Context; + + PostMessage(threadsContext->WindowHandle, WM_PH_THREADS_UPDATED, (ULONG)threadsContext->Provider->RunId, threadsContext->Provider->RunId == 1); +} + +static VOID NTAPI ThreadsLoadingStateChangedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREADS_CONTEXT threadsContext = (PPH_THREADS_CONTEXT)Context; + + PostMessage( + threadsContext->ListContext.TreeNewHandle, + TNM_SETCURSOR, + 0, + // Parameter contains TRUE if loading symbols + (LPARAM)(Parameter ? LoadCursor(NULL, IDC_APPSTARTING) : NULL) + ); +} + +VOID PhpInitializeThreadMenu( + _In_ PPH_EMENU Menu, + _In_ HANDLE ProcessId, + _In_ PPH_THREAD_ITEM *Threads, + _In_ ULONG NumberOfThreads + ) +{ + PPH_EMENU_ITEM item; + + if (NumberOfThreads == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfThreads == 1) + { + // All menu items are enabled by default. + } + else + { + ULONG menuItemsMultiEnabled[] = + { + ID_THREAD_TERMINATE, + ID_THREAD_SUSPEND, + ID_THREAD_RESUME, + ID_THREAD_COPY + }; + ULONG i; + + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + + // These menu items are capable of manipulating + // multiple threads. + for (i = 0; i < sizeof(menuItemsMultiEnabled) / sizeof(ULONG); i++) + { + PhEnableEMenuItem(Menu, menuItemsMultiEnabled[i], TRUE); + } + } + + // Remove irrelevant menu items. + + if (WindowsVersion < WINDOWS_VISTA) + { + // Remove I/O priority. + if (item = PhFindEMenuItem(Menu, 0, L"I/O Priority", 0)) + PhDestroyEMenuItem(item); + // Remove page priority. + if (item = PhFindEMenuItem(Menu, 0, L"Page Priority", 0)) + PhDestroyEMenuItem(item); + } + + PhEnableEMenuItem(Menu, ID_THREAD_TOKEN, FALSE); + + // Priority + if (NumberOfThreads == 1) + { + HANDLE threadHandle; + ULONG threadPriority = THREAD_PRIORITY_ERROR_RETURN; + IO_PRIORITY_HINT ioPriority = -1; + ULONG pagePriority = -1; + ULONG id = 0; + + if (NT_SUCCESS(PhOpenThread( + &threadHandle, + ThreadQueryAccess, + Threads[0]->ThreadId + ))) + { + THREAD_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhGetThreadBasicInformation(threadHandle, &basicInfo))) + { + threadPriority = basicInfo.BasePriority; + } + + if (WindowsVersion >= WINDOWS_VISTA) + { + PhGetThreadIoPriority(threadHandle, &ioPriority); + PhGetThreadPagePriority(threadHandle, &pagePriority); + } + + // Token + { + HANDLE tokenHandle; + + if (NT_SUCCESS(NtOpenThreadToken( + threadHandle, + TOKEN_QUERY, + TRUE, + &tokenHandle + ))) + { + PhEnableEMenuItem(Menu, ID_THREAD_TOKEN, TRUE); + NtClose(tokenHandle); + } + } + + NtClose(threadHandle); + } + + switch (threadPriority) + { + case THREAD_PRIORITY_TIME_CRITICAL + 1: + case THREAD_PRIORITY_TIME_CRITICAL: + id = ID_PRIORITY_TIMECRITICAL; + break; + case THREAD_PRIORITY_HIGHEST: + id = ID_PRIORITY_HIGHEST; + break; + case THREAD_PRIORITY_ABOVE_NORMAL: + id = ID_PRIORITY_ABOVENORMAL; + break; + case THREAD_PRIORITY_NORMAL: + id = ID_PRIORITY_NORMAL; + break; + case THREAD_PRIORITY_BELOW_NORMAL: + id = ID_PRIORITY_BELOWNORMAL; + break; + case THREAD_PRIORITY_LOWEST: + id = ID_PRIORITY_LOWEST; + break; + case THREAD_PRIORITY_IDLE: + case THREAD_PRIORITY_IDLE - 1: + id = ID_PRIORITY_IDLE; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + + if (ioPriority != -1) + { + id = 0; + + switch (ioPriority) + { + case IoPriorityVeryLow: + id = ID_IOPRIORITY_VERYLOW; + break; + case IoPriorityLow: + id = ID_IOPRIORITY_LOW; + break; + case IoPriorityNormal: + id = ID_IOPRIORITY_NORMAL; + break; + case IoPriorityHigh: + id = ID_IOPRIORITY_HIGH; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } + + if (pagePriority != -1) + { + id = 0; + + switch (pagePriority) + { + case MEMORY_PRIORITY_VERY_LOW: + id = ID_PAGEPRIORITY_VERYLOW; + break; + case MEMORY_PRIORITY_LOW: + id = ID_PAGEPRIORITY_LOW; + break; + case MEMORY_PRIORITY_MEDIUM: + id = ID_PAGEPRIORITY_MEDIUM; + break; + case MEMORY_PRIORITY_BELOW_NORMAL: + id = ID_PAGEPRIORITY_BELOWNORMAL; + break; + case MEMORY_PRIORITY_NORMAL: + id = ID_PAGEPRIORITY_NORMAL; + break; + } + + if (id != 0) + { + PhSetFlagsEMenuItem(Menu, id, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } + } +} + +static NTSTATUS NTAPI PhpThreadPermissionsOpenThread( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + return PhOpenThread(Handle, DesiredAccess, (HANDLE)Context); +} + +static NTSTATUS NTAPI PhpOpenThreadTokenObject( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + return NtOpenThreadToken( + (HANDLE)Context, + DesiredAccess, + TRUE, + Handle + ); +} + +VOID PhpUpdateThreadDetails( + _In_ HWND hwndDlg, + _In_ PPH_THREADS_CONTEXT Context, + _In_ BOOLEAN Force + ) +{ + PPH_THREAD_ITEM *threads; + ULONG numberOfThreads; + PPH_THREAD_ITEM threadItem; + PPH_STRING startModule = NULL; + PPH_STRING started = NULL; + WCHAR kernelTime[PH_TIMESPAN_STR_LEN_1] = L"N/A"; + WCHAR userTime[PH_TIMESPAN_STR_LEN_1] = L"N/A"; + PPH_STRING contextSwitches = NULL; + PPH_STRING cycles = NULL; + PPH_STRING state = NULL; + WCHAR priority[PH_INT32_STR_LEN_1] = L"N/A"; + WCHAR basePriority[PH_INT32_STR_LEN_1] = L"N/A"; + PWSTR ioPriority = L"N/A"; + PWSTR pagePriority = L"N/A"; + WCHAR idealProcessor[PH_INT32_STR_LEN + 1 + PH_INT32_STR_LEN + 1] = L"N/A"; + HANDLE threadHandle; + SYSTEMTIME time; + IO_PRIORITY_HINT ioPriorityInteger; + ULONG pagePriorityInteger; + PROCESSOR_NUMBER idealProcessorNumber; + ULONG suspendCount; + + PhGetSelectedThreadItems(&Context->ListContext, &threads, &numberOfThreads); + + if (numberOfThreads == 1) + threadItem = threads[0]; + else + threadItem = NULL; + + PhFree(threads); + + if (numberOfThreads != 1 && !Force) + return; + + if (numberOfThreads == 1) + { + startModule = threadItem->StartAddressFileName; + + PhLargeIntegerToLocalSystemTime(&time, &threadItem->CreateTime); + started = PhaFormatDateTime(&time); + + PhPrintTimeSpan(kernelTime, threadItem->KernelTime.QuadPart, PH_TIMESPAN_HMSM); + PhPrintTimeSpan(userTime, threadItem->UserTime.QuadPart, PH_TIMESPAN_HMSM); + + contextSwitches = PhaFormatUInt64(threadItem->ContextSwitchesDelta.Value, TRUE); + + if (WINDOWS_HAS_CYCLE_TIME) + cycles = PhaFormatUInt64(threadItem->CyclesDelta.Value, TRUE); + + if (threadItem->State != Waiting) + { + if ((ULONG)threadItem->State < MaximumThreadState) + state = PhaCreateString(PhKThreadStateNames[(ULONG)threadItem->State]); + else + state = PhaCreateString(L"Unknown"); + } + else + { + if ((ULONG)threadItem->WaitReason < MaximumWaitReason) + state = PhaConcatStrings2(L"Wait:", PhKWaitReasonNames[(ULONG)threadItem->WaitReason]); + else + state = PhaCreateString(L"Waiting"); + } + + PhPrintInt32(priority, threadItem->Priority); + PhPrintInt32(basePriority, threadItem->BasePriority); + + if (NT_SUCCESS(PhOpenThread(&threadHandle, ThreadQueryAccess, threadItem->ThreadId))) + { + if (NT_SUCCESS(PhGetThreadIoPriority(threadHandle, &ioPriorityInteger)) && + ioPriorityInteger < MaxIoPriorityTypes) + { + ioPriority = PhIoPriorityHintNames[ioPriorityInteger]; + } + + if (NT_SUCCESS(PhGetThreadPagePriority(threadHandle, &pagePriorityInteger)) && + pagePriorityInteger <= MEMORY_PRIORITY_NORMAL) + { + pagePriority = PhPagePriorityNames[pagePriorityInteger]; + } + + if (NT_SUCCESS(NtQueryInformationThread(threadHandle, ThreadIdealProcessorEx, &idealProcessorNumber, sizeof(PROCESSOR_NUMBER), NULL))) + { + PH_FORMAT format[3]; + + PhInitFormatU(&format[0], idealProcessorNumber.Group); + PhInitFormatC(&format[1], ':'); + PhInitFormatU(&format[2], idealProcessorNumber.Number); + PhFormatToBuffer(format, 3, idealProcessor, sizeof(idealProcessor), NULL); + } + + if (threadItem->WaitReason == Suspended && NT_SUCCESS(NtQueryInformationThread(threadHandle, ThreadSuspendCount, &suspendCount, sizeof(ULONG), NULL))) + { + PH_FORMAT format[4]; + + PhInitFormatSR(&format[0], state->sr); + PhInitFormatS(&format[1], L" ("); + PhInitFormatU(&format[2], suspendCount); + PhInitFormatS(&format[3], L")"); + state = PH_AUTO(PhFormat(format, 4, 30)); + } + + NtClose(threadHandle); + } + } + + if (Force) + { + // These don't change... + + SetDlgItemText(hwndDlg, IDC_STARTMODULE, PhGetStringOrEmpty(startModule)); + EnableWindow(GetDlgItem(hwndDlg, IDC_OPENSTARTMODULE), !!startModule); + + SetDlgItemText(hwndDlg, IDC_STARTED, PhGetStringOrDefault(started, L"N/A")); + } + + SetDlgItemText(hwndDlg, IDC_KERNELTIME, kernelTime); + SetDlgItemText(hwndDlg, IDC_USERTIME, userTime); + SetDlgItemText(hwndDlg, IDC_CONTEXTSWITCHES, PhGetStringOrDefault(contextSwitches, L"N/A")); + SetDlgItemText(hwndDlg, IDC_CYCLES, PhGetStringOrDefault(cycles, L"N/A")); + SetDlgItemText(hwndDlg, IDC_STATE, PhGetStringOrDefault(state, L"N/A")); + SetDlgItemText(hwndDlg, IDC_PRIORITY, priority); + SetDlgItemText(hwndDlg, IDC_BASEPRIORITY, basePriority); + SetDlgItemText(hwndDlg, IDC_IOPRIORITY, ioPriority); + SetDlgItemText(hwndDlg, IDC_PAGEPRIORITY, pagePriority); + SetDlgItemText(hwndDlg, IDC_IDEALPROCESSOR, idealProcessor); +} + +VOID PhShowThreadContextMenu( + _In_ HWND hwndDlg, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_THREADS_CONTEXT Context, + _In_ PPH_TREENEW_CONTEXT_MENU ContextMenu + ) +{ + PPH_THREAD_ITEM *threads; + ULONG numberOfThreads; + + PhGetSelectedThreadItems(&Context->ListContext, &threads, &numberOfThreads); + + if (numberOfThreads != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + PH_PLUGIN_MENU_INFORMATION menuInfo; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_THREAD), 0); + PhSetFlagsEMenuItem(menu, ID_THREAD_INSPECT, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + PhpInitializeThreadMenu(menu, ProcessItem->ProcessId, threads, numberOfThreads); + PhInsertCopyCellEMenuItem(menu, ID_THREAD_COPY, Context->ListContext.TreeNewHandle, ContextMenu->Column); + + if (PhPluginsEnabled) + { + PhPluginInitializeMenuInfo(&menuInfo, menu, hwndDlg, 0); + menuInfo.u.Thread.ProcessId = ProcessItem->ProcessId; + menuInfo.u.Thread.Threads = threads; + menuInfo.u.Thread.NumberOfThreads = numberOfThreads; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadMenuInitializing), &menuInfo); + } + + item = PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + ContextMenu->Location.x, + ContextMenu->Location.y + ); + + if (item) + { + BOOLEAN handled = FALSE; + + handled = PhHandleCopyCellEMenuItem(item); + + if (!handled && PhPluginsEnabled) + handled = PhPluginTriggerEMenuItem(&menuInfo, item); + + if (!handled) + SendMessage(hwndDlg, WM_COMMAND, item->Id, 0); + } + + PhDestroyEMenu(menu); + } + + PhFree(threads); +} + +INT_PTR CALLBACK PhpProcessThreadsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPH_THREADS_CONTEXT threadsContext; + HWND tnHandle; + + if (PhpPropPageDlgProcHeader(hwndDlg, uMsg, lParam, + &propSheetPage, &propPageContext, &processItem)) + { + threadsContext = (PPH_THREADS_CONTEXT)propPageContext->Context; + + if (threadsContext) + tnHandle = threadsContext->ListContext.TreeNewHandle; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + threadsContext = propPageContext->Context = + PhAllocate(PhEmGetObjectSize(EmThreadsContextType, sizeof(PH_THREADS_CONTEXT))); + + // The thread provider has a special registration mechanism. + threadsContext->Provider = PhCreateThreadProvider( + processItem->ProcessId + ); + PhRegisterCallback( + &threadsContext->Provider->ThreadAddedEvent, + ThreadAddedHandler, + threadsContext, + &threadsContext->AddedEventRegistration + ); + PhRegisterCallback( + &threadsContext->Provider->ThreadModifiedEvent, + ThreadModifiedHandler, + threadsContext, + &threadsContext->ModifiedEventRegistration + ); + PhRegisterCallback( + &threadsContext->Provider->ThreadRemovedEvent, + ThreadRemovedHandler, + threadsContext, + &threadsContext->RemovedEventRegistration + ); + PhRegisterCallback( + &threadsContext->Provider->UpdatedEvent, + ThreadsUpdatedHandler, + threadsContext, + &threadsContext->UpdatedEventRegistration + ); + PhRegisterCallback( + &threadsContext->Provider->LoadingStateChangedEvent, + ThreadsLoadingStateChangedHandler, + threadsContext, + &threadsContext->LoadingStateChangedEventRegistration + ); + threadsContext->WindowHandle = hwndDlg; + + // Initialize the list. + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + BringWindowToTop(tnHandle); + PhInitializeThreadList(hwndDlg, tnHandle, &threadsContext->ListContext); + TreeNew_SetEmptyText(tnHandle, &EmptyThreadsText, 0); + PhInitializeProviderEventQueue(&threadsContext->EventQueue, 100); + + // Use Cycles instead of Context Switches on Vista and above, but only when we can + // open the process, since cycle time information requires sufficient access to the + // threads. + if (WINDOWS_HAS_CYCLE_TIME) + { + HANDLE processHandle; + PROCESS_EXTENDED_BASIC_INFORMATION extendedBasicInfo; + + // We make a distinction between PROCESS_QUERY_INFORMATION and PROCESS_QUERY_LIMITED_INFORMATION since + // the latter can be used when opening audiodg.exe even though we can't access its threads using + // THREAD_QUERY_LIMITED_INFORMATION. + + if (processItem->ProcessId == SYSTEM_IDLE_PROCESS_ID) + { + threadsContext->ListContext.UseCycleTime = TRUE; + } + else if (NT_SUCCESS(PhOpenProcess(&processHandle, PROCESS_QUERY_INFORMATION, processItem->ProcessId))) + { + threadsContext->ListContext.UseCycleTime = TRUE; + NtClose(processHandle); + } + else if (NT_SUCCESS(PhOpenProcess(&processHandle, PROCESS_QUERY_LIMITED_INFORMATION, processItem->ProcessId))) + { + threadsContext->ListContext.UseCycleTime = TRUE; + + // We can't use cycle time for protected processes (without KProcessHacker). + if (NT_SUCCESS(PhGetProcessExtendedBasicInformation(processHandle, &extendedBasicInfo)) && extendedBasicInfo.IsProtectedProcess) + { + threadsContext->ListContext.UseCycleTime = FALSE; + } + + NtClose(processHandle); + } + } + + if (processItem->ServiceList && processItem->ServiceList->Count != 0 && WINDOWS_HAS_SERVICE_TAGS) + threadsContext->ListContext.HasServices = TRUE; + + PhEmCallObjectOperation(EmThreadsContextType, threadsContext, EmObjectCreate); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &threadsContext->ListContext.Cm; + treeNewInfo.SystemContext = threadsContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadTreeNewInitializing), &treeNewInfo); + } + + PhLoadSettingsThreadList(&threadsContext->ListContext); + + PhThreadProviderInitialUpdate(threadsContext->Provider); + PhRegisterThreadProvider(threadsContext->Provider, &threadsContext->ProviderRegistration); + + SET_BUTTON_ICON(IDC_OPENSTARTMODULE, PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_FOLDER))); + } + break; + case WM_DESTROY: + { + PhEmCallObjectOperation(EmThreadsContextType, threadsContext, EmObjectDelete); + + PhUnregisterCallback( + &threadsContext->Provider->ThreadAddedEvent, + &threadsContext->AddedEventRegistration + ); + PhUnregisterCallback( + &threadsContext->Provider->ThreadModifiedEvent, + &threadsContext->ModifiedEventRegistration + ); + PhUnregisterCallback( + &threadsContext->Provider->ThreadRemovedEvent, + &threadsContext->RemovedEventRegistration + ); + PhUnregisterCallback( + &threadsContext->Provider->UpdatedEvent, + &threadsContext->UpdatedEventRegistration + ); + PhUnregisterCallback( + &threadsContext->Provider->LoadingStateChangedEvent, + &threadsContext->LoadingStateChangedEventRegistration + ); + PhUnregisterThreadProvider(threadsContext->Provider, &threadsContext->ProviderRegistration); + PhSetTerminatingThreadProvider(threadsContext->Provider); + PhDereferenceObject(threadsContext->Provider); + PhDeleteProviderEventQueue(&threadsContext->EventQueue); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = tnHandle; + treeNewInfo.CmData = &threadsContext->ListContext.Cm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadTreeNewUninitializing), &treeNewInfo); + } + + PhSaveSettingsThreadList(&threadsContext->ListContext); + PhDeleteThreadList(&threadsContext->ListContext); + + PhFree(threadsContext); + + PhpPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (!propPageContext->LayoutInitialized) + { + PPH_LAYOUT_ITEM dialogItem; + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), + dialogItem, PH_ANCHOR_ALL); + +#define ADD_BL_ITEM(Id) \ + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, Id), dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM) + + // Thread details area + { + ULONG id; + + for (id = IDC_STATICBL1; id <= IDC_STATICBL11; id++) + ADD_BL_ITEM(id); + + // Not in sequence + ADD_BL_ITEM(IDC_STATICBL12); + } + + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_STARTMODULE), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_OPENSTARTMODULE), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + ADD_BL_ITEM(IDC_STARTED); + ADD_BL_ITEM(IDC_KERNELTIME); + ADD_BL_ITEM(IDC_USERTIME); + ADD_BL_ITEM(IDC_CONTEXTSWITCHES); + ADD_BL_ITEM(IDC_CYCLES); + ADD_BL_ITEM(IDC_STATE); + ADD_BL_ITEM(IDC_PRIORITY); + ADD_BL_ITEM(IDC_BASEPRIORITY); + ADD_BL_ITEM(IDC_IOPRIORITY); + ADD_BL_ITEM(IDC_PAGEPRIORITY); + ADD_BL_ITEM(IDC_IDEALPROCESSOR); + + PhDoPropPageLayout(hwndDlg); + + propPageContext->LayoutInitialized = TRUE; + } + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case ID_SHOWCONTEXTMENU: + { + PhShowThreadContextMenu(hwndDlg, processItem, threadsContext, (PPH_TREENEW_CONTEXT_MENU)lParam); + } + break; + case ID_THREAD_INSPECT: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + PhReferenceObject(threadsContext->Provider); + PhShowThreadStackDialog( + hwndDlg, + threadsContext->Provider->ProcessId, + threadItem->ThreadId, + threadsContext->Provider + ); + PhDereferenceObject(threadsContext->Provider); + } + } + break; + case ID_THREAD_TERMINATE: + { + PPH_THREAD_ITEM *threads; + ULONG numberOfThreads; + + PhGetSelectedThreadItems(&threadsContext->ListContext, &threads, &numberOfThreads); + PhReferenceObjects(threads, numberOfThreads); + + if (PhUiTerminateThreads(hwndDlg, threads, numberOfThreads)) + PhDeselectAllThreadNodes(&threadsContext->ListContext); + + PhDereferenceObjects(threads, numberOfThreads); + PhFree(threads); + } + break; + case ID_THREAD_SUSPEND: + { + PPH_THREAD_ITEM *threads; + ULONG numberOfThreads; + + PhGetSelectedThreadItems(&threadsContext->ListContext, &threads, &numberOfThreads); + PhReferenceObjects(threads, numberOfThreads); + PhUiSuspendThreads(hwndDlg, threads, numberOfThreads); + PhDereferenceObjects(threads, numberOfThreads); + PhFree(threads); + } + break; + case ID_THREAD_RESUME: + { + PPH_THREAD_ITEM *threads; + ULONG numberOfThreads; + + PhGetSelectedThreadItems(&threadsContext->ListContext, &threads, &numberOfThreads); + PhReferenceObjects(threads, numberOfThreads); + PhUiResumeThreads(hwndDlg, threads, numberOfThreads); + PhDereferenceObjects(threads, numberOfThreads); + PhFree(threads); + } + break; + case ID_THREAD_AFFINITY: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + PhReferenceObject(threadItem); + PhShowProcessAffinityDialog(hwndDlg, NULL, threadItem); + PhDereferenceObject(threadItem); + } + } + break; + case ID_THREAD_PERMISSIONS: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + if (threadItem) + { + stdObjectSecurity.OpenObject = PhpThreadPermissionsOpenThread; + stdObjectSecurity.ObjectType = L"Thread"; + stdObjectSecurity.Context = threadItem->ThreadId; + + if (PhGetAccessEntries(L"Thread", &accessEntries, &numberOfAccessEntries)) + { + PhEditSecurity( + hwndDlg, + PhaFormatString(L"Thread %u", HandleToUlong(threadItem->ThreadId))->Buffer, + PhStdGetObjectSecurity, + PhStdSetObjectSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + } + } + break; + case ID_THREAD_TOKEN: + { + NTSTATUS status; + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + HANDLE threadHandle; + + if (threadItem) + { + if (NT_SUCCESS(status = PhOpenThread( + &threadHandle, + ThreadQueryAccess, + threadItem->ThreadId + ))) + { + PhShowTokenProperties( + hwndDlg, + PhpOpenThreadTokenObject, + (PVOID)threadHandle, + NULL + ); + + NtClose(threadHandle); + } + else + { + PhShowStatus(hwndDlg, L"Unable to open the thread", status, 0); + } + } + } + break; + case ID_ANALYZE_WAIT: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + PhReferenceObject(threadsContext->Provider->SymbolProvider); + PhUiAnalyzeWaitThread( + hwndDlg, + processItem->ProcessId, + threadItem->ThreadId, + threadsContext->Provider->SymbolProvider + ); + PhDereferenceObject(threadsContext->Provider->SymbolProvider); + } + } + break; + case ID_PRIORITY_TIMECRITICAL: + case ID_PRIORITY_HIGHEST: + case ID_PRIORITY_ABOVENORMAL: + case ID_PRIORITY_NORMAL: + case ID_PRIORITY_BELOWNORMAL: + case ID_PRIORITY_LOWEST: + case ID_PRIORITY_IDLE: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + ULONG threadPriorityWin32; + + switch (id) + { + case ID_PRIORITY_TIMECRITICAL: + threadPriorityWin32 = THREAD_PRIORITY_TIME_CRITICAL; + break; + case ID_PRIORITY_HIGHEST: + threadPriorityWin32 = THREAD_PRIORITY_HIGHEST; + break; + case ID_PRIORITY_ABOVENORMAL: + threadPriorityWin32 = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case ID_PRIORITY_NORMAL: + threadPriorityWin32 = THREAD_PRIORITY_NORMAL; + break; + case ID_PRIORITY_BELOWNORMAL: + threadPriorityWin32 = THREAD_PRIORITY_BELOW_NORMAL; + break; + case ID_PRIORITY_LOWEST: + threadPriorityWin32 = THREAD_PRIORITY_LOWEST; + break; + case ID_PRIORITY_IDLE: + threadPriorityWin32 = THREAD_PRIORITY_IDLE; + break; + } + + PhReferenceObject(threadItem); + PhUiSetPriorityThread(hwndDlg, threadItem, threadPriorityWin32); + PhDereferenceObject(threadItem); + } + } + break; + case ID_IOPRIORITY_VERYLOW: + case ID_IOPRIORITY_LOW: + case ID_IOPRIORITY_NORMAL: + case ID_IOPRIORITY_HIGH: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + IO_PRIORITY_HINT ioPriority; + + switch (id) + { + case ID_IOPRIORITY_VERYLOW: + ioPriority = IoPriorityVeryLow; + break; + case ID_IOPRIORITY_LOW: + ioPriority = IoPriorityLow; + break; + case ID_IOPRIORITY_NORMAL: + ioPriority = IoPriorityNormal; + break; + case ID_IOPRIORITY_HIGH: + ioPriority = IoPriorityHigh; + break; + } + + PhReferenceObject(threadItem); + PhUiSetIoPriorityThread(hwndDlg, threadItem, ioPriority); + PhDereferenceObject(threadItem); + } + } + break; + case ID_PAGEPRIORITY_VERYLOW: + case ID_PAGEPRIORITY_LOW: + case ID_PAGEPRIORITY_MEDIUM: + case ID_PAGEPRIORITY_BELOWNORMAL: + case ID_PAGEPRIORITY_NORMAL: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem) + { + ULONG pagePriority; + + switch (id) + { + case ID_PAGEPRIORITY_VERYLOW: + pagePriority = MEMORY_PRIORITY_VERY_LOW; + break; + case ID_PAGEPRIORITY_LOW: + pagePriority = MEMORY_PRIORITY_LOW; + break; + case ID_PAGEPRIORITY_MEDIUM: + pagePriority = MEMORY_PRIORITY_MEDIUM; + break; + case ID_PAGEPRIORITY_BELOWNORMAL: + pagePriority = MEMORY_PRIORITY_BELOW_NORMAL; + break; + case ID_PAGEPRIORITY_NORMAL: + pagePriority = MEMORY_PRIORITY_NORMAL; + break; + } + + PhReferenceObject(threadItem); + PhUiSetPagePriorityThread(hwndDlg, threadItem, pagePriority); + PhDereferenceObject(threadItem); + } + } + break; + case ID_THREAD_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(tnHandle, 0); + PhSetClipboardString(tnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + case IDC_OPENSTARTMODULE: + { + PPH_THREAD_ITEM threadItem = PhGetSelectedThreadItem(&threadsContext->ListContext); + + if (threadItem && threadItem->StartAddressFileName) + { + PhShellExploreFile(hwndDlg, threadItem->StartAddressFileName->Buffer); + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + break; + case PSN_KILLACTIVE: + // Can't disable, it screws up the deltas. + break; + } + } + break; + case WM_PH_THREADS_UPDATED: + { + ULONG upToRunId = (ULONG)wParam; + BOOLEAN firstRun = !!lParam; + PPH_PROVIDER_EVENT events; + ULONG count; + ULONG i; + + events = PhFlushProviderEventQueue(&threadsContext->EventQueue, upToRunId, &count); + + if (events) + { + TreeNew_SetRedraw(tnHandle, FALSE); + + for (i = 0; i < count; i++) + { + PH_PROVIDER_EVENT_TYPE type = PH_PROVIDER_EVENT_TYPE(events[i]); + PPH_THREAD_ITEM threadItem = PH_PROVIDER_EVENT_OBJECT(events[i]); + + switch (type) + { + case ProviderAddedEvent: + PhAddThreadNode(&threadsContext->ListContext, threadItem, firstRun); + PhDereferenceObject(threadItem); + break; + case ProviderModifiedEvent: + PhUpdateThreadNode(&threadsContext->ListContext, PhFindThreadNode(&threadsContext->ListContext, threadItem->ThreadId)); + break; + case ProviderRemovedEvent: + PhRemoveThreadNode(&threadsContext->ListContext, PhFindThreadNode(&threadsContext->ListContext, threadItem->ThreadId)); + break; + } + } + + PhFree(events); + } + + PhTickThreadNodes(&threadsContext->ListContext); + + if (count != 0) + TreeNew_SetRedraw(tnHandle, TRUE); + + if (propPageContext->PropContext->SelectThreadId) + { + PPH_THREAD_NODE threadNode; + + if (threadNode = PhFindThreadNode(&threadsContext->ListContext, propPageContext->PropContext->SelectThreadId)) + { + if (threadNode->Node.Visible) + { + TreeNew_SetFocusNode(tnHandle, &threadNode->Node); + TreeNew_SetMarkNode(tnHandle, &threadNode->Node); + TreeNew_SelectRange(tnHandle, threadNode->Node.Index, threadNode->Node.Index); + TreeNew_EnsureVisible(tnHandle, &threadNode->Node); + } + } + + propPageContext->PropContext->SelectThreadId = NULL; + } + + PhpUpdateThreadDetails(hwndDlg, threadsContext, FALSE); + } + break; + case WM_PH_THREAD_SELECTION_CHANGED: + { + PhpUpdateThreadDetails(hwndDlg, threadsContext, TRUE); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/prpgtok.c b/ProcessHacker/prpgtok.c new file mode 100644 index 0000000..7b74354 --- /dev/null +++ b/ProcessHacker/prpgtok.c @@ -0,0 +1,118 @@ +/* + * Process Hacker - + * Process properties: Token page + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +NTSTATUS NTAPI PhpOpenProcessTokenForPage( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + (HANDLE)Context + ))) + return status; + + status = PhOpenProcessToken(processHandle, DesiredAccess, Handle); + NtClose(processHandle); + + return status; +} + +INT_PTR CALLBACK PhpProcessTokenHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_SHOWWINDOW: + { + if (!GetProp(hwndDlg, PhMakeContextAtom())) // LayoutInitialized + { + PPH_LAYOUT_ITEM dialogItem; + HWND groupsLv; + HWND privilegesLv; + + // This is a big violation of abstraction... + + dialogItem = PhAddPropPageLayoutItem(hwndDlg, hwndDlg, + PH_PROP_PAGE_TAB_CONTROL_PARENT, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_USER), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_USERSID), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_VIRTUALIZED), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_APPCONTAINERSID), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_GROUPS), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_PRIVILEGES), + dialogItem, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_INSTRUCTION), + dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_INTEGRITY), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_ADVANCED), + dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + PhDoPropPageLayout(hwndDlg); + + groupsLv = GetDlgItem(hwndDlg, IDC_GROUPS); + privilegesLv = GetDlgItem(hwndDlg, IDC_PRIVILEGES); + + if (ListView_GetItemCount(groupsLv) != 0) + { + ListView_SetColumnWidth(groupsLv, 0, LVSCW_AUTOSIZE); + ExtendedListView_SetColumnWidth(groupsLv, 1, ELVSCW_AUTOSIZE_REMAININGSPACE); + } + + if (ListView_GetItemCount(privilegesLv) != 0) + { + ListView_SetColumnWidth(privilegesLv, 0, LVSCW_AUTOSIZE); + ListView_SetColumnWidth(privilegesLv, 1, LVSCW_AUTOSIZE); + ExtendedListView_SetColumnWidth(privilegesLv, 2, ELVSCW_AUTOSIZE_REMAININGSPACE); + } + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)TRUE); + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/resource.h b/ProcessHacker/resource.h new file mode 100644 index 0000000..084c13e --- /dev/null +++ b/ProcessHacker/resource.h @@ -0,0 +1,745 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ProcessHacker.rc +// +#define IDR_RT_MANIFEST 1 +#define IDI_PROCESSHACKER 101 +#define IDR_MAINWND 102 +#define IDR_MAINWND_ACCEL 102 +#define IDD_PROCGENERAL 103 +#define IDD_PROCMODULES 104 +#define IDD_PROCTHREADS 105 +#define IDD_PROCHANDLES 106 +#define IDD_PROCENVIRONMENT 107 +#define IDD_THRDSTACK 108 +#define IDR_THREAD 110 +#define IDR_HANDLE 111 +#define IDR_MODULE 112 +#define IDC_CPU 112 +#define IDC_PRIVATEBYTES 113 +#define IDC_IO 114 +#define IDB_CROSS 117 +#define IDB_TICK 118 +#define IDC_PHYSICAL 119 +#define IDR_COMPUTER 120 +#define IDC_MEMORY 120 +#define IDD_ABOUT 121 +#define IDC_NEWOBJECTS 121 +#define IDC_REMOVEDOBJECTS 122 +#define IDR_PROCESS 123 +#define IDC_CPUUSER 123 +#define IDR_SERVICE 124 +#define IDC_CPUKERNEL 124 +#define IDC_IORO 125 +#define IDD_SRVLIST 125 +#define IDD_SRVGENERAL 126 +#define IDC_IOW 126 +#define IDD_HNDLGENERAL 128 +#define IDD_INFORMATION 129 +#define IDD_FINDOBJECTS 130 +#define IDD_OBJTOKEN 131 +#define ID_PLUGIN_MENU_ITEM 131 +#define ID_SHOWCONTEXTMENU 132 +#define IDR_PRIVILEGE 133 +#define IDC_SEPARATOR 133 +#define IDR_FINDOBJ 134 +#define IDC_COMMIT 134 +#define IDD_HIDDENPROCESSES 135 +#define ID_TRAYICONS_REGISTERED 135 +#define IDD_RUNAS 136 +#define ID_COPY_CELL 136 +#define IDD_PROGRESS 137 +#define IDD_PAGEFILES 138 +#define IDD_TOKGENERAL 139 +#define IDD_TOKADVANCED 140 +#define IDD_OBJJOB 141 +#define IDD_OBJEVENT 142 +#define IDD_OBJMUTANT 143 +#define IDD_OBJSEMAPHORE 144 +#define IDD_OBJTIMER 145 +#define IDD_JOBSTATISTICS 146 +#define IDD_OBJEVENTPAIR 147 +#define IDD_OBJSECTION 148 +#define IDD_AFFINITY 149 +#define IDD_SYSINFO 150 +#define IDR_USER 151 +#define IDD_EDITMESSAGE 152 +#define IDD_SESSION 153 +#define IDD_PROCMEMORY 154 +#define IDD_CHOOSE 155 +#define IDD_OPTGENERAL 162 +#define IDD_OPTHIGHLIGHTING 163 +#define IDR_NETWORK 164 +#define IDD_CHOOSECOLUMNS 166 +#define IDD_NETSTACK 167 +#define IDD_CREATESERVICE 168 +#define IDD_PROCPERFORMANCE 169 +#define IDD_PROCSTATISTICS 170 +#define IDD_OPTADVANCED 171 +#define IDR_ICON 173 +#define IDD_GDIHANDLES 175 +#define IDD_LOG 178 +#define IDD_OPTSYMBOLS 179 +#define IDD_MEMEDIT 180 +#define IDR_MEMORY 181 +#define IDD_MEMPROTECT 182 +#define IDD_MEMRESULTS 183 +#define IDR_MEMFILTER 184 +#define IDD_MEMSTRING 185 +#define IDD_OPTGRAPHS 186 +#define IDD_PLUGINS 187 +#define IDD_HANDLESTATS 188 +#define IDD_PROCRECORD 189 +#define IDD_CHOOSEPROCESS 190 +#define IDD_PROCSERVICES 191 +#define IDI_PHAPPLICATION 191 +#define IDI_COG 192 +#define IDD_SHADOWSESSION 193 +#define IDI_PHAPPLICATIONGO 194 +#define IDD_TOKCAPABILITIES 194 +#define IDI_COGGO 195 +#define IDD_TOKATTRIBUTES 195 +#define IDD_SYSINFO_CPU 196 +#define IDD_SYSINFO_CPUPANEL 197 +#define IDD_SYSINFO_MEMPANEL 198 +#define IDR_SYSINFO_ACCEL 198 +#define IDD_SYSINFO_MEM 199 +#define IDD_SYSINFO_IO 200 +#define IDD_SYSINFO_IOPANEL 201 +#define IDD_MEMLISTS 202 +#define IDR_EMPTYMEMLISTS 204 +#define IDD_CONTAINER 205 +#define IDD_SYSINFO_MEMPANELXP 206 +#define IDD_MINIINFO 207 +#define IDD_MINIINFO_LIST 210 +#define IDR_MINIINFO 211 +#define IDR_MINIINFO_PROCESS 212 +#define IDR_ENVIRONMENT 214 +#define IDD_MITIGATION 215 +#define IDI_PIN 216 +#define IDI_FOLDER 217 +#define IDI_PENCIL 218 +#define IDI_MAGNIFIER 219 +#define IDD_EDITENV 221 +#define IDC_TERMINATE 1003 +#define IDC_FILEICON 1005 +#define IDC_FILE 1006 +#define IDC_PROCESS 1007 +#define IDC_COMPANYNAME 1009 +#define IDC_VERSION 1010 +#define IDC_REFRESH 1015 +#define IDC_PERMISSIONS 1018 +#define IDC_FILENAME 1020 +#define IDC_CMDLINE 1021 +#define IDC_URL 1021 +#define IDC_RUNSELECTED 1021 +#define IDC_CURDIR 1022 +#define IDC_STARTED 1023 +#define IDC_ABOUT_NAME 1024 +#define IDC_PEBADDRESS 1024 +#define IDC_TERMINATED 1024 +#define IDC_PARENTPROCESS 1025 +#define IDC_MITIGATION 1026 +#define IDC_PAUSE 1027 +#define IDC_PROTECTION 1027 +#define IDC_START 1028 +#define IDC_DESCRIPTION 1029 +#define IDC_TYPE 1032 +#define IDC_STARTTYPE 1033 +#define IDC_ADDRESS 1033 +#define IDC_IMPERSONATIONLEVEL 1033 +#define IDC_ERRORCONTROL 1034 +#define IDC_GRANTED_ACCESS 1034 +#define IDC_TOKENLUID 1034 +#define IDC_GROUP 1035 +#define IDC_AUTHENTICATIONLUID 1035 +#define IDC_BINARYPATH 1036 +#define IDC_MEMORYUSED 1036 +#define IDC_USERACCOUNT 1037 +#define IDC_MEMORYAVAILABLE 1037 +#define IDC_PASSWORD 1040 +#define IDC_PASSWORDCHECK 1041 +#define IDC_SERVICEDLL 1042 +#define IDC_NAME 1044 +#define IDC_REFERENCES 1045 +#define IDC_INTERNALNAME 1045 +#define IDC_HANDLES 1046 +#define IDC_PROCESSTYPELABEL 1046 +#define IDC_AUTHOR 1046 +#define IDC_PAGED 1047 +#define IDC_PROCESSTYPETEXT 1047 +#define IDC_NONPAGED 1048 +#define IDC_TEXT 1048 +#define IDC_DIAGNOSTICS 1049 +#define IDC_FILTER 1050 +#define IDC_RESULTS 1051 +#define IDC_USER 1052 +#define IDC_USERSID 1053 +#define IDC_ADVANCED 1054 +#define IDC_OWNER 1054 +#define IDC_GROUPS 1055 +#define IDC_PRIMARYGROUP 1055 +#define IDC_PRIVILEGES 1056 +#define IDC_VIRTUALIZATION 1056 +#define IDC_SESSIONID 1057 +#define IDC_ELEVATED 1058 +#define IDC_VIRTUALIZED 1059 +#define IDC_SOURCENAME 1059 +#define IDC_PROPERTIES 1060 +#define IDC_SOURCELUID 1060 +#define IDC_APPCONTAINERSID 1060 +#define IDC_LIST 1061 +#define IDC_PROCESSES 1063 +#define IDC_SCAN 1064 +#define IDC_SAVE 1065 +#define IDC_METHOD 1067 +#define IDC_INTRO 1068 +#define IDC_PROGRAM 1069 +#define IDC_BROWSE 1070 +#define IDC_USERNAME 1071 +#define IDC_SESSIONS 1074 +#define IDC_DESKTOPS 1075 +#define IDC_PROGRESS 1076 +#define IDC_PROGRESSTEXT 1077 +#define IDC_LINKEDTOKEN 1079 +#define IDC_DISABLEALL 1079 +#define IDC_MOVEUP 1079 +#define IDC_CHANGE 1079 +#define IDC_CLEAR 1079 +#define IDC_GOTO 1079 +#define IDC_OPTIONS 1079 +#define IDC_DETAILS 1079 +#define IDC_ADD 1079 +#define IDC_FONT 1079 +#define IDC_INTEGRITY 1079 +#define IDC_MORE 1079 +#define IDC_VIEWMITIGATION 1080 +#define IDC_VIEWPARENTPROCESS 1081 +#define IDC_OPENFILENAME 1082 +#define IDC_LIMITS 1083 +#define IDC_SIGNALED 1084 +#define IDC_SET 1085 +#define IDC_RESET 1086 +#define IDC_PULSE 1087 +#define IDC_COUNT 1088 +#define IDC_ABANDONED 1089 +#define IDC_CURRENTCOUNT 1090 +#define IDC_MAXIMUMCOUNT 1091 +#define IDC_ACQUIRE 1092 +#define IDC_RELEASE 1093 +#define IDC_CANCEL 1094 +#define IDC_OWNERLABEL 1095 +#define IDC_SETLOW 1096 +#define IDC_SETHIGH 1097 +#define IDC_SIZE_ 1098 +#define IDC_CPU0 1099 +#define IDC_CPU1 1100 +#define IDC_CPU2 1101 +#define IDC_CPU3 1102 +#define IDC_CPU4 1103 +#define IDC_CPU5 1104 +#define IDC_CPU6 1105 +#define IDC_CPU7 1106 +#define IDC_CPU8 1107 +#define IDC_TITLE 1107 +#define IDC_CPU9 1108 +#define IDC_CPU10 1109 +#define IDC_TIMEOUT 1109 +#define IDC_CPU11 1110 +#define IDC_CPU12 1111 +#define IDC_STATE 1111 +#define IDC_CPU13 1112 +#define IDC_CLIENTNAME 1112 +#define IDC_CPU14 1113 +#define IDC_CLIENTADDRESS 1113 +#define IDC_CPU15 1114 +#define IDC_CLIENTDISPLAY 1114 +#define IDC_CPU16 1115 +#define IDC_CHOICE 1115 +#define IDC_CPU17 1116 +#define IDC_OPTION 1116 +#define IDC_CPU18 1117 +#define IDC_CHOICEUSER 1117 +#define IDC_CPU19 1118 +#define IDC_HIDEUNNAMEDHANDLES 1118 +#define IDC_CPU20 1119 +#define IDC_CPU21 1120 +#define IDC_STARTMODULE 1120 +#define IDC_CPU22 1121 +#define IDC_OPENSTARTMODULE 1121 +#define IDC_CPU23 1122 +#define IDC_KERNELTIME 1122 +#define IDC_CPU24 1123 +#define IDC_USERTIME 1123 +#define IDC_CPU25 1124 +#define IDC_CONTEXTSWITCHES 1124 +#define IDC_CPU26 1125 +#define IDC_CYCLES 1125 +#define IDC_CPU27 1126 +#define IDC_PRIORITY 1126 +#define IDC_CPU28 1127 +#define IDC_BASEPRIORITY 1127 +#define IDC_CPU29 1128 +#define IDC_IOPRIORITY 1128 +#define IDC_CPU30 1129 +#define IDC_PAGEPRIORITY 1129 +#define IDC_CPU31 1130 +#define IDC_STATICBL1 1130 +#define IDC_STATICBL2 1131 +#define IDC_CPU32 1131 +#define IDC_STATICBL3 1132 +#define IDC_CPU33 1132 +#define IDC_STATICBL4 1133 +#define IDC_CPU34 1133 +#define IDC_STATICBL5 1134 +#define IDC_CPU35 1134 +#define IDC_STATICBL6 1135 +#define IDC_CPU36 1135 +#define IDC_STATICBL7 1136 +#define IDC_CPU37 1136 +#define IDC_STATICBL8 1137 +#define IDC_CPU38 1137 +#define IDC_STATICBL9 1138 +#define IDC_CPU39 1138 +#define IDC_STATICBL10 1139 +#define IDC_CPU40 1139 +#define IDC_STATICBL11 1140 +#define IDC_CPU41 1140 +#define IDC_CHOICESIMPLE 1141 +#define IDC_CPU42 1141 +#define IDC_MESSAGE 1142 +#define IDC_CPU43 1142 +#define IDC_SEARCHENGINE 1143 +#define IDC_CPU44 1143 +#define IDC_MAXSIZEUNIT 1144 +#define IDC_CPU45 1144 +#define IDC_ALLOWONLYONEINSTANCE 1145 +#define IDC_CPU46 1145 +#define IDC_HIDEONCLOSE 1146 +#define IDC_CPU47 1146 +#define IDC_ENABLEWARNINGS 1147 +#define IDC_HIDEONMINIMIZE 1147 +#define IDC_CPU48 1147 +#define IDC_STARTHIDDEN 1148 +#define IDC_CPU49 1148 +#define IDC_ENABLEKERNELMODEDRIVER 1149 +#define IDC_CPU50 1149 +#define IDC_CPU51 1150 +#define IDC_PEVIEWER 1151 +#define IDC_CPU52 1151 +#define IDC_CPU53 1152 +#define IDC_CPU54 1153 +#define IDC_CPU55 1154 +#define IDC_HIGHLIGHTINGDURATION 1155 +#define IDC_CPU56 1155 +#define IDC_CPU57 1156 +#define IDC_ENABLEALL 1157 +#define IDC_CPU58 1157 +#define IDC_ZACTIVEPROCESSES_V 1158 +#define IDC_CPU59 1158 +#define IDC_ZTOTALPROCESSES_V 1159 +#define IDC_CPU60 1159 +#define IDC_ZTERMINATEDPROCESSES_V 1160 +#define IDC_CPU61 1160 +#define IDC_ZUSERTIME_V 1161 +#define IDC_CPU62 1161 +#define IDC_ZKERNELTIME_V 1162 +#define IDC_CPU63 1162 +#define IDC_ZUSERTIMEPERIOD_V 1163 +#define IDC_ZKERNELTIMEPERIOD_V 1164 +#define IDC_ZPAGEFAULTS_V 1165 +#define IDC_ZPEAKPROCESSUSAGE_V 1166 +#define IDC_ZPEAKJOBUSAGE_V 1167 +#define IDC_ZIOREADS_V 1168 +#define IDC_ZIOREADBYTES_V 1169 +#define IDC_ZIOWRITES_V 1170 +#define IDC_ZIOWRITEBYTES_V 1171 +#define IDC_ZIOOTHER_V 1172 +#define IDC_ZIOOTHERBYTES_V 1173 +#define IDC_MOVEDOWN 1176 +#define IDC_DISPLAYNAME 1179 +#define IDC_ZPRIORITY_V 1181 +#define IDC_ZCYCLES_V 1182 +#define IDC_ZTOTALTIME_V 1185 +#define IDC_ZPRIVATEBYTES_V 1186 +#define IDC_ZWORKINGSET_V 1187 +#define IDC_ZPEAKWORKINGSET_V 1188 +#define IDC_ZVIRTUALSIZE_V 1189 +#define IDC_ZPEAKVIRTUALSIZE_V 1190 +#define IDC_ZPEAKPRIVATEBYTES_V 1191 +#define IDC_ZPAGEPRIORITY_V 1192 +#define IDC_ZIOPRIORITY_V 1194 +#define IDC_ZHANDLES_V 1195 +#define IDC_ZGDIHANDLES_V 1196 +#define IDC_ZOTHERDELTA_V 1196 +#define IDC_ZUSERHANDLES_V 1197 +#define IDC_ZOTHER_V 1197 +#define IDC_GROUPCPU 1198 +#define IDC_GROUPPRIVATEBYTES 1199 +#define IDC_GROUPIO 1200 +#define IDC_ONEGRAPHPERCPU 1202 +#define IDC_ALWAYSONTOP 1203 +#define IDC_COPY 1206 +#define IDC_INACTIVE 1207 +#define IDC_ACTIVE 1208 +#define IDC_SHOW 1209 +#define IDC_HIDE 1210 +#define IDC_REPLACETASKMANAGER 1211 +#define IDC_AUTOSCROLL 1215 +#define IDC_UNDECORATESYMBOLS 1216 +#define IDC_DBGHELPPATH 1217 +#define IDC_DBGHELPSEARCHPATH 1218 +#define IDC_COLLAPSESERVICES 1219 +#define IDC_ICONSINGLECLICK 1220 +#define IDC_DESKTOP 1221 +#define IDC_REREAD 1224 +#define IDC_WRITE 1225 +#define IDC_VALUE 1230 +#define IDC_DELAYEDSTART 1234 +#define IDC_MINIMUMLENGTH 1236 +#define IDC_DETECTUNICODE 1237 +#define IDC_PRIVATE 1238 +#define IDC_IMAGE 1239 +#define IDC_MAPPED 1240 +#define IDC_STRINGS 1242 +#define IDC_SHOWTEXT 1245 +#define IDC_ICONPROCESSES 1248 +#define IDC_CLEANUP 1251 +#define IDC_ENABLESTAGE2 1253 +#define IDC_TOGGLEELEVATION 1254 +#define IDC_ENABLEPLUGINS 1258 +#define IDC_PARENT 1263 +#define IDC_PROCESSNAME 1264 +#define IDC_SERVICES_LAYOUT 1266 +#define IDC_LINK_SF 1267 +#define IDC_CREDITS 1270 +#define IDC_ENABLENETWORKRESOLVE 1271 +#define IDC_LOGONTIME 1272 +#define IDC_ZPEAKHANDLES_V 1273 +#define IDC_PROPAGATECPUUSAGE 1274 +#define IDC_SHIFT 1274 +#define IDC_USEOLDCOLORS 1274 +#define IDC_ICONTOGGLESVISIBILITY 1274 +#define IDC_OPENURL 1279 +#define IDC_COMPANYNAME_LINK 1279 +#define IDC_SAMPLECOUNT 1280 +#define IDC_SAMPLECOUNTLABEL 1281 +#define IDC_DISABLE 1282 +#define IDC_VIRTUALKEY 1283 +#define IDC_CTRL 1284 +#define IDC_ALT 1285 +#define IDC_CONNECTTIME 1286 +#define IDC_DISCONNECTTIME 1287 +#define IDC_LASTINPUTTIME 1288 +#define IDC_ZPRIVATEWS_V 1289 +#define IDC_ZSHAREABLEWS_V 1290 +#define IDC_ZSHAREDWS_V 1291 +#define IDC_ENABLEINSTANTTOOLTIPS 1292 +#define IDC_ENABLECYCLECPUUSAGE 1293 +#define IDC_IDEALPROCESSOR 1294 +#define IDC_STATICBL12 1295 +#define IDC_BASICINFORMATION 1296 +#define IDC_INSTRUCTION 1298 +#define IDC_CPUNAME 1299 +#define IDC_LAYOUT 1300 +#define IDC_UTILIZATION 1301 +#define IDC_SPEED 1302 +#define IDC_ZPROCESSES_V 1303 +#define IDC_ZTHREADS_V 1304 +#define IDC_ZCONTEXTSWITCHESDELTA_V 1307 +#define IDC_ZINTERRUPTSDELTA_V 1308 +#define IDC_ZDPCSDELTA_V 1309 +#define IDC_ZSYSTEMCALLSDELTA_V 1310 +#define IDC_GRAPH_LAYOUT 1311 +#define IDC_TOTALPHYSICAL 1312 +#define IDC_ZCOMMITCURRENT_V 1314 +#define IDC_ZCOMMITPEAK_V 1315 +#define IDC_ZCOMMITLIMIT_V 1316 +#define IDC_ZPHYSICALCURRENT_V 1317 +#define IDC_ZPHYSICALTOTAL_V 1318 +#define IDC_ZPHYSICALCACHEWS_V 1319 +#define IDC_ZPHYSICALKERNELWS_V 1320 +#define IDC_ZPHYSICALDRIVERWS_V 1321 +#define IDC_ZPAGEDWORKINGSET_V 1322 +#define IDC_ZPAGEDVIRTUALSIZE_V 1323 +#define IDC_ZPAGEDLIMIT_V 1324 +#define IDC_ZPAGEDALLOCSDELTA_V 1325 +#define IDC_ZPAGEDFREESDELTA_V 1326 +#define IDC_ZNONPAGEDUSAGE_V 1327 +#define IDC_ZNONPAGEDLIMIT_V 1328 +#define IDC_ZNONPAGEDALLOCSDELTA_V 1329 +#define IDC_ZNONPAGEDFREESDELTA_V 1330 +#define IDC_ZPHYSICALRESERVED_V 1331 +#define IDC_ZLISTZEROED_V 1332 +#define IDC_ZLISTFREE_V 1333 +#define IDC_ZLISTMODIFIED_V 1334 +#define IDC_ZLISTMODIFIEDNOWRITE_V 1335 +#define IDC_ZLISTSTANDBY_V 1337 +#define IDC_COMMIT_L 1339 +#define IDC_PHYSICAL_L 1340 +#define IDC_ZUPTIME_V 1341 +#define IDC_ZOTHERBYTESDELTA_V 1342 +#define IDC_ZREADSDELTA_V 1343 +#define IDC_ZREADBYTESDELTA_V 1344 +#define IDC_ZWRITESDELTA_V 1345 +#define IDC_ZLISTSTANDBY0_V 1346 +#define IDC_ZWRITEBYTESDELTA_V 1346 +#define IDC_ZLISTSTANDBY1_V 1347 +#define IDC_ZREADS_V 1347 +#define IDC_ZLISTSTANDBY2_V 1348 +#define IDC_ZREADBYTES_V 1348 +#define IDC_ZLISTSTANDBY3_V 1349 +#define IDC_ZWRITES_V 1349 +#define IDC_ZLISTBAD_V 1350 +#define IDC_ZWRITEBYTES_V 1350 +#define IDC_ZLISTREPURPOSED_V 1351 +#define IDC_ZOTHERBYTES_V 1351 +#define IDC_ZLISTREPURPOSED0_V 1352 +#define IDC_ZLISTREPURPOSED1_V 1353 +#define IDC_ZLISTREPURPOSED2_V 1354 +#define IDC_ZLISTREPURPOSED3_V 1355 +#define IDC_ZLISTREPURPOSED4_V 1356 +#define IDC_ZLISTREPURPOSED5_V 1357 +#define IDC_ZLISTREPURPOSED6_V 1358 +#define IDC_ZLISTREPURPOSED7_V 1359 +#define IDC_EMPTY 1360 +#define IDC_SAMPLECOUNTAUTOMATIC 1361 +#define IDC_SHOWCOMMITINSUMMARY 1361 +#define IDC_ZLISTSTANDBY4_V 1364 +#define IDC_ZLISTSTANDBY5_V 1365 +#define IDC_SELECTALL 1365 +#define IDC_ZLISTSTANDBY6_V 1366 +#define IDC_DESELECTALL 1366 +#define IDC_ZLISTSTANDBY7_V 1367 +#define IDC_INSPECT 1367 +#define IDC_ZPAGINGPAGEFAULTSDELTA_V 1368 +#define IDC_HIDEFREEREGIONS 1368 +#define IDC_ZPAGINGPAGEREADSDELTA_V 1369 +#define IDC_BYTESPERROW 1369 +#define IDC_ZPAGINGPAGEFILEWRITESDELTA_V 1370 +#define IDC_PIN 1370 +#define IDC_ZPAGINGMAPPEDWRITESDELTA_V 1371 +#define IDC_ZLISTMODIFIEDPAGEFILE_V 1373 +#define IDC_SECTION 1375 +#define IDC_REGEX 1377 +#define IDC_DESCRIPTIONLABEL 1378 +#define IDC_STARTATLOGON 1380 +#define IDC_VIEWCOMMANDLINE 1381 +#define IDC_DELETE 1382 +#define IDC_EDIT 1383 +#define IDC_NEW 1384 +#define ID_MAINWND_PROCESSTL 2001 +#define ID_MAINWND_SERVICETL 2002 +#define ID_MAINWND_NETWORKTL 2003 +#define ID_MAINWND_PROCESSLV 2004 +#define ID_HACKER_EXIT 40001 +#define ID_PROCESS_PROPERTIES 40006 +#define ID_PROCESS_TERMINATE 40007 +#define ID_PROCESS_SUSPEND 40008 +#define ID_PROCESS_RESUME 40009 +#define ID_HACKER_SAVE 40010 +#define ID_THREAD_TERMINATE 40011 +#define ID_THREAD_SUSPEND 40012 +#define ID_THREAD_RESUME 40013 +#define ID_THREAD_PERMISSIONS 40015 +#define ID_THREAD_TOKEN 40016 +#define ID_ANALYZE_WAIT 40018 +#define ID_PRIORITY_TIMECRITICAL 40020 +#define ID_PRIORITY_HIGHEST 40021 +#define ID_PRIORITY_ABOVENORMAL 40022 +#define ID_PRIORITY_NORMAL 40023 +#define ID_PRIORITY_BELOWNORMAL 40024 +#define ID_PRIORITY_LOWEST 40025 +#define ID_PRIORITY_IDLE 40026 +#define ID_IOPRIORITY_VERYLOW 40028 +#define ID_IOPRIORITY_LOW 40029 +#define ID_IOPRIORITY_NORMAL 40030 +#define ID_IOPRIORITY_HIGH 40031 +#define ID_PROCESS_RESTART 40032 +#define ID_PROCESS_VIRTUALIZATION 40034 +#define ID_PROCESS_AFFINITY 40035 +#define ID_PROCESS_CREATEDUMPFILE 40036 +#define ID_MISCELLANEOUS_DETACHFROMDEBUGGER 40039 +#define ID_MISCELLANEOUS_HEAPS 40040 +#define ID_MISCELLANEOUS_INJECTDLL 40041 +#define ID_PRIORITY_REALTIME 40048 +#define ID_PRIORITY_HIGH 40049 +#define ID_WINDOW_BRINGTOFRONT 40055 +#define ID_WINDOW_RESTORE 40056 +#define ID_WINDOW_MINIMIZE 40057 +#define ID_WINDOW_MAXIMIZE 40058 +#define ID_WINDOW_CLOSE 40059 +#define ID_PROCESS_SEARCHONLINE 40060 +#define ID_HANDLE_CLOSE 40061 +#define ID_HANDLE_PROTECTED 40062 +#define ID_HANDLE_INHERIT 40063 +#define ID_HANDLE_PROPERTIES 40064 +#define ID_MODULE_UNLOAD 40066 +#define ID_MODULE_PROPERTIES 40068 +#define ID_PROCESS_TERMINATETREE 40069 +#define ID_THREAD_INSPECT 40075 +#define ID_HACKER_RUN 40076 +#define ID_HACKER_RUNASADMINISTRATOR 40077 +#define ID_HACKER_RUNAS 40078 +#define ID_HACKER_SHOWDETAILSFORALLPROCESSES 40079 +#define ID_HACKER_FINDHANDLESORDLLS 40082 +#define ID_HACKER_OPTIONS 40083 +#define ID_VIEW_SYSTEMINFORMATION 40091 +#define ID_TRAYICONS_CPUHISTORY 40093 +#define ID_TRAYICONS_CPUUSAGE 40094 +#define ID_TRAYICONS_COMMITHISTORY 40096 +#define ID_TRAYICONS_PHYSICALMEMORYHISTORY 40097 +#define ID_VIEW_REFRESH 40098 +#define ID_TOOLS_CREATESERVICE 40101 +#define ID_TOOLS_HIDDENPROCESSES 40102 +#define ID_TOOLS_INSPECTEXECUTABLEFILE 40103 +#define ID_HELP_LOG 40104 +#define ID_HELP_DONATE 40105 +#define ID_HELP_ABOUT 40106 +#define ID_SERVICE_GOTOPROCESS 40107 +#define ID_SERVICE_START 40108 +#define ID_SERVICE_CONTINUE 40109 +#define ID_SERVICE_PAUSE 40110 +#define ID_SERVICE_STOP 40111 +#define ID_SERVICE_DELETE 40112 +#define ID_SERVICE_PROPERTIES 40113 +#define ID_SERVICE_OPENKEY 40114 +#define ID_PRIVILEGE_ENABLE 40116 +#define ID_PRIVILEGE_DISABLE 40117 +#define ID_PRIVILEGE_REMOVE 40118 +#define ID_OBJECT_CLOSE 40119 +#define ID_OBJECT_PROPERTIES 40120 +#define ID_HELP_DEBUGCONSOLE 40122 +#define ID_MODULE_SEARCHONLINE 40125 +#define ID_TOOLS_PAGEFILES 40126 +#define ID_USER_DISCONNECT 40127 +#define ID_USER_LOGOFF 40128 +#define ID_USER_SENDMESSAGE 40129 +#define ID_USER_PROPERTIES 40130 +#define ID_USERS_DUMMY 40131 +#define ID_MODULE_INSPECT 40132 +#define ID_PROCESS_DEBUG 40133 +#define ID_USER_CONNECT 40138 +#define ID_NETWORK_GOTOPROCESS 40139 +#define ID_NETWORK_CLOSE 40140 +#define ID_OPACITY_10 40142 +#define ID_OPACITY_20 40143 +#define ID_OPACITY_30 40144 +#define ID_OPACITY_40 40145 +#define ID_OPACITY_50 40146 +#define ID_OPACITY_60 40147 +#define ID_OPACITY_70 40148 +#define ID_OPACITY_80 40149 +#define ID_OPACITY_90 40150 +#define ID_OPACITY_OPAQUE 40151 +#define ID_VIEW_ALWAYSONTOP 40153 +#define ID_UPDATEINTERVAL_FAST 40155 +#define ID_UPDATEINTERVAL_NORMAL 40156 +#define ID_UPDATEINTERVAL_BELOWNORMAL 40157 +#define ID_UPDATEINTERVAL_SLOW 40158 +#define ID_UPDATEINTERVAL_VERYSLOW 40159 +#define ID_COMPUTER_LOCK 40160 +#define ID_COMPUTER_LOGOFF 40161 +#define ID_COMPUTER_SLEEP 40162 +#define ID_COMPUTER_HIBERNATE 40163 +#define ID_COMPUTER_RESTART 40164 +#define ID_COMPUTER_SHUTDOWN 40165 +#define ID_NETWORK_VIEWSTACK 40167 +#define ID_TRAYICONS_IOHISTORY 40168 +#define ID_ICON_EXIT 40169 +#define ID_ICON_SHOWHIDEPROCESSHACKER 40171 +#define ID_ICON_SYSTEMINFORMATION 40172 +#define ID_PROCESSES_DUMMY 40177 +#define ID_NOTIFICATIONS_ENABLEALL 40178 +#define ID_NOTIFICATIONS_DISABLEALL 40179 +#define ID_NOTIFICATIONS_NEWPROCESSES 40180 +#define ID_NOTIFICATIONS_TERMINATEDPROCESSES 40181 +#define ID_NOTIFICATIONS_NEWSERVICES 40182 +#define ID_NOTIFICATIONS_STARTEDSERVICES 40183 +#define ID_NOTIFICATIONS_STOPPEDSERVICES 40184 +#define ID_NOTIFICATIONS_DELETEDSERVICES 40185 +#define ID_MISCELLANEOUS_GDIHANDLES 40188 +#define ID_ESC_EXIT 40190 +#define ID_PROCESS_COPY 40194 +#define ID_THREAD_COPY 40195 +#define ID_PRIVILEGE_COPY 40196 +#define ID_NETWORK_COPY 40197 +#define ID_SERVICE_COPY 40198 +#define ID_MODULE_COPY 40199 +#define ID_HANDLE_COPY 40200 +#define ID_OBJECT_COPY 40201 +#define ID_MEMORY_SAVE 40208 +#define ID_MEMORY_CHANGEPROTECTION 40209 +#define ID_MEMORY_FREE 40210 +#define ID_MEMORY_DECOMMIT 40211 +#define ID_MEMORY_COPY 40213 +#define ID_MEMORY_READWRITEMEMORY 40214 +#define ID_MEMORY_READWRITEADDRESS 40215 +#define ID_FILTER_CONTAINS 40216 +#define ID_FILTER_CONTAINS_CASEINSENSITIVE 40218 +#define ID_FILTER_REGEX 40219 +#define ID_FILTER_REGEX_CASEINSENSITIVE 40221 +#define ID_TAB_NEXT 40223 +#define ID_TAB_PREV 40224 +#define ID_MISCELLANEOUS_RUNAS 40229 +#define ID_MISCELLANEOUS_RUNASTHISUSER 40230 +#define ID_HACKER_PLUGINS 40231 +#define ID_VIEW_HIDEPROCESSESFROMOTHERUSERS 40232 +#define ID_THREAD_AFFINITY 40233 +#define ID_VIEW_HIDESIGNEDPROCESSES 40234 +#define ID_VIEW_UPDATEAUTOMATICALLY 40235 +#define ID_HACKER_RUNASLIMITEDUSER 40236 +#define ID_USER_REMOTECONTROL 40237 +#define ID_PAGEPRIORITY_NORMAL 40239 +#define ID_PAGEPRIORITY_BELOWNORMAL 40240 +#define ID_PAGEPRIORITY_MEDIUM 40241 +#define ID_PAGEPRIORITY_LOW 40242 +#define ID_PAGEPRIORITY_VERYLOW 40243 +#define ID_VIEW_SHOWCPUBELOW001 40246 +#define ID_MODULE_OPENFILELOCATION 40247 +#define ID_PROCESS_OPENFILELOCATION 40248 +#define ID_MISCELLANEOUS_REDUCEWORKINGSET 40249 +#define ID_EMPTY_EMPTYWORKINGSETS 40250 +#define ID_EMPTY_EMPTYMODIFIEDPAGELIST 40251 +#define ID_EMPTY_EMPTYSTANDBYLIST 40252 +#define ID_EMPTY_EMPTYPRIORITY0STANDBYLIST 40253 +#define IDC_BACK 40255 +#define ID_DIGIT1 40263 +#define ID_DIGIT2 40264 +#define ID_DIGIT3 40265 +#define ID_DIGIT4 40266 +#define ID_DIGIT5 40267 +#define ID_DIGIT6 40268 +#define ID_DIGIT7 40269 +#define ID_DIGIT8 40270 +#define ID_DIGIT9 40271 +#define ID_VIEW_HIDEDRIVERSERVICES 40273 +#define ID_VIEW_SECTIONPLACEHOLDER 40274 +#define ID_VIEW_SCROLLTONEWPROCESSES 40275 +#define ID_TOOLS_STARTTASKMANAGER 40277 +#define ID_COMPUTER_SHUTDOWNHYBRID 40278 +#define ID_COMPUTER_RESTARTBOOTOPTIONS 40280 +#define ID_HANDLE_OBJECTPROPERTIES1 40282 +#define ID_HANDLE_OBJECTPROPERTIES2 40283 +#define ID_OBJECT_GOTOOWNINGPROCESS 40284 +#define ID_NETWORK_GOTOSERVICE 40285 +#define ID_SERVICE_OPENFILELOCATION 40286 +#define ID_PROCESS_GOTOPROCESS 40287 +#define ID_MINIINFO_REFRESH 40288 +#define ID_MINIINFO_REFRESHAUTOMATICALLY 40289 +#define ID_ENVIRONMENT_EDIT 40290 +#define ID_ENVIRONMENT_COPY 40291 +#define ID_ENVIRONMENT_DELETE 40292 +#define IDDYNAMIC 50000 +#define IDPLUGINS 55000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 223 +#define _APS_NEXT_COMMAND_VALUE 40293 +#define _APS_NEXT_CONTROL_VALUE 1385 +#define _APS_NEXT_SYMED_VALUE 169 +#endif +#endif diff --git a/ProcessHacker/resources/ProcessHacker.png b/ProcessHacker/resources/ProcessHacker.png new file mode 100644 index 0000000..73f282b Binary files /dev/null and b/ProcessHacker/resources/ProcessHacker.png differ diff --git a/ProcessHacker/resources/application.ico b/ProcessHacker/resources/application.ico new file mode 100644 index 0000000..3627a17 Binary files /dev/null and b/ProcessHacker/resources/application.ico differ diff --git a/ProcessHacker/resources/application_go.ico b/ProcessHacker/resources/application_go.ico new file mode 100644 index 0000000..0a72a48 Binary files /dev/null and b/ProcessHacker/resources/application_go.ico differ diff --git a/ProcessHacker/resources/cog.ico b/ProcessHacker/resources/cog.ico new file mode 100644 index 0000000..99c0b7a Binary files /dev/null and b/ProcessHacker/resources/cog.ico differ diff --git a/ProcessHacker/resources/cog_go.ico b/ProcessHacker/resources/cog_go.ico new file mode 100644 index 0000000..d679437 Binary files /dev/null and b/ProcessHacker/resources/cog_go.ico differ diff --git a/ProcessHacker/resources/cross.bmp b/ProcessHacker/resources/cross.bmp new file mode 100644 index 0000000..ca0867d Binary files /dev/null and b/ProcessHacker/resources/cross.bmp differ diff --git a/ProcessHacker/resources/folder.ico b/ProcessHacker/resources/folder.ico new file mode 100644 index 0000000..363b7e0 Binary files /dev/null and b/ProcessHacker/resources/folder.ico differ diff --git a/ProcessHacker/resources/magnifier.ico b/ProcessHacker/resources/magnifier.ico new file mode 100644 index 0000000..5ba022d Binary files /dev/null and b/ProcessHacker/resources/magnifier.ico differ diff --git a/ProcessHacker/resources/pencil.ico b/ProcessHacker/resources/pencil.ico new file mode 100644 index 0000000..8325766 Binary files /dev/null and b/ProcessHacker/resources/pencil.ico differ diff --git a/ProcessHacker/resources/pin.ico b/ProcessHacker/resources/pin.ico new file mode 100644 index 0000000..a71ecc9 Binary files /dev/null and b/ProcessHacker/resources/pin.ico differ diff --git a/ProcessHacker/resources/tick.bmp b/ProcessHacker/resources/tick.bmp new file mode 100644 index 0000000..4aa2798 Binary files /dev/null and b/ProcessHacker/resources/tick.bmp differ diff --git a/ProcessHacker/runas.c b/ProcessHacker/runas.c new file mode 100644 index 0000000..68cdb79 --- /dev/null +++ b/ProcessHacker/runas.c @@ -0,0 +1,1164 @@ +/* + * Process Hacker - + * run as dialog + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The run-as mechanism has three stages: + * 1. The user enters the information into the dialog box. Here it is decided whether the run-as + * service is needed. If it is not, PhCreateProcessAsUser is called directly. Otherwise, + * PhExecuteRunAsCommand2 is called for stage 2. + * 2. PhExecuteRunAsCommand2 creates a random service name and tries to create the service and + * execute it (using PhExecuteRunAsCommand). If the process has insufficient permissions, an + * elevated instance of phsvc is started and PhSvcCallExecuteRunAsCommand is called. + * 3. The service is started, and sets up an instance of phsvc with the same random service name as + * its port name. Either the original or elevated Process Hacker instance then calls + * PhSvcCallInvokeRunAsService to complete the operation. + */ + +/* + * + * ProcessHacker.exe (user, limited privileges) + * * | ^ + * | | | phsvc API (LPC) + * | | | + * | v | + * ProcessHacker.exe (user, full privileges) + * | ^ | ^ + * | | SCM API (RPC) | | + * | | | | + * v | | | phsvc API (LPC) + * services.exe | | + * * | | + * | | | + * | | | + * | v | + * ProcessHacker.exe (NT AUTHORITY\SYSTEM) + * * + * | + * | + * | + * program.exe + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _RUNAS_DIALOG_CONTEXT +{ + HANDLE ProcessId; + PPH_LIST DesktopList; + PPH_STRING CurrentWinStaName; +} RUNAS_DIALOG_CONTEXT, *PRUNAS_DIALOG_CONTEXT; + +INT_PTR CALLBACK PhpRunAsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhSetDesktopWinStaAccess( + VOID + ); + +VOID PhpSplitUserName( + _In_ PWSTR UserName, + _Out_ PPH_STRING *DomainPart, + _Out_ PPH_STRING *UserPart + ); + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +static PH_KEY_VALUE_PAIR PhpLogonTypePairs[] = +{ + SIP(L"Batch", LOGON32_LOGON_BATCH), + SIP(L"Interactive", LOGON32_LOGON_INTERACTIVE), + SIP(L"Network", LOGON32_LOGON_NETWORK), + SIP(L"New credentials", LOGON32_LOGON_NEW_CREDENTIALS), + SIP(L"Service", LOGON32_LOGON_SERVICE) +}; + +static WCHAR RunAsOldServiceName[32] = L""; +static PH_QUEUED_LOCK RunAsOldServiceLock = PH_QUEUED_LOCK_INIT; + +static PPH_STRING RunAsServiceName; +static SERVICE_STATUS_HANDLE RunAsServiceStatusHandle; +static PHSVC_STOP RunAsServiceStop; + +VOID PhShowRunAsDialog( + _In_ HWND ParentWindowHandle, + _In_opt_ HANDLE ProcessId + ) +{ + RUNAS_DIALOG_CONTEXT context; + + context.ProcessId = ProcessId; + context.DesktopList = NULL; + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_RUNAS), + ParentWindowHandle, + PhpRunAsDlgProc, + (LPARAM)&context + ); +} + +static VOID PhpAddAccountsToComboBox( + _In_ HWND ComboBoxHandle + ) +{ + LSA_HANDLE policyHandle; + LSA_ENUMERATION_HANDLE enumerationContext = 0; + PLSA_ENUMERATION_INFORMATION buffer; + ULONG count; + ULONG i; + PPH_STRING name; + SID_NAME_USE nameUse; + + if (NT_SUCCESS(PhOpenLsaPolicy(&policyHandle, POLICY_VIEW_LOCAL_INFORMATION, NULL))) + { + while (NT_SUCCESS(LsaEnumerateAccounts( + policyHandle, + &enumerationContext, + &buffer, + 0x100, + &count + ))) + { + for (i = 0; i < count; i++) + { + name = PhGetSidFullName(buffer[i].Sid, TRUE, &nameUse); + + if (name) + { + if (nameUse == SidTypeUser) + ComboBox_AddString(ComboBoxHandle, name->Buffer); + + PhDereferenceObject(name); + } + } + + LsaFreeMemory(buffer); + } + + LsaClose(policyHandle); + } +} + +static BOOLEAN IsServiceAccount( + _In_ PPH_STRING UserName + ) +{ + if ( + PhEqualString2(UserName, L"NT AUTHORITY\\LOCAL SERVICE", TRUE) || + PhEqualString2(UserName, L"NT AUTHORITY\\NETWORK SERVICE", TRUE) || + PhEqualString2(UserName, L"NT AUTHORITY\\SYSTEM", TRUE) + ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +static PPH_STRING GetCurrentWinStaName( + VOID + ) +{ + PPH_STRING string; + + string = PhCreateStringEx(NULL, 0x200); + + if (GetUserObjectInformation( + GetProcessWindowStation(), + UOI_NAME, + string->Buffer, + (ULONG)string->Length + 2, + NULL + )) + { + PhTrimToNullTerminatorString(string); + return string; + } + else + { + PhDereferenceObject(string); + return PhCreateString(L"WinSta0"); // assume the current window station is WinSta0 + } +} + +static BOOL CALLBACK EnumDesktopsCallback( + _In_ PWSTR DesktopName, + _In_ LPARAM Context + ) +{ + PRUNAS_DIALOG_CONTEXT context = (PRUNAS_DIALOG_CONTEXT)Context; + + PhAddItemList(context->DesktopList, PhConcatStrings( + 3, + context->CurrentWinStaName->Buffer, + L"\\", + DesktopName + )); + + return TRUE; +} + +INT_PTR CALLBACK PhpRunAsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PRUNAS_DIALOG_CONTEXT context; + + if (uMsg != WM_INITDIALOG) + { + context = (PRUNAS_DIALOG_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + } + else + { + context = (PRUNAS_DIALOG_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND typeComboBoxHandle = GetDlgItem(hwndDlg, IDC_TYPE); + HWND userNameComboBoxHandle = GetDlgItem(hwndDlg, IDC_USERNAME); + ULONG sessionId; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + if (SHAutoComplete_I) + { + SHAutoComplete_I( + GetDlgItem(hwndDlg, IDC_PROGRAM), + SHACF_AUTOAPPEND_FORCE_ON | SHACF_AUTOSUGGEST_FORCE_ON | SHACF_FILESYS_ONLY + ); + } + + ComboBox_AddString(typeComboBoxHandle, L"Batch"); + ComboBox_AddString(typeComboBoxHandle, L"Interactive"); + ComboBox_AddString(typeComboBoxHandle, L"Network"); + ComboBox_AddString(typeComboBoxHandle, L"New credentials"); + ComboBox_AddString(typeComboBoxHandle, L"Service"); + PhSelectComboBoxString(typeComboBoxHandle, L"Interactive", FALSE); + + ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\SYSTEM"); + ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\LOCAL SERVICE"); + ComboBox_AddString(userNameComboBoxHandle, L"NT AUTHORITY\\NETWORK SERVICE"); + + PhpAddAccountsToComboBox(userNameComboBoxHandle); + + if (NT_SUCCESS(PhGetProcessSessionId(NtCurrentProcess(), &sessionId))) + SetDlgItemInt(hwndDlg, IDC_SESSIONID, sessionId, FALSE); + + SetDlgItemText(hwndDlg, IDC_DESKTOP, L"WinSta0\\Default"); + SetDlgItemText(hwndDlg, IDC_PROGRAM, PhaGetStringSetting(L"RunAsProgram")->Buffer); + + if (!context->ProcessId) + { + SetDlgItemText(hwndDlg, IDC_USERNAME, + PH_AUTO_T(PH_STRING, PhGetStringSetting(L"RunAsUserName"))->Buffer); + + // Fire the user name changed event so we can fix the logon type. + SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDC_USERNAME, CBN_EDITCHANGE), 0); + } + else + { + HANDLE processHandle; + HANDLE tokenHandle; + PTOKEN_USER user; + PPH_STRING userName; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + context->ProcessId + ))) + { + if (NT_SUCCESS(PhOpenProcessToken( + processHandle, + TOKEN_QUERY, + &tokenHandle + ))) + { + if (NT_SUCCESS(PhGetTokenUser(tokenHandle, &user))) + { + if (userName = PhGetSidFullName(user->User.Sid, TRUE, NULL)) + { + SetDlgItemText(hwndDlg, IDC_USERNAME, userName->Buffer); + PhDereferenceObject(userName); + } + + PhFree(user); + } + + NtClose(tokenHandle); + } + + NtClose(processHandle); + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_USERNAME), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_TYPE), FALSE); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_PROGRAM), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_PROGRAM), 0, -1); + + //if (!PhGetOwnTokenAttributes().Elevated) + // SendMessage(GetDlgItem(hwndDlg, IDOK), BCM_SETSHIELD, 0, TRUE); + + if (!WINDOWS_HAS_UAC) + ShowWindow(GetDlgItem(hwndDlg, IDC_TOGGLEELEVATION), SW_HIDE); + } + break; + case WM_DESTROY: + { + if (context->DesktopList) + PhDereferenceObject(context->DesktopList); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + NTSTATUS status; + PPH_STRING program; + PPH_STRING userName; + PPH_STRING password; + PPH_STRING logonTypeString; + ULONG logonType; + ULONG sessionId; + PPH_STRING desktopName; + BOOLEAN useLinkedToken; + + program = PhaGetDlgItemText(hwndDlg, IDC_PROGRAM); + userName = PhaGetDlgItemText(hwndDlg, IDC_USERNAME); + logonTypeString = PhaGetDlgItemText(hwndDlg, IDC_TYPE); + + // Fix up the user name if it doesn't have a domain. + if (PhFindCharInString(userName, 0, '\\') == -1) + { + PSID sid; + PPH_STRING newUserName; + + if (NT_SUCCESS(PhLookupName(&userName->sr, &sid, NULL, NULL))) + { + if (newUserName = PH_AUTO(PhGetSidFullName(sid, TRUE, NULL))) + userName = newUserName; + + PhFree(sid); + } + } + + if (!IsServiceAccount(userName)) + password = PhGetWindowText(GetDlgItem(hwndDlg, IDC_PASSWORD)); + else + password = NULL; + + sessionId = GetDlgItemInt(hwndDlg, IDC_SESSIONID, NULL, FALSE); + desktopName = PhaGetDlgItemText(hwndDlg, IDC_DESKTOP); + + if (WINDOWS_HAS_UAC) + useLinkedToken = Button_GetCheck(GetDlgItem(hwndDlg, IDC_TOGGLEELEVATION)) == BST_CHECKED; + else + useLinkedToken = FALSE; + + if (PhFindIntegerSiKeyValuePairs( + PhpLogonTypePairs, + sizeof(PhpLogonTypePairs), + logonTypeString->Buffer, + &logonType + )) + { + if ( + logonType == LOGON32_LOGON_INTERACTIVE && + !context->ProcessId && + sessionId == NtCurrentPeb()->SessionId && + !useLinkedToken + ) + { + // We are eligible to load the user profile. + // This must be done here, not in the service, because + // we need to be in the target session. + + PH_CREATE_PROCESS_AS_USER_INFO createInfo; + PPH_STRING domainPart; + PPH_STRING userPart; + + PhpSplitUserName(userName->Buffer, &domainPart, &userPart); + + memset(&createInfo, 0, sizeof(PH_CREATE_PROCESS_AS_USER_INFO)); + createInfo.CommandLine = program->Buffer; + createInfo.UserName = userPart->Buffer; + createInfo.DomainName = domainPart->Buffer; + createInfo.Password = PhGetStringOrEmpty(password); + + // Whenever we can, try not to set the desktop name; it breaks a lot of things. + // Note that on XP we must set it, otherwise the program doesn't display correctly. + if (WindowsVersion < WINDOWS_VISTA || (desktopName->Length != 0 && !PhEqualString2(desktopName, L"WinSta0\\Default", TRUE))) + createInfo.DesktopName = desktopName->Buffer; + + PhSetDesktopWinStaAccess(); + + status = PhCreateProcessAsUser( + &createInfo, + PH_CREATE_PROCESS_WITH_PROFILE, + NULL, + NULL, + NULL + ); + + if (domainPart) PhDereferenceObject(domainPart); + if (userPart) PhDereferenceObject(userPart); + } + else + { + status = PhExecuteRunAsCommand2( + hwndDlg, + program->Buffer, + userName->Buffer, + PhGetStringOrEmpty(password), + logonType, + context->ProcessId, + sessionId, + desktopName->Buffer, + useLinkedToken + ); + } + } + else + { + status = STATUS_INVALID_PARAMETER; + } + + if (password) + { + RtlSecureZeroMemory(password->Buffer, password->Length); + PhDereferenceObject(password); + } + + if (!NT_SUCCESS(status)) + { + if (status != STATUS_CANCELLED) + PhShowStatus(hwndDlg, L"Unable to start the program", status, 0); + } + else if (status != STATUS_TIMEOUT) + { + PhSetStringSetting2(L"RunAsProgram", &program->sr); + PhSetStringSetting2(L"RunAsUserName", &userName->sr); + EndDialog(hwndDlg, IDOK); + } + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Programs (*.exe;*.pif;*.com;*.bat)", L"*.exe;*.pif;*.com;*.bat" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + PhSetFileDialogFileName(fileDialog, PhaGetDlgItemText(hwndDlg, IDC_PROGRAM)->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + PPH_STRING fileName; + + fileName = PhGetFileDialogFileName(fileDialog); + SetDlgItemText(hwndDlg, IDC_PROGRAM, fileName->Buffer); + PhDereferenceObject(fileName); + } + + PhFreeFileDialog(fileDialog); + } + break; + case IDC_USERNAME: + { + PPH_STRING userName = NULL; + + if (!context->ProcessId && HIWORD(wParam) == CBN_SELCHANGE) + { + userName = PH_AUTO(PhGetComboBoxString(GetDlgItem(hwndDlg, IDC_USERNAME), -1)); + } + else if (!context->ProcessId && ( + HIWORD(wParam) == CBN_EDITCHANGE || + HIWORD(wParam) == CBN_CLOSEUP + )) + { + userName = PhaGetDlgItemText(hwndDlg, IDC_USERNAME); + } + + if (userName) + { + if (IsServiceAccount(userName)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), FALSE); + + // Hack for Windows XP + if ( + PhEqualString2(userName, L"NT AUTHORITY\\SYSTEM", TRUE) && + WindowsVersion <= WINDOWS_XP + ) + { + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"New credentials", FALSE); + } + else + { + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"Service", FALSE); + } + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_PASSWORD), TRUE); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"Interactive", FALSE); + } + } + } + break; + case IDC_SESSIONS: + { + PPH_EMENU sessionsMenu; + PSESSIONIDW sessions; + ULONG numberOfSessions; + ULONG i; + RECT buttonRect; + PPH_EMENU_ITEM selectedItem; + + sessionsMenu = PhCreateEMenu(); + + if (WinStationEnumerateW(NULL, &sessions, &numberOfSessions)) + { + for (i = 0; i < numberOfSessions; i++) + { + PPH_STRING menuString; + WINSTATIONINFORMATION winStationInfo; + ULONG returnLength; + + if (!WinStationQueryInformationW( + NULL, + sessions[i].SessionId, + WinStationInformation, + &winStationInfo, + sizeof(WINSTATIONINFORMATION), + &returnLength + )) + { + winStationInfo.Domain[0] = 0; + winStationInfo.UserName[0] = 0; + } + + if ( + winStationInfo.UserName[0] != 0 && + sessions[i].WinStationName[0] != 0 + ) + { + menuString = PhaFormatString( + L"%u: %s (%s\\%s)", + sessions[i].SessionId, + sessions[i].WinStationName, + winStationInfo.Domain, + winStationInfo.UserName + ); + } + else if (winStationInfo.UserName[0] != 0) + { + menuString = PhaFormatString( + L"%u: %s\\%s", + sessions[i].SessionId, + winStationInfo.Domain, + winStationInfo.UserName + ); + } + else if (sessions[i].WinStationName[0] != 0) + { + menuString = PhaFormatString( + L"%u: %s", + sessions[i].SessionId, + sessions[i].WinStationName + ); + } + else + { + menuString = PhaFormatString(L"%u", sessions[i].SessionId); + } + + PhInsertEMenuItem(sessionsMenu, + PhCreateEMenuItem(0, 0, menuString->Buffer, NULL, UlongToPtr(sessions[i].SessionId)), -1); + } + + WinStationFreeMemory(sessions); + + GetWindowRect(GetDlgItem(hwndDlg, IDC_SESSIONS), &buttonRect); + + selectedItem = PhShowEMenu( + sessionsMenu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + buttonRect.right, + buttonRect.top + ); + + if (selectedItem) + { + SetDlgItemInt( + hwndDlg, + IDC_SESSIONID, + PtrToUlong(selectedItem->Context), + FALSE + ); + } + + PhDestroyEMenu(sessionsMenu); + } + } + break; + case IDC_DESKTOPS: + { + PPH_EMENU desktopsMenu; + ULONG i; + RECT buttonRect; + PPH_EMENU_ITEM selectedItem; + + desktopsMenu = PhCreateEMenu(); + + if (!context->DesktopList) + context->DesktopList = PhCreateList(10); + + context->CurrentWinStaName = GetCurrentWinStaName(); + + EnumDesktops(GetProcessWindowStation(), EnumDesktopsCallback, (LPARAM)context); + + for (i = 0; i < context->DesktopList->Count; i++) + { + PhInsertEMenuItem( + desktopsMenu, + PhCreateEMenuItem(0, 0, ((PPH_STRING)context->DesktopList->Items[i])->Buffer, NULL, NULL), + -1 + ); + } + + GetWindowRect(GetDlgItem(hwndDlg, IDC_DESKTOPS), &buttonRect); + + selectedItem = PhShowEMenu( + desktopsMenu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + buttonRect.right, + buttonRect.top + ); + + if (selectedItem) + { + SetDlgItemText( + hwndDlg, + IDC_DESKTOP, + selectedItem->Text + ); + } + + for (i = 0; i < context->DesktopList->Count; i++) + PhDereferenceObject(context->DesktopList->Items[i]); + + PhClearList(context->DesktopList); + PhDereferenceObject(context->CurrentWinStaName); + PhDestroyEMenu(desktopsMenu); + } + break; + } + } + break; + } + + return FALSE; +} + +/** + * Sets the access control lists of the current window station + * and desktop to allow all access. + */ +VOID PhSetDesktopWinStaAccess( + VOID + ) +{ + static SID_IDENTIFIER_AUTHORITY appPackageAuthority = SECURITY_APP_PACKAGE_AUTHORITY; + + HWINSTA wsHandle; + HDESK desktopHandle; + ULONG allocationLength; + PSECURITY_DESCRIPTOR securityDescriptor; + PACL dacl; + CHAR allAppPackagesSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; + PSID allAppPackagesSid; + + // TODO: Set security on the correct window station and desktop. + + allAppPackagesSid = (PISID)allAppPackagesSidBuffer; + RtlInitializeSid(allAppPackagesSid, &appPackageAuthority, SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT); + *RtlSubAuthoritySid(allAppPackagesSid, 0) = SECURITY_APP_PACKAGE_BASE_RID; + *RtlSubAuthoritySid(allAppPackagesSid, 1) = SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE; + + // We create a DACL that allows everyone to access everything. + + allocationLength = SECURITY_DESCRIPTOR_MIN_LENGTH + + (ULONG)sizeof(ACL) + + (ULONG)sizeof(ACCESS_ALLOWED_ACE) + + RtlLengthSid(&PhSeEveryoneSid) + + (ULONG)sizeof(ACCESS_ALLOWED_ACE) + + RtlLengthSid(allAppPackagesSid); + securityDescriptor = PhAllocate(allocationLength); + dacl = (PACL)((PCHAR)securityDescriptor + SECURITY_DESCRIPTOR_MIN_LENGTH); + + RtlCreateSecurityDescriptor(securityDescriptor, SECURITY_DESCRIPTOR_REVISION); + + RtlCreateAcl(dacl, allocationLength - SECURITY_DESCRIPTOR_MIN_LENGTH, ACL_REVISION); + RtlAddAccessAllowedAce(dacl, ACL_REVISION, GENERIC_ALL, &PhSeEveryoneSid); + + if (WindowsVersion >= WINDOWS_8) + { + RtlAddAccessAllowedAce(dacl, ACL_REVISION, GENERIC_ALL, allAppPackagesSid); + } + + RtlSetDaclSecurityDescriptor(securityDescriptor, TRUE, dacl, FALSE); + + if (wsHandle = OpenWindowStation( + L"WinSta0", + FALSE, + WRITE_DAC + )) + { + PhSetObjectSecurity(wsHandle, DACL_SECURITY_INFORMATION, securityDescriptor); + CloseWindowStation(wsHandle); + } + + if (desktopHandle = OpenDesktop( + L"Default", + 0, + FALSE, + WRITE_DAC | DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS + )) + { + PhSetObjectSecurity(desktopHandle, DACL_SECURITY_INFORMATION, securityDescriptor); + CloseDesktop(desktopHandle); + } + + PhFree(securityDescriptor); +} + +/** + * Executes the run-as service. + * + * \param Parameters The run-as parameters. + * + * \remarks This function requires administrator-level access. + */ +NTSTATUS PhExecuteRunAsCommand( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + NTSTATUS status; + ULONG win32Result; + PPH_STRING commandLine; + SC_HANDLE scManagerHandle; + SC_HANDLE serviceHandle; + PPH_STRING portName; + UNICODE_STRING portNameUs; + ULONG attempts; + LARGE_INTEGER interval; + + if (!(scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE))) + return PhGetLastWin32ErrorAsNtStatus(); + + commandLine = PhFormatString(L"\"%s\" -ras \"%s\"", PhApplicationFileName->Buffer, Parameters->ServiceName); + + serviceHandle = CreateService( + scManagerHandle, + Parameters->ServiceName, + Parameters->ServiceName, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_IGNORE, + commandLine->Buffer, + NULL, + NULL, + NULL, + L"LocalSystem", + L"" + ); + win32Result = GetLastError(); + + PhDereferenceObject(commandLine); + + CloseServiceHandle(scManagerHandle); + + if (!serviceHandle) + { + return NTSTATUS_FROM_WIN32(win32Result); + } + + PhSetDesktopWinStaAccess(); + + StartService(serviceHandle, 0, NULL); + DeleteService(serviceHandle); + + portName = PhConcatStrings2(L"\\BaseNamedObjects\\", Parameters->ServiceName); + PhStringRefToUnicodeString(&portName->sr, &portNameUs); + attempts = 10; + + // Try to connect several times because the server may take + // a while to initialize. + do + { + status = PhSvcConnectToServer(&portNameUs, 0); + + if (NT_SUCCESS(status)) + break; + + interval.QuadPart = -50 * PH_TIMEOUT_MS; + NtDelayExecution(FALSE, &interval); + } while (--attempts != 0); + + PhDereferenceObject(portName); + + if (NT_SUCCESS(status)) + { + status = PhSvcCallInvokeRunAsService(Parameters); + PhSvcDisconnectFromServer(); + } + + if (serviceHandle) + CloseServiceHandle(serviceHandle); + + return status; +} + +/** + * Starts a program as another user. + * + * \param hWnd A handle to the parent window. + * \param Program The command line of the program to start. + * \param UserName The user to start the program as. The user + * name should be specified as: domain\\name. This parameter + * can be NULL if \a ProcessIdWithToken is specified. + * \param Password The password for the specified user. If there + * is no password, specify an empty string. This parameter + * can be NULL if \a ProcessIdWithToken is specified. + * \param LogonType The logon type for the specified user. This + * parameter can be 0 if \a ProcessIdWithToken is specified. + * \param ProcessIdWithToken The ID of a process from which + * to duplicate the token. + * \param SessionId The ID of the session to run the program + * under. + * \param DesktopName The window station and desktop to run the + * program under. + * \param UseLinkedToken Uses the linked token if possible. + * + * \retval STATUS_CANCELLED The user cancelled the operation. + * + * \remarks This function will cause another instance of + * Process Hacker to be executed if the current security context + * does not have sufficient system access. This is done + * through a UAC elevation prompt. + */ +NTSTATUS PhExecuteRunAsCommand2( + _In_ HWND hWnd, + _In_ PWSTR Program, + _In_opt_ PWSTR UserName, + _In_opt_ PWSTR Password, + _In_opt_ ULONG LogonType, + _In_opt_ HANDLE ProcessIdWithToken, + _In_ ULONG SessionId, + _In_ PWSTR DesktopName, + _In_ BOOLEAN UseLinkedToken + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PH_RUNAS_SERVICE_PARAMETERS parameters; + WCHAR serviceName[32]; + PPH_STRING portName; + UNICODE_STRING portNameUs; + + memset(¶meters, 0, sizeof(PH_RUNAS_SERVICE_PARAMETERS)); + parameters.ProcessId = HandleToUlong(ProcessIdWithToken); + parameters.UserName = UserName; + parameters.Password = Password; + parameters.LogonType = LogonType; + parameters.SessionId = SessionId; + parameters.CommandLine = Program; + parameters.DesktopName = DesktopName; + parameters.UseLinkedToken = UseLinkedToken; + + // Try to use an existing instance of the service if possible. + if (RunAsOldServiceName[0] != 0) + { + PhAcquireQueuedLockExclusive(&RunAsOldServiceLock); + + portName = PhConcatStrings2(L"\\BaseNamedObjects\\", RunAsOldServiceName); + PhStringRefToUnicodeString(&portName->sr, &portNameUs); + + if (NT_SUCCESS(PhSvcConnectToServer(&portNameUs, 0))) + { + parameters.ServiceName = RunAsOldServiceName; + status = PhSvcCallInvokeRunAsService(¶meters); + PhSvcDisconnectFromServer(); + + PhDereferenceObject(portName); + PhReleaseQueuedLockExclusive(&RunAsOldServiceLock); + + return status; + } + + PhDereferenceObject(portName); + PhReleaseQueuedLockExclusive(&RunAsOldServiceLock); + } + + // An existing instance was not available. Proceed normally. + + memcpy(serviceName, L"ProcessHacker", 13 * sizeof(WCHAR)); + PhGenerateRandomAlphaString(&serviceName[13], 16); + PhAcquireQueuedLockExclusive(&RunAsOldServiceLock); + memcpy(RunAsOldServiceName, serviceName, sizeof(serviceName)); + PhReleaseQueuedLockExclusive(&RunAsOldServiceLock); + + parameters.ServiceName = serviceName; + + if (PhGetOwnTokenAttributes().Elevated) + { + status = PhExecuteRunAsCommand(¶meters); + } + else + { + if (PhUiConnectToPhSvc(hWnd, FALSE)) + { + status = PhSvcCallExecuteRunAsCommand(¶meters); + PhUiDisconnectFromPhSvc(); + } + else + { + status = STATUS_CANCELLED; + } + } + + return status; +} + +static VOID PhpSplitUserName( + _In_ PWSTR UserName, + _Out_ PPH_STRING *DomainPart, + _Out_ PPH_STRING *UserPart + ) +{ + PH_STRINGREF userName; + PH_STRINGREF domainPart; + PH_STRINGREF userPart; + + PhInitializeStringRefLongHint(&userName, UserName); + + if (PhSplitStringRefAtChar(&userName, '\\', &domainPart, &userPart)) + { + *DomainPart = PhCreateString2(&domainPart); + *UserPart = PhCreateString2(&userPart); + } + else + { + *DomainPart = NULL; + *UserPart = PhCreateString2(&userName); + } +} + +static VOID SetRunAsServiceStatus( + _In_ ULONG State + ) +{ + SERVICE_STATUS status; + + memset(&status, 0, sizeof(SERVICE_STATUS)); + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwCurrentState = State; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP; + + SetServiceStatus(RunAsServiceStatusHandle, &status); +} + +static DWORD WINAPI RunAsServiceHandlerEx( + _In_ DWORD dwControl, + _In_ DWORD dwEventType, + _In_ LPVOID lpEventData, + _In_ LPVOID lpContext + ) +{ + switch (dwControl) + { + case SERVICE_CONTROL_STOP: + PhSvcStop(&RunAsServiceStop); + return NO_ERROR; + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + } + + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static VOID WINAPI RunAsServiceMain( + _In_ DWORD dwArgc, + _In_ LPTSTR *lpszArgv + ) +{ + PPH_STRING portName; + UNICODE_STRING portNameUs; + LARGE_INTEGER timeout; + + memset(&RunAsServiceStop, 0, sizeof(PHSVC_STOP)); + RunAsServiceStatusHandle = RegisterServiceCtrlHandlerEx(RunAsServiceName->Buffer, RunAsServiceHandlerEx, NULL); + SetRunAsServiceStatus(SERVICE_RUNNING); + + portName = PhConcatStrings2(L"\\BaseNamedObjects\\", RunAsServiceName->Buffer); + PhStringRefToUnicodeString(&portName->sr, &portNameUs); + // Use a shorter timeout value to reduce the time spent running as SYSTEM. + timeout.QuadPart = -5 * PH_TIMEOUT_SEC; + + PhSvcMain(&portNameUs, &timeout, &RunAsServiceStop); + + SetRunAsServiceStatus(SERVICE_STOPPED); +} + +NTSTATUS PhRunAsServiceStart( + _In_ PPH_STRING ServiceName + ) +{ + HANDLE tokenHandle; + SERVICE_TABLE_ENTRY entry; + + // Enable some required privileges. + + if (NT_SUCCESS(NtOpenProcessToken( + NtCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES, + &tokenHandle + ))) + { + PhSetTokenPrivilege(tokenHandle, L"SeAssignPrimaryTokenPrivilege", NULL, SE_PRIVILEGE_ENABLED); + PhSetTokenPrivilege(tokenHandle, L"SeBackupPrivilege", NULL, SE_PRIVILEGE_ENABLED); + PhSetTokenPrivilege(tokenHandle, L"SeImpersonatePrivilege", NULL, SE_PRIVILEGE_ENABLED); + PhSetTokenPrivilege(tokenHandle, L"SeIncreaseQuotaPrivilege", NULL, SE_PRIVILEGE_ENABLED); + PhSetTokenPrivilege(tokenHandle, L"SeRestorePrivilege", NULL, SE_PRIVILEGE_ENABLED); + NtClose(tokenHandle); + } + + RunAsServiceName = ServiceName; + + entry.lpServiceName = ServiceName->Buffer; + entry.lpServiceProc = RunAsServiceMain; + + StartServiceCtrlDispatcher(&entry); + + return STATUS_SUCCESS; +} + +NTSTATUS PhInvokeRunAsService( + _In_ PPH_RUNAS_SERVICE_PARAMETERS Parameters + ) +{ + NTSTATUS status; + PPH_STRING domainName; + PPH_STRING userName; + PH_CREATE_PROCESS_AS_USER_INFO createInfo; + ULONG flags; + + if (Parameters->UserName) + { + PhpSplitUserName(Parameters->UserName, &domainName, &userName); + } + else + { + domainName = NULL; + userName = NULL; + } + + memset(&createInfo, 0, sizeof(PH_CREATE_PROCESS_AS_USER_INFO)); + createInfo.ApplicationName = Parameters->FileName; + createInfo.CommandLine = Parameters->CommandLine; + createInfo.CurrentDirectory = Parameters->CurrentDirectory; + createInfo.DomainName = PhGetString(domainName); + createInfo.UserName = PhGetString(userName); + createInfo.Password = Parameters->Password; + createInfo.LogonType = Parameters->LogonType; + createInfo.SessionId = Parameters->SessionId; + createInfo.DesktopName = Parameters->DesktopName; + + flags = PH_CREATE_PROCESS_SET_SESSION_ID; + + if (Parameters->ProcessId) + { + createInfo.ProcessIdWithToken = UlongToHandle(Parameters->ProcessId); + flags |= PH_CREATE_PROCESS_USE_PROCESS_TOKEN; + } + + if (Parameters->UseLinkedToken) + flags |= PH_CREATE_PROCESS_USE_LINKED_TOKEN; + + status = PhCreateProcessAsUser( + &createInfo, + flags, + NULL, + NULL, + NULL + ); + + if (domainName) PhDereferenceObject(domainName); + if (userName) PhDereferenceObject(userName); + + return status; +} diff --git a/ProcessHacker/sessmsg.c b/ProcessHacker/sessmsg.c new file mode 100644 index 0000000..f86206c --- /dev/null +++ b/ProcessHacker/sessmsg.c @@ -0,0 +1,159 @@ +/* + * Process Hacker - + * send message window + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +static PH_KEY_VALUE_PAIR PhpMessageBoxIconPairs[] = +{ + SIP(L"None", 0), + SIP(L"Information", MB_ICONINFORMATION), + SIP(L"Warning", MB_ICONWARNING), + SIP(L"Error", MB_ICONERROR), + SIP(L"Question", MB_ICONQUESTION) +}; + +INT_PTR CALLBACK PhpSessionSendMessageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowSessionSendMessageDialog( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ) +{ + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_EDITMESSAGE), + ParentWindowHandle, + PhpSessionSendMessageDlgProc, + (LPARAM)SessionId + ); +} + +INT_PTR CALLBACK PhpSessionSendMessageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND iconComboBox; + + SetProp(hwndDlg, L"SessionId", UlongToHandle((ULONG)lParam)); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + iconComboBox = GetDlgItem(hwndDlg, IDC_TYPE); + + ComboBox_AddString(iconComboBox, L"None"); + ComboBox_AddString(iconComboBox, L"Information"); + ComboBox_AddString(iconComboBox, L"Warning"); + ComboBox_AddString(iconComboBox, L"Error"); + ComboBox_AddString(iconComboBox, L"Question"); + PhSelectComboBoxString(iconComboBox, L"None", FALSE); + + if (PhCurrentUserName) + { + SetDlgItemText( + hwndDlg, + IDC_TITLE, + PhaFormatString(L"Message from %s", PhCurrentUserName->Buffer)->Buffer + ); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_TEXT), TRUE); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, L"SessionId"); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + ULONG sessionId = HandleToUlong(GetProp(hwndDlg, L"SessionId")); + PPH_STRING title; + PPH_STRING text; + ULONG icon = 0; + ULONG64 timeout = 0; + ULONG response; + + title = PhaGetDlgItemText(hwndDlg, IDC_TITLE); + text = PhaGetDlgItemText(hwndDlg, IDC_TEXT); + + PhFindIntegerSiKeyValuePairs( + PhpMessageBoxIconPairs, + sizeof(PhpMessageBoxIconPairs), + PhaGetDlgItemText(hwndDlg, IDC_TYPE)->Buffer, + &icon + ); + PhStringToInteger64( + &PhaGetDlgItemText(hwndDlg, IDC_TIMEOUT)->sr, + 10, + &timeout + ); + + if (WinStationSendMessageW( + NULL, + sessionId, + title->Buffer, + (ULONG)title->Length, + text->Buffer, + (ULONG)text->Length, + icon, + (ULONG)timeout, + &response, + TRUE + )) + { + EndDialog(hwndDlg, IDOK); + } + else + { + PhShowStatus(hwndDlg, L"Unable to send the message", 0, GetLastError()); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/sessprp.c b/ProcessHacker/sessprp.c new file mode 100644 index 0000000..89b27c3 --- /dev/null +++ b/ProcessHacker/sessprp.c @@ -0,0 +1,236 @@ +/* + * Process Hacker - + * session properties + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +INT_PTR CALLBACK PhpSessionPropertiesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +static PH_KEY_VALUE_PAIR PhpConnectStatePairs[] = +{ + SIP(L"Active", State_Active), + SIP(L"Connected", State_Connected), + SIP(L"ConnectQuery", State_ConnectQuery), + SIP(L"Shadow", State_Shadow), + SIP(L"Disconnected", State_Disconnected), + SIP(L"Idle", State_Idle), + SIP(L"Listen", State_Listen), + SIP(L"Reset", State_Reset), + SIP(L"Down", State_Down), + SIP(L"Init", State_Init) +}; + +VOID PhShowSessionProperties( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ) +{ + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_SESSION), + ParentWindowHandle, + PhpSessionPropertiesDlgProc, + (LPARAM)SessionId + ); +} + +INT_PTR CALLBACK PhpSessionPropertiesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG sessionId = (ULONG)lParam; + WINSTATIONINFORMATION winStationInfo; + BOOLEAN haveWinStationInfo; + WINSTATIONCLIENT clientInfo; + BOOLEAN haveClientInfo; + ULONG returnLength; + PWSTR stateString; + + SetProp(hwndDlg, L"SessionId", UlongToHandle(sessionId)); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + // Query basic session information + + haveWinStationInfo = WinStationQueryInformationW( + NULL, + sessionId, + WinStationInformation, + &winStationInfo, + sizeof(WINSTATIONINFORMATION), + &returnLength + ); + + // Query client information + + haveClientInfo = WinStationQueryInformationW( + NULL, + sessionId, + WinStationClient, + &clientInfo, + sizeof(WINSTATIONCLIENT), + &returnLength + ); + + if (haveWinStationInfo) + { + SetDlgItemText(hwndDlg, IDC_USERNAME, + PhaFormatString(L"%s\\%s", winStationInfo.Domain, winStationInfo.UserName)->Buffer); + } + + SetDlgItemInt(hwndDlg, IDC_SESSIONID, sessionId, FALSE); + + if (haveWinStationInfo) + { + if (PhFindStringSiKeyValuePairs( + PhpConnectStatePairs, + sizeof(PhpConnectStatePairs), + winStationInfo.ConnectState, + &stateString + )) + { + SetDlgItemText(hwndDlg, IDC_STATE, stateString); + } + } + + if (haveWinStationInfo && winStationInfo.LogonTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + PPH_STRING time; + + PhLargeIntegerToLocalSystemTime(&systemTime, &winStationInfo.LogonTime); + time = PhFormatDateTime(&systemTime); + SetDlgItemText(hwndDlg, IDC_LOGONTIME, time->Buffer); + PhDereferenceObject(time); + } + + if (haveWinStationInfo && winStationInfo.ConnectTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + PPH_STRING time; + + PhLargeIntegerToLocalSystemTime(&systemTime, &winStationInfo.ConnectTime); + time = PhFormatDateTime(&systemTime); + SetDlgItemText(hwndDlg, IDC_CONNECTTIME, time->Buffer); + PhDereferenceObject(time); + } + + if (haveWinStationInfo && winStationInfo.DisconnectTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + PPH_STRING time; + + PhLargeIntegerToLocalSystemTime(&systemTime, &winStationInfo.DisconnectTime); + time = PhFormatDateTime(&systemTime); + SetDlgItemText(hwndDlg, IDC_DISCONNECTTIME, time->Buffer); + PhDereferenceObject(time); + } + + if (haveWinStationInfo && winStationInfo.LastInputTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + PPH_STRING time; + + PhLargeIntegerToLocalSystemTime(&systemTime, &winStationInfo.LastInputTime); + time = PhFormatDateTime(&systemTime); + SetDlgItemText(hwndDlg, IDC_LASTINPUTTIME, time->Buffer); + PhDereferenceObject(time); + } + + if (haveClientInfo && clientInfo.ClientName[0] != 0) + { + WCHAR addressString[65]; + + SetDlgItemText(hwndDlg, IDC_CLIENTNAME, clientInfo.ClientName); + + if (clientInfo.ClientAddressFamily == AF_INET6) + { + struct in6_addr address; + ULONG i; + PUSHORT in; + PUSHORT out; + + // IPv6 is special - the client address data is a reversed version of + // the real address. + + in = (PUSHORT)clientInfo.ClientAddress; + out = (PUSHORT)address.u.Word; + + for (i = 8; i != 0; i--) + { + *out = _byteswap_ushort(*in); + in++; + out++; + } + + RtlIpv6AddressToString(&address, addressString); + } + else + { + wcscpy_s(addressString, 65, clientInfo.ClientAddress); + } + + SetDlgItemText(hwndDlg, IDC_CLIENTADDRESS, addressString); + + SetDlgItemText(hwndDlg, IDC_CLIENTDISPLAY, + PhaFormatString(L"%ux%u@%u", clientInfo.HRes, + clientInfo.VRes, clientInfo.ColorDepth)->Buffer + ); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDOK), TRUE); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, L"SessionId"); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/sessshad.c b/ProcessHacker/sessshad.c new file mode 100644 index 0000000..509c644 --- /dev/null +++ b/ProcessHacker/sessshad.c @@ -0,0 +1,237 @@ +/* + * Process Hacker - + * session shadow configuration + * + * Copyright (C) 2011-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +static PH_KEY_VALUE_PAIR VirtualKeyPairs[] = +{ + SIP(L"0", '0'), + SIP(L"1", '1'), + SIP(L"2", '2'), + SIP(L"3", '3'), + SIP(L"4", '4'), + SIP(L"5", '5'), + SIP(L"6", '6'), + SIP(L"7", '7'), + SIP(L"8", '8'), + SIP(L"9", '9'), + SIP(L"A", 'A'), + SIP(L"B", 'B'), + SIP(L"C", 'C'), + SIP(L"D", 'D'), + SIP(L"E", 'E'), + SIP(L"F", 'F'), + SIP(L"G", 'G'), + SIP(L"H", 'H'), + SIP(L"I", 'I'), + SIP(L"J", 'J'), + SIP(L"K", 'K'), + SIP(L"L", 'L'), + SIP(L"M", 'M'), + SIP(L"N", 'N'), + SIP(L"O", 'O'), + SIP(L"P", 'P'), + SIP(L"Q", 'Q'), + SIP(L"R", 'R'), + SIP(L"S", 'S'), + SIP(L"T", 'T'), + SIP(L"U", 'U'), + SIP(L"V", 'V'), + SIP(L"W", 'W'), + SIP(L"X", 'X'), + SIP(L"Y", 'Y'), + SIP(L"Z", 'Z'), + SIP(L"{backspace}", VK_BACK), + SIP(L"{delete}", VK_DELETE), + SIP(L"{down}", VK_DOWN), + SIP(L"{end}", VK_END), + SIP(L"{enter}", VK_RETURN), + SIP(L"{F2}", VK_F2), + SIP(L"{F3}", VK_F3), + SIP(L"{F4}", VK_F4), + SIP(L"{F5}", VK_F5), + SIP(L"{F6}", VK_F6), + SIP(L"{F7}", VK_F7), + SIP(L"{F8}", VK_F8), + SIP(L"{F9}", VK_F9), + SIP(L"{F10}", VK_F10), + SIP(L"{F11}", VK_F11), + SIP(L"{F12}", VK_F12), + SIP(L"{home}", VK_HOME), + SIP(L"{insert}", VK_INSERT), + SIP(L"{left}", VK_LEFT), + SIP(L"{-}", VK_SUBTRACT), + SIP(L"{pagedown}", VK_NEXT), + SIP(L"{pageup}", VK_PRIOR), + SIP(L"{+}", VK_ADD), + SIP(L"{prtscrn}", VK_SNAPSHOT), + SIP(L"{right}", VK_RIGHT), + SIP(L"{spacebar}", VK_SPACE), + SIP(L"{*}", VK_MULTIPLY), + SIP(L"{tab}", VK_TAB), + SIP(L"{up}", VK_UP) +}; + +INT_PTR CALLBACK PhpSessionShadowDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowSessionShadowDialog( + _In_ HWND ParentWindowHandle, + _In_ ULONG SessionId + ) +{ + if (SessionId == NtCurrentPeb()->SessionId) + { + PhShowError(ParentWindowHandle, L"You cannot remote control the current session."); + return; + } + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_SHADOWSESSION), + ParentWindowHandle, + PhpSessionShadowDlgProc, + (LPARAM)SessionId + ); +} + +INT_PTR CALLBACK PhpSessionShadowDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND virtualKeyComboBox; + PH_INTEGER_PAIR hotkey; + ULONG i; + PWSTR stringToSelect; + + SetProp(hwndDlg, L"SessionId", UlongToHandle((ULONG)lParam)); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + hotkey = PhGetIntegerPairSetting(L"SessionShadowHotkey"); + + // Set up the hotkeys. + + virtualKeyComboBox = GetDlgItem(hwndDlg, IDC_VIRTUALKEY); + stringToSelect = L"{*}"; + + for (i = 0; i < sizeof(VirtualKeyPairs) / sizeof(PH_KEY_VALUE_PAIR); i++) + { + ComboBox_AddString(virtualKeyComboBox, VirtualKeyPairs[i].Key); + + if (PtrToUlong(VirtualKeyPairs[i].Value) == (ULONG)hotkey.X) + { + stringToSelect = VirtualKeyPairs[i].Key; + } + } + + PhSelectComboBoxString(virtualKeyComboBox, stringToSelect, FALSE); + + // Set up the modifiers. + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_SHIFT), hotkey.Y & KBDSHIFT); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_CTRL), hotkey.Y & KBDCTRL); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ALT), hotkey.Y & KBDALT); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, L"SessionId"); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + ULONG sessionId = HandleToUlong(GetProp(hwndDlg, L"SessionId")); + ULONG virtualKey; + ULONG modifiers; + WCHAR computerName[64]; + ULONG computerNameLength = 64; + + virtualKey = VK_MULTIPLY; + PhFindIntegerSiKeyValuePairs( + VirtualKeyPairs, + sizeof(VirtualKeyPairs), + PhaGetDlgItemText(hwndDlg, IDC_VIRTUALKEY)->Buffer, + &virtualKey + ); + + modifiers = 0; + + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_SHIFT)) == BST_CHECKED) + modifiers |= KBDSHIFT; + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_CTRL)) == BST_CHECKED) + modifiers |= KBDCTRL; + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_ALT)) == BST_CHECKED) + modifiers |= KBDALT; + + if (GetComputerName(computerName, &computerNameLength)) + { + if (WinStationShadow(NULL, computerName, sessionId, (UCHAR)virtualKey, (USHORT)modifiers)) + { + PH_INTEGER_PAIR hotkey; + + hotkey.X = virtualKey; + hotkey.Y = modifiers; + PhSetIntegerPairSetting(L"SessionShadowHotkey", hotkey); + + EndDialog(hwndDlg, IDOK); + } + else + { + PhShowStatus(hwndDlg, L"Unable to remote control the session", 0, GetLastError()); + } + } + else + { + PhShowError(hwndDlg, L"The computer name is too long."); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/settings.c b/ProcessHacker/settings.c new file mode 100644 index 0000000..d72cc72 --- /dev/null +++ b/ProcessHacker/settings.c @@ -0,0 +1,1197 @@ +/* + * Process Hacker - + * program settings + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains a program-specific settings system. All possible + * settings are defined at program startup and added to a hashtable. + * The values of these settings can then be read in from a XML file or + * saved to a XML file at any time. Settings which are not recognized + * are added to a list of "ignored settings"; this is necessary to + * support plugin settings, as we don't want their settings to get + * deleted whenever the plugins are disabled. + * + * The get/set functions are very strict. If the wrong function is used + * (the get-integer-setting function is used on a string setting) or + * the setting does not exist, an exception will be raised. + */ + +#define PH_SETTINGS_PRIVATE +#include +#include "mxml/mxml.h" +#include +#include + +PPH_HASHTABLE PhSettingsHashtable; +PH_QUEUED_LOCK PhSettingsLock = PH_QUEUED_LOCK_INIT; + +PPH_LIST PhIgnoredSettings; + +// These macros make sure the C strings can be seamlessly converted into +// PH_STRINGREFs at compile time, for a small speed boost. + +#define ADD_SETTING_WRAPPER(Type, Name, DefaultValue) \ + { \ + static PH_STRINGREF name = PH_STRINGREF_INIT(Name); \ + static PH_STRINGREF defaultValue = PH_STRINGREF_INIT(DefaultValue); \ + PhpAddSetting(Type, &name, &defaultValue); \ + } + +#define PhpAddStringSetting(A, B) ADD_SETTING_WRAPPER(StringSettingType, A, B) +#define PhpAddIntegerSetting(A, B) ADD_SETTING_WRAPPER(IntegerSettingType, A, B) +#define PhpAddIntegerPairSetting(A, B) ADD_SETTING_WRAPPER(IntegerPairSettingType, A, B) +#define PhpAddScalableIntegerPairSetting(A, B) ADD_SETTING_WRAPPER(ScalableIntegerPairSettingType, A, B) + +VOID PhSettingsInitialization( + VOID + ) +{ + PhSettingsHashtable = PhCreateHashtable( + sizeof(PH_SETTING), + PhpSettingsHashtableEqualFunction, + PhpSettingsHashtableHashFunction, + 256 + ); + PhIgnoredSettings = PhCreateList(4); + + PhpAddIntegerSetting(L"AllowOnlyOneInstance", L"1"); + PhpAddIntegerSetting(L"CloseOnEscape", L"0"); + PhpAddIntegerSetting(L"CollapseServicesOnStart", L"0"); + PhpAddStringSetting(L"DbgHelpPath", L"dbghelp.dll"); + PhpAddStringSetting(L"DbgHelpSearchPath", L""); + PhpAddIntegerSetting(L"DbgHelpUndecorate", L"1"); + PhpAddStringSetting(L"DisabledPlugins", L""); + PhpAddIntegerSetting(L"ElevationLevel", L"1"); // PromptElevateAction + PhpAddIntegerSetting(L"EnableCycleCpuUsage", L"1"); + PhpAddIntegerSetting(L"EnableInstantTooltips", L"0"); + PhpAddIntegerSetting(L"EnableKph", L"1"); + PhpAddIntegerSetting(L"EnableNetworkResolve", L"1"); + PhpAddIntegerSetting(L"EnablePlugins", L"1"); + PhpAddIntegerSetting(L"EnableServiceNonPoll", L"1"); + PhpAddIntegerSetting(L"EnableStage2", L"1"); + PhpAddIntegerSetting(L"EnableWarnings", L"1"); + PhpAddStringSetting(L"EnvironmentListViewColumns", L""); + PhpAddIntegerSetting(L"FindObjRegex", L"0"); + PhpAddStringSetting(L"FindObjListViewColumns", L""); + PhpAddIntegerPairSetting(L"FindObjWindowPosition", L"350,350"); + PhpAddScalableIntegerPairSetting(L"FindObjWindowSize", L"@96|550,420"); + PhpAddIntegerSetting(L"FirstRun", L"1"); + PhpAddStringSetting(L"Font", L""); // null + PhpAddIntegerSetting(L"ForceNoParent", L"0"); + PhpAddStringSetting(L"HandleTreeListColumns", L""); + PhpAddStringSetting(L"HandleTreeListSort", L"0,1"); // 0, AscendingSortOrder + PhpAddStringSetting(L"HiddenProcessesListViewColumns", L""); + PhpAddIntegerPairSetting(L"HiddenProcessesWindowPosition", L"400,400"); + PhpAddScalableIntegerPairSetting(L"HiddenProcessesWindowSize", L"@96|520,400"); + PhpAddIntegerSetting(L"HideDriverServices", L"0"); + PhpAddIntegerSetting(L"HideOnClose", L"0"); + PhpAddIntegerSetting(L"HideOnMinimize", L"0"); + PhpAddIntegerSetting(L"HideOtherUserProcesses", L"0"); + PhpAddIntegerSetting(L"HideSignedProcesses", L"0"); + PhpAddIntegerSetting(L"HideUnnamedHandles", L"1"); + PhpAddIntegerSetting(L"HighlightingDuration", L"3e8"); // 1000ms + PhpAddIntegerSetting(L"IconMask", L"1"); // PH_ICON_CPU_HISTORY + PhpAddStringSetting(L"IconMaskList", L""); + PhpAddIntegerSetting(L"IconNotifyMask", L"c"); // PH_NOTIFY_SERVICE_CREATE | PH_NOTIFY_SERVICE_DELETE + PhpAddIntegerSetting(L"IconProcesses", L"f"); // 15 + PhpAddIntegerSetting(L"IconSingleClick", L"0"); + PhpAddIntegerSetting(L"IconTogglesVisibility", L"1"); + PhpAddIntegerSetting(L"LogEntries", L"200"); // 512 + PhpAddStringSetting(L"LogListViewColumns", L""); + PhpAddIntegerPairSetting(L"LogWindowPosition", L"300,300"); + PhpAddScalableIntegerPairSetting(L"LogWindowSize", L"@96|450,500"); + PhpAddIntegerSetting(L"MainWindowAlwaysOnTop", L"0"); + PhpAddIntegerSetting(L"MainWindowOpacity", L"0"); // means 100% + PhpAddIntegerPairSetting(L"MainWindowPosition", L"100,100"); + PhpAddScalableIntegerPairSetting(L"MainWindowSize", L"@96|800,600"); + PhpAddIntegerSetting(L"MainWindowState", L"1"); + PhpAddIntegerSetting(L"MaxSizeUnit", L"6"); + PhpAddIntegerSetting(L"MemEditBytesPerRow", L"10"); // 16 + PhpAddStringSetting(L"MemEditGotoChoices", L""); + PhpAddIntegerPairSetting(L"MemEditPosition", L"450,450"); + PhpAddScalableIntegerPairSetting(L"MemEditSize", L"@96|600,500"); + PhpAddStringSetting(L"MemFilterChoices", L""); + PhpAddStringSetting(L"MemResultsListViewColumns", L""); + PhpAddIntegerPairSetting(L"MemResultsPosition", L"300,300"); + PhpAddScalableIntegerPairSetting(L"MemResultsSize", L"@96|500,520"); + PhpAddStringSetting(L"MemoryTreeListColumns", L""); + PhpAddStringSetting(L"MemoryTreeListSort", L"0,0"); // 0, NoSortOrder + PhpAddIntegerPairSetting(L"MemoryListsWindowPosition", L"400,400"); + PhpAddStringSetting(L"MemoryReadWriteAddressChoices", L""); + PhpAddIntegerSetting(L"MiniInfoWindowEnabled", L"1"); + PhpAddIntegerSetting(L"MiniInfoWindowOpacity", L"0"); // means 100% + PhpAddIntegerSetting(L"MiniInfoWindowPinned", L"0"); + PhpAddIntegerPairSetting(L"MiniInfoWindowPosition", L"200,200"); + PhpAddIntegerSetting(L"MiniInfoWindowRefreshAutomatically", L"1"); + PhpAddScalableIntegerPairSetting(L"MiniInfoWindowSize", L"@96|10,200"); + PhpAddStringSetting(L"ModuleTreeListColumns", L""); + PhpAddStringSetting(L"ModuleTreeListSort", L"0,0"); // 0, NoSortOrder + PhpAddStringSetting(L"NetworkTreeListColumns", L""); + PhpAddStringSetting(L"NetworkTreeListSort", L"0,1"); // 0, AscendingSortOrder + PhpAddIntegerSetting(L"NoPurgeProcessRecords", L"0"); + PhpAddStringSetting(L"PluginsDirectory", L"plugins"); + PhpAddStringSetting(L"ProcessServiceListViewColumns", L""); + PhpAddStringSetting(L"ProcessTreeListColumns", L""); + PhpAddStringSetting(L"ProcessTreeListSort", L"0,0"); // 0, NoSortOrder + PhpAddStringSetting(L"ProcPropPage", L"General"); + PhpAddIntegerPairSetting(L"ProcPropPosition", L"200,200"); + PhpAddScalableIntegerPairSetting(L"ProcPropSize", L"@96|460,580"); + PhpAddStringSetting(L"ProgramInspectExecutables", L"peview.exe \"%s\""); + PhpAddIntegerSetting(L"PropagateCpuUsage", L"0"); + PhpAddStringSetting(L"RunAsProgram", L""); + PhpAddStringSetting(L"RunAsUserName", L""); + PhpAddIntegerSetting(L"SampleCount", L"200"); // 512 + PhpAddIntegerSetting(L"SampleCountAutomatic", L"1"); + PhpAddIntegerSetting(L"ScrollToNewProcesses", L"0"); + PhpAddStringSetting(L"SearchEngine", L"http://www.google.com/search?q=\"%s\""); + PhpAddStringSetting(L"ServiceListViewColumns", L""); + PhpAddStringSetting(L"ServiceTreeListColumns", L""); + PhpAddStringSetting(L"ServiceTreeListSort", L"0,1"); // 0, AscendingSortOrder + PhpAddIntegerPairSetting(L"SessionShadowHotkey", L"106,2"); // VK_MULTIPLY,KBDCTRL + PhpAddIntegerSetting(L"ShowCommitInSummary", L"1"); + PhpAddIntegerSetting(L"ShowCpuBelow001", L"0"); + PhpAddIntegerSetting(L"StartHidden", L"0"); + PhpAddIntegerSetting(L"SysInfoWindowAlwaysOnTop", L"0"); + PhpAddIntegerSetting(L"SysInfoWindowOneGraphPerCpu", L"0"); + PhpAddIntegerPairSetting(L"SysInfoWindowPosition", L"200,200"); + PhpAddStringSetting(L"SysInfoWindowSection", L""); + PhpAddScalableIntegerPairSetting(L"SysInfoWindowSize", L"@96|620,590"); + PhpAddIntegerSetting(L"SysInfoWindowState", L"1"); + PhpAddIntegerSetting(L"ThinRows", L"0"); + PhpAddStringSetting(L"ThreadTreeListColumns", L""); + PhpAddStringSetting(L"ThreadTreeListSort", L"1,2"); // 1, DescendingSortOrder + PhpAddStringSetting(L"ThreadStackListViewColumns", L""); + PhpAddScalableIntegerPairSetting(L"ThreadStackWindowSize", L"@96|420,380"); + PhpAddIntegerSetting(L"UpdateInterval", L"3e8"); // 1000ms + + // Colors are specified with R in the lowest byte, then G, then B. + // So: bbggrr. + PhpAddIntegerSetting(L"ColorNew", L"00ff7f"); // Chartreuse + PhpAddIntegerSetting(L"ColorRemoved", L"283cff"); + PhpAddIntegerSetting(L"UseColorOwnProcesses", L"1"); + PhpAddIntegerSetting(L"ColorOwnProcesses", L"aaffff"); + PhpAddIntegerSetting(L"UseColorSystemProcesses", L"1"); + PhpAddIntegerSetting(L"ColorSystemProcesses", L"ffccaa"); + PhpAddIntegerSetting(L"UseColorServiceProcesses", L"1"); + PhpAddIntegerSetting(L"ColorServiceProcesses", L"ffffcc"); + PhpAddIntegerSetting(L"UseColorJobProcesses", L"0"); + PhpAddIntegerSetting(L"ColorJobProcesses", L"3f85cd"); // Peru + PhpAddIntegerSetting(L"UseColorWow64Processes", L"0"); + PhpAddIntegerSetting(L"ColorWow64Processes", L"8f8fbc"); // Rosy Brown + PhpAddIntegerSetting(L"UseColorDebuggedProcesses", L"1"); + PhpAddIntegerSetting(L"ColorDebuggedProcesses", L"ffbbcc"); + PhpAddIntegerSetting(L"UseColorElevatedProcesses", L"1"); + PhpAddIntegerSetting(L"ColorElevatedProcesses", L"00aaff"); + PhpAddIntegerSetting(L"UseColorImmersiveProcesses", L"1"); + PhpAddIntegerSetting(L"ColorImmersiveProcesses", L"cbc0ff"); // Pink + PhpAddIntegerSetting(L"UseColorSuspended", L"1"); + PhpAddIntegerSetting(L"ColorSuspended", L"777777"); + PhpAddIntegerSetting(L"UseColorDotNet", L"1"); + PhpAddIntegerSetting(L"ColorDotNet", L"00ffde"); + PhpAddIntegerSetting(L"UseColorPacked", L"1"); + PhpAddIntegerSetting(L"ColorPacked", L"9314ff"); // Deep Pink + PhpAddIntegerSetting(L"UseColorGuiThreads", L"1"); + PhpAddIntegerSetting(L"ColorGuiThreads", L"77ffff"); + PhpAddIntegerSetting(L"UseColorRelocatedModules", L"1"); + PhpAddIntegerSetting(L"ColorRelocatedModules", L"80c0ff"); + PhpAddIntegerSetting(L"UseColorProtectedHandles", L"1"); + PhpAddIntegerSetting(L"ColorProtectedHandles", L"777777"); + PhpAddIntegerSetting(L"UseColorInheritHandles", L"1"); + PhpAddIntegerSetting(L"ColorInheritHandles", L"ffff77"); + + PhpAddIntegerSetting(L"GraphShowText", L"1"); + PhpAddIntegerSetting(L"GraphColorMode", L"0"); + PhpAddIntegerSetting(L"ColorCpuKernel", L"00ff00"); + PhpAddIntegerSetting(L"ColorCpuUser", L"0000ff"); + PhpAddIntegerSetting(L"ColorIoReadOther", L"00ffff"); + PhpAddIntegerSetting(L"ColorIoWrite", L"ff0077"); + PhpAddIntegerSetting(L"ColorPrivate", L"0077ff"); + PhpAddIntegerSetting(L"ColorPhysical", L"ffff00"); + + PhUpdateCachedSettings(); +} + +VOID PhUpdateCachedSettings( + VOID + ) +{ +#define UPDATE_INTEGER_CS(Name) (PhCs##Name = PhGetIntegerSetting(L#Name)) + + UPDATE_INTEGER_CS(CollapseServicesOnStart); + UPDATE_INTEGER_CS(ForceNoParent); + UPDATE_INTEGER_CS(HighlightingDuration); + UPDATE_INTEGER_CS(PropagateCpuUsage); + UPDATE_INTEGER_CS(ScrollToNewProcesses); + UPDATE_INTEGER_CS(ShowCpuBelow001); + UPDATE_INTEGER_CS(UpdateInterval); + + UPDATE_INTEGER_CS(ColorNew); + UPDATE_INTEGER_CS(ColorRemoved); + UPDATE_INTEGER_CS(UseColorOwnProcesses); + UPDATE_INTEGER_CS(ColorOwnProcesses); + UPDATE_INTEGER_CS(UseColorSystemProcesses); + UPDATE_INTEGER_CS(ColorSystemProcesses); + UPDATE_INTEGER_CS(UseColorServiceProcesses); + UPDATE_INTEGER_CS(ColorServiceProcesses); + UPDATE_INTEGER_CS(UseColorJobProcesses); + UPDATE_INTEGER_CS(ColorJobProcesses); + UPDATE_INTEGER_CS(UseColorWow64Processes); + UPDATE_INTEGER_CS(ColorWow64Processes); + UPDATE_INTEGER_CS(UseColorDebuggedProcesses); + UPDATE_INTEGER_CS(ColorDebuggedProcesses); + UPDATE_INTEGER_CS(UseColorElevatedProcesses); + UPDATE_INTEGER_CS(ColorElevatedProcesses); + UPDATE_INTEGER_CS(UseColorImmersiveProcesses); + UPDATE_INTEGER_CS(ColorImmersiveProcesses); + UPDATE_INTEGER_CS(UseColorSuspended); + UPDATE_INTEGER_CS(ColorSuspended); + UPDATE_INTEGER_CS(UseColorDotNet); + UPDATE_INTEGER_CS(ColorDotNet); + UPDATE_INTEGER_CS(UseColorPacked); + UPDATE_INTEGER_CS(ColorPacked); + UPDATE_INTEGER_CS(UseColorGuiThreads); + UPDATE_INTEGER_CS(ColorGuiThreads); + UPDATE_INTEGER_CS(UseColorRelocatedModules); + UPDATE_INTEGER_CS(ColorRelocatedModules); + UPDATE_INTEGER_CS(UseColorProtectedHandles); + UPDATE_INTEGER_CS(ColorProtectedHandles); + UPDATE_INTEGER_CS(UseColorInheritHandles); + UPDATE_INTEGER_CS(ColorInheritHandles); + UPDATE_INTEGER_CS(GraphShowText); + UPDATE_INTEGER_CS(GraphColorMode); + UPDATE_INTEGER_CS(ColorCpuKernel); + UPDATE_INTEGER_CS(ColorCpuUser); + UPDATE_INTEGER_CS(ColorIoReadOther); + UPDATE_INTEGER_CS(ColorIoWrite); + UPDATE_INTEGER_CS(ColorPrivate); + UPDATE_INTEGER_CS(ColorPhysical); +} + +BOOLEAN NTAPI PhpSettingsHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_SETTING setting1 = (PPH_SETTING)Entry1; + PPH_SETTING setting2 = (PPH_SETTING)Entry2; + + return PhEqualStringRef(&setting1->Name, &setting2->Name, FALSE); +} + +ULONG NTAPI PhpSettingsHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPH_SETTING setting = (PPH_SETTING)Entry; + + return PhHashBytes((PUCHAR)setting->Name.Buffer, setting->Name.Length); +} + +static VOID PhpAddSetting( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_STRINGREF Name, + _In_ PPH_STRINGREF DefaultValue + ) +{ + PH_SETTING setting; + + setting.Type = Type; + setting.Name = *Name; + setting.DefaultValue = *DefaultValue; + memset(&setting.u, 0, sizeof(setting.u)); + + PhpSettingFromString(Type, &setting.DefaultValue, NULL, &setting); + + PhAddEntryHashtable(PhSettingsHashtable, &setting); +} + +static ULONG PhpGetCurrentScale( + VOID + ) +{ + static PH_INITONCE initOnce; + static ULONG dpi = 96; + + if (PhBeginInitOnce(&initOnce)) + { + HDC hdc; + + if (hdc = GetDC(NULL)) + { + dpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } + + PhEndInitOnce(&initOnce); + } + + return dpi; +} + +static PPH_STRING PhpSettingToString( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_SETTING Setting + ) +{ + switch (Type) + { + case StringSettingType: + { + if (!Setting->u.Pointer) + return PhReferenceEmptyString(); + + PhReferenceObject(Setting->u.Pointer); + + return (PPH_STRING)Setting->u.Pointer; + } + case IntegerSettingType: + { + return PhFormatString(L"%x", Setting->u.Integer); + } + case IntegerPairSettingType: + { + PPH_INTEGER_PAIR integerPair = &Setting->u.IntegerPair; + + return PhFormatString(L"%d,%d", integerPair->X, integerPair->Y); + } + case ScalableIntegerPairSettingType: + { + PPH_SCALABLE_INTEGER_PAIR scalableIntegerPair = Setting->u.Pointer; + + if (!scalableIntegerPair) + return PhReferenceEmptyString(); + + return PhFormatString(L"@%u|%d,%d", scalableIntegerPair->Scale, scalableIntegerPair->X, scalableIntegerPair->Y); + } + } + + return PhReferenceEmptyString(); +} + +static BOOLEAN PhpSettingFromString( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_STRINGREF StringRef, + _In_opt_ PPH_STRING String, + _Inout_ PPH_SETTING Setting + ) +{ + switch (Type) + { + case StringSettingType: + { + if (String) + { + PhSetReference(&Setting->u.Pointer, String); + } + else + { + Setting->u.Pointer = PhCreateString2(StringRef); + } + + return TRUE; + } + case IntegerSettingType: + { + ULONG64 integer; + + if (PhStringToInteger64(StringRef, 16, &integer)) + { + Setting->u.Integer = (ULONG)integer; + return TRUE; + } + else + { + return FALSE; + } + } + case IntegerPairSettingType: + { + LONG64 x; + LONG64 y; + PH_STRINGREF xString; + PH_STRINGREF yString; + + if (!PhSplitStringRefAtChar(StringRef, ',', &xString, &yString)) + return FALSE; + + if (PhStringToInteger64(&xString, 10, &x) && PhStringToInteger64(&yString, 10, &y)) + { + Setting->u.IntegerPair.X = (LONG)x; + Setting->u.IntegerPair.Y = (LONG)y; + return TRUE; + } + else + { + return FALSE; + } + } + case ScalableIntegerPairSettingType: + { + ULONG64 scale; + LONG64 x; + LONG64 y; + PH_STRINGREF stringRef; + PH_STRINGREF firstPart; + PH_STRINGREF secondPart; + PPH_SCALABLE_INTEGER_PAIR scalableIntegerPair; + + stringRef = *StringRef; + + if (stringRef.Length != 0 && stringRef.Buffer[0] == '@') + { + PhSkipStringRef(&stringRef, sizeof(WCHAR)); + + if (!PhSplitStringRefAtChar(&stringRef, '|', &firstPart, &stringRef)) + return FALSE; + if (!PhStringToInteger64(&firstPart, 10, &scale)) + return FALSE; + } + else + { + scale = PhpGetCurrentScale(); + } + + if (!PhSplitStringRefAtChar(&stringRef, ',', &firstPart, &secondPart)) + return FALSE; + + if (PhStringToInteger64(&firstPart, 10, &x) && PhStringToInteger64(&secondPart, 10, &y)) + { + scalableIntegerPair = PhAllocate(sizeof(PH_SCALABLE_INTEGER_PAIR)); + scalableIntegerPair->X = (LONG)x; + scalableIntegerPair->Y = (LONG)y; + scalableIntegerPair->Scale = (ULONG)scale; + Setting->u.Pointer = scalableIntegerPair; + return TRUE; + } + else + { + return FALSE; + } + } + } + + return FALSE; +} + +static VOID PhpFreeSettingValue( + _In_ PH_SETTING_TYPE Type, + _In_ PPH_SETTING Setting + ) +{ + switch (Type) + { + case StringSettingType: + PhClearReference(&Setting->u.Pointer); + break; + case ScalableIntegerPairSettingType: + PhFree(Setting->u.Pointer); + Setting->u.Pointer = NULL; + break; + } +} + +static PVOID PhpLookupSetting( + _In_ PPH_STRINGREF Name + ) +{ + PH_SETTING lookupSetting; + PPH_SETTING setting; + + lookupSetting.Name = *Name; + setting = (PPH_SETTING)PhFindEntryHashtable( + PhSettingsHashtable, + &lookupSetting + ); + + return setting; +} + +_May_raise_ ULONG PhGetIntegerSetting( + _In_ PWSTR Name + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + ULONG value; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockShared(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == IntegerSettingType) + { + value = setting->u.Integer; + } + else + { + setting = NULL; + } + + PhReleaseQueuedLockShared(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); + + return value; +} + +_May_raise_ PH_INTEGER_PAIR PhGetIntegerPairSetting( + _In_ PWSTR Name + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + PH_INTEGER_PAIR value; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockShared(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == IntegerPairSettingType) + { + value = setting->u.IntegerPair; + } + else + { + setting = NULL; + } + + PhReleaseQueuedLockShared(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); + + return value; +} + +_May_raise_ PH_SCALABLE_INTEGER_PAIR PhGetScalableIntegerPairSetting( + _In_ PWSTR Name, + _In_ BOOLEAN ScaleToCurrent + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + PH_SCALABLE_INTEGER_PAIR value; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockShared(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == ScalableIntegerPairSettingType) + { + value = *(PPH_SCALABLE_INTEGER_PAIR)setting->u.Pointer; + } + else + { + setting = NULL; + } + + PhReleaseQueuedLockShared(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); + + if (ScaleToCurrent) + { + ULONG currentScale; + + currentScale = PhpGetCurrentScale(); + + if (value.Scale != currentScale && value.Scale != 0) + { + value.X = PhMultiplyDivideSigned(value.X, currentScale, value.Scale); + value.Y = PhMultiplyDivideSigned(value.Y, currentScale, value.Scale); + value.Scale = currentScale; + } + } + + return value; +} + +_May_raise_ PPH_STRING PhGetStringSetting( + _In_ PWSTR Name + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + PPH_STRING value; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockShared(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == StringSettingType) + { + if (setting->u.Pointer) + { + PhSetReference(&value, setting->u.Pointer); + } + else + { + // Set to NULL, create an empty string + // outside of the lock. + value = NULL; + } + } + else + { + setting = NULL; + } + + PhReleaseQueuedLockShared(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); + + if (!value) + value = PhReferenceEmptyString(); + + return value; +} + +_May_raise_ VOID PhSetIntegerSetting( + _In_ PWSTR Name, + _In_ ULONG Value + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == IntegerSettingType) + { + setting->u.Integer = Value; + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); +} + +_May_raise_ VOID PhSetIntegerPairSetting( + _In_ PWSTR Name, + _In_ PH_INTEGER_PAIR Value + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == IntegerPairSettingType) + { + setting->u.IntegerPair = Value; + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); +} + +_May_raise_ VOID PhSetScalableIntegerPairSetting( + _In_ PWSTR Name, + _In_ PH_SCALABLE_INTEGER_PAIR Value + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == ScalableIntegerPairSettingType) + { + PhpFreeSettingValue(ScalableIntegerPairSettingType, setting); + setting->u.Pointer = PhAllocateCopy(&Value, sizeof(PH_SCALABLE_INTEGER_PAIR)); + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); +} + +_May_raise_ VOID PhSetScalableIntegerPairSetting2( + _In_ PWSTR Name, + _In_ PH_INTEGER_PAIR Value + ) +{ + PH_SCALABLE_INTEGER_PAIR scalableIntegerPair; + + scalableIntegerPair.Pair = Value; + scalableIntegerPair.Scale = PhpGetCurrentScale(); + PhSetScalableIntegerPairSetting(Name, scalableIntegerPair); +} + +_May_raise_ VOID PhSetStringSetting( + _In_ PWSTR Name, + _In_ PWSTR Value + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == StringSettingType) + { + PhpFreeSettingValue(StringSettingType, setting); + setting->u.Pointer = PhCreateString(Value); + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); +} + +_May_raise_ VOID PhSetStringSetting2( + _In_ PWSTR Name, + _In_ PPH_STRINGREF Value + ) +{ + PPH_SETTING setting; + PH_STRINGREF name; + + PhInitializeStringRef(&name, Name); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + setting = PhpLookupSetting(&name); + + if (setting && setting->Type == StringSettingType) + { + PhpFreeSettingValue(StringSettingType, setting); + setting->u.Pointer = PhCreateString2(Value); + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + if (!setting) + PhRaiseStatus(STATUS_NOT_FOUND); +} + +VOID PhpFreeIgnoredSetting( + _In_ PPH_SETTING Setting + ) +{ + PhFree(Setting->Name.Buffer); + PhDereferenceObject(Setting->u.Pointer); + + PhFree(Setting); +} + +VOID PhpClearIgnoredSettings( + VOID + ) +{ + ULONG i; + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + for (i = 0; i < PhIgnoredSettings->Count; i++) + { + PhpFreeIgnoredSetting(PhIgnoredSettings->Items[i]); + } + + PhClearList(PhIgnoredSettings); + + PhReleaseQueuedLockExclusive(&PhSettingsLock); +} + +VOID PhClearIgnoredSettings( + VOID + ) +{ + PhpClearIgnoredSettings(); +} + +VOID PhConvertIgnoredSettings( + VOID + ) +{ + ULONG i; + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + for (i = 0; i < PhIgnoredSettings->Count; i++) + { + PPH_SETTING ignoredSetting = PhIgnoredSettings->Items[i]; + PPH_SETTING setting; + + setting = PhpLookupSetting(&ignoredSetting->Name); + + if (setting) + { + PhpFreeSettingValue(setting->Type, setting); + + if (!PhpSettingFromString( + setting->Type, + &((PPH_STRING)ignoredSetting->u.Pointer)->sr, + ignoredSetting->u.Pointer, + setting + )) + { + PhpSettingFromString( + setting->Type, + &setting->DefaultValue, + NULL, + setting + ); + } + + PhpFreeIgnoredSetting(ignoredSetting); + + PhRemoveItemList(PhIgnoredSettings, i); + i--; + } + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); +} + +mxml_type_t PhpSettingsLoadCallback( + _In_ mxml_node_t *node + ) +{ + return MXML_OPAQUE; +} + +NTSTATUS PhLoadSettings( + _In_ PWSTR FileName + ) +{ + NTSTATUS status; + HANDLE fileHandle; + LARGE_INTEGER fileSize; + mxml_node_t *topNode; + mxml_node_t *currentNode; + + PhpClearIgnoredSettings(); + + status = PhCreateFileWin32( + &fileHandle, + FileName, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + return status; + + if (NT_SUCCESS(PhGetFileSize(fileHandle, &fileSize)) && fileSize.QuadPart == 0) + { + // A blank file is OK. There are no settings to load. + NtClose(fileHandle); + return status; + } + + topNode = mxmlLoadFd(NULL, fileHandle, PhpSettingsLoadCallback); + NtClose(fileHandle); + + if (!topNode) + return STATUS_FILE_CORRUPT_ERROR; + + if (topNode->type != MXML_ELEMENT) + { + mxmlDelete(topNode); + return STATUS_FILE_CORRUPT_ERROR; + } + + currentNode = topNode->child; + + while (currentNode) + { + PPH_STRING settingName = NULL; + + if ( + currentNode->type == MXML_ELEMENT && + currentNode->value.element.num_attrs >= 1 && + _stricmp(currentNode->value.element.attrs[0].name, "name") == 0 + ) + { + settingName = PhConvertUtf8ToUtf16(currentNode->value.element.attrs[0].value); + } + + if (settingName) + { + PPH_STRING settingValue; + + settingValue = PhGetOpaqueXmlNodeText(currentNode); + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + { + PPH_SETTING setting; + + setting = PhpLookupSetting(&settingName->sr); + + if (setting) + { + PhpFreeSettingValue(setting->Type, setting); + + if (!PhpSettingFromString( + setting->Type, + &settingValue->sr, + settingValue, + setting + )) + { + PhpSettingFromString( + setting->Type, + &setting->DefaultValue, + NULL, + setting + ); + } + } + else + { + setting = PhAllocate(sizeof(PH_SETTING)); + setting->Name.Buffer = PhAllocateCopy(settingName->Buffer, settingName->Length + sizeof(WCHAR)); + setting->Name.Length = settingName->Length; + PhReferenceObject(settingValue); + setting->u.Pointer = settingValue; + + PhAddItemList(PhIgnoredSettings, setting); + } + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); + + PhDereferenceObject(settingValue); + PhDereferenceObject(settingName); + } + + currentNode = currentNode->next; + } + + mxmlDelete(topNode); + + PhUpdateCachedSettings(); + + return STATUS_SUCCESS; +} + +char *PhpSettingsSaveCallback( + _In_ mxml_node_t *node, + _In_ int position + ) +{ + if (PhEqualBytesZ(node->value.element.name, "setting", TRUE)) + { + if (position == MXML_WS_BEFORE_OPEN) + return " "; + else if (position == MXML_WS_AFTER_CLOSE) + return "\r\n"; + } + else if (PhEqualBytesZ(node->value.element.name, "settings", TRUE)) + { + if (position == MXML_WS_AFTER_OPEN) + return "\r\n"; + } + + return NULL; +} + +mxml_node_t *PhpCreateSettingElement( + _Inout_ mxml_node_t *ParentNode, + _In_ PPH_STRINGREF SettingName, + _In_ PPH_STRINGREF SettingValue + ) +{ + mxml_node_t *settingNode; + mxml_node_t *textNode; + PPH_BYTES settingNameUtf8; + PPH_BYTES settingValueUtf8; + + // Create the setting element. + + settingNode = mxmlNewElement(ParentNode, "setting"); + + settingNameUtf8 = PhConvertUtf16ToUtf8Ex(SettingName->Buffer, SettingName->Length); + mxmlElementSetAttr(settingNode, "name", settingNameUtf8->Buffer); + PhDereferenceObject(settingNameUtf8); + + // Set the value. + + settingValueUtf8 = PhConvertUtf16ToUtf8Ex(SettingValue->Buffer, SettingValue->Length); + textNode = mxmlNewOpaque(settingNode, settingValueUtf8->Buffer); + PhDereferenceObject(settingValueUtf8); + + return settingNode; +} + +NTSTATUS PhSaveSettings( + _In_ PWSTR FileName + ) +{ + NTSTATUS status; + HANDLE fileHandle; + mxml_node_t *topNode; + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_SETTING setting; + + topNode = mxmlNewElement(MXML_NO_PARENT, "settings"); + + PhAcquireQueuedLockShared(&PhSettingsLock); + + PhBeginEnumHashtable(PhSettingsHashtable, &enumContext); + + while (setting = PhNextEnumHashtable(&enumContext)) + { + PPH_STRING settingValue; + + settingValue = PhpSettingToString(setting->Type, setting); + PhpCreateSettingElement(topNode, &setting->Name, &settingValue->sr); + PhDereferenceObject(settingValue); + } + + // Write the ignored settings. + { + ULONG i; + + for (i = 0; i < PhIgnoredSettings->Count; i++) + { + PPH_STRING settingValue; + + setting = PhIgnoredSettings->Items[i]; + settingValue = setting->u.Pointer; + PhpCreateSettingElement(topNode, &setting->Name, &settingValue->sr); + } + } + + PhReleaseQueuedLockShared(&PhSettingsLock); + + // Create the directory if it does not exist. + { + PPH_STRING fullPath; + ULONG indexOfFileName; + PPH_STRING directoryName; + + fullPath = PhGetFullPath(FileName, &indexOfFileName); + + if (fullPath) + { + if (indexOfFileName != -1) + { + directoryName = PhSubstring(fullPath, 0, indexOfFileName); + SHCreateDirectoryEx(NULL, directoryName->Buffer, NULL); + PhDereferenceObject(directoryName); + } + + PhDereferenceObject(fullPath); + } + } + + status = PhCreateFileWin32( + &fileHandle, + FileName, + FILE_GENERIC_WRITE, + 0, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + { + mxmlDelete(topNode); + return status; + } + + mxmlSaveFd(topNode, fileHandle, PhpSettingsSaveCallback); + mxmlDelete(topNode); + NtClose(fileHandle); + + return STATUS_SUCCESS; +} + +VOID PhResetSettings( + VOID + ) +{ + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_SETTING setting; + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + PhBeginEnumHashtable(PhSettingsHashtable, &enumContext); + + while (setting = PhNextEnumHashtable(&enumContext)) + { + PhpFreeSettingValue(setting->Type, setting); + PhpSettingFromString(setting->Type, &setting->DefaultValue, NULL, setting); + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); +} + +VOID PhAddSettings( + _In_ PPH_SETTING_CREATE Settings, + _In_ ULONG NumberOfSettings + ) +{ + ULONG i; + + PhAcquireQueuedLockExclusive(&PhSettingsLock); + + for (i = 0; i < NumberOfSettings; i++) + { + PH_STRINGREF name; + PH_STRINGREF defaultValue; + + PhInitializeStringRefLongHint(&name, Settings[i].Name); + PhInitializeStringRefLongHint(&defaultValue, Settings[i].DefaultValue); + PhpAddSetting(Settings[i].Type, &name, &defaultValue); + } + + PhReleaseQueuedLockExclusive(&PhSettingsLock); +} diff --git a/ProcessHacker/srvcr.c b/ProcessHacker/srvcr.c new file mode 100644 index 0000000..f02e48a --- /dev/null +++ b/ProcessHacker/srvcr.c @@ -0,0 +1,223 @@ +/* + * Process Hacker - + * service creation dialog + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +INT_PTR CALLBACK PhpCreateServiceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowCreateServiceDialog( + _In_ HWND ParentWindowHandle + ) +{ + DialogBox( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_CREATESERVICE), + ParentWindowHandle, + PhpCreateServiceDlgProc + ); +} + +INT_PTR CALLBACK PhpCreateServiceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_TYPE), PhServiceTypeStrings, + sizeof(PhServiceTypeStrings) / sizeof(WCHAR *)); + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_STARTTYPE), PhServiceStartTypeStrings, + sizeof(PhServiceStartTypeStrings) / sizeof(WCHAR *)); + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_ERRORCONTROL), PhServiceErrorControlStrings, + sizeof(PhServiceErrorControlStrings) / sizeof(WCHAR *)); + + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), L"Own Process", FALSE); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_STARTTYPE), L"Demand Start", FALSE); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_ERRORCONTROL), L"Ignore", FALSE); + + if (!PhGetOwnTokenAttributes().Elevated) + { + SendMessage(GetDlgItem(hwndDlg, IDOK), BCM_SETSHIELD, 0, TRUE); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_NAME), TRUE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + EndDialog(hwndDlg, IDCANCEL); + } + break; + case IDOK: + { + NTSTATUS status = 0; + BOOLEAN success = FALSE; + SC_HANDLE scManagerHandle; + SC_HANDLE serviceHandle; + ULONG win32Result = 0; + PPH_STRING serviceName; + PPH_STRING serviceDisplayName; + PPH_STRING serviceTypeString; + PPH_STRING serviceStartTypeString; + PPH_STRING serviceErrorControlString; + ULONG serviceType; + ULONG serviceStartType; + ULONG serviceErrorControl; + PPH_STRING serviceBinaryPath; + + serviceName = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_NAME))); + serviceDisplayName = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_DISPLAYNAME))); + + serviceTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_TYPE))); + serviceStartTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_STARTTYPE))); + serviceErrorControlString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_ERRORCONTROL))); + serviceType = PhGetServiceTypeInteger(serviceTypeString->Buffer); + serviceStartType = PhGetServiceStartTypeInteger(serviceStartTypeString->Buffer); + serviceErrorControl = PhGetServiceErrorControlInteger(serviceErrorControlString->Buffer); + + serviceBinaryPath = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_BINARYPATH))); + + if (PhGetOwnTokenAttributes().Elevated) + { + if (scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) + { + if (serviceHandle = CreateService( + scManagerHandle, + serviceName->Buffer, + serviceDisplayName->Buffer, + SERVICE_CHANGE_CONFIG, + serviceType, + serviceStartType, + serviceErrorControl, + serviceBinaryPath->Buffer, + NULL, + NULL, + NULL, + NULL, + L"" + )) + { + EndDialog(hwndDlg, IDOK); + CloseServiceHandle(serviceHandle); + success = TRUE; + } + else + { + win32Result = GetLastError(); + } + + CloseServiceHandle(scManagerHandle); + } + else + { + win32Result = GetLastError(); + } + } + else + { + if (PhUiConnectToPhSvc(hwndDlg, FALSE)) + { + status = PhSvcCallCreateService( + serviceName->Buffer, + serviceDisplayName->Buffer, + serviceType, + serviceStartType, + serviceErrorControl, + serviceBinaryPath->Buffer, + NULL, + NULL, + NULL, + NULL, + L"" + ); + PhUiDisconnectFromPhSvc(); + + if (NT_SUCCESS(status)) + { + EndDialog(hwndDlg, IDOK); + success = TRUE; + } + } + else + { + // User cancelled elevation. + success = TRUE; + } + } + + if (!success) + PhShowStatus(hwndDlg, L"Unable to create the service", status, win32Result); + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Executable files (*.exe;*.sys)", L"*.exe;*.sys" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PhGetFileName(PhaGetDlgItemText(hwndDlg, IDC_BINARYPATH)); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + PhDereferenceObject(fileName); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PhGetFileDialogFileName(fileDialog); + SetDlgItemText(hwndDlg, IDC_BINARYPATH, fileName->Buffer); + PhDereferenceObject(fileName); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/srvctl.c b/ProcessHacker/srvctl.c new file mode 100644 index 0000000..1b6f0f9 --- /dev/null +++ b/ProcessHacker/srvctl.c @@ -0,0 +1,409 @@ +/* + * Process Hacker - + * service list control + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +typedef struct _PH_SERVICES_CONTEXT +{ + PPH_SERVICE_ITEM *Services; + ULONG NumberOfServices; + PH_CALLBACK_REGISTRATION ModifiedEventRegistration; + + PWSTR ListViewSettingName; + + PH_LAYOUT_MANAGER LayoutManager; + HWND WindowHandle; +} PH_SERVICES_CONTEXT, *PPH_SERVICES_CONTEXT; + +VOID NTAPI ServiceModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +INT_PTR CALLBACK PhpServicesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +/** + * Creates a service list property page. + * + * \param ParentWindowHandle The parent of the service list. + * \param Services An array of service items. Each + * service item must have a reference that is transferred + * to this function. The array must be allocated using + * PhAllocate() and must not be freed by the caller; it + * will be freed automatically when no longer needed. + * \param NumberOfServices The number of service items + * in \a Services. + */ +HWND PhCreateServiceListControl( + _In_ HWND ParentWindowHandle, + _In_ PPH_SERVICE_ITEM *Services, + _In_ ULONG NumberOfServices + ) +{ + HWND windowHandle; + PPH_SERVICES_CONTEXT servicesContext; + + servicesContext = PhAllocate(sizeof(PH_SERVICES_CONTEXT)); + + memset(servicesContext, 0, sizeof(PH_SERVICES_CONTEXT)); + servicesContext->Services = Services; + servicesContext->NumberOfServices = NumberOfServices; + + windowHandle = CreateDialogParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_SRVLIST), + ParentWindowHandle, + PhpServicesPageProc, + (LPARAM)servicesContext + ); + + if (!windowHandle) + { + PhFree(servicesContext); + return windowHandle; + } + + return windowHandle; +} + +static VOID NTAPI ServiceModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_MODIFIED_DATA serviceModifiedData = (PPH_SERVICE_MODIFIED_DATA)Parameter; + PPH_SERVICES_CONTEXT servicesContext = (PPH_SERVICES_CONTEXT)Context; + PPH_SERVICE_MODIFIED_DATA copy; + + copy = PhAllocateCopy(serviceModifiedData, sizeof(PH_SERVICE_MODIFIED_DATA)); + + PostMessage(servicesContext->WindowHandle, WM_PH_SERVICE_MODIFIED, 0, (LPARAM)copy); +} + +VOID PhpFixProcessServicesControls( + _In_ HWND hWnd, + _In_opt_ PPH_SERVICE_ITEM ServiceItem + ) +{ + HWND startButton; + HWND pauseButton; + HWND descriptionLabel; + + startButton = GetDlgItem(hWnd, IDC_START); + pauseButton = GetDlgItem(hWnd, IDC_PAUSE); + descriptionLabel = GetDlgItem(hWnd, IDC_DESCRIPTION); + + if (ServiceItem) + { + SC_HANDLE serviceHandle; + PPH_STRING description; + + switch (ServiceItem->State) + { + case SERVICE_RUNNING: + { + SetWindowText(startButton, L"S&top"); + SetWindowText(pauseButton, L"&Pause"); + EnableWindow(startButton, ServiceItem->ControlsAccepted & SERVICE_ACCEPT_STOP); + EnableWindow(pauseButton, ServiceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + } + break; + case SERVICE_PAUSED: + { + SetWindowText(startButton, L"S&top"); + SetWindowText(pauseButton, L"C&ontinue"); + EnableWindow(startButton, ServiceItem->ControlsAccepted & SERVICE_ACCEPT_STOP); + EnableWindow(pauseButton, ServiceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + } + break; + case SERVICE_STOPPED: + { + SetWindowText(startButton, L"&Start"); + SetWindowText(pauseButton, L"&Pause"); + EnableWindow(startButton, TRUE); + EnableWindow(pauseButton, FALSE); + } + break; + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_STOP_PENDING: + { + SetWindowText(startButton, L"&Start"); + SetWindowText(pauseButton, L"&Pause"); + EnableWindow(startButton, FALSE); + EnableWindow(pauseButton, FALSE); + } + break; + } + + if (serviceHandle = PhOpenService( + ServiceItem->Name->Buffer, + SERVICE_QUERY_CONFIG + )) + { + if (description = PhGetServiceDescription(serviceHandle)) + { + SetWindowText(descriptionLabel, description->Buffer); + PhDereferenceObject(description); + } + + CloseServiceHandle(serviceHandle); + } + } + else + { + SetWindowText(startButton, L"&Start"); + SetWindowText(pauseButton, L"&Pause"); + EnableWindow(startButton, FALSE); + EnableWindow(pauseButton, FALSE); + SetWindowText(descriptionLabel, L""); + } +} + +INT_PTR CALLBACK PhpServicesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_SERVICES_CONTEXT servicesContext; + HWND lvHandle; + + if (uMsg == WM_INITDIALOG) + { + servicesContext = (PPH_SERVICES_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)servicesContext); + } + else + { + servicesContext = (PPH_SERVICES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + } + + if (!servicesContext) + return FALSE; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG i; + + PhRegisterCallback( + &PhServiceModifiedEvent, + ServiceModifiedHandler, + servicesContext, + &servicesContext->ModifiedEventRegistration + ); + + servicesContext->WindowHandle = hwndDlg; + + // Initialize the list. + PhSetListViewStyle(lvHandle, TRUE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 120, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 220, L"Display name"); + + PhSetExtendedListView(lvHandle); + + for (i = 0; i < servicesContext->NumberOfServices; i++) + { + PPH_SERVICE_ITEM serviceItem; + INT lvItemIndex; + + serviceItem = servicesContext->Services[i]; + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, serviceItem->Name->Buffer, serviceItem); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, serviceItem->DisplayName->Buffer); + } + + ExtendedListView_SortItems(lvHandle); + + PhpFixProcessServicesControls(hwndDlg, NULL); + + PhInitializeLayoutManager(&servicesContext->LayoutManager, hwndDlg); + PhAddLayoutItem(&servicesContext->LayoutManager, GetDlgItem(hwndDlg, IDC_LIST), + NULL, PH_ANCHOR_ALL); + PhAddLayoutItem(&servicesContext->LayoutManager, GetDlgItem(hwndDlg, IDC_DESCRIPTION), + NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&servicesContext->LayoutManager, GetDlgItem(hwndDlg, IDC_START), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&servicesContext->LayoutManager, GetDlgItem(hwndDlg, IDC_PAUSE), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + } + break; + case WM_DESTROY: + { + ULONG i; + + for (i = 0; i < servicesContext->NumberOfServices; i++) + PhDereferenceObject(servicesContext->Services[i]); + + PhFree(servicesContext->Services); + + PhUnregisterCallback( + &PhServiceModifiedEvent, + &servicesContext->ModifiedEventRegistration + ); + + if (servicesContext->ListViewSettingName) + PhSaveListViewColumnsToSetting(servicesContext->ListViewSettingName, lvHandle); + + PhDeleteLayoutManager(&servicesContext->LayoutManager); + + PhFree(servicesContext); + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case IDC_START: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedListViewItemParam(lvHandle); + + if (serviceItem) + { + switch (serviceItem->State) + { + case SERVICE_RUNNING: + PhUiStopService(hwndDlg, serviceItem); + break; + case SERVICE_PAUSED: + PhUiStopService(hwndDlg, serviceItem); + break; + case SERVICE_STOPPED: + PhUiStartService(hwndDlg, serviceItem); + break; + } + } + } + break; + case IDC_PAUSE: + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedListViewItemParam(lvHandle); + + if (serviceItem) + { + switch (serviceItem->State) + { + case SERVICE_RUNNING: + PhUiPauseService(hwndDlg, serviceItem); + break; + case SERVICE_PAUSED: + PhUiContinueService(hwndDlg, serviceItem); + break; + } + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + PhHandleListViewNotifyBehaviors(lParam, lvHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + + switch (header->code) + { + case NM_DBLCLK: + { + if (header->hwndFrom == lvHandle) + { + PPH_SERVICE_ITEM serviceItem = PhGetSelectedListViewItemParam(lvHandle); + + if (serviceItem) + { + PhShowServiceProperties(hwndDlg, serviceItem); + } + } + } + break; + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == lvHandle) + { + //LPNMITEMACTIVATE itemActivate = (LPNMITEMACTIVATE)header; + PPH_SERVICE_ITEM serviceItem = NULL; + + if (ListView_GetSelectedCount(lvHandle) == 1) + serviceItem = PhGetSelectedListViewItemParam(lvHandle); + + PhpFixProcessServicesControls(hwndDlg, serviceItem); + } + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&servicesContext->LayoutManager); + } + break; + case WM_PH_SERVICE_MODIFIED: + { + PPH_SERVICE_MODIFIED_DATA serviceModifiedData = (PPH_SERVICE_MODIFIED_DATA)lParam; + PPH_SERVICE_ITEM serviceItem = NULL; + + if (ListView_GetSelectedCount(lvHandle) == 1) + serviceItem = PhGetSelectedListViewItemParam(lvHandle); + + if (serviceModifiedData->Service == serviceItem) + { + PhpFixProcessServicesControls(hwndDlg, serviceItem); + } + + PhFree(serviceModifiedData); + } + break; + case WM_PH_SET_LIST_VIEW_SETTINGS: + { + PWSTR settingName = (PWSTR)lParam; + + servicesContext->ListViewSettingName = settingName; + PhLoadListViewColumnsFromSetting(settingName, lvHandle); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/srvlist.c b/ProcessHacker/srvlist.c new file mode 100644 index 0000000..cb77239 --- /dev/null +++ b/ProcessHacker/srvlist.c @@ -0,0 +1,901 @@ +/* + * Process Hacker - + * service list + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhpServiceNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG PhpServiceNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpRemoveServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ); + +LONG PhpServiceTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpServiceTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +static HWND ServiceTreeListHandle; +static ULONG ServiceTreeListSortColumn; +static PH_SORT_ORDER ServiceTreeListSortOrder; +static PH_CM_MANAGER ServiceTreeListCm; + +static PPH_HASHTABLE ServiceNodeHashtable; // hashtable of all nodes +static PPH_LIST ServiceNodeList; // list of all nodes + +static PH_TN_FILTER_SUPPORT FilterSupport; + +static BOOLEAN ServiceIconsLoaded = FALSE; +static HICON ServiceApplicationIcon; +static HICON ServiceApplicationGoIcon; +static HICON ServiceCogIcon; +static HICON ServiceCogGoIcon; + +BOOLEAN PhServiceTreeListStateHighlighting = TRUE; +static PPH_POINTER_LIST ServiceNodeStateList = NULL; // list of nodes which need to be processed + +VOID PhServiceTreeListInitialization( + VOID + ) +{ + ServiceNodeHashtable = PhCreateHashtable( + sizeof(PPH_SERVICE_NODE), + PhpServiceNodeHashtableEqualFunction, + PhpServiceNodeHashtableHashFunction, + 100 + ); + ServiceNodeList = PhCreateList(100); +} + +BOOLEAN PhpServiceNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_SERVICE_NODE serviceNode1 = *(PPH_SERVICE_NODE *)Entry1; + PPH_SERVICE_NODE serviceNode2 = *(PPH_SERVICE_NODE *)Entry2; + + return serviceNode1->ServiceItem == serviceNode2->ServiceItem; +} + +ULONG PhpServiceNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)(*(PPH_SERVICE_NODE *)Entry)->ServiceItem); +} + +VOID PhInitializeServiceTreeList( + _In_ HWND hwnd + ) +{ + ServiceTreeListHandle = hwnd; + PhSetControlTheme(ServiceTreeListHandle, L"explorer"); + SendMessage(TreeNew_GetTooltips(ServiceTreeListHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT); + + TreeNew_SetCallback(hwnd, PhpServiceTreeNewCallback, NULL); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHSVTLC_NAME, TRUE, L"Name", 140, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_DISPLAYNAME, TRUE, L"Display name", 220, PH_ALIGN_LEFT, 1, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_TYPE, TRUE, L"Type", 100, PH_ALIGN_LEFT, 2, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_STATUS, TRUE, L"Status", 70, PH_ALIGN_LEFT, 3, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_STARTTYPE, TRUE, L"Start type", 130, PH_ALIGN_LEFT, 4, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_PID, TRUE, L"PID", 50, PH_ALIGN_RIGHT, 5, DT_RIGHT); + + PhAddTreeNewColumn(hwnd, PHSVTLC_BINARYPATH, FALSE, L"Binary path", 180, PH_ALIGN_LEFT, -1, DT_PATH_ELLIPSIS); + PhAddTreeNewColumn(hwnd, PHSVTLC_ERRORCONTROL, FALSE, L"Error control", 70, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_GROUP, FALSE, L"Group", 100, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumn(hwnd, PHSVTLC_DESCRIPTION, FALSE, L"Description", 200, PH_ALIGN_LEFT, -1, 0); + PhAddTreeNewColumnEx(hwnd, PHSVTLC_KEYMODIFIEDTIME, FALSE, L"Key modified time", 140, PH_ALIGN_LEFT, -1, 0, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetSort(hwnd, 0, AscendingSortOrder); + + PhCmInitializeManager(&ServiceTreeListCm, hwnd, PHSVTLC_MAXIMUM, PhpServiceTreeNewPostSortFunction); + + if (PhPluginsEnabled) + { + PH_PLUGIN_TREENEW_INFORMATION treeNewInfo; + + treeNewInfo.TreeNewHandle = hwnd; + treeNewInfo.CmData = &ServiceTreeListCm; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackServiceTreeNewInitializing), &treeNewInfo); + } + + PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, ServiceNodeList); +} + +VOID PhLoadSettingsServiceTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhGetStringSetting(L"ServiceTreeListColumns"); + sortSettings = PhGetStringSetting(L"ServiceTreeListSort"); + PhCmLoadSettingsEx(ServiceTreeListHandle, &ServiceTreeListCm, 0, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +VOID PhSaveSettingsServiceTreeList( + VOID + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(ServiceTreeListHandle, &ServiceTreeListCm, 0, &sortSettings); + PhSetStringSetting2(L"ServiceTreeListColumns", &settings->sr); + PhSetStringSetting2(L"ServiceTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +struct _PH_TN_FILTER_SUPPORT *PhGetFilterSupportServiceTreeList( + VOID + ) +{ + return &FilterSupport; +} + +PPH_SERVICE_NODE PhAddServiceNode( + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ ULONG RunId + ) +{ + PPH_SERVICE_NODE serviceNode; + + serviceNode = PhAllocate(PhEmGetObjectSize(EmServiceNodeType, sizeof(PH_SERVICE_NODE))); + memset(serviceNode, 0, sizeof(PH_SERVICE_NODE)); + PhInitializeTreeNewNode(&serviceNode->Node); + + if (PhServiceTreeListStateHighlighting && RunId != 1) + { + PhChangeShStateTn( + &serviceNode->Node, + &serviceNode->ShState, + &ServiceNodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + serviceNode->ServiceItem = ServiceItem; + PhReferenceObject(ServiceItem); + + memset(serviceNode->TextCache, 0, sizeof(PH_STRINGREF) * PHSVTLC_MAXIMUM); + serviceNode->Node.TextCache = serviceNode->TextCache; + serviceNode->Node.TextCacheSize = PHSVTLC_MAXIMUM; + + PhAddEntryHashtable(ServiceNodeHashtable, &serviceNode); + PhAddItemList(ServiceNodeList, serviceNode); + + if (FilterSupport.FilterList) + serviceNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &serviceNode->Node); + + PhEmCallObjectOperation(EmServiceNodeType, serviceNode, EmObjectCreate); + + TreeNew_NodesStructured(ServiceTreeListHandle); + + return serviceNode; +} + +PPH_SERVICE_NODE PhFindServiceNode( + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + PH_SERVICE_NODE lookupServiceNode; + PPH_SERVICE_NODE lookupServiceNodePtr = &lookupServiceNode; + PPH_SERVICE_NODE *serviceNode; + + lookupServiceNode.ServiceItem = ServiceItem; + + serviceNode = (PPH_SERVICE_NODE *)PhFindEntryHashtable( + ServiceNodeHashtable, + &lookupServiceNodePtr + ); + + if (serviceNode) + return *serviceNode; + else + return NULL; +} + +VOID PhRemoveServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashtable(ServiceNodeHashtable, &ServiceNode); + + if (PhServiceTreeListStateHighlighting) + { + PhChangeShStateTn( + &ServiceNode->Node, + &ServiceNode->ShState, + &ServiceNodeStateList, + RemovingItemState, + PhCsColorRemoved, + ServiceTreeListHandle + ); + } + else + { + PhpRemoveServiceNode(ServiceNode); + } +} + +VOID PhpRemoveServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ) +{ + ULONG index; + + PhEmCallObjectOperation(EmServiceNodeType, ServiceNode, EmObjectDelete); + + // Remove from list and cleanup. + + if ((index = PhFindItemList(ServiceNodeList, ServiceNode)) != -1) + PhRemoveItemList(ServiceNodeList, index); + + PhClearReference(&ServiceNode->BinaryPath); + PhClearReference(&ServiceNode->LoadOrderGroup); + PhClearReference(&ServiceNode->Description); + + PhClearReference(&ServiceNode->TooltipText); + + PhClearReference(&ServiceNode->KeyModifiedTimeText); + + PhDereferenceObject(ServiceNode->ServiceItem); + + PhFree(ServiceNode); + + TreeNew_NodesStructured(ServiceTreeListHandle); +} + +VOID PhUpdateServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ) +{ + memset(ServiceNode->TextCache, 0, sizeof(PH_STRINGREF) * PHSVTLC_MAXIMUM); + PhClearReference(&ServiceNode->TooltipText); + + ServiceNode->ValidMask = 0; + PhInvalidateTreeNewNode(&ServiceNode->Node, TN_CACHE_ICON); + TreeNew_NodesStructured(ServiceTreeListHandle); +} + +VOID PhTickServiceNodes( + VOID + ) +{ + if (ServiceTreeListSortOrder != NoSortOrder && ServiceTreeListSortColumn >= PHSVTLC_MAXIMUM) + { + // Sorting is on, but it's not one of our columns. Force a rebuild. (If it was one of our + // columns, the restructure would have been handled in PhUpdateServiceNode.) + TreeNew_NodesStructured(ServiceTreeListHandle); + } + + PH_TICK_SH_STATE_TN(PH_SERVICE_NODE, ShState, ServiceNodeStateList, PhpRemoveServiceNode, PhCsHighlightingDuration, ServiceTreeListHandle, TRUE, NULL); +} + +static VOID PhpUpdateServiceNodeConfig( + _Inout_ PPH_SERVICE_NODE ServiceNode + ) +{ + if (!(ServiceNode->ValidMask & PHSN_CONFIG)) + { + SC_HANDLE serviceHandle; + LPQUERY_SERVICE_CONFIG serviceConfig; + + if (serviceHandle = PhOpenService(ServiceNode->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG)) + { + if (serviceConfig = PhGetServiceConfig(serviceHandle)) + { + if (serviceConfig->lpBinaryPathName) + PhMoveReference(&ServiceNode->BinaryPath, PhCreateString(serviceConfig->lpBinaryPathName)); + if (serviceConfig->lpLoadOrderGroup) + PhMoveReference(&ServiceNode->LoadOrderGroup, PhCreateString(serviceConfig->lpLoadOrderGroup)); + + PhFree(serviceConfig); + } + + CloseServiceHandle(serviceHandle); + } + + ServiceNode->ValidMask |= PHSN_CONFIG; + } +} + +static VOID PhpUpdateServiceNodeDescription( + _Inout_ PPH_SERVICE_NODE ServiceNode + ) +{ + if (!(ServiceNode->ValidMask & PHSN_DESCRIPTION)) + { + SC_HANDLE serviceHandle; + + if (serviceHandle = PhOpenService(ServiceNode->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG)) + { + PhMoveReference(&ServiceNode->Description, PhGetServiceDescription(serviceHandle)); + + CloseServiceHandle(serviceHandle); + } + + ServiceNode->ValidMask |= PHSN_DESCRIPTION; + } +} + +static VOID PhpUpdateServiceNodeKey( + _Inout_ PPH_SERVICE_NODE ServiceNode + ) +{ + if (!(ServiceNode->ValidMask & PHSN_KEY)) + { + static PH_STRINGREF servicesKeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\"); + + HANDLE keyHandle; + PPH_STRING keyName; + + keyName = PhConcatStringRef2(&servicesKeyName, &ServiceNode->ServiceItem->Name->sr); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName->sr, + 0 + ))) + { + PKEY_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhQueryKey(keyHandle, KeyBasicInformation, &basicInfo))) + { + ServiceNode->KeyLastWriteTime = basicInfo->LastWriteTime; + PhFree(basicInfo); + } + + NtClose(keyHandle); + } + + PhDereferenceObject(keyName); + + ServiceNode->ValidMask |= PHSN_KEY; + } +} + +#define SORT_FUNCTION(Column) PhpServiceTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpServiceTreeNewCompare##Column( \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_SERVICE_NODE node1 = *(PPH_SERVICE_NODE *)_elem1; \ + PPH_SERVICE_NODE node2 = *(PPH_SERVICE_NODE *)_elem2; \ + PPH_SERVICE_ITEM serviceItem1 = node1->ServiceItem; \ + PPH_SERVICE_ITEM serviceItem2 = node2->ServiceItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + /* if (sortResult == 0) */ \ + /* sortResult = PhCompareString(serviceItem1->Name, serviceItem2->Name, TRUE); */ \ + \ + return PhModifySort(sortResult, ServiceTreeListSortOrder); \ +} + +LONG PhpServiceTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(Name) +{ + sortResult = PhCompareString(serviceItem1->Name, serviceItem2->Name, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(DisplayName) +{ + sortResult = PhCompareStringWithNull(serviceItem1->DisplayName, serviceItem2->DisplayName, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Type) +{ + sortResult = intcmp(serviceItem1->Type, serviceItem2->Type); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Status) +{ + sortResult = intcmp(serviceItem1->State, serviceItem2->State); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(StartType) +{ + sortResult = intcmp(serviceItem1->StartType, serviceItem2->StartType); + + if (sortResult == 0) + sortResult = intcmp(serviceItem1->DelayedStart, serviceItem2->DelayedStart); + if (sortResult == 0) + sortResult = intcmp(serviceItem1->HasTriggers, serviceItem2->HasTriggers); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Pid) +{ + sortResult = uintptrcmp((ULONG_PTR)serviceItem1->ProcessId, (ULONG_PTR)serviceItem2->ProcessId); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(BinaryPath) +{ + PhpUpdateServiceNodeConfig(node1); + PhpUpdateServiceNodeConfig(node2); + sortResult = PhCompareStringWithNull(node1->BinaryPath, node2->BinaryPath, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ErrorControl) +{ + sortResult = intcmp(serviceItem1->ErrorControl, serviceItem2->ErrorControl); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Group) +{ + PhpUpdateServiceNodeConfig(node1); + PhpUpdateServiceNodeConfig(node2); + sortResult = PhCompareStringWithNull(node1->LoadOrderGroup, node2->LoadOrderGroup, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Description) +{ + PhpUpdateServiceNodeDescription(node1); + PhpUpdateServiceNodeDescription(node2); + sortResult = PhCompareStringWithNull(node1->Description, node2->Description, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(KeyModifiedTime) +{ + PhpUpdateServiceNodeKey(node1); + PhpUpdateServiceNodeKey(node2); + sortResult = int64cmp(node1->KeyLastWriteTime.QuadPart, node2->KeyLastWriteTime.QuadPart); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpServiceTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_NODE node; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &ServiceTreeListCm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Name), + SORT_FUNCTION(DisplayName), + SORT_FUNCTION(Type), + SORT_FUNCTION(Status), + SORT_FUNCTION(StartType), + SORT_FUNCTION(Pid), + SORT_FUNCTION(BinaryPath), + SORT_FUNCTION(ErrorControl), + SORT_FUNCTION(Group), + SORT_FUNCTION(Description), + SORT_FUNCTION(KeyModifiedTime) + }; + int (__cdecl *sortFunction)(const void *, const void *); + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)ServiceNodeList->Items, + ServiceNodeList->Count, + ServiceTreeListSortColumn, + ServiceTreeListSortOrder, + &ServiceTreeListCm + )) + { + if (ServiceTreeListSortColumn < PHSVTLC_MAXIMUM) + sortFunction = sortFunctions[ServiceTreeListSortColumn]; + else + sortFunction = NULL; + + if (sortFunction) + { + qsort(ServiceNodeList->Items, ServiceNodeList->Count, sizeof(PVOID), sortFunction); + } + } + + getChildren->Children = (PPH_TREENEW_NODE *)ServiceNodeList->Items; + getChildren->NumberOfChildren = ServiceNodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_SERVICE_ITEM serviceItem; + + node = (PPH_SERVICE_NODE)getCellText->Node; + serviceItem = node->ServiceItem; + + switch (getCellText->Id) + { + case PHSVTLC_NAME: + getCellText->Text = serviceItem->Name->sr; + break; + case PHSVTLC_DISPLAYNAME: + getCellText->Text = serviceItem->DisplayName->sr; + break; + case PHSVTLC_TYPE: + PhInitializeStringRefLongHint(&getCellText->Text, PhGetServiceTypeString(serviceItem->Type)); + break; + case PHSVTLC_STATUS: + PhInitializeStringRefLongHint(&getCellText->Text, PhGetServiceStateString(serviceItem->State)); + break; + case PHSVTLC_STARTTYPE: + { + PH_FORMAT format[2]; + PWSTR additional = NULL; + SIZE_T returnLength; + + PhInitFormatS(&format[0], PhGetServiceStartTypeString(serviceItem->StartType)); + + if (serviceItem->DelayedStart && serviceItem->HasTriggers) + additional = L" (delayed, trigger)"; + else if (serviceItem->DelayedStart) + additional = L" (delayed)"; + else if (serviceItem->HasTriggers) + additional = L" (trigger)"; + + if (additional) + PhInitFormatS(&format[1], additional); + + if (PhFormatToBuffer(format, 1 + (additional ? 1 : 0), node->StartTypeText, + sizeof(node->StartTypeText), &returnLength)) + { + getCellText->Text.Buffer = node->StartTypeText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); // minus null terminator + } + } + break; + case PHSVTLC_PID: + PhInitializeStringRefLongHint(&getCellText->Text, serviceItem->ProcessIdString); + break; + case PHSVTLC_BINARYPATH: + PhpUpdateServiceNodeConfig(node); + getCellText->Text = PhGetStringRef(node->BinaryPath); + break; + case PHSVTLC_ERRORCONTROL: + PhInitializeStringRefLongHint(&getCellText->Text, PhGetServiceErrorControlString(serviceItem->ErrorControl)); + break; + case PHSVTLC_GROUP: + PhpUpdateServiceNodeConfig(node); + getCellText->Text = PhGetStringRef(node->LoadOrderGroup); + break; + case PHSVTLC_DESCRIPTION: + PhpUpdateServiceNodeDescription(node); + getCellText->Text = PhGetStringRef(node->Description); + break; + case PHSVTLC_KEYMODIFIEDTIME: + PhpUpdateServiceNodeKey(node); + + if (node->KeyLastWriteTime.QuadPart != 0) + { + SYSTEMTIME systemTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &node->KeyLastWriteTime); + PhMoveReference(&node->KeyModifiedTimeText, PhFormatDateTime(&systemTime)); + getCellText->Text = node->KeyModifiedTimeText->sr; + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeIcon: + { + PPH_TREENEW_GET_NODE_ICON getNodeIcon = Parameter1; + + node = (PPH_SERVICE_NODE)getNodeIcon->Node; + + if (!ServiceIconsLoaded) + { + ServiceApplicationIcon = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_PHAPPLICATION)); + ServiceApplicationGoIcon = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_PHAPPLICATIONGO)); + ServiceCogIcon = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_COG)); + ServiceCogGoIcon = PH_LOAD_SHARED_ICON_SMALL(MAKEINTRESOURCE(IDI_COGGO)); + + ServiceIconsLoaded = TRUE; + } + + if (node->ServiceItem->Type == SERVICE_KERNEL_DRIVER || node->ServiceItem->Type == SERVICE_FILE_SYSTEM_DRIVER) + { + if (node->ServiceItem->State == SERVICE_RUNNING) + getNodeIcon->Icon = ServiceCogGoIcon; + else + getNodeIcon->Icon = ServiceCogIcon; + } + else + { + if (node->ServiceItem->State == SERVICE_RUNNING) + getNodeIcon->Icon = ServiceApplicationGoIcon; + else + getNodeIcon->Icon = ServiceApplicationIcon; + } + + getNodeIcon->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + + node = (PPH_SERVICE_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0) + return FALSE; + + if (!node->TooltipText) + node->TooltipText = PhGetServiceTooltipText(node->ServiceItem); + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->MaximumWidth = 550; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &ServiceTreeListSortColumn, &ServiceTreeListSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_SERVICE_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(ServiceTreeListHandle, 0, -1); + break; + case VK_DELETE: + SendMessage(PhMainWndHandle, WM_COMMAND, ID_SERVICE_DELETE, 0); + break; + case VK_RETURN: + if (GetKeyState(VK_CONTROL) >= 0) + SendMessage(PhMainWndHandle, WM_COMMAND, ID_SERVICE_PROPERTIES, 0); + else + SendMessage(PhMainWndHandle, WM_COMMAND, ID_SERVICE_OPENFILELOCATION, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = AscendingSortOrder; + PhInitializeTreeNewColumnMenu(&data); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(PhMainWndHandle, WM_COMMAND, ID_SERVICE_PROPERTIES, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + PhShowServiceContextMenu(contextMenu); + } + return TRUE; + } + + return FALSE; +} + +PPH_SERVICE_ITEM PhGetSelectedServiceItem( + VOID + ) +{ + PPH_SERVICE_ITEM serviceItem = NULL; + ULONG i; + + for (i = 0; i < ServiceNodeList->Count; i++) + { + PPH_SERVICE_NODE node = ServiceNodeList->Items[i]; + + if (node->Node.Selected) + { + serviceItem = node->ServiceItem; + break; + } + } + + return serviceItem; +} + +VOID PhGetSelectedServiceItems( + _Out_ PPH_SERVICE_ITEM **Services, + _Out_ PULONG NumberOfServices + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < ServiceNodeList->Count; i++) + { + PPH_SERVICE_NODE node = ServiceNodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->ServiceItem); + } + + *NumberOfServices = (ULONG)array.Count; + *Services = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllServiceNodes( + VOID + ) +{ + TreeNew_DeselectRange(ServiceTreeListHandle, 0, -1); +} + +VOID PhSelectAndEnsureVisibleServiceNode( + _In_ PPH_SERVICE_NODE ServiceNode + ) +{ + PhDeselectAllServiceNodes(); + + if (!ServiceNode->Node.Visible) + return; + + TreeNew_SetFocusNode(ServiceTreeListHandle, &ServiceNode->Node); + TreeNew_SetMarkNode(ServiceTreeListHandle, &ServiceNode->Node); + TreeNew_SelectRange(ServiceTreeListHandle, ServiceNode->Node.Index, ServiceNode->Node.Index); + TreeNew_EnsureVisible(ServiceTreeListHandle, &ServiceNode->Node); +} + +VOID PhCopyServiceList( + VOID + ) +{ + PPH_STRING text; + + text = PhGetTreeNewText(ServiceTreeListHandle, 0); + PhSetClipboardString(ServiceTreeListHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID PhWriteServiceList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ) +{ + PPH_LIST lines; + ULONG i; + + lines = PhGetGenericTreeNewLines(ServiceTreeListHandle, Mode); + + for (i = 0; i < lines->Count; i++) + { + PPH_STRING line; + + line = lines->Items[i]; + PhWriteStringAsUtf8FileStream(FileStream, &line->sr); + PhDereferenceObject(line); + PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n"); + } + + PhDereferenceObject(lines); +} diff --git a/ProcessHacker/srvprp.c b/ProcessHacker/srvprp.c new file mode 100644 index 0000000..1739442 --- /dev/null +++ b/ProcessHacker/srvprp.c @@ -0,0 +1,579 @@ +/* + * Process Hacker - + * service properties + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _SERVICE_PROPERTIES_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + BOOLEAN Ready; + BOOLEAN Dirty; + + BOOLEAN OldDelayedStart; +} SERVICE_PROPERTIES_CONTEXT, *PSERVICE_PROPERTIES_CONTEXT; + +INT_PTR CALLBACK PhpServiceGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static NTSTATUS PhpOpenService( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + SC_HANDLE serviceHandle; + + if (!(serviceHandle = PhOpenService( + ((PPH_SERVICE_ITEM)Context)->Name->Buffer, + DesiredAccess + ))) + return PhGetLastWin32ErrorAsNtStatus(); + + *Handle = serviceHandle; + + return STATUS_SUCCESS; +} + +static _Callback_ NTSTATUS PhpSetServiceSecurity( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PPH_STD_OBJECT_SECURITY stdObjectSecurity; + + stdObjectSecurity = (PPH_STD_OBJECT_SECURITY)Context; + + status = PhStdSetObjectSecurity(SecurityDescriptor, SecurityInformation, Context); + + if ((status == STATUS_ACCESS_DENIED || status == NTSTATUS_FROM_WIN32(ERROR_ACCESS_DENIED)) && !PhGetOwnTokenAttributes().Elevated) + { + // Elevate using phsvc. + if (PhUiConnectToPhSvc(NULL, FALSE)) + { + status = PhSvcCallSetServiceSecurity( + ((PPH_SERVICE_ITEM)stdObjectSecurity->Context)->Name->Buffer, + SecurityInformation, + SecurityDescriptor + ); + PhUiDisconnectFromPhSvc(); + } + } + + return status; +} + +VOID PhShowServiceProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[32]; + SERVICE_PROPERTIES_CONTEXT context; + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = ServiceItem->Name->Buffer; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // General + + memset(&context, 0, sizeof(SERVICE_PROPERTIES_CONTEXT)); + context.ServiceItem = ServiceItem; + context.Ready = FALSE; + context.Dirty = FALSE; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVGENERAL); + propSheetPage.pfnDlgProc = PhpServiceGeneralDlgProc; + propSheetPage.lParam = (LPARAM)&context; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Security + + stdObjectSecurity.OpenObject = PhpOpenService; + stdObjectSecurity.ObjectType = L"Service"; + stdObjectSecurity.Context = ServiceItem; + + if (PhGetAccessEntries(L"Service", &accessEntries, &numberOfAccessEntries)) + { + pages[propSheetHeader.nPages++] = PhCreateSecurityPage( + ServiceItem->Name->Buffer, + PhStdGetObjectSecurity, + PhpSetServiceSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + + if (PhPluginsEnabled) + { + PH_PLUGIN_OBJECT_PROPERTIES objectProperties; + + objectProperties.Parameter = ServiceItem; + objectProperties.NumberOfPages = propSheetHeader.nPages; + objectProperties.MaximumNumberOfPages = sizeof(pages) / sizeof(HPROPSHEETPAGE); + objectProperties.Pages = pages; + + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackServicePropertiesInitializing), &objectProperties); + + propSheetHeader.nPages = objectProperties.NumberOfPages; + } + + PhModalPropertySheet(&propSheetHeader); +} + +static VOID PhpRefreshControls( + _In_ HWND hwndDlg + ) +{ + if ( + WindowsVersion >= WINDOWS_VISTA && + PhEqualString2(PhaGetDlgItemText(hwndDlg, IDC_STARTTYPE), L"Auto Start", FALSE) + ) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), TRUE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), FALSE); + } +} + +INT_PTR CALLBACK PhpServiceGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PSERVICE_PROPERTIES_CONTEXT context = (PSERVICE_PROPERTIES_CONTEXT)propSheetPage->lParam; + PPH_SERVICE_ITEM serviceItem = context->ServiceItem; + SC_HANDLE serviceHandle; + ULONG startType; + ULONG errorControl; + PPH_STRING serviceDll; + + // HACK + PhCenterWindow(GetParent(hwndDlg), GetParent(GetParent(hwndDlg))); + + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)context); + + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_TYPE), PhServiceTypeStrings, + sizeof(PhServiceTypeStrings) / sizeof(WCHAR *)); + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_STARTTYPE), PhServiceStartTypeStrings, + sizeof(PhServiceStartTypeStrings) / sizeof(WCHAR *)); + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_ERRORCONTROL), PhServiceErrorControlStrings, + sizeof(PhServiceErrorControlStrings) / sizeof(WCHAR *)); + + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, serviceItem->DisplayName->Buffer); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_TYPE), + PhGetServiceTypeString(serviceItem->Type), FALSE); + + startType = serviceItem->StartType; + errorControl = serviceItem->ErrorControl; + serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_CONFIG); + + if (serviceHandle) + { + LPQUERY_SERVICE_CONFIG config; + PPH_STRING description; + BOOLEAN delayedStart; + + if (config = PhGetServiceConfig(serviceHandle)) + { + SetDlgItemText(hwndDlg, IDC_GROUP, config->lpLoadOrderGroup); + SetDlgItemText(hwndDlg, IDC_BINARYPATH, config->lpBinaryPathName); + SetDlgItemText(hwndDlg, IDC_USERACCOUNT, config->lpServiceStartName); + + if (startType != config->dwStartType || errorControl != config->dwErrorControl) + { + startType = config->dwStartType; + errorControl = config->dwErrorControl; + PhMarkNeedsConfigUpdateServiceItem(serviceItem); + } + + PhFree(config); + } + + if (description = PhGetServiceDescription(serviceHandle)) + { + SetDlgItemText(hwndDlg, IDC_DESCRIPTION, description->Buffer); + PhDereferenceObject(description); + } + + if ( + WindowsVersion >= WINDOWS_VISTA && + PhGetServiceDelayedAutoStart(serviceHandle, &delayedStart) + ) + { + context->OldDelayedStart = delayedStart; + + if (delayedStart) + Button_SetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART), BST_CHECKED); + } + + CloseServiceHandle(serviceHandle); + } + + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_STARTTYPE), + PhGetServiceStartTypeString(startType), FALSE); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_ERRORCONTROL), + PhGetServiceErrorControlString(errorControl), FALSE); + + SetDlgItemText(hwndDlg, IDC_PASSWORD, L"password"); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK), BST_UNCHECKED); + + if (NT_SUCCESS(PhGetServiceDllParameter(&serviceItem->Name->sr, &serviceDll))) + { + SetDlgItemText(hwndDlg, IDC_SERVICEDLL, serviceDll->Buffer); + PhDereferenceObject(serviceDll); + } + else + { + SetDlgItemText(hwndDlg, IDC_SERVICEDLL, L"N/A"); + } + + PhpRefreshControls(hwndDlg); + + context->Ready = TRUE; + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + PSERVICE_PROPERTIES_CONTEXT context = + (PSERVICE_PROPERTIES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + // Workaround for property sheet + multiline edit: http://support.microsoft.com/kb/130765 + + SendMessage(GetParent(hwndDlg), uMsg, wParam, lParam); + } + break; + case IDC_PASSWORD: + { + if (HIWORD(wParam) == EN_CHANGE) + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK), BST_CHECKED); + } + } + break; + case IDC_DELAYEDSTART: + { + context->Dirty = TRUE; + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Executable files (*.exe;*.sys)", L"*.exe;*.sys" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING commandLine; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + commandLine = PhaGetDlgItemText(hwndDlg, IDC_BINARYPATH); + + if (context->ServiceItem->Type & SERVICE_WIN32) + { + PH_STRINGREF dummyFileName; + PH_STRINGREF dummyArguments; + + if (!PhParseCommandLineFuzzy(&commandLine->sr, &dummyFileName, &dummyArguments, &fileName)) + fileName = NULL; + + if (!fileName) + PhSwapReference(&fileName, commandLine); + } + else + { + fileName = PhGetFileName(commandLine); + } + + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + PhDereferenceObject(fileName); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PhGetFileDialogFileName(fileDialog); + SetDlgItemText(hwndDlg, IDC_BINARYPATH, fileName->Buffer); + PhDereferenceObject(fileName); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + + switch (HIWORD(wParam)) + { + case EN_CHANGE: + case CBN_SELCHANGE: + { + PhpRefreshControls(hwndDlg); + + if (context->Ready) + context->Dirty = TRUE; + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_STARTTYPE)); + } + return TRUE; + case PSN_KILLACTIVE: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); + } + return TRUE; + case PSN_APPLY: + { + NTSTATUS status; + PSERVICE_PROPERTIES_CONTEXT context = + (PSERVICE_PROPERTIES_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + PPH_SERVICE_ITEM serviceItem = context->ServiceItem; + SC_HANDLE serviceHandle; + PPH_STRING newServiceTypeString; + PPH_STRING newServiceStartTypeString; + PPH_STRING newServiceErrorControlString; + ULONG newServiceType; + ULONG newServiceStartType; + ULONG newServiceErrorControl; + PPH_STRING newServiceGroup; + PPH_STRING newServiceBinaryPath; + PPH_STRING newServiceUserAccount; + PPH_STRING newServicePassword; + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + + if (!context->Dirty) + { + return TRUE; + } + + newServiceTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_TYPE))); + newServiceStartTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_STARTTYPE))); + newServiceErrorControlString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_ERRORCONTROL))); + newServiceType = PhGetServiceTypeInteger(newServiceTypeString->Buffer); + newServiceStartType = PhGetServiceStartTypeInteger(newServiceStartTypeString->Buffer); + newServiceErrorControl = PhGetServiceErrorControlInteger(newServiceErrorControlString->Buffer); + + newServiceGroup = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_GROUP))); + newServiceBinaryPath = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_BINARYPATH))); + newServiceUserAccount = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_USERACCOUNT))); + + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_PASSWORDCHECK)) == BST_CHECKED) + { + newServicePassword = PhGetWindowText(GetDlgItem(hwndDlg, IDC_PASSWORD)); + } + else + { + newServicePassword = NULL; + } + + if (newServiceType == SERVICE_KERNEL_DRIVER && newServiceUserAccount->Length == 0) + { + newServiceUserAccount = NULL; + } + + serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_CHANGE_CONFIG); + + if (serviceHandle) + { + if (ChangeServiceConfig( + serviceHandle, + newServiceType, + newServiceStartType, + newServiceErrorControl, + newServiceBinaryPath->Buffer, + newServiceGroup->Buffer, + NULL, + NULL, + PhGetString(newServiceUserAccount), + PhGetString(newServicePassword), + NULL + )) + { + if (WindowsVersion >= WINDOWS_VISTA) + { + BOOLEAN newDelayedStart; + + newDelayedStart = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART)) == BST_CHECKED; + + if (newDelayedStart != context->OldDelayedStart) + { + PhSetServiceDelayedAutoStart(serviceHandle, newDelayedStart); + } + } + + PhMarkNeedsConfigUpdateServiceItem(serviceItem); + + CloseServiceHandle(serviceHandle); + } + else + { + CloseServiceHandle(serviceHandle); + goto ErrorCase; + } + } + else + { + if (GetLastError() == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated) + { + // Elevate using phsvc. + if (PhUiConnectToPhSvc(hwndDlg, FALSE)) + { + if (NT_SUCCESS(status = PhSvcCallChangeServiceConfig( + serviceItem->Name->Buffer, + newServiceType, + newServiceStartType, + newServiceErrorControl, + newServiceBinaryPath->Buffer, + newServiceGroup->Buffer, + NULL, + NULL, + PhGetString(newServiceUserAccount), + PhGetString(newServicePassword), + NULL + ))) + { + if (WindowsVersion >= WINDOWS_VISTA) + { + BOOLEAN newDelayedStart; + + newDelayedStart = Button_GetCheck(GetDlgItem(hwndDlg, IDC_DELAYEDSTART)) == BST_CHECKED; + + if (newDelayedStart != context->OldDelayedStart) + { + SERVICE_DELAYED_AUTO_START_INFO info; + + info.fDelayedAutostart = newDelayedStart; + PhSvcCallChangeServiceConfig2( + serviceItem->Name->Buffer, + SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + &info + ); + } + } + + PhMarkNeedsConfigUpdateServiceItem(serviceItem); + } + + PhUiDisconnectFromPhSvc(); + + if (!NT_SUCCESS(status)) + { + SetLastError(PhNtStatusToDosError(status)); + goto ErrorCase; + } + } + else + { + // User cancelled elevation. + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + } + else + { + goto ErrorCase; + } + } + + goto Cleanup; +ErrorCase: + if (PhShowMessage( + hwndDlg, + MB_ICONERROR | MB_RETRYCANCEL, + L"Unable to change service configuration: %s", + PH_AUTO_T(PH_STRING, PhGetWin32Message(GetLastError()))->Buffer + ) == IDRETRY) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + +Cleanup: + if (newServicePassword) + { + RtlSecureZeroMemory(newServicePassword->Buffer, newServicePassword->Length); + PhDereferenceObject(newServicePassword); + } + } + return TRUE; + } + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/srvprv.c b/ProcessHacker/srvprv.c new file mode 100644 index 0000000..bbfe6a5 --- /dev/null +++ b/ProcessHacker/srvprv.c @@ -0,0 +1,1040 @@ +/* + * Process Hacker - + * service provider + * + * Copyright (C) 2009-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +typedef DWORD (WINAPI *_NotifyServiceStatusChangeW)( + _In_ SC_HANDLE hService, + _In_ DWORD dwNotifyMask, + _In_ PSERVICE_NOTIFYW pNotifyBuffer + ); + +typedef struct _PHP_SERVICE_NAME_ENTRY +{ + PH_HASH_ENTRY HashEntry; + PH_STRINGREF Name; + ENUM_SERVICE_STATUS_PROCESS *ServiceEntry; +} PHP_SERVICE_NAME_ENTRY, *PPHP_SERVICE_NAME_ENTRY; + +typedef enum _PHP_SERVICE_NOTIFY_STATE +{ + SnNone, + SnAdding, + SnRemoving, + SnNotify +} PHP_SERVICE_NOTIFY_STATE; + +typedef struct _PHP_SERVICE_NOTIFY_CONTEXT +{ + LIST_ENTRY ListEntry; + SC_HANDLE ServiceHandle; + PPH_STRING ServiceName; // Valid only when adding + BOOLEAN IsServiceManager; + PHP_SERVICE_NOTIFY_STATE State; + SERVICE_NOTIFY Buffer; +} PHP_SERVICE_NOTIFY_CONTEXT, *PPHP_SERVICE_NOTIFY_CONTEXT; + +VOID NTAPI PhpServiceItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +BOOLEAN NTAPI PhpServiceHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpServiceHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpAddProcessItemService( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_SERVICE_ITEM ServiceItem + ); + +VOID PhpRemoveProcessItemService( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_SERVICE_ITEM ServiceItem + ); + +VOID PhpInitializeServiceNonPoll( + VOID + ); + +PPH_OBJECT_TYPE PhServiceItemType; + +PPH_HASHTABLE PhServiceHashtable; +PH_QUEUED_LOCK PhServiceHashtableLock = PH_QUEUED_LOCK_INIT; + +PHAPPAPI PH_CALLBACK_DECLARE(PhServiceAddedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhServiceModifiedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhServiceRemovedEvent); +PHAPPAPI PH_CALLBACK_DECLARE(PhServicesUpdatedEvent); + +BOOLEAN PhEnableServiceNonPoll = FALSE; +static BOOLEAN PhpNonPollInitialized = FALSE; +static BOOLEAN PhpNonPollActive = FALSE; +static HANDLE PhpNonPollThreadHandle; +static ULONG PhpNonPollGate; +static _NotifyServiceStatusChangeW NotifyServiceStatusChangeW_I; +static HANDLE PhpNonPollEventHandle; +static PH_QUEUED_LOCK PhpNonPollServiceListLock = PH_QUEUED_LOCK_INIT; +static LIST_ENTRY PhpNonPollServiceListHead; +static LIST_ENTRY PhpNonPollServicePendingListHead; + +BOOLEAN PhServiceProviderInitialization( + VOID + ) +{ + PhServiceItemType = PhCreateObjectType(L"ServiceItem", 0, PhpServiceItemDeleteProcedure); + PhServiceHashtable = PhCreateHashtable( + sizeof(PPH_SERVICE_ITEM), + PhpServiceHashtableEqualFunction, + PhpServiceHashtableHashFunction, + 40 + ); + + return TRUE; +} + +PPH_SERVICE_ITEM PhCreateServiceItem( + _In_opt_ LPENUM_SERVICE_STATUS_PROCESS Information + ) +{ + PPH_SERVICE_ITEM serviceItem; + + serviceItem = PhCreateObject( + PhEmGetObjectSize(EmServiceItemType, sizeof(PH_SERVICE_ITEM)), + PhServiceItemType + ); + memset(serviceItem, 0, sizeof(PH_SERVICE_ITEM)); + + if (Information) + { + serviceItem->Name = PhCreateString(Information->lpServiceName); + serviceItem->Key = serviceItem->Name->sr; + serviceItem->DisplayName = PhCreateString(Information->lpDisplayName); + serviceItem->Type = Information->ServiceStatusProcess.dwServiceType; + serviceItem->State = Information->ServiceStatusProcess.dwCurrentState; + serviceItem->ControlsAccepted = Information->ServiceStatusProcess.dwControlsAccepted; + serviceItem->Flags = Information->ServiceStatusProcess.dwServiceFlags; + serviceItem->ProcessId = UlongToHandle(Information->ServiceStatusProcess.dwProcessId); + + if (serviceItem->ProcessId) + PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId)); + } + + PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectCreate); + + return serviceItem; +} + +VOID PhpServiceItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)Object; + + PhEmCallObjectOperation(EmServiceItemType, serviceItem, EmObjectDelete); + + if (serviceItem->Name) PhDereferenceObject(serviceItem->Name); + if (serviceItem->DisplayName) PhDereferenceObject(serviceItem->DisplayName); +} + +BOOLEAN PhpServiceHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)Entry1; + PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)Entry2; + + return PhEqualStringRef(&serviceItem1->Key, &serviceItem2->Key, TRUE); +} + +ULONG PhpServiceHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPH_SERVICE_ITEM serviceItem = *(PPH_SERVICE_ITEM *)Entry; + + return PhHashStringRef(&serviceItem->Key, TRUE); +} + +PPH_SERVICE_ITEM PhpLookupServiceItem( + _In_ PPH_STRINGREF Name + ) +{ + PH_SERVICE_ITEM lookupServiceItem; + PPH_SERVICE_ITEM lookupServiceItemPtr = &lookupServiceItem; + PPH_SERVICE_ITEM *serviceItem; + + // Construct a temporary service item for the lookup. + lookupServiceItem.Key = *Name; + + serviceItem = (PPH_SERVICE_ITEM *)PhFindEntryHashtable( + PhServiceHashtable, + &lookupServiceItemPtr + ); + + if (serviceItem) + return *serviceItem; + else + return NULL; +} + +PPH_SERVICE_ITEM PhReferenceServiceItem( + _In_ PWSTR Name + ) +{ + PPH_SERVICE_ITEM serviceItem; + PH_STRINGREF key; + + PhInitializeStringRef(&key, Name); + + PhAcquireQueuedLockShared(&PhServiceHashtableLock); + + serviceItem = PhpLookupServiceItem(&key); + + if (serviceItem) + PhReferenceObject(serviceItem); + + PhReleaseQueuedLockShared(&PhServiceHashtableLock); + + return serviceItem; +} + +VOID PhMarkNeedsConfigUpdateServiceItem( + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + ServiceItem->NeedsConfigUpdate = TRUE; + + if (PhEnableServiceNonPoll) + PhpNonPollGate = 1; +} + +VOID PhpRemoveServiceItem( + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + PhRemoveEntryHashtable(PhServiceHashtable, &ServiceItem); + PhDereferenceObject(ServiceItem); +} + +PH_SERVICE_CHANGE PhGetServiceChange( + _In_ PPH_SERVICE_MODIFIED_DATA Data + ) +{ + if ( + ( + Data->OldService.State == SERVICE_STOPPED || + Data->OldService.State == SERVICE_START_PENDING + ) && + Data->Service->State == SERVICE_RUNNING + ) + { + return ServiceStarted; + } + + if ( + ( + Data->OldService.State == SERVICE_PAUSED || + Data->OldService.State == SERVICE_CONTINUE_PENDING + ) && + Data->Service->State == SERVICE_RUNNING + ) + { + return ServiceContinued; + } + + if ( + ( + Data->OldService.State == SERVICE_RUNNING || + Data->OldService.State == SERVICE_PAUSE_PENDING + ) && + Data->Service->State == SERVICE_PAUSED + ) + { + return ServicePaused; + } + + if ( + ( + Data->OldService.State == SERVICE_RUNNING || + Data->OldService.State == SERVICE_STOP_PENDING + ) && + Data->Service->State == SERVICE_STOPPED + ) + { + return ServiceStopped; + } + + return -1; +} + +VOID PhUpdateProcessItemServices( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_SERVICE_ITEM *serviceItem; + + // We don't need to lock as long as the service provider + // never runs concurrently with the process provider. This + // is currently true. + + PhBeginEnumHashtable(PhServiceHashtable, &enumContext); + + while (serviceItem = PhNextEnumHashtable(&enumContext)) + { + if ( + (*serviceItem)->PendingProcess && + (*serviceItem)->ProcessId == ProcessItem->ProcessId + ) + { + PhpAddProcessItemService(ProcessItem, *serviceItem); + } + } +} + +VOID PhpAddProcessItemService( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock); + + if (!ProcessItem->ServiceList) + ProcessItem->ServiceList = PhCreatePointerList(2); + + if (!PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem)) + { + PhReferenceObject(ServiceItem); + PhAddItemPointerList(ProcessItem->ServiceList, ServiceItem); + } + + PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock); + + ServiceItem->PendingProcess = FALSE; + ProcessItem->JustProcessed = 1; +} + +VOID PhpRemoveProcessItemService( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + HANDLE pointerHandle; + + if (!ProcessItem->ServiceList) + return; + + PhAcquireQueuedLockExclusive(&ProcessItem->ServiceListLock); + + if (pointerHandle = PhFindItemPointerList(ProcessItem->ServiceList, ServiceItem)) + { + PhRemoveItemPointerList(ProcessItem->ServiceList, pointerHandle); + PhDereferenceObject(ServiceItem); + } + + PhReleaseQueuedLockExclusive(&ProcessItem->ServiceListLock); + + ProcessItem->JustProcessed = 1; +} + +VOID PhpUpdateServiceItemConfig( + _In_ SC_HANDLE ScManagerHandle, + _In_ PPH_SERVICE_ITEM ServiceItem + ) +{ + SC_HANDLE serviceHandle; + + serviceHandle = OpenService(ScManagerHandle, ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG); + + if (serviceHandle) + { + LPQUERY_SERVICE_CONFIG config; + SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo; + ULONG returnLength; + PSERVICE_TRIGGER_INFO triggerInfo; + + config = PhGetServiceConfig(serviceHandle); + + if (config) + { + ServiceItem->StartType = config->dwStartType; + ServiceItem->ErrorControl = config->dwErrorControl; + + PhFree(config); + } + + if (QueryServiceConfig2( + serviceHandle, + SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + (BYTE *)&delayedAutoStartInfo, + sizeof(SERVICE_DELAYED_AUTO_START_INFO), + &returnLength + )) + { + ServiceItem->DelayedStart = delayedAutoStartInfo.fDelayedAutostart; + } + else + { + ServiceItem->DelayedStart = FALSE; + } + + if (triggerInfo = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO)) + { + ServiceItem->HasTriggers = triggerInfo->cTriggers != 0; + PhFree(triggerInfo); + } + else + { + ServiceItem->HasTriggers = FALSE; + } + + CloseServiceHandle(serviceHandle); + } +} + +static BOOLEAN PhpCompareServiceNameEntry( + _In_ PPHP_SERVICE_NAME_ENTRY Value1, + _In_ PPHP_SERVICE_NAME_ENTRY Value2 + ) +{ + return PhEqualStringRef(&Value1->Name, &Value2->Name, TRUE); +} + +static ULONG PhpHashServiceNameEntry( + _In_ PPHP_SERVICE_NAME_ENTRY Value + ) +{ + return PhHashStringRef(&Value->Name, TRUE); +} + +VOID PhServiceProviderUpdate( + _In_ PVOID Object + ) +{ + static SC_HANDLE scManagerHandle = NULL; + static ULONG runCount = 0; + + static PPH_HASH_ENTRY nameHashSet[256]; + static PPHP_SERVICE_NAME_ENTRY nameEntries = NULL; + static ULONG nameEntriesCount; + static ULONG nameEntriesAllocated = 0; + + LPENUM_SERVICE_STATUS_PROCESS services; + ULONG numberOfServices; + ULONG i; + PPH_HASH_ENTRY hashEntry; + + // We always execute the first run, and we only initialize non-polling after the first run. + if (PhEnableServiceNonPoll && runCount != 0) + { + if (!PhpNonPollInitialized) + { + if (WindowsVersion >= WINDOWS_VISTA) + { + PhpInitializeServiceNonPoll(); + } + + PhpNonPollInitialized = TRUE; + } + + if (PhpNonPollActive) + { + if (InterlockedExchange(&PhpNonPollGate, 0) == 0) + { + // Non-poll gate is closed; skip all processing. + goto UpdateEnd; + } + } + } + + if (!scManagerHandle) + { + scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE); + + if (!scManagerHandle) + return; + } + + services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices); + + if (!services) + return; + + // Build a hash set containing the service names. + + // This has caused a massive decrease in background CPU usage, and + // is certainly much better than the quadratic-time string comparisons + // we were doing before (in the "Look for dead services" section). + + nameEntriesCount = 0; + + if (nameEntriesAllocated < numberOfServices) + { + nameEntriesAllocated = numberOfServices + 32; + + if (nameEntries) PhFree(nameEntries); + nameEntries = PhAllocate(sizeof(PHP_SERVICE_NAME_ENTRY) * nameEntriesAllocated); + } + + PhInitializeHashSet(nameHashSet, PH_HASH_SET_SIZE(nameHashSet)); + + for (i = 0; i < numberOfServices; i++) + { + PPHP_SERVICE_NAME_ENTRY entry; + + entry = &nameEntries[nameEntriesCount++]; + PhInitializeStringRefLongHint(&entry->Name, services[i].lpServiceName); + entry->ServiceEntry = &services[i]; + PhAddEntryHashSet( + nameHashSet, + PH_HASH_SET_SIZE(nameHashSet), + &entry->HashEntry, + PhpHashServiceNameEntry(entry) + ); + } + + // Look for dead services. + { + PPH_LIST servicesToRemove = NULL; + PH_HASHTABLE_ENUM_CONTEXT enumContext; + PPH_SERVICE_ITEM *serviceItem; + + PhBeginEnumHashtable(PhServiceHashtable, &enumContext); + + while (serviceItem = PhNextEnumHashtable(&enumContext)) + { + BOOLEAN found = FALSE; + PHP_SERVICE_NAME_ENTRY lookupNameEntry; + + // Check if the service still exists. + + lookupNameEntry.Name = (*serviceItem)->Name->sr; + hashEntry = PhFindEntryHashSet( + nameHashSet, + PH_HASH_SET_SIZE(nameHashSet), + PhpHashServiceNameEntry(&lookupNameEntry) + ); + + for (; hashEntry; hashEntry = hashEntry->Next) + { + PPHP_SERVICE_NAME_ENTRY nameEntry; + + nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry); + + if (PhpCompareServiceNameEntry(&lookupNameEntry, nameEntry)) + { + found = TRUE; + break; + } + } + + if (!found) + { + // Remove the service from its process. + if ((*serviceItem)->ProcessId) + { + PPH_PROCESS_ITEM processItem; + + processItem = PhReferenceProcessItem((HANDLE)(*serviceItem)->ProcessId); + + if (processItem) + { + PhpRemoveProcessItemService(processItem, *serviceItem); + PhDereferenceObject(processItem); + } + } + + // Raise the service removed event. + PhInvokeCallback(&PhServiceRemovedEvent, *serviceItem); + + if (!servicesToRemove) + servicesToRemove = PhCreateList(2); + + PhAddItemList(servicesToRemove, *serviceItem); + } + } + + if (servicesToRemove) + { + PhAcquireQueuedLockExclusive(&PhServiceHashtableLock); + + for (i = 0; i < servicesToRemove->Count; i++) + { + PhpRemoveServiceItem((PPH_SERVICE_ITEM)servicesToRemove->Items[i]); + } + + PhReleaseQueuedLockExclusive(&PhServiceHashtableLock); + PhDereferenceObject(servicesToRemove); + } + } + + // Look for new services and update existing ones. + for (i = 0; i < PH_HASH_SET_SIZE(nameHashSet); i++) + { + for (hashEntry = nameHashSet[i]; hashEntry; hashEntry = hashEntry->Next) + { + PPH_SERVICE_ITEM serviceItem; + PPHP_SERVICE_NAME_ENTRY nameEntry; + ENUM_SERVICE_STATUS_PROCESS *serviceEntry; + + nameEntry = CONTAINING_RECORD(hashEntry, PHP_SERVICE_NAME_ENTRY, HashEntry); + serviceEntry = nameEntry->ServiceEntry; + serviceItem = PhpLookupServiceItem(&nameEntry->Name); + + if (!serviceItem) + { + // Create the service item and fill in basic information. + + serviceItem = PhCreateServiceItem(serviceEntry); + + PhpUpdateServiceItemConfig(scManagerHandle, serviceItem); + + // Add the service to its process, if appropriate. + if ( + ( + serviceItem->State == SERVICE_RUNNING || + serviceItem->State == SERVICE_PAUSED + ) && + serviceItem->ProcessId + ) + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(serviceItem->ProcessId)) + { + PhpAddProcessItemService(processItem, serviceItem); + PhDereferenceObject(processItem); + } + else + { + // The process doesn't exist yet (to us). Set the pending + // flag and when the process is added this will be + // fixed. + serviceItem->PendingProcess = TRUE; + } + } + + // Add the service item to the hashtable. + PhAcquireQueuedLockExclusive(&PhServiceHashtableLock); + PhAddEntryHashtable(PhServiceHashtable, &serviceItem); + PhReleaseQueuedLockExclusive(&PhServiceHashtableLock); + + // Raise the service added event. + PhInvokeCallback(&PhServiceAddedEvent, serviceItem); + } + else + { + if ( + serviceItem->Type != serviceEntry->ServiceStatusProcess.dwServiceType || + serviceItem->State != serviceEntry->ServiceStatusProcess.dwCurrentState || + serviceItem->ControlsAccepted != serviceEntry->ServiceStatusProcess.dwControlsAccepted || + serviceItem->ProcessId != UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId) || + serviceItem->NeedsConfigUpdate + ) + { + PH_SERVICE_MODIFIED_DATA serviceModifiedData; + PH_SERVICE_CHANGE serviceChange; + + // The service has been "modified". + + serviceModifiedData.Service = serviceItem; + memset(&serviceModifiedData.OldService, 0, sizeof(PH_SERVICE_ITEM)); + serviceModifiedData.OldService.Type = serviceItem->Type; + serviceModifiedData.OldService.State = serviceItem->State; + serviceModifiedData.OldService.ControlsAccepted = serviceItem->ControlsAccepted; + serviceModifiedData.OldService.ProcessId = serviceItem->ProcessId; + + // Update the service item. + serviceItem->Type = serviceEntry->ServiceStatusProcess.dwServiceType; + serviceItem->State = serviceEntry->ServiceStatusProcess.dwCurrentState; + serviceItem->ControlsAccepted = serviceEntry->ServiceStatusProcess.dwControlsAccepted; + serviceItem->ProcessId = UlongToHandle(serviceEntry->ServiceStatusProcess.dwProcessId); + + if (serviceItem->ProcessId) + PhPrintUInt32(serviceItem->ProcessIdString, HandleToUlong(serviceItem->ProcessId)); + else + serviceItem->ProcessIdString[0] = 0; + + // Add/remove the service from its process. + + serviceChange = PhGetServiceChange(&serviceModifiedData); + + if ( + (serviceChange == ServiceStarted && serviceItem->ProcessId) || + (serviceChange == ServiceStopped && serviceModifiedData.OldService.ProcessId) + ) + { + PPH_PROCESS_ITEM processItem; + + if (serviceChange == ServiceStarted) + processItem = PhReferenceProcessItem(serviceItem->ProcessId); + else + processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId); + + if (processItem) + { + if (serviceChange == ServiceStarted) + PhpAddProcessItemService(processItem, serviceItem); + else + PhpRemoveProcessItemService(processItem, serviceItem); + + PhDereferenceObject(processItem); + } + else + { + if (serviceChange == ServiceStarted) + serviceItem->PendingProcess = TRUE; + else + serviceItem->PendingProcess = FALSE; + } + } + else if ( + serviceItem->State == SERVICE_RUNNING && + serviceItem->ProcessId != serviceModifiedData.OldService.ProcessId && + serviceItem->ProcessId + ) + { + PPH_PROCESS_ITEM processItem; + + // The service stopped and started, and the only change we have detected + // is in the process ID. + + if (processItem = PhReferenceProcessItem(serviceModifiedData.OldService.ProcessId)) + { + PhpRemoveProcessItemService(processItem, serviceItem); + PhDereferenceObject(processItem); + } + + if (processItem = PhReferenceProcessItem(serviceItem->ProcessId)) + { + PhpAddProcessItemService(processItem, serviceItem); + PhDereferenceObject(processItem); + } + else + { + serviceItem->PendingProcess = TRUE; + } + } + + // Do a config update if necessary. + if (serviceItem->NeedsConfigUpdate) + { + PhpUpdateServiceItemConfig(scManagerHandle, serviceItem); + serviceItem->NeedsConfigUpdate = FALSE; + } + + // Raise the service modified event. + PhInvokeCallback(&PhServiceModifiedEvent, &serviceModifiedData); + } + } + } + } + + PhFree(services); + +UpdateEnd: + PhInvokeCallback(&PhServicesUpdatedEvent, NULL); + runCount++; +} + +VOID CALLBACK PhpServiceNonPollScNotifyCallback( + _In_ PVOID pParameter + ) +{ + PSERVICE_NOTIFYW notifyBuffer = pParameter; + PPHP_SERVICE_NOTIFY_CONTEXT notifyContext = notifyBuffer->pContext; + + if (notifyBuffer->dwNotificationStatus == ERROR_SUCCESS) + { + if ((notifyBuffer->dwNotificationTriggered & (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED)) && + notifyBuffer->pszServiceNames) + { + PWSTR name; + SIZE_T nameLength; + + name = notifyBuffer->pszServiceNames; + + while (TRUE) + { + nameLength = PhCountStringZ(name); + + if (nameLength == 0) + break; + + if (name[0] == '/') + { + PPHP_SERVICE_NOTIFY_CONTEXT newNotifyContext; + + // Service creation + newNotifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + memset(newNotifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + newNotifyContext->State = SnAdding; + newNotifyContext->ServiceName = PhCreateString(name + 1); + InsertTailList(&PhpNonPollServicePendingListHead, &newNotifyContext->ListEntry); + } + + name += nameLength + 1; + } + + LocalFree(notifyBuffer->pszServiceNames); + } + + notifyContext->State = SnNotify; + RemoveEntryList(¬ifyContext->ListEntry); + InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); + } + else if (notifyBuffer->dwNotificationStatus == ERROR_SERVICE_MARKED_FOR_DELETE) + { + if (!notifyContext->IsServiceManager) + { + notifyContext->State = SnRemoving; + RemoveEntryList(¬ifyContext->ListEntry); + InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); + } + } + else + { + notifyContext->State = SnNotify; + RemoveEntryList(¬ifyContext->ListEntry); + InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); + } + + PhpNonPollGate = 1; + NtSetEvent(PhpNonPollEventHandle, NULL); +} + +VOID PhpDestroyServiceNotifyContext( + _In_ PPHP_SERVICE_NOTIFY_CONTEXT NotifyContext + ) +{ + if (NotifyContext->Buffer.pszServiceNames) + LocalFree(NotifyContext->Buffer.pszServiceNames); + + CloseServiceHandle(NotifyContext->ServiceHandle); + PhClearReference(&NotifyContext->ServiceName); + PhFree(NotifyContext); +} + +NTSTATUS PhpServiceNonPollThreadStart( + _In_ PVOID Parameter + ) +{ + ULONG result; + SC_HANDLE scManagerHandle; + LPENUM_SERVICE_STATUS_PROCESS services; + ULONG numberOfServices; + ULONG i; + PLIST_ENTRY listEntry; + PPHP_SERVICE_NOTIFY_CONTEXT notifyContext; + + if (!NT_SUCCESS(NtCreateEvent(&PhpNonPollEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) + { + PhpNonPollActive = FALSE; + PhpNonPollGate = 1; + return STATUS_UNSUCCESSFUL; + } + + while (TRUE) + { + scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); + + if (!scManagerHandle) + goto ErrorExit; + + InitializeListHead(&PhpNonPollServiceListHead); + InitializeListHead(&PhpNonPollServicePendingListHead); + + if (!(services = PhEnumServices(scManagerHandle, 0, 0, &numberOfServices))) + goto ErrorExit; + + for (i = 0; i < numberOfServices; i++) + { + SC_HANDLE serviceHandle; + + if (serviceHandle = OpenService(scManagerHandle, services[i].lpServiceName, SERVICE_QUERY_STATUS)) + { + notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + notifyContext->ServiceHandle = serviceHandle; + notifyContext->State = SnNotify; + InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); + } + } + + PhFree(services); + + notifyContext = PhAllocate(sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + memset(notifyContext, 0, sizeof(PHP_SERVICE_NOTIFY_CONTEXT)); + notifyContext->ServiceHandle = scManagerHandle; + notifyContext->IsServiceManager = TRUE; + notifyContext->State = SnNotify; + InsertTailList(&PhpNonPollServicePendingListHead, ¬ifyContext->ListEntry); + + while (TRUE) + { + BOOLEAN lagging = FALSE; + + listEntry = PhpNonPollServicePendingListHead.Flink; + + while (listEntry != &PhpNonPollServicePendingListHead) + { + notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); + listEntry = listEntry->Flink; + + switch (notifyContext->State) + { + case SnNone: + break; + case SnAdding: + notifyContext->ServiceHandle = + OpenService(scManagerHandle, notifyContext->ServiceName->Buffer, SERVICE_QUERY_STATUS); + + if (!notifyContext->ServiceHandle) + { + RemoveEntryList(¬ifyContext->ListEntry); + PhpDestroyServiceNotifyContext(notifyContext); + continue; + } + + PhClearReference(¬ifyContext->ServiceName); + notifyContext->State = SnNotify; + goto NotifyCase; + case SnRemoving: + RemoveEntryList(¬ifyContext->ListEntry); + PhpDestroyServiceNotifyContext(notifyContext); + break; + case SnNotify: +NotifyCase: + memset(¬ifyContext->Buffer, 0, sizeof(SERVICE_NOTIFY)); + notifyContext->Buffer.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE; + notifyContext->Buffer.pfnNotifyCallback = PhpServiceNonPollScNotifyCallback; + notifyContext->Buffer.pContext = notifyContext; + result = NotifyServiceStatusChangeW_I( + notifyContext->ServiceHandle, + notifyContext->IsServiceManager + ? (SERVICE_NOTIFY_CREATED | SERVICE_NOTIFY_DELETED) + : (SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_START_PENDING | SERVICE_NOTIFY_STOP_PENDING | + SERVICE_NOTIFY_RUNNING | SERVICE_NOTIFY_CONTINUE_PENDING | SERVICE_NOTIFY_PAUSE_PENDING | + SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_DELETE_PENDING), + ¬ifyContext->Buffer + ); + + switch (result) + { + case ERROR_SUCCESS: + notifyContext->State = SnNone; + RemoveEntryList(¬ifyContext->ListEntry); + InsertTailList(&PhpNonPollServiceListHead, ¬ifyContext->ListEntry); + break; + case ERROR_SERVICE_NOTIFY_CLIENT_LAGGING: + // We are lagging behind. Re-open the handle to the SCM as soon as possible. + lagging = TRUE; + break; + case ERROR_SERVICE_MARKED_FOR_DELETE: + default: + RemoveEntryList(¬ifyContext->ListEntry); + PhpDestroyServiceNotifyContext(notifyContext); + break; + } + + break; + } + } + + while (NtWaitForSingleObject(PhpNonPollEventHandle, TRUE, NULL) != STATUS_WAIT_0) + NOTHING; + + if (lagging) + break; + } + + // Execute all pending callbacks. + NtTestAlert(); + + listEntry = PhpNonPollServiceListHead.Flink; + + while (listEntry != &PhpNonPollServiceListHead) + { + notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); + listEntry = listEntry->Flink; + PhpDestroyServiceNotifyContext(notifyContext); + } + + listEntry = PhpNonPollServicePendingListHead.Flink; + + while (listEntry != &PhpNonPollServicePendingListHead) + { + notifyContext = CONTAINING_RECORD(listEntry, PHP_SERVICE_NOTIFY_CONTEXT, ListEntry); + listEntry = listEntry->Flink; + PhpDestroyServiceNotifyContext(notifyContext); + } + + CloseServiceHandle(scManagerHandle); + } + + NtClose(PhpNonPollEventHandle); + + return STATUS_SUCCESS; + +ErrorExit: + PhpNonPollActive = FALSE; + PhpNonPollGate = 1; + NtClose(PhpNonPollEventHandle); + return STATUS_UNSUCCESSFUL; +} + +VOID PhpInitializeServiceNonPoll( + VOID + ) +{ + // Dynamically import the required functions. + + NotifyServiceStatusChangeW_I = PhGetModuleProcAddress(L"advapi32.dll", "NotifyServiceStatusChangeW"); + + if (!NotifyServiceStatusChangeW_I) + return; + + PhpNonPollActive = TRUE; + PhpNonPollGate = 1; // initially the gate should be open since we only just initialized everything + + PhpNonPollThreadHandle = PhCreateThread(0, PhpServiceNonPollThreadStart, NULL); + + if (!PhpNonPollThreadHandle) + { + PhpNonPollActive = FALSE; + return; + } +} diff --git a/ProcessHacker/sysinfo.c b/ProcessHacker/sysinfo.c new file mode 100644 index 0000000..04d465a --- /dev/null +++ b/ProcessHacker/sysinfo.c @@ -0,0 +1,2039 @@ +/* + * Process Hacker - + * System Information window + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains the System Information framework. The "framework" handles creation, layout and + * events for the top-level window itself. It manages the list of sections. + * + * A section is an object that provides information to the user about a type of system resource. The + * CPU, Memory and I/O sections are added automatically to every System Information window. Plugins + * can also add sections to the window. There are two views: summary and section. In summary view, + * rows of graphs are displayed in the window, one graph for each section. The section is + * responsible for providing the graph data and any text to draw on the left-hand side of the graph. + * In section view, the graphs become mini-graphs on the left-hand side of the window. The section + * displays its own embedded dialog in the remaining space. Any controls contained in this dialog, + * including graphs, are the responsibility of the section. + * + * Users can enter section view by: + * * Clicking on a graph or mini-graph. + * * Pressing a number from 1 to 9. + * * Using the tab or arrow keys to select a graph and pressing space or enter. + * + * Users can return to summary view by: + * * Clicking "Back" on the left-hand side of the window. + * * Pressing Backspace. + * * Using the tab or arrow keys to select "Back" and pressing space or enter. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static HANDLE PhSipThread = NULL; +HWND PhSipWindow = NULL; +static PPH_LIST PhSipDialogList = NULL; +static PH_EVENT InitializedEvent = PH_EVENT_INIT; +static PWSTR InitialSectionName; +static PH_LAYOUT_MANAGER WindowLayoutManager; +static RECT MinimumSize; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + +static PPH_LIST SectionList; +static PH_SYSINFO_PARAMETERS CurrentParameters; +static PH_SYSINFO_VIEW_TYPE CurrentView; +static PPH_SYSINFO_SECTION CurrentSection; +static HWND ContainerControl; +static HWND SeparatorControl; +static HWND RestoreSummaryControl; +static WNDPROC RestoreSummaryControlOldWndProc; +static BOOLEAN RestoreSummaryControlHot; +static BOOLEAN RestoreSummaryControlHasFocus; + +static HTHEME ThemeData; +static BOOLEAN ThemeHasItemBackground; + +static BOOLEAN AlwaysOnTop; + +VOID PhShowSystemInformationDialog( + _In_opt_ PWSTR SectionName + ) +{ + InitialSectionName = SectionName; + + if (!PhSipWindow) + { + if (!(PhSipThread = PhCreateThread(0, PhSipSysInfoThreadStart, NULL))) + { + PhShowStatus(PhMainWndHandle, L"Unable to create the system information window", 0, GetLastError()); + return; + } + + PhWaitForEvent(&InitializedEvent, NULL); + } + + SendMessage(PhSipWindow, SI_MSG_SYSINFO_ACTIVATE, (WPARAM)SectionName, 0); +} + +NTSTATUS PhSipSysInfoThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + BOOL result; + MSG message; + HACCEL acceleratorTable; + BOOLEAN processed; + + PhInitializeAutoPool(&autoPool); + + PhSipWindow = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_SYSINFO), + NULL, + PhSipSysInfoDialogProc + ); + + PhSetEvent(&InitializedEvent); + + acceleratorTable = LoadAccelerators(PhInstanceHandle, MAKEINTRESOURCE(IDR_SYSINFO_ACCEL)); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + processed = FALSE; + + if ( + message.hwnd == PhSipWindow || + IsChild(PhSipWindow, message.hwnd) + ) + { + if (TranslateAccelerator(PhSipWindow, acceleratorTable, &message)) + processed = TRUE; + } + + if (!processed && PhSipDialogList) + { + ULONG i; + + for (i = 0; i < PhSipDialogList->Count; i++) + { + if (IsDialogMessage((HWND)PhSipDialogList->Items[i], &message)) + { + processed = TRUE; + break; + } + } + } + + if (!processed) + { + if (!IsDialogMessage(PhSipWindow, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + PhResetEvent(&InitializedEvent); + NtClose(PhSipThread); + + PhSipWindow = NULL; + PhSipThread = NULL; + + if (PhSipDialogList) + { + PhDereferenceObject(PhSipDialogList); + PhSipDialogList = NULL; + } + + return STATUS_SUCCESS; +} + +INT_PTR CALLBACK PhSipSysInfoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhSipWindow = hwndDlg; + PhSipOnInitDialog(); + } + break; + case WM_DESTROY: + { + PhSipOnDestroy(); + } + break; + case WM_NCDESTROY: + { + PhSipOnNcDestroy(); + } + break; + case WM_SHOWWINDOW: + { + PhSipOnShowWindow(!!wParam, (ULONG)lParam); + } + break; + case WM_SYSCOMMAND: + { + if (PhSipOnSysCommand((ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) + return 0; + } + break; + case WM_SIZE: + { + PhSipOnSize(); + } + break; + case WM_SIZING: + { + PhSipOnSizing((ULONG)wParam, (PRECT)lParam); + } + break; + case WM_THEMECHANGED: + { + PhSipOnThemeChanged(); + } + break; + case WM_COMMAND: + { + PhSipOnCommand(LOWORD(wParam), HIWORD(wParam)); + } + break; + case WM_NOTIFY: + { + LRESULT result; + + if (PhSipOnNotify((NMHDR *)lParam, &result)) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, result); + return TRUE; + } + } + break; + case WM_DRAWITEM: + { + if (PhSipOnDrawItem(wParam, (DRAWITEMSTRUCT *)lParam)) + return TRUE; + } + break; + } + + if (uMsg >= SI_MSG_SYSINFO_FIRST && uMsg <= SI_MSG_SYSINFO_LAST) + { + PhSipOnUserMessage(uMsg, wParam, lParam); + } + + return FALSE; +} + +INT_PTR CALLBACK PhSipContainerDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +VOID PhSipOnInitDialog( + VOID + ) +{ + PhInitializeLayoutManager(&WindowLayoutManager, PhSipWindow); + + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(PhSipWindow, IDC_INSTRUCTION), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(PhSipWindow, IDC_ALWAYSONTOP), NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&WindowLayoutManager, GetDlgItem(PhSipWindow, IDOK), NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + PhSipSysInfoUpdateHandler, + NULL, + &ProcessesUpdatedRegistration + ); + + ContainerControl = CreateDialog( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_CONTAINER), + PhSipWindow, + PhSipContainerDialogProc + ); + + PhSetControlTheme(PhSipWindow, L"explorer"); + PhSipUpdateThemeData(); +} + +VOID PhSipOnDestroy( + VOID + ) +{ + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &ProcessesUpdatedRegistration + ); + + if (CurrentSection) + PhSetStringSetting2(L"SysInfoWindowSection", &CurrentSection->Name); + else + PhSetStringSetting(L"SysInfoWindowSection", L""); + + PhSetIntegerSetting(L"SysInfoWindowAlwaysOnTop", AlwaysOnTop); + + PhSaveWindowPlacementToSetting(L"SysInfoWindowPosition", L"SysInfoWindowSize", PhSipWindow); + PhSipSaveWindowState(); +} + +VOID PhSipOnNcDestroy( + VOID + ) +{ + ULONG i; + PPH_SYSINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + PhSipDestroySection(section); + } + + PhDereferenceObject(SectionList); + SectionList = NULL; + PhSipDeleteParameters(); + + if (ThemeData) + { + CloseThemeData(ThemeData); + ThemeData = NULL; + } + + PhDeleteLayoutManager(&WindowLayoutManager); + PostQuitMessage(0); +} + +VOID PhSipOnShowWindow( + _In_ BOOLEAN Showing, + _In_ ULONG State + ) +{ + RECT buttonRect; + RECT clientRect; + PH_STRINGREF sectionName; + PPH_SYSINFO_SECTION section; + + if (SectionList) + return; + + SectionList = PhCreateList(8); + PhSipInitializeParameters(); + CurrentView = SysInfoSummaryView; + CurrentSection = NULL; + + PhSipCreateInternalSection(L"CPU", 0, PhSipCpuSectionCallback); + PhSipCreateInternalSection(L"Memory", 0, PhSipMemorySectionCallback); + PhSipCreateInternalSection(L"I/O", 0, PhSipIoSectionCallback); + + if (PhPluginsEnabled) + { + PH_PLUGIN_SYSINFO_POINTERS pointers; + + pointers.WindowHandle = PhSipWindow; + pointers.CreateSection = PhSipCreateSection; + pointers.FindSection = PhSipFindSection; + pointers.EnterSectionView = PhSipEnterSectionView; + pointers.RestoreSummaryView = PhSipRestoreSummaryView; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackSystemInformationInitializing), &pointers); + } + + SeparatorControl = CreateWindow( + L"STATIC", + NULL, + WS_CHILD | SS_OWNERDRAW, + 0, + 0, + 3, + 3, + PhSipWindow, + (HMENU)IDC_SEPARATOR, + PhInstanceHandle, + NULL + ); + + RestoreSummaryControl = CreateWindow( + L"STATIC", + NULL, + WS_CHILD | WS_TABSTOP | SS_OWNERDRAW | SS_NOTIFY, + 0, + 0, + 3, + 3, + PhSipWindow, + (HMENU)IDC_RESET, + PhInstanceHandle, + NULL + ); + RestoreSummaryControlOldWndProc = (WNDPROC)GetWindowLongPtr(RestoreSummaryControl, GWLP_WNDPROC); + SetWindowLongPtr(RestoreSummaryControl, GWLP_WNDPROC, (LONG_PTR)PhSipPanelHookWndProc); + RestoreSummaryControlHot = FALSE; + + EnableThemeDialogTexture(ContainerControl, ETDT_ENABLETAB); + + GetWindowRect(GetDlgItem(PhSipWindow, IDOK), &buttonRect); + MapWindowPoints(NULL, PhSipWindow, (POINT *)&buttonRect, 2); + GetClientRect(PhSipWindow, &clientRect); + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = WindowsVersion >= WINDOWS_VISTA ? 430 : 370; // XP doesn't have the Memory Lists group + MinimumSize.bottom = 290; + MapDialogRect(PhSipWindow, &MinimumSize); + + MinimumSize.right += CurrentParameters.PanelWidth; + MinimumSize.right += GetSystemMetrics(SM_CXFRAME) * 2; + MinimumSize.bottom += GetSystemMetrics(SM_CYFRAME) * 2; + + if (SectionList->Count != 0) + { + ULONG newMinimumHeight; + + newMinimumHeight = + GetSystemMetrics(SM_CYCAPTION) + + CurrentParameters.WindowPadding + + CurrentParameters.MinimumGraphHeight * SectionList->Count + + CurrentParameters.MinimumGraphHeight + // Back button + CurrentParameters.GraphPadding * SectionList->Count + + CurrentParameters.WindowPadding + + clientRect.bottom - buttonRect.top + + GetSystemMetrics(SM_CYFRAME) * 2; + + if (newMinimumHeight > (ULONG)MinimumSize.bottom) + MinimumSize.bottom = newMinimumHeight; + } + + PhLoadWindowPlacementFromSetting(L"SysInfoWindowPosition", L"SysInfoWindowSize", PhSipWindow); + + if (PhGetIntegerSetting(L"SysInfoWindowState") == SW_MAXIMIZE) + ShowWindow(PhSipWindow, SW_MAXIMIZE); + + if (InitialSectionName) + PhInitializeStringRefLongHint(§ionName, InitialSectionName); + else + sectionName = PhaGetStringSetting(L"SysInfoWindowSection")->sr; + + if (sectionName.Length != 0 && (section = PhSipFindSection(§ionName))) + { + PhSipEnterSectionView(section); + } + + AlwaysOnTop = (BOOLEAN)PhGetIntegerSetting(L"SysInfoWindowAlwaysOnTop"); + Button_SetCheck(GetDlgItem(PhSipWindow, IDC_ALWAYSONTOP), AlwaysOnTop ? BST_CHECKED : BST_UNCHECKED); + PhSipSetAlwaysOnTop(); + + PhSipOnSize(); + PhSipOnUserMessage(SI_MSG_SYSINFO_UPDATE, 0, 0); +} + +BOOLEAN PhSipOnSysCommand( + _In_ ULONG Type, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ) +{ + switch (Type) + { + case SC_MINIMIZE: + { + // Save the current window state because we may not have a chance to later. + PhSipSaveWindowState(); + } + break; + } + + return FALSE; +} + +VOID PhSipOnSize( + VOID + ) +{ + PhLayoutManagerLayout(&WindowLayoutManager); + + if (SectionList && SectionList->Count != 0) + { + if (CurrentView == SysInfoSummaryView) + PhSipLayoutSummaryView(); + else if (CurrentView == SysInfoSectionView) + PhSipLayoutSectionView(); + } +} + +VOID PhSipOnSizing( + _In_ ULONG Edge, + _In_ PRECT DragRectangle + ) +{ + PhResizingMinimumSize(DragRectangle, Edge, MinimumSize.right, MinimumSize.bottom); +} + +VOID PhSipOnThemeChanged( + VOID + ) +{ + PhSipUpdateThemeData(); +} + +VOID PhSipOnCommand( + _In_ ULONG Id, + _In_ ULONG Code + ) +{ + switch (Id) + { + case IDCANCEL: + case IDOK: + DestroyWindow(PhSipWindow); + break; + case IDC_ALWAYSONTOP: + { + AlwaysOnTop = Button_GetCheck(GetDlgItem(PhSipWindow, IDC_ALWAYSONTOP)) == BST_CHECKED; + PhSipSetAlwaysOnTop(); + } + break; + case IDC_RESET: + { + if (Code == STN_CLICKED) + { + PhSipRestoreSummaryView(); + } + } + break; + case IDC_BACK: + { + PhSipRestoreSummaryView(); + } + break; + case IDC_REFRESH: + { + ProcessHacker_Refresh(PhMainWndHandle); + } + break; + case IDC_PAUSE: + { + ProcessHacker_SetUpdateAutomatically(PhMainWndHandle, !ProcessHacker_GetUpdateAutomatically(PhMainWndHandle)); + } + break; + } + + if (SectionList && Id >= ID_DIGIT1 && Id <= ID_DIGIT9) + { + ULONG index; + + index = Id - ID_DIGIT1; + + if (index < SectionList->Count) + PhSipEnterSectionView(SectionList->Items[index]); + } + + if (SectionList && Code == STN_CLICKED) + { + ULONG i; + PPH_SYSINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (Id == section->PanelId) + { + PhSipEnterSectionView(section); + break; + } + } + } +} + +BOOLEAN PhSipOnNotify( + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + PPH_SYSINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (getDrawInfo->Header.hwndFrom == section->GraphHandle) + { + section->Callback(section, SysInfoGraphGetDrawInfo, drawInfo, 0); + + if (CurrentView == SysInfoSectionView) + { + drawInfo->Flags &= ~(PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y); + } + else + { + ULONG badWidth = CurrentParameters.PanelWidth; + + // Try not to draw max data point labels that will get covered by the + // fade-out part of the graph. + if (badWidth < drawInfo->Width) + drawInfo->LabelMaxYIndexLimit = (drawInfo->Width - badWidth) / 2; + else + drawInfo->LabelMaxYIndexLimit = -1; + } + + break; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + ULONG i; + PPH_SYSINFO_SECTION section; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (getTooltipText->Header.hwndFrom == section->GraphHandle) + { + PH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT graphGetTooltipText; + + graphGetTooltipText.Index = getTooltipText->Index; + PhInitializeEmptyStringRef(&graphGetTooltipText.Text); + + section->Callback(section, SysInfoGraphGetTooltipText, &graphGetTooltipText, 0); + + getTooltipText->Text = graphGetTooltipText.Text; + + break; + } + } + } + } + break; + case GCN_DRAWPANEL: + { + PPH_GRAPH_DRAWPANEL drawPanel = (PPH_GRAPH_DRAWPANEL)Header; + ULONG i; + PPH_SYSINFO_SECTION section; + + if (CurrentView == SysInfoSummaryView) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (drawPanel->Header.hwndFrom == section->GraphHandle) + { + PhSipDrawPanel(section, drawPanel->hdc, &drawPanel->Rect); + break; + } + } + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + ULONG i; + PPH_SYSINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (mouseEvent->Header.hwndFrom == section->GraphHandle) + { + if (mouseEvent->Message == WM_LBUTTONDOWN) + { + PhSipEnterSectionView(section); + } + + break; + } + } + } + break; + } + + return FALSE; +} + +BOOLEAN PhSipOnDrawItem( + _In_ ULONG_PTR Id, + _In_ DRAWITEMSTRUCT *DrawItemStruct + ) +{ + ULONG i; + PPH_SYSINFO_SECTION section; + + if (Id == IDC_RESET) + { + PhSipDrawRestoreSummaryPanel(DrawItemStruct->hDC, &DrawItemStruct->rcItem); + return TRUE; + } + else if (Id == IDC_SEPARATOR) + { + PhSipDrawSeparator(DrawItemStruct->hDC, &DrawItemStruct->rcItem); + return TRUE; + } + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (Id == section->PanelId) + { + PhSipDrawPanel(section, DrawItemStruct->hDC, &DrawItemStruct->rcItem); + return TRUE; + } + } + + return FALSE; +} + +VOID PhSipOnUserMessage( + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ) +{ + switch (Message) + { + case SI_MSG_SYSINFO_ACTIVATE: + { + PWSTR sectionName = (PWSTR)WParam; + + if (SectionList && sectionName) + { + PH_STRINGREF sectionNameSr; + PPH_SYSINFO_SECTION section; + + PhInitializeStringRefLongHint(§ionNameSr, sectionName); + section = PhSipFindSection(§ionNameSr); + + if (section) + PhSipEnterSectionView(section); + else + PhSipRestoreSummaryView(); + } + + if (IsIconic(PhSipWindow)) + ShowWindow(PhSipWindow, SW_RESTORE); + else + ShowWindow(PhSipWindow, SW_SHOW); + + SetForegroundWindow(PhSipWindow); + } + break; + case SI_MSG_SYSINFO_UPDATE: + { + ULONG i; + PPH_SYSINFO_SECTION section; + + if (SectionList) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + section->Callback(section, SysInfoTick, NULL, NULL); + + section->GraphState.Valid = FALSE; + section->GraphState.TooltipIndex = -1; + Graph_MoveGrid(section->GraphHandle, 1); + Graph_Draw(section->GraphHandle); + Graph_UpdateTooltip(section->GraphHandle); + InvalidateRect(section->GraphHandle, NULL, FALSE); + + InvalidateRect(section->PanelHandle, NULL, FALSE); + } + } + } + break; + case SI_MSG_SYSINFO_CHANGE_SETTINGS: + { + ULONG i; + PPH_SYSINFO_SECTION section; + PH_GRAPH_OPTIONS options; + + if (SectionList) + { + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + Graph_GetOptions(section->GraphHandle, &options); + options.FadeOutBackColor = CurrentParameters.GraphBackColor; + Graph_SetOptions(section->GraphHandle, &options); + } + } + + InvalidateRect(PhSipWindow, NULL, TRUE); + } + break; + } +} + +VOID PhSiNotifyChangeSettings( + VOID + ) +{ + HWND window; + + PhSipUpdateColorParameters(); + + window = PhSipWindow; + + if (window) + PostMessage(window, SI_MSG_SYSINFO_CHANGE_SETTINGS, 0, 0); +} + +VOID PhSiSetColorsGraphDrawInfo( + _Out_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ COLORREF Color1, + _In_ COLORREF Color2 + ) +{ + static PH_QUEUED_LOCK lock = PH_QUEUED_LOCK_INIT; + static ULONG lastDpi = -1; + static HFONT iconTitleFont; + + // Get the appropriate fonts. + + if (DrawInfo->Flags & PH_GRAPH_LABEL_MAX_Y) + { + PhAcquireQueuedLockExclusive(&lock); + + if (lastDpi != PhGlobalDpi) + { + LOGFONT logFont; + + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + logFont.lfHeight += PhMultiplyDivide(1, PhGlobalDpi, 72); + iconTitleFont = CreateFontIndirect(&logFont); + } + + if (!iconTitleFont) + iconTitleFont = PhApplicationFont; + + lastDpi = PhGlobalDpi; + } + + DrawInfo->LabelYFont = iconTitleFont; + + PhReleaseQueuedLockExclusive(&lock); + } + + DrawInfo->TextFont = PhApplicationFont; + + // Set up the colors. + + switch (PhCsGraphColorMode) + { + case 0: // New colors + DrawInfo->BackColor = RGB(0xef, 0xef, 0xef); + DrawInfo->LineColor1 = PhHalveColorBrightness(Color1); + DrawInfo->LineBackColor1 = PhMakeColorBrighter(Color1, 125); + DrawInfo->LineColor2 = PhHalveColorBrightness(Color2); + DrawInfo->LineBackColor2 = PhMakeColorBrighter(Color2, 125); + DrawInfo->GridColor = RGB(0xc7, 0xc7, 0xc7); + DrawInfo->LabelYColor = RGB(0xa0, 0x60, 0x20); + DrawInfo->TextColor = RGB(0x00, 0x00, 0x00); + DrawInfo->TextBoxColor = RGB(0xe7, 0xe7, 0xe7); + break; + case 1: // Old colors + DrawInfo->BackColor = RGB(0x00, 0x00, 0x00); + DrawInfo->LineColor1 = Color1; + DrawInfo->LineBackColor1 = PhHalveColorBrightness(Color1); + DrawInfo->LineColor2 = Color2; + DrawInfo->LineBackColor2 = PhHalveColorBrightness(Color2); + DrawInfo->GridColor = RGB(0x00, 0x57, 0x00); + DrawInfo->LabelYColor = RGB(0xd0, 0xa0, 0x70); + DrawInfo->TextColor = RGB(0x00, 0xff, 0x00); + DrawInfo->TextBoxColor = RGB(0x00, 0x22, 0x00); + break; + } +} + +PPH_STRING PhSiSizeLabelYFunction( + _In_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ ULONG DataIndex, + _In_ FLOAT Value, + _In_ FLOAT Parameter + ) +{ + ULONG64 size; + + size = (ULONG64)(Value * Parameter); + + if (size != 0) + { + PH_FORMAT format; + + format.Type = SizeFormatType | FormatUsePrecision | FormatUseRadix; + format.Precision = 0; + format.Radix = -1; + format.u.Size = size; + + return PhFormat(&format, 1, 0); + } + else + { + return PhReferenceEmptyString(); + } +} + +VOID PhSipRegisterDialog( + _In_ HWND DialogWindowHandle + ) +{ + if (!PhSipDialogList) + PhSipDialogList = PhCreateList(4); + + PhAddItemList(PhSipDialogList, DialogWindowHandle); +} + +VOID PhSipUnregisterDialog( + _In_ HWND DialogWindowHandle + ) +{ + ULONG index; + + if ((index = PhFindItemList(PhSipDialogList, DialogWindowHandle)) != -1) + PhRemoveItemList(PhSipDialogList, index); +} + +VOID PhSipInitializeParameters( + VOID + ) +{ + LOGFONT logFont; + HDC hdc; + TEXTMETRIC textMetrics; + HFONT originalFont; + + memset(&CurrentParameters, 0, sizeof(PH_SYSINFO_PARAMETERS)); + + CurrentParameters.SysInfoWindowHandle = PhSipWindow; + CurrentParameters.ContainerWindowHandle = ContainerControl; + + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + CurrentParameters.Font = CreateFontIndirect(&logFont); + } + else + { + CurrentParameters.Font = PhApplicationFont; + GetObject(PhApplicationFont, sizeof(LOGFONT), &logFont); + } + + hdc = GetDC(PhSipWindow); + + logFont.lfHeight -= PhMultiplyDivide(3, GetDeviceCaps(hdc, LOGPIXELSY), 72); + CurrentParameters.MediumFont = CreateFontIndirect(&logFont); + + logFont.lfHeight -= PhMultiplyDivide(3, GetDeviceCaps(hdc, LOGPIXELSY), 72); + CurrentParameters.LargeFont = CreateFontIndirect(&logFont); + + PhSipUpdateColorParameters(); + + CurrentParameters.ColorSetupFunction = PhSiSetColorsGraphDrawInfo; + + originalFont = SelectObject(hdc, CurrentParameters.Font); + GetTextMetrics(hdc, &textMetrics); + CurrentParameters.FontHeight = textMetrics.tmHeight; + CurrentParameters.FontAverageWidth = textMetrics.tmAveCharWidth; + + SelectObject(hdc, CurrentParameters.MediumFont); + GetTextMetrics(hdc, &textMetrics); + CurrentParameters.MediumFontHeight = textMetrics.tmHeight; + CurrentParameters.MediumFontAverageWidth = textMetrics.tmAveCharWidth; + + SelectObject(hdc, originalFont); + + // Internal padding and other values + CurrentParameters.PanelPadding = PH_SCALE_DPI(PH_SYSINFO_PANEL_PADDING); + CurrentParameters.WindowPadding = PH_SCALE_DPI(PH_SYSINFO_WINDOW_PADDING); + CurrentParameters.GraphPadding = PH_SCALE_DPI(PH_SYSINFO_GRAPH_PADDING); + CurrentParameters.SmallGraphWidth = PH_SCALE_DPI(PH_SYSINFO_SMALL_GRAPH_WIDTH); + CurrentParameters.SmallGraphPadding = PH_SCALE_DPI(PH_SYSINFO_SMALL_GRAPH_PADDING); + CurrentParameters.SeparatorWidth = PH_SCALE_DPI(PH_SYSINFO_SEPARATOR_WIDTH); + CurrentParameters.CpuPadding = PH_SCALE_DPI(PH_SYSINFO_CPU_PADDING); + CurrentParameters.MemoryPadding = PH_SCALE_DPI(PH_SYSINFO_MEMORY_PADDING); + + CurrentParameters.MinimumGraphHeight = + CurrentParameters.PanelPadding + + CurrentParameters.MediumFontHeight + + CurrentParameters.PanelPadding; + + CurrentParameters.SectionViewGraphHeight = + CurrentParameters.PanelPadding + + CurrentParameters.MediumFontHeight + + CurrentParameters.PanelPadding + + CurrentParameters.FontHeight + + 2 + + CurrentParameters.FontHeight + + CurrentParameters.PanelPadding + + 2; + + CurrentParameters.PanelWidth = CurrentParameters.MediumFontAverageWidth * 10; + + ReleaseDC(PhSipWindow, hdc); +} + +VOID PhSipDeleteParameters( + VOID + ) +{ + if (CurrentParameters.Font) + DeleteObject(CurrentParameters.Font); + if (CurrentParameters.MediumFont) + DeleteObject(CurrentParameters.MediumFont); + if (CurrentParameters.LargeFont) + DeleteObject(CurrentParameters.LargeFont); +} + +VOID PhSipUpdateColorParameters( + VOID + ) +{ + switch (PhCsGraphColorMode) + { + case 0: // New colors + CurrentParameters.GraphBackColor = RGB(0xef, 0xef, 0xef); + CurrentParameters.PanelForeColor = RGB(0x00, 0x00, 0x00); + break; + case 1: // Old colors + CurrentParameters.GraphBackColor = RGB(0x00, 0x00, 0x00); + CurrentParameters.PanelForeColor = RGB(0xff, 0xff, 0xff); + break; + } +} + +PPH_SYSINFO_SECTION PhSipCreateSection( + _In_ PPH_SYSINFO_SECTION Template + ) +{ + PPH_SYSINFO_SECTION section; + PH_GRAPH_OPTIONS options; + + section = PhAllocate(sizeof(PH_SYSINFO_SECTION)); + memset(section, 0, sizeof(PH_SYSINFO_SECTION)); + + section->Name = Template->Name; + section->Flags = Template->Flags; + section->Callback = Template->Callback; + section->Context = Template->Context; + + section->GraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP | GC_STYLE_FADEOUT | GC_STYLE_DRAW_PANEL, + 0, + 0, + 3, + 3, + PhSipWindow, + NULL, + PhInstanceHandle, + NULL + ); + PhInitializeGraphState(§ion->GraphState); + section->Parameters = &CurrentParameters; + + Graph_GetOptions(section->GraphHandle, &options); + options.FadeOutBackColor = CurrentParameters.GraphBackColor; + options.FadeOutWidth = CurrentParameters.PanelWidth + PH_SYSINFO_FADE_ADD; + options.DefaultCursor = LoadCursor(NULL, IDC_HAND); + Graph_SetOptions(section->GraphHandle, &options); + Graph_SetTooltip(section->GraphHandle, TRUE); + + section->PanelId = IDDYNAMIC + SectionList->Count * 2 + 2; + section->PanelHandle = CreateWindow( + L"STATIC", + NULL, + WS_CHILD | SS_OWNERDRAW | SS_NOTIFY, + 0, + 0, + 3, + 3, + PhSipWindow, + (HMENU)(ULONG_PTR)section->PanelId, + PhInstanceHandle, + NULL + ); + + SetProp(section->GraphHandle, PhMakeContextAtom(), section); + section->GraphOldWndProc = (WNDPROC)GetWindowLongPtr(section->GraphHandle, GWLP_WNDPROC); + SetWindowLongPtr(section->GraphHandle, GWLP_WNDPROC, (LONG_PTR)PhSipGraphHookWndProc); + + SetProp(section->PanelHandle, PhMakeContextAtom(), section); + section->PanelOldWndProc = (WNDPROC)GetWindowLongPtr(section->PanelHandle, GWLP_WNDPROC); + SetWindowLongPtr(section->PanelHandle, GWLP_WNDPROC, (LONG_PTR)PhSipPanelHookWndProc); + + PhAddItemList(SectionList, section); + + section->Callback(section, SysInfoCreate, NULL, NULL); + + return section; +} + +VOID PhSipDestroySection( + _In_ PPH_SYSINFO_SECTION Section + ) +{ + Section->Callback(Section, SysInfoDestroy, NULL, NULL); + + PhDeleteGraphState(&Section->GraphState); + PhFree(Section); +} + +PPH_SYSINFO_SECTION PhSipFindSection( + _In_ PPH_STRINGREF Name + ) +{ + ULONG i; + PPH_SYSINFO_SECTION section; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (PhEqualStringRef(§ion->Name, Name, TRUE)) + return section; + } + + return NULL; +} + +PPH_SYSINFO_SECTION PhSipCreateInternalSection( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_ PPH_SYSINFO_SECTION_CALLBACK Callback + ) +{ + PH_SYSINFO_SECTION section; + + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + PhInitializeStringRef(§ion.Name, Name); + section.Flags = Flags; + section.Callback = Callback; + + return PhSipCreateSection(§ion); +} + +VOID PhSipDrawRestoreSummaryPanel( + _In_ HDC hdc, + _In_ PRECT Rect + ) +{ + FillRect(hdc, Rect, GetSysColorBrush(COLOR_3DFACE)); + + if (RestoreSummaryControlHot || RestoreSummaryControlHasFocus) + { + if (ThemeHasItemBackground) + { + DrawThemeBackground( + ThemeData, + hdc, + TVP_TREEITEM, + TREIS_HOT, + Rect, + Rect + ); + } + else + { + FillRect(hdc, Rect, GetSysColorBrush(COLOR_WINDOW)); + } + } + + SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); + SetBkMode(hdc, TRANSPARENT); + + SelectObject(hdc, CurrentParameters.MediumFont); + DrawText(hdc, L"Back", 4, Rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); +} + +VOID PhSipDrawSeparator( + _In_ HDC hdc, + _In_ PRECT Rect + ) +{ + RECT rect; + + rect = *Rect; + FillRect(hdc, &rect, GetSysColorBrush(COLOR_3DHIGHLIGHT)); + rect.left += 1; + FillRect(hdc, &rect, GetSysColorBrush(COLOR_3DSHADOW)); +} + +VOID PhSipDrawPanel( + _In_ PPH_SYSINFO_SECTION Section, + _In_ HDC hdc, + _In_ PRECT Rect + ) +{ + PH_SYSINFO_DRAW_PANEL sysInfoDrawPanel; + + if (CurrentView == SysInfoSectionView) + FillRect(hdc, Rect, GetSysColorBrush(COLOR_3DFACE)); + + sysInfoDrawPanel.hdc = hdc; + sysInfoDrawPanel.Rect = *Rect; + sysInfoDrawPanel.Rect.right = CurrentParameters.PanelWidth; + sysInfoDrawPanel.CustomDraw = FALSE; + + sysInfoDrawPanel.Title = NULL; + sysInfoDrawPanel.SubTitle = NULL; + sysInfoDrawPanel.SubTitleOverflow = NULL; + + Section->Callback(Section, SysInfoGraphDrawPanel, &sysInfoDrawPanel, NULL); + + if (!sysInfoDrawPanel.CustomDraw) + { + sysInfoDrawPanel.Rect.right = Rect->right; + PhSipDefaultDrawPanel(Section, &sysInfoDrawPanel); + } + + PhClearReference(&sysInfoDrawPanel.Title); + PhClearReference(&sysInfoDrawPanel.SubTitle); + PhClearReference(&sysInfoDrawPanel.SubTitleOverflow); +} + +VOID PhSipDefaultDrawPanel( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PPH_SYSINFO_DRAW_PANEL DrawPanel + ) +{ + HDC hdc; + RECT rect; + ULONG flags; + + hdc = DrawPanel->hdc; + + if (ThemeHasItemBackground) + { + if (CurrentView == SysInfoSectionView) + { + INT stateId; + + stateId = -1; + + if (Section->GraphHot || Section->PanelHot || Section->HasFocus) + { + if (Section == CurrentSection) + stateId = TREIS_HOTSELECTED; + else + stateId = TREIS_HOT; + } + else if (Section == CurrentSection) + { + stateId = TREIS_SELECTED; + } + + if (stateId != -1) + { + RECT themeRect; + + themeRect = DrawPanel->Rect; + themeRect.left -= 2; // remove left edge + + DrawThemeBackground( + ThemeData, + hdc, + TVP_TREEITEM, + stateId, + &themeRect, + &DrawPanel->Rect + ); + } + } + else if (Section->HasFocus) + { + DrawThemeBackground( + ThemeData, + hdc, + TVP_TREEITEM, + TREIS_HOT, + &DrawPanel->Rect, + &DrawPanel->Rect + ); + } + } + else + { + if (CurrentView == SysInfoSectionView) + { + HBRUSH brush; + + brush = NULL; + + if (Section->GraphHot || Section->PanelHot || Section->HasFocus) + { + brush = GetSysColorBrush(COLOR_WINDOW); // TODO: Use a different color + } + else if (Section == CurrentSection) + { + brush = GetSysColorBrush(COLOR_WINDOW); + } + + if (brush) + { + FillRect(hdc, &DrawPanel->Rect, brush); + } + } + } + + if (CurrentView == SysInfoSummaryView) + SetTextColor(hdc, CurrentParameters.PanelForeColor); + else + SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); + + SetBkMode(hdc, TRANSPARENT); + + rect.left = CurrentParameters.SmallGraphPadding + CurrentParameters.PanelPadding; + rect.top = CurrentParameters.PanelPadding; + rect.right = CurrentParameters.PanelWidth; + rect.bottom = DrawPanel->Rect.bottom; + + flags = DT_NOPREFIX; + + if (CurrentView == SysInfoSummaryView) + rect.right = DrawPanel->Rect.right; // allow the text to overflow + else + flags |= DT_END_ELLIPSIS; + + if (DrawPanel->Title) + { + SelectObject(hdc, CurrentParameters.MediumFont); + DrawText(hdc, DrawPanel->Title->Buffer, (ULONG)DrawPanel->Title->Length / 2, &rect, flags | DT_SINGLELINE); + } + + if (DrawPanel->SubTitle) + { + RECT measureRect; + + rect.top += CurrentParameters.MediumFontHeight + CurrentParameters.PanelPadding; + SelectObject(hdc, CurrentParameters.Font); + + measureRect = rect; + DrawText(hdc, DrawPanel->SubTitle->Buffer, (ULONG)DrawPanel->SubTitle->Length / 2, &measureRect, (flags & ~DT_END_ELLIPSIS) | DT_CALCRECT); + + if (measureRect.right <= rect.right || !DrawPanel->SubTitleOverflow) + { + // Text fits; draw normally. + DrawText(hdc, DrawPanel->SubTitle->Buffer, (ULONG)DrawPanel->SubTitle->Length / 2, &rect, flags); + } + else + { + // Text doesn't fit; draw the alternative text. + DrawText(hdc, DrawPanel->SubTitleOverflow->Buffer, (ULONG)DrawPanel->SubTitleOverflow->Length / 2, &rect, flags); + } + } +} + +VOID PhSipLayoutSummaryView( + VOID + ) +{ + RECT buttonRect; + RECT clientRect; + ULONG availableHeight; + ULONG availableWidth; + ULONG graphHeight; + ULONG i; + PPH_SYSINFO_SECTION section; + HDWP deferHandle; + ULONG y; + + GetWindowRect(GetDlgItem(PhSipWindow, IDOK), &buttonRect); + MapWindowPoints(NULL, PhSipWindow, (POINT *)&buttonRect, 2); + GetClientRect(PhSipWindow, &clientRect); + + availableHeight = buttonRect.top - CurrentParameters.WindowPadding * 2; + availableWidth = clientRect.right - CurrentParameters.WindowPadding * 2; + graphHeight = (availableHeight - CurrentParameters.GraphPadding * (SectionList->Count - 1)) / SectionList->Count; + + deferHandle = BeginDeferWindowPos(SectionList->Count); + y = CurrentParameters.WindowPadding; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + deferHandle = DeferWindowPos( + deferHandle, + section->GraphHandle, + NULL, + CurrentParameters.WindowPadding, + y, + availableWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + CurrentParameters.GraphPadding; + + section->GraphState.Valid = FALSE; + } + + EndDeferWindowPos(deferHandle); +} + +VOID PhSipLayoutSectionView( + VOID + ) +{ + RECT buttonRect; + RECT clientRect; + ULONG availableHeight; + ULONG availableWidth; + ULONG graphHeight; + ULONG i; + PPH_SYSINFO_SECTION section; + HDWP deferHandle; + ULONG y; + ULONG containerLeft; + + GetWindowRect(GetDlgItem(PhSipWindow, IDOK), &buttonRect); + MapWindowPoints(NULL, PhSipWindow, (POINT *)&buttonRect, 2); + GetClientRect(PhSipWindow, &clientRect); + + availableHeight = buttonRect.top - CurrentParameters.WindowPadding * 2; + availableWidth = clientRect.right - CurrentParameters.WindowPadding * 2; + graphHeight = (availableHeight - CurrentParameters.SmallGraphPadding * SectionList->Count) / (SectionList->Count + 1); + + if (graphHeight > CurrentParameters.SectionViewGraphHeight) + graphHeight = CurrentParameters.SectionViewGraphHeight; + + deferHandle = BeginDeferWindowPos(SectionList->Count * 2 + 3); + y = CurrentParameters.WindowPadding; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + deferHandle = DeferWindowPos( + deferHandle, + section->GraphHandle, + NULL, + CurrentParameters.WindowPadding, + y, + CurrentParameters.SmallGraphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos( + deferHandle, + section->PanelHandle, + NULL, + CurrentParameters.WindowPadding + CurrentParameters.SmallGraphWidth, + y, + CurrentParameters.SmallGraphPadding + CurrentParameters.PanelWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + InvalidateRect(section->PanelHandle, NULL, TRUE); + + y += graphHeight + CurrentParameters.SmallGraphPadding; + + section->GraphState.Valid = FALSE; + } + + deferHandle = DeferWindowPos( + deferHandle, + RestoreSummaryControl, + NULL, + CurrentParameters.WindowPadding, + y, + CurrentParameters.SmallGraphWidth + CurrentParameters.SmallGraphPadding + CurrentParameters.PanelWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + InvalidateRect(RestoreSummaryControl, NULL, TRUE); + + deferHandle = DeferWindowPos( + deferHandle, + SeparatorControl, + NULL, + CurrentParameters.WindowPadding + CurrentParameters.SmallGraphWidth + CurrentParameters.SmallGraphPadding + CurrentParameters.PanelWidth + CurrentParameters.WindowPadding - 7, + 0, + CurrentParameters.SeparatorWidth, + CurrentParameters.WindowPadding + availableHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + containerLeft = CurrentParameters.WindowPadding + CurrentParameters.SmallGraphWidth + CurrentParameters.SmallGraphPadding + CurrentParameters.PanelWidth + CurrentParameters.WindowPadding - 7 + CurrentParameters.SeparatorWidth; + deferHandle = DeferWindowPos( + deferHandle, + ContainerControl, + NULL, + containerLeft, + 0, + clientRect.right - containerLeft, + CurrentParameters.WindowPadding + availableHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); + + if (CurrentSection && CurrentSection->DialogHandle) + { + SetWindowPos( + CurrentSection->DialogHandle, + NULL, + CurrentParameters.WindowPadding, + CurrentParameters.WindowPadding, + clientRect.right - containerLeft - CurrentParameters.WindowPadding - CurrentParameters.WindowPadding, + availableHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + } +} + +VOID PhSipEnterSectionView( + _In_ PPH_SYSINFO_SECTION NewSection + ) +{ + ULONG i; + PPH_SYSINFO_SECTION section; + BOOLEAN fromSummaryView; + PPH_SYSINFO_SECTION oldSection; + HDWP deferHandle; + HDWP containerDeferHandle; + + if (CurrentSection == NewSection) + return; + + fromSummaryView = CurrentView == SysInfoSummaryView; + CurrentView = SysInfoSectionView; + oldSection = CurrentSection; + CurrentSection = NewSection; + + deferHandle = BeginDeferWindowPos(SectionList->Count + 4); + containerDeferHandle = BeginDeferWindowPos(SectionList->Count); + + PhSipEnterSectionViewInner(NewSection, fromSummaryView, &deferHandle, &containerDeferHandle); + PhSipLayoutSectionView(); + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + if (section != NewSection) + PhSipEnterSectionViewInner(section, fromSummaryView, &deferHandle, &containerDeferHandle); + } + + deferHandle = DeferWindowPos(deferHandle, ContainerControl, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW_ONLY); + deferHandle = DeferWindowPos(deferHandle, RestoreSummaryControl, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW_ONLY); + deferHandle = DeferWindowPos(deferHandle, SeparatorControl, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW_ONLY); + deferHandle = DeferWindowPos(deferHandle, GetDlgItem(PhSipWindow, IDC_INSTRUCTION), NULL, 0, 0, 0, 0, SWP_HIDEWINDOW_ONLY); + + EndDeferWindowPos(deferHandle); + EndDeferWindowPos(containerDeferHandle); + + if (oldSection) + InvalidateRect(oldSection->PanelHandle, NULL, TRUE); + + InvalidateRect(NewSection->PanelHandle, NULL, TRUE); + + if (NewSection->DialogHandle) + RedrawWindow(NewSection->DialogHandle, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW); +} + +VOID PhSipEnterSectionViewInner( + _In_ PPH_SYSINFO_SECTION Section, + _In_ BOOLEAN FromSummaryView, + _Inout_ HDWP *DeferHandle, + _Inout_ HDWP *ContainerDeferHandle + ) +{ + Section->HasFocus = FALSE; + Section->Callback(Section, SysInfoViewChanging, (PVOID)CurrentView, CurrentSection); + + if (FromSummaryView) + { + PhSetWindowStyle(Section->GraphHandle, GC_STYLE_FADEOUT | GC_STYLE_DRAW_PANEL, 0); + *DeferHandle = DeferWindowPos(*DeferHandle, Section->PanelHandle, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW_ONLY); + } + + if (Section == CurrentSection && !Section->DialogHandle) + PhSipCreateSectionDialog(Section); + + if (Section->DialogHandle) + { + if (Section == CurrentSection) + *ContainerDeferHandle = DeferWindowPos(*ContainerDeferHandle, Section->DialogHandle, NULL, 0, 0, 0, 0, SWP_SHOWWINDOW_ONLY | SWP_NOREDRAW); + else + *ContainerDeferHandle = DeferWindowPos(*ContainerDeferHandle, Section->DialogHandle, NULL, 0, 0, 0, 0, SWP_HIDEWINDOW_ONLY | SWP_NOREDRAW); + } +} + +VOID PhSipRestoreSummaryView( + VOID + ) +{ + ULONG i; + PPH_SYSINFO_SECTION section; + + if (CurrentView == SysInfoSummaryView) + return; + + CurrentView = SysInfoSummaryView; + CurrentSection = NULL; + + for (i = 0; i < SectionList->Count; i++) + { + section = SectionList->Items[i]; + + section->Callback(section, SysInfoViewChanging, (PVOID)CurrentView, NULL); + + PhSetWindowStyle(section->GraphHandle, GC_STYLE_FADEOUT | GC_STYLE_DRAW_PANEL, GC_STYLE_FADEOUT | GC_STYLE_DRAW_PANEL); + ShowWindow(section->PanelHandle, SW_HIDE); + + if (section->DialogHandle) + ShowWindow(section->DialogHandle, SW_HIDE); + } + + ShowWindow(ContainerControl, SW_HIDE); + ShowWindow(RestoreSummaryControl, SW_HIDE); + ShowWindow(SeparatorControl, SW_HIDE); + ShowWindow(GetDlgItem(PhSipWindow, IDC_INSTRUCTION), SW_SHOW); + + PhSipLayoutSummaryView(); +} + +VOID PhSipCreateSectionDialog( + _In_ PPH_SYSINFO_SECTION Section + ) +{ + PH_SYSINFO_CREATE_DIALOG createDialog; + + memset(&createDialog, 0, sizeof(PH_SYSINFO_CREATE_DIALOG)); + + if (Section->Callback(Section, SysInfoCreateDialog, &createDialog, NULL)) + { + if (!createDialog.CustomCreate) + { + Section->DialogHandle = PhCreateDialogFromTemplate( + ContainerControl, + DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD, + createDialog.Instance, + createDialog.Template, + createDialog.DialogProc, + createDialog.Parameter + ); + } + } +} + +LRESULT CALLBACK PhSipGraphHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + WNDPROC oldWndProc; + PPH_SYSINFO_SECTION section; + + section = GetProp(hwnd, PhMakeContextAtom()); + + if (!section) + return 0; + + oldWndProc = section->GraphOldWndProc; + + switch (uMsg) + { + case WM_DESTROY: + { + RemoveProp(hwnd, PhMakeContextAtom()); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + } + break; + case WM_SETFOCUS: + section->HasFocus = TRUE; + + if (CurrentView == SysInfoSummaryView) + { + Graph_Draw(section->GraphHandle); + InvalidateRect(section->GraphHandle, NULL, FALSE); + } + else + { + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + + break; + case WM_KILLFOCUS: + section->HasFocus = FALSE; + + if (CurrentView == SysInfoSummaryView) + { + Graph_Draw(section->GraphHandle); + InvalidateRect(section->GraphHandle, NULL, FALSE); + } + else + { + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + + break; + case WM_GETDLGCODE: + if (wParam == VK_SPACE || wParam == VK_RETURN || + wParam == VK_UP || wParam == VK_DOWN || + wParam == VK_LEFT || wParam == VK_RIGHT) + return DLGC_WANTALLKEYS; + break; + case WM_KEYDOWN: + { + if (wParam == VK_SPACE || wParam == VK_RETURN) + { + PhSipEnterSectionView(section); + } + else if (wParam == VK_UP || wParam == VK_LEFT || + wParam == VK_DOWN || wParam == VK_RIGHT) + { + ULONG i; + + for (i = 0; i < SectionList->Count; i++) + { + if (SectionList->Items[i] == section) + { + if ((wParam == VK_UP || wParam == VK_LEFT) && i > 0) + { + SetFocus(((PPH_SYSINFO_SECTION)SectionList->Items[i - 1])->GraphHandle); + } + else if (wParam == VK_DOWN || wParam == VK_RIGHT) + { + if (i < SectionList->Count - 1) + SetFocus(((PPH_SYSINFO_SECTION)SectionList->Items[i + 1])->GraphHandle); + else + SetFocus(RestoreSummaryControl); + } + + break; + } + } + } + } + break; + case WM_MOUSEMOVE: + { + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = hwnd; + trackMouseEvent.dwHoverTime = 0; + TrackMouseEvent(&trackMouseEvent); + + if (!(section->GraphHot || section->PanelHot)) + { + section->GraphHot = TRUE; + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + else + { + section->GraphHot = TRUE; + } + } + break; + case WM_MOUSELEAVE: + { + if (!section->PanelHot) + { + section->GraphHot = FALSE; + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + else + { + section->GraphHot = FALSE; + } + + section->HasFocus = FALSE; + + if (CurrentView == SysInfoSummaryView) + { + Graph_Draw(section->GraphHandle); + InvalidateRect(section->GraphHandle, NULL, FALSE); + } + } + break; + case WM_NCLBUTTONDOWN: + { + PhSipEnterSectionView(section); + } + break; + case WM_UPDATEUISTATE: + { + switch (LOWORD(wParam)) + { + case UIS_SET: + if (HIWORD(wParam) & UISF_HIDEFOCUS) + section->HideFocus = TRUE; + break; + case UIS_CLEAR: + if (HIWORD(wParam) & UISF_HIDEFOCUS) + section->HideFocus = FALSE; + break; + case UIS_INITIALIZE: + section->HideFocus = !!(HIWORD(wParam) & UISF_HIDEFOCUS); + break; + } + } + break; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +LRESULT CALLBACK PhSipPanelHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + WNDPROC oldWndProc; + PPH_SYSINFO_SECTION section; + + section = GetProp(hwnd, PhMakeContextAtom()); + + if (section) + oldWndProc = section->PanelOldWndProc; + else + oldWndProc = RestoreSummaryControlOldWndProc; + + switch (uMsg) + { + case WM_DESTROY: + { + RemoveProp(hwnd, PhMakeContextAtom()); + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + } + break; + case WM_SETFOCUS: + { + if (!section) + { + RestoreSummaryControlHasFocus = TRUE; + InvalidateRect(hwnd, NULL, TRUE); + } + } + break; + case WM_KILLFOCUS: + { + if (!section) + { + RestoreSummaryControlHasFocus = FALSE; + InvalidateRect(hwnd, NULL, TRUE); + } + } + break; + case WM_SETCURSOR: + { + SetCursor(LoadCursor(NULL, IDC_HAND)); + } + return TRUE; + case WM_GETDLGCODE: + if (wParam == VK_SPACE || wParam == VK_RETURN || + wParam == VK_UP || wParam == VK_DOWN || + wParam == VK_LEFT || wParam == VK_RIGHT) + return DLGC_WANTALLKEYS; + break; + case WM_KEYDOWN: + { + if (wParam == VK_SPACE || wParam == VK_RETURN) + { + if (section) + PhSipEnterSectionView(section); + else + PhSipRestoreSummaryView(); + } + else if (wParam == VK_UP || wParam == VK_LEFT) + { + if (!section && SectionList->Count != 0) + { + SetFocus(((PPH_SYSINFO_SECTION)SectionList->Items[SectionList->Count - 1])->GraphHandle); + } + } + } + break; + case WM_MOUSEMOVE: + { + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = hwnd; + trackMouseEvent.dwHoverTime = 0; + TrackMouseEvent(&trackMouseEvent); + + if (section) + { + if (!(section->GraphHot || section->PanelHot)) + { + section->PanelHot = TRUE; + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + else + { + section->PanelHot = TRUE; + } + } + else + { + RestoreSummaryControlHot = TRUE; + InvalidateRect(RestoreSummaryControl, NULL, TRUE); + } + } + break; + case WM_MOUSELEAVE: + { + if (section) + { + section->HasFocus = FALSE; + + if (!section->GraphHot) + { + section->PanelHot = FALSE; + InvalidateRect(section->PanelHandle, NULL, TRUE); + } + else + { + section->PanelHot = FALSE; + } + } + else + { + RestoreSummaryControlHasFocus = FALSE; + RestoreSummaryControlHot = FALSE; + InvalidateRect(RestoreSummaryControl, NULL, TRUE); + } + } + break; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +VOID PhSipUpdateThemeData( + VOID + ) +{ + if (ThemeData) + { + CloseThemeData(ThemeData); + ThemeData = NULL; + } + + ThemeData = OpenThemeData(PhSipWindow, L"TREEVIEW"); + + if (ThemeData) + { + ThemeHasItemBackground = !!IsThemePartDefined(ThemeData, TVP_TREEITEM, 0); + } + else + { + ThemeHasItemBackground = FALSE; + } +} + +VOID PhSipSetAlwaysOnTop( + VOID + ) +{ + SetFocus(PhSipWindow); // HACK - SetWindowPos doesn't work properly without this + SetWindowPos(PhSipWindow, AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); +} + +VOID PhSipSaveWindowState( + VOID + ) +{ + WINDOWPLACEMENT placement = { sizeof(placement) }; + + GetWindowPlacement(PhSipWindow, &placement); + + if (placement.showCmd == SW_NORMAL) + PhSetIntegerSetting(L"SysInfoWindowState", SW_NORMAL); + else if (placement.showCmd == SW_MAXIMIZE) + PhSetIntegerSetting(L"SysInfoWindowState", SW_MAXIMIZE); +} + +VOID NTAPI PhSipSysInfoUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(PhSipWindow, SI_MSG_SYSINFO_UPDATE, 0, 0); +} + +PPH_STRING PhSipFormatSizeWithPrecision( + _In_ ULONG64 Size, + _In_ USHORT Precision + ) +{ + PH_FORMAT format; + + format.Type = SizeFormatType | FormatUsePrecision; + format.Precision = Precision; + format.u.Size = Size; + + return PH_AUTO(PhFormat(&format, 1, 0)); +} diff --git a/ProcessHacker/syssccpu.c b/ProcessHacker/syssccpu.c new file mode 100644 index 0000000..9534782 --- /dev/null +++ b/ProcessHacker/syssccpu.c @@ -0,0 +1,979 @@ +/* + * Process Hacker - + * System Information CPU section + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +static PPH_SYSINFO_SECTION CpuSection; +static HWND CpuDialog; +static PH_LAYOUT_MANAGER CpuLayoutManager; +static RECT CpuGraphMargin; +static HWND CpuGraphHandle; +static PH_GRAPH_STATE CpuGraphState; +static HWND *CpusGraphHandle; +static PPH_GRAPH_STATE CpusGraphState; +static BOOLEAN OneGraphPerCpu; +static HWND CpuPanel; +static ULONG CpuTicked; +static ULONG NumberOfProcessors; +static PSYSTEM_INTERRUPT_INFORMATION InterruptInformation; +static PPROCESSOR_POWER_INFORMATION PowerInformation; +static PSYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION CurrentPerformanceDistribution; +static PSYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION PreviousPerformanceDistribution; +static PH_UINT32_DELTA ContextSwitchesDelta; +static PH_UINT32_DELTA InterruptsDelta; +static PH_UINT64_DELTA DpcsDelta; +static PH_UINT32_DELTA SystemCallsDelta; + +BOOLEAN PhSipCpuSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoCreate: + { + CpuSection = Section; + } + return TRUE; + case SysInfoDestroy: + { + if (CpuDialog) + { + PhSipUninitializeCpuDialog(); + CpuDialog = NULL; + } + } + return TRUE; + case SysInfoTick: + { + if (CpuDialog) + { + PhSipTickCpuDialog(); + } + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PhInstanceHandle; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_CPU); + createDialog->DialogProc = PhSipCpuDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhCsColorCpuKernel, PhCsColorCpuUser); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, PhCpuKernelHistory.Count); + + if (!Section->GraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&PhCpuKernelHistory, Section->GraphState.Data1, drawInfo->LineDataCount); + PhCopyCircularBuffer_FLOAT(&PhCpuUserHistory, Section->GraphState.Data2, drawInfo->LineDataCount); + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + FLOAT cpuKernel; + FLOAT cpuUser; + + cpuKernel = PhGetItemCircularBuffer_FLOAT(&PhCpuKernelHistory, getTooltipText->Index); + cpuUser = PhGetItemCircularBuffer_FLOAT(&PhCpuUserHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"%.2f%%%s\n%s", + (cpuKernel + cpuUser) * 100, + PhGetStringOrEmpty(PhSipGetMaxCpuString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + + drawPanel->Title = PhCreateString(L"CPU"); + drawPanel->SubTitle = PhFormatString(L"%.2f%%", (PhCpuKernelUsage + PhCpuUserUsage) * 100); + } + return TRUE; + } + + return FALSE; +} + +VOID PhSipInitializeCpuDialog( + VOID + ) +{ + ULONG i; + + PhInitializeDelta(&ContextSwitchesDelta); + PhInitializeDelta(&InterruptsDelta); + PhInitializeDelta(&DpcsDelta); + PhInitializeDelta(&SystemCallsDelta); + + NumberOfProcessors = (ULONG)PhSystemBasicInformation.NumberOfProcessors; + CpusGraphHandle = PhAllocate(sizeof(HWND) * NumberOfProcessors); + CpusGraphState = PhAllocate(sizeof(PH_GRAPH_STATE) * NumberOfProcessors); + InterruptInformation = PhAllocate(sizeof(SYSTEM_INTERRUPT_INFORMATION) * NumberOfProcessors); + PowerInformation = PhAllocate(sizeof(PROCESSOR_POWER_INFORMATION) * NumberOfProcessors); + + PhInitializeGraphState(&CpuGraphState); + + for (i = 0; i < NumberOfProcessors; i++) + PhInitializeGraphState(&CpusGraphState[i]); + + CpuTicked = 0; + + if (!NT_SUCCESS(NtPowerInformation( + ProcessorInformation, + NULL, + 0, + PowerInformation, + sizeof(PROCESSOR_POWER_INFORMATION) * NumberOfProcessors + ))) + { + memset(PowerInformation, 0, sizeof(PROCESSOR_POWER_INFORMATION) * NumberOfProcessors); + } + + CurrentPerformanceDistribution = NULL; + PreviousPerformanceDistribution = NULL; + + if (WindowsVersion >= WINDOWS_7) + PhSipQueryProcessorPerformanceDistribution(&CurrentPerformanceDistribution); +} + +VOID PhSipUninitializeCpuDialog( + VOID + ) +{ + ULONG i; + + PhDeleteGraphState(&CpuGraphState); + + for (i = 0; i < NumberOfProcessors; i++) + PhDeleteGraphState(&CpusGraphState[i]); + + PhFree(CpusGraphHandle); + PhFree(CpusGraphState); + PhFree(InterruptInformation); + PhFree(PowerInformation); + + if (CurrentPerformanceDistribution) + PhFree(CurrentPerformanceDistribution); + if (PreviousPerformanceDistribution) + PhFree(PreviousPerformanceDistribution); + + PhSetIntegerSetting(L"SysInfoWindowOneGraphPerCpu", OneGraphPerCpu); +} + +VOID PhSipTickCpuDialog( + VOID + ) +{ + ULONG64 dpcCount; + ULONG i; + + dpcCount = 0; + + if (NT_SUCCESS(NtQuerySystemInformation( + SystemInterruptInformation, + InterruptInformation, + sizeof(SYSTEM_INTERRUPT_INFORMATION) * NumberOfProcessors, + NULL + ))) + { + for (i = 0; i < NumberOfProcessors; i++) + dpcCount += InterruptInformation[i].DpcCount; + } + + PhUpdateDelta(&ContextSwitchesDelta, PhPerfInformation.ContextSwitches); + PhUpdateDelta(&InterruptsDelta, PhCpuTotals.InterruptCount); + PhUpdateDelta(&DpcsDelta, dpcCount); + PhUpdateDelta(&SystemCallsDelta, PhPerfInformation.SystemCalls); + + if (!NT_SUCCESS(NtPowerInformation( + ProcessorInformation, + NULL, + 0, + PowerInformation, + sizeof(PROCESSOR_POWER_INFORMATION) * NumberOfProcessors + ))) + { + memset(PowerInformation, 0, sizeof(PROCESSOR_POWER_INFORMATION) * NumberOfProcessors); + } + + if (WindowsVersion >= WINDOWS_7) + { + if (PreviousPerformanceDistribution) + PhFree(PreviousPerformanceDistribution); + + PreviousPerformanceDistribution = CurrentPerformanceDistribution; + CurrentPerformanceDistribution = NULL; + PhSipQueryProcessorPerformanceDistribution(&CurrentPerformanceDistribution); + } + + CpuTicked++; + + if (CpuTicked > 2) + CpuTicked = 2; + + PhSipUpdateCpuGraphs(); + PhSipUpdateCpuPanel(); +} + +INT_PTR CALLBACK PhSipCpuDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + WCHAR brandString[49]; + + PhSipInitializeCpuDialog(); + + CpuDialog = hwndDlg; + PhInitializeLayoutManager(&CpuLayoutManager, hwndDlg); + PhAddLayoutItem(&CpuLayoutManager, GetDlgItem(hwndDlg, IDC_CPUNAME), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&CpuLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + CpuGraphMargin = graphItem->Margin; + panelItem = PhAddLayoutItem(&CpuLayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)CpuSection->Parameters->LargeFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_CPUNAME), WM_SETFONT, (WPARAM)CpuSection->Parameters->MediumFont, FALSE); + + PhSipGetCpuBrandString(brandString); + SetDlgItemText(hwndDlg, IDC_CPUNAME, brandString); + + CpuPanel = CreateDialog(PhInstanceHandle, MAKEINTRESOURCE(IDD_SYSINFO_CPUPANEL), hwndDlg, PhSipCpuPanelDialogProc); + ShowWindow(CpuPanel, SW_SHOW); + PhAddLayoutItemEx(&CpuLayoutManager, CpuPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + PhSipCreateCpuGraphs(); + + if (NumberOfProcessors != 1) + { + OneGraphPerCpu = (BOOLEAN)PhGetIntegerSetting(L"SysInfoWindowOneGraphPerCpu"); + Button_SetCheck(GetDlgItem(CpuPanel, IDC_ONEGRAPHPERCPU), OneGraphPerCpu ? BST_CHECKED : BST_UNCHECKED); + PhSipSetOneGraphPerCpu(); + } + else + { + OneGraphPerCpu = FALSE; + EnableWindow(GetDlgItem(CpuPanel, IDC_ONEGRAPHPERCPU), FALSE); + PhSipSetOneGraphPerCpu(); + } + + PhSipUpdateCpuGraphs(); + PhSipUpdateCpuPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&CpuLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&CpuLayoutManager); + PhSipLayoutCpuGraphs(); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + ULONG i; + + if (header->hwndFrom == CpuGraphHandle) + { + PhSipNotifyCpuGraph(-1, header); + } + else + { + for (i = 0; i < NumberOfProcessors; i++) + { + if (header->hwndFrom == CpusGraphHandle[i]) + { + PhSipNotifyCpuGraph(i, header); + break; + } + } + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhSipCpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SendMessage(GetDlgItem(hwndDlg, IDC_UTILIZATION), WM_SETFONT, (WPARAM)CpuSection->Parameters->MediumFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_SPEED), WM_SETFONT, (WPARAM)CpuSection->Parameters->MediumFont, FALSE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_ONEGRAPHPERCPU: + { + OneGraphPerCpu = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ONEGRAPHPERCPU)) == BST_CHECKED; + PhSipLayoutCpuGraphs(); + PhSipSetOneGraphPerCpu(); + } + break; + } + } + break; + } + + return FALSE; +} + +VOID PhSipCreateCpuGraphs( + VOID + ) +{ + ULONG i; + + CpuGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + CpuDialog, + NULL, + PhInstanceHandle, + NULL + ); + Graph_SetTooltip(CpuGraphHandle, TRUE); + + for (i = 0; i < NumberOfProcessors; i++) + { + CpusGraphHandle[i] = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + CpuDialog, + NULL, + PhInstanceHandle, + NULL + ); + Graph_SetTooltip(CpusGraphHandle[i], TRUE); + } +} + +VOID PhSipLayoutCpuGraphs( + VOID + ) +{ + RECT clientRect; + HDWP deferHandle; + + GetClientRect(CpuDialog, &clientRect); + deferHandle = BeginDeferWindowPos(OneGraphPerCpu ? NumberOfProcessors : 1); + + if (!OneGraphPerCpu) + { + deferHandle = DeferWindowPos( + deferHandle, + CpuGraphHandle, + NULL, + CpuGraphMargin.left, + CpuGraphMargin.top, + clientRect.right - CpuGraphMargin.left - CpuGraphMargin.right, + clientRect.bottom - CpuGraphMargin.top - CpuGraphMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + } + else + { + ULONG numberOfRows = 1; + ULONG numberOfColumns = NumberOfProcessors; + + for (ULONG rows = 2; rows <= NumberOfProcessors / rows; rows++) + { + if (NumberOfProcessors % rows != 0) + continue; + + numberOfRows = rows; + numberOfColumns = NumberOfProcessors / rows; + } + + if (numberOfRows == 1) + { + numberOfRows = (ULONG)sqrt(NumberOfProcessors); + numberOfColumns = (NumberOfProcessors + numberOfRows - 1) / numberOfRows; + } + + ULONG numberOfYPaddings = numberOfRows - 1; + ULONG numberOfXPaddings = numberOfColumns - 1; + + ULONG cellHeight = (clientRect.bottom - CpuGraphMargin.top - CpuGraphMargin.bottom - CpuSection->Parameters->CpuPadding * numberOfYPaddings) / numberOfRows; + ULONG y = CpuGraphMargin.top; + ULONG cellWidth; + ULONG x; + ULONG i = 0; + + for (ULONG row = 0; row < numberOfRows; row++) + { + // Give the last row the remaining space; the height we calculated might be off by a few + // pixels due to integer division. + if (row == numberOfRows - 1) + cellHeight = clientRect.bottom - CpuGraphMargin.bottom - y; + + cellWidth = (clientRect.right - CpuGraphMargin.left - CpuGraphMargin.right - CpuSection->Parameters->CpuPadding * numberOfXPaddings) / numberOfColumns; + x = CpuGraphMargin.left; + + for (ULONG column = 0; column < numberOfColumns; column++) + { + // Give the last cell the remaining space; the width we calculated might be off by a few + // pixels due to integer division. + if (column == numberOfColumns - 1) + cellWidth = clientRect.right - CpuGraphMargin.right - x; + + if (i < NumberOfProcessors) + { + deferHandle = DeferWindowPos( + deferHandle, + CpusGraphHandle[i], + NULL, + x, + y, + cellWidth, + cellHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + i++; + } + + x += cellWidth + CpuSection->Parameters->CpuPadding; + } + + y += cellHeight + CpuSection->Parameters->CpuPadding; + } + } + + EndDeferWindowPos(deferHandle); +} + +VOID PhSipSetOneGraphPerCpu( + VOID + ) +{ + ULONG i; + + ShowWindow(CpuGraphHandle, !OneGraphPerCpu ? SW_SHOW : SW_HIDE); + + for (i = 0; i < NumberOfProcessors; i++) + { + ShowWindow(CpusGraphHandle[i], OneGraphPerCpu ? SW_SHOW : SW_HIDE); + } +} + +VOID PhSipNotifyCpuGraph( + _In_ ULONG Index, + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorCpuKernel, PhCsColorCpuUser); + + if (Index == -1) + { + PhGraphStateGetDrawInfo( + &CpuGraphState, + getDrawInfo, + PhCpuKernelHistory.Count + ); + + if (!CpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&PhCpuKernelHistory, CpuGraphState.Data1, drawInfo->LineDataCount); + PhCopyCircularBuffer_FLOAT(&PhCpuUserHistory, CpuGraphState.Data2, drawInfo->LineDataCount); + CpuGraphState.Valid = TRUE; + } + } + else + { + PhGraphStateGetDrawInfo( + &CpusGraphState[Index], + getDrawInfo, + PhCpuKernelHistory.Count + ); + + if (!CpusGraphState[Index].Valid) + { + PhCopyCircularBuffer_FLOAT(&PhCpusKernelHistory[Index], CpusGraphState[Index].Data1, drawInfo->LineDataCount); + PhCopyCircularBuffer_FLOAT(&PhCpusUserHistory[Index], CpusGraphState[Index].Data2, drawInfo->LineDataCount); + CpusGraphState[Index].Valid = TRUE; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (Index == -1) + { + if (CpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT cpuKernel; + FLOAT cpuUser; + + cpuKernel = PhGetItemCircularBuffer_FLOAT(&PhCpuKernelHistory, getTooltipText->Index); + cpuUser = PhGetItemCircularBuffer_FLOAT(&PhCpuUserHistory, getTooltipText->Index); + + PhMoveReference(&CpuGraphState.TooltipText, PhFormatString( + L"%.2f%%%s\n%s", + (cpuKernel + cpuUser) * 100, + PhGetStringOrEmpty(PhSipGetMaxCpuString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = CpuGraphState.TooltipText->sr; + } + else + { + if (CpusGraphState[Index].TooltipIndex != getTooltipText->Index) + { + FLOAT cpuKernel; + FLOAT cpuUser; + + cpuKernel = PhGetItemCircularBuffer_FLOAT(&PhCpusKernelHistory[Index], getTooltipText->Index); + cpuUser = PhGetItemCircularBuffer_FLOAT(&PhCpusUserHistory[Index], getTooltipText->Index); + + PhMoveReference(&CpusGraphState[Index].TooltipText, PhFormatString( + L"%.2f%% (K: %.2f%%, U: %.2f%%)%s\n%s", + (cpuKernel + cpuUser) * 100, + cpuKernel * 100, + cpuUser * 100, + PhGetStringOrEmpty(PhSipGetMaxCpuString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = CpusGraphState[Index].TooltipText->sr; + } + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record; + + record = NULL; + + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = PhSipReferenceMaxCpuRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(CpuDialog, record); + PhDereferenceProcessRecord(record); + } + } + break; + } +} + +VOID PhSipUpdateCpuGraphs( + VOID + ) +{ + ULONG i; + + CpuGraphState.Valid = FALSE; + CpuGraphState.TooltipIndex = -1; + Graph_MoveGrid(CpuGraphHandle, 1); + Graph_Draw(CpuGraphHandle); + Graph_UpdateTooltip(CpuGraphHandle); + InvalidateRect(CpuGraphHandle, NULL, FALSE); + + for (i = 0; i < NumberOfProcessors; i++) + { + CpusGraphState[i].Valid = FALSE; + CpusGraphState[i].TooltipIndex = -1; + Graph_MoveGrid(CpusGraphHandle[i], 1); + Graph_Draw(CpusGraphHandle[i]); + Graph_UpdateTooltip(CpusGraphHandle[i]); + InvalidateRect(CpusGraphHandle[i], NULL, FALSE); + } +} + +VOID PhSipUpdateCpuPanel( + VOID + ) +{ + HWND hwnd = CpuPanel; + DOUBLE cpuFraction; + DOUBLE cpuGhz; + BOOLEAN distributionSucceeded; + SYSTEM_TIMEOFDAY_INFORMATION timeOfDayInfo; + WCHAR uptimeString[PH_TIMESPAN_STR_LEN_1] = L"Unknown"; + + SetDlgItemText(hwnd, IDC_UTILIZATION, PhaFormatString(L"%.2f%%", (PhCpuUserUsage + PhCpuKernelUsage) * 100)->Buffer); + + cpuGhz = 0; + distributionSucceeded = FALSE; + + if (WindowsVersion >= WINDOWS_7 && CurrentPerformanceDistribution && PreviousPerformanceDistribution) + { + if (PhSipGetCpuFrequencyFromDistribution(&cpuFraction)) + { + cpuGhz = (DOUBLE)PowerInformation[0].MaxMhz * cpuFraction / 1000; + distributionSucceeded = TRUE; + } + } + + if (!distributionSucceeded) + cpuGhz = (DOUBLE)PowerInformation[0].CurrentMhz / 1000; + + SetDlgItemText(hwnd, IDC_SPEED, PhaFormatString(L"%.2f / %.2f GHz", cpuGhz, (DOUBLE)PowerInformation[0].MaxMhz / 1000)->Buffer); + + SetDlgItemText(hwnd, IDC_ZPROCESSES_V, PhaFormatUInt64(PhTotalProcesses, TRUE)->Buffer); + SetDlgItemText(hwnd, IDC_ZTHREADS_V, PhaFormatUInt64(PhTotalThreads, TRUE)->Buffer); + SetDlgItemText(hwnd, IDC_ZHANDLES_V, PhaFormatUInt64(PhTotalHandles, TRUE)->Buffer); + + if (NT_SUCCESS(NtQuerySystemInformation( + SystemTimeOfDayInformation, + &timeOfDayInfo, + sizeof(SYSTEM_TIMEOFDAY_INFORMATION), + NULL + ))) + { + PhPrintTimeSpan(uptimeString, timeOfDayInfo.CurrentTime.QuadPart - timeOfDayInfo.BootTime.QuadPart, PH_TIMESPAN_DHMS); + } + + SetDlgItemText(hwnd, IDC_ZUPTIME_V, uptimeString); + + if (CpuTicked > 1) + SetDlgItemText(hwnd, IDC_ZCONTEXTSWITCHESDELTA_V, PhaFormatUInt64(ContextSwitchesDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(hwnd, IDC_ZCONTEXTSWITCHESDELTA_V, L"-"); + + if (CpuTicked > 1) + SetDlgItemText(hwnd, IDC_ZINTERRUPTSDELTA_V, PhaFormatUInt64(InterruptsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(hwnd, IDC_ZINTERRUPTSDELTA_V, L"-"); + + if (CpuTicked > 1) + SetDlgItemText(hwnd, IDC_ZDPCSDELTA_V, PhaFormatUInt64(DpcsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(hwnd, IDC_ZDPCSDELTA_V, L"-"); + + if (CpuTicked > 1) + SetDlgItemText(hwnd, IDC_ZSYSTEMCALLSDELTA_V, PhaFormatUInt64(SystemCallsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(hwnd, IDC_ZSYSTEMCALLSDELTA_V, L"-"); +} + +PPH_PROCESS_RECORD PhSipReferenceMaxCpuRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + LONG maxProcessIdLong; + HANDLE maxProcessId; + + // Find the process record for the max. CPU process for the particular time. + + maxProcessIdLong = PhGetItemCircularBuffer_ULONG(&PhMaxCpuHistory, Index); + + if (!maxProcessIdLong) + return NULL; + + // This must be treated as a signed integer to handle Interrupts correctly. + maxProcessId = LongToHandle(maxProcessIdLong); + + // Note that the time we get has its components beyond seconds cleared. + // For example: + // * At 2.5 seconds a process is started. + // * At 2.75 seconds our process provider is fired, and the process is determined + // to have 75% CPU usage, which happens to be the maximum CPU usage. + // * However the 2.75 seconds is recorded as 2 seconds due to + // RtlTimeToSecondsSince1980. + // * If we call PhFindProcessRecord, it cannot find the process because it was + // started at 2.5 seconds, not 2 seconds or older. + // + // This mean we must add one second minus one tick (100ns) to the time, giving us + // 2.9999999 seconds. This will then make sure we find the process. + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(maxProcessId, &time); +} + +PPH_STRING PhSipGetMaxCpuString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; +#ifdef PH_RECORD_MAX_USAGE + FLOAT maxCpuUsage; +#endif + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = PhSipReferenceMaxCpuRecord(Index)) + { + // We found the process record, so now we construct the max. usage string. +#ifdef PH_RECORD_MAX_USAGE + maxCpuUsage = PhGetItemCircularBuffer_FLOAT(&PhMaxCpuUsageHistory, Index); + + // Make sure we don't try to display the PID of DPCs or Interrupts. + if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) + { + maxUsageString = PhaFormatString( + L"\n%s (%u): %.2f%%", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId), + maxCpuUsage * 100 + ); + } + else + { + maxUsageString = PhaFormatString( + L"\n%s: %.2f%%", + maxProcessRecord->ProcessName->Buffer, + maxCpuUsage * 100 + ); + } +#else + maxUsageString = PhaConcatStrings2(L"\n", maxProcessRecord->ProcessName->Buffer); +#endif + + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} + +VOID PhSipGetCpuBrandString( + _Out_writes_(49) PWSTR BrandString + ) +{ + ULONG brandString[4 * 3]; + + __cpuid(&brandString[0], 0x80000002); + __cpuid(&brandString[4], 0x80000003); + __cpuid(&brandString[8], 0x80000004); + + PhZeroExtendToUtf16Buffer((PSTR)brandString, 48, BrandString); + BrandString[48] = 0; +} + +BOOLEAN PhSipGetCpuFrequencyFromDistribution( + _Out_ DOUBLE *Fraction + ) +{ + ULONG stateSize; + PVOID differences; + PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION stateDistribution; + PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION stateDifference; + PSYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8 hitcountOld; + ULONG i; + ULONG j; + DOUBLE count; + DOUBLE total; + + // Calculate the differences from the last performance distribution. + + if (CurrentPerformanceDistribution->ProcessorCount != NumberOfProcessors || PreviousPerformanceDistribution->ProcessorCount != NumberOfProcessors) + return FALSE; + + stateSize = FIELD_OFFSET(SYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION, States) + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT) * 2; + differences = PhAllocate(stateSize * NumberOfProcessors); + + for (i = 0; i < NumberOfProcessors; i++) + { + stateDistribution = (PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION)((PCHAR)CurrentPerformanceDistribution + CurrentPerformanceDistribution->Offsets[i]); + stateDifference = (PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION)((PCHAR)differences + stateSize * i); + + if (stateDistribution->StateCount != 2) + { + PhFree(differences); + return FALSE; + } + + for (j = 0; j < stateDistribution->StateCount; j++) + { + if (WindowsVersion >= WINDOWS_8_1) + { + stateDifference->States[j] = stateDistribution->States[j]; + } + else + { + hitcountOld = (PSYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8)((PCHAR)stateDistribution->States + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8) * j); + stateDifference->States[j].Hits.QuadPart = hitcountOld->Hits; + stateDifference->States[j].PercentFrequency = hitcountOld->PercentFrequency; + } + } + } + + for (i = 0; i < NumberOfProcessors; i++) + { + stateDistribution = (PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION)((PCHAR)PreviousPerformanceDistribution + PreviousPerformanceDistribution->Offsets[i]); + stateDifference = (PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION)((PCHAR)differences + stateSize * i); + + if (stateDistribution->StateCount != 2) + { + PhFree(differences); + return FALSE; + } + + for (j = 0; j < stateDistribution->StateCount; j++) + { + if (WindowsVersion >= WINDOWS_8_1) + { + stateDifference->States[j].Hits.QuadPart -= stateDistribution->States[j].Hits.QuadPart; + } + else + { + hitcountOld = (PSYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8)((PCHAR)stateDistribution->States + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT_WIN8) * j); + stateDifference->States[j].Hits.QuadPart -= hitcountOld->Hits; + } + } + } + + // Calculate the frequency. + + count = 0; + total = 0; + + for (i = 0; i < NumberOfProcessors; i++) + { + stateDifference = (PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION)((PCHAR)differences + stateSize * i); + + for (j = 0; j < 2; j++) + { + count += (ULONGLONG)stateDifference->States[j].Hits.QuadPart; + total += (ULONGLONG)stateDifference->States[j].Hits.QuadPart * stateDifference->States[j].PercentFrequency; + } + } + + PhFree(differences); + + if (count == 0) + return FALSE; + + total /= count; + total /= 100; + *Fraction = total; + + return TRUE; +} + +NTSTATUS PhSipQueryProcessorPerformanceDistribution( + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + ULONG attempts; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemProcessorPerformanceDistribution, + buffer, + bufferSize, + &bufferSize + ); + attempts = 0; + + while (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 8) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemProcessorPerformanceDistribution, + buffer, + bufferSize, + &bufferSize + ); + attempts++; + } + + if (NT_SUCCESS(status)) + *Buffer = buffer; + else + PhFree(buffer); + + return status; +} diff --git a/ProcessHacker/sysscio.c b/ProcessHacker/sysscio.c new file mode 100644 index 0000000..0001fa1 --- /dev/null +++ b/ProcessHacker/sysscio.c @@ -0,0 +1,535 @@ +/* + * Process Hacker - + * System Information I/O section + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +static PPH_SYSINFO_SECTION IoSection; +static HWND IoDialog; +static PH_LAYOUT_MANAGER IoLayoutManager; +static HWND IoGraphHandle; +static PH_GRAPH_STATE IoGraphState; +static HWND IoPanel; +static ULONG IoTicked; +static PH_UINT64_DELTA IoReadDelta; +static PH_UINT64_DELTA IoWriteDelta; +static PH_UINT64_DELTA IoOtherDelta; + +BOOLEAN PhSipIoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoCreate: + { + IoSection = Section; + } + return TRUE; + case SysInfoDestroy: + { + if (IoDialog) + { + PhSipUninitializeIoDialog(); + IoDialog = NULL; + } + } + break; + case SysInfoTick: + { + if (IoDialog) + { + PhSipTickIoDialog(); + } + } + break; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PhInstanceHandle; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_IO); + createDialog->DialogProc = PhSipIoDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + ULONG i; + FLOAT max; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhCsColorIoReadOther, PhCsColorIoWrite); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, PhIoReadHistory.Count); + + if (!Section->GraphState.Valid) + { + max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + Section->GraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoOtherHistory, i); + Section->GraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + Section->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + Section->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + ULONG64 ioRead; + ULONG64 ioWrite; + ULONG64 ioOther; + + ioRead = PhGetItemCircularBuffer_ULONG64(&PhIoReadHistory, getTooltipText->Index); + ioWrite = PhGetItemCircularBuffer_ULONG64(&PhIoWriteHistory, getTooltipText->Index); + ioOther = PhGetItemCircularBuffer_ULONG64(&PhIoOtherHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\nO: %s%s\n%s", + PhaFormatSize(ioRead, -1)->Buffer, + PhaFormatSize(ioWrite, -1)->Buffer, + PhaFormatSize(ioOther, -1)->Buffer, + PhGetStringOrEmpty(PhSipGetMaxIoString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + + drawPanel->Title = PhCreateString(L"I/O"); + drawPanel->SubTitle = PhFormatString( + L"R+O: %s\nW: %s", + PhSipFormatSizeWithPrecision(PhIoReadDelta.Delta + PhIoOtherDelta.Delta, 1)->Buffer, + PhSipFormatSizeWithPrecision(PhIoWriteDelta.Delta, 1)->Buffer + ); + } + return TRUE; + } + + return FALSE; +} + +VOID PhSipInitializeIoDialog( + VOID + ) +{ + PhInitializeDelta(&IoReadDelta); + PhInitializeDelta(&IoWriteDelta); + PhInitializeDelta(&IoOtherDelta); + + PhInitializeGraphState(&IoGraphState); + + IoTicked = 0; +} + +VOID PhSipUninitializeIoDialog( + VOID + ) +{ + PhDeleteGraphState(&IoGraphState); +} + +VOID PhSipTickIoDialog( + VOID + ) +{ + PhUpdateDelta(&IoReadDelta, PhPerfInformation.IoReadOperationCount); + PhUpdateDelta(&IoWriteDelta, PhPerfInformation.IoWriteOperationCount); + PhUpdateDelta(&IoOtherDelta, PhPerfInformation.IoOtherOperationCount); + + IoTicked++; + + if (IoTicked > 2) + IoTicked = 2; + + PhSipUpdateIoGraph(); + PhSipUpdateIoPanel(); +} + +INT_PTR CALLBACK PhSipIoDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + PhSipInitializeIoDialog(); + + IoDialog = hwndDlg; + PhInitializeLayoutManager(&IoLayoutManager, hwndDlg); + graphItem = PhAddLayoutItem(&IoLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + panelItem = PhAddLayoutItem(&IoLayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)IoSection->Parameters->LargeFont, FALSE); + + IoPanel = CreateDialog(PhInstanceHandle, MAKEINTRESOURCE(IDD_SYSINFO_IOPANEL), hwndDlg, PhSipIoPanelDialogProc); + ShowWindow(IoPanel, SW_SHOW); + PhAddLayoutItemEx(&IoLayoutManager, IoPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + IoGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + IoDialog, + (HMENU)IDC_IO, + PhInstanceHandle, + NULL + ); + Graph_SetTooltip(IoGraphHandle, TRUE); + + PhAddLayoutItemEx(&IoLayoutManager, IoGraphHandle, NULL, PH_ANCHOR_ALL, graphItem->Margin); + + PhSipUpdateIoGraph(); + PhSipUpdateIoPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&IoLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&IoLayoutManager); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + if (header->hwndFrom == IoGraphHandle) + { + PhSipNotifyIoGraph(header); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhSipIoPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + NOTHING; + } + break; + } + + return FALSE; +} + +VOID PhSipNotifyIoGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorIoReadOther, PhCsColorIoWrite); + + PhGraphStateGetDrawInfo( + &IoGraphState, + getDrawInfo, + PhIoReadHistory.Count + ); + + if (!IoGraphState.Valid) + { + FLOAT max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + IoGraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoOtherHistory, i); + IoGraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG64(&PhIoWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + IoGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + IoGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + IoGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (IoGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 ioRead; + ULONG64 ioWrite; + ULONG64 ioOther; + + ioRead = PhGetItemCircularBuffer_ULONG64(&PhIoReadHistory, getTooltipText->Index); + ioWrite = PhGetItemCircularBuffer_ULONG64(&PhIoWriteHistory, getTooltipText->Index); + ioOther = PhGetItemCircularBuffer_ULONG64(&PhIoOtherHistory, getTooltipText->Index); + + PhMoveReference(&IoGraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\nO: %s%s\n%s", + PhaFormatSize(ioRead, -1)->Buffer, + PhaFormatSize(ioWrite, -1)->Buffer, + PhaFormatSize(ioOther, -1)->Buffer, + PhGetStringOrEmpty(PhSipGetMaxIoString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = IoGraphState.TooltipText->sr; + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record; + + record = NULL; + + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = PhSipReferenceMaxIoRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(IoDialog, record); + PhDereferenceProcessRecord(record); + } + } + break; + } +} + +VOID PhSipUpdateIoGraph( + VOID + ) +{ + IoGraphState.Valid = FALSE; + IoGraphState.TooltipIndex = -1; + Graph_MoveGrid(IoGraphHandle, 1); + Graph_Draw(IoGraphHandle); + Graph_UpdateTooltip(IoGraphHandle); + InvalidateRect(IoGraphHandle, NULL, FALSE); +} + +VOID PhSipUpdateIoPanel( + VOID + ) +{ + // I/O Deltas + + if (IoTicked > 1) + { + SetDlgItemText(IoPanel, IDC_ZREADSDELTA_V, PhaFormatUInt64(IoReadDelta.Delta, TRUE)->Buffer); + SetDlgItemText(IoPanel, IDC_ZWRITESDELTA_V, PhaFormatUInt64(IoWriteDelta.Delta, TRUE)->Buffer); + SetDlgItemText(IoPanel, IDC_ZOTHERDELTA_V, PhaFormatUInt64(IoOtherDelta.Delta, TRUE)->Buffer); + } + else + { + SetDlgItemText(IoPanel, IDC_ZREADSDELTA_V, L"-"); + SetDlgItemText(IoPanel, IDC_ZWRITESDELTA_V, L"-"); + SetDlgItemText(IoPanel, IDC_ZOTHERDELTA_V, L"-"); + } + + if (PhIoReadHistory.Count != 0) + { + SetDlgItemText(IoPanel, IDC_ZREADBYTESDELTA_V, PhaFormatSize(PhIoReadDelta.Delta, -1)->Buffer); + SetDlgItemText(IoPanel, IDC_ZWRITEBYTESDELTA_V, PhaFormatSize(PhIoWriteDelta.Delta, -1)->Buffer); + SetDlgItemText(IoPanel, IDC_ZOTHERBYTESDELTA_V, PhaFormatSize(PhIoOtherDelta.Delta, -1)->Buffer); + } + else + { + SetDlgItemText(IoPanel, IDC_ZREADBYTESDELTA_V, L"-"); + SetDlgItemText(IoPanel, IDC_ZWRITEBYTESDELTA_V, L"-"); + SetDlgItemText(IoPanel, IDC_ZOTHERBYTESDELTA_V, L"-"); + } + + // I/O Totals + + SetDlgItemText(IoPanel, IDC_ZREADS_V, PhaFormatUInt64(PhPerfInformation.IoReadOperationCount, TRUE)->Buffer); + SetDlgItemText(IoPanel, IDC_ZREADBYTES_V, PhaFormatSize(PhPerfInformation.IoReadTransferCount.QuadPart, -1)->Buffer); + SetDlgItemText(IoPanel, IDC_ZWRITES_V, PhaFormatUInt64(PhPerfInformation.IoWriteOperationCount, TRUE)->Buffer); + SetDlgItemText(IoPanel, IDC_ZWRITEBYTES_V, PhaFormatSize(PhPerfInformation.IoWriteTransferCount.QuadPart, -1)->Buffer); + SetDlgItemText(IoPanel, IDC_ZOTHER_V, PhaFormatUInt64(PhPerfInformation.IoOtherOperationCount, TRUE)->Buffer); + SetDlgItemText(IoPanel, IDC_ZOTHERBYTES_V, PhaFormatSize(PhPerfInformation.IoOtherTransferCount.QuadPart, -1)->Buffer); +} + +PPH_PROCESS_RECORD PhSipReferenceMaxIoRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + ULONG maxProcessId; + + // Find the process record for the max. I/O process for the particular time. + + maxProcessId = PhGetItemCircularBuffer_ULONG(&PhMaxIoHistory, Index); + + if (!maxProcessId) + return NULL; + + // See above for the explanation. + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); +} + +PPH_STRING PhSipGetMaxIoString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; +#ifdef PH_RECORD_MAX_USAGE + ULONG64 maxIoReadOther; + ULONG64 maxIoWrite; +#endif + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = PhSipReferenceMaxIoRecord(Index)) + { + // We found the process record, so now we construct the max. usage string. +#ifdef PH_RECORD_MAX_USAGE + maxIoReadOther = PhGetItemCircularBuffer_ULONG64(&PhMaxIoReadOtherHistory, Index); + maxIoWrite = PhGetItemCircularBuffer_ULONG64(&PhMaxIoWriteHistory, Index); + + if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) + { + maxUsageString = PhaFormatString( + L"\n%s (%u): R+O: %s, W: %s", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId), + PhaFormatSize(maxIoReadOther, -1)->Buffer, + PhaFormatSize(maxIoWrite, -1)->Buffer + ); + } + else + { + maxUsageString = PhaFormatString( + L"\n%s: R+O: %s, W: %s", + maxProcessRecord->ProcessName->Buffer, + PhaFormatSize(maxIoReadOther, -1)->Buffer, + PhaFormatSize(maxIoWrite, -1)->Buffer + ); + } +#else + maxUsageString = PhaConcatStrings2(L"\n", maxProcessRecord->ProcessName->Buffer); +#endif + + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} diff --git a/ProcessHacker/sysscmem.c b/ProcessHacker/sysscmem.c new file mode 100644 index 0000000..a339618 --- /dev/null +++ b/ProcessHacker/sysscmem.c @@ -0,0 +1,924 @@ +/* + * Process Hacker - + * System Information memory section + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static PPH_SYSINFO_SECTION MemorySection; +static HWND MemoryDialog; +static PH_LAYOUT_MANAGER MemoryLayoutManager; +static RECT MemoryGraphMargin; +static HWND CommitGraphHandle; +static PH_GRAPH_STATE CommitGraphState; +static HWND PhysicalGraphHandle; +static PH_GRAPH_STATE PhysicalGraphState; +static HWND MemoryPanel; +static ULONG MemoryTicked; +static PH_UINT32_DELTA PagedAllocsDelta; +static PH_UINT32_DELTA PagedFreesDelta; +static PH_UINT32_DELTA NonPagedAllocsDelta; +static PH_UINT32_DELTA NonPagedFreesDelta; +static PH_UINT32_DELTA PageFaultsDelta; +static PH_UINT32_DELTA PageReadsDelta; +static PH_UINT32_DELTA PagefileWritesDelta; +static PH_UINT32_DELTA MappedWritesDelta; +static BOOLEAN MmAddressesInitialized; +static PSIZE_T MmSizeOfPagedPoolInBytes; +static PSIZE_T MmMaximumNonPagedPoolInBytes; +static ULONGLONG InstalledMemory; + +BOOLEAN PhSipMemorySectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoCreate: + { + MemorySection = Section; + } + return TRUE; + case SysInfoDestroy: + { + if (MemoryDialog) + { + PhSipUninitializeMemoryDialog(); + MemoryDialog = NULL; + } + } + break; + case SysInfoTick: + { + if (MemoryDialog) + { + PhSipTickMemoryDialog(); + } + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PhInstanceHandle; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_MEM); + createDialog->DialogProc = PhSipMemoryDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + ULONG i; + + if (PhGetIntegerSetting(L"ShowCommitInSummary")) + { + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhCsColorPrivate, 0); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, PhCommitHistory.Count); + + if (!Section->GraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + Section->GraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhCommitHistory, i); + } + + if (PhPerfInformation.CommitLimit != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + Section->GraphState.Data1, + (FLOAT)PhPerfInformation.CommitLimit, + drawInfo->LineDataCount + ); + } + + Section->GraphState.Valid = TRUE; + } + } + else + { + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhCsColorPhysical, 0); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, PhPhysicalHistory.Count); + + if (!Section->GraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + Section->GraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, i); + } + + if (PhSystemBasicInformation.NumberOfPhysicalPages != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + Section->GraphState.Data1, + (FLOAT)PhSystemBasicInformation.NumberOfPhysicalPages, + drawInfo->LineDataCount + ); + } + + Section->GraphState.Valid = TRUE; + } + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + ULONG usedPages; + + if (PhGetIntegerSetting(L"ShowCommitInSummary")) + { + usedPages = PhGetItemCircularBuffer_ULONG(&PhCommitHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"Commit charge: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + else + { + usedPages = PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"Physical memory: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + ULONG totalPages; + ULONG usedPages; + + if (PhGetIntegerSetting(L"ShowCommitInSummary")) + { + totalPages = PhPerfInformation.CommitLimit; + usedPages = PhPerfInformation.CommittedPages; + } + else + { + totalPages = PhSystemBasicInformation.NumberOfPhysicalPages; + usedPages = totalPages - PhPerfInformation.AvailablePages; + } + + drawPanel->Title = PhCreateString(L"Memory"); + drawPanel->SubTitle = PhFormatString( + L"%.0f%%\n%s / %s", + (FLOAT)usedPages * 100 / totalPages, + PhSipFormatSizeWithPrecision(UInt32x32To64(usedPages, PAGE_SIZE), 1)->Buffer, + PhSipFormatSizeWithPrecision(UInt32x32To64(totalPages, PAGE_SIZE), 1)->Buffer + ); + drawPanel->SubTitleOverflow = PhFormatString( + L"%.0f%%\n%s", + (FLOAT)usedPages * 100 / totalPages, + PhSipFormatSizeWithPrecision(UInt32x32To64(usedPages, PAGE_SIZE), 1)->Buffer + ); + } + return TRUE; + } + + return FALSE; +} + +VOID PhSipInitializeMemoryDialog( + VOID + ) +{ + PhInitializeDelta(&PagedAllocsDelta); + PhInitializeDelta(&PagedFreesDelta); + PhInitializeDelta(&NonPagedAllocsDelta); + PhInitializeDelta(&NonPagedFreesDelta); + PhInitializeDelta(&PageFaultsDelta); + PhInitializeDelta(&PageReadsDelta); + PhInitializeDelta(&PagefileWritesDelta); + PhInitializeDelta(&MappedWritesDelta); + + PhInitializeGraphState(&CommitGraphState); + PhInitializeGraphState(&PhysicalGraphState); + + MemoryTicked = 0; + + if (!MmAddressesInitialized && KphIsConnected()) + { + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhSipLoadMmAddresses, NULL); + MmAddressesInitialized = TRUE; + } +} + +VOID PhSipUninitializeMemoryDialog( + VOID + ) +{ + PhDeleteGraphState(&CommitGraphState); + PhDeleteGraphState(&PhysicalGraphState); +} + +VOID PhSipTickMemoryDialog( + VOID + ) +{ + PhUpdateDelta(&PagedAllocsDelta, PhPerfInformation.PagedPoolAllocs); + PhUpdateDelta(&PagedFreesDelta, PhPerfInformation.PagedPoolFrees); + PhUpdateDelta(&NonPagedAllocsDelta, PhPerfInformation.NonPagedPoolAllocs); + PhUpdateDelta(&NonPagedFreesDelta, PhPerfInformation.NonPagedPoolFrees); + PhUpdateDelta(&PageFaultsDelta, PhPerfInformation.PageFaultCount); + PhUpdateDelta(&PageReadsDelta, PhPerfInformation.PageReadCount); + PhUpdateDelta(&PagefileWritesDelta, PhPerfInformation.DirtyPagesWriteCount); + PhUpdateDelta(&MappedWritesDelta, PhPerfInformation.MappedPagesWriteCount); + + MemoryTicked++; + + if (MemoryTicked > 2) + MemoryTicked = 2; + + PhSipUpdateMemoryGraphs(); + PhSipUpdateMemoryPanel(); +} + +INT_PTR CALLBACK PhSipMemoryDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + static BOOL (WINAPI *getPhysicallyInstalledSystemMemory)(PULONGLONG) = NULL; + + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + PhSipInitializeMemoryDialog(); + + MemoryDialog = hwndDlg; + PhInitializeLayoutManager(&MemoryLayoutManager, hwndDlg); + PhAddLayoutItem(&MemoryLayoutManager, GetDlgItem(hwndDlg, IDC_TOTALPHYSICAL), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&MemoryLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + MemoryGraphMargin = graphItem->Margin; + panelItem = PhAddLayoutItem(&MemoryLayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)MemorySection->Parameters->LargeFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_TOTALPHYSICAL), WM_SETFONT, (WPARAM)MemorySection->Parameters->MediumFont, FALSE); + + if (!getPhysicallyInstalledSystemMemory) + getPhysicallyInstalledSystemMemory = PhGetModuleProcAddress(L"kernel32.dll", "GetPhysicallyInstalledSystemMemory"); + + InstalledMemory = 0; + + if (getPhysicallyInstalledSystemMemory && getPhysicallyInstalledSystemMemory(&InstalledMemory)) + { + SetDlgItemText(hwndDlg, IDC_TOTALPHYSICAL, + PhaConcatStrings2(PhaFormatSize(InstalledMemory * 1024, -1)->Buffer, L" installed")->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_TOTALPHYSICAL, + PhaConcatStrings2(PhaFormatSize(UInt32x32To64(PhSystemBasicInformation.NumberOfPhysicalPages, PAGE_SIZE), -1)->Buffer, L" total")->Buffer); + } + + MemoryPanel = CreateDialog( + PhInstanceHandle, + WindowsVersion >= WINDOWS_VISTA ? MAKEINTRESOURCE(IDD_SYSINFO_MEMPANEL) : MAKEINTRESOURCE(IDD_SYSINFO_MEMPANELXP), + hwndDlg, + PhSipMemoryPanelDialogProc + ); + ShowWindow(MemoryPanel, SW_SHOW); + PhAddLayoutItemEx(&MemoryLayoutManager, MemoryPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + CommitGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + MemoryDialog, + (HMENU)IDC_COMMIT, + PhInstanceHandle, + NULL + ); + Graph_SetTooltip(CommitGraphHandle, TRUE); + + PhysicalGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + MemoryDialog, + (HMENU)IDC_PHYSICAL, + PhInstanceHandle, + NULL + ); + Graph_SetTooltip(PhysicalGraphHandle, TRUE); + + PhSipUpdateMemoryGraphs(); + PhSipUpdateMemoryPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&MemoryLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&MemoryLayoutManager); + PhSipLayoutMemoryGraphs(); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + if (header->hwndFrom == CommitGraphHandle) + { + PhSipNotifyCommitGraph(header); + } + else if (header->hwndFrom == PhysicalGraphHandle) + { + PhSipNotifyPhysicalGraph(header); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhSipMemoryPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + NOTHING; + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_MORE: + { + PhShowMemoryListsDialog(PhSipWindow, PhSipRegisterDialog, PhSipUnregisterDialog); + } + break; + } + } + break; + } + + return FALSE; +} + +VOID PhSipLayoutMemoryGraphs( + VOID + ) +{ + RECT clientRect; + RECT labelRect; + ULONG graphWidth; + ULONG graphHeight; + HDWP deferHandle; + ULONG y; + + GetClientRect(MemoryDialog, &clientRect); + GetClientRect(GetDlgItem(MemoryDialog, IDC_COMMIT_L), &labelRect); + graphWidth = clientRect.right - MemoryGraphMargin.left - MemoryGraphMargin.right; + graphHeight = (clientRect.bottom - MemoryGraphMargin.top - MemoryGraphMargin.bottom - labelRect.bottom * 2 - MemorySection->Parameters->MemoryPadding * 3) / 2; + + deferHandle = BeginDeferWindowPos(4); + y = MemoryGraphMargin.top; + + deferHandle = DeferWindowPos( + deferHandle, + GetDlgItem(MemoryDialog, IDC_COMMIT_L), + NULL, + MemoryGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + MemorySection->Parameters->MemoryPadding; + + deferHandle = DeferWindowPos( + deferHandle, + CommitGraphHandle, + NULL, + MemoryGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + MemorySection->Parameters->MemoryPadding; + + deferHandle = DeferWindowPos( + deferHandle, + GetDlgItem(MemoryDialog, IDC_PHYSICAL_L), + NULL, + MemoryGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + MemorySection->Parameters->MemoryPadding; + + deferHandle = DeferWindowPos( + deferHandle, + PhysicalGraphHandle, + NULL, + MemoryGraphMargin.left, + y, + graphWidth, + clientRect.bottom - MemoryGraphMargin.bottom - y, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); +} + +VOID PhSipNotifyCommitGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorPrivate, 0); + + PhGraphStateGetDrawInfo( + &CommitGraphState, + getDrawInfo, + PhCommitHistory.Count + ); + + if (!CommitGraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + CommitGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhCommitHistory, i); + } + + if (PhPerfInformation.CommitLimit != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + CommitGraphState.Data1, + (FLOAT)PhPerfInformation.CommitLimit, + drawInfo->LineDataCount + ); + } + + CommitGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (CommitGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG usedPages; + + usedPages = PhGetItemCircularBuffer_ULONG(&PhCommitHistory, getTooltipText->Index); + + PhMoveReference(&CommitGraphState.TooltipText, PhFormatString( + L"Commit charge: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = CommitGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID PhSipNotifyPhysicalGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhCsColorPhysical, 0); + + PhGraphStateGetDrawInfo( + &PhysicalGraphState, + getDrawInfo, + PhPhysicalHistory.Count + ); + + if (!PhysicalGraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + PhysicalGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, i); + } + + if (PhSystemBasicInformation.NumberOfPhysicalPages != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + PhysicalGraphState.Data1, + (FLOAT)PhSystemBasicInformation.NumberOfPhysicalPages, + drawInfo->LineDataCount + ); + } + + PhysicalGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (PhysicalGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG usedPages; + + usedPages = PhGetItemCircularBuffer_ULONG(&PhPhysicalHistory, getTooltipText->Index); + + PhMoveReference(&PhysicalGraphState.TooltipText, PhFormatString( + L"Physical memory: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = PhysicalGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID PhSipUpdateMemoryGraphs( + VOID + ) +{ + CommitGraphState.Valid = FALSE; + CommitGraphState.TooltipIndex = -1; + Graph_MoveGrid(CommitGraphHandle, 1); + Graph_Draw(CommitGraphHandle); + Graph_UpdateTooltip(CommitGraphHandle); + InvalidateRect(CommitGraphHandle, NULL, FALSE); + + PhysicalGraphState.Valid = FALSE; + PhysicalGraphState.TooltipIndex = -1; + Graph_MoveGrid(PhysicalGraphHandle, 1); + Graph_Draw(PhysicalGraphHandle); + Graph_UpdateTooltip(PhysicalGraphHandle); + InvalidateRect(PhysicalGraphHandle, NULL, FALSE); +} + +VOID PhSipUpdateMemoryPanel( + VOID + ) +{ + PWSTR pagedLimit; + PWSTR nonPagedLimit; + SYSTEM_MEMORY_LIST_INFORMATION memoryListInfo; + + // Commit charge + + SetDlgItemText(MemoryPanel, IDC_ZCOMMITCURRENT_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.CommittedPages, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZCOMMITPEAK_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.PeakCommitment, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZCOMMITLIMIT_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.CommitLimit, PAGE_SIZE), -1)->Buffer); + + // Physical memory + + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALCURRENT_V, + PhaFormatSize(UInt32x32To64(PhSystemBasicInformation.NumberOfPhysicalPages - PhPerfInformation.AvailablePages, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALTOTAL_V, + PhaFormatSize(UInt32x32To64(PhSystemBasicInformation.NumberOfPhysicalPages, PAGE_SIZE), -1)->Buffer); + + if (InstalledMemory != 0) + { + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALRESERVED_V, + PhaFormatSize(InstalledMemory * 1024 - UInt32x32To64(PhSystemBasicInformation.NumberOfPhysicalPages, PAGE_SIZE), -1)->Buffer); + } + else + { + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALRESERVED_V, L"-"); + } + + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALCACHEWS_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.ResidentSystemCachePage, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALKERNELWS_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.ResidentSystemCodePage, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZPHYSICALDRIVERWS_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.ResidentSystemDriverPage, PAGE_SIZE), -1)->Buffer); + + // Paged pool + + SetDlgItemText(MemoryPanel, IDC_ZPAGEDWORKINGSET_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.ResidentPagedPoolPage, PAGE_SIZE), -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZPAGEDVIRTUALSIZE_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.PagedPoolPages, PAGE_SIZE), -1)->Buffer); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGEDALLOCSDELTA_V, PhaFormatUInt64(PagedAllocsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGEDALLOCSDELTA_V, L"-"); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGEDFREESDELTA_V, PhaFormatUInt64(PagedFreesDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGEDFREESDELTA_V, L"-"); + + // Non-paged pool + + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDUSAGE_V, + PhaFormatSize(UInt32x32To64(PhPerfInformation.NonPagedPoolPages, PAGE_SIZE), -1)->Buffer); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDALLOCSDELTA_V, PhaFormatUInt64(PagedAllocsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDALLOCSDELTA_V, L"-"); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDFREESDELTA_V, PhaFormatUInt64(NonPagedFreesDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDFREESDELTA_V, L"-"); + + // Pools (KPH) + + if (MmAddressesInitialized && (MmSizeOfPagedPoolInBytes || MmMaximumNonPagedPoolInBytes)) + { + SIZE_T paged; + SIZE_T nonPaged; + + PhSipGetPoolLimits(&paged, &nonPaged); + + if (paged != -1) + pagedLimit = PhaFormatSize(paged, -1)->Buffer; + else + pagedLimit = L"N/A"; + + if (nonPaged != -1) + nonPagedLimit = PhaFormatSize(nonPaged, -1)->Buffer; + else + nonPagedLimit = L"N/A"; + } + else + { + if (!KphIsConnected()) + { + pagedLimit = nonPagedLimit = L"no driver"; + } + else + { + pagedLimit = nonPagedLimit = L"no symbols"; + } + } + + SetDlgItemText(MemoryPanel, IDC_ZPAGEDLIMIT_V, pagedLimit); + SetDlgItemText(MemoryPanel, IDC_ZNONPAGEDLIMIT_V, nonPagedLimit); + + // Paging + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEFAULTSDELTA_V, PhaFormatUInt64(PageFaultsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEFAULTSDELTA_V, L"-"); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEREADSDELTA_V, PhaFormatUInt64(PageReadsDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEREADSDELTA_V, L"-"); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEFILEWRITESDELTA_V, PhaFormatUInt64(PagefileWritesDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGINGPAGEFILEWRITESDELTA_V, L"-"); + + if (MemoryTicked > 1) + SetDlgItemText(MemoryPanel, IDC_ZPAGINGMAPPEDWRITESDELTA_V, PhaFormatUInt64(MappedWritesDelta.Delta, TRUE)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZPAGINGMAPPEDWRITESDELTA_V, L"-"); + + // Memory lists + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (NT_SUCCESS(NtQuerySystemInformation( + SystemMemoryListInformation, + &memoryListInfo, + sizeof(SYSTEM_MEMORY_LIST_INFORMATION), + NULL + ))) + { + ULONG_PTR standbyPageCount; + ULONG_PTR repurposedPageCount; + ULONG i; + + standbyPageCount = 0; + repurposedPageCount = 0; + + for (i = 0; i < 8; i++) + { + standbyPageCount += memoryListInfo.PageCountByPriority[i]; + repurposedPageCount += memoryListInfo.RepurposedPagesByPriority[i]; + } + + SetDlgItemText(MemoryPanel, IDC_ZLISTZEROED_V, PhaFormatSize((ULONG64)memoryListInfo.ZeroPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTFREE_V, PhaFormatSize((ULONG64)memoryListInfo.FreePageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIED_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIEDNOWRITE_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedNoWritePageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY_V, PhaFormatSize((ULONG64)standbyPageCount * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY0_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[0] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY1_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[1] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY2_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[2] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY3_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[3] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY4_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[4] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY5_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[5] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY6_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[6] * PAGE_SIZE, -1)->Buffer); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY7_V, PhaFormatSize((ULONG64)memoryListInfo.PageCountByPriority[7] * PAGE_SIZE, -1)->Buffer); + + if (WindowsVersion >= WINDOWS_8) + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIEDPAGEFILE_V, PhaFormatSize((ULONG64)memoryListInfo.ModifiedPageCountPageFile * PAGE_SIZE, -1)->Buffer); + else + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIEDPAGEFILE_V, L"N/A"); + } + else + { + SetDlgItemText(MemoryPanel, IDC_ZLISTZEROED_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTFREE_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIED_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIEDNOWRITE_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTMODIFIEDPAGEFILE_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY0_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY1_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY2_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY3_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY4_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY5_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY6_V, L"N/A"); + SetDlgItemText(MemoryPanel, IDC_ZLISTSTANDBY7_V, L"N/A"); + } + } +} + +NTSTATUS PhSipLoadMmAddresses( + _In_ PVOID Parameter + ) +{ + PRTL_PROCESS_MODULES kernelModules; + PPH_SYMBOL_PROVIDER symbolProvider; + PPH_STRING kernelFileName; + PPH_STRING newFileName; + PH_SYMBOL_INFORMATION symbolInfo; + + if (NT_SUCCESS(PhEnumKernelModules(&kernelModules))) + { + if (kernelModules->NumberOfModules >= 1) + { + symbolProvider = PhCreateSymbolProvider(NULL); + PhLoadSymbolProviderOptions(symbolProvider); + + kernelFileName = PH_AUTO(PhConvertMultiByteToUtf16(kernelModules->Modules[0].FullPathName)); + newFileName = PH_AUTO(PhGetFileName(kernelFileName)); + + PhLoadModuleSymbolProvider( + symbolProvider, + newFileName->Buffer, + (ULONG64)kernelModules->Modules[0].ImageBase, + kernelModules->Modules[0].ImageSize + ); + + if (PhGetSymbolFromName( + symbolProvider, + L"MmSizeOfPagedPoolInBytes", + &symbolInfo + )) + { + MmSizeOfPagedPoolInBytes = (PSIZE_T)symbolInfo.Address; + } + + if (PhGetSymbolFromName( + symbolProvider, + L"MmMaximumNonPagedPoolInBytes", + &symbolInfo + )) + { + MmMaximumNonPagedPoolInBytes = (PSIZE_T)symbolInfo.Address; + } + + PhDereferenceObject(symbolProvider); + } + + PhFree(kernelModules); + } + + return STATUS_SUCCESS; +} + +VOID PhSipGetPoolLimits( + _Out_ PSIZE_T Paged, + _Out_ PSIZE_T NonPaged + ) +{ + SIZE_T paged = -1; + SIZE_T nonPaged = -1; + + if (MmSizeOfPagedPoolInBytes && WindowsVersion < WINDOWS_8) + { + KphReadVirtualMemoryUnsafe( + NtCurrentProcess(), + MmSizeOfPagedPoolInBytes, + &paged, + sizeof(SIZE_T), + NULL + ); + } + + if (MmMaximumNonPagedPoolInBytes) + { + KphReadVirtualMemoryUnsafe( + NtCurrentProcess(), + MmMaximumNonPagedPoolInBytes, + &nonPaged, + sizeof(SIZE_T), + NULL + ); + } + + *Paged = paged; + *NonPaged = nonPaged; +} diff --git a/ProcessHacker/thrdlist.c b/ProcessHacker/thrdlist.c new file mode 100644 index 0000000..9011c6e --- /dev/null +++ b/ProcessHacker/thrdlist.c @@ -0,0 +1,720 @@ +/* + * Process Hacker - + * thread list + * + * Copyright (C) 2011-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +BOOLEAN PhpThreadNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG PhpThreadNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpDestroyThreadNode( + _In_ PPH_THREAD_NODE ThreadNode + ); + +VOID PhpRemoveThreadNode( + _In_ PPH_THREAD_NODE ThreadNode, + _In_ PPH_THREAD_LIST_CONTEXT Context + ); + +LONG PhpThreadTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ); + +BOOLEAN NTAPI PhpThreadTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID PhInitializeThreadList( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + HWND hwnd; + + memset(Context, 0, sizeof(PH_THREAD_LIST_CONTEXT)); + Context->EnableStateHighlighting = TRUE; + + Context->NodeHashtable = PhCreateHashtable( + sizeof(PPH_THREAD_NODE), + PhpThreadNodeHashtableEqualFunction, + PhpThreadNodeHashtableHashFunction, + 100 + ); + Context->NodeList = PhCreateList(100); + + Context->ParentWindowHandle = ParentWindowHandle; + Context->TreeNewHandle = TreeNewHandle; + hwnd = TreeNewHandle; + PhSetControlTheme(hwnd, L"explorer"); + + TreeNew_SetCallback(hwnd, PhpThreadTreeNewCallback, Context); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, PHTHTLC_TID, TRUE, L"TID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT); + PhAddTreeNewColumnEx(hwnd, PHTHTLC_CPU, TRUE, L"CPU", 45, PH_ALIGN_RIGHT, 1, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, PHTHTLC_CYCLESDELTA, TRUE, L"Cycles delta", 80, PH_ALIGN_RIGHT, 2, DT_RIGHT, TRUE); + PhAddTreeNewColumn(hwnd, PHTHTLC_STARTADDRESS, TRUE, L"Start address", 180, PH_ALIGN_LEFT, 3, 0); + PhAddTreeNewColumnEx(hwnd, PHTHTLC_PRIORITY, TRUE, L"Priority", 80, PH_ALIGN_LEFT, 4, 0, TRUE); + PhAddTreeNewColumn(hwnd, PHTHTLC_SERVICE, FALSE, L"Service", 100, PH_ALIGN_LEFT, -1, 0); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetSort(hwnd, PHTHTLC_CYCLESDELTA, DescendingSortOrder); + + PhCmInitializeManager(&Context->Cm, hwnd, PHTHTLC_MAXIMUM, PhpThreadTreeNewPostSortFunction); +} + +VOID PhDeleteThreadList( + _In_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + ULONG i; + + PhCmDeleteManager(&Context->Cm); + + for (i = 0; i < Context->NodeList->Count; i++) + PhpDestroyThreadNode(Context->NodeList->Items[i]); + + PhDereferenceObject(Context->NodeHashtable); + PhDereferenceObject(Context->NodeList); +} + +BOOLEAN PhpThreadNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_THREAD_NODE threadNode1 = *(PPH_THREAD_NODE *)Entry1; + PPH_THREAD_NODE threadNode2 = *(PPH_THREAD_NODE *)Entry2; + + return threadNode1->ThreadId == threadNode2->ThreadId; +} + +ULONG PhpThreadNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return HandleToUlong((*(PPH_THREAD_NODE *)Entry)->ThreadId) / 4; +} + +VOID PhLoadSettingsThreadList( + _Inout_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + PH_TREENEW_COLUMN column; + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + if (!Context->UseCycleTime) + { + column.Id = PHTHTLC_CYCLESDELTA; + column.Text = L"Context switches delta"; + TreeNew_SetColumn(Context->TreeNewHandle, TN_COLUMN_TEXT, &column); + } + + if (Context->HasServices) + { + column.Id = PHTHTLC_SERVICE; + column.Visible = TRUE; + TreeNew_SetColumn(Context->TreeNewHandle, TN_COLUMN_FLAG_VISIBLE, &column); + } + + settings = PhGetStringSetting(L"ThreadTreeListColumns"); + sortSettings = PhGetStringSetting(L"ThreadTreeListSort"); + PhCmLoadSettingsEx(Context->TreeNewHandle, &Context->Cm, PH_CM_COLUMN_WIDTHS_ONLY, &settings->sr, &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); + + TreeNew_GetSort(Context->TreeNewHandle, &sortColumn, &sortOrder); + + // Make sure we're not sorting by an invisible column. + if (sortOrder != NoSortOrder && !(TreeNew_GetColumn(Context->TreeNewHandle, sortColumn, &column) && column.Visible)) + { + TreeNew_SetSort(Context->TreeNewHandle, PHTHTLC_CYCLESDELTA, DescendingSortOrder); + } +} + +VOID PhSaveSettingsThreadList( + _Inout_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + PPH_STRING settings; + PPH_STRING sortSettings; + + settings = PhCmSaveSettingsEx(Context->TreeNewHandle, &Context->Cm, PH_CM_COLUMN_WIDTHS_ONLY, &sortSettings); + PhSetStringSetting2(L"ThreadTreeListColumns", &settings->sr); + PhSetStringSetting2(L"ThreadTreeListSort", &sortSettings->sr); + PhDereferenceObject(settings); + PhDereferenceObject(sortSettings); +} + +PPH_THREAD_NODE PhAddThreadNode( + _Inout_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_ITEM ThreadItem, + _In_ BOOLEAN FirstRun + ) +{ + PPH_THREAD_NODE threadNode; + + threadNode = PhAllocate(PhEmGetObjectSize(EmThreadNodeType, sizeof(PH_THREAD_NODE))); + memset(threadNode, 0, sizeof(PH_THREAD_NODE)); + PhInitializeTreeNewNode(&threadNode->Node); + + if (Context->EnableStateHighlighting && !FirstRun) + { + PhChangeShStateTn( + &threadNode->Node, + &threadNode->ShState, + &Context->NodeStateList, + NewItemState, + PhCsColorNew, + NULL + ); + } + + threadNode->ThreadId = ThreadItem->ThreadId; + threadNode->ThreadItem = ThreadItem; + PhReferenceObject(ThreadItem); + + memset(threadNode->TextCache, 0, sizeof(PH_STRINGREF) * PHTHTLC_MAXIMUM); + threadNode->Node.TextCache = threadNode->TextCache; + threadNode->Node.TextCacheSize = PHTHTLC_MAXIMUM; + + PhAddEntryHashtable(Context->NodeHashtable, &threadNode); + PhAddItemList(Context->NodeList, threadNode); + + PhEmCallObjectOperation(EmThreadNodeType, threadNode, EmObjectCreate); + + TreeNew_NodesStructured(Context->TreeNewHandle); + + return threadNode; +} + +PPH_THREAD_NODE PhFindThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ HANDLE ThreadId + ) +{ + PH_THREAD_NODE lookupThreadNode; + PPH_THREAD_NODE lookupThreadNodePtr = &lookupThreadNode; + PPH_THREAD_NODE *threadNode; + + lookupThreadNode.ThreadId = ThreadId; + + threadNode = (PPH_THREAD_NODE *)PhFindEntryHashtable( + Context->NodeHashtable, + &lookupThreadNodePtr + ); + + if (threadNode) + return *threadNode; + else + return NULL; +} + +VOID PhRemoveThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_NODE ThreadNode + ) +{ + // Remove from the hashtable here to avoid problems in case the key is re-used. + PhRemoveEntryHashtable(Context->NodeHashtable, &ThreadNode); + + if (Context->EnableStateHighlighting) + { + PhChangeShStateTn( + &ThreadNode->Node, + &ThreadNode->ShState, + &Context->NodeStateList, + RemovingItemState, + PhCsColorRemoved, + Context->TreeNewHandle + ); + } + else + { + PhpRemoveThreadNode(ThreadNode, Context); + } +} + +VOID PhpDestroyThreadNode( + _In_ PPH_THREAD_NODE ThreadNode + ) +{ + PhEmCallObjectOperation(EmThreadNodeType, ThreadNode, EmObjectDelete); + + if (ThreadNode->CyclesDeltaText) PhDereferenceObject(ThreadNode->CyclesDeltaText); + if (ThreadNode->StartAddressText) PhDereferenceObject(ThreadNode->StartAddressText); + if (ThreadNode->PriorityText) PhDereferenceObject(ThreadNode->PriorityText); + + PhDereferenceObject(ThreadNode->ThreadItem); + + PhFree(ThreadNode); +} + +VOID PhpRemoveThreadNode( + _In_ PPH_THREAD_NODE ThreadNode, + _In_ PPH_THREAD_LIST_CONTEXT Context // PH_TICK_SH_STATE requires this parameter to be after ThreadNode + ) +{ + ULONG index; + + // Remove from list and cleanup. + + if ((index = PhFindItemList(Context->NodeList, ThreadNode)) != -1) + PhRemoveItemList(Context->NodeList, index); + + PhpDestroyThreadNode(ThreadNode); + + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhUpdateThreadNode( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _In_ PPH_THREAD_NODE ThreadNode + ) +{ + memset(ThreadNode->TextCache, 0, sizeof(PH_STRINGREF) * PHTHTLC_MAXIMUM); + + ThreadNode->ValidMask = 0; + PhInvalidateTreeNewNode(&ThreadNode->Node, TN_CACHE_COLOR); + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID PhTickThreadNodes( + _In_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + PH_TICK_SH_STATE_TN(PH_THREAD_NODE, ShState, Context->NodeStateList, PhpRemoveThreadNode, PhCsHighlightingDuration, Context->TreeNewHandle, TRUE, NULL, Context); +} + +#define SORT_FUNCTION(Column) PhpThreadTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl PhpThreadTreeNewCompare##Column( \ + _In_ void *_context, \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PPH_THREAD_NODE node1 = *(PPH_THREAD_NODE *)_elem1; \ + PPH_THREAD_NODE node2 = *(PPH_THREAD_NODE *)_elem2; \ + PPH_THREAD_ITEM threadItem1 = node1->ThreadItem; \ + PPH_THREAD_ITEM threadItem2 = node2->ThreadItem; \ + PPH_THREAD_LIST_CONTEXT context = (PPH_THREAD_LIST_CONTEXT)_context; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = uintptrcmp((ULONG_PTR)node1->ThreadId, (ULONG_PTR)node2->ThreadId); \ + \ + return PhModifySort(sortResult, context->TreeNewSortOrder); \ +} + +LONG PhpThreadTreeNewPostSortFunction( + _In_ LONG Result, + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ PH_SORT_ORDER SortOrder + ) +{ + if (Result == 0) + Result = uintptrcmp((ULONG_PTR)((PPH_THREAD_NODE)Node1)->ThreadId, (ULONG_PTR)((PPH_THREAD_NODE)Node2)->ThreadId); + + return PhModifySort(Result, SortOrder); +} + +BEGIN_SORT_FUNCTION(Tid) +{ + sortResult = uintptrcmp((ULONG_PTR)node1->ThreadId, (ULONG_PTR)node2->ThreadId); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Cpu) +{ + sortResult = singlecmp(threadItem1->CpuUsage, threadItem2->CpuUsage); + + if (sortResult == 0) + { + if (context->UseCycleTime) + sortResult = uint64cmp(threadItem1->CyclesDelta.Delta, threadItem2->CyclesDelta.Delta); + else + sortResult = uintcmp(threadItem1->ContextSwitchesDelta.Delta, threadItem2->ContextSwitchesDelta.Delta); + } +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(CyclesDelta) +{ + if (context->UseCycleTime) + sortResult = uint64cmp(threadItem1->CyclesDelta.Delta, threadItem2->CyclesDelta.Delta); + else + sortResult = uintcmp(threadItem1->ContextSwitchesDelta.Delta, threadItem2->ContextSwitchesDelta.Delta); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(StartAddress) +{ + sortResult = PhCompareStringWithNull(threadItem1->StartAddressString, threadItem2->StartAddressString, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Priority) +{ + sortResult = intcmp(threadItem1->BasePriorityIncrement, threadItem2->BasePriorityIncrement); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Service) +{ + sortResult = PhCompareStringWithNull(threadItem1->ServiceName, threadItem2->ServiceName, TRUE); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI PhpThreadTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PPH_THREAD_LIST_CONTEXT context; + PPH_THREAD_NODE node; + + context = Context; + + if (PhCmForwardMessage(hwnd, Message, Parameter1, Parameter2, &context->Cm)) + return TRUE; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Tid), + SORT_FUNCTION(Cpu), + SORT_FUNCTION(CyclesDelta), + SORT_FUNCTION(StartAddress), + SORT_FUNCTION(Priority), + SORT_FUNCTION(Service) + }; + int (__cdecl *sortFunction)(void *, const void *, const void *); + + if (!PhCmForwardSort( + (PPH_TREENEW_NODE *)context->NodeList->Items, + context->NodeList->Count, + context->TreeNewSortColumn, + context->TreeNewSortOrder, + &context->Cm + )) + { + if (context->TreeNewSortColumn < PHTHTLC_MAXIMUM) + sortFunction = sortFunctions[context->TreeNewSortColumn]; + else + sortFunction = NULL; + } + else + { + sortFunction = NULL; + } + + if (sortFunction) + { + qsort_s(context->NodeList->Items, context->NodeList->Count, sizeof(PVOID), sortFunction, context); + } + + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeList->Items; + getChildren->NumberOfChildren = context->NodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PPH_THREAD_ITEM threadItem; + + node = (PPH_THREAD_NODE)getCellText->Node; + threadItem = node->ThreadItem; + + switch (getCellText->Id) + { + case PHTHTLC_TID: + PhInitializeStringRefLongHint(&getCellText->Text, threadItem->ThreadIdString); + break; + case PHTHTLC_CPU: + { + FLOAT cpuUsage; + + cpuUsage = threadItem->CpuUsage * 100; + + if (cpuUsage >= 0.01) + { + PH_FORMAT format; + SIZE_T returnLength; + + PhInitFormatF(&format, cpuUsage, 2); + + if (PhFormatToBuffer(&format, 1, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength)) + { + getCellText->Text.Buffer = node->CpuUsageText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); // minus null terminator + } + } + else if (cpuUsage != 0 && PhCsShowCpuBelow001) + { + PH_FORMAT format[2]; + SIZE_T returnLength; + + PhInitFormatS(&format[0], L"< "); + PhInitFormatF(&format[1], 0.01, 2); + + if (PhFormatToBuffer(format, 2, node->CpuUsageText, sizeof(node->CpuUsageText), &returnLength)) + { + getCellText->Text.Buffer = node->CpuUsageText; + getCellText->Text.Length = returnLength - sizeof(WCHAR); + } + } + } + break; + case PHTHTLC_CYCLESDELTA: + if (context->UseCycleTime) + { + if (threadItem->CyclesDelta.Delta != threadItem->CyclesDelta.Value && threadItem->CyclesDelta.Delta != 0) + { + PhMoveReference(&node->CyclesDeltaText, PhFormatUInt64(threadItem->CyclesDelta.Delta, TRUE)); + getCellText->Text = node->CyclesDeltaText->sr; + } + } + else + { + if (threadItem->ContextSwitchesDelta.Delta != threadItem->ContextSwitchesDelta.Value && threadItem->ContextSwitchesDelta.Delta != 0) + { + PhMoveReference(&node->CyclesDeltaText, PhFormatUInt64(threadItem->ContextSwitchesDelta.Delta, TRUE)); + getCellText->Text = node->CyclesDeltaText->sr; + } + } + break; + case PHTHTLC_STARTADDRESS: + PhSwapReference(&node->StartAddressText, threadItem->StartAddressString); + getCellText->Text = PhGetStringRef(node->StartAddressText); + break; + case PHTHTLC_PRIORITY: + PhMoveReference(&node->PriorityText, PhGetBasePriorityIncrementString(threadItem->BasePriorityIncrement)); + getCellText->Text = PhGetStringRef(node->PriorityText); + break; + case PHTHTLC_SERVICE: + getCellText->Text = PhGetStringRef(threadItem->ServiceName); + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + PPH_THREAD_ITEM threadItem; + + node = (PPH_THREAD_NODE)getNodeColor->Node; + threadItem = node->ThreadItem; + + if (!threadItem) + ; // Dummy + else if (PhCsUseColorSuspended && threadItem->WaitReason == Suspended) + getNodeColor->BackColor = PhCsColorSuspended; + else if (PhCsUseColorGuiThreads && threadItem->IsGuiThread) + getNodeColor->BackColor = PhCsColorGuiThreads; + + getNodeColor->Flags = TN_CACHE | TN_AUTO_FORECOLOR; + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewSelectionChanged: + { + SendMessage(context->ParentWindowHandle, WM_PH_THREAD_SELECTION_CHANGED, 0, 0); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_THREAD_COPY, 0); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(context->TreeNewHandle, 0, -1); + break; + case VK_DELETE: + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_THREAD_TERMINATE, 0); + break; + case VK_RETURN: + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_THREAD_INSPECT, 0); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + // Customizable columns are disabled until we can figure out how to make it + // co-operate with the column adjustments (e.g. Cycles Delta vs Context Switches Delta, + // Service column). + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = PHTHTLC_CYCLESDELTA; + data.DefaultSortOrder = DescendingSortOrder; + PhInitializeTreeNewColumnMenuEx(&data, PH_TN_COLUMN_MENU_NO_VISIBILITY); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_THREAD_INSPECT, 0); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_CONTEXT_MENU contextMenu = Parameter1; + + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_SHOWCONTEXTMENU, (LPARAM)contextMenu); + } + return TRUE; + case TreeNewGetDialogCode: + { + PULONG code = Parameter2; + + if (PtrToUlong(Parameter1) == VK_RETURN) + { + *code = DLGC_WANTMESSAGE; + return TRUE; + } + } + return FALSE; + } + + return FALSE; +} + +PPH_THREAD_ITEM PhGetSelectedThreadItem( + _In_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + PPH_THREAD_ITEM threadItem = NULL; + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_THREAD_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + { + threadItem = node->ThreadItem; + break; + } + } + + return threadItem; +} + +VOID PhGetSelectedThreadItems( + _In_ PPH_THREAD_LIST_CONTEXT Context, + _Out_ PPH_THREAD_ITEM **Threads, + _Out_ PULONG NumberOfThreads + ) +{ + PH_ARRAY array; + ULONG i; + + PhInitializeArray(&array, sizeof(PVOID), 2); + + for (i = 0; i < Context->NodeList->Count; i++) + { + PPH_THREAD_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + PhAddItemArray(&array, &node->ThreadItem); + } + + *NumberOfThreads = (ULONG)array.Count; + *Threads = PhFinalArrayItems(&array); +} + +VOID PhDeselectAllThreadNodes( + _In_ PPH_THREAD_LIST_CONTEXT Context + ) +{ + TreeNew_DeselectRange(Context->TreeNewHandle, 0, -1); +} diff --git a/ProcessHacker/thrdprv.c b/ProcessHacker/thrdprv.c new file mode 100644 index 0000000..5d3f10d --- /dev/null +++ b/ProcessHacker/thrdprv.c @@ -0,0 +1,1113 @@ +/* + * Process Hacker - + * thread provider + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The thread provider is tied to the process provider, and runs by registering a callback for the + * processes-updated event. This is because calculating CPU usage depends on deltas calculated by + * the process provider. However, this does increase the complexity of the thread provider system. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _PH_THREAD_QUERY_DATA +{ + SLIST_ENTRY ListEntry; + PPH_THREAD_PROVIDER ThreadProvider; + PPH_THREAD_ITEM ThreadItem; + ULONG64 RunId; + + PPH_STRING StartAddressString; + PH_SYMBOL_RESOLVE_LEVEL StartAddressResolveLevel; + + PPH_STRING ServiceName; +} PH_THREAD_QUERY_DATA, *PPH_THREAD_QUERY_DATA; + +typedef struct _PH_THREAD_SYMBOL_LOAD_CONTEXT +{ + HANDLE ProcessId; + PPH_THREAD_PROVIDER ThreadProvider; + PPH_SYMBOL_PROVIDER SymbolProvider; +} PH_THREAD_SYMBOL_LOAD_CONTEXT, *PPH_THREAD_SYMBOL_LOAD_CONTEXT; + +VOID NTAPI PhpThreadProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpThreadItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +BOOLEAN NTAPI PhpThreadHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI PhpThreadHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID PhpThreadProviderCallbackHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID PhpThreadProviderUpdate( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PVOID ProcessInformation + ); + +PPH_OBJECT_TYPE PhThreadProviderType; +PPH_OBJECT_TYPE PhThreadItemType; + +PH_WORK_QUEUE PhThreadProviderWorkQueue; +PH_INITONCE PhThreadProviderWorkQueueInitOnce = PH_INITONCE_INIT; + +BOOLEAN PhThreadProviderInitialization( + VOID + ) +{ + PhThreadProviderType = PhCreateObjectType(L"ThreadProvider", 0, PhpThreadProviderDeleteProcedure); + PhThreadItemType = PhCreateObjectType(L"ThreadItem", 0, PhpThreadItemDeleteProcedure); + + return TRUE; +} + +VOID PhpQueueThreadWorkQueueItem( + _In_ PTHREAD_START_ROUTINE Function, + _In_opt_ PVOID Context + ) +{ + if (PhBeginInitOnce(&PhThreadProviderWorkQueueInitOnce)) + { + PhInitializeWorkQueue(&PhThreadProviderWorkQueue, 0, 1, 1000); + PhEndInitOnce(&PhThreadProviderWorkQueueInitOnce); + } + + PhQueueItemWorkQueue(&PhThreadProviderWorkQueue, Function, Context); +} + +PPH_THREAD_PROVIDER PhCreateThreadProvider( + _In_ HANDLE ProcessId + ) +{ + PPH_THREAD_PROVIDER threadProvider; + + threadProvider = PhCreateObject( + PhEmGetObjectSize(EmThreadProviderType, sizeof(PH_THREAD_PROVIDER)), + PhThreadProviderType + ); + memset(threadProvider, 0, sizeof(PH_THREAD_PROVIDER)); + + threadProvider->ThreadHashtable = PhCreateHashtable( + sizeof(PPH_THREAD_ITEM), + PhpThreadHashtableEqualFunction, + PhpThreadHashtableHashFunction, + 20 + ); + PhInitializeFastLock(&threadProvider->ThreadHashtableLock); + + PhInitializeCallback(&threadProvider->ThreadAddedEvent); + PhInitializeCallback(&threadProvider->ThreadModifiedEvent); + PhInitializeCallback(&threadProvider->ThreadRemovedEvent); + PhInitializeCallback(&threadProvider->UpdatedEvent); + PhInitializeCallback(&threadProvider->LoadingStateChangedEvent); + + threadProvider->ProcessId = ProcessId; + threadProvider->SymbolProvider = PhCreateSymbolProvider(ProcessId); + + if (threadProvider->SymbolProvider) + { + if (threadProvider->SymbolProvider->IsRealHandle) + threadProvider->ProcessHandle = threadProvider->SymbolProvider->ProcessHandle; + } + + RtlInitializeSListHead(&threadProvider->QueryListHead); + PhInitializeQueuedLock(&threadProvider->LoadSymbolsLock); + + threadProvider->RunId = 1; + threadProvider->SymbolsLoadedRunId = 0; // Force symbols to be loaded the first time we try to resolve an address + + PhEmCallObjectOperation(EmThreadProviderType, threadProvider, EmObjectCreate); + + return threadProvider; +} + +VOID PhpThreadProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_THREAD_PROVIDER threadProvider = (PPH_THREAD_PROVIDER)Object; + + PhEmCallObjectOperation(EmThreadProviderType, threadProvider, EmObjectDelete); + + // Dereference all thread items (we referenced them + // when we added them to the hashtable). + PhDereferenceAllThreadItems(threadProvider); + + PhDereferenceObject(threadProvider->ThreadHashtable); + PhDeleteFastLock(&threadProvider->ThreadHashtableLock); + PhDeleteCallback(&threadProvider->ThreadAddedEvent); + PhDeleteCallback(&threadProvider->ThreadModifiedEvent); + PhDeleteCallback(&threadProvider->ThreadRemovedEvent); + PhDeleteCallback(&threadProvider->UpdatedEvent); + PhDeleteCallback(&threadProvider->LoadingStateChangedEvent); + + // Destroy all queue items. + { + PSLIST_ENTRY entry; + PPH_THREAD_QUERY_DATA data; + + entry = RtlInterlockedFlushSList(&threadProvider->QueryListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_THREAD_QUERY_DATA, ListEntry); + entry = entry->Next; + + PhClearReference(&data->StartAddressString); + PhClearReference(&data->ServiceName); + PhDereferenceObject(data->ThreadItem); + PhFree(data); + } + } + + // We don't close the process handle because it is owned by the symbol provider. + if (threadProvider->SymbolProvider) PhDereferenceObject(threadProvider->SymbolProvider); +} + +VOID PhRegisterThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _Out_ PPH_CALLBACK_REGISTRATION CallbackRegistration + ) +{ + PhReferenceObject(ThreadProvider); + PhRegisterCallback(&PhProcessesUpdatedEvent, PhpThreadProviderCallbackHandler, ThreadProvider, CallbackRegistration); +} + +VOID PhUnregisterThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PPH_CALLBACK_REGISTRATION CallbackRegistration + ) +{ + PhUnregisterCallback(&PhProcessesUpdatedEvent, CallbackRegistration); + PhDereferenceObject(ThreadProvider); +} + +VOID PhSetTerminatingThreadProvider( + _Inout_ PPH_THREAD_PROVIDER ThreadProvider + ) +{ + ThreadProvider->Terminating = TRUE; +} + +static BOOLEAN LoadSymbolsEnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PPH_THREAD_SYMBOL_LOAD_CONTEXT context = Context; + PPH_SYMBOL_PROVIDER symbolProvider = context->SymbolProvider; + + if (context->ThreadProvider->Terminating) + return FALSE; + + // If we're loading kernel module symbols for a process other than System, ignore modules which + // are in user space. This may happen in Windows 7. + if (context->ProcessId == SYSTEM_PROCESS_ID && + context->ThreadProvider->ProcessId != SYSTEM_PROCESS_ID && + (ULONG_PTR)Module->BaseAddress <= PhSystemBasicInformation.MaximumUserModeAddress) + { + return TRUE; + } + + PhLoadModuleSymbolProvider( + symbolProvider, + Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, + Module->Size + ); + + return TRUE; +} + +static BOOLEAN LoadBasicSymbolsEnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PPH_THREAD_SYMBOL_LOAD_CONTEXT context = Context; + PPH_SYMBOL_PROVIDER symbolProvider = context->SymbolProvider; + + if (context->ThreadProvider->Terminating) + return FALSE; + + if (PhEqualString2(Module->Name, L"ntdll.dll", TRUE) || + PhEqualString2(Module->Name, L"kernel32.dll", TRUE)) + { + PhLoadModuleSymbolProvider( + symbolProvider, + Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, + Module->Size + ); + } + + return TRUE; +} + +VOID PhLoadSymbolsThreadProvider( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ) +{ + PH_THREAD_SYMBOL_LOAD_CONTEXT loadContext; + ULONG64 runId; + + loadContext.ThreadProvider = ThreadProvider; + loadContext.SymbolProvider = ThreadProvider->SymbolProvider; + + PhAcquireQueuedLockExclusive(&ThreadProvider->LoadSymbolsLock); + runId = ThreadProvider->RunId; + PhLoadSymbolProviderOptions(ThreadProvider->SymbolProvider); + + if (ThreadProvider->ProcessId != SYSTEM_IDLE_PROCESS_ID) + { + if (ThreadProvider->SymbolProvider->IsRealHandle || + ThreadProvider->ProcessId == SYSTEM_PROCESS_ID) + { + loadContext.ProcessId = ThreadProvider->ProcessId; + PhEnumGenericModules( + ThreadProvider->ProcessId, + ThreadProvider->SymbolProvider->ProcessHandle, + 0, + LoadSymbolsEnumGenericModulesCallback, + &loadContext + ); + } + else + { + // We can't enumerate the process modules. Load symbols for ntdll.dll and kernel32.dll. + loadContext.ProcessId = NtCurrentProcessId(); + PhEnumGenericModules( + NtCurrentProcessId(), + NtCurrentProcess(), + 0, + LoadBasicSymbolsEnumGenericModulesCallback, + &loadContext + ); + } + + // Load kernel module symbols as well. + if (ThreadProvider->ProcessId != SYSTEM_PROCESS_ID) + { + loadContext.ProcessId = SYSTEM_PROCESS_ID; + PhEnumGenericModules( + SYSTEM_PROCESS_ID, + NULL, + 0, + LoadSymbolsEnumGenericModulesCallback, + &loadContext + ); + } + } + else + { + // System Idle Process has one thread for each CPU, each having a start address at + // KiIdleLoop. We need to load symbols for the kernel. + + PRTL_PROCESS_MODULES kernelModules; + + if (NT_SUCCESS(PhEnumKernelModules(&kernelModules))) + { + if (kernelModules->NumberOfModules > 0) + { + PPH_STRING fileName; + PPH_STRING newFileName; + + fileName = PhConvertMultiByteToUtf16(kernelModules->Modules[0].FullPathName); + newFileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + + PhLoadModuleSymbolProvider( + ThreadProvider->SymbolProvider, + newFileName->Buffer, + (ULONG64)kernelModules->Modules[0].ImageBase, + kernelModules->Modules[0].ImageSize + ); + PhDereferenceObject(newFileName); + } + + PhFree(kernelModules); + } + } + + ThreadProvider->SymbolsLoadedRunId = runId; + PhReleaseQueuedLockExclusive(&ThreadProvider->LoadSymbolsLock); +} + +PPH_THREAD_ITEM PhCreateThreadItem( + _In_ HANDLE ThreadId + ) +{ + PPH_THREAD_ITEM threadItem; + + threadItem = PhCreateObject( + PhEmGetObjectSize(EmThreadItemType, sizeof(PH_THREAD_ITEM)), + PhThreadItemType + ); + memset(threadItem, 0, sizeof(PH_THREAD_ITEM)); + threadItem->ThreadId = ThreadId; + PhPrintUInt32(threadItem->ThreadIdString, HandleToUlong(ThreadId)); + + PhEmCallObjectOperation(EmThreadItemType, threadItem, EmObjectCreate); + + return threadItem; +} + +VOID PhpThreadItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_THREAD_ITEM threadItem = (PPH_THREAD_ITEM)Object; + + PhEmCallObjectOperation(EmThreadItemType, threadItem, EmObjectDelete); + + if (threadItem->ThreadHandle) NtClose(threadItem->ThreadHandle); + if (threadItem->StartAddressString) PhDereferenceObject(threadItem->StartAddressString); + if (threadItem->StartAddressFileName) PhDereferenceObject(threadItem->StartAddressFileName); + if (threadItem->ServiceName) PhDereferenceObject(threadItem->ServiceName); +} + +BOOLEAN PhpThreadHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + return + (*(PPH_THREAD_ITEM *)Entry1)->ThreadId == + (*(PPH_THREAD_ITEM *)Entry2)->ThreadId; +} + +ULONG PhpThreadHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return HandleToUlong((*(PPH_THREAD_ITEM *)Entry)->ThreadId) / 4; +} + +PPH_THREAD_ITEM PhReferenceThreadItem( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ HANDLE ThreadId + ) +{ + PH_THREAD_ITEM lookupThreadItem; + PPH_THREAD_ITEM lookupThreadItemPtr = &lookupThreadItem; + PPH_THREAD_ITEM *threadItemPtr; + PPH_THREAD_ITEM threadItem; + + lookupThreadItem.ThreadId = ThreadId; + + PhAcquireFastLockShared(&ThreadProvider->ThreadHashtableLock); + + threadItemPtr = (PPH_THREAD_ITEM *)PhFindEntryHashtable( + ThreadProvider->ThreadHashtable, + &lookupThreadItemPtr + ); + + if (threadItemPtr) + { + threadItem = *threadItemPtr; + PhReferenceObject(threadItem); + } + else + { + threadItem = NULL; + } + + PhReleaseFastLockShared(&ThreadProvider->ThreadHashtableLock); + + return threadItem; +} + +VOID PhDereferenceAllThreadItems( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ) +{ + ULONG enumerationKey = 0; + PPH_THREAD_ITEM *threadItem; + + PhAcquireFastLockExclusive(&ThreadProvider->ThreadHashtableLock); + + while (PhEnumHashtable(ThreadProvider->ThreadHashtable, (PVOID *)&threadItem, &enumerationKey)) + { + PhDereferenceObject(*threadItem); + } + + PhReleaseFastLockExclusive(&ThreadProvider->ThreadHashtableLock); +} + +VOID PhpRemoveThreadItem( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PPH_THREAD_ITEM ThreadItem + ) +{ + PhRemoveEntryHashtable(ThreadProvider->ThreadHashtable, &ThreadItem); + PhDereferenceObject(ThreadItem); +} + +NTSTATUS PhpThreadQueryWorker( + _In_ PVOID Parameter + ) +{ + PPH_THREAD_QUERY_DATA data = (PPH_THREAD_QUERY_DATA)Parameter; + LONG newSymbolsLoading; + + if (data->ThreadProvider->Terminating) + goto Done; + + newSymbolsLoading = _InterlockedIncrement(&data->ThreadProvider->SymbolsLoading); + + if (newSymbolsLoading == 1) + PhInvokeCallback(&data->ThreadProvider->LoadingStateChangedEvent, (PVOID)TRUE); + + if (data->ThreadProvider->SymbolsLoadedRunId == 0) + PhLoadSymbolsThreadProvider(data->ThreadProvider); + + data->StartAddressString = PhGetSymbolFromAddress( + data->ThreadProvider->SymbolProvider, + data->ThreadItem->StartAddress, + &data->StartAddressResolveLevel, + &data->ThreadItem->StartAddressFileName, + NULL, + NULL + ); + + if (data->StartAddressResolveLevel == PhsrlAddress && data->ThreadProvider->SymbolsLoadedRunId < data->RunId) + { + // The process may have loaded new modules, so load symbols for those and try again. + + PhLoadSymbolsThreadProvider(data->ThreadProvider); + + PhClearReference(&data->StartAddressString); + PhClearReference(&data->ThreadItem->StartAddressFileName); + data->StartAddressString = PhGetSymbolFromAddress( + data->ThreadProvider->SymbolProvider, + data->ThreadItem->StartAddress, + &data->StartAddressResolveLevel, + &data->ThreadItem->StartAddressFileName, + NULL, + NULL + ); + } + + newSymbolsLoading = _InterlockedDecrement(&data->ThreadProvider->SymbolsLoading); + + if (newSymbolsLoading == 0) + PhInvokeCallback(&data->ThreadProvider->LoadingStateChangedEvent, (PVOID)FALSE); + + // Check if the process has services - we'll need to know before getting service tag/name + // information. + if (WINDOWS_HAS_SERVICE_TAGS && !data->ThreadProvider->HasServicesKnown) + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(data->ThreadProvider->ProcessId)) + { + data->ThreadProvider->HasServices = processItem->ServiceList && processItem->ServiceList->Count != 0; + PhDereferenceObject(processItem); + } + + data->ThreadProvider->HasServicesKnown = TRUE; + } + + // Get the service tag, and the service name. + if (WINDOWS_HAS_SERVICE_TAGS && + data->ThreadProvider->SymbolProvider->IsRealHandle && + data->ThreadItem->ThreadHandle) + { + PVOID serviceTag; + + if (NT_SUCCESS(PhGetThreadServiceTag( + data->ThreadItem->ThreadHandle, + data->ThreadProvider->ProcessHandle, + &serviceTag + ))) + { + data->ServiceName = PhGetServiceNameFromTag( + data->ThreadProvider->ProcessId, + serviceTag + ); + } + } + +Done: + RtlInterlockedPushEntrySList(&data->ThreadProvider->QueryListHead, &data->ListEntry); + PhDereferenceObject(data->ThreadProvider); + + return STATUS_SUCCESS; +} + +VOID PhpQueueThreadQuery( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PPH_THREAD_ITEM ThreadItem + ) +{ + PPH_THREAD_QUERY_DATA data; + + data = PhAllocate(sizeof(PH_THREAD_QUERY_DATA)); + memset(data, 0, sizeof(PH_THREAD_QUERY_DATA)); + PhSetReference(&data->ThreadProvider, ThreadProvider); + PhSetReference(&data->ThreadItem, ThreadItem); + data->RunId = ThreadProvider->RunId; + + PhpQueueThreadWorkQueueItem(PhpThreadQueryWorker, data); +} + +PPH_STRING PhpGetThreadBasicStartAddress( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ ULONG64 Address, + _Out_ PPH_SYMBOL_RESOLVE_LEVEL ResolveLevel + ) +{ + ULONG64 modBase; + PPH_STRING fileName = NULL; + PPH_STRING baseName = NULL; + PPH_STRING symbol; + + modBase = PhGetModuleFromAddress( + ThreadProvider->SymbolProvider, + Address, + &fileName + ); + + if (fileName == NULL) + { + *ResolveLevel = PhsrlAddress; + + symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2); + PhPrintPointer(symbol->Buffer, (PVOID)Address); + PhTrimToNullTerminatorString(symbol); + } + else + { + PH_FORMAT format[3]; + + baseName = PhGetBaseName(fileName); + *ResolveLevel = PhsrlModule; + + PhInitFormatSR(&format[0], baseName->sr); + PhInitFormatS(&format[1], L"+0x"); + PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase)); + + symbol = PhFormat(format, 3, baseName->Length + 6 + 32); + } + + if (fileName) + PhDereferenceObject(fileName); + if (baseName) + PhDereferenceObject(baseName); + + return symbol; +} + +static NTSTATUS PhpGetThreadCycleTime( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PPH_THREAD_ITEM ThreadItem, + _Out_ PULONG64 CycleTime + ) +{ + if (ThreadProvider->ProcessId != SYSTEM_IDLE_PROCESS_ID) + { + return PhGetThreadCycleTime(ThreadItem->ThreadHandle, CycleTime); + } + else + { + if (HandleToUlong(ThreadItem->ThreadId) < (ULONG)PhSystemBasicInformation.NumberOfProcessors) + { + *CycleTime = PhCpuIdleCycleTime[HandleToUlong(ThreadItem->ThreadId)].QuadPart; + return STATUS_SUCCESS; + } + } + + return STATUS_INVALID_PARAMETER; +} + +PPH_STRING PhGetBasePriorityIncrementString( + _In_ LONG Increment + ) +{ + switch (Increment) + { + case THREAD_BASE_PRIORITY_LOWRT + 1: + case THREAD_BASE_PRIORITY_LOWRT: + return PhCreateString(L"Time critical"); + case THREAD_PRIORITY_HIGHEST: + return PhCreateString(L"Highest"); + case THREAD_PRIORITY_ABOVE_NORMAL: + return PhCreateString(L"Above normal"); + case THREAD_PRIORITY_NORMAL: + return PhCreateString(L"Normal"); + case THREAD_PRIORITY_BELOW_NORMAL: + return PhCreateString(L"Below normal"); + case THREAD_PRIORITY_LOWEST: + return PhCreateString(L"Lowest"); + case THREAD_BASE_PRIORITY_IDLE: + case THREAD_BASE_PRIORITY_IDLE - 1: + return PhCreateString(L"Idle"); + case THREAD_PRIORITY_ERROR_RETURN: + return NULL; + default: + return PhFormatString(L"%d", Increment); + } +} + +VOID PhThreadProviderInitialUpdate( + _In_ PPH_THREAD_PROVIDER ThreadProvider + ) +{ + PVOID processes; + + if (NT_SUCCESS(PhEnumProcesses(&processes))) + { + PhpThreadProviderUpdate(ThreadProvider, processes); + PhFree(processes); + } +} + +VOID PhpThreadProviderCallbackHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (PhProcessInformation) + { + PhpThreadProviderUpdate((PPH_THREAD_PROVIDER)Context, PhProcessInformation); + } +} + +VOID PhpThreadProviderUpdate( + _In_ PPH_THREAD_PROVIDER ThreadProvider, + _In_ PVOID ProcessInformation + ) +{ + PPH_THREAD_PROVIDER threadProvider = ThreadProvider; + PSYSTEM_PROCESS_INFORMATION process; + SYSTEM_PROCESS_INFORMATION localProcess; + PSYSTEM_THREAD_INFORMATION threads; + ULONG numberOfThreads; + ULONG i; + + process = PhFindProcessInformation(ProcessInformation, threadProvider->ProcessId); + + if (!process) + { + // The process doesn't exist anymore. Pretend it does but has no threads. + process = &localProcess; + process->NumberOfThreads = 0; + } + + threads = process->Threads; + numberOfThreads = process->NumberOfThreads; + + // System Idle Process has one thread per CPU. They all have a TID of 0. We can't have duplicate + // TIDs, so we'll assign unique TIDs. + if (threadProvider->ProcessId == SYSTEM_IDLE_PROCESS_ID) + { + for (i = 0; i < numberOfThreads; i++) + { + threads[i].ClientId.UniqueThread = UlongToHandle(i); + } + } + + // Look for dead threads. + { + PPH_LIST threadsToRemove = NULL; + ULONG enumerationKey = 0; + PPH_THREAD_ITEM *threadItem; + + while (PhEnumHashtable(threadProvider->ThreadHashtable, (PVOID *)&threadItem, &enumerationKey)) + { + BOOLEAN found = FALSE; + + // Check if the thread still exists. + for (i = 0; i < numberOfThreads; i++) + { + PSYSTEM_THREAD_INFORMATION thread = &threads[i]; + + if ((*threadItem)->ThreadId == thread->ClientId.UniqueThread) + { + found = TRUE; + break; + } + } + + if (!found) + { + // Raise the thread removed event. + PhInvokeCallback(&threadProvider->ThreadRemovedEvent, *threadItem); + + if (!threadsToRemove) + threadsToRemove = PhCreateList(2); + + PhAddItemList(threadsToRemove, *threadItem); + } + } + + if (threadsToRemove) + { + PhAcquireFastLockExclusive(&threadProvider->ThreadHashtableLock); + + for (i = 0; i < threadsToRemove->Count; i++) + { + PhpRemoveThreadItem( + threadProvider, + (PPH_THREAD_ITEM)threadsToRemove->Items[i] + ); + } + + PhReleaseFastLockExclusive(&threadProvider->ThreadHashtableLock); + PhDereferenceObject(threadsToRemove); + } + } + + // Go through the queued thread query data. + { + PSLIST_ENTRY entry; + PPH_THREAD_QUERY_DATA data; + + entry = RtlInterlockedFlushSList(&threadProvider->QueryListHead); + + while (entry) + { + data = CONTAINING_RECORD(entry, PH_THREAD_QUERY_DATA, ListEntry); + entry = entry->Next; + + if (data->StartAddressResolveLevel == PhsrlFunction && data->StartAddressString) + { + PhSwapReference(&data->ThreadItem->StartAddressString, data->StartAddressString); + data->ThreadItem->StartAddressResolveLevel = data->StartAddressResolveLevel; + } + + PhMoveReference(&data->ThreadItem->ServiceName, data->ServiceName); + + data->ThreadItem->JustResolved = TRUE; + + if (data->StartAddressString) PhDereferenceObject(data->StartAddressString); + PhDereferenceObject(data->ThreadItem); + PhFree(data); + } + } + + // Look for new threads and update existing ones. + for (i = 0; i < numberOfThreads; i++) + { + PSYSTEM_THREAD_INFORMATION thread = &threads[i]; + PPH_THREAD_ITEM threadItem; + THREAD_BASIC_INFORMATION basicInfo; + + threadItem = PhReferenceThreadItem(threadProvider, thread->ClientId.UniqueThread); + + if (!threadItem) + { + PVOID startAddress = NULL; + + threadItem = PhCreateThreadItem(thread->ClientId.UniqueThread); + + threadItem->CreateTime = thread->CreateTime; + threadItem->KernelTime = thread->KernelTime; + threadItem->UserTime = thread->UserTime; + + PhUpdateDelta(&threadItem->ContextSwitchesDelta, thread->ContextSwitches); + threadItem->Priority = thread->Priority; + threadItem->BasePriority = thread->BasePriority; + threadItem->State = (KTHREAD_STATE)thread->ThreadState; + threadItem->WaitReason = thread->WaitReason; + + // Try to open a handle to the thread. + if (!NT_SUCCESS(PhOpenThread( + &threadItem->ThreadHandle, + THREAD_QUERY_INFORMATION, + threadItem->ThreadId + ))) + { + PhOpenThread( + &threadItem->ThreadHandle, + ThreadQueryAccess, + threadItem->ThreadId + ); + } + + // Get the cycle count. + if (WINDOWS_HAS_CYCLE_TIME) + { + ULONG64 cycles; + + if (NT_SUCCESS(PhpGetThreadCycleTime( + threadProvider, + threadItem, + &cycles + ))) + { + PhUpdateDelta(&threadItem->CyclesDelta, cycles); + } + } + + // Initialize the CPU time deltas. + PhUpdateDelta(&threadItem->CpuKernelDelta, threadItem->KernelTime.QuadPart); + PhUpdateDelta(&threadItem->CpuUserDelta, threadItem->UserTime.QuadPart); + + // Try to get the start address. + + if (threadItem->ThreadHandle) + { + NtQueryInformationThread( + threadItem->ThreadHandle, + ThreadQuerySetWin32StartAddress, + &startAddress, + sizeof(PVOID), + NULL + ); + } + + if (!startAddress) + startAddress = thread->StartAddress; + + threadItem->StartAddress = (ULONG64)startAddress; + + // Get the base priority increment (relative to the process priority). + if (threadItem->ThreadHandle && NT_SUCCESS(PhGetThreadBasicInformation( + threadItem->ThreadHandle, + &basicInfo + ))) + { + threadItem->BasePriorityIncrement = basicInfo.BasePriority; + } + else + { + threadItem->BasePriorityIncrement = THREAD_PRIORITY_ERROR_RETURN; + } + + if (threadProvider->SymbolsLoadedRunId != 0) + { + threadItem->StartAddressString = PhpGetThreadBasicStartAddress( + threadProvider, + threadItem->StartAddress, + &threadItem->StartAddressResolveLevel + ); + } + + if (!threadItem->StartAddressString) + { + threadItem->StartAddressResolveLevel = PhsrlAddress; + threadItem->StartAddressString = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2); + PhPrintPointer( + threadItem->StartAddressString->Buffer, + (PVOID)threadItem->StartAddress + ); + PhTrimToNullTerminatorString(threadItem->StartAddressString); + } + + PhpQueueThreadQuery(threadProvider, threadItem); + + // Is it a GUI thread? + { + GUITHREADINFO info = { sizeof(GUITHREADINFO) }; + + threadItem->IsGuiThread = !!GetGUIThreadInfo(HandleToUlong(threadItem->ThreadId), &info); + } + + // Add the thread item to the hashtable. + PhAcquireFastLockExclusive(&threadProvider->ThreadHashtableLock); + PhAddEntryHashtable(threadProvider->ThreadHashtable, &threadItem); + PhReleaseFastLockExclusive(&threadProvider->ThreadHashtableLock); + + // Raise the thread added event. + PhInvokeCallback(&threadProvider->ThreadAddedEvent, threadItem); + } + else + { + BOOLEAN modified = FALSE; + + if (threadItem->JustResolved) + modified = TRUE; + + threadItem->KernelTime = thread->KernelTime; + threadItem->UserTime = thread->UserTime; + + threadItem->Priority = thread->Priority; + threadItem->BasePriority = thread->BasePriority; + + threadItem->State = (KTHREAD_STATE)thread->ThreadState; + + if (threadItem->WaitReason != thread->WaitReason) + { + threadItem->WaitReason = thread->WaitReason; + modified = TRUE; + } + + // If the resolve level is only at address, it probably + // means symbols weren't loaded the last time we + // tried to get the start address. Try again. + if (threadItem->StartAddressResolveLevel == PhsrlAddress) + { + if (threadProvider->SymbolsLoadedRunId != 0) + { + PPH_STRING newStartAddressString; + + newStartAddressString = PhpGetThreadBasicStartAddress( + threadProvider, + threadItem->StartAddress, + &threadItem->StartAddressResolveLevel + ); + + PhMoveReference( + &threadItem->StartAddressString, + newStartAddressString + ); + + modified = TRUE; + } + } + + // If we couldn't resolve the start address to a module+offset, use the StartAddress + // instead of the Win32StartAddress and try again. Note that we check the resolve level + // again because we may have changed it in the previous block. + if (threadItem->JustResolved && + threadItem->StartAddressResolveLevel == PhsrlAddress) + { + if (threadItem->StartAddress != (ULONG64)thread->StartAddress) + { + threadItem->StartAddress = (ULONG64)thread->StartAddress; + PhpQueueThreadQuery(threadProvider, threadItem); + } + } + + // Update the context switch count. + { + ULONG oldDelta; + + oldDelta = threadItem->ContextSwitchesDelta.Delta; + PhUpdateDelta(&threadItem->ContextSwitchesDelta, thread->ContextSwitches); + + if (threadItem->ContextSwitchesDelta.Delta != oldDelta) + { + modified = TRUE; + } + } + + // Update the cycle count. + if (WINDOWS_HAS_CYCLE_TIME) + { + ULONG64 cycles; + ULONG64 oldDelta; + + oldDelta = threadItem->CyclesDelta.Delta; + + if (NT_SUCCESS(PhpGetThreadCycleTime( + threadProvider, + threadItem, + &cycles + ))) + { + PhUpdateDelta(&threadItem->CyclesDelta, cycles); + + if (threadItem->CyclesDelta.Delta != oldDelta) + { + modified = TRUE; + } + } + } + + // Update the CPU time deltas. + PhUpdateDelta(&threadItem->CpuKernelDelta, threadItem->KernelTime.QuadPart); + PhUpdateDelta(&threadItem->CpuUserDelta, threadItem->UserTime.QuadPart); + + // Update the CPU usage. + // If the cycle time isn't available, we'll fall back to using the CPU time. + if (WINDOWS_HAS_CYCLE_TIME && PhEnableCycleCpuUsage && (threadProvider->ProcessId == SYSTEM_IDLE_PROCESS_ID || threadItem->ThreadHandle)) + { + threadItem->CpuUsage = (FLOAT)threadItem->CyclesDelta.Delta / PhCpuTotalCycleDelta; + } + else + { + threadItem->CpuUsage = (FLOAT)(threadItem->CpuKernelDelta.Delta + threadItem->CpuUserDelta.Delta) / + (PhCpuKernelDelta.Delta + PhCpuUserDelta.Delta + PhCpuIdleDelta.Delta); + } + + // Update the base priority increment. + { + LONG oldBasePriorityIncrement = threadItem->BasePriorityIncrement; + + if (threadItem->ThreadHandle && NT_SUCCESS(PhGetThreadBasicInformation( + threadItem->ThreadHandle, + &basicInfo + ))) + { + threadItem->BasePriorityIncrement = basicInfo.BasePriority; + } + else + { + threadItem->BasePriorityIncrement = THREAD_PRIORITY_ERROR_RETURN; + } + + if (threadItem->BasePriorityIncrement != oldBasePriorityIncrement) + { + modified = TRUE; + } + } + + // Update the GUI thread status. + { + GUITHREADINFO info = { sizeof(GUITHREADINFO) }; + BOOLEAN oldIsGuiThread = threadItem->IsGuiThread; + + threadItem->IsGuiThread = !!GetGUIThreadInfo(HandleToUlong(threadItem->ThreadId), &info); + + if (threadItem->IsGuiThread != oldIsGuiThread) + modified = TRUE; + } + + threadItem->JustResolved = FALSE; + + if (modified) + { + // Raise the thread modified event. + PhInvokeCallback(&threadProvider->ThreadModifiedEvent, threadItem); + } + + PhDereferenceObject(threadItem); + } + } + + PhInvokeCallback(&threadProvider->UpdatedEvent, NULL); + threadProvider->RunId++; +} diff --git a/ProcessHacker/thrdstk.c b/ProcessHacker/thrdstk.c new file mode 100644 index 0000000..e0e8a91 --- /dev/null +++ b/ProcessHacker/thrdstk.c @@ -0,0 +1,694 @@ +/* + * Process Hacker - + * thread stack viewer + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#define WM_PH_COMPLETED (WM_APP + 301) +#define WM_PH_STATUS_UPDATE (WM_APP + 302) + +typedef struct _THREAD_STACK_CONTEXT +{ + HANDLE ProcessId; + HANDLE ThreadId; + HANDLE ThreadHandle; + HWND ListViewHandle; + PPH_THREAD_PROVIDER ThreadProvider; + PPH_SYMBOL_PROVIDER SymbolProvider; + BOOLEAN CustomWalk; + + BOOLEAN StopWalk; + PPH_LIST List; + PPH_LIST NewList; + HWND ProgressWindowHandle; + NTSTATUS WalkStatus; + PPH_STRING StatusMessage; + PH_QUEUED_LOCK StatusLock; +} THREAD_STACK_CONTEXT, *PTHREAD_STACK_CONTEXT; + +typedef struct _THREAD_STACK_ITEM +{ + PH_THREAD_STACK_FRAME StackFrame; + ULONG Index; + PPH_STRING Symbol; +} THREAD_STACK_ITEM, *PTHREAD_STACK_ITEM; + +INT_PTR CALLBACK PhpThreadStackDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhpFreeThreadStackItem( + _In_ PTHREAD_STACK_ITEM StackItem + ); + +NTSTATUS PhpRefreshThreadStack( + _In_ HWND hwnd, + _In_ PTHREAD_STACK_CONTEXT ThreadStackContext + ); + +INT_PTR CALLBACK PhpThreadStackProgressDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +VOID PhShowThreadStackDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PPH_THREAD_PROVIDER ThreadProvider + ) +{ + NTSTATUS status; + THREAD_STACK_CONTEXT threadStackContext; + HANDLE threadHandle = NULL; + + // If the user is trying to view a system thread stack + // but KProcessHacker is not loaded, show an error message. + if (ProcessId == SYSTEM_PROCESS_ID && !KphIsConnected()) + { + PhShowError(ParentWindowHandle, KPH_ERROR_MESSAGE); + return; + } + + memset(&threadStackContext, 0, sizeof(THREAD_STACK_CONTEXT)); + threadStackContext.ProcessId = ProcessId; + threadStackContext.ThreadId = ThreadId; + threadStackContext.ThreadProvider = ThreadProvider; + threadStackContext.SymbolProvider = ThreadProvider->SymbolProvider; + + if (!NT_SUCCESS(status = PhOpenThread( + &threadHandle, + THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, + ThreadId + ))) + { + if (KphIsConnected()) + { + status = PhOpenThread( + &threadHandle, + ThreadQueryAccess, + ThreadId + ); + } + } + + if (!NT_SUCCESS(status)) + { + PhShowStatus(ParentWindowHandle, L"Unable to open the thread", status, 0); + return; + } + + threadStackContext.ThreadHandle = threadHandle; + threadStackContext.List = PhCreateList(10); + threadStackContext.NewList = PhCreateList(10); + PhInitializeQueuedLock(&threadStackContext.StatusLock); + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_THRDSTACK), + ParentWindowHandle, + PhpThreadStackDlgProc, + (LPARAM)&threadStackContext + ); + + PhClearReference(&threadStackContext.StatusMessage); + PhDereferenceObject(threadStackContext.NewList); + PhDereferenceObject(threadStackContext.List); + + if (threadStackContext.ThreadHandle) + NtClose(threadStackContext.ThreadHandle); +} + +static INT_PTR CALLBACK PhpThreadStackDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + PTHREAD_STACK_CONTEXT threadStackContext; + PPH_STRING title; + HWND lvHandle; + PPH_LAYOUT_MANAGER layoutManager; + + threadStackContext = (PTHREAD_STACK_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)threadStackContext); + + title = PhFormatString(L"Stack - thread %u", HandleToUlong(threadStackContext->ThreadId)); + SetWindowText(hwndDlg, title->Buffer); + PhDereferenceObject(title); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 30, L" "); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 300, L"Name"); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhLoadListViewColumnsFromSetting(L"ThreadStackListViewColumns", lvHandle); + + threadStackContext->ListViewHandle = lvHandle; + + layoutManager = PhAllocate(sizeof(PH_LAYOUT_MANAGER)); + PhInitializeLayoutManager(layoutManager, hwndDlg); + SetProp(hwndDlg, L"LayoutManager", (HANDLE)layoutManager); + + PhAddLayoutItem(layoutManager, lvHandle, NULL, + PH_ANCHOR_ALL); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_COPY), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDC_REFRESH), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(layoutManager, GetDlgItem(hwndDlg, IDOK), + NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 190; + rect.bottom = 120; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + PhLoadWindowPlacementFromSetting(NULL, L"ThreadStackWindowSize", hwndDlg); + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + if (PhPluginsEnabled) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.Type = PluginThreadStackInitializing; + control.UniqueKey = threadStackContext; + control.u.Initializing.ProcessId = threadStackContext->ProcessId; + control.u.Initializing.ThreadId = threadStackContext->ThreadId; + control.u.Initializing.ThreadHandle = threadStackContext->ThreadHandle; + control.u.Initializing.SymbolProvider = threadStackContext->SymbolProvider; + control.u.Initializing.CustomWalk = FALSE; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + + threadStackContext->CustomWalk = control.u.Initializing.CustomWalk; + } + + status = PhpRefreshThreadStack(hwndDlg, threadStackContext); + + if (status == STATUS_ABANDONED) + EndDialog(hwndDlg, IDCANCEL); + else if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to load the stack", status, 0); + } + break; + case WM_DESTROY: + { + PPH_LAYOUT_MANAGER layoutManager; + PTHREAD_STACK_CONTEXT threadStackContext; + ULONG i; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhDeleteLayoutManager(layoutManager); + PhFree(layoutManager); + + threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (PhPluginsEnabled) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.Type = PluginThreadStackUninitializing; + control.UniqueKey = threadStackContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + } + + for (i = 0; i < threadStackContext->List->Count; i++) + PhpFreeThreadStackItem(threadStackContext->List->Items[i]); + + PhSaveListViewColumnsToSetting(L"ThreadStackListViewColumns", GetDlgItem(hwndDlg, IDC_LIST)); + PhSaveWindowPlacementToSetting(NULL, L"ThreadStackWindowSize", hwndDlg); + + RemoveProp(hwndDlg, PhMakeContextAtom()); + RemoveProp(hwndDlg, L"LayoutManager"); + } + break; + case WM_COMMAND: + { + INT id = LOWORD(wParam); + + switch (id) + { + case IDCANCEL: // Esc and X button to close + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_REFRESH: + { + NTSTATUS status; + + if (!NT_SUCCESS(status = PhpRefreshThreadStack( + hwndDlg, + (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()) + ))) + { + PhShowStatus(hwndDlg, L"Unable to load the stack", status, 0); + } + } + break; + case IDC_COPY: + { + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + if (ListView_GetSelectedCount(lvHandle) == 0) + PhSetStateAllListViewItems(lvHandle, LVIS_SELECTED, LVIS_SELECTED); + + PhCopyListView(lvHandle); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)lvHandle, TRUE); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case LVN_GETINFOTIP: + { + LPNMLVGETINFOTIP getInfoTip = (LPNMLVGETINFOTIP)header; + HWND lvHandle; + PTHREAD_STACK_CONTEXT threadStackContext; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + if (header->hwndFrom == lvHandle) + { + PTHREAD_STACK_ITEM stackItem; + PPH_THREAD_STACK_FRAME stackFrame; + + if (PhGetListViewItemParam(lvHandle, getInfoTip->iItem, &stackItem)) + { + PH_STRING_BUILDER stringBuilder; + PPH_STRING fileName; + PH_SYMBOL_LINE_INFORMATION lineInfo; + + stackFrame = &stackItem->StackFrame; + PhInitializeStringBuilder(&stringBuilder, 40); + + PhAppendFormatStringBuilder( + &stringBuilder, + L"Stack: 0x%Ix, Frame: 0x%Ix\n", + stackFrame->StackAddress, + stackFrame->FrameAddress + ); + + // There are no params for kernel-mode stack traces. + if ((ULONG_PTR)stackFrame->PcAddress <= PhSystemBasicInformation.MaximumUserModeAddress) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"Parameters: 0x%Ix, 0x%Ix, 0x%Ix, 0x%Ix\n", + stackFrame->Params[0], + stackFrame->Params[1], + stackFrame->Params[2], + stackFrame->Params[3] + ); + } + + if (PhGetLineFromAddress( + threadStackContext->SymbolProvider, + (ULONG64)stackFrame->PcAddress, + &fileName, + NULL, + &lineInfo + )) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"File: %s: line %u\n", + fileName->Buffer, + lineInfo.LineNumber + ); + PhDereferenceObject(fileName); + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + if (PhPluginsEnabled) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.Type = PluginThreadStackGetTooltip; + control.UniqueKey = threadStackContext; + control.u.GetTooltip.StackFrame = stackFrame; + control.u.GetTooltip.StringBuilder = &stringBuilder; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + } + + PhCopyListViewInfoTip(getInfoTip, &stringBuilder.String->sr); + PhDeleteStringBuilder(&stringBuilder); + } + } + } + break; + } + } + break; + case WM_SIZE: + { + PPH_LAYOUT_MANAGER layoutManager; + + layoutManager = (PPH_LAYOUT_MANAGER)GetProp(hwndDlg, L"LayoutManager"); + PhLayoutManagerLayout(layoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + } + + return FALSE; +} + +static VOID PhpFreeThreadStackItem( + _In_ PTHREAD_STACK_ITEM StackItem + ) +{ + PhClearReference(&StackItem->Symbol); + PhFree(StackItem); +} + +static NTSTATUS PhpRefreshThreadStack( + _In_ HWND hwnd, + _In_ PTHREAD_STACK_CONTEXT ThreadStackContext + ) +{ + ULONG i; + + ThreadStackContext->StopWalk = FALSE; + PhMoveReference(&ThreadStackContext->StatusMessage, PhCreateString(L"Loading stack...")); + + DialogBoxParam( + PhInstanceHandle, + MAKEINTRESOURCE(IDD_PROGRESS), + hwnd, + PhpThreadStackProgressDlgProc, + (LPARAM)ThreadStackContext + ); + + if (!ThreadStackContext->StopWalk && NT_SUCCESS(ThreadStackContext->WalkStatus)) + { + for (i = 0; i < ThreadStackContext->List->Count; i++) + PhpFreeThreadStackItem(ThreadStackContext->List->Items[i]); + + PhDereferenceObject(ThreadStackContext->List); + ThreadStackContext->List = ThreadStackContext->NewList; + ThreadStackContext->NewList = PhCreateList(10); + + ListView_DeleteAllItems(ThreadStackContext->ListViewHandle); + SendMessage(ThreadStackContext->ListViewHandle, WM_SETREDRAW, FALSE, 0); + + for (i = 0; i < ThreadStackContext->List->Count; i++) + { + PTHREAD_STACK_ITEM item = ThreadStackContext->List->Items[i]; + INT lvItemIndex; + WCHAR integerString[PH_INT32_STR_LEN_1]; + + PhPrintUInt32(integerString, item->Index); + lvItemIndex = PhAddListViewItem(ThreadStackContext->ListViewHandle, MAXINT, integerString, item); + PhSetListViewSubItem(ThreadStackContext->ListViewHandle, lvItemIndex, 1, PhGetStringOrDefault(item->Symbol, L"???")); + } + + SendMessage(ThreadStackContext->ListViewHandle, WM_SETREDRAW, TRUE, 0); + InvalidateRect(ThreadStackContext->ListViewHandle, NULL, FALSE); + } + else + { + for (i = 0; i < ThreadStackContext->NewList->Count; i++) + PhpFreeThreadStackItem(ThreadStackContext->NewList->Items[i]); + + PhClearList(ThreadStackContext->NewList); + } + + if (ThreadStackContext->StopWalk) + return STATUS_ABANDONED; + + return ThreadStackContext->WalkStatus; +} + +static BOOLEAN NTAPI PhpWalkThreadStackCallback( + _In_ PPH_THREAD_STACK_FRAME StackFrame, + _In_opt_ PVOID Context + ) +{ + PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)Context; + PPH_STRING symbol; + PTHREAD_STACK_ITEM item; + + if (threadStackContext->StopWalk) + return FALSE; + + PhAcquireQueuedLockExclusive(&threadStackContext->StatusLock); + PhMoveReference(&threadStackContext->StatusMessage, + PhFormatString(L"Processing frame %u...", threadStackContext->NewList->Count)); + PhReleaseQueuedLockExclusive(&threadStackContext->StatusLock); + PostMessage(threadStackContext->ProgressWindowHandle, WM_PH_STATUS_UPDATE, 0, 0); + + symbol = PhGetSymbolFromAddress( + threadStackContext->SymbolProvider, + (ULONG64)StackFrame->PcAddress, + NULL, + NULL, + NULL, + NULL + ); + + if (symbol && + (StackFrame->Flags & PH_THREAD_STACK_FRAME_I386) && + !(StackFrame->Flags & PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT)) + { + PhMoveReference(&symbol, PhConcatStrings2(symbol->Buffer, L" (No unwind info)")); + } + + item = PhAllocate(sizeof(THREAD_STACK_ITEM)); + item->StackFrame = *StackFrame; + item->Index = threadStackContext->NewList->Count; + + if (PhPluginsEnabled) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.Type = PluginThreadStackResolveSymbol; + control.UniqueKey = threadStackContext; + control.u.ResolveSymbol.StackFrame = StackFrame; + control.u.ResolveSymbol.Symbol = symbol; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + + symbol = control.u.ResolveSymbol.Symbol; + } + + item->Symbol = symbol; + PhAddItemList(threadStackContext->NewList, item); + + return TRUE; +} + +static NTSTATUS PhpRefreshThreadStackThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + NTSTATUS status; + PTHREAD_STACK_CONTEXT threadStackContext = Parameter; + CLIENT_ID clientId; + BOOLEAN defaultWalk; + + PhInitializeAutoPool(&autoPool); + + PhLoadSymbolsThreadProvider(threadStackContext->ThreadProvider); + + clientId.UniqueProcess = threadStackContext->ProcessId; + clientId.UniqueThread = threadStackContext->ThreadId; + defaultWalk = TRUE; + + if (threadStackContext->CustomWalk) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.Type = PluginThreadStackWalkStack; + control.UniqueKey = threadStackContext; + control.u.WalkStack.Status = STATUS_UNSUCCESSFUL; + control.u.WalkStack.ThreadHandle = threadStackContext->ThreadHandle; + control.u.WalkStack.ProcessHandle = threadStackContext->SymbolProvider->ProcessHandle; + control.u.WalkStack.ClientId = &clientId; + control.u.WalkStack.Flags = PH_WALK_I386_STACK | PH_WALK_AMD64_STACK | PH_WALK_KERNEL_STACK; + control.u.WalkStack.Callback = PhpWalkThreadStackCallback; + control.u.WalkStack.CallbackContext = threadStackContext; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + status = control.u.WalkStack.Status; + + if (NT_SUCCESS(status)) + defaultWalk = FALSE; + } + + if (defaultWalk) + { + PH_PLUGIN_THREAD_STACK_CONTROL control; + + control.UniqueKey = threadStackContext; + + if (PhPluginsEnabled) + { + control.Type = PluginThreadStackBeginDefaultWalkStack; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + } + + status = PhWalkThreadStack( + threadStackContext->ThreadHandle, + threadStackContext->SymbolProvider->ProcessHandle, + &clientId, + threadStackContext->SymbolProvider, + PH_WALK_I386_STACK | PH_WALK_AMD64_STACK | PH_WALK_KERNEL_STACK, + PhpWalkThreadStackCallback, + threadStackContext + ); + + if (PhPluginsEnabled) + { + control.Type = PluginThreadStackEndDefaultWalkStack; + PhInvokeCallback(PhGetGeneralCallback(GeneralCallbackThreadStackControl), &control); + } + } + + if (threadStackContext->NewList->Count != 0) + status = STATUS_SUCCESS; + + threadStackContext->WalkStatus = status; + PostMessage(threadStackContext->ProgressWindowHandle, WM_PH_COMPLETED, 0, 0); + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +static INT_PTR CALLBACK PhpThreadStackProgressDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PTHREAD_STACK_CONTEXT threadStackContext; + HANDLE threadHandle; + + threadStackContext = (PTHREAD_STACK_CONTEXT)lParam; + SetProp(hwndDlg, PhMakeContextAtom(), (HANDLE)threadStackContext); + threadStackContext->ProgressWindowHandle = hwndDlg; + + if (threadHandle = PhCreateThread(0, PhpRefreshThreadStackThreadStart, threadStackContext)) + { + NtClose(threadHandle); + } + else + { + threadStackContext->WalkStatus = STATUS_UNSUCCESSFUL; + EndDialog(hwndDlg, IDOK); + break; + } + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE); + SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75); + SetWindowText(hwndDlg, L"Loading stack..."); + } + break; + case WM_DESTROY: + { + RemoveProp(hwndDlg, PhMakeContextAtom()); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + + EnableWindow(GetDlgItem(hwndDlg, IDCANCEL), FALSE); + threadStackContext->StopWalk = TRUE; + } + break; + } + } + break; + case WM_PH_COMPLETED: + { + EndDialog(hwndDlg, IDOK); + } + break; + case WM_PH_STATUS_UPDATE: + { + PTHREAD_STACK_CONTEXT threadStackContext = (PTHREAD_STACK_CONTEXT)GetProp(hwndDlg, PhMakeContextAtom()); + PPH_STRING message; + + PhAcquireQueuedLockExclusive(&threadStackContext->StatusLock); + message = threadStackContext->StatusMessage; + PhReferenceObject(message); + PhReleaseQueuedLockExclusive(&threadStackContext->StatusLock); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, message->Buffer); + PhDereferenceObject(message); + } + break; + } + + return 0; +} diff --git a/ProcessHacker/tokprp.c b/ProcessHacker/tokprp.c new file mode 100644 index 0000000..3736812 --- /dev/null +++ b/ProcessHacker/tokprp.c @@ -0,0 +1,1933 @@ +/* + * Process Hacker - + * token properties + * + * Copyright (C) 2010-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +typedef struct _ATTRIBUTE_NODE +{ + PH_TREENEW_NODE Node; + PPH_LIST Children; + PPH_STRING Text; +} ATTRIBUTE_NODE, *PATTRIBUTE_NODE; + +typedef struct _ATTRIBUTE_TREE_CONTEXT +{ + PPH_LIST RootList; + PPH_LIST NodeList; +} ATTRIBUTE_TREE_CONTEXT, *PATTRIBUTE_TREE_CONTEXT; + +typedef struct _TOKEN_PAGE_CONTEXT +{ + PPH_OPEN_OBJECT OpenObject; + PVOID Context; + DLGPROC HookProc; + + HWND GroupsListViewHandle; + HWND PrivilegesListViewHandle; + + PTOKEN_GROUPS Groups; + PTOKEN_PRIVILEGES Privileges; + PTOKEN_GROUPS Capabilities; + + ATTRIBUTE_TREE_CONTEXT ClaimsTreeContext; + ATTRIBUTE_TREE_CONTEXT AuthzTreeContext; +} TOKEN_PAGE_CONTEXT, *PTOKEN_PAGE_CONTEXT; + +INT CALLBACK PhpTokenPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +INT_PTR CALLBACK PhpTokenPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhpShowTokenAdvancedProperties( + _In_ HWND ParentWindowHandle, + _In_ PTOKEN_PAGE_CONTEXT Context + ); + +INT_PTR CALLBACK PhpTokenGeneralPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpTokenAdvancedPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpTokenCapabilitiesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +BOOLEAN NTAPI PhpAttributeTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +INT_PTR CALLBACK PhpTokenClaimsPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PhpTokenAttributesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhShowTokenProperties( + _In_ HWND ParentWindowHandle, + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ PWSTR Title + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + HPROPSHEETPAGE pages[1]; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = Title ? Title : L"Token"; + propSheetHeader.nPages = 1; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + pages[0] = PhCreateTokenPage(OpenObject, Context, NULL); + + PhModalPropertySheet(&propSheetHeader); +} + +HPROPSHEETPAGE PhCreateTokenPage( + _In_ PPH_OPEN_OBJECT OpenObject, + _In_opt_ PVOID Context, + _In_opt_ DLGPROC HookProc + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + PROPSHEETPAGE propSheetPage; + PTOKEN_PAGE_CONTEXT tokenPageContext; + + tokenPageContext = PhCreateAlloc(sizeof(TOKEN_PAGE_CONTEXT)); + memset(tokenPageContext, 0, sizeof(TOKEN_PAGE_CONTEXT)); + tokenPageContext->OpenObject = OpenObject; + tokenPageContext->Context = Context; + tokenPageContext->HookProc = HookProc; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USECALLBACK; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_OBJTOKEN); + propSheetPage.pfnDlgProc = PhpTokenPageProc; + propSheetPage.lParam = (LPARAM)tokenPageContext; + propSheetPage.pfnCallback = PhpTokenPropPageProc; + + propSheetPageHandle = CreatePropertySheetPage(&propSheetPage); + // CreatePropertySheetPage would have sent PSPCB_ADDREF (below), + // which would have added a reference. + PhDereferenceObject(tokenPageContext); + + return propSheetPageHandle; +} + +INT CALLBACK PhpTokenPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + + tokenPageContext = (PTOKEN_PAGE_CONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + { + PhReferenceObject(tokenPageContext); + } + else if (uMsg == PSPCB_RELEASE) + { + PhDereferenceObject(tokenPageContext); + } + + return 1; +} + +PPH_STRING PhGetGroupAttributesString( + _In_ ULONG Attributes + ) +{ + PWSTR baseString; + PPH_STRING string; + + if (Attributes & SE_GROUP_INTEGRITY) + { + if (Attributes & SE_GROUP_INTEGRITY_ENABLED) + return PhCreateString(L"Integrity"); + else + return PhCreateString(L"Integrity (disabled)"); + } + + if (Attributes & SE_GROUP_LOGON_ID) + baseString = L"Logon ID"; + else if (Attributes & SE_GROUP_MANDATORY) + baseString = L"Mandatory"; + else if (Attributes & SE_GROUP_OWNER) + baseString = L"Owner"; + else if (Attributes & SE_GROUP_RESOURCE) + baseString = L"Resource"; + else if (Attributes & SE_GROUP_USE_FOR_DENY_ONLY) + baseString = L"Use for deny only"; + else + baseString = NULL; + + if (!baseString) + { + if (Attributes & SE_GROUP_ENABLED_BY_DEFAULT) + return PhCreateString(L"Default enabled"); + else if (Attributes & SE_GROUP_ENABLED) + return PhReferenceEmptyString(); + else + return PhCreateString(L"Disabled"); + } + + if (Attributes & SE_GROUP_ENABLED_BY_DEFAULT) + string = PhConcatStrings2(baseString, L" (default enabled)"); + else if (Attributes & SE_GROUP_ENABLED) + string = PhCreateString(baseString); + else + string = PhConcatStrings2(baseString, L" (disabled)"); + + return string; +} + +COLORREF PhGetGroupAttributesColor( + _In_ ULONG Attributes + ) +{ + if (Attributes & SE_GROUP_INTEGRITY) + { + if (Attributes & SE_GROUP_INTEGRITY_ENABLED) + return RGB(0xe0, 0xf0, 0xe0); + else + return GetSysColor(COLOR_WINDOW); + } + + if (Attributes & SE_GROUP_ENABLED_BY_DEFAULT) + return RGB(0xe0, 0xf0, 0xe0); + else if (Attributes & SE_GROUP_ENABLED) + return GetSysColor(COLOR_WINDOW); + else + return RGB(0xf0, 0xe0, 0xe0); +} + +static COLORREF NTAPI PhpTokenGroupColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PSID_AND_ATTRIBUTES sidAndAttributes = Param; + + return PhGetGroupAttributesColor(sidAndAttributes->Attributes); +} + +PWSTR PhGetPrivilegeAttributesString( + _In_ ULONG Attributes + ) +{ + if (Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT) + return L"Default Enabled"; + else if (Attributes & SE_PRIVILEGE_ENABLED) + return L"Enabled"; + else + return L"Disabled"; +} + +COLORREF PhGetPrivilegeAttributesColor( + _In_ ULONG Attributes + ) +{ + if (Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT) + return RGB(0xc0, 0xf0, 0xc0); + else if (Attributes & SE_PRIVILEGE_ENABLED) + return RGB(0xe0, 0xf0, 0xe0); + else + return RGB(0xf0, 0xe0, 0xe0); +} + +static COLORREF NTAPI PhpTokenPrivilegeColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PLUID_AND_ATTRIBUTES luidAndAttributes = Param; + + return PhGetPrivilegeAttributesColor(luidAndAttributes->Attributes); +} + +PWSTR PhGetElevationTypeString( + _In_ TOKEN_ELEVATION_TYPE ElevationType + ) +{ + switch (ElevationType) + { + case TokenElevationTypeFull: + return L"Yes"; + case TokenElevationTypeLimited: + return L"No"; + default: + return L"N/A"; + } +} + +BOOLEAN PhpUpdateTokenGroups( + _In_ HWND hwndDlg, + _In_ PTOKEN_PAGE_CONTEXT TokenPageContext, + _In_ HWND GroupsLv, + _In_ HANDLE TokenHandle + ) +{ + PTOKEN_GROUPS groups; + ULONG i; + + if (!NT_SUCCESS(PhGetTokenGroups(TokenHandle, &groups))) + return FALSE; + + ExtendedListView_SetRedraw(GroupsLv, FALSE); + + ListView_DeleteAllItems(GroupsLv); + + for (i = 0; i < groups->GroupCount; i++) + { + INT lvItemIndex; + PPH_STRING fullName; + PPH_STRING attributesString; + + if (!(fullName = PhGetSidFullName(groups->Groups[i].Sid, TRUE, NULL))) + fullName = PhSidToStringSid(groups->Groups[i].Sid); + + if (fullName) + { + lvItemIndex = PhAddListViewItem(GroupsLv, MAXINT, fullName->Buffer, &groups->Groups[i]); + attributesString = PhGetGroupAttributesString(groups->Groups[i].Attributes); + PhSetListViewSubItem(GroupsLv, lvItemIndex, 1, attributesString->Buffer); + + PhDereferenceObject(attributesString); + PhDereferenceObject(fullName); + } + } + + ExtendedListView_SortItems(GroupsLv); + + ExtendedListView_SetRedraw(GroupsLv, TRUE); + + if (TokenPageContext->Groups) + PhFree(TokenPageContext->Groups); + + TokenPageContext->Groups = groups; + + return TRUE; +} + +FORCEINLINE PTOKEN_PAGE_CONTEXT PhpTokenPageHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return (PTOKEN_PAGE_CONTEXT)PhpGenericPropertyPageHeader( + hwndDlg, uMsg, wParam, lParam, L"TokenPageContext"); +} + +INT_PTR CALLBACK PhpTokenPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + if (tokenPageContext->HookProc) + { + if (tokenPageContext->HookProc(hwndDlg, uMsg, wParam, lParam)) + return TRUE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND groupsLv; + HWND privilegesLv; + HANDLE tokenHandle; + + tokenPageContext->GroupsListViewHandle = groupsLv = GetDlgItem(hwndDlg, IDC_GROUPS); + tokenPageContext->PrivilegesListViewHandle = privilegesLv = GetDlgItem(hwndDlg, IDC_PRIVILEGES); + PhSetListViewStyle(groupsLv, FALSE, TRUE); + PhSetListViewStyle(privilegesLv, FALSE, TRUE); + PhSetControlTheme(groupsLv, L"explorer"); + PhSetControlTheme(privilegesLv, L"explorer"); + + PhAddListViewColumn(groupsLv, 0, 0, 0, LVCFMT_LEFT, 160, L"Name"); + PhAddListViewColumn(groupsLv, 1, 1, 1, LVCFMT_LEFT, 200, L"Flags"); + + PhAddListViewColumn(privilegesLv, 0, 0, 0, LVCFMT_LEFT, 100, L"Name"); + PhAddListViewColumn(privilegesLv, 1, 1, 1, LVCFMT_LEFT, 100, L"Status"); + PhAddListViewColumn(privilegesLv, 2, 2, 2, LVCFMT_LEFT, 170, L"Description"); + + PhSetExtendedListView(groupsLv); + ExtendedListView_SetItemColorFunction(groupsLv, PhpTokenGroupColorFunction); + + PhSetExtendedListView(privilegesLv); + ExtendedListView_SetItemColorFunction(privilegesLv, PhpTokenPrivilegeColorFunction); + + SetDlgItemText(hwndDlg, IDC_USER, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_USERSID, L"Unknown"); + SetDlgItemText(hwndDlg, IDC_APPCONTAINERSID, L"Unknown"); + + if (!WINDOWS_HAS_UAC) + ShowWindow(GetDlgItem(hwndDlg, IDC_INTEGRITY), SW_HIDE); + + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + PTOKEN_USER tokenUser; + PPH_STRING fullUserName; + PPH_STRING stringUserSid; + ULONG sessionId; + TOKEN_ELEVATION_TYPE elevationType; + BOOLEAN isVirtualizationAllowed; + BOOLEAN isVirtualizationEnabled; + PTOKEN_APPCONTAINER_INFORMATION appContainerInfo; + PPH_STRING appContainerSid; + ULONG i; + + if (NT_SUCCESS(PhGetTokenUser(tokenHandle, &tokenUser))) + { + if (fullUserName = PhGetSidFullName(tokenUser->User.Sid, TRUE, NULL)) + { + SetDlgItemText(hwndDlg, IDC_USER, fullUserName->Buffer); + PhDereferenceObject(fullUserName); + } + + if (stringUserSid = PhSidToStringSid(tokenUser->User.Sid)) + { + SetDlgItemText(hwndDlg, IDC_USERSID, stringUserSid->Buffer); + PhDereferenceObject(stringUserSid); + } + + PhFree(tokenUser); + } + + if (NT_SUCCESS(PhGetTokenSessionId(tokenHandle, &sessionId))) + SetDlgItemInt(hwndDlg, IDC_SESSIONID, sessionId, FALSE); + + if (WINDOWS_HAS_UAC) + { + if (NT_SUCCESS(PhGetTokenElevationType(tokenHandle, &elevationType))) + SetDlgItemText(hwndDlg, IDC_ELEVATED, PhGetElevationTypeString(elevationType)); + + if (NT_SUCCESS(PhGetTokenIsVirtualizationAllowed(tokenHandle, &isVirtualizationAllowed))) + { + if (isVirtualizationAllowed) + { + if (NT_SUCCESS(PhGetTokenIsVirtualizationEnabled(tokenHandle, &isVirtualizationEnabled))) + { + SetDlgItemText( + hwndDlg, + IDC_VIRTUALIZED, + isVirtualizationEnabled ? L"Yes" : L"No" + ); + } + } + else + { + SetDlgItemText(hwndDlg, IDC_VIRTUALIZED, L"Not allowed"); + } + } + } + else + { + SetDlgItemText(hwndDlg, IDC_ELEVATED, L"N/A"); + SetDlgItemText(hwndDlg, IDC_VIRTUALIZED, L"N/A"); + } + + if (WINDOWS_HAS_IMMERSIVE) + { + appContainerSid = NULL; + + if (NT_SUCCESS(PhQueryTokenVariableSize(tokenHandle, TokenAppContainerSid, &appContainerInfo))) + { + if (appContainerInfo->TokenAppContainer) + appContainerSid = PhSidToStringSid(appContainerInfo->TokenAppContainer); + + PhFree(appContainerInfo); + } + + if (appContainerSid) + { + SetDlgItemText(hwndDlg, IDC_APPCONTAINERSID, appContainerSid->Buffer); + PhDereferenceObject(appContainerSid); + } + else + { + SetDlgItemText(hwndDlg, IDC_APPCONTAINERSID, L"N/A"); + } + } + else + { + SetDlgItemText(hwndDlg, IDC_APPCONTAINERSID, L"N/A"); + } + + // Groups + PhpUpdateTokenGroups(hwndDlg, tokenPageContext, groupsLv, tokenHandle); + + // Privileges + if (NT_SUCCESS(PhGetTokenPrivileges(tokenHandle, &tokenPageContext->Privileges))) + { + for (i = 0; i < tokenPageContext->Privileges->PrivilegeCount; i++) + { + INT lvItemIndex; + PPH_STRING privilegeName; + PPH_STRING privilegeDisplayName; + + if (PhLookupPrivilegeName( + &tokenPageContext->Privileges->Privileges[i].Luid, + &privilegeName + )) + { + privilegeDisplayName = NULL; + PhLookupPrivilegeDisplayName(&privilegeName->sr, &privilegeDisplayName); + + // Name + lvItemIndex = PhAddListViewItem(privilegesLv, MAXINT, privilegeName->Buffer, + &tokenPageContext->Privileges->Privileges[i]); + // Status + PhSetListViewSubItem(privilegesLv, lvItemIndex, 1, + PhGetPrivilegeAttributesString( + tokenPageContext->Privileges->Privileges[i].Attributes)); + // Description + PhSetListViewSubItem(privilegesLv, lvItemIndex, 2, + PhGetString(privilegeDisplayName)); + + if (privilegeDisplayName) PhDereferenceObject(privilegeDisplayName); + PhDereferenceObject(privilegeName); + } + } + + ExtendedListView_SortItems(privilegesLv); + } + + NtClose(tokenHandle); + } + } + break; + case WM_DESTROY: + { + if (tokenPageContext->Groups) PhFree(tokenPageContext->Groups); + if (tokenPageContext->Privileges) PhFree(tokenPageContext->Privileges); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_PRIVILEGE_ENABLE: + case ID_PRIVILEGE_DISABLE: + case ID_PRIVILEGE_REMOVE: + { + NTSTATUS status; + PLUID_AND_ATTRIBUTES *privileges; + ULONG numberOfPrivileges; + HANDLE tokenHandle; + ULONG i; + + if (LOWORD(wParam) == ID_PRIVILEGE_REMOVE) + { + if (!PhShowConfirmMessage( + hwndDlg, + L"remove", + L"the selected privilege(s)", + L"Removing privileges may reduce the functionality of the process, " + L"and is permanent for the lifetime of the process.", + FALSE + )) + break; + } + + PhGetSelectedListViewItemParams( + tokenPageContext->PrivilegesListViewHandle, + &privileges, + &numberOfPrivileges + ); + + status = tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_ADJUST_PRIVILEGES, + tokenPageContext->Context + ); + + if (NT_SUCCESS(status)) + { + ExtendedListView_SetRedraw(tokenPageContext->PrivilegesListViewHandle, FALSE); + + for (i = 0; i < numberOfPrivileges; i++) + { + PPH_STRING privilegeName = NULL; + ULONG newAttributes; + + PhLookupPrivilegeName(&privileges[i]->Luid, &privilegeName); + PH_AUTO(privilegeName); + + switch (LOWORD(wParam)) + { + case ID_PRIVILEGE_ENABLE: + newAttributes = SE_PRIVILEGE_ENABLED; + break; + case ID_PRIVILEGE_DISABLE: + newAttributes = 0; + break; + case ID_PRIVILEGE_REMOVE: + newAttributes = SE_PRIVILEGE_REMOVED; + break; + } + + // Privileges which are enabled by default cannot be + // modified except to remove them. + + if ( + privileges[i]->Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT && + LOWORD(wParam) != ID_PRIVILEGE_REMOVE + ) + { + if (LOWORD(wParam) == ID_PRIVILEGE_DISABLE) + { + if (!PhShowContinueStatus( + hwndDlg, + PhaConcatStrings2(L"Unable to disable ", privilegeName->Buffer)->Buffer, + STATUS_UNSUCCESSFUL, + 0 + )) + break; + } + + continue; + } + + if (PhSetTokenPrivilege( + tokenHandle, + NULL, + &privileges[i]->Luid, + newAttributes + )) + { + INT lvItemIndex = PhFindListViewItemByParam( + tokenPageContext->PrivilegesListViewHandle, + -1, + privileges[i] + ); + + if (LOWORD(wParam) != ID_PRIVILEGE_REMOVE) + { + // Refresh the status text (and background + // color). + privileges[i]->Attributes = newAttributes; + PhSetListViewSubItem( + tokenPageContext->PrivilegesListViewHandle, + lvItemIndex, + 1, + PhGetPrivilegeAttributesString(newAttributes) + ); + } + else + { + ListView_DeleteItem( + tokenPageContext->PrivilegesListViewHandle, + lvItemIndex + ); + } + } + else + { + PWSTR action = L"set"; + + switch (LOWORD(wParam)) + { + case ID_PRIVILEGE_ENABLE: + action = L"enable"; + break; + case ID_PRIVILEGE_DISABLE: + action = L"disable"; + break; + case ID_PRIVILEGE_REMOVE: + action = L"remove"; + break; + } + + if (!PhShowContinueStatus( + hwndDlg, + PhaFormatString(L"Unable to %s %s", action, privilegeName->Buffer)->Buffer, + STATUS_UNSUCCESSFUL, + 0 + )) + break; + } + } + + ExtendedListView_SetRedraw(tokenPageContext->PrivilegesListViewHandle, TRUE); + + NtClose(tokenHandle); + } + else + { + PhShowStatus(hwndDlg, L"Unable to open the token", status, 0); + } + + PhFree(privileges); + + ExtendedListView_SortItems(tokenPageContext->PrivilegesListViewHandle); + } + break; + case ID_PRIVILEGE_COPY: + { + PhCopyListView(tokenPageContext->PrivilegesListViewHandle); + } + break; + case IDC_INTEGRITY: + { + NTSTATUS status; + RECT rect; + PPH_EMENU menu; + HANDLE tokenHandle; + MANDATORY_LEVEL integrityLevel; + PPH_EMENU_ITEM selectedItem; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_INTEGRITY), &rect); + + menu = PhCreateEMenu(); + + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelSecureProcess, L"Protected", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelSystem, L"System", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelHigh, L"High", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelMedium, L"Medium", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelLow, L"Low", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, MandatoryLevelUntrusted, L"Untrusted", NULL, NULL), -1); + + integrityLevel = -1; + + // Put a radio check on the menu item that corresponds with the current integrity level. + // Also disable menu items which correspond to higher integrity levels since + // NtSetInformationToken doesn't allow integrity levels to be raised. + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + if (NT_SUCCESS(PhGetTokenIntegrityLevel( + tokenHandle, + &integrityLevel, + NULL + ))) + { + ULONG i; + + for (i = 0; i < menu->Items->Count; i++) + { + PPH_EMENU_ITEM menuItem = menu->Items->Items[i]; + + if (menuItem->Id == integrityLevel) + { + menuItem->Flags |= PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK; + } + else if (menuItem->Id > (ULONG)integrityLevel) + { + menuItem->Flags |= PH_EMENU_DISABLED; + } + } + } + + NtClose(tokenHandle); + } + + selectedItem = PhShowEMenu( + menu, + hwndDlg, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + rect.left, + rect.bottom + ); + + if (selectedItem && selectedItem->Id != integrityLevel) + { + if (PhShowConfirmMessage( + hwndDlg, + L"set", + L"the integrity level", + L"Once lowered, the integrity level of the token cannot be raised again.", + FALSE + )) + { + if (NT_SUCCESS(status = tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, + tokenPageContext->Context + ))) + { + static SID_IDENTIFIER_AUTHORITY mandatoryLabelAuthority = SECURITY_MANDATORY_LABEL_AUTHORITY; + + UCHAR newSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; + PSID newSid; + TOKEN_MANDATORY_LABEL mandatoryLabel; + + newSid = (PSID)newSidBuffer; + RtlInitializeSid(newSid, &mandatoryLabelAuthority, 1); + *RtlSubAuthoritySid(newSid, 0) = MANDATORY_LEVEL_TO_MANDATORY_RID(selectedItem->Id); + mandatoryLabel.Label.Sid = newSid; + mandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY; + + status = NtSetInformationToken( + tokenHandle, + TokenIntegrityLevel, + &mandatoryLabel, + sizeof(TOKEN_MANDATORY_LABEL) + ); + + if (NT_SUCCESS(status)) + PhpUpdateTokenGroups(hwndDlg, tokenPageContext, GetDlgItem(hwndDlg, IDC_GROUPS), tokenHandle); + + NtClose(tokenHandle); + } + + if (!NT_SUCCESS(status)) + PhShowStatus(hwndDlg, L"Unable to set the integrity level", status, 0); + } + } + + PhDestroyEMenu(menu); + } + break; + case IDC_ADVANCED: + { + PhpShowTokenAdvancedProperties(hwndDlg, tokenPageContext); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_SESSIONID)); + return TRUE; + } + break; + } + + PhHandleListViewNotifyBehaviors(lParam, tokenPageContext->GroupsListViewHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + PhHandleListViewNotifyBehaviors(lParam, tokenPageContext->PrivilegesListViewHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + } + break; + case WM_CONTEXTMENU: + { + if ((HWND)wParam == tokenPageContext->PrivilegesListViewHandle) + { + POINT point; + + point.x = (SHORT)LOWORD(lParam); + point.y = (SHORT)HIWORD(lParam); + + if (point.x == -1 && point.y == -1) + PhGetListViewContextMenuPoint((HWND)wParam, &point); + + if (ListView_GetSelectedCount(tokenPageContext->PrivilegesListViewHandle) != 0) + { + PPH_EMENU menu; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PhInstanceHandle, MAKEINTRESOURCE(IDR_PRIVILEGE), 0); + PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); + PhDestroyEMenu(menu); + } + } + } + break; + } + + REFLECT_MESSAGE_DLG(hwndDlg, tokenPageContext->GroupsListViewHandle, uMsg, wParam, lParam); + REFLECT_MESSAGE_DLG(hwndDlg, tokenPageContext->PrivilegesListViewHandle, uMsg, wParam, lParam); + + return FALSE; +} + +VOID PhpShowTokenAdvancedProperties( + _In_ HWND ParentWindowHandle, + _In_ PTOKEN_PAGE_CONTEXT Context + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + HPROPSHEETPAGE pages[6]; + PROPSHEETPAGE page; + ULONG numberOfPages; + PH_STD_OBJECT_SECURITY stdObjectSecurity; + PPH_ACCESS_ENTRY accessEntries; + ULONG numberOfAccessEntries; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = ParentWindowHandle; + propSheetHeader.pszCaption = L"Token"; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + numberOfPages = 0; + + // General + + memset(&page, 0, sizeof(PROPSHEETPAGE)); + page.dwSize = sizeof(PROPSHEETPAGE); + page.pszTemplate = MAKEINTRESOURCE(IDD_TOKGENERAL); + page.pfnDlgProc = PhpTokenGeneralPageProc; + page.lParam = (LPARAM)Context; + pages[numberOfPages++] = CreatePropertySheetPage(&page); + + // Advanced + + memset(&page, 0, sizeof(PROPSHEETPAGE)); + page.dwSize = sizeof(PROPSHEETPAGE); + page.pszTemplate = MAKEINTRESOURCE(IDD_TOKADVANCED); + page.pfnDlgProc = PhpTokenAdvancedPageProc; + page.lParam = (LPARAM)Context; + pages[numberOfPages++] = CreatePropertySheetPage(&page); + + if (WindowsVersion >= WINDOWS_8) + { + // Capabilities + + memset(&page, 0, sizeof(PROPSHEETPAGE)); + page.dwSize = sizeof(PROPSHEETPAGE); + page.pszTemplate = MAKEINTRESOURCE(IDD_TOKCAPABILITIES); + page.pfnDlgProc = PhpTokenCapabilitiesPageProc; + page.lParam = (LPARAM)Context; + pages[numberOfPages++] = CreatePropertySheetPage(&page); + + // Claims + + memset(&page, 0, sizeof(PROPSHEETPAGE)); + page.dwSize = sizeof(PROPSHEETPAGE); + page.dwFlags = PSP_USETITLE; + page.pszTemplate = MAKEINTRESOURCE(IDD_TOKATTRIBUTES); + page.pszTitle = L"Claims"; + page.pfnDlgProc = PhpTokenClaimsPageProc; + page.lParam = (LPARAM)Context; + pages[numberOfPages++] = CreatePropertySheetPage(&page); + + // (Token) Attributes + + memset(&page, 0, sizeof(PROPSHEETPAGE)); + page.dwSize = sizeof(PROPSHEETPAGE); + page.dwFlags = PSP_USETITLE; + page.pszTemplate = MAKEINTRESOURCE(IDD_TOKATTRIBUTES); + page.pszTitle = L"Attributes"; + page.pfnDlgProc = PhpTokenAttributesPageProc; + page.lParam = (LPARAM)Context; + pages[numberOfPages++] = CreatePropertySheetPage(&page); + } + + // Security + + stdObjectSecurity.OpenObject = Context->OpenObject; + stdObjectSecurity.ObjectType = L"Token"; + stdObjectSecurity.Context = Context->Context; + + if (PhGetAccessEntries(L"Token", &accessEntries, &numberOfAccessEntries)) + { + pages[numberOfPages++] = PhCreateSecurityPage( + L"Token", + PhStdGetObjectSecurity, + PhStdSetObjectSecurity, + &stdObjectSecurity, + accessEntries, + numberOfAccessEntries + ); + PhFree(accessEntries); + } + + propSheetHeader.nPages = numberOfPages; + PhModalPropertySheet(&propSheetHeader); +} + +static NTSTATUS PhpOpenLinkedToken( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ) +{ + return PhGetTokenLinkedToken((HANDLE)Context, Handle); +} + +INT_PTR CALLBACK PhpTokenGeneralPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE tokenHandle; + PPH_STRING tokenUserName = NULL; + PPH_STRING tokenUserSid = NULL; + PPH_STRING tokenOwnerName = NULL; + PPH_STRING tokenPrimaryGroupName = NULL; + ULONG tokenSessionId = -1; + PWSTR tokenElevated = L"N/A"; + BOOLEAN hasLinkedToken = FALSE; + PWSTR tokenVirtualization = L"N/A"; + WCHAR tokenSourceName[TOKEN_SOURCE_LENGTH + 1] = L"Unknown"; + WCHAR tokenSourceLuid[PH_PTR_STR_LEN_1] = L"Unknown"; + + // HACK + PhCenterWindow(GetParent(hwndDlg), GetParent(GetParent(hwndDlg))); + + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + PTOKEN_USER tokenUser; + PTOKEN_OWNER tokenOwner; + PTOKEN_PRIMARY_GROUP tokenPrimaryGroup; + TOKEN_ELEVATION_TYPE elevationType; + BOOLEAN isVirtualizationAllowed; + BOOLEAN isVirtualizationEnabled; + + if (NT_SUCCESS(PhGetTokenUser(tokenHandle, &tokenUser))) + { + tokenUserName = PH_AUTO(PhGetSidFullName(tokenUser->User.Sid, TRUE, NULL)); + tokenUserSid = PH_AUTO(PhSidToStringSid(tokenUser->User.Sid)); + + PhFree(tokenUser); + } + + if (NT_SUCCESS(PhGetTokenOwner(tokenHandle, &tokenOwner))) + { + tokenOwnerName = PH_AUTO(PhGetSidFullName(tokenOwner->Owner, TRUE, NULL)); + PhFree(tokenOwner); + } + + if (NT_SUCCESS(PhGetTokenPrimaryGroup(tokenHandle, &tokenPrimaryGroup))) + { + tokenPrimaryGroupName = PH_AUTO(PhGetSidFullName( + tokenPrimaryGroup->PrimaryGroup, TRUE, NULL)); + PhFree(tokenPrimaryGroup); + } + + PhGetTokenSessionId(tokenHandle, &tokenSessionId); + + if (WINDOWS_HAS_UAC) + { + if (NT_SUCCESS(PhGetTokenElevationType(tokenHandle, &elevationType))) + { + tokenElevated = PhGetElevationTypeString(elevationType); + hasLinkedToken = elevationType != TokenElevationTypeDefault; + } + + if (NT_SUCCESS(PhGetTokenIsVirtualizationAllowed(tokenHandle, &isVirtualizationAllowed))) + { + if (isVirtualizationAllowed) + { + if (NT_SUCCESS(PhGetTokenIsVirtualizationEnabled(tokenHandle, &isVirtualizationEnabled))) + { + tokenVirtualization = isVirtualizationEnabled ? L"Enabled" : L"Disabled"; + } + } + else + { + tokenVirtualization = L"Not Allowed"; + } + } + } + + NtClose(tokenHandle); + } + + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY_SOURCE, + tokenPageContext->Context + ))) + { + TOKEN_SOURCE tokenSource; + + if (NT_SUCCESS(PhGetTokenSource(tokenHandle, &tokenSource))) + { + PhCopyStringZFromBytes( + tokenSource.SourceName, + TOKEN_SOURCE_LENGTH, + tokenSourceName, + sizeof(tokenSourceName) / 2, + NULL + ); + + PhPrintPointer(tokenSourceLuid, UlongToPtr(tokenSource.SourceIdentifier.LowPart)); + } + + NtClose(tokenHandle); + } + + SetDlgItemText(hwndDlg, IDC_USER, PhGetStringOrDefault(tokenUserName, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_USERSID, PhGetStringOrDefault(tokenUserSid, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_OWNER, PhGetStringOrDefault(tokenOwnerName, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_PRIMARYGROUP, PhGetStringOrDefault(tokenPrimaryGroupName, L"Unknown")); + + if (tokenSessionId != -1) + SetDlgItemInt(hwndDlg, IDC_SESSIONID, tokenSessionId, FALSE); + else + SetDlgItemText(hwndDlg, IDC_SESSIONID, L"Unknown"); + + SetDlgItemText(hwndDlg, IDC_ELEVATED, tokenElevated); + SetDlgItemText(hwndDlg, IDC_VIRTUALIZATION, tokenVirtualization); + SetDlgItemText(hwndDlg, IDC_SOURCENAME, tokenSourceName); + SetDlgItemText(hwndDlg, IDC_SOURCELUID, tokenSourceLuid); + + if (!hasLinkedToken) + ShowWindow(GetDlgItem(hwndDlg, IDC_LINKEDTOKEN), SW_HIDE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_LINKEDTOKEN: + { + NTSTATUS status; + HANDLE tokenHandle; + + if (NT_SUCCESS(status = tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + PhShowTokenProperties(hwndDlg, PhpOpenLinkedToken, (PVOID)tokenHandle, L"Linked Token"); + NtClose(tokenHandle); + } + else + { + PhShowStatus(hwndDlg, L"Unable to open the token", status, 0); + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_LINKEDTOKEN)); + return TRUE; + } + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PhpTokenAdvancedPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE tokenHandle; + PWSTR tokenType = L"Unknown"; + PWSTR tokenImpersonationLevel = L"Unknown"; + WCHAR tokenLuid[PH_PTR_STR_LEN_1] = L"Unknown"; + WCHAR authenticationLuid[PH_PTR_STR_LEN_1] = L"Unknown"; + PPH_STRING memoryUsed = NULL; + PPH_STRING memoryAvailable = NULL; + + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + TOKEN_STATISTICS statistics; + + if (NT_SUCCESS(PhGetTokenStatistics(tokenHandle, &statistics))) + { + switch (statistics.TokenType) + { + case TokenPrimary: + tokenType = L"Primary"; + break; + case TokenImpersonation: + tokenType = L"Impersonation"; + break; + } + + if (statistics.TokenType == TokenImpersonation) + { + switch (statistics.ImpersonationLevel) + { + case SecurityAnonymous: + tokenImpersonationLevel = L"Anonymous"; + break; + case SecurityIdentification: + tokenImpersonationLevel = L"Identification"; + break; + case SecurityImpersonation: + tokenImpersonationLevel = L"Impersonation"; + break; + case SecurityDelegation: + tokenImpersonationLevel = L"Delegation"; + break; + } + } + else + { + tokenImpersonationLevel = L"N/A"; + } + + PhPrintPointer(tokenLuid, UlongToPtr(statistics.TokenId.LowPart)); + PhPrintPointer(authenticationLuid, UlongToPtr(statistics.AuthenticationId.LowPart)); + + // DynamicCharged contains the number of bytes allocated. + // DynamicAvailable contains the number of bytes free. + memoryUsed = PhaFormatSize(statistics.DynamicCharged - statistics.DynamicAvailable, -1); + memoryAvailable = PhaFormatSize(statistics.DynamicCharged, -1); + } + + NtClose(tokenHandle); + } + + SetDlgItemText(hwndDlg, IDC_TYPE, tokenType); + SetDlgItemText(hwndDlg, IDC_IMPERSONATIONLEVEL, tokenImpersonationLevel); + SetDlgItemText(hwndDlg, IDC_TOKENLUID, tokenLuid); + SetDlgItemText(hwndDlg, IDC_AUTHENTICATIONLUID, authenticationLuid); + SetDlgItemText(hwndDlg, IDC_MEMORYUSED, PhGetStringOrDefault(memoryUsed, L"Unknown")); + SetDlgItemText(hwndDlg, IDC_MEMORYAVAILABLE, PhGetStringOrDefault(memoryAvailable, L"Unknown")); + } + break; + } + + return FALSE; +} + +static COLORREF NTAPI PhpTokenCapabilitiesColorFunction( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ) +{ + PSID_AND_ATTRIBUTES sidAndAttributes = Param; + + return PhGetGroupAttributesColor(sidAndAttributes->Attributes); +} + +INT_PTR CALLBACK PhpTokenCapabilitiesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + HWND lvHandle; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE tokenHandle; + ULONG i; + + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 160, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 200, L"Flags"); + PhSetExtendedListView(lvHandle); + ExtendedListView_SetItemColorFunction(lvHandle, PhpTokenCapabilitiesColorFunction); + + if (NT_SUCCESS(tokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + tokenPageContext->Context + ))) + { + if (NT_SUCCESS(PhQueryTokenVariableSize(tokenHandle, TokenCapabilities, &tokenPageContext->Capabilities))) + { + for (i = 0; i < tokenPageContext->Capabilities->GroupCount; i++) + { + INT lvItemIndex; + PPH_STRING name; + PPH_STRING attributesString; + + name = PhGetSidFullName(tokenPageContext->Capabilities->Groups[i].Sid, TRUE, NULL); + + if (!name) + name = PhSidToStringSid(tokenPageContext->Capabilities->Groups[i].Sid); + + if (name) + { + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, name->Buffer, + &tokenPageContext->Capabilities->Groups[i]); + attributesString = PhGetGroupAttributesString( + tokenPageContext->Capabilities->Groups[i].Attributes); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, attributesString->Buffer); + + PhDereferenceObject(attributesString); + PhDereferenceObject(name); + } + } + + if (ListView_GetItemCount(lvHandle) != 0) + { + ListView_SetColumnWidth(lvHandle, 0, LVSCW_AUTOSIZE); + ExtendedListView_SetColumnWidth(lvHandle, 1, ELVSCW_AUTOSIZE_REMAININGSPACE); + } + + ExtendedListView_SortItems(lvHandle); + } + + NtClose(tokenHandle); + } + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PhFree(tokenPageContext->Capabilities); + tokenPageContext->Capabilities = NULL; + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyBehaviors(lParam, lvHandle, PH_LIST_VIEW_DEFAULT_1_BEHAVIORS); + } + break; + } + + REFLECT_MESSAGE_DLG(hwndDlg, lvHandle, uMsg, wParam, lParam); + + return FALSE; +} + +BOOLEAN NTAPI PhpAttributeTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PATTRIBUTE_TREE_CONTEXT context; + PATTRIBUTE_NODE node; + + context = Context; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + node = (PATTRIBUTE_NODE)getChildren->Node; + + if (!node) + { + getChildren->Children = (PPH_TREENEW_NODE *)context->RootList->Items; + getChildren->NumberOfChildren = context->RootList->Count; + } + else + { + getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; + getChildren->NumberOfChildren = node->Children->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + node = (PATTRIBUTE_NODE)isLeaf->Node; + + isLeaf->IsLeaf = node->Children->Count == 0; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + + node = (PATTRIBUTE_NODE)getCellText->Node; + + if (getCellText->Id == 0) + getCellText->Text = PhGetStringRef(node->Text); + else + return FALSE; + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + { + PPH_STRING text; + + text = PhGetTreeNewText(hwnd, 0); + PhSetClipboardString(hwnd, &text->sr); + PhDereferenceObject(text); + } + break; + } + } + return TRUE; + } + + return FALSE; +} + +PATTRIBUTE_NODE PhpAddAttributeNode( + _In_ PATTRIBUTE_TREE_CONTEXT Context, + _In_opt_ PATTRIBUTE_NODE Parent, + _In_opt_ _Assume_refs_(1) PPH_STRING Text + ) +{ + PATTRIBUTE_NODE node; + + node = PhAllocate(sizeof(ATTRIBUTE_NODE)); + memset(node, 0, sizeof(ATTRIBUTE_NODE)); + PhInitializeTreeNewNode(&node->Node); + + node->Children = PhCreateList(2); + + PhAddItemList(Context->NodeList, node); + + if (Parent) + PhAddItemList(Parent->Children, node); + else + PhAddItemList(Context->RootList, node); + + PhMoveReference(&node->Text, Text); + + return node; +} + +VOID PhpDestroyAttributeNode( + _In_ PATTRIBUTE_NODE Node + ) +{ + PhDereferenceObject(Node->Children); + PhClearReference(&Node->Text); + PhFree(Node); +} + +VOID PhpInitializeAttributeTreeContext( + _Out_ PATTRIBUTE_TREE_CONTEXT Context, + _In_ HWND TreeNewHandle + ) +{ + PH_TREENEW_VIEW_PARTS parts; + + Context->NodeList = PhCreateList(10); + Context->RootList = PhCreateList(10); + + PhSetControlTheme(TreeNewHandle, L"explorer"); + TreeNew_SetCallback(TreeNewHandle, PhpAttributeTreeNewCallback, Context); + TreeNew_GetViewParts(TreeNewHandle, &parts); + PhAddTreeNewColumnEx2(TreeNewHandle, 0, TRUE, L"Attributes", parts.ClientRect.right - parts.VScrollWidth, PH_ALIGN_LEFT, 0, 0, TN_COLUMN_FLAG_NODPISCALEONADD); +} + +VOID PhpDeleteAttributeTreeContext( + _Inout_ PATTRIBUTE_TREE_CONTEXT Context + ) +{ + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + PhpDestroyAttributeNode(Context->NodeList->Items[i]); + + PhDereferenceObject(Context->NodeList); + PhDereferenceObject(Context->RootList); +} + +PWSTR PhGetSecurityAttributeTypeString( + _In_ USHORT Type + ) +{ + // These types are shared between CLAIM_* and TOKEN_* security attributes. + + switch (Type) + { + case TOKEN_SECURITY_ATTRIBUTE_TYPE_INVALID: + return L"Invalid"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64: + return L"Int64"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64: + return L"UInt64"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING: + return L"String"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN: + return L"FQBN"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_SID: + return L"SID"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN: + return L"Boolean"; + case TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING: + return L"Octet string"; + default: + return L"(Unknown)"; + } +} + +PPH_STRING PhGetSecurityAttributeFlagsString( + _In_ ULONG Flags + ) +{ + PH_STRING_BUILDER sb; + + // These flags are shared between CLAIM_* and TOKEN_* security attributes. + + PhInitializeStringBuilder(&sb, 100); + + if (Flags & TOKEN_SECURITY_ATTRIBUTE_MANDATORY) + PhAppendStringBuilder2(&sb, L"Mandatory, "); + if (Flags & TOKEN_SECURITY_ATTRIBUTE_DISABLED) + PhAppendStringBuilder2(&sb, L"Disabled, "); + if (Flags & TOKEN_SECURITY_ATTRIBUTE_DISABLED_BY_DEFAULT) + PhAppendStringBuilder2(&sb, L"Default disabled, "); + if (Flags & TOKEN_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY) + PhAppendStringBuilder2(&sb, L"Use for deny only, "); + if (Flags & TOKEN_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE) + PhAppendStringBuilder2(&sb, L"Case-sensitive, "); + if (Flags & TOKEN_SECURITY_ATTRIBUTE_NON_INHERITABLE) + PhAppendStringBuilder2(&sb, L"Non-inheritable, "); + + if (sb.String->Length != 0) + PhRemoveEndStringBuilder(&sb, 2); + else + PhAppendStringBuilder2(&sb, L"(None)"); + + return PhFinalStringBuilderString(&sb); +} + +PPH_STRING PhFormatClaimSecurityAttributeValue( + _In_ PCLAIM_SECURITY_ATTRIBUTE_V1 Attribute, + _In_ ULONG ValueIndex + ) +{ + PH_FORMAT format; + + switch (Attribute->ValueType) + { + case CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64: + PhInitFormatI64D(&format, Attribute->Values.pInt64[ValueIndex]); + return PhFormat(&format, 1, 0); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64: + PhInitFormatI64U(&format, Attribute->Values.pUint64[ValueIndex]); + return PhFormat(&format, 1, 0); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING: + return PhCreateString(Attribute->Values.ppString[ValueIndex]); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_FQBN: + return PhFormatString(L"Version %I64u: %s", + Attribute->Values.pFqbn[ValueIndex].Version, + Attribute->Values.pFqbn[ValueIndex].Name); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_SID: + { + if (RtlValidSid(Attribute->Values.pOctetString[ValueIndex].pValue)) + { + PPH_STRING name; + + name = PhGetSidFullName(Attribute->Values.pOctetString[ValueIndex].pValue, TRUE, NULL); + + if (name) + return name; + + name = PhSidToStringSid(Attribute->Values.pOctetString[ValueIndex].pValue); + + if (name) + return name; + } + } + return PhCreateString(L"(Invalid SID)"); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN: + return PhCreateString(Attribute->Values.pInt64[ValueIndex] != 0 ? L"True" : L"False"); + case CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING: + return PhCreateString(L"(Octet string)"); + default: + return PhCreateString(L"(Unknown)"); + } +} + +PPH_STRING PhFormatTokenSecurityAttributeValue( + _In_ PTOKEN_SECURITY_ATTRIBUTE_V1 Attribute, + _In_ ULONG ValueIndex + ) +{ + PH_FORMAT format; + + switch (Attribute->ValueType) + { + case TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64: + PhInitFormatI64D(&format, Attribute->Values.pInt64[ValueIndex]); + return PhFormat(&format, 1, 0); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64: + PhInitFormatI64U(&format, Attribute->Values.pUint64[ValueIndex]); + return PhFormat(&format, 1, 0); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING: + return PhCreateStringFromUnicodeString(&Attribute->Values.pString[ValueIndex]); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN: + return PhFormatString(L"Version %I64u: %.*s", + Attribute->Values.pFqbn[ValueIndex].Version, + Attribute->Values.pFqbn[ValueIndex].Name.Length / sizeof(WCHAR), + Attribute->Values.pFqbn[ValueIndex].Name.Buffer); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_SID: + { + if (RtlValidSid(Attribute->Values.pOctetString[ValueIndex].pValue)) + { + PPH_STRING name; + + name = PhGetSidFullName(Attribute->Values.pOctetString[ValueIndex].pValue, TRUE, NULL); + + if (name) + return name; + + name = PhSidToStringSid(Attribute->Values.pOctetString[ValueIndex].pValue); + + if (name) + return name; + } + } + return PhCreateString(L"(Invalid SID)"); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN: + return PhCreateString(Attribute->Values.pInt64[ValueIndex] != 0 ? L"True" : L"False"); + case TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING: + return PhCreateString(L"(Octet string)"); + default: + return PhCreateString(L"(Unknown)"); + } +} + +BOOLEAN PhpAddTokenClaimAttributes( + _In_ PTOKEN_PAGE_CONTEXT TokenPageContext, + _In_ HWND tnHandle, + _In_ BOOLEAN DeviceClaims, + _In_ PATTRIBUTE_NODE Parent + ) +{ + HANDLE tokenHandle; + PCLAIM_SECURITY_ATTRIBUTES_INFORMATION info; + ULONG i; + ULONG j; + + if (!NT_SUCCESS(TokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + TokenPageContext->Context + ))) + return FALSE; + + if (NT_SUCCESS(PhQueryTokenVariableSize(tokenHandle, DeviceClaims ? TokenDeviceClaimAttributes : TokenUserClaimAttributes, &info))) + { + for (i = 0; i < info->AttributeCount; i++) + { + PCLAIM_SECURITY_ATTRIBUTE_V1 attribute = &info->Attribute.pAttributeV1[i]; + PATTRIBUTE_NODE node; + PPH_STRING temp; + + // Attribute + node = PhpAddAttributeNode(&TokenPageContext->ClaimsTreeContext, Parent, PhCreateString(attribute->Name)); + // Type + PhpAddAttributeNode(&TokenPageContext->ClaimsTreeContext, node, + PhFormatString(L"Type: %s", PhGetSecurityAttributeTypeString(attribute->ValueType))); + // Flags + temp = PhGetSecurityAttributeFlagsString(attribute->Flags); + PhpAddAttributeNode(&TokenPageContext->ClaimsTreeContext, node, + PhFormatString(L"Flags: %s", temp->Buffer)); + PhDereferenceObject(temp); + + // Values + for (j = 0; j < attribute->ValueCount; j++) + { + temp = PhFormatClaimSecurityAttributeValue(attribute, j); + PhpAddAttributeNode(&TokenPageContext->ClaimsTreeContext, node, + PhFormatString(L"Value %u: %s", j, temp->Buffer)); + PhDereferenceObject(temp); + } + } + + PhFree(info); + } + + NtClose(tokenHandle); + + TreeNew_NodesStructured(tnHandle); + + return TRUE; +} + +INT_PTR CALLBACK PhpTokenClaimsPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + HWND tnHandle; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (uMsg) + { + case WM_INITDIALOG: + { + PATTRIBUTE_NODE userNode; + PATTRIBUTE_NODE deviceNode; + + PhpInitializeAttributeTreeContext(&tokenPageContext->ClaimsTreeContext, tnHandle); + + TreeNew_SetRedraw(tnHandle, FALSE); + + userNode = PhpAddAttributeNode(&tokenPageContext->ClaimsTreeContext, NULL, PhCreateString(L"User claims")); + PhpAddTokenClaimAttributes(tokenPageContext, tnHandle, FALSE, userNode); + deviceNode = PhpAddAttributeNode(&tokenPageContext->ClaimsTreeContext, NULL, PhCreateString(L"Device claims")); + PhpAddTokenClaimAttributes(tokenPageContext, tnHandle, TRUE, deviceNode); + + if (userNode->Children->Count == 0) + PhpAddAttributeNode(&tokenPageContext->ClaimsTreeContext, userNode, PhCreateString(L"(None)")); + if (deviceNode->Children->Count == 0) + PhpAddAttributeNode(&tokenPageContext->ClaimsTreeContext, deviceNode, PhCreateString(L"(None)")); + + TreeNew_NodesStructured(tnHandle); + TreeNew_SetRedraw(tnHandle, TRUE); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PhpDeleteAttributeTreeContext(&tokenPageContext->ClaimsTreeContext); + } + break; + } + + return FALSE; +} + +BOOLEAN PhpAddTokenAttributes( + _In_ PTOKEN_PAGE_CONTEXT TokenPageContext, + _In_ HWND tnHandle + ) +{ + HANDLE tokenHandle; + PTOKEN_SECURITY_ATTRIBUTES_INFORMATION info; + ULONG i; + ULONG j; + + if (!NT_SUCCESS(TokenPageContext->OpenObject( + &tokenHandle, + TOKEN_QUERY, + TokenPageContext->Context + ))) + return FALSE; + + if (NT_SUCCESS(PhQueryTokenVariableSize(tokenHandle, TokenSecurityAttributes, &info))) + { + for (i = 0; i < info->AttributeCount; i++) + { + PTOKEN_SECURITY_ATTRIBUTE_V1 attribute = &info->Attribute.pAttributeV1[i]; + PATTRIBUTE_NODE node; + PPH_STRING temp; + + // Attribute + node = PhpAddAttributeNode(&TokenPageContext->AuthzTreeContext, NULL, + PhCreateStringFromUnicodeString(&attribute->Name)); + // Type + PhpAddAttributeNode(&TokenPageContext->AuthzTreeContext, node, + PhFormatString(L"Type: %s", PhGetSecurityAttributeTypeString(attribute->ValueType))); + // Flags + temp = PhGetSecurityAttributeFlagsString(attribute->Flags); + PhpAddAttributeNode(&TokenPageContext->AuthzTreeContext, node, + PhFormatString(L"Flags: %s", temp->Buffer)); + PhDereferenceObject(temp); + + // Values + for (j = 0; j < attribute->ValueCount; j++) + { + temp = PhFormatTokenSecurityAttributeValue(attribute, j); + PhpAddAttributeNode(&TokenPageContext->AuthzTreeContext, node, + PhFormatString(L"Value %u: %s", j, temp->Buffer)); + PhDereferenceObject(temp); + } + } + + PhFree(info); + } + + NtClose(tokenHandle); + + TreeNew_NodesStructured(tnHandle); + + return TRUE; +} + +INT_PTR CALLBACK PhpTokenAttributesPageProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PTOKEN_PAGE_CONTEXT tokenPageContext; + HWND tnHandle; + + tokenPageContext = PhpTokenPageHeader(hwndDlg, uMsg, wParam, lParam); + + if (!tokenPageContext) + return FALSE; + + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhpInitializeAttributeTreeContext(&tokenPageContext->AuthzTreeContext, tnHandle); + + TreeNew_SetRedraw(tnHandle, FALSE); + + PhpAddTokenAttributes(tokenPageContext, tnHandle); + + if (tokenPageContext->AuthzTreeContext.RootList->Count == 0) + PhpAddAttributeNode(&tokenPageContext->AuthzTreeContext, NULL, PhCreateString(L"(None)")); + + TreeNew_NodesStructured(tnHandle); + TreeNew_SetRedraw(tnHandle, TRUE); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PhpDeleteAttributeTreeContext(&tokenPageContext->AuthzTreeContext); + } + break; + } + + return FALSE; +} diff --git a/ProcessHacker/update_rev.bat b/ProcessHacker/update_rev.bat new file mode 100644 index 0000000..aa3262a --- /dev/null +++ b/ProcessHacker/update_rev.bat @@ -0,0 +1,30 @@ +@ECHO OFF +SETLOCAL + +SET "UPDATEGITREVISION=..\tools\UpdateGitRevision\UpdateGitRevision\bin\Release\UpdateGitRevision.exe" + +PUSHD %~dp0 + +"%UPDATEGITREVISION%" "include\phapprev_in.h" "include\phapprev.h" +IF %ERRORLEVEL% NEQ 0 GOTO NoUpdateGitRevision + +POPD +ENDLOCAL +EXIT /B + + +:NoUpdateGitRevision +ECHO. & ECHO UpdateGitRevision.exe wasn't found! +ECHO You need to build it. +ECHO I'll use PHAPP_VERSION_REVISION=0 for now. + +ECHO #ifndef PHAPPREV_H> "include\phapprev.h" +ECHO #define PHAPPREV_H>> "include\phapprev.h" +ECHO.>> "include\phapprev.h" +ECHO #define PHAPP_VERSION_REVISION 0 >> "include\phapprev.h" +ECHO.>> "include\phapprev.h" +ECHO #endif // PHAPPREV_H>> "include\phapprev.h" + +POPD +ENDLOCAL +EXIT /B diff --git a/ProcessHacker/version.rc b/ProcessHacker/version.rc new file mode 100644 index 0000000..6caa53e --- /dev/null +++ b/ProcessHacker/version.rc @@ -0,0 +1,35 @@ +#include "winres.h" +#include "include/phappres.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION PHAPP_VERSION_NUMBER + PRODUCTVERSION PHAPP_VERSION_NUMBER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Process Hacker" + VALUE "FileVersion", PHAPP_VERSION_STRING + VALUE "InternalName", "Process Hacker" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "ProcessHacker.exe" + VALUE "ProductName", "Process Hacker" + VALUE "ProductVersion", PHAPP_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END diff --git a/README.md b/README.md new file mode 100644 index 0000000..9bd7593 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +Process Hacker is a powerful free and open source process viewer. + +## Getting started + +Simply run ProcessHacker.exe to start Process Hacker. There are two +versions, 32-bit (x86) and 64-bit (x64). If you are not sure which +version to use, open Control Panel > System and check the "System +type". You cannot run the 32-bit version of Process Hacker on a +64-bit system and expect it to work correctly, unlike other programs. + +## System requirements + +Windows XP SP2 or higher, 32-bit or 64-bit. + +## Settings + +If you are running Process Hacker from a USB drive, you may want to +save Process Hacker's settings there as well. To do this, create a +blank file named "ProcessHacker.exe.settings.xml" in the same +directory as ProcessHacker.exe. You can do this using Windows Explorer: + +1. Make sure "Hide extensions for known file types" is unticked in + Tools > Folder options > View. +2. Right-click in the folder and choose New > Text Document. +3. Rename the file to ProcessHacker.exe.settings.xml (delete the ".txt" + extension). + +## Plugins + +Plugins can be configured from Hacker > Plugins. + +If you experience any crashes involving plugins, make sure they +are up to date. + +The ExtendedTools plugin is only available for Windows Vista and +above. Disk and Network information provided by this plugin is +only available when running Process Hacker with administrative +rights. + +## KProcessHacker + +Process Hacker uses a kernel-mode driver, KProcessHacker, to +assist with certain functionality. This includes: + +* Capturing kernel-mode stack traces +* More efficiently enumerating process handles +* Retrieving names for file handles +* Retrieving names for EtwRegistration objects +* Setting handle attributes + +KProcessHacker is only available on Windows 7 and above. + +Note that by default, KProcessHacker only allows connections from +processes with SeDebugPrivilege. To allow Process Hacker to show details +for all processes when it is not running as administrator: + +1. In Registry Editor, navigate to: + HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\KProcessHacker3 +2. Under this key, create a key named Parameters if it does not exist. +3. Create a DWORD value named SecurityLevel and set it to 2. If you are + not using an official build, you may need to set it to 0 instead. +4. Restart the KProcessHacker3 service (sc stop KProcessHacker3, + sc start KProcessHacker3). diff --git a/build/Installer/Custom_Messages.iss b/build/Installer/Custom_Messages.iss new file mode 100644 index 0000000..10f038b --- /dev/null +++ b/build/Installer/Custom_Messages.iss @@ -0,0 +1,61 @@ +;* Process Hacker - Installer custom messages +;* +;* Copyright (C) 2010-2015 XhmikosR +;* +;* This file is part of Process Hacker. +;* +;* Process Hacker is free software; you can redistribute it and/or modify +;* it under the terms of the GNU General Public License as published by +;* the Free Software Foundation, either version 3 of the License, or +;* (at your option) any later version. +;* +;* Process Hacker is distributed in the hope that it will be useful, +;* but WITHOUT ANY WARRANTY; without even the implied warranty of +;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;* GNU General Public License for more details. +;* +;* You should have received a copy of the GNU General Public License +;* along with Process Hacker. If not, see . + + +[CustomMessages] +;sm=Start Menu, tsk=Task, com=Comment, msg=Message, comp=Component +;English +en.comp_Main_App =Main application +en.comp_PE_Viewer =PE Viewer +en.comp_Plugins =Plugins +en.comp_DotNetTools =.NET Tools +en.comp_ExtendedNotifications =Extended Notifications +en.comp_ExtendedServices =Extended Services +en.comp_ExtendedTools =Extended Tools +en.comp_HardwareDevices =Hardware Devices +en.comp_NetworkTools =Network Tools +en.comp_OnlineChecks =Online Checks +en.comp_SbieSupport =Sandboxie Support +en.comp_ToolStatus =Toolbar and Status Bar +en.comp_Updater =Updater +en.comp_UserNotes =User Notes +en.comp_WindowExplorer =Window Explorer +en.msg_SetupIsRunningWarning =Process Hacker Setup is already running! +en.msg_DeleteLogSettings =Do you also want to delete Process Hacker's settings?%n%nIf you plan to reinstall Process Hacker then you do not have to delete them. +en.msg_ServiceManager =The service manager is not available. +en.tsk_AllUsers =For all users +en.tsk_CreateKPHService =Install KProcessHacker driver and allow unrestricted access (not recommended) +en.tsk_CurrentUser =For the current user only +en.tsk_DeleteKPHService =Delete KProcessHacker driver +en.tsk_Other =Other tasks: +en.tsk_RemoveStartup =Remove Process Hacker from Windows startup +en.tsk_ResetSettings =Reset Process Hacker settings +en.tsk_RestoreTaskmgr =Restore Windows task manager +en.tsk_SetDefaultTaskmgr =Set Process Hacker as the default task manager for Windows +en.tsk_StartupDescr =Start Process Hacker on system startup +en.tsk_StartupDescrMin =Minimized on system tray +en.tsk_Startup =Startup options: +en.tsk_full =Full installation +en.tsk_minimal =Minimal installation +en.tsk_custom =Custom installation +en.run_ViewChangelog =View Changelog +en.run_VisitWebsite =Visit Process Hacker Website +en.sm_Changelog =Changelog +en.sm_com_Changelog =Process Hacker Changelog +en.sm_Help =Help and Support diff --git a/build/Installer/Icons/ProcessHackerLarge.bmp b/build/Installer/Icons/ProcessHackerLarge.bmp new file mode 100644 index 0000000..c54e432 Binary files /dev/null and b/build/Installer/Icons/ProcessHackerLarge.bmp differ diff --git a/build/Installer/Icons/ProcessHackerSmall.bmp b/build/Installer/Icons/ProcessHackerSmall.bmp new file mode 100644 index 0000000..d8fea44 Binary files /dev/null and b/build/Installer/Icons/ProcessHackerSmall.bmp differ diff --git a/build/Installer/Icons/uninstall.ico b/build/Installer/Icons/uninstall.ico new file mode 100644 index 0000000..a4debc8 Binary files /dev/null and b/build/Installer/Icons/uninstall.ico differ diff --git a/build/Installer/Process_Hacker_installer.iss b/build/Installer/Process_Hacker_installer.iss new file mode 100644 index 0000000..692875c --- /dev/null +++ b/build/Installer/Process_Hacker_installer.iss @@ -0,0 +1,423 @@ +;* Process Hacker - Installer script +;* +;* Copyright (C) 2011 wj32 +;* Copyright (C) 2010-2016 XhmikosR +;* +;* This file is part of Process Hacker. +;* +;* Process Hacker is free software; you can redistribute it and/or modify +;* it under the terms of the GNU General Public License as published by +;* the Free Software Foundation, either version 3 of the License, or +;* (at your option) any later version. +;* +;* Process Hacker is distributed in the hope that it will be useful, +;* but WITHOUT ANY WARRANTY; without even the implied warranty of +;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;* GNU General Public License for more details. +;* +;* You should have received a copy of the GNU General Public License +;* along with Process Hacker. If not, see . +; +; +; Requirements: +; *Inno Setup: http://www.jrsoftware.org/isdl.php + + +#if VER < EncodeVer(5,5,8) + #error Update your Inno Setup version (5.5.8 or newer) +#endif + +#include "..\..\ProcessHacker\include\phappres.h" + +; Include the custom messages and services +#include "Custom_Messages.iss" +#include "Services.iss" + +#define installer_build_number "14" +#define copyright "Copyright © 2010-2016, Process Hacker Team. Licensed under the GNU GPL, v3." + +#if defined(TWO_DIGIT_VER) + #define app_version str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + #define app_version_long str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + ".0" + "." + str(PHAPP_VERSION_REVISION) + #define app_version_full str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + " (r" + str(PHAPP_VERSION_REVISION) + ")" +#elif defined(THREE_DIGIT_VER) + #define app_version str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + #define app_version_long str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + "." + str(PHAPP_VERSION_BUILD) + "." + str(PHAPP_VERSION_REVISION) + #define app_version_full str(PHAPP_VERSION_MAJOR) + "." + str(PHAPP_VERSION_MINOR) + "." + str(PHAPP_VERSION_BUILD) + " (r" + str(PHAPP_VERSION_REVISION) + ")" +#endif + +#define installer_build_date GetDateTimeString('mmm, d yyyy', '', '') +#define quick_launch "{userappdata}\Microsoft\Internet Explorer\Quick Launch" + + +[Setup] +AppID=Process_Hacker2 +AppCopyright={#copyright} +AppContact=http://sourceforge.net/projects/processhacker/support +AppName=Process Hacker +AppVerName=Process Hacker {#app_version_full} +AppVersion={#app_version_long} +AppPublisher=wj32 +AppPublisherURL=http://processhacker.sourceforge.net/ +AppSupportURL=http://sourceforge.net/projects/processhacker/support +AppUpdatesURL=http://processhacker.sourceforge.net/ +UninstallDisplayName=Process Hacker {#app_version_full} +DefaultDirName={pf}\Process Hacker 2 +DefaultGroupName=Process Hacker 2 +VersionInfoCompany=wj32 +VersionInfoCopyright={#copyright} +VersionInfoDescription=Process Hacker Setup +VersionInfoProductName=Process Hacker +VersionInfoProductTextVersion={#app_version_full} +VersionInfoProductVersion={#app_version_long} +VersionInfoTextVersion={#app_version_full} +VersionInfoVersion={#app_version_long} +MinVersion=5.01.2600sp2 +LicenseFile=..\..\LICENSE.txt +SetupIconFile=..\..\ProcessHacker\ProcessHacker.ico +UninstallDisplayIcon={app}\ProcessHacker.exe +WizardImageFile=Icons\ProcessHackerLarge.bmp +WizardSmallImageFile=Icons\ProcessHackerSmall.bmp +OutputDir=. +OutputBaseFilename=processhacker-{#app_version}-setup +AllowNoIcons=yes +Compression=lzma2/max +InternalCompressLevel=max +SolidCompression=yes +EnableDirDoesntExistWarning=no +ShowTasksTreeLines=yes +PrivilegesRequired=admin +DisableDirPage=auto +DisableProgramGroupPage=auto +AppMutex=Global\ProcessHacker2Mutant +ArchitecturesInstallIn64BitMode=x64 + + +[Languages] +Name: en; MessagesFile: compiler:Default.isl + + +[Messages] +WelcomeLabel1=[name/ver] +WelcomeLabel2=This will install [name] on your computer.%n%nIt is recommended that you close all other applications before continuing. +BeveledLabel=Process Hacker v{#app_version_full}, Setup v{#installer_build_number} built on {#installer_build_date} +SetupAppTitle=Setup - Process Hacker +SetupWindowTitle=Setup - Process Hacker + + +[Types] +Name: full; Description: {cm:tsk_full} +Name: minimal; Description: {cm:tsk_minimal} +Name: custom; Description: {cm:tsk_custom}; Flags: iscustom + + +[Components] +Name: main; Description: {cm:comp_Main_App}; Types: full minimal custom; Flags: fixed +Name: peview; Description: {cm:comp_PE_Viewer}; Types: full minimal custom; Flags: disablenouninstallwarning +Name: plugins; Description: {cm:comp_Plugins}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\dotnettools; Description: {cm:comp_DotNetTools}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\extendednotifications; Description: {cm:comp_ExtendedNotifications}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\extendedservices; Description: {cm:comp_ExtendedServices}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\extendedtools; Description: {cm:comp_ExtendedTools}; Types: full custom; Flags: disablenouninstallwarning; MinVersion: 6.0 +Name: plugins\hardwaredevices; Description: {cm:comp_HardwareDevices}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\networktools; Description: {cm:comp_NetworkTools}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\onlinechecks; Description: {cm:comp_OnlineChecks}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\sbiesupport; Description: {cm:comp_SbieSupport}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\toolstatus; Description: {cm:comp_ToolStatus}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\updater; Description: {cm:comp_Updater}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\usernotes; Description: {cm:comp_UserNotes}; Types: full custom; Flags: disablenouninstallwarning +Name: plugins\windowexplorer; Description: {cm:comp_WindowExplorer}; Types: full custom; Flags: disablenouninstallwarning + + +[Tasks] +Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons} +Name: desktopicon\user; Description: {cm:tsk_CurrentUser}; GroupDescription: {cm:AdditionalIcons}; Flags: exclusive +Name: desktopicon\common; Description: {cm:tsk_AllUsers}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked exclusive +Name: quicklaunchicon; Description: {cm:CreateQuickLaunchIcon}; GroupDescription: {cm:AdditionalIcons}; OnlyBelowVersion: 6.01; Flags: unchecked + +Name: startup; Description: {cm:tsk_StartupDescr}; GroupDescription: {cm:tsk_Startup}; Check: not StartupCheck(); Flags: unchecked checkablealone +Name: startup\minimized; Description: {cm:tsk_StartupDescrMin}; GroupDescription: {cm:tsk_Startup}; Check: not StartupCheck(); Flags: unchecked +Name: remove_startup; Description: {cm:tsk_RemoveStartup}; GroupDescription: {cm:tsk_Startup}; Check: StartupCheck(); Flags: unchecked + +Name: set_default_taskmgr; Description: {cm:tsk_SetDefaultTaskmgr}; GroupDescription: {cm:tsk_Other}; Check: not PHDefaulTaskmgrCheck(); Flags: checkedonce unchecked +Name: restore_taskmgr; Description: {cm:tsk_RestoreTaskmgr}; GroupDescription: {cm:tsk_Other}; Check: PHDefaulTaskmgrCheck(); Flags: checkedonce unchecked + +Name: reset_settings; Description: {cm:tsk_ResetSettings}; GroupDescription: {cm:tsk_Other}; Check: SettingsExistCheck(); Flags: checkedonce unchecked + +Name: create_KPH_service; Description: {cm:tsk_CreateKPHService}; GroupDescription: {cm:tsk_Other}; Check: not KPHServiceCheck(); Flags: unchecked +Name: delete_KPH_service; Description: {cm:tsk_DeleteKPHService}; GroupDescription: {cm:tsk_Other}; Check: KPHServiceCheck(); Flags: unchecked + + +[Files] +Source: ..\..\CHANGELOG.txt; DestDir: {app}; Flags: ignoreversion +Source: ..\..\COPYRIGHT.txt; DestDir: {app}; Flags: ignoreversion +Source: ..\..\LICENSE.txt; DestDir: {app}; Flags: ignoreversion +Source: ..\..\README.md; DestDir: {app}; DestName: README.txt; Flags: ignoreversion + +Source: ..\..\bin\Release32\ProcessHacker.exe; DestDir: {app}; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release32\ProcessHacker.sig; DestDir: {app}; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\ProcessHacker.exe; DestDir: {app}; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release64\ProcessHacker.sig; DestDir: {app}; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\ProcessHacker.exe; DestDir: {app}\x86; Flags: ignoreversion; Check: Is64BitInstallMode() + +Source: ..\..\KProcessHacker\bin-signed\i386\kprocesshacker.sys; DestDir: {app}; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\KProcessHacker\bin-signed\amd64\kprocesshacker.sys; DestDir: {app}; Flags: ignoreversion; Check: Is64BitInstallMode() + +Source: ..\..\bin\Release32\peview.exe; DestDir: {app}; Components: peview; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\peview.exe; DestDir: {app}; Components: peview; Flags: ignoreversion; Check: Is64BitInstallMode() + +Source: ..\..\bin\Release32\plugins\DotNetTools.dll; DestDir: {app}\plugins; Components: plugins\dotnettools; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\DotNetTools.dll; DestDir: {app}\plugins; Components: plugins\dotnettools; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\DotNetTools.dll; DestDir: {app}\x86\plugins; Components: plugins\dotnettools; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\ExtendedNotifications.dll; DestDir: {app}\plugins; Components: plugins\extendednotifications; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\ExtendedNotifications.dll; DestDir: {app}\plugins; Components: plugins\extendednotifications; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\ExtendedServices.dll; DestDir: {app}\plugins; Components: plugins\extendedservices; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\ExtendedServices.dll; DestDir: {app}\plugins; Components: plugins\extendedservices; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\ExtendedTools.dll; DestDir: {app}\plugins; Components: plugins\extendedtools; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\ExtendedTools.dll; DestDir: {app}\plugins; Components: plugins\extendedtools; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\HardwareDevices.dll; DestDir: {app}\plugins; Components: plugins\hardwaredevices; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\HardwareDevices.dll; DestDir: {app}\plugins; Components: plugins\hardwaredevices; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\NetworkTools.dll; DestDir: {app}\plugins; Components: plugins\networktools; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\NetworkTools.dll; DestDir: {app}\plugins; Components: plugins\networktools; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\OnlineChecks.dll; DestDir: {app}\plugins; Components: plugins\onlinechecks; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\OnlineChecks.dll; DestDir: {app}\plugins; Components: plugins\onlinechecks; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\SbieSupport.dll; DestDir: {app}\plugins; Components: plugins\sbiesupport; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\SbieSupport.dll; DestDir: {app}\plugins; Components: plugins\sbiesupport; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\ToolStatus.dll; DestDir: {app}\plugins; Components: plugins\toolstatus; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\ToolStatus.dll; DestDir: {app}\plugins; Components: plugins\toolstatus; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\Updater.dll; DestDir: {app}\plugins; Components: plugins\updater; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\Updater.dll; DestDir: {app}\plugins; Components: plugins\updater; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\UserNotes.dll; DestDir: {app}\plugins; Components: plugins\usernotes; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\UserNotes.dll; DestDir: {app}\plugins; Components: plugins\usernotes; Flags: ignoreversion; Check: Is64BitInstallMode() +Source: ..\..\bin\Release32\plugins\WindowExplorer.dll; DestDir: {app}\plugins; Components: plugins\windowexplorer; Flags: ignoreversion; Check: not Is64BitInstallMode() +Source: ..\..\bin\Release64\plugins\WindowExplorer.dll; DestDir: {app}\plugins; Components: plugins\windowexplorer; Flags: ignoreversion; Check: Is64BitInstallMode() + +Source: Icons\uninstall.ico; DestDir: {app}; Flags: ignoreversion + + +[Icons] +Name: {group}\PE Viewer; Filename: {app}\peview.exe; WorkingDir: {app}; Comment: PE Viewer; IconFilename: {app}\peview.exe; IconIndex: 0; Components: peview; Flags: excludefromshowinnewinstall +Name: {group}\Process Hacker 2; Filename: {app}\ProcessHacker.exe; WorkingDir: {app}; Comment: Process Hacker {#app_version_full}; IconFilename: {app}\ProcessHacker.exe; IconIndex: 0; AppUserModelID: "wj32.ProcessHacker2" +Name: {group}\{cm:sm_Help}\{cm:sm_Changelog}; Filename: {app}\CHANGELOG.txt; WorkingDir: {app}; Comment: {cm:sm_com_Changelog} +Name: {group}\{cm:sm_Help}\{cm:ProgramOnTheWeb,Process Hacker 2}; Filename: http://processhacker.sourceforge.net/; Comment: {cm:ProgramOnTheWeb,Process Hacker 2} +Name: {group}\{cm:UninstallProgram,Process Hacker 2}; Filename: {uninstallexe}; WorkingDir: {app}; Comment: {cm:UninstallProgram,Process Hacker 2}; IconFilename: {app}\uninstall.ico + +Name: {commondesktop}\Process Hacker 2; Filename: {app}\ProcessHacker.exe; WorkingDir: {app}; Comment: Process Hacker {#app_version_full}; IconFilename: {app}\ProcessHacker.exe; IconIndex: 0; Tasks: desktopicon\common +Name: {userdesktop}\Process Hacker 2; Filename: {app}\ProcessHacker.exe; WorkingDir: {app}; Comment: Process Hacker {#app_version_full}; IconFilename: {app}\ProcessHacker.exe; IconIndex: 0; Tasks: desktopicon\user; AppUserModelID: "wj32.ProcessHacker2" +Name: {#quick_launch}\Process Hacker 2; Filename: {app}\ProcessHacker.exe; WorkingDir: {app}; Comment: Process Hacker {#app_version_full}; IconFilename: {app}\ProcessHacker.exe; IconIndex: 0; Tasks: quicklaunchicon + + +[InstallDelete] +Type: files; Name: {userdesktop}\Process Hacker 2.lnk; Check: not IsTaskSelected('desktopicon\user') and IsUpgrade() +Type: files; Name: {commondesktop}\Process Hacker 2.lnk; Check: not IsTaskSelected('desktopicon\common') and IsUpgrade() +Type: files; Name: {#quick_launch}\Process Hacker 2.lnk; Check: not IsTaskSelected('quicklaunchicon') and IsUpgrade(); OnlyBelowVersion: 6.01 +Type: files; Name: {group}\Help and Support\Process Hacker Help.lnk; Check: IsUpgrade() + +Type: files; Name: {userappdata}\Process Hacker 2\settings.xml; Tasks: reset_settings +Type: dirifempty; Name: {userappdata}\Process Hacker; Tasks: reset_settings + +Type: files; Name: {app}\Help.htm; Check: IsUpgrade() +Type: files; Name: {app}\peview.exe; Check: not IsComponentSelected('peview') and IsUpgrade() +Type: files; Name: {group}\PE Viewer.lnk; Check: not IsComponentSelected('peview') and IsUpgrade() +Type: files; Name: {app}\plugins\DotNetTools.dll; Check: not IsComponentSelected('plugins\dotnettools') and IsUpgrade() +Type: files; Name: {app}\plugins\ExtendedNotifications.dll; Check: not IsComponentSelected('plugins\extendednotifications') and IsUpgrade() +Type: files; Name: {app}\plugins\ExtendedServices.dll; Check: not IsComponentSelected('plugins\extendedservices') and IsUpgrade() +Type: files; Name: {app}\plugins\ExtendedTools.dll; Check: not IsComponentSelected('plugins\extendedtools') and IsUpgrade() +Type: files; Name: {app}\plugins\HardwareDevices.dll; Check: not IsComponentSelected('plugins\hardwaredevices') and IsUpgrade() +Type: files; Name: {app}\plugins\NetAdapters.dll; Check: IsUpgrade() +Type: files; Name: {app}\plugins\NetworkTools.dll; Check: not IsComponentSelected('plugins\networktools') and IsUpgrade() +Type: files; Name: {app}\plugins\OnlineChecks.dll; Check: not IsComponentSelected('plugins\onlinechecks') and IsUpgrade() +Type: files; Name: {app}\plugins\SbieSupport.dll; Check: not IsComponentSelected('plugins\sbiesupport') and IsUpgrade() +Type: files; Name: {app}\plugins\ToolStatus.dll; Check: not IsComponentSelected('plugins\toolstatus') and IsUpgrade() +Type: files; Name: {app}\plugins\Updater.dll; Check: not IsComponentSelected('plugins\updater') and IsUpgrade() +Type: files; Name: {app}\plugins\UserNotes.dll; Check: not IsComponentSelected('plugins\usernotes') and IsUpgrade() +Type: files; Name: {app}\plugins\WindowExplorer.dll; Check: not IsComponentSelected('plugins\windowexplorer') and IsUpgrade() +Type: dirifempty; Name: {app}\plugins + + +[Run] +Filename: {app}\ProcessHacker.exe; Description: {cm:LaunchProgram,Process Hacker 2}; Flags: nowait postinstall skipifsilent +Filename: {app}\CHANGELOG.txt; Description: {cm:run_ViewChangelog}; Flags: nowait postinstall skipifsilent unchecked shellexec +Filename: http://processhacker.sourceforge.net/; Description: {cm:run_VisitWebsite}; Flags: nowait postinstall skipifsilent unchecked shellexec + + +[Code] +const + installer_mutex = 'process_hacker2_setup_mutex'; + IFEO = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\taskmgr.exe'; + HKCURUN = 'Software\Microsoft\Windows\CurrentVersion\Run'; + + +function IsUpgrade(): Boolean; +var + sPrevPath: String; +begin + sPrevPath := WizardForm.PrevAppDir; + Result := (sPrevPath <> ''); +end; + + +function ShouldSkipPage(PageID: Integer): Boolean; +begin + // Hide the License and the Ready to install page if it's an upgrade + if IsUpgrade() and (PageID = wpLicense) or (PageID = wpReady) then + Result := True; +end; + + +// Check if KProcessHacker is installed as a service +function KPHServiceCheck(): Boolean; +var + dwStart: DWORD; +begin + if RegQueryDWordValue(HKLM, 'SYSTEM\CurrentControlSet\Services\KProcessHacker2', 'Start', dwStart) then begin + if dwStart = 1 then + Result := True; + end else + Result := False; +end; + + +// Check if Process Hacker is set as the default Task Manager for Windows +function PHDefaulTaskmgrCheck(): Boolean; +var + sDebugger: String; +begin + if RegQueryStringValue(HKLM, IFEO, 'Debugger', sDebugger) then begin + if sDebugger = (ExpandConstant('"{app}\ProcessHacker.exe"')) then + Result := True; + end else + Result := False; +end; + + +// Check if Process Hacker's settings exist +function SettingsExistCheck(): Boolean; +begin + if FileExists(ExpandConstant('{userappdata}\Process Hacker 2\settings.xml')) then + Result := True + else + Result := False; +end; + + +// Check if Process Hacker is configured to run on startup in order to control +// startup choice from within the installer +function StartupCheck(): Boolean; +var + svalue: String; +begin + if RegQueryStringValue(HKCU, HKCURUN, 'Process Hacker 2', svalue) then begin + if (svalue = (ExpandConstant('"{app}\ProcessHacker.exe"'))) or (svalue = (ExpandConstant('"{app}\ProcessHacker.exe" -hide'))) then + Result := True; + end else + Result := False; +end; + + +procedure CurPageChanged(CurPageID: Integer); +begin + if IsUpgrade() and (CurPageID = wpSelectTasks) then + WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall); +end; + + +procedure CurStepChanged(CurStep: TSetupStep); +var + iResultCode: Integer; +begin + if CurStep = ssInstall then begin + if IsServiceRunning('KProcessHacker2') then + StopService('KProcessHacker2'); + if IsTaskSelected('delete_KPH_service') then + RemoveService('KProcessHacker2'); + end; + + if CurStep = ssPostInstall then begin + if (KPHServiceCheck() and not IsTaskSelected('delete_KPH_service') or (IsTaskSelected('create_KPH_service'))) then begin + StopService('KProcessHacker2'); + RemoveService('KProcessHacker2'); + + if not Exec(ExpandConstant('{app}\ProcessHacker.exe'), '-installkph -s', '', SW_HIDE, ewWaitUntilTerminated, iResultCode) then begin + // handle failure if necessary; iResultCode contains the error code + end; + end; + + if IsTaskSelected('set_default_taskmgr') then + RegWriteStringValue(HKLM, IFEO, 'Debugger', ExpandConstant('"{app}\ProcessHacker.exe"')); + if IsTaskSelected('restore_taskmgr') then begin + RegDeleteValue(HKLM, IFEO, 'Debugger'); + RegDeleteKeyIfEmpty(HKLM, IFEO); + end; + + if IsTaskSelected('startup') then + RegWriteStringValue(HKCU, HKCURUN, 'Process Hacker 2', ExpandConstant('"{app}\ProcessHacker.exe"')); + if IsTaskSelected('startup\minimized') then + RegWriteStringValue(HKCU, HKCURUN, 'Process Hacker 2', ExpandConstant('"{app}\ProcessHacker.exe" -hide')); + if IsTaskSelected('remove_startup') then + RegDeleteValue(HKCU, HKCURUN, 'Process Hacker 2'); + + end; +end; + + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usUninstall then begin + StopService('KProcessHacker2'); + RemoveService('KProcessHacker2'); + + // When uninstalling ask user to delete Process Hacker's settings + // based on whether the settings file exists only + if SettingsExistCheck() then begin + if SuppressibleMsgBox(CustomMessage('msg_DeleteLogSettings'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES then + DeleteFile(ExpandConstant('{userappdata}\Process Hacker 2\settings.xml')); + end; + + if PHDefaulTaskmgrCheck() then + RegDeleteValue(HKLM, IFEO, 'Debugger'); + RegDeleteKeyIfEmpty(HKLM, IFEO); + if StartupCheck() then + RegDeleteValue(HKCU, HKCURUN, 'Process Hacker 2'); + + RemoveDir(ExpandConstant('{userappdata}\Process Hacker 2')); + RemoveDir(ExpandConstant('{app}\plugins')); + RemoveDir(ExpandConstant('{app}')); + + end; +end; + + +procedure InitializeWizard(); +begin + WizardForm.SelectTasksLabel.Hide; + WizardForm.TasksList.Top := 0; + WizardForm.TasksList.Height := PageFromID(wpSelectTasks).SurfaceHeight; +end; + + +function InitializeSetup(): Boolean; +begin + // Create a mutex for the installer and if it's already running then expose a message and stop installation + if CheckForMutexes(installer_mutex) and not WizardSilent() then begin + SuppressibleMsgBox(CustomMessage('msg_SetupIsRunningWarning'), mbError, MB_OK, MB_OK); + Result := False; + end + else begin + Result := True; + CreateMutex(installer_mutex); + end; +end; + + +function InitializeUninstall(): Boolean; +begin + if CheckForMutexes(installer_mutex) then begin + SuppressibleMsgBox(CustomMessage('msg_SetupIsRunningWarning'), mbError, MB_OK, MB_OK); + Result := False; + end + else begin + Result := True; + CreateMutex(installer_mutex); + end; +end; diff --git a/build/Installer/Services.iss b/build/Installer/Services.iss new file mode 100644 index 0000000..3e70048 --- /dev/null +++ b/build/Installer/Services.iss @@ -0,0 +1,200 @@ +;* Original source: http://goo.gl/PTi56 +;* +;* Process Hacker - Various services functions +;* +;* This file is part of Process Hacker. +;* +;* Process Hacker is free software; you can redistribute it and/or modify +;* it under the terms of the GNU General Public License as published by +;* the Free Software Foundation, either version 3 of the License, or +;* (at your option) any later version. +;* +;* Process Hacker is distributed in the hope that it will be useful, +;* but WITHOUT ANY WARRANTY; without even the implied warranty of +;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;* GNU General Public License for more details. +;* +;* You should have received a copy of the GNU General Public License +;* along with Process Hacker. If not, see . + + +[Code] +// Various service related functions + +type + SERVICE_STATUS = record + dwServiceType : cardinal; + dwCurrentState : cardinal; + dwControlsAccepted : cardinal; + dwWin32ExitCode : cardinal; + dwServiceSpecificExitCode : cardinal; + dwCheckPoint : cardinal; + dwWaitHint : cardinal; + end; + HANDLE = cardinal; + +const + SERVICE_QUERY_CONFIG = $1; + SERVICE_CHANGE_CONFIG = $2; + SERVICE_QUERY_STATUS = $4; + SERVICE_START = $10; + SERVICE_STOP = $20; + SERVICE_ALL_ACCESS = $f01ff; + SC_MANAGER_ALL_ACCESS = $f003f; + SERVICE_KERNEL_DRIVER = $1; + SERVICE_WIN32_OWN_PROCESS = $10; + SERVICE_WIN32_SHARE_PROCESS = $20; + SERVICE_WIN32 = $30; + SERVICE_INTERACTIVE_PROCESS = $100; + SERVICE_BOOT_START = $0; + SERVICE_SYSTEM_START = $1; + SERVICE_AUTO_START = $2; + SERVICE_DEMAND_START = $3; + SERVICE_DISABLED = $4; + SERVICE_DELETE = $10000; + SERVICE_CONTROL_STOP = $1; + SERVICE_CONTROL_PAUSE = $2; + SERVICE_CONTROL_CONTINUE = $3; + SERVICE_CONTROL_INTERROGATE = $4; + SERVICE_STOPPED = $1; + SERVICE_START_PENDING = $2; + SERVICE_STOP_PENDING = $3; + SERVICE_RUNNING = $4; + SERVICE_CONTINUE_PENDING = $5; + SERVICE_PAUSE_PENDING = $6; + SERVICE_PAUSED = $7; + +// ####################################################################################### +// nt based service utilities +// ####################################################################################### +function OpenSCManager(lpMachineName, lpDatabaseName: AnsiString; dwDesiredAccess: cardinal): HANDLE; +external 'OpenSCManagerA@advapi32.dll stdcall'; + +function OpenService(hSCManager: HANDLE; lpServiceName: AnsiString; dwDesiredAccess: cardinal): HANDLE; +external 'OpenServiceA@advapi32.dll stdcall'; + +function CloseServiceHandle(hSCObject: HANDLE): Boolean; +external 'CloseServiceHandle@advapi32.dll stdcall'; + +function CreateService(hSCManager: HANDLE; lpServiceName, lpDisplayName: AnsiString; dwDesiredAccess, dwServiceType, dwStartType, dwErrorControl: cardinal; lpBinaryPathName, lpLoadOrderGroup: AnsiString; lpdwTagId: cardinal; lpDependencies, lpServiceStartName, lpPassword: AnsiString): cardinal; +external 'CreateServiceA@advapi32.dll stdcall'; + +function DeleteService(hService: HANDLE): Boolean; +external 'DeleteService@advapi32.dll stdcall'; + +function StartNTService(hService: HANDLE; dwNumServiceArgs: cardinal; lpServiceArgVectors: cardinal): Boolean; +external 'StartServiceA@advapi32.dll stdcall'; + +function ControlService(hService: HANDLE; dwControl: cardinal; var ServiceStatus: SERVICE_STATUS): Boolean; +external 'ControlService@advapi32.dll stdcall'; + +function QueryServiceStatus(hService: HANDLE; var ServiceStatus: SERVICE_STATUS): Boolean; +external 'QueryServiceStatus@advapi32.dll stdcall'; + +function QueryServiceStatusEx(hService: HANDLE; ServiceStatus: SERVICE_STATUS): Boolean; +external 'QueryServiceStatus@advapi32.dll stdcall'; + + +function OpenServiceManager(): HANDLE; +begin + Result := OpenSCManager('', 'ServicesActive', SC_MANAGER_ALL_ACCESS); + if Result = 0 then + SuppressibleMsgBox(CustomMessage('msg_ServiceManager'), mbError, MB_OK, MB_OK); +end; + + +function InstallService(FileName, ServiceName, DisplayName, Description: AnsiString; ServiceType, StartType: cardinal): Boolean; +var + hSCM : HANDLE; + hService : HANDLE; +begin + hSCM := OpenServiceManager(); + Result := False; + if hSCM <> 0 then begin + hService := CreateService(hSCM, ServiceName, DisplayName, SERVICE_ALL_ACCESS, ServiceType, StartType, 0, FileName, '', 0, '', '', ''); + if hService <> 0 then begin + Result := True; + // Win2K & WinXP supports aditional description text for services + if Description <> '' then + RegWriteStringValue(HKLM, 'System\CurrentControlSet\Services\' + ServiceName, 'Description', Description); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + + +function RemoveService(ServiceName: String): Boolean; +var + hSCM : HANDLE; + hService : HANDLE; +begin + hSCM := OpenServiceManager(); + Result := False; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_DELETE); + if hService <> 0 then begin + Result := DeleteService(hService); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + + +function StartService(ServiceName: String): Boolean; +var + hSCM : HANDLE; + hService : HANDLE; +begin + hSCM := OpenServiceManager(); + Result := False; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_START); + if hService <> 0 then begin + Result := StartNTService(hService, 0, 0); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + + +function StopService(ServiceName: String): Boolean; +var + hSCM : HANDLE; + hService : HANDLE; + Status : SERVICE_STATUS; +begin + hSCM := OpenServiceManager(); + Result := False; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_STOP); + if hService <> 0 then begin + Result := ControlService(hService, SERVICE_CONTROL_STOP, Status); + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; + + +function IsServiceRunning(ServiceName: String): Boolean; +var + hSCM : HANDLE; + hService : HANDLE; + Status : SERVICE_STATUS; +begin + hSCM := OpenServiceManager(); + Result := False; + if hSCM <> 0 then begin + hService := OpenService(hSCM, ServiceName, SERVICE_QUERY_STATUS); + if hService <> 0 then begin + if QueryServiceStatus(hService, Status) then begin + Result := (Status.dwCurrentState = SERVICE_RUNNING); + end; + CloseServiceHandle(hService); + end; + CloseServiceHandle(hSCM); + end; +end; diff --git a/build/coverity/coverity-build.bat b/build/coverity/coverity-build.bat new file mode 100644 index 0000000..7064476 --- /dev/null +++ b/build/coverity/coverity-build.bat @@ -0,0 +1,14 @@ +@ECHO OFF + +SET MSBUILD_SWITCHES=/nologo /consoleloggerparameters:Verbosity=minimal /maxcpucount^ + /nodeReuse:true /target:Rebuild /property:Configuration="Release" + +MSBuild "..\..\ProcessHacker.sln" %MSBUILD_SWITCHES%;Platform=Win32 +MSBuild "..\..\ProcessHacker.sln" %MSBUILD_SWITCHES%;Platform=x64 + +PUSHD ..\sdk +CALL "makesdk.cmd" +POPD + +MSBuild "..\..\plugins\Plugins.sln" %MSBUILD_SWITCHES%;Platform=Win32 +MSBuild "..\..\plugins\Plugins.sln" %MSBUILD_SWITCHES%;Platform=x64 diff --git a/build/coverity/coverity.bat b/build/coverity/coverity.bat new file mode 100644 index 0000000..90a8502 --- /dev/null +++ b/build/coverity/coverity.bat @@ -0,0 +1,39 @@ +@ECHO OFF + +SETLOCAL + +PUSHD %~dp0 + +SET COVDIR=H:\progs\thirdparty\cov-analysis-win64 + +CALL "%VS140COMNTOOLS%\vsvars32.bat" + +"%COVDIR%\bin\cov-build.exe" --dir cov-int coverity-build.bat + +IF EXIST "ProcessHacker.lzma" DEL "ProcessHacker.lzma" +IF EXIST "ProcessHacker.tar" DEL "ProcessHacker.tar" +IF EXIST "ProcessHacker.tgz" DEL "ProcessHacker.tgz" + + +:tar +tar --version 1>&2 2>NUL || (ECHO. & ECHO ERROR: tar not found & GOTO SevenZip) +tar caf "ProcessHacker.lzma" "cov-int" +GOTO End + +:SevenZip +IF NOT EXIST "%PROGRAMFILES%\7-Zip\7z.exe" ( + ECHO. + ECHO ERROR: "%PROGRAMFILES%\7-Zip\7z.exe" not found + GOTO End +) +"%PROGRAMFILES%\7-Zip\7z.exe" a -ttar "ProcessHacker.tar" "cov-int" +"%PROGRAMFILES%\7-Zip\7z.exe" a -tgzip "ProcessHacker.tgz" "ProcessHacker.tar" +IF EXIST "ProcessHacker.tar" DEL "ProcessHacker.tar" + + +:End +POPD +ECHO. & ECHO Press any key to close this window... +PAUSE >NUL +ENDLOCAL +EXIT /B diff --git a/build/internal/DigiCert High Assurance EV Root CA.crt b/build/internal/DigiCert High Assurance EV Root CA.crt new file mode 100644 index 0000000..c42e0fc --- /dev/null +++ b/build/internal/DigiCert High Assurance EV Root CA.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFOzCCAyOgAwIBAgIKYSBNtAAAAAAAJzANBgkqhkiG9w0BAQUFADB/MQswCQYD +VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe +MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNyb3Nv +ZnQgQ29kZSBWZXJpZmljYXRpb24gUm9vdDAeFw0xMTA0MTUxOTQ1MzNaFw0yMTA0 +MTUxOTU1MzNaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx +GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhp +Z2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD1ZQ0Z6IKHLBfaaZAscS3 +so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80ltcZF+Y7arpl/DpIT4T2JR +vvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46OFBbdzEbjbPHJEWap6xt +ABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZdHFMsfpjNGgYWpGhz0DQE +E1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdmt4i3ePLKCqg4qwpkwr9m +XZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjgcswgcgwEQYDVR0gBAowCDAG +BgRVHSAAMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSx +PsNpA/i/RwHUmCYaCALvY2QrwzAfBgNVHSMEGDAWgBRi+wohW39DbhHaCVRQa/XS +lnHxnjBVBgNVHR8ETjBMMEqgSKBGhkRodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +cGtpL2NybC9wcm9kdWN0cy9NaWNyb3NvZnRDb2RlVmVyaWZSb290LmNybDANBgkq +hkiG9w0BAQUFAAOCAgEAIIzBWe1vnGstwUo+dR1FTEFQHL2A6tmwkosGKhM/Uxae +VjlqimO2eCR59X24uUehCpbC9su9omafBuGs0nkJDv083KwCDHCvPxvseH7U60sF +YCbZc2GRIe2waGPglxKrb6AS7dmf0tonPLPkVvnR1IEPcb1CfKaJ3M3VvZWiq/GT +EX3orDEpqF1mcEGd/HXJ1bMaOSrQhQVQi6yRysSTy3GlnaSUb1gM+m4gxAgxtYWd +foH50j3KWxiFbAqG7CIJG6V0NE9/KLyVSqsdtpiwXQmkd3Z+76eOXYT2GCTL0W2m +w6GcwhB1gP+dMv3mz0M6gvfOj+FyKptit1/tlRo5XC+UbUi3AV8zL7vcLXM0iQRC +ChyLefmj+hfv+qEaEN/gssGV61wMBZc7NT4YiE3bbL8kiY3Ivdifezk6JKDV39Hz +ShqX9qZveh+wkKmzrAE5kdNht2TxPlc4A6/OetK1kPWu3DmZ1bY8l+2myxbHfWsq +TJCU5kxU/R7NIOzOaJyHWOlhYL7rDsnVGX2f6Xi9DqwhdQePqW7gjGoqa5zj52W8 +vC08bdwE3GdFNjKvBIG8qABuYUyVxVzUjo6fL8EydL29EWUDB83vt14CV9qG1Boo +NK+ISbLPpd2CVm9oqhTiWVT+/+ru7+qScCJggeMlI8CfzA9JsjWqWMM6w9kWlBA= +-----END CERTIFICATE----- diff --git a/build/internal/DigiCertHighAssuranceCodeSigningCA-1.crt b/build/internal/DigiCertHighAssuranceCodeSigningCA-1.crt new file mode 100644 index 0000000..cddf4d0 Binary files /dev/null and b/build/internal/DigiCertHighAssuranceCodeSigningCA-1.crt differ diff --git a/build/internal/dobuildrelease.cmd b/build/internal/dobuildrelease.cmd new file mode 100644 index 0000000..8768318 --- /dev/null +++ b/build/internal/dobuildrelease.cmd @@ -0,0 +1,34 @@ +@echo off + +rem The first parameter specifies the ProcessHacker2 base directory. +rem The second parameter specifies the output directory. +rem Variables: +rem INNOBIN - Specify the path to Inno Setup. +rem e.g. C:\Program Files\Inno Setup 5 +rem SVNBIN - Specify the path to the SVN client. +rem SEVENZIPBIN - Specify the path to 7-Zip. +rem e.g. C:\Program Files\7-Zip +rem SIGN - Specify 1 to sign executable files. +rem SIGN_TIMESTAMP - Specify 1 to timestamp executable files. +rem MAJORVERSION - Specify the major version of the Process Hacker release being built. +rem e.g. 2 +rem MINORVERSION - Specify the minor version of the Process Hacker release being built. +rem e.g. 8 + +rem Build the main projects + +set PHBASE=%1 + +devenv %1\ProcessHacker.sln /build "Release|Win32" +devenv %1\ProcessHacker.sln /build "Release|x64" +call %1\build\internal\wait.cmd 2 + +pushd %1\build\sdk\ +call makesdk.cmd +popd + +devenv %1\plugins\Plugins.sln /build "Release|Win32" +devenv %1\plugins\Plugins.sln /build "Release|x64" +call %1\build\internal\wait.cmd 2 + +call %1\build\internal\dorelease.cmd %1 %2 diff --git a/build/internal/dorelease.cmd b/build/internal/dorelease.cmd new file mode 100644 index 0000000..00687c3 --- /dev/null +++ b/build/internal/dorelease.cmd @@ -0,0 +1,127 @@ +@echo off + +if "%1" == "" goto :notset +if "%2" == "" goto :notset + +if exist %2\processhacker-*-*.* del %2\processhacker-*-*.* + +rem Source distribution + +if exist "%GITBIN%\git.exe". ( + "%GITBIN%\git.exe" --git-dir=%1\.git --work-tree=%1 archive --format zip --output %2\processhacker-%MAJORVERSION%.%MINORVERSION%-src.zip master + if exist "%SEVENZIPBIN%\7z.exe" ( + if exist %2\ProcessHacker2 rmdir /S /Q %2\ProcessHacker2 + "%SEVENZIPBIN%\7z.exe" x %2\processhacker-%MAJORVERSION%.%MINORVERSION%-src.zip -o%2\ProcessHacker2 + del %2\processhacker-%MAJORVERSION%.%MINORVERSION%-src.zip + echo #define PHAPP_VERSION_REVISION 0 > %2\ProcessHacker2\ProcessHacker\include\phapprev.h + "%SEVENZIPBIN%\7z.exe" a -mx9 %2\processhacker-%MAJORVERSION%.%MINORVERSION%-src.zip %2\ProcessHacker2\* + ) +) + +rem SDK distribution + +if exist "%SEVENZIPBIN%\7z.exe" "%SEVENZIPBIN%\7z.exe" a -mx9 %2\processhacker-%MAJORVERSION%.%MINORVERSION%-sdk.zip %1\sdk\* + +rem Binary distribution + +if exist %2\bin rmdir /S /Q %2\bin +mkdir %2\bin + +for %%a in ( + CHANGELOG.txt + COPYRIGHT.txt + LICENSE.txt +) do copy %1\%%a %2\bin\%%a +copy %1\README.md %2\bin\README.txt + +if "%SIGN%" == "1" ( + call %1\build\internal\sign.cmd %1\bin\Release32\ProcessHacker.exe + call %1\build\internal\sign.cmd %1\bin\Release32\peview.exe + call %1\build\internal\sign.cmd %1\bin\Release64\ProcessHacker.exe + call %1\build\internal\sign.cmd %1\bin\Release64\peview.exe +) + +if exist "%KPH_PRIVATE_KEY%". ( + %1\tools\CustomSignTool\bin\Release32\CustomSignTool.exe sign -k "%KPH_PRIVATE_KEY%" -s %1\bin\Release32\ProcessHacker.sig %1\bin\Release32\ProcessHacker.exe + %1\tools\CustomSignTool\bin\Release32\CustomSignTool.exe sign -k "%KPH_PRIVATE_KEY%" -s %1\bin\Release64\ProcessHacker.sig %1\bin\Release64\ProcessHacker.exe +) + +mkdir %2\bin\x86 +copy %1\bin\Release32\ProcessHacker.exe %2\bin\x86\ +copy %1\bin\Release32\ProcessHacker.sig %2\bin\x86\ +copy %1\KProcessHacker\bin-signed\i386\kprocesshacker.sys %2\bin\x86\ +copy %1\bin\Release32\peview.exe %2\bin\x86\ + +mkdir %2\bin\x64 +copy %1\bin\Release64\ProcessHacker.exe %2\bin\x64\ +copy %1\bin\Release64\ProcessHacker.sig %2\bin\x64\ +copy %1\KProcessHacker\bin-signed\amd64\kprocesshacker.sys %2\bin\x64\ +copy %1\bin\Release64\peview.exe %2\bin\x64\ + +mkdir %2\bin\x86\plugins +for %%a in ( + DotNetTools + ExtendedNotifications + ExtendedServices + ExtendedTools + HardwareDevices + NetworkTools + OnlineChecks + SbieSupport + ToolStatus + Updater + UserNotes + WindowExplorer +) do ( + if "%SIGN%" == "1" ( + call %1\build\internal\sign.cmd %1\bin\Release32\plugins\%%a.dll sha2only + ) + copy %1\bin\Release32\plugins\%%a.dll %2\bin\x86\plugins\%%a.dll +) + +mkdir %2\bin\x64\plugins +for %%a in ( + DotNetTools + ExtendedNotifications + ExtendedServices + ExtendedTools + HardwareDevices + NetworkTools + OnlineChecks + SbieSupport + ToolStatus + Updater + UserNotes + WindowExplorer +) do ( + if "%SIGN%" == "1" ( + call %1\build\internal\sign.cmd %1\bin\Release64\plugins\%%a.dll sha2only + ) + copy %1\bin\Release64\plugins\%%a.dll %2\bin\x64\plugins\%%a.dll +) + +if exist "%SEVENZIPBIN%\7z.exe" "%SEVENZIPBIN%\7z.exe" a -mx9 %2\processhacker-%MAJORVERSION%.%MINORVERSION%-bin.zip %2\bin\* + +rem Installer distribution + +if exist "%INNOBIN%\iscc.exe". ( + pushd %1\build\Installer\ + del *.exe + "%INNOBIN%\iscc.exe" Process_Hacker_installer.iss + popd +) + +if exist %1\build\Installer\processhacker-%MAJORVERSION%.%MINORVERSION%-setup.exe ( + copy %1\build\Installer\processhacker-%MAJORVERSION%.%MINORVERSION%-setup.exe %2\ + if "%SIGN%" == "1" ( + call %1\build\internal\sign.cmd %2\processhacker-%MAJORVERSION%.%MINORVERSION%-setup.exe + ) +) + +goto :end + +:notset +echo Parameters not set. +pause + +:end diff --git a/build/internal/readme.txt b/build/internal/readme.txt new file mode 100644 index 0000000..96b4071 --- /dev/null +++ b/build/internal/readme.txt @@ -0,0 +1,3 @@ +I use these scripts on my own machine. + +wj32. \ No newline at end of file diff --git a/build/internal/sign.cmd b/build/internal/sign.cmd new file mode 100644 index 0000000..00cba93 --- /dev/null +++ b/build/internal/sign.cmd @@ -0,0 +1,22 @@ +@echo off + +if "%1" == "" goto :notset + +set additional= +if "%2" == "kmcs" set additional=/ac "%PHBASE%\build\internal\DigiCert High Assurance EV Root CA.crt" + +set timestamp= +if "%SIGN_TIMESTAMP%" == "1" set timestamp=/t http://timestamp.digicert.com +set timestamp_rfc= +if "%SIGN_TIMESTAMP%" == "1" set timestamp_rfc=/tr http://timestamp.digicert.com /td sha256 + +if not "%2" == "sha2only" signtool sign %timestamp% /i "DigiCert High Assurance Code Signing CA-1" %additional% %1 +signtool sign /as /fd sha256 %timestamp_rfc% /i "DigiCert SHA2 High Assurance Code Signing CA" %additional% %1 + +goto :end + +:notset +echo Parameters not set. +pause + +:end diff --git a/build/internal/wait.cmd b/build/internal/wait.cmd new file mode 100644 index 0000000..352ad0f --- /dev/null +++ b/build/internal/wait.cmd @@ -0,0 +1 @@ +ping -n %1 localhost > nul \ No newline at end of file diff --git a/phlib/apiimport.c b/phlib/apiimport.c new file mode 100644 index 0000000..bcf06a4 --- /dev/null +++ b/phlib/apiimport.c @@ -0,0 +1,68 @@ +/* + * Process Hacker - + * procedure import module + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +PVOID PhpImportProcedure( + _Inout_ PVOID *Cache, + _Inout_ PBOOLEAN CacheValid, + _In_ PWSTR ModuleName, + _In_ PSTR ProcedureName + ) +{ + HMODULE module; + PVOID procedure; + + if (*CacheValid) + return *Cache; + + module = GetModuleHandle(ModuleName); + + if (!module) + return NULL; + + procedure = GetProcAddress(module, ProcedureName); + *Cache = procedure; + MemoryBarrier(); + *CacheValid = TRUE; + + return procedure; +} + +#define PH_DEFINE_IMPORT(Module, Name) \ +_##Name Name##_Import(VOID) \ +{ \ + static PVOID cache = NULL; \ + static BOOLEAN cacheValid = FALSE; \ +\ + return (_##Name)PhpImportProcedure(&cache, &cacheValid, Module, #Name); \ +} + +PH_DEFINE_IMPORT(L"comctl32.dll", TaskDialogIndirect); +PH_DEFINE_IMPORT(L"ntdll.dll", NtQueryInformationEnlistment); +PH_DEFINE_IMPORT(L"ntdll.dll", NtQueryInformationResourceManager); +PH_DEFINE_IMPORT(L"ntdll.dll", NtQueryInformationTransaction); +PH_DEFINE_IMPORT(L"ntdll.dll", NtQueryInformationTransactionManager); +PH_DEFINE_IMPORT(L"shell32.dll", SHCreateShellItem); +PH_DEFINE_IMPORT(L"shell32.dll", SHOpenFolderAndSelectItems); +PH_DEFINE_IMPORT(L"shell32.dll", SHParseDisplayName); diff --git a/phlib/avltree.c b/phlib/avltree.c new file mode 100644 index 0000000..7bb8e02 --- /dev/null +++ b/phlib/avltree.c @@ -0,0 +1,1081 @@ +/* + * Process Hacker - + * AVL tree + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +/** + * Initializes an AVL tree. + * + * \param Tree The tree. + * \param CompareFunction A function used to compare tree elements. + */ +VOID PhInitializeAvlTree( + _Out_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_TREE_COMPARE_FUNCTION CompareFunction + ) +{ + Tree->Root.Parent = NULL; + Tree->Root.Left = NULL; + Tree->Root.Right = NULL; + Tree->Root.Balance = 0; + Tree->Count = 0; + + Tree->CompareFunction = CompareFunction; +} + +/** + * Finds an element in an AVL tree. + * + * \param Tree The tree. + * \param Element The element to find. + * \param Result The result of the search. + */ +FORCEINLINE PPH_AVL_LINKS PhpFindElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element, + _Out_ PLONG Result + ) +{ + PPH_AVL_LINKS links; + LONG result; + + links = PhRootElementAvlTree(Tree); + + if (!links) + { + *Result = 1; + return &Tree->Root; + } + + while (TRUE) + { + result = Tree->CompareFunction(Element, links); + + if (result == 0) + { + *Result = 0; + return links; + } + else if (result < 0) + { + if (links->Left) + { + links = links->Left; + } + else + { + *Result = -1; + return links; + } + } + else + { + if (links->Right) + { + links = links->Right; + } + else + { + *Result = 1; + return links; + } + } + } +} + +FORCEINLINE VOID PhpRotateLeftAvlLinks( + _Inout_ PPH_AVL_LINKS *Root + ) +{ + PPH_AVL_LINKS P; + PPH_AVL_LINKS Q; + + // P + // | | + // A Q + // | | + // B C + // + // becomes + // + // Q + // | | + // P C + // | | + // A B + // + // P and Q must exist. + // B may not exist. + // A and C are not affected. + + P = *Root; + Q = P->Right; + + // The new root is Q + + *Root = Q; + Q->Parent = P->Parent; + + // P.Right = Q.Left (transfer B) + + P->Right = Q->Left; + + if (P->Right) + P->Right->Parent = P; + + // Q.Left = P + + Q->Left = P; + P->Parent = Q; +} + +FORCEINLINE VOID PhpRotateLeftTwiceAvlLinks( + _Inout_ PPH_AVL_LINKS *Root + ) +{ + PPH_AVL_LINKS P; + PPH_AVL_LINKS Q; + PPH_AVL_LINKS R; + + // P + // | | + // A Q + // | | + // R D + // | | + // B C + // + // becomes + // + // R + // | | + // P Q + // | | | | + // A B C D + // + // P, Q, and R must exist. + // B and C may not exist. + // A and D are not affected. + + // PhpRotateRightAvlLinks(&(*Root)->Right); + // PhpRotateLeftAvlLinks(Root); + + // P is the current root + // Q is P.Right + // R is Q.Left (P.Right.Left) + + P = *Root; + Q = P->Right; + R = Q->Left; + + // The new root is R + + *Root = R; + R->Parent = P->Parent; + + // Q.Left = R.Right (transfer C) + + Q->Left = R->Right; + + if (Q->Left) + Q->Left->Parent = Q; + + // R.Right = Q + + R->Right = Q; + Q->Parent = R; + + // P.Right = R.Left (transfer B) + + P->Right = R->Left; + + if (P->Right) + P->Right->Parent = P; + + // R.Left = P + + R->Left = P; + P->Parent = R; +} + +FORCEINLINE VOID PhpRotateRightAvlLinks( + _Inout_ PPH_AVL_LINKS *Root + ) +{ + PPH_AVL_LINKS Q; + PPH_AVL_LINKS P; + + // Q + // | | + // P C + // | | + // A B + // + // becomes + // + // P + // | | + // A Q + // | | + // B C + // + // Q and P must exist. + // B may not exist. + // A and C are not affected. + + Q = *Root; + P = Q->Left; + + // The new root is P + + *Root = P; + P->Parent = Q->Parent; + + // Q.Left = P.Right (transfer B) + + Q->Left = P->Right; + + if (Q->Left) + Q->Left->Parent = Q; + + // P.Right = Q + + P->Right = Q; + Q->Parent = P; +} + +FORCEINLINE VOID PhpRotateRightTwiceAvlLinks( + _Inout_ PPH_AVL_LINKS *Root + ) +{ + PPH_AVL_LINKS P; + PPH_AVL_LINKS Q; + PPH_AVL_LINKS R; + + // P + // | | + // Q D + // | | + // A R + // | | + // B C + // + // becomes + // + // R + // | | + // Q P + // | | | | + // A B C D + // + // P, Q, and R must exist. + // B and C may not exist. + // A and D are not affected. + + // PhpRotateLeftAvlLinks(&(*Root)->Left); + // PhpRotateRightAvlLinks(Root); + + // P is the current root + // Q is P.Left + // R is Q.Right (P.Left.Right) + + P = *Root; + Q = P->Left; + R = Q->Right; + + // The new root is R + + *Root = R; + R->Parent = P->Parent; + + // Q.Right = R.Left (transfer B) + + Q->Right = R->Left; + + if (Q->Right) + Q->Right->Parent = Q; + + // R.Left = Q + + R->Left = Q; + Q->Parent = R; + + // P.Left = R.Right (transfer C) + + P->Left = R->Right; + + if (P->Left) + P->Left->Parent = P; + + // R.Right = P + + R->Right = P; + P->Parent = R; +} + +ULONG PhpRebalanceAvlLinks( + _Inout_ PPH_AVL_LINKS *Root + ) +{ + PPH_AVL_LINKS P; + PPH_AVL_LINKS Q; + PPH_AVL_LINKS R; + + P = *Root; + + if (P->Balance == -1) + { + Q = P->Left; + + if (Q->Balance == -1) + { + // Left-left + + PhpRotateRightAvlLinks(Root); + + P->Balance = 0; + Q->Balance = 0; + + return 1; + } + else if (Q->Balance == 1) + { + // Left-right + + R = Q->Right; + + PhpRotateRightTwiceAvlLinks(Root); + + if (R->Balance == -1) + { + P->Balance = 1; + Q->Balance = 0; + } + else if (R->Balance == 1) + { + P->Balance = 0; + Q->Balance = -1; + } + else + { + P->Balance = 0; + Q->Balance = 0; + } + + R->Balance = 0; + + return 2; + } + else + { + // Special (only occurs when removing) + + // D + // | | + // B E + // | | + // A C + // + // Removing E results in: + // + // D + // | + // B + // | | + // A C + // + // which is unbalanced. Rotating right at B results in: + // + // B + // | | + // A D + // | + // C + // + // The same applies for the mirror case. + + PhpRotateRightAvlLinks(Root); + + Q->Balance = 1; + + return 3; + } + } + else + { + Q = P->Right; + + if (Q->Balance == 1) + { + // Right-right + + PhpRotateLeftAvlLinks(Root); + + P->Balance = 0; + Q->Balance = 0; + + return 1; + } + else if (Q->Balance == -1) + { + // Right-left + + R = Q->Left; + + PhpRotateLeftTwiceAvlLinks(Root); + + if (R->Balance == -1) + { + P->Balance = 0; + Q->Balance = 1; + } + else if (R->Balance == 1) + { + P->Balance = -1; + Q->Balance = 0; + } + else + { + P->Balance = 0; + Q->Balance = 0; + } + + R->Balance = 0; + + return 2; + } + else + { + // Special (only occurs when removing) + + PhpRotateLeftAvlLinks(Root); + + Q->Balance = -1; + + return 3; + } + } +} + +/** + * Adds an element to an AVL tree. + * + * \param Tree The tree. + * \param Element The element to add. + * + * \return NULL if the element was added, or an existing element. + */ +PPH_AVL_LINKS PhAddElementAvlTree( + _Inout_ PPH_AVL_TREE Tree, + _Out_ PPH_AVL_LINKS Element + ) +{ + LONG result; + PPH_AVL_LINKS P; + PPH_AVL_LINKS root; + LONG balance; + + P = PhpFindElementAvlTree(Tree, Element, &result); + + if (result < 0) + P->Left = Element; + else if (result > 0) + P->Right = Element; + else + return P; + + Element->Parent = P; + Element->Left = NULL; + Element->Right = NULL; + Element->Balance = 0; + + // Balance the tree. + + P = Element; + root = PhRootElementAvlTree(Tree); + + while (P != root) + { + // In this implementation, the balance factor is the right height minus left height. + + if (P->Parent->Left == P) + balance = -1; + else + balance = 1; + + P = P->Parent; + + if (P->Balance == 0) + { + // The balance becomes -1 or 1. Rotations are not needed + // yet, but we should keep tracing upwards. + + P->Balance = balance; + } + else if (P->Balance != balance) + { + // The balance is opposite the new balance, so it now + // becomes 0. + + P->Balance = 0; + + break; + } + else + { + PPH_AVL_LINKS *ref; + + // The balance is the same as the new balance, meaning + // it now becomes -2 or 2. Rotations are needed. + + if (P->Parent->Left == P) + ref = &P->Parent->Left; + else + ref = &P->Parent->Right; + + PhpRebalanceAvlLinks(ref); + + break; + } + } + + Tree->Count++; + + return NULL; +} + +/** + * Removes an element from an AVL tree. + * + * \param Tree The tree. + * \param Element An element already present in the tree. + */ +VOID PhRemoveElementAvlTree( + _Inout_ PPH_AVL_TREE Tree, + _Inout_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS newElement; + PPH_AVL_LINKS *replace; + PPH_AVL_LINKS P; + PPH_AVL_LINKS root; + LONG balance; + + if (!Element->Left || !Element->Right) + { + newElement = Element; + } + else if (Element->Balance >= 0) // Pick the side depending on the balance to minimize rebalances + { + newElement = Element->Right; + + while (newElement->Left) + newElement = newElement->Left; + } + else + { + newElement = Element->Left; + + while (newElement->Right) + newElement = newElement->Right; + } + + if (newElement->Parent->Left == newElement) + { + replace = &newElement->Parent->Left; + balance = -1; + } + else + { + replace = &newElement->Parent->Right; + balance = 1; + } + + if (!newElement->Right) + { + *replace = newElement->Left; + + if (newElement->Left) + newElement->Left->Parent = newElement->Parent; + } + else + { + *replace = newElement->Right; + newElement->Right->Parent = newElement->Parent; // we know Right exists + } + + P = newElement->Parent; + root = &Tree->Root; + + while (P != root) + { + if (P->Balance == balance) + { + // The balance is cancelled by the remove operation and becomes 0. + // Rotations are not needed yet, but we should keep tracing upwards. + + P->Balance = 0; + } + else if (P->Balance == 0) + { + // The balance is 0, so it now becomes -1 or 1. + + P->Balance = -balance; + + break; + } + else + { + PPH_AVL_LINKS *ref; + + // The balance is the same as the new balance, meaning + // it now becomes -2 or 2. Rotations are needed. + + if (P->Parent->Left == P) + ref = &P->Parent->Left; + else + ref = &P->Parent->Right; + + // We can stop tracing if we have a special case rotation. + if (PhpRebalanceAvlLinks(ref) == 3) + break; + + P = P->Parent; + } + + if (P->Parent->Left == P) + balance = -1; + else + balance = 1; + + P = P->Parent; + } + + if (newElement != Element) + { + // Replace the subject with the new subject. + + *newElement = *Element; + + if (Element->Parent->Left == Element) + newElement->Parent->Left = newElement; + else + newElement->Parent->Right = newElement; + + if (newElement->Left) + newElement->Left->Parent = newElement; + if (newElement->Right) + newElement->Right->Parent = newElement; + } + + Tree->Count--; +} + +/** + * Finds an element in an AVL tree. + * + * \param Tree The tree. + * \param Element An element to find. + * + * \return The element, or NULL if it could not be found. + */ +PPH_AVL_LINKS PhFindElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + LONG result; + + links = PhpFindElementAvlTree(Tree, Element, &result); + + if (result == 0) + return links; + else + return NULL; +} + +/** + * Finds the first element in an AVL tree that is greater than or equal to the specified element. + * + * \param Tree The tree. + * \param Element The element to find. + * + * \return The bound element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhLowerBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + PPH_AVL_LINKS closest; + LONG result; + + links = PhRootElementAvlTree(Tree); + closest = NULL; + + while (links) + { + result = Tree->CompareFunction(Element, links); + + if (result > 0) + { + links = links->Right; + } + else + { + closest = links; + links = links->Left; + } + } + + return closest; +} + +/** + * Finds the first element in an AVL tree that is greater than the specified element. + * + * \param Tree The tree. + * \param Element The element to find. + * + * \return The bound element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhUpperBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + PPH_AVL_LINKS closest; + LONG result; + + links = PhRootElementAvlTree(Tree); + closest = NULL; + + while (links) + { + result = Tree->CompareFunction(Element, links); + + if (result >= 0) + { + links = links->Right; + } + else + { + closest = links; + links = links->Left; + } + } + + return closest; +} + +/** + * Finds the last element in an AVL tree that is less than the specified element. + * + * \param Tree The tree. + * \param Element The element to find. + * + * \return The bound element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhLowerDualBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + PPH_AVL_LINKS closest; + LONG result; + + links = PhRootElementAvlTree(Tree); + closest = NULL; + + while (links) + { + result = Tree->CompareFunction(Element, links); + + if (result > 0) + { + closest = links; + links = links->Right; + } + else + { + links = links->Left; + } + } + + return closest; +} + +/** + * Finds the last element in an AVL tree that is less than or equal to the specified element. + * + * \param Tree The tree. + * \param Element The element to find. + * + * \return The bound element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhUpperDualBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + PPH_AVL_LINKS closest; + LONG result; + + links = PhRootElementAvlTree(Tree); + closest = NULL; + + while (links) + { + result = Tree->CompareFunction(Element, links); + + if (result >= 0) + { + closest = links; + links = links->Right; + } + else + { + links = links->Left; + } + } + + return closest; +} + +/** + * Finds the smallest element in an AVL tree. + * + * \param Tree The tree. + * + * \return An element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhMinimumElementAvlTree( + _In_ PPH_AVL_TREE Tree + ) +{ + PPH_AVL_LINKS links; + + links = PhRootElementAvlTree(Tree); + + if (!links) + return NULL; + + while (links->Left) + links = links->Left; + + return links; +} + +/** + * Finds the biggest element in an AVL tree. + * + * \param Tree The tree. + * + * \return An element, or NULL if the tree is empty. + */ +PPH_AVL_LINKS PhMaximumElementAvlTree( + _In_ PPH_AVL_TREE Tree + ) +{ + PPH_AVL_LINKS links; + + links = PhRootElementAvlTree(Tree); + + if (!links) + return NULL; + + while (links->Right) + links = links->Right; + + return links; +} + +/** + * Finds the next element in an AVL tree. + * + * \param Element The element. + * + * \return The next element, or NULL if there are no more elements. + */ +PPH_AVL_LINKS PhSuccessorElementAvlTree( + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + + if (Element->Right) + { + Element = Element->Right; + + while (Element->Left) + Element = Element->Left; + + return Element; + } + else + { + // Trace back to the next vertical level. Note + // that this code does in fact return NULL when there + // are no more elements because of the way the root + // element is constructed. + + links = Element->Parent; + + while (links && links->Right == Element) + { + Element = links; + links = links->Parent; + } + + return links; + } +} + +/** + * Finds the previous element in an AVL tree. + * + * \param Element The element. + * + * \return The previous element, or NULL if there are no more elements. + */ +PPH_AVL_LINKS PhPredecessorElementAvlTree( + _In_ PPH_AVL_LINKS Element + ) +{ + PPH_AVL_LINKS links; + + if (Element->Left) + { + Element = Element->Left; + + while (Element->Right) + Element = Element->Right; + + return Element; + } + else + { + links = Element->Parent; + + while (links && links->Left == Element) + { + Element = links; + links = links->Parent; + } + + if (links) + { + // We need an additional check because the tree root is + // stored in Root.Right, not Left. + if (!links->Parent) + return NULL; // reached Root, so no more elements + } + + return links; + } +} + +/** + * Enumerates the elements in an AVL tree. + * + * \param Tree The tree. + * \param Order The enumeration order. + * \param Callback The callback function. + * \param Context A user-defined value to pass to the callback function. + */ +VOID PhEnumAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PH_TREE_ENUMERATION_ORDER Order, + _In_ PPH_ENUM_AVL_TREE_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + // The maximum height of an AVL tree is around 1.44 * log2(n). + // The maximum number of elements in this implementation is 2^32, so the maximum height is + // around 46.08. + PPH_AVL_LINKS stackBase[47]; + PPH_AVL_LINKS *stack; + PPH_AVL_LINKS links; + + stack = stackBase; + + switch (Order) + { + case TreeEnumerateInOrder: + links = PhRootElementAvlTree(Tree); + + while (links) + { + *stack++ = links; + links = links->Left; + } + + while (stack != stackBase) + { + links = *--stack; + + if (!Callback(Tree, links, Context)) + break; + + links = links->Right; + + while (links) + { + *stack++ = links; + links = links->Left; + } + } + + break; + case TreeEnumerateInReverseOrder: + links = PhRootElementAvlTree(Tree); + + while (links) + { + *stack++ = links; + links = links->Right; + } + + while (stack != stackBase) + { + links = *--stack; + + if (!Callback(Tree, links, Context)) + break; + + links = links->Left; + + while (links) + { + *stack++ = links; + links = links->Right; + } + } + + break; + } +} diff --git a/phlib/basesup.c b/phlib/basesup.c new file mode 100644 index 0000000..1c2238d --- /dev/null +++ b/phlib/basesup.c @@ -0,0 +1,5983 @@ +/* + * Process Hacker - + * base support functions + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains basic low-level code as well as general algorithms and data structures. + * + * Memory allocation. PhAllocate is a wrapper around RtlAllocateHeap, and always allocates from the + * phlib heap. PhAllocatePage is a wrapper around NtAllocateVirtualMemory and allocates pages. + * + * Null-terminated strings. The Ph*StringZ functions manipulate null-terminated strings. The copying + * functions provide a simple way to copy strings which may not be null-terminated, but have a + * specified limit. + * + * String. The design of the string object was chosen for maximum compatibility. As such each string + * buffer must be null-terminated, and each object contains an embedded PH_STRINGREF structure. Note + * that efficient sub-string creation (no copying, only references the parent string object) could + * not be implemented due to the mandatory null-termination. String objects must be regarded as + * immutable (for thread-safety reasons) unless the object has just been created and no references + * have been shared. + * + * String builder. This is a set of functions which allow for efficient modification of strings. For + * performance reasons, these functions modify string objects directly, even though they are + * normally immutable. + * + * List. A simple PVOID list that resizes itself when needed. + * + * Pointer list. Similar to the normal list object, but uses a free list in order to support + * constant time insertion and deletion. In order for the free list to work, normal entries have + * their lowest bit clear while free entries have their lowest bit set, with the index of the next + * free entry in the upper bits. + * + * Hashtable. A hashtable with power-of-two bucket sizes and with all entries stored in a single + * array. This improves locality but may be inefficient when resizing the hashtable. It is a good + * idea to store pointers to objects as entries, as opposed to the objects themselves. + * + * Simple hashtable. A wrapper around the normal hashtable, with PVOID keys and PVOID values. + * + * Free list. A thread-safe memory allocation method where freed blocks are stored in a S-list, and + * allocations are made from this list whenever possible. + * + * Callback. A thread-safe notification mechanism where clients can register callback functions + * which are then invoked by other code. + */ + +#include +#include +#include +#include + +#define PH_VECTOR_LEVEL_NONE 0 +#define PH_VECTOR_LEVEL_SSE2 1 +#define PH_VECTOR_LEVEL_AVX 2 + +typedef struct _PHP_BASE_THREAD_CONTEXT +{ + PUSER_THREAD_START_ROUTINE StartAddress; + PVOID Parameter; +} PHP_BASE_THREAD_CONTEXT, *PPHP_BASE_THREAD_CONTEXT; + +VOID NTAPI PhpListDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpPointerListDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpQueueDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID NTAPI PhpHashtableDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +// Types + +PPH_OBJECT_TYPE PhStringType; +PPH_OBJECT_TYPE PhBytesType; +PPH_OBJECT_TYPE PhListType; +PPH_OBJECT_TYPE PhPointerListType; +PPH_OBJECT_TYPE PhHashtableType; + +// Misc. + +static BOOLEAN PhpVectorLevel = PH_VECTOR_LEVEL_NONE; +static PPH_STRING PhSharedEmptyString = NULL; + +// Threads + +static PH_FREE_LIST PhpBaseThreadContextFreeList; +#ifdef DEBUG +ULONG PhDbgThreadDbgTlsIndex; +LIST_ENTRY PhDbgThreadListHead; +PH_QUEUED_LOCK PhDbgThreadListLock = PH_QUEUED_LOCK_INIT; +#endif + +// Data + +static ULONG PhpPrimeNumbers[] = +{ + 0x3, 0x7, 0xb, 0x11, 0x17, 0x1d, 0x25, 0x2f, 0x3b, 0x47, 0x59, 0x6b, 0x83, + 0xa3, 0xc5, 0xef, 0x125, 0x161, 0x1af, 0x209, 0x277, 0x2f9, 0x397, 0x44f, + 0x52f, 0x63d, 0x78b, 0x91d, 0xaf1, 0xd2b, 0xfd1, 0x12fd, 0x16cf, 0x1b65, + 0x20e3, 0x2777, 0x2f6f, 0x38ff, 0x446f, 0x521f, 0x628d, 0x7655, 0x8e01, + 0xaa6b, 0xcc89, 0xf583, 0x126a7, 0x1619b, 0x1a857, 0x1fd3b, 0x26315, 0x2dd67, + 0x3701b, 0x42023, 0x4f361, 0x5f0ed, 0x72125, 0x88e31, 0xa443b, 0xc51eb, + 0xec8c1, 0x11bdbf, 0x154a3f, 0x198c4f, 0x1ea867, 0x24ca19, 0x2c25c1, 0x34fa1b, + 0x3f928f, 0x4c4987, 0x5b8b6f, 0x6dda89 +}; + +/** + * Initializes the base support module. + */ +BOOLEAN PhBaseInitialization( + VOID + ) +{ + PH_OBJECT_TYPE_PARAMETERS parameters; + + // The following relies on the (technically undefined) value of XState being zero before Windows 7 SP1. + // NOTE: This is unused for now. + /*if (USER_SHARED_DATA->XState.EnabledFeatures & XSTATE_MASK_AVX) + PhpVectorLevel = PH_VECTOR_LEVEL_AVX; + else*/ if (USER_SHARED_DATA->ProcessorFeatures[PF_XMMI64_INSTRUCTIONS_AVAILABLE]) + PhpVectorLevel = PH_VECTOR_LEVEL_SSE2; + + PhStringType = PhCreateObjectType(L"String", 0, NULL); + PhBytesType = PhCreateObjectType(L"Bytes", 0, NULL); + + parameters.FreeListSize = sizeof(PH_LIST); + parameters.FreeListCount = 128; + + PhListType = PhCreateObjectTypeEx(L"List", PH_OBJECT_TYPE_USE_FREE_LIST, PhpListDeleteProcedure, ¶meters); + PhPointerListType = PhCreateObjectType(L"PointerList", 0, PhpPointerListDeleteProcedure); + + parameters.FreeListSize = sizeof(PH_HASHTABLE); + parameters.FreeListCount = 64; + + PhHashtableType = PhCreateObjectTypeEx(L"Hashtable", PH_OBJECT_TYPE_USE_FREE_LIST, PhpHashtableDeleteProcedure, ¶meters); + + PhInitializeFreeList(&PhpBaseThreadContextFreeList, sizeof(PHP_BASE_THREAD_CONTEXT), 16); + +#ifdef DEBUG + PhDbgThreadDbgTlsIndex = TlsAlloc(); + InitializeListHead(&PhDbgThreadListHead); +#endif + + return TRUE; +} + +NTSTATUS PhpBaseThreadStart( + _In_ PVOID Parameter + ) +{ + NTSTATUS status; + HRESULT result; + PHP_BASE_THREAD_CONTEXT context; +#ifdef DEBUG + PHP_BASE_THREAD_DBG dbg; +#endif + + context = *(PPHP_BASE_THREAD_CONTEXT)Parameter; + PhFreeToFreeList(&PhpBaseThreadContextFreeList, Parameter); + +#ifdef DEBUG + dbg.ClientId = NtCurrentTeb()->ClientId; + + dbg.StartAddress = context.StartAddress; + dbg.Parameter = context.Parameter; + dbg.CurrentAutoPool = NULL; + + PhAcquireQueuedLockExclusive(&PhDbgThreadListLock); + InsertTailList(&PhDbgThreadListHead, &dbg.ListEntry); + PhReleaseQueuedLockExclusive(&PhDbgThreadListLock); + + TlsSetValue(PhDbgThreadDbgTlsIndex, &dbg); +#endif + + // Initialization code + + result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + // Call the user-supplied function. + status = context.StartAddress(context.Parameter); + + // De-initialization code + + if (result == S_OK || result == S_FALSE) + CoUninitialize(); + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgThreadListLock); + RemoveEntryList(&dbg.ListEntry); + PhReleaseQueuedLockExclusive(&PhDbgThreadListLock); +#endif + + return status; +} + +/** + * Creates a thread. + * + * \param StackSize The initial stack size of the thread. + * \param StartAddress The function to execute in the thread. + * \param Parameter A user-defined value to pass to the function. + */ +HANDLE PhCreateThread( + _In_opt_ SIZE_T StackSize, + _In_ PUSER_THREAD_START_ROUTINE StartAddress, + _In_opt_ PVOID Parameter + ) +{ + HANDLE threadHandle; + PPHP_BASE_THREAD_CONTEXT context; + + context = PhAllocateFromFreeList(&PhpBaseThreadContextFreeList); + context->StartAddress = StartAddress; + context->Parameter = Parameter; + + threadHandle = CreateThread( + NULL, + StackSize, + PhpBaseThreadStart, + context, + 0, + NULL + ); + + if (threadHandle) + { + PHLIB_INC_STATISTIC(BaseThreadsCreated); + return threadHandle; + } + else + { + PHLIB_INC_STATISTIC(BaseThreadsCreateFailed); + PhFreeToFreeList(&PhpBaseThreadContextFreeList, context); + return NULL; + } +} + +/** + * Gets the current system time (UTC). + * + * \remarks Use this function instead of NtQuerySystemTime() because no system calls are involved. + */ +VOID PhQuerySystemTime( + _Out_ PLARGE_INTEGER SystemTime + ) +{ + do + { + SystemTime->HighPart = USER_SHARED_DATA->SystemTime.High1Time; + SystemTime->LowPart = USER_SHARED_DATA->SystemTime.LowPart; + } while (SystemTime->HighPart != USER_SHARED_DATA->SystemTime.High2Time); +} + +/** + * Gets the offset of the current time zone from UTC. + */ +VOID PhQueryTimeZoneBias( + _Out_ PLARGE_INTEGER TimeZoneBias + ) +{ + do + { + TimeZoneBias->HighPart = USER_SHARED_DATA->TimeZoneBias.High1Time; + TimeZoneBias->LowPart = USER_SHARED_DATA->TimeZoneBias.LowPart; + } while (TimeZoneBias->HighPart != USER_SHARED_DATA->TimeZoneBias.High2Time); +} + +/** + * Converts system time to local time. + * + * \param SystemTime A UTC time value. + * \param LocalTime A variable which receives the local time value. This may be the same variable as + * \a SystemTime. + * + * \remarks Use this function instead of RtlSystemTimeToLocalTime() because no system calls are + * involved. + */ +VOID PhSystemTimeToLocalTime( + _In_ PLARGE_INTEGER SystemTime, + _Out_ PLARGE_INTEGER LocalTime + ) +{ + LARGE_INTEGER timeZoneBias; + + PhQueryTimeZoneBias(&timeZoneBias); + LocalTime->QuadPart = SystemTime->QuadPart - timeZoneBias.QuadPart; +} + +/** + * Converts local time to system time. + * + * \param LocalTime A local time value. + * \param SystemTime A variable which receives the UTC time value. This may be the same variable as + * \a LocalTime. + * + * \remarks Use this function instead of RtlLocalTimeToSystemTime() because no system calls are + * involved. + */ +VOID PhLocalTimeToSystemTime( + _In_ PLARGE_INTEGER LocalTime, + _Out_ PLARGE_INTEGER SystemTime + ) +{ + LARGE_INTEGER timeZoneBias; + + PhQueryTimeZoneBias(&timeZoneBias); + SystemTime->QuadPart = LocalTime->QuadPart + timeZoneBias.QuadPart; +} + +/** + * Allocates a block of memory. + * + * \param Size The number of bytes to allocate. + * + * \return A pointer to the allocated block of memory. + * + * \remarks If the function fails to allocate the block of memory, it raises an exception. The block + * is guaranteed to be aligned at MEMORY_ALLOCATION_ALIGNMENT bytes. + */ +_May_raise_ +_Check_return_ +_Ret_notnull_ +_Post_writable_byte_size_(Size) +PVOID PhAllocate( + _In_ SIZE_T Size + ) +{ + return RtlAllocateHeap(PhHeapHandle, HEAP_GENERATE_EXCEPTIONS, Size); +} + +/** + * Allocates a block of memory. + * + * \param Size The number of bytes to allocate. + * + * \return A pointer to the allocated block of memory, or NULL if the block could not be allocated. + */ +PVOID PhAllocateSafe( + _In_ SIZE_T Size + ) +{ + return RtlAllocateHeap(PhHeapHandle, 0, Size); +} + +/** + * Allocates a block of memory. + * + * \param Size The number of bytes to allocate. + * \param Flags Flags controlling the allocation. + * + * \return A pointer to the allocated block of memory, or NULL if the block could not be allocated. + */ +PVOID PhAllocateExSafe( + _In_ SIZE_T Size, + _In_ ULONG Flags + ) +{ + return RtlAllocateHeap(PhHeapHandle, Flags, Size); +} + +/** + * Frees a block of memory allocated with PhAllocate(). + * + * \param Memory A pointer to a block of memory. + */ +VOID PhFree( + _Frees_ptr_opt_ PVOID Memory + ) +{ + RtlFreeHeap(PhHeapHandle, 0, Memory); +} + +/** + * Re-allocates a block of memory originally allocated with PhAllocate(). + * + * \param Memory A pointer to a block of memory. + * \param Size The new size of the memory block, in bytes. + * + * \return A pointer to the new block of memory. The existing contents of the memory block are + * copied to the new block. + * + * \remarks If the function fails to allocate the block of memory, it raises an exception. + */ +_May_raise_ +_Post_writable_byte_size_(Size) +PVOID PhReAllocate( + _Frees_ptr_opt_ PVOID Memory, + _In_ SIZE_T Size + ) +{ + return RtlReAllocateHeap(PhHeapHandle, HEAP_GENERATE_EXCEPTIONS, Memory, Size); +} + +/** + * Re-allocates a block of memory originally allocated with PhAllocate(). + * + * \param Memory A pointer to a block of memory. + * \param Size The new size of the memory block, in bytes. + * + * \return A pointer to the new block of memory, or NULL if the block could not be allocated. The + * existing contents of the memory block are copied to the new block. + */ +PVOID PhReAllocateSafe( + _In_ PVOID Memory, + _In_ SIZE_T Size + ) +{ + return RtlReAllocateHeap(PhHeapHandle, 0, Memory, Size); +} + +/** + * Allocates pages of memory. + * + * \param Size The number of bytes to allocate. The number of pages allocated will be large enough + * to contain \a Size bytes. + * \param NewSize The number of bytes actually allocated. This is \a Size rounded up to the next + * multiple of PAGE_SIZE. + * + * \return A pointer to the allocated block of memory, or NULL if the block could not be allocated. + */ +_Check_return_ +_Ret_maybenull_ +PVOID PhAllocatePage( + _In_ SIZE_T Size, + _Out_opt_ PSIZE_T NewSize + ) +{ + PVOID baseAddress; + + baseAddress = NULL; + + if (NT_SUCCESS(NtAllocateVirtualMemory( + NtCurrentProcess(), + &baseAddress, + 0, + &Size, + MEM_COMMIT, + PAGE_READWRITE + ))) + { + if (NewSize) + *NewSize = Size; + + return baseAddress; + } + else + { + return NULL; + } +} + +/** + * Frees pages of memory allocated with PhAllocatePage(). + * + * \param Memory A pointer to a block of memory. + */ +VOID PhFreePage( + _Frees_ptr_opt_ PVOID Memory + ) +{ + SIZE_T size; + + size = 0; + + NtFreeVirtualMemory( + NtCurrentProcess(), + &Memory, + &size, + MEM_RELEASE + ); +} + +/** + * Determines the length of the specified string, in characters. + * + * \param String The string. + */ +SIZE_T PhCountStringZ( + _In_ PWSTR String + ) +{ + if (PhpVectorLevel >= PH_VECTOR_LEVEL_SSE2) + { + PWSTR p; + ULONG unaligned; + __m128i b; + __m128i z; + ULONG mask; + ULONG index; + + p = (PWSTR)((ULONG_PTR)String & ~0xe); // String should be 2 byte aligned + unaligned = PtrToUlong(String) & 0xf; + z = _mm_setzero_si128(); + + if (unaligned != 0) + { + b = _mm_load_si128((__m128i *)p); + b = _mm_cmpeq_epi16(b, z); + mask = _mm_movemask_epi8(b) >> unaligned; + + if (_BitScanForward(&index, mask)) + return index / sizeof(WCHAR); + + p += 16 / sizeof(WCHAR); + } + + while (TRUE) + { + b = _mm_load_si128((__m128i *)p); + b = _mm_cmpeq_epi16(b, z); + mask = _mm_movemask_epi8(b); + + if (_BitScanForward(&index, mask)) + return (SIZE_T)(p - String) + index / sizeof(WCHAR); + + p += 16 / sizeof(WCHAR); + } + } + else + { + return wcslen(String); + } +} + +/** + * Allocates space for and copies a byte string. + * + * \param String The string to duplicate. + * + * \return The new string, which can be freed using PhFree(). + */ +PSTR PhDuplicateBytesZ( + _In_ PSTR String + ) +{ + PSTR newString; + SIZE_T length; + + length = strlen(String) + 1; // include the null terminator + + newString = PhAllocate(length); + memcpy(newString, String, length); + + return newString; +} + +/** + * Allocates space for and copies a byte string. + * + * \param String The string to duplicate. + * + * \return The new string, which can be freed using PhFree(), or NULL if storage could not be + * allocated. + */ +PSTR PhDuplicateBytesZSafe( + _In_ PSTR String + ) +{ + PSTR newString; + SIZE_T length; + + length = strlen(String) + 1; // include the null terminator + + newString = PhAllocateSafe(length); + + if (!newString) + return NULL; + + memcpy(newString, String, length); + + return newString; +} + +/** + * Allocates space for and copies a 16-bit string. + * + * \param String The string to duplicate. + * + * \return The new string, which can be freed using PhFree(). + */ +PWSTR PhDuplicateStringZ( + _In_ PWSTR String + ) +{ + PWSTR newString; + SIZE_T length; + + length = (PhCountStringZ(String) + 1) * sizeof(WCHAR); // include the null terminator + + newString = PhAllocate(length); + memcpy(newString, String, length); + + return newString; +} + +/** + * Copies a string with optional null termination and a maximum number of characters. + * + * \param InputBuffer A pointer to the input string. + * \param InputCount The maximum number of characters to copy, not including the null terminator. + * Specify -1 for no limit. + * \param OutputBuffer A pointer to the output buffer. + * \param OutputCount The number of characters in the output buffer, including the null terminator. + * \param ReturnCount A variable which receives the number of characters required to contain the + * input string, including the null terminator. If the function returns TRUE, this variable contains + * the number of characters written to the output buffer. + * + * \return TRUE if the input string was copied to the output string, otherwise FALSE. + * + * \remarks The function stops copying when it encounters the first null character in the input + * string. It will also stop copying when it reaches the character count specified in \a InputCount, + * if \a InputCount is not -1. + */ +BOOLEAN PhCopyBytesZ( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ) +{ + SIZE_T i; + BOOLEAN copied; + + // Determine the length of the input string. + + if (InputCount != -1) + { + i = 0; + + while (i < InputCount && InputBuffer[i]) + i++; + } + else + { + i = strlen(InputBuffer); + } + + // Copy the string if there is enough room. + + if (OutputBuffer && OutputCount >= i + 1) // need one character for null terminator + { + memcpy(OutputBuffer, InputBuffer, i); + OutputBuffer[i] = 0; + copied = TRUE; + } + else + { + copied = FALSE; + } + + if (ReturnCount) + *ReturnCount = i + 1; + + return copied; +} + +/** + * Copies a string with optional null termination and a maximum number of characters. + * + * \param InputBuffer A pointer to the input string. + * \param InputCount The maximum number of characters to copy, not including the null terminator. + * Specify -1 for no limit. + * \param OutputBuffer A pointer to the output buffer. + * \param OutputCount The number of characters in the output buffer, including the null terminator. + * \param ReturnCount A variable which receives the number of characters required to contain the + * input string, including the null terminator. If the function returns TRUE, this variable contains + * the number of characters written to the output buffer. + * + * \return TRUE if the input string was copied to the output string, otherwise FALSE. + * + * \remarks The function stops copying when it encounters the first null character in the input + * string. It will also stop copying when it reaches the character count specified in \a InputCount, + * if \a InputCount is not -1. + */ +BOOLEAN PhCopyStringZ( + _In_ PWSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ) +{ + SIZE_T i; + BOOLEAN copied; + + // Determine the length of the input string. + + if (InputCount != -1) + { + i = 0; + + while (i < InputCount && InputBuffer[i]) + i++; + } + else + { + i = PhCountStringZ(InputBuffer); + } + + // Copy the string if there is enough room. + + if (OutputBuffer && OutputCount >= i + 1) // need one character for null terminator + { + memcpy(OutputBuffer, InputBuffer, i * sizeof(WCHAR)); + OutputBuffer[i] = 0; + copied = TRUE; + } + else + { + copied = FALSE; + } + + if (ReturnCount) + *ReturnCount = i + 1; + + return copied; +} + +/** + * Copies a string with optional null termination and a maximum number of characters. + * + * \param InputBuffer A pointer to the input string. + * \param InputCount The maximum number of characters to copy, not including the null terminator. + * Specify -1 for no limit. + * \param OutputBuffer A pointer to the output buffer. + * \param OutputCount The number of characters in the output buffer, including the null terminator. + * \param ReturnCount A variable which receives the number of characters required to contain the + * input string, including the null terminator. If the function returns TRUE, this variable contains + * the number of characters written to the output buffer. + * + * \return TRUE if the input string was copied to the output string, otherwise FALSE. + * + * \remarks The function stops copying when it encounters the first null character in the input + * string. It will also stop copying when it reaches the character count specified in \a InputCount, + * if \a InputCount is not -1. + */ +BOOLEAN PhCopyStringZFromBytes( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ) +{ + SIZE_T i; + BOOLEAN copied; + + // Determine the length of the input string. + + if (InputCount != -1) + { + i = 0; + + while (i < InputCount && InputBuffer[i]) + i++; + } + else + { + i = strlen(InputBuffer); + } + + // Copy the string if there is enough room. + + if (OutputBuffer && OutputCount >= i + 1) // need one character for null terminator + { + PhZeroExtendToUtf16Buffer(InputBuffer, i, OutputBuffer); + OutputBuffer[i] = 0; + copied = TRUE; + } + else + { + copied = FALSE; + } + + if (ReturnCount) + *ReturnCount = i + 1; + + return copied; +} + +/** + * Copies a string with optional null termination and a maximum number of characters. + * + * \param InputBuffer A pointer to the input string. + * \param InputCount The maximum number of characters to copy, not including the null terminator. + * Specify -1 for no limit. + * \param OutputBuffer A pointer to the output buffer. + * \param OutputCount The number of characters in the output buffer, including the null terminator. + * \param ReturnCount A variable which receives the number of characters required to contain the + * input string, including the null terminator. If the function returns TRUE, this variable contains + * the number of characters written to the output buffer. + * + * \return TRUE if the input string was copied to the output string, otherwise FALSE. + * + * \remarks The function stops copying when it encounters the first null character in the input + * string. It will also stop copying when it reaches the character count specified in \a InputCount, + * if \a InputCount is not -1. + */ +BOOLEAN PhCopyStringZFromMultiByte( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ) +{ + NTSTATUS status; + SIZE_T i; + ULONG unicodeBytes; + BOOLEAN copied; + + // Determine the length of the input string. + + if (InputCount != -1) + { + i = 0; + + while (i < InputCount && InputBuffer[i]) + i++; + } + else + { + i = strlen(InputBuffer); + } + + // Determine the length of the output string. + + status = RtlMultiByteToUnicodeSize( + &unicodeBytes, + InputBuffer, + (ULONG)i + ); + + if (!NT_SUCCESS(status)) + { + if (ReturnCount) + *ReturnCount = -1; + + return FALSE; + } + + // Convert the string to Unicode if there is enough room. + + if (OutputBuffer && OutputCount >= unicodeBytes / sizeof(WCHAR) + 1) + { + status = RtlMultiByteToUnicodeN( + OutputBuffer, + unicodeBytes, + NULL, + InputBuffer, + (ULONG)i + ); + + if (NT_SUCCESS(status)) + { + // RtlMultiByteToUnicodeN doesn't null terminate the string. + *(PWCHAR)((PCHAR)OutputBuffer + unicodeBytes) = 0; + copied = TRUE; + } + else + { + copied = FALSE; + } + } + else + { + copied = FALSE; + } + + if (ReturnCount) + *ReturnCount = unicodeBytes / sizeof(WCHAR) + 1; + + return copied; +} + +FORCEINLINE LONG PhpCompareRightNatural( + _In_ PWSTR A, + _In_ PWSTR B + ) +{ + LONG bias = 0; + + for (; ; A++, B++) + { + if (!PhIsDigitCharacter(*A) && !PhIsDigitCharacter(*B)) + { + return bias; + } + else if (!PhIsDigitCharacter(*A)) + { + return -1; + } + else if (!PhIsDigitCharacter(*B)) + { + return 1; + } + else if (*A < *B) + { + if (bias == 0) + bias = -1; + } + else if (*A > *B) + { + if (bias == 0) + bias = 1; + } + else if (!*A && !*B) + { + return bias; + } + } + + return 0; +} + +FORCEINLINE LONG PhpCompareLeftNatural( + _In_ PWSTR A, + _In_ PWSTR B + ) +{ + for (; ; A++, B++) + { + if (!PhIsDigitCharacter(*A) && !PhIsDigitCharacter(*B)) + { + return 0; + } + else if (!PhIsDigitCharacter(*A)) + { + return -1; + } + else if (!PhIsDigitCharacter(*B)) + { + return 1; + } + else if (*A < *B) + { + return -1; + } + else if (*A > *B) + { + return 1; + } + } + + return 0; +} + +FORCEINLINE LONG PhpCompareStringZNatural( + _In_ PWSTR A, + _In_ PWSTR B, + _In_ BOOLEAN IgnoreCase + ) +{ + /* strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + This code has been modified for Process Hacker. + */ + + ULONG ai, bi; + WCHAR ca, cb; + LONG result; + BOOLEAN fractional; + + ai = 0; + bi = 0; + + while (TRUE) + { + ca = A[ai]; + cb = B[bi]; + + /* Skip over leading spaces or zeros. */ + + while (ca == ' ') + ca = A[++ai]; + + while (cb == ' ') + cb = B[++bi]; + + /* Process run of digits. */ + if (PhIsDigitCharacter(ca) && PhIsDigitCharacter(cb)) + { + fractional = (ca == '0' || cb == '0'); + + if (fractional) + { + if ((result = PhpCompareLeftNatural(A + ai, B + bi)) != 0) + return result; + } + else + { + if ((result = PhpCompareRightNatural(A + ai, B + bi)) != 0) + return result; + } + } + + if (!ca && !cb) + { + /* Strings are considered the same. */ + return 0; + } + + if (IgnoreCase) + { + ca = towupper(ca); + cb = towupper(cb); + } + + if (ca < cb) + return -1; + else if (ca > cb) + return 1; + + ai++; + bi++; + } +} + +/** + * Compares two strings in natural sort order. + * + * \param A The first string. + * \param B The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +LONG PhCompareStringZNatural( + _In_ PWSTR A, + _In_ PWSTR B, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return PhpCompareStringZNatural(A, B, FALSE); + else + return PhpCompareStringZNatural(A, B, TRUE); +} + +/** + * Compares two strings. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase TRUE to perform a case-insensitive comparison, otherwise FALSE. + */ +LONG PhCompareStringRef( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ BOOLEAN IgnoreCase + ) +{ + SIZE_T l1; + SIZE_T l2; + PWCHAR s1; + PWCHAR s2; + WCHAR c1; + WCHAR c2; + PWCHAR end; + + // Note: this function assumes that the difference between the lengths of the two strings can + // fit inside a LONG. + + l1 = String1->Length; + l2 = String2->Length; + assert(!(l1 & 1)); + assert(!(l2 & 1)); + s1 = String1->Buffer; + s2 = String2->Buffer; + + end = (PWCHAR)((PCHAR)s1 + (l1 <= l2 ? l1 : l2)); + + if (!IgnoreCase) + { + while (s1 != end) + { + c1 = *s1; + c2 = *s2; + + if (c1 != c2) + return (LONG)c1 - (LONG)c2; + + s1++; + s2++; + } + } + else + { + while (s1 != end) + { + c1 = *s1; + c2 = *s2; + + if (c1 != c2) + { + c1 = RtlUpcaseUnicodeChar(c1); + c2 = RtlUpcaseUnicodeChar(c2); + + if (c1 != c2) + return (LONG)c1 - (LONG)c2; + } + + s1++; + s2++; + } + } + + return (LONG)(l1 - l2); +} + +/** + * Determines if two strings are equal. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase TRUE to perform a case-insensitive comparison, otherwise FALSE. + */ +BOOLEAN PhEqualStringRef( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ BOOLEAN IgnoreCase + ) +{ + SIZE_T l1; + SIZE_T l2; + PWSTR s1; + PWSTR s2; + WCHAR c1; + WCHAR c2; + SIZE_T length; + + l1 = String1->Length; + l2 = String2->Length; + assert(!(l1 & 1)); + assert(!(l2 & 1)); + + if (l1 != l2) + return FALSE; + + s1 = String1->Buffer; + s2 = String2->Buffer; + + if (PhpVectorLevel >= PH_VECTOR_LEVEL_SSE2) + { + length = l1 / 16; + + if (length != 0) + { + __m128i b1; + __m128i b2; + + do + { + b1 = _mm_loadu_si128((__m128i *)s1); + b2 = _mm_loadu_si128((__m128i *)s2); + b1 = _mm_cmpeq_epi32(b1, b2); + + if (_mm_movemask_epi8(b1) != 0xffff) + { + if (!IgnoreCase) + { + return FALSE; + } + else + { + // Compare character-by-character to ignore case. + l1 = length * 16 + (l1 & 15); + l1 /= sizeof(WCHAR); + goto CompareCharacters; + } + } + + s1 += 16 / sizeof(WCHAR); + s2 += 16 / sizeof(WCHAR); + } while (--length != 0); + } + + // Compare character-by-character because we have no more 16-byte blocks to compare. + l1 = (l1 & 15) / sizeof(WCHAR); + } + else + { + length = l1 / sizeof(ULONG_PTR); + + if (length != 0) + { + do + { + if (*(PULONG_PTR)s1 != *(PULONG_PTR)s2) + { + if (!IgnoreCase) + { + return FALSE; + } + else + { + // Compare character-by-character to ignore case. + l1 = length * sizeof(ULONG_PTR) + (l1 & (sizeof(ULONG_PTR) - 1)); + l1 /= sizeof(WCHAR); + goto CompareCharacters; + } + } + + s1 += sizeof(ULONG_PTR) / sizeof(WCHAR); + s2 += sizeof(ULONG_PTR) / sizeof(WCHAR); + } while (--length != 0); + } + + // Compare character-by-character because we have no more ULONG_PTR blocks to compare. + l1 = (l1 & (sizeof(ULONG_PTR) - 1)) / sizeof(WCHAR); + } + +CompareCharacters: + if (l1 != 0) + { + if (!IgnoreCase) + { + do + { + c1 = *s1; + c2 = *s2; + + if (c1 != c2) + return FALSE; + + s1++; + s2++; + } while (--l1 != 0); + } + else + { + do + { + c1 = *s1; + c2 = *s2; + + if (c1 != c2) + { + c1 = RtlUpcaseUnicodeChar(c1); + c2 = RtlUpcaseUnicodeChar(c2); + + if (c1 != c2) + return FALSE; + } + + s1++; + s2++; + } while (--l1 != 0); + } + } + + return TRUE; +} + +/** + * Locates a character in a string. + * + * \param String The string to search. + * \param Character The character to search for. + * \param IgnoreCase TRUE to perform a case-insensitive search, otherwise FALSE. + * + * \return The index, in characters, of the first occurrence of \a Character in \a String1. If + * \a Character was not found, -1 is returned. + */ +ULONG_PTR PhFindCharInStringRef( + _In_ PPH_STRINGREF String, + _In_ WCHAR Character, + _In_ BOOLEAN IgnoreCase + ) +{ + PWSTR buffer; + SIZE_T length; + + buffer = String->Buffer; + length = String->Length / sizeof(WCHAR); + + if (!IgnoreCase) + { + if (PhpVectorLevel >= PH_VECTOR_LEVEL_SSE2) + { + SIZE_T length16; + + length16 = String->Length / 16; + length &= 7; + + if (length16 != 0) + { + __m128i pattern; + __m128i block; + ULONG mask; + ULONG index; + + pattern = _mm_set1_epi16(Character); + + do + { + block = _mm_loadu_si128((__m128i *)buffer); + block = _mm_cmpeq_epi16(block, pattern); + mask = _mm_movemask_epi8(block); + + if (_BitScanForward(&index, mask)) + return (String->Length - length16 * 16) / sizeof(WCHAR) - length + index / 2; + + buffer += 16 / sizeof(WCHAR); + } while (--length16 != 0); + } + } + + if (length != 0) + { + do + { + if (*buffer == Character) + return String->Length / sizeof(WCHAR) - length; + + buffer++; + } while (--length != 0); + } + } + else + { + if (length != 0) + { + WCHAR c; + + c = RtlUpcaseUnicodeChar(Character); + + do + { + if (RtlUpcaseUnicodeChar(*buffer) == c) + return String->Length / sizeof(WCHAR) - length; + + buffer++; + } while (--length != 0); + } + } + + return -1; +} + +/** + * Locates a character in a string, searching backwards. + * + * \param String The string to search. + * \param Character The character to search for. + * \param IgnoreCase TRUE to perform a case-insensitive search, otherwise FALSE. + * + * \return The index, in characters, of the last occurrence of \a Character in \a String1. If + * \a Character was not found, -1 is returned. + */ +ULONG_PTR PhFindLastCharInStringRef( + _In_ PPH_STRINGREF String, + _In_ WCHAR Character, + _In_ BOOLEAN IgnoreCase + ) +{ + PWCHAR buffer; + SIZE_T length; + + buffer = (PWCHAR)((PCHAR)String->Buffer + String->Length); + length = String->Length / sizeof(WCHAR); + + if (!IgnoreCase) + { + if (PhpVectorLevel >= PH_VECTOR_LEVEL_SSE2) + { + SIZE_T length16; + + length16 = String->Length / 16; + length &= 7; + + if (length16 != 0) + { + __m128i pattern; + __m128i block; + ULONG mask; + ULONG index; + + pattern = _mm_set1_epi16(Character); + buffer -= 16 / sizeof(WCHAR); + + do + { + block = _mm_loadu_si128((__m128i *)buffer); + block = _mm_cmpeq_epi16(block, pattern); + mask = _mm_movemask_epi8(block); + + if (_BitScanReverse(&index, mask)) + return (length16 - 1) * 16 / sizeof(WCHAR) + length + index / 2; + + buffer -= 16 / sizeof(WCHAR); + } while (--length16 != 0); + + buffer += 16 / sizeof(WCHAR); + } + } + + if (length != 0) + { + buffer--; + + do + { + if (*buffer == Character) + return length - 1; + + buffer--; + } while (--length != 0); + } + } + else + { + if (length != 0) + { + WCHAR c; + + c = RtlUpcaseUnicodeChar(Character); + buffer--; + + do + { + if (RtlUpcaseUnicodeChar(*buffer) == c) + return length - 1; + + buffer--; + } while (--length != 0); + } + } + + return -1; +} + +/** + * Locates a string in a string. + * + * \param String The string to search. + * \param SubString The string to search for. + * \param IgnoreCase TRUE to perform a case-insensitive search, otherwise FALSE. + * + * \return The index, in characters, of the first occurrence of \a SubString in \a String. If + * \a SubString was not found, -1 is returned. + */ +ULONG_PTR PhFindStringInStringRef( + _In_ PPH_STRINGREF String, + _In_ PPH_STRINGREF SubString, + _In_ BOOLEAN IgnoreCase + ) +{ + SIZE_T length1; + SIZE_T length2; + PH_STRINGREF sr1; + PH_STRINGREF sr2; + WCHAR c; + SIZE_T i; + + length1 = String->Length / sizeof(WCHAR); + length2 = SubString->Length / sizeof(WCHAR); + + // Can't be a substring if it's bigger than the first string. + if (length2 > length1) + return -1; + // We always get a match if the substring is zero-length. + if (length2 == 0) + return 0; + + sr1.Buffer = String->Buffer; + sr1.Length = SubString->Length - sizeof(WCHAR); + sr2.Buffer = SubString->Buffer; + sr2.Length = SubString->Length - sizeof(WCHAR); + + if (!IgnoreCase) + { + c = *sr2.Buffer++; + + for (i = length1 - length2 + 1; i != 0; i--) + { + if (*sr1.Buffer++ == c && PhEqualStringRef(&sr1, &sr2, FALSE)) + { + goto FoundUString; + } + } + } + else + { + c = RtlUpcaseUnicodeChar(*sr2.Buffer++); + + for (i = length1 - length2 + 1; i != 0; i--) + { + if (RtlUpcaseUnicodeChar(*sr1.Buffer++) == c && PhEqualStringRef(&sr1, &sr2, TRUE)) + { + goto FoundUString; + } + } + } + + return -1; +FoundUString: + return (ULONG_PTR)(sr1.Buffer - String->Buffer - 1); +} + +/** + * Splits a string. + * + * \param Input The input string. + * \param Separator The character to split at. + * \param FirstPart A variable which receives the part of \a Input before the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * \a Input. + * \param SecondPart A variable which recieves the part of \a Input after the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * an empty string. + * + * \return TRUE if \a Separator was found in \a Input, otherwise FALSE. + */ +BOOLEAN PhSplitStringRefAtChar( + _In_ PPH_STRINGREF Input, + _In_ WCHAR Separator, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ) +{ + PH_STRINGREF input; + ULONG_PTR index; + + input = *Input; // get a copy of the input because FirstPart/SecondPart may alias Input + index = PhFindCharInStringRef(Input, Separator, FALSE); + + if (index == -1) + { + // The separator was not found. + + FirstPart->Buffer = Input->Buffer; + FirstPart->Length = Input->Length; + SecondPart->Buffer = NULL; + SecondPart->Length = 0; + + return FALSE; + } + + FirstPart->Buffer = input.Buffer; + FirstPart->Length = index * sizeof(WCHAR); + SecondPart->Buffer = (PWCHAR)((PCHAR)input.Buffer + index * sizeof(WCHAR) + sizeof(WCHAR)); + SecondPart->Length = input.Length - index * sizeof(WCHAR) - sizeof(WCHAR); + + return TRUE; +} + +/** + * Splits a string at the last occurrence of a character. + * + * \param Input The input string. + * \param Separator The character to split at. + * \param FirstPart A variable which receives the part of \a Input before the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * \a Input. + * \param SecondPart A variable which recieves the part of \a Input after the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * an empty string. + * + * \return TRUE if \a Separator was found in \a Input, otherwise FALSE. + */ +BOOLEAN PhSplitStringRefAtLastChar( + _In_ PPH_STRINGREF Input, + _In_ WCHAR Separator, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ) +{ + PH_STRINGREF input; + ULONG_PTR index; + + input = *Input; // get a copy of the input because FirstPart/SecondPart may alias Input + index = PhFindLastCharInStringRef(Input, Separator, FALSE); + + if (index == -1) + { + // The separator was not found. + + FirstPart->Buffer = Input->Buffer; + FirstPart->Length = Input->Length; + SecondPart->Buffer = NULL; + SecondPart->Length = 0; + + return FALSE; + } + + FirstPart->Buffer = input.Buffer; + FirstPart->Length = index * sizeof(WCHAR); + SecondPart->Buffer = (PWCHAR)((PCHAR)input.Buffer + index * sizeof(WCHAR) + sizeof(WCHAR)); + SecondPart->Length = input.Length - index * sizeof(WCHAR) - sizeof(WCHAR); + + return TRUE; +} + +/** + * Splits a string. + * + * \param Input The input string. + * \param Separator The string to split at. + * \param IgnoreCase TRUE to perform a case-insensitive search, otherwise FALSE. + * \param FirstPart A variable which receives the part of \a Input before the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * \a Input. + * \param SecondPart A variable which recieves the part of \a Input after the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * an empty string. + * + * \return TRUE if \a Separator was found in \a Input, otherwise FALSE. + */ +BOOLEAN PhSplitStringRefAtString( + _In_ PPH_STRINGREF Input, + _In_ PPH_STRINGREF Separator, + _In_ BOOLEAN IgnoreCase, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ) +{ + PH_STRINGREF input; + ULONG_PTR index; + + input = *Input; // get a copy of the input because FirstPart/SecondPart may alias Input + index = PhFindStringInStringRef(Input, Separator, IgnoreCase); + + if (index == -1) + { + // The separator was not found. + + FirstPart->Buffer = Input->Buffer; + FirstPart->Length = Input->Length; + SecondPart->Buffer = NULL; + SecondPart->Length = 0; + + return FALSE; + } + + FirstPart->Buffer = input.Buffer; + FirstPart->Length = index * sizeof(WCHAR); + SecondPart->Buffer = (PWCHAR)((PCHAR)input.Buffer + index * sizeof(WCHAR) + Separator->Length); + SecondPart->Length = input.Length - index * sizeof(WCHAR) - Separator->Length; + + return TRUE; +} + +/** + * Splits a string. + * + * \param Input The input string. + * \param Separator The character set, string or range to split at. + * \param Flags A combination of flags. + * \li \c PH_SPLIT_AT_CHAR_SET \a Separator specifies a character set. \a Input will be split at a + * character which is contained in \a Separator. + * \li \c PH_SPLIT_AT_STRING \a Separator specifies a string. \a Input will be split an occurrence + * of \a Separator. + * \li \c PH_SPLIT_AT_RANGE \a Separator specifies a range. The \a Buffer field contains a character + * index cast to \c PWSTR and the \a Length field contains the length of the range, in bytes. + * \li \c PH_SPLIT_CASE_INSENSITIVE Specifies a case-insensitive search. + * \li \c PH_SPLIT_COMPLEMENT_CHAR_SET If used with \c PH_SPLIT_AT_CHAR_SET, the separator is a + * character which is not contained in \a Separator. + * \li \c PH_SPLIT_START_AT_END If used with \c PH_SPLIT_AT_CHAR_SET, the search is performed + * starting from the end of the string. + * \li \c PH_SPLIT_CHAR_SET_IS_UPPERCASE If used with \c PH_SPLIT_CASE_INSENSITIVE, specifies that + * the character set in \a Separator contains only uppercase characters. + * \param FirstPart A variable which receives the part of \a Input before the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * \a Input. + * \param SecondPart A variable which recieves the part of \a Input after the separator. This may be + * the same variable as \a Input. If the separator is not found in \a Input, this variable is set to + * an empty string. + * \param SeparatorPart A variable which receives the part of \a Input that is the separator. If the + * separator is not found in \a Input, this variable is set to an empty string. + * + * \return TRUE if a separator was found in \a Input, otherwise FALSE. + */ +BOOLEAN PhSplitStringRefEx( + _In_ PPH_STRINGREF Input, + _In_ PPH_STRINGREF Separator, + _In_ ULONG Flags, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart, + _Out_opt_ PPH_STRINGREF SeparatorPart + ) +{ + PH_STRINGREF input; + SIZE_T separatorIndex; + SIZE_T separatorLength; + PWCHAR charSet; + SIZE_T charSetCount; + BOOLEAN charSetTable[256]; + BOOLEAN charSetTableComplete; + SIZE_T i; + SIZE_T j; + USHORT c; + PWCHAR s; + LONG_PTR direction; + + input = *Input; // Get a copy of the input because FirstPart/SecondPart/SeparatorPart may alias Input + + if (Flags & PH_SPLIT_AT_RANGE) + { + separatorIndex = (SIZE_T)Separator->Buffer; + separatorLength = Separator->Length; + + if (separatorIndex == -1) + goto SeparatorNotFound; + + goto SeparatorFound; + } + else if (Flags & PH_SPLIT_AT_STRING) + { + if (Flags & PH_SPLIT_START_AT_END) + { + // not implemented + goto SeparatorNotFound; + } + + separatorIndex = PhFindStringInStringRef(Input, Separator, !!(Flags & PH_SPLIT_CASE_INSENSITIVE)); + + if (separatorIndex == -1) + goto SeparatorNotFound; + + separatorLength = Separator->Length; + goto SeparatorFound; + } + + // Special case for character sets with only one character. + if (!(Flags & PH_SPLIT_COMPLEMENT_CHAR_SET) && Separator->Length == sizeof(WCHAR)) + { + if (!(Flags & PH_SPLIT_START_AT_END)) + separatorIndex = PhFindCharInStringRef(Input, Separator->Buffer[0], !!(Flags & PH_SPLIT_CASE_INSENSITIVE)); + else + separatorIndex = PhFindLastCharInStringRef(Input, Separator->Buffer[0], !!(Flags & PH_SPLIT_CASE_INSENSITIVE)); + + if (separatorIndex == -1) + goto SeparatorNotFound; + + separatorLength = sizeof(WCHAR); + goto SeparatorFound; + } + + if (input.Length == 0) + goto SeparatorNotFound; + + // Build the character set lookup table. + + charSet = Separator->Buffer; + charSetCount = Separator->Length / sizeof(WCHAR); + memset(charSetTable, 0, sizeof(charSetTable)); + charSetTableComplete = TRUE; + + for (i = 0; i < charSetCount; i++) + { + c = charSet[i]; + + if (Flags & PH_SPLIT_CASE_INSENSITIVE) + c = RtlUpcaseUnicodeChar(c); + + charSetTable[c & 0xff] = TRUE; + + if (c >= 256) + charSetTableComplete = FALSE; + } + + // Perform the search. + + i = input.Length / sizeof(WCHAR); + separatorLength = sizeof(WCHAR); + + if (!(Flags & PH_SPLIT_START_AT_END)) + { + s = input.Buffer; + direction = 1; + } + else + { + s = (PWCHAR)((PCHAR)input.Buffer + input.Length - sizeof(WCHAR)); + direction = -1; + } + + do + { + c = *s; + + if (Flags & PH_SPLIT_CASE_INSENSITIVE) + c = RtlUpcaseUnicodeChar(c); + + if (c < 256 && charSetTableComplete) + { + if (!(Flags & PH_SPLIT_COMPLEMENT_CHAR_SET)) + { + if (charSetTable[c]) + goto CharFound; + } + else + { + if (!charSetTable[c]) + goto CharFound; + } + } + else + { + if (!(Flags & PH_SPLIT_COMPLEMENT_CHAR_SET)) + { + if (charSetTable[c & 0xff]) + { + if (!(Flags & PH_SPLIT_CASE_INSENSITIVE) || (Flags & PH_SPLIT_CHAR_SET_IS_UPPERCASE)) + { + for (j = 0; j < charSetCount; j++) + { + if (charSet[j] == c) + goto CharFound; + } + } + else + { + for (j = 0; j < charSetCount; j++) + { + if (RtlUpcaseUnicodeChar(charSet[j]) == c) + goto CharFound; + } + } + } + } + else + { + if (charSetTable[c & 0xff]) + { + if (!(Flags & PH_SPLIT_CASE_INSENSITIVE) || (Flags & PH_SPLIT_CHAR_SET_IS_UPPERCASE)) + { + for (j = 0; j < charSetCount; j++) + { + if (charSet[j] == c) + break; + } + } + else + { + for (j = 0; j < charSetCount; j++) + { + if (RtlUpcaseUnicodeChar(charSet[j]) == c) + break; + } + } + + if (j == charSetCount) + goto CharFound; + } + else + { + goto CharFound; + } + } + } + + s += direction; + } while (--i != 0); + + goto SeparatorNotFound; + +CharFound: + separatorIndex = s - input.Buffer; + +SeparatorFound: + FirstPart->Buffer = input.Buffer; + FirstPart->Length = separatorIndex * sizeof(WCHAR); + SecondPart->Buffer = (PWCHAR)((PCHAR)input.Buffer + separatorIndex * sizeof(WCHAR) + separatorLength); + SecondPart->Length = input.Length - separatorIndex * sizeof(WCHAR) - separatorLength; + + if (SeparatorPart) + { + SeparatorPart->Buffer = input.Buffer + separatorIndex; + SeparatorPart->Length = separatorLength; + } + + return TRUE; + +SeparatorNotFound: + FirstPart->Buffer = input.Buffer; + FirstPart->Length = input.Length; + SecondPart->Buffer = NULL; + SecondPart->Length = 0; + + if (SeparatorPart) + { + SeparatorPart->Buffer = NULL; + SeparatorPart->Length = 0; + } + + return FALSE; +} + +VOID PhTrimStringRef( + _Inout_ PPH_STRINGREF String, + _In_ PPH_STRINGREF CharSet, + _In_ ULONG Flags + ) +{ + PWCHAR charSet; + SIZE_T charSetCount; + BOOLEAN charSetTable[256]; + BOOLEAN charSetTableComplete; + SIZE_T i; + SIZE_T j; + USHORT c; + SIZE_T trimCount; + SIZE_T count; + PWCHAR s; + + if (String->Length == 0 || CharSet->Length == 0) + return; + + if (CharSet->Length == sizeof(WCHAR)) + { + c = CharSet->Buffer[0]; + + if (!(Flags & PH_TRIM_END_ONLY)) + { + trimCount = 0; + count = String->Length / sizeof(WCHAR); + s = String->Buffer; + + while (count-- != 0) + { + if (*s++ != c) + break; + + trimCount++; + } + + PhSkipStringRef(String, trimCount * sizeof(WCHAR)); + } + + if (!(Flags & PH_TRIM_START_ONLY)) + { + trimCount = 0; + count = String->Length / sizeof(WCHAR); + s = (PWCHAR)((PCHAR)String->Buffer + String->Length - sizeof(WCHAR)); + + while (count-- != 0) + { + if (*s-- != c) + break; + + trimCount++; + } + + String->Length -= trimCount * sizeof(WCHAR); + } + + return; + } + + // Build the character set lookup table. + + charSet = CharSet->Buffer; + charSetCount = CharSet->Length / sizeof(WCHAR); + memset(charSetTable, 0, sizeof(charSetTable)); + charSetTableComplete = TRUE; + + for (i = 0; i < charSetCount; i++) + { + c = charSet[i]; + charSetTable[c & 0xff] = TRUE; + + if (c >= 256) + charSetTableComplete = FALSE; + } + + // Trim the string. + + if (!(Flags & PH_TRIM_END_ONLY)) + { + trimCount = 0; + count = String->Length / sizeof(WCHAR); + s = String->Buffer; + + while (count-- != 0) + { + c = *s++; + + if (!charSetTable[c & 0xff]) + break; + + if (!charSetTableComplete) + { + for (j = 0; j < charSetCount; j++) + { + if (charSet[j] == c) + goto CharFound; + } + + break; + } + +CharFound: + trimCount++; + } + + PhSkipStringRef(String, trimCount * sizeof(WCHAR)); + } + + if (!(Flags & PH_TRIM_START_ONLY)) + { + trimCount = 0; + count = String->Length / sizeof(WCHAR); + s = (PWCHAR)((PCHAR)String->Buffer + String->Length - sizeof(WCHAR)); + + while (count-- != 0) + { + c = *s--; + + if (!charSetTable[c & 0xff]) + break; + + if (!charSetTableComplete) + { + for (j = 0; j < charSetCount; j++) + { + if (charSet[j] == c) + goto CharFound2; + } + + break; + } + +CharFound2: + trimCount++; + } + + String->Length -= trimCount * sizeof(WCHAR); + } +} + +/** + * Creates a string object from an existing null-terminated string. + * + * \param Buffer A null-terminated Unicode string. + */ +PPH_STRING PhCreateString( + _In_ PWSTR Buffer + ) +{ + return PhCreateStringEx(Buffer, wcslen(Buffer) * sizeof(WCHAR)); +} + +/** + * Creates a string object using a specified length. + * + * \param Buffer A null-terminated Unicode string. + * \param Length The length, in bytes, of the string. + */ +PPH_STRING PhCreateStringEx( + _In_opt_ PWCHAR Buffer, + _In_ SIZE_T Length + ) +{ + PPH_STRING string; + + string = PhCreateObject( + FIELD_OFFSET(PH_STRING, Data) + Length + sizeof(WCHAR), // Null terminator + PhStringType + ); + + assert(!(Length & 1)); + string->Length = Length; + string->Buffer = string->Data; + *(PWCHAR)((PCHAR)string->Buffer + Length) = 0; + + if (Buffer) + { + memcpy(string->Buffer, Buffer, Length); + } + + return string; +} + +/** + * Obtains a reference to a zero-length string. + */ +PPH_STRING PhReferenceEmptyString( + VOID + ) +{ + PPH_STRING string; + PPH_STRING newString; + + string = PhSharedEmptyString; + + if (!string) + { + newString = PhCreateStringEx(NULL, 0); + + string = _InterlockedCompareExchangePointer( + &PhSharedEmptyString, + newString, + NULL + ); + + if (!string) + { + string = newString; // success + } + else + { + PhDereferenceObject(newString); + } + } + + return PhReferenceObject(string); +} + +/** + * Concatenates multiple strings. + * + * \param Count The number of strings to concatenate. + */ +PPH_STRING PhConcatStrings( + _In_ ULONG Count, + ... + ) +{ + va_list argptr; + + va_start(argptr, Count); + + return PhConcatStrings_V(Count, argptr); +} + +/** + * Concatenates multiple strings. + * + * \param Count The number of strings to concatenate. + * \param ArgPtr A pointer to an array of strings. + */ +PPH_STRING PhConcatStrings_V( + _In_ ULONG Count, + _In_ va_list ArgPtr + ) +{ + va_list argptr; + ULONG i; + SIZE_T totalLength = 0; + SIZE_T stringLength; + SIZE_T cachedLengths[PH_CONCAT_STRINGS_LENGTH_CACHE_SIZE]; + PWSTR arg; + PPH_STRING string; + + // Compute the total length, in bytes, of the strings. + + argptr = ArgPtr; + + for (i = 0; i < Count; i++) + { + arg = va_arg(argptr, PWSTR); + stringLength = PhCountStringZ(arg) * sizeof(WCHAR); + totalLength += stringLength; + + if (i < PH_CONCAT_STRINGS_LENGTH_CACHE_SIZE) + cachedLengths[i] = stringLength; + } + + // Create the string. + + string = PhCreateStringEx(NULL, totalLength); + totalLength = 0; + + // Append the strings one by one. + + argptr = ArgPtr; + + for (i = 0; i < Count; i++) + { + arg = va_arg(argptr, PWSTR); + + if (i < PH_CONCAT_STRINGS_LENGTH_CACHE_SIZE) + stringLength = cachedLengths[i]; + else + stringLength = PhCountStringZ(arg) * sizeof(WCHAR); + + memcpy( + (PCHAR)string->Buffer + totalLength, + arg, + stringLength + ); + totalLength += stringLength; + } + + return string; +} + +/** + * Concatenates two strings. + * + * \param String1 The first string. + * \param String2 The second string. + */ +PPH_STRING PhConcatStrings2( + _In_ PWSTR String1, + _In_ PWSTR String2 + ) +{ + PPH_STRING string; + SIZE_T length1; + SIZE_T length2; + + length1 = PhCountStringZ(String1) * sizeof(WCHAR); + length2 = PhCountStringZ(String2) * sizeof(WCHAR); + string = PhCreateStringEx(NULL, length1 + length2); + memcpy( + string->Buffer, + String1, + length1 + ); + memcpy( + (PCHAR)string->Buffer + length1, + String2, + length2 + ); + + return string; +} + +/** + * Concatenates two strings. + * + * \param String1 The first string. + * \param String2 The second string. + */ +PPH_STRING PhConcatStringRef2( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2 + ) +{ + PPH_STRING string; + + assert(!(String1->Length & 1)); + assert(!(String2->Length & 1)); + + string = PhCreateStringEx(NULL, String1->Length + String2->Length); + memcpy(string->Buffer, String1->Buffer, String1->Length); + memcpy((PCHAR)string->Buffer + String1->Length, String2->Buffer, String2->Length); + + return string; +} + +/** + * Concatenates three strings. + * + * \param String1 The first string. + * \param String2 The second string. + * \param String3 The third string. + */ +PPH_STRING PhConcatStringRef3( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ PPH_STRINGREF String3 + ) +{ + PPH_STRING string; + PCHAR buffer; + + assert(!(String1->Length & 1)); + assert(!(String2->Length & 1)); + assert(!(String3->Length & 1)); + + string = PhCreateStringEx(NULL, String1->Length + String2->Length + String3->Length); + + buffer = (PCHAR)string->Buffer; + memcpy(buffer, String1->Buffer, String1->Length); + + buffer += String1->Length; + memcpy(buffer, String2->Buffer, String2->Length); + + buffer += String2->Length; + memcpy(buffer, String3->Buffer, String3->Length); + + return string; +} + +/** + * Creates a string using format specifiers. + * + * \param Format The format-control string. + */ +PPH_STRING PhFormatString( + _In_ _Printf_format_string_ PWSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + + return PhFormatString_V(Format, argptr); +} + +/** + * Creates a string using format specifiers. + * + * \param Format The format-control string. + * \param ArgPtr A pointer to the list of arguments. + */ +PPH_STRING PhFormatString_V( + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ) +{ + PPH_STRING string; + int length; + + length = _vscwprintf(Format, ArgPtr); + + if (length == -1) + return NULL; + + string = PhCreateStringEx(NULL, length * sizeof(WCHAR)); + _vsnwprintf(string->Buffer, length, Format, ArgPtr); + + return string; +} + +/** + * Creates a bytes object from an existing null-terminated string of bytes. + * + * \param Buffer A null-terminated byte string. + */ +PPH_BYTES PhCreateBytes( + _In_ PSTR Buffer + ) +{ + return PhCreateBytesEx(Buffer, strlen(Buffer) * sizeof(CHAR)); +} + +/** + * Creates a bytes object. + * + * \param Buffer An array of bytes. + * \param Length The length of \a Buffer, in bytes. + */ +PPH_BYTES PhCreateBytesEx( + _In_opt_ PCHAR Buffer, + _In_ SIZE_T Length + ) +{ + PPH_BYTES bytes; + + bytes = PhCreateObject( + FIELD_OFFSET(PH_BYTES, Data) + Length + sizeof(CHAR), // Null terminator for compatibility + PhBytesType + ); + + bytes->Length = Length; + bytes->Buffer = bytes->Data; + bytes->Buffer[Length] = 0; + + if (Buffer) + { + memcpy(bytes->Buffer, Buffer, Length); + } + + return bytes; +} + +BOOLEAN PhWriteUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _In_ ULONG CodeUnit + ) +{ + switch (Decoder->Encoding) + { + case PH_UNICODE_UTF8: + if (Decoder->InputCount >= 4) + return FALSE; + Decoder->u.Utf8.Input[Decoder->InputCount] = (UCHAR)CodeUnit; + Decoder->InputCount++; + return TRUE; + case PH_UNICODE_UTF16: + if (Decoder->InputCount >= 2) + return FALSE; + Decoder->u.Utf16.Input[Decoder->InputCount] = (USHORT)CodeUnit; + Decoder->InputCount++; + return TRUE; + case PH_UNICODE_UTF32: + if (Decoder->InputCount >= 1) + return FALSE; + Decoder->u.Utf32.Input = CodeUnit; + Decoder->InputCount = 1; + return TRUE; + default: + PhRaiseStatus(STATUS_UNSUCCESSFUL); + } +} + +BOOLEAN PhpReadUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _Out_ PULONG CodeUnit + ) +{ + switch (Decoder->Encoding) + { + case PH_UNICODE_UTF8: + if (Decoder->InputCount == 0) + return FALSE; + *CodeUnit = Decoder->u.Utf8.Input[0]; + Decoder->u.Utf8.Input[0] = Decoder->u.Utf8.Input[1]; + Decoder->u.Utf8.Input[1] = Decoder->u.Utf8.Input[2]; + Decoder->u.Utf8.Input[2] = Decoder->u.Utf8.Input[3]; + Decoder->InputCount--; + return TRUE; + case PH_UNICODE_UTF16: + if (Decoder->InputCount == 0) + return FALSE; + *CodeUnit = Decoder->u.Utf16.Input[0]; + Decoder->u.Utf16.Input[0] = Decoder->u.Utf16.Input[1]; + Decoder->InputCount--; + return TRUE; + case PH_UNICODE_UTF32: + if (Decoder->InputCount == 0) + return FALSE; + *CodeUnit = Decoder->u.Utf32.Input; + Decoder->InputCount--; + return TRUE; + default: + PhRaiseStatus(STATUS_UNSUCCESSFUL); + } +} + +VOID PhpUnreadUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _In_ ULONG CodeUnit + ) +{ + switch (Decoder->Encoding) + { + case PH_UNICODE_UTF8: + if (Decoder->InputCount >= 4) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + Decoder->u.Utf8.Input[3] = Decoder->u.Utf8.Input[2]; + Decoder->u.Utf8.Input[2] = Decoder->u.Utf8.Input[1]; + Decoder->u.Utf8.Input[1] = Decoder->u.Utf8.Input[0]; + Decoder->u.Utf8.Input[0] = (UCHAR)CodeUnit; + Decoder->InputCount++; + break; + case PH_UNICODE_UTF16: + if (Decoder->InputCount >= 2) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + Decoder->u.Utf16.Input[1] = Decoder->u.Utf16.Input[0]; + Decoder->u.Utf16.Input[0] = (USHORT)CodeUnit; + Decoder->InputCount++; + break; + case PH_UNICODE_UTF32: + if (Decoder->InputCount >= 1) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + Decoder->u.Utf32.Input = CodeUnit; + Decoder->InputCount = 1; + break; + default: + PhRaiseStatus(STATUS_UNSUCCESSFUL); + } +} + +BOOLEAN PhpDecodeUtf8Error( + _Inout_ PPH_UNICODE_DECODER Decoder, + _Out_ PULONG CodePoint, + _In_ ULONG Which + ) +{ + if (Which >= 4) + PhpUnreadUnicodeDecoder(Decoder, Decoder->u.Utf8.CodeUnit4); + if (Which >= 3) + PhpUnreadUnicodeDecoder(Decoder, Decoder->u.Utf8.CodeUnit3); + if (Which >= 2) + PhpUnreadUnicodeDecoder(Decoder, Decoder->u.Utf8.CodeUnit2); + + *CodePoint = (ULONG)Decoder->u.Utf8.CodeUnit1 + 0xdc00; + Decoder->State = 0; + + return TRUE; +} + +BOOLEAN PhDecodeUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _Out_ PULONG CodePoint + ) +{ + ULONG codeUnit; + + while (TRUE) + { + switch (Decoder->Encoding) + { + case PH_UNICODE_UTF8: + if (!PhpReadUnicodeDecoder(Decoder, &codeUnit)) + return FALSE; + + switch (Decoder->State) + { + case 0: + Decoder->u.Utf8.CodeUnit1 = (UCHAR)codeUnit; + + if (codeUnit < 0x80) + { + *CodePoint = codeUnit; + return TRUE; + } + else if (codeUnit < 0xc2) + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 1); + } + else if (codeUnit < 0xe0) + { + Decoder->State = 1; // 2 byte sequence + continue; + } + else if (codeUnit < 0xf0) + { + Decoder->State = 2; // 3 byte sequence + continue; + } + else if (codeUnit < 0xf5) + { + Decoder->State = 3; // 4 byte sequence + continue; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 1); + } + + break; + case 1: // 2 byte sequence + Decoder->u.Utf8.CodeUnit2 = (UCHAR)codeUnit; + + if ((codeUnit & 0xc0) == 0x80) + { + *CodePoint = ((ULONG)Decoder->u.Utf8.CodeUnit1 << 6) + codeUnit - 0x3080; + Decoder->State = 0; + return TRUE; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 2); + } + + break; + case 2: // 3 byte sequence (1) + Decoder->u.Utf8.CodeUnit2 = (UCHAR)codeUnit; + + if (((codeUnit & 0xc0) == 0x80) && + (Decoder->u.Utf8.CodeUnit1 != 0xe0 || codeUnit >= 0xa0)) + { + Decoder->State = 4; // 3 byte sequence (2) + continue; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 2); + } + + break; + case 3: // 4 byte sequence (1) + Decoder->u.Utf8.CodeUnit2 = (UCHAR)codeUnit; + + if (((codeUnit & 0xc0) == 0x80) && + (Decoder->u.Utf8.CodeUnit1 != 0xf0 || codeUnit >= 0x90) && + (Decoder->u.Utf8.CodeUnit1 != 0xf4 || codeUnit < 0x90)) + { + Decoder->State = 5; // 4 byte sequence (2) + continue; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 2); + } + + break; + case 4: // 3 byte sequence (2) + Decoder->u.Utf8.CodeUnit3 = (UCHAR)codeUnit; + + if ((codeUnit & 0xc0) == 0x80) + { + *CodePoint = + ((ULONG)Decoder->u.Utf8.CodeUnit1 << 12) + + ((ULONG)Decoder->u.Utf8.CodeUnit2 << 6) + + codeUnit - 0xe2080; + Decoder->State = 0; + return TRUE; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 3); + } + + break; + case 5: // 4 byte sequence (2) + Decoder->u.Utf8.CodeUnit3 = (UCHAR)codeUnit; + + if ((codeUnit & 0xc0) == 0x80) + { + Decoder->State = 6; // 4 byte sequence (3) + continue; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 3); + } + + break; + case 6: // 4 byte sequence (3) + Decoder->u.Utf8.CodeUnit4 = (UCHAR)codeUnit; + + if ((codeUnit & 0xc0) == 0x80) + { + *CodePoint = + ((ULONG)Decoder->u.Utf8.CodeUnit1 << 18) + + ((ULONG)Decoder->u.Utf8.CodeUnit2 << 12) + + ((ULONG)Decoder->u.Utf8.CodeUnit3 << 6) + + codeUnit - 0x3c82080; + Decoder->State = 0; + return TRUE; + } + else + { + return PhpDecodeUtf8Error(Decoder, CodePoint, 4); + } + + break; + } + + return FALSE; + case PH_UNICODE_UTF16: + if (!PhpReadUnicodeDecoder(Decoder, &codeUnit)) + return FALSE; + + switch (Decoder->State) + { + case 0: + if (PH_UNICODE_UTF16_IS_HIGH_SURROGATE(codeUnit)) + { + Decoder->u.Utf16.CodeUnit = (USHORT)codeUnit; + Decoder->State = 1; + continue; + } + else + { + *CodePoint = codeUnit; + return TRUE; + } + break; + case 1: + if (PH_UNICODE_UTF16_IS_LOW_SURROGATE(codeUnit)) + { + *CodePoint = PH_UNICODE_UTF16_TO_CODE_POINT(Decoder->u.Utf16.CodeUnit, codeUnit); + Decoder->State = 0; + return TRUE; + } + else + { + *CodePoint = Decoder->u.Utf16.CodeUnit; + PhpUnreadUnicodeDecoder(Decoder, codeUnit); + Decoder->State = 0; + return TRUE; + } + break; + } + + return FALSE; + case PH_UNICODE_UTF32: + if (PhpReadUnicodeDecoder(Decoder, CodePoint)) + return TRUE; + return FALSE; + default: + return FALSE; + } + } +} + +BOOLEAN PhEncodeUnicode( + _In_ UCHAR Encoding, + _In_ ULONG CodePoint, + _Out_opt_ PVOID CodeUnits, + _Out_ PULONG NumberOfCodeUnits + ) +{ + switch (Encoding) + { + case PH_UNICODE_UTF8: + { + PUCHAR codeUnits = CodeUnits; + + if (CodePoint < 0x80) + { + *NumberOfCodeUnits = 1; + + if (codeUnits) + codeUnits[0] = (UCHAR)CodePoint; + } + else if (CodePoint <= 0x7ff) + { + *NumberOfCodeUnits = 2; + + if (codeUnits) + { + codeUnits[0] = (UCHAR)(CodePoint >> 6) + 0xc0; + codeUnits[1] = (UCHAR)(CodePoint & 0x3f) + 0x80; + } + } + else if (CodePoint <= 0xffff) + { + *NumberOfCodeUnits = 3; + + if (codeUnits) + { + codeUnits[0] = (UCHAR)(CodePoint >> 12) + 0xe0; + codeUnits[1] = (UCHAR)((CodePoint >> 6) & 0x3f) + 0x80; + codeUnits[2] = (UCHAR)(CodePoint & 0x3f) + 0x80; + } + } + else if (CodePoint <= PH_UNICODE_MAX_CODE_POINT) + { + *NumberOfCodeUnits = 4; + + if (codeUnits) + { + codeUnits[0] = (UCHAR)(CodePoint >> 18) + 0xf0; + codeUnits[1] = (UCHAR)((CodePoint >> 12) & 0x3f) + 0x80; + codeUnits[2] = (UCHAR)((CodePoint >> 6) & 0x3f) + 0x80; + codeUnits[3] = (UCHAR)(CodePoint & 0x3f) + 0x80; + } + } + else + { + return FALSE; + } + } + return TRUE; + case PH_UNICODE_UTF16: + { + PUSHORT codeUnits = CodeUnits; + + if (CodePoint < 0x10000) + { + *NumberOfCodeUnits = 1; + + if (codeUnits) + codeUnits[0] = (USHORT)CodePoint; + } + else if (CodePoint <= PH_UNICODE_MAX_CODE_POINT) + { + *NumberOfCodeUnits = 2; + + if (codeUnits) + { + codeUnits[0] = PH_UNICODE_UTF16_TO_HIGH_SURROGATE(CodePoint); + codeUnits[1] = PH_UNICODE_UTF16_TO_LOW_SURROGATE(CodePoint); + } + } + else + { + return FALSE; + } + } + return TRUE; + case PH_UNICODE_UTF32: + *NumberOfCodeUnits = 1; + if (CodeUnits) + *(PULONG)CodeUnits = CodePoint; + return TRUE; + default: + return FALSE; + } +} + +/** + * Converts an ASCII string to a UTF-16 string by zero-extending each byte. + * + * \param Input The original ASCII string. + * \param InputLength The length of \a Input. + * \param Output A buffer which will contain the converted string. + */ +VOID PhZeroExtendToUtf16Buffer( + _In_reads_bytes_(InputLength) PCH Input, + _In_ SIZE_T InputLength, + _Out_writes_bytes_(InputLength * sizeof(WCHAR)) PWCH Output + ) +{ + SIZE_T inputLength; + + inputLength = InputLength & -4; + + if (inputLength) + { + do + { + Output[0] = C_1uTo2(Input[0]); + Output[1] = C_1uTo2(Input[1]); + Output[2] = C_1uTo2(Input[2]); + Output[3] = C_1uTo2(Input[3]); + Input += 4; + Output += 4; + inputLength -= 4; + } while (inputLength != 0); + } + + switch (InputLength & 3) + { + case 3: + *Output++ = C_1uTo2(*Input++); + case 2: + *Output++ = C_1uTo2(*Input++); + case 1: + *Output++ = C_1uTo2(*Input++); + } +} + +PPH_STRING PhZeroExtendToUtf16Ex( + _In_reads_bytes_(InputLength) PCH Input, + _In_ SIZE_T InputLength + ) +{ + PPH_STRING string; + + string = PhCreateStringEx(NULL, InputLength * sizeof(WCHAR)); + PhZeroExtendToUtf16Buffer(Input, InputLength, string->Buffer); + + return string; +} + +PPH_BYTES PhConvertUtf16ToAsciiEx( + _In_ PWCH Buffer, + _In_ SIZE_T Length, + _In_opt_ CHAR Replacement + ) +{ + PPH_BYTES bytes; + PH_UNICODE_DECODER decoder; + PWCH in; + SIZE_T inRemaining; + PCH out; + SIZE_T outLength; + ULONG codePoint; + + bytes = PhCreateBytesEx(NULL, Length / sizeof(WCHAR)); + PhInitializeUnicodeDecoder(&decoder, PH_UNICODE_UTF16); + in = Buffer; + inRemaining = Length / sizeof(WCHAR); + out = bytes->Buffer; + outLength = 0; + + while (inRemaining != 0) + { + PhWriteUnicodeDecoder(&decoder, (USHORT)*in); + in++; + inRemaining--; + + while (PhDecodeUnicodeDecoder(&decoder, &codePoint)) + { + if (codePoint < 0x80) + { + *out++ = (CHAR)codePoint; + outLength++; + } + else if (Replacement) + { + *out++ = Replacement; + outLength++; + } + } + } + + bytes->Length = outLength; + bytes->Buffer[outLength] = 0; + + return bytes; +} + +/** + * Creates a string object from an existing null-terminated multi-byte string. + * + * \param Buffer A null-terminated multi-byte string. + */ +PPH_STRING PhConvertMultiByteToUtf16( + _In_ PSTR Buffer + ) +{ + return PhConvertMultiByteToUtf16Ex( + Buffer, + strlen(Buffer) + ); +} + +/** + * Creates a string object from an existing null-terminated multi-byte string. + * + * \param Buffer A null-terminated multi-byte string. + * \param Length The number of bytes to use. + */ +PPH_STRING PhConvertMultiByteToUtf16Ex( + _In_ PCHAR Buffer, + _In_ SIZE_T Length + ) +{ + NTSTATUS status; + PPH_STRING string; + ULONG unicodeBytes; + + status = RtlMultiByteToUnicodeSize( + &unicodeBytes, + Buffer, + (ULONG)Length + ); + + if (!NT_SUCCESS(status)) + return NULL; + + string = PhCreateStringEx(NULL, unicodeBytes); + status = RtlMultiByteToUnicodeN( + string->Buffer, + (ULONG)string->Length, + NULL, + Buffer, + (ULONG)Length + ); + + if (!NT_SUCCESS(status)) + { + PhDereferenceObject(string); + return NULL; + } + + return string; +} + +/** + * Creates a multi-byte string from an existing null-terminated UTF-16 string. + * + * \param Buffer A null-terminated UTF-16 string. + */ +PPH_BYTES PhConvertUtf16ToMultiByte( + _In_ PWSTR Buffer + ) +{ + return PhConvertUtf16ToMultiByteEx( + Buffer, + PhCountStringZ(Buffer) * sizeof(WCHAR) + ); +} + +/** + * Creates a multi-byte string from an existing null-terminated UTF-16 string. + * + * \param Buffer A null-terminated UTF-16 string. + * \param Length The number of bytes to use. + */ +PPH_BYTES PhConvertUtf16ToMultiByteEx( + _In_ PWCHAR Buffer, + _In_ SIZE_T Length + ) +{ + NTSTATUS status; + PPH_BYTES bytes; + ULONG multiByteLength; + + status = RtlUnicodeToMultiByteSize( + &multiByteLength, + Buffer, + (ULONG)Length + ); + + if (!NT_SUCCESS(status)) + return NULL; + + bytes = PhCreateBytesEx(NULL, multiByteLength); + status = RtlUnicodeToMultiByteN( + bytes->Buffer, + (ULONG)bytes->Length, + NULL, + Buffer, + (ULONG)Length + ); + + if (!NT_SUCCESS(status)) + { + PhDereferenceObject(bytes); + return NULL; + } + + return bytes; +} + +BOOLEAN PhConvertUtf8ToUtf16Size( + _Out_ PSIZE_T BytesInUtf16String, + _In_reads_bytes_(BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T BytesInUtf8String + ) +{ + BOOLEAN result; + PH_UNICODE_DECODER decoder; + PCH in; + SIZE_T inRemaining; + SIZE_T bytesInUtf16String; + ULONG codePoint; + ULONG numberOfCodeUnits; + + result = TRUE; + PhInitializeUnicodeDecoder(&decoder, PH_UNICODE_UTF8); + in = Utf8String; + inRemaining = BytesInUtf8String; + bytesInUtf16String = 0; + + while (inRemaining != 0) + { + PhWriteUnicodeDecoder(&decoder, (UCHAR)*in); + in++; + inRemaining--; + + while (PhDecodeUnicodeDecoder(&decoder, &codePoint)) + { + if (PhEncodeUnicode(PH_UNICODE_UTF16, codePoint, NULL, &numberOfCodeUnits)) + bytesInUtf16String += numberOfCodeUnits * sizeof(WCHAR); + else + result = FALSE; + } + } + + *BytesInUtf16String = bytesInUtf16String; + + return result; +} + +BOOLEAN PhConvertUtf8ToUtf16Buffer( + _Out_writes_bytes_to_(MaxBytesInUtf16String, *BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T MaxBytesInUtf16String, + _Out_opt_ PSIZE_T BytesInUtf16String, + _In_reads_bytes_(BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T BytesInUtf8String + ) +{ + BOOLEAN result; + PH_UNICODE_DECODER decoder; + PCH in; + SIZE_T inRemaining; + PWCH out; + SIZE_T outRemaining; + SIZE_T bytesInUtf16String; + ULONG codePoint; + USHORT codeUnits[2]; + ULONG numberOfCodeUnits; + + result = TRUE; + PhInitializeUnicodeDecoder(&decoder, PH_UNICODE_UTF8); + in = Utf8String; + inRemaining = BytesInUtf8String; + out = Utf16String; + outRemaining = MaxBytesInUtf16String / sizeof(WCHAR); + bytesInUtf16String = 0; + + while (inRemaining != 0) + { + PhWriteUnicodeDecoder(&decoder, (UCHAR)*in); + in++; + inRemaining--; + + while (PhDecodeUnicodeDecoder(&decoder, &codePoint)) + { + if (PhEncodeUnicode(PH_UNICODE_UTF16, codePoint, codeUnits, &numberOfCodeUnits)) + { + bytesInUtf16String += numberOfCodeUnits * sizeof(WCHAR); + + if (outRemaining >= numberOfCodeUnits) + { + *out++ = codeUnits[0]; + + if (numberOfCodeUnits >= 2) + *out++ = codeUnits[1]; + + outRemaining -= numberOfCodeUnits; + } + else + { + result = FALSE; + } + } + else + { + result = FALSE; + } + } + } + + if (BytesInUtf16String) + *BytesInUtf16String = bytesInUtf16String; + + return result; +} + +PPH_STRING PhConvertUtf8ToUtf16( + _In_ PSTR Buffer + ) +{ + return PhConvertUtf8ToUtf16Ex( + Buffer, + strlen(Buffer) + ); +} + +PPH_STRING PhConvertUtf8ToUtf16Ex( + _In_ PCHAR Buffer, + _In_ SIZE_T Length + ) +{ + PPH_STRING string; + SIZE_T utf16Bytes; + + if (!PhConvertUtf8ToUtf16Size( + &utf16Bytes, + Buffer, + Length + )) + { + return NULL; + } + + string = PhCreateStringEx(NULL, utf16Bytes); + + if (!PhConvertUtf8ToUtf16Buffer( + string->Buffer, + string->Length, + NULL, + Buffer, + Length + )) + { + PhDereferenceObject(string); + return NULL; + } + + return string; +} + +BOOLEAN PhConvertUtf16ToUtf8Size( + _Out_ PSIZE_T BytesInUtf8String, + _In_reads_bytes_(BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T BytesInUtf16String + ) +{ + BOOLEAN result; + PH_UNICODE_DECODER decoder; + PWCH in; + SIZE_T inRemaining; + SIZE_T bytesInUtf8String; + ULONG codePoint; + ULONG numberOfCodeUnits; + + result = TRUE; + PhInitializeUnicodeDecoder(&decoder, PH_UNICODE_UTF16); + in = Utf16String; + inRemaining = BytesInUtf16String / sizeof(WCHAR); + bytesInUtf8String = 0; + + while (inRemaining != 0) + { + PhWriteUnicodeDecoder(&decoder, (USHORT)*in); + in++; + inRemaining--; + + while (PhDecodeUnicodeDecoder(&decoder, &codePoint)) + { + if (PhEncodeUnicode(PH_UNICODE_UTF8, codePoint, NULL, &numberOfCodeUnits)) + bytesInUtf8String += numberOfCodeUnits; + else + result = FALSE; + } + } + + *BytesInUtf8String = bytesInUtf8String; + + return result; +} + +BOOLEAN PhConvertUtf16ToUtf8Buffer( + _Out_writes_bytes_to_(MaxBytesInUtf8String, *BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T MaxBytesInUtf8String, + _Out_opt_ PSIZE_T BytesInUtf8String, + _In_reads_bytes_(BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T BytesInUtf16String + ) +{ + BOOLEAN result; + PH_UNICODE_DECODER decoder; + PWCH in; + SIZE_T inRemaining; + PCH out; + SIZE_T outRemaining; + SIZE_T bytesInUtf8String; + ULONG codePoint; + UCHAR codeUnits[4]; + ULONG numberOfCodeUnits; + + result = TRUE; + PhInitializeUnicodeDecoder(&decoder, PH_UNICODE_UTF16); + in = Utf16String; + inRemaining = BytesInUtf16String / sizeof(WCHAR); + out = Utf8String; + outRemaining = MaxBytesInUtf8String; + bytesInUtf8String = 0; + + while (inRemaining != 0) + { + PhWriteUnicodeDecoder(&decoder, (USHORT)*in); + in++; + inRemaining--; + + while (PhDecodeUnicodeDecoder(&decoder, &codePoint)) + { + if (PhEncodeUnicode(PH_UNICODE_UTF8, codePoint, codeUnits, &numberOfCodeUnits)) + { + bytesInUtf8String += numberOfCodeUnits; + + if (outRemaining >= numberOfCodeUnits) + { + *out++ = codeUnits[0]; + + if (numberOfCodeUnits >= 2) + *out++ = codeUnits[1]; + if (numberOfCodeUnits >= 3) + *out++ = codeUnits[2]; + if (numberOfCodeUnits >= 4) + *out++ = codeUnits[3]; + + outRemaining -= numberOfCodeUnits; + } + else + { + result = FALSE; + } + } + else + { + result = FALSE; + } + } + } + + if (BytesInUtf8String) + *BytesInUtf8String = bytesInUtf8String; + + return result; +} + +PPH_BYTES PhConvertUtf16ToUtf8( + _In_ PWSTR Buffer + ) +{ + return PhConvertUtf16ToUtf8Ex( + Buffer, + PhCountStringZ(Buffer) * sizeof(WCHAR) + ); +} + +PPH_BYTES PhConvertUtf16ToUtf8Ex( + _In_ PWCHAR Buffer, + _In_ SIZE_T Length + ) +{ + PPH_BYTES bytes; + SIZE_T utf8Bytes; + + if (!PhConvertUtf16ToUtf8Size( + &utf8Bytes, + Buffer, + Length + )) + { + return NULL; + } + + bytes = PhCreateBytesEx(NULL, utf8Bytes); + + if (!PhConvertUtf16ToUtf8Buffer( + bytes->Buffer, + bytes->Length, + NULL, + Buffer, + Length + )) + { + PhDereferenceObject(bytes); + return NULL; + } + + return bytes; +} + +/** + * Initializes a string builder object. + * + * \param StringBuilder A string builder object. + * \param InitialCapacity The number of bytes to allocate initially. + */ +VOID PhInitializeStringBuilder( + _Out_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T InitialCapacity + ) +{ + // Make sure the initial capacity is even, as required for all string objects. + if (InitialCapacity & 1) + InitialCapacity++; + + StringBuilder->AllocatedLength = InitialCapacity; + + // Allocate a PH_STRING for the string builder. + // We will dereference it and allocate a new one when we need to resize the string. + + StringBuilder->String = PhCreateStringEx(NULL, StringBuilder->AllocatedLength); + + // We will keep modifying the Length field of the string so that: + // 1. We know how much of the string is used, and + // 2. The user can simply get a reference to the string and use it as-is. + + StringBuilder->String->Length = 0; + + // Write the null terminator. + StringBuilder->String->Buffer[0] = 0; + + PHLIB_INC_STATISTIC(BaseStringBuildersCreated); +} + +/** + * Frees resources used by a string builder object. + * + * \param StringBuilder A string builder object. + */ +VOID PhDeleteStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder + ) +{ + PhDereferenceObject(StringBuilder->String); +} + +/** + * Obtains a reference to the string constructed by a string builder object and frees resources used + * by the object. + * + * \param StringBuilder A string builder object. + * + * \return A pointer to a string. You must free the string using PhDereferenceObject() when you no + * longer need it. + */ +PPH_STRING PhFinalStringBuilderString( + _Inout_ PPH_STRING_BUILDER StringBuilder + ) +{ + return StringBuilder->String; +} + +VOID PhpResizeStringBuilder( + _In_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T NewCapacity + ) +{ + PPH_STRING newString; + + // Double the string size. If that still isn't enough room, just use the new length. + + StringBuilder->AllocatedLength *= 2; + + if (StringBuilder->AllocatedLength < NewCapacity) + StringBuilder->AllocatedLength = NewCapacity; + + // Allocate a new string. + newString = PhCreateStringEx(NULL, StringBuilder->AllocatedLength); + + // Copy the old string to the new string. + memcpy( + newString->Buffer, + StringBuilder->String->Buffer, + StringBuilder->String->Length + sizeof(WCHAR) // Include null terminator + ); + + // Copy the old string length. + newString->Length = StringBuilder->String->Length; + + // Dereference the old string and replace it with the new string. + PhMoveReference(&StringBuilder->String, newString); + + PHLIB_INC_STATISTIC(BaseStringBuildersResized); +} + +FORCEINLINE VOID PhpWriteNullTerminatorStringBuilder( + _In_ PPH_STRING_BUILDER StringBuilder + ) +{ + assert(!(StringBuilder->String->Length & 1)); + *(PWCHAR)((PCHAR)StringBuilder->String->Buffer + StringBuilder->String->Length) = 0; +} + +/** + * Appends a string to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param String The string to append. + */ +VOID PhAppendStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PPH_STRINGREF String + ) +{ + PhAppendStringBuilderEx( + StringBuilder, + String->Buffer, + String->Length + ); +} + +/** + * Appends a string to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param String The string to append. + */ +VOID PhAppendStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PWSTR String + ) +{ + PhAppendStringBuilderEx( + StringBuilder, + String, + PhCountStringZ(String) * sizeof(WCHAR) + ); +} + +/** + * Appends a string to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param String The string to append. Specify NULL to simply reserve \a Length bytes. + * \param Length The number of bytes to append. + */ +VOID PhAppendStringBuilderEx( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_opt_ PWCHAR String, + _In_ SIZE_T Length + ) +{ + if (Length == 0) + return; + + // See if we need to re-allocate the string. + if (StringBuilder->AllocatedLength < StringBuilder->String->Length + Length) + { + PhpResizeStringBuilder(StringBuilder, StringBuilder->String->Length + Length); + } + + // Copy the string, add the length, then write the null terminator. + + if (String) + { + memcpy( + (PCHAR)StringBuilder->String->Buffer + StringBuilder->String->Length, + String, + Length + ); + } + + StringBuilder->String->Length += Length; + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Appends a character to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param Character The character to append. + */ +VOID PhAppendCharStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ WCHAR Character + ) +{ + if (StringBuilder->AllocatedLength < StringBuilder->String->Length + sizeof(WCHAR)) + { + PhpResizeStringBuilder(StringBuilder, StringBuilder->String->Length + sizeof(WCHAR)); + } + + *(PWCHAR)((PCHAR)StringBuilder->String->Buffer + StringBuilder->String->Length) = Character; + StringBuilder->String->Length += sizeof(WCHAR); + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Appends a number of characters to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param Character The character to append. + * \param Count The number of times to append the character. + */ +VOID PhAppendCharStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ WCHAR Character, + _In_ SIZE_T Count + ) +{ + if (Count == 0) + return; + + // See if we need to re-allocate the string. + if (StringBuilder->AllocatedLength < StringBuilder->String->Length + Count * sizeof(WCHAR)) + { + PhpResizeStringBuilder(StringBuilder, StringBuilder->String->Length + Count * sizeof(WCHAR)); + } + + wmemset( + (PWCHAR)((PCHAR)StringBuilder->String->Buffer + StringBuilder->String->Length), + Character, + Count + ); + + StringBuilder->String->Length += Count * sizeof(WCHAR); + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Appends a formatted string to the end of a string builder string. + * + * \param StringBuilder A string builder object. + * \param Format The format-control string. + */ +VOID PhAppendFormatStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ _Printf_format_string_ PWSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + PhAppendFormatStringBuilder_V(StringBuilder, Format, argptr); +} + +VOID PhAppendFormatStringBuilder_V( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ) +{ + int length; + SIZE_T lengthInBytes; + + length = _vscwprintf(Format, ArgPtr); + + if (length == -1 || length == 0) + return; + + lengthInBytes = length * sizeof(WCHAR); + + if (StringBuilder->AllocatedLength < StringBuilder->String->Length + lengthInBytes) + PhpResizeStringBuilder(StringBuilder, StringBuilder->String->Length + lengthInBytes); + + _vsnwprintf( + (PWCHAR)((PCHAR)StringBuilder->String->Buffer + StringBuilder->String->Length), + length, + Format, + ArgPtr + ); + + StringBuilder->String->Length += lengthInBytes; + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Inserts a string into a string builder string. + * + * \param StringBuilder A string builder object. + * \param Index The index, in characters, at which to insert the string. + * \param String The string to insert. + */ +VOID PhInsertStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_ PPH_STRINGREF String + ) +{ + PhInsertStringBuilderEx( + StringBuilder, + Index, + String->Buffer, + String->Length + ); +} + +/** + * Inserts a string into a string builder string. + * + * \param StringBuilder A string builder object. + * \param Index The index, in characters, at which to insert the string. + * \param String The string to insert. + */ +VOID PhInsertStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_ PWSTR String + ) +{ + PhInsertStringBuilderEx( + StringBuilder, + Index, + String, + PhCountStringZ(String) * sizeof(WCHAR) + ); +} + +/** + * Inserts a string into a string builder string. + * + * \param StringBuilder A string builder object. + * \param Index The index, in characters, at which to insert the string. + * \param String The string to insert. Specify NULL to simply reserve \a Length bytes at \a Index. + * \param Length The number of bytes to insert. + */ +VOID PhInsertStringBuilderEx( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_opt_ PWCHAR String, + _In_ SIZE_T Length + ) +{ + if (Length == 0) + return; + + // See if we need to re-allocate the string. + if (StringBuilder->AllocatedLength < StringBuilder->String->Length + Length) + { + PhpResizeStringBuilder(StringBuilder, StringBuilder->String->Length + Length); + } + + if (Index * sizeof(WCHAR) < StringBuilder->String->Length) + { + // Create some space for the string. + memmove( + &StringBuilder->String->Buffer[Index + Length / sizeof(WCHAR)], + &StringBuilder->String->Buffer[Index], + StringBuilder->String->Length - Index * sizeof(WCHAR) + ); + } + + if (String) + { + // Copy the new string. + memcpy( + &StringBuilder->String->Buffer[Index], + String, + Length + ); + } + + StringBuilder->String->Length += Length; + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Removes characters from a string builder string. + * + * \param StringBuilder A string builder object. + * \param StartIndex The index, in characters, at which to begin removing characters. + * \param Count The number of characters to remove. + */ +VOID PhRemoveStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ) +{ + // Overwrite the removed part with the part + // behind it. + + memmove( + &StringBuilder->String->Buffer[StartIndex], + &StringBuilder->String->Buffer[StartIndex + Count], + StringBuilder->String->Length - (Count + StartIndex) * sizeof(WCHAR) + ); + StringBuilder->String->Length -= Count * sizeof(WCHAR); + PhpWriteNullTerminatorStringBuilder(StringBuilder); +} + +/** + * Initializes a byte string builder object. + * + * \param BytesBuilder A byte string builder object. + * \param InitialCapacity The number of bytes to allocate initially. + */ +VOID PhInitializeBytesBuilder( + _Out_ PPH_BYTES_BUILDER BytesBuilder, + _In_ SIZE_T InitialCapacity + ) +{ + BytesBuilder->AllocatedLength = InitialCapacity; + BytesBuilder->Bytes = PhCreateBytesEx(NULL, BytesBuilder->AllocatedLength); + BytesBuilder->Bytes->Length = 0; + BytesBuilder->Bytes->Buffer[0] = 0; +} + +/** + * Frees resources used by a byte string builder object. + * + * \param BytesBuilder A byte string builder object. + */ +VOID PhDeleteBytesBuilder( + _Inout_ PPH_BYTES_BUILDER BytesBuilder + ) +{ + PhDereferenceObject(BytesBuilder->Bytes); +} + +/** + * Obtains a reference to the byte string constructed by a byte string builder object and frees + * resources used by the object. + * + * \param BytesBuilder A byte string builder object. + * + * \return A pointer to a byte string. You must free the byte string using PhDereferenceObject() + * when you no longer need it. + */ +PPH_BYTES PhFinalBytesBuilderBytes( + _Inout_ PPH_BYTES_BUILDER BytesBuilder + ) +{ + return BytesBuilder->Bytes; +} + +VOID PhpResizeBytesBuilder( + _In_ PPH_BYTES_BUILDER BytesBuilder, + _In_ SIZE_T NewCapacity + ) +{ + PPH_BYTES newBytes; + + // Double the byte string size. If that still isn't enough room, just use the new length. + + BytesBuilder->AllocatedLength *= 2; + + if (BytesBuilder->AllocatedLength < NewCapacity) + BytesBuilder->AllocatedLength = NewCapacity; + + // Allocate a new byte string. + newBytes = PhCreateBytesEx(NULL, BytesBuilder->AllocatedLength); + + // Copy the old byte string to the new byte string. + memcpy( + newBytes->Buffer, + BytesBuilder->Bytes->Buffer, + BytesBuilder->Bytes->Length + sizeof(CHAR) // Include null terminator + ); + + // Copy the old byte string length. + newBytes->Length = BytesBuilder->Bytes->Length; + + // Dereference the old byte string and replace it with the new byte string. + PhMoveReference(&BytesBuilder->Bytes, newBytes); +} + +FORCEINLINE VOID PhpWriteNullTerminatorBytesBuilder( + _In_ PPH_BYTES_BUILDER BytesBuilder + ) +{ + BytesBuilder->Bytes->Buffer[BytesBuilder->Bytes->Length] = 0; +} + +/** + * Appends a byte string to the end of a byte string builder string. + * + * \param BytesBuilder A byte string builder object. + * \param Bytes The byte string to append. + */ +VOID PhAppendBytesBuilder( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_ PPH_BYTESREF Bytes + ) +{ + PhAppendBytesBuilderEx( + BytesBuilder, + Bytes->Buffer, + Bytes->Length, + 0, + NULL + ); +} + +/** + * Appends a byte string to the end of a byte string builder string. + * + * \param BytesBuilder A byte string builder object. + * \param Bytes The byte string to append. + */ +VOID PhAppendBytesBuilder2( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_ PCHAR Bytes + ) +{ + PhAppendBytesBuilderEx( + BytesBuilder, + Bytes, + strlen(Bytes), + 0, + NULL + ); +} + +/** + * Appends a byte string to the end of a byte string builder string. + * + * \param BytesBuilder A byte string builder object. + * \param Buffer The byte string to append. Specify NULL to simply reserve \a Length bytes. + * \param Length The number of bytes to append. + * \param Alignment The required alignment. This should not be greater than 8. + * \param Offset A variable which receives the byte offset of the appended or reserved bytes in the + * byte string builder string. + * + * \return A pointer to the appended or reserved bytes. + */ +PVOID PhAppendBytesBuilderEx( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_opt_ PVOID Buffer, + _In_ SIZE_T Length, + _In_opt_ SIZE_T Alignment, + _Out_opt_ PSIZE_T Offset + ) +{ + SIZE_T currentLength; + + currentLength = BytesBuilder->Bytes->Length; + + if (Length == 0) + goto Done; + + if (Alignment) + currentLength = ALIGN_UP_BY(currentLength, Alignment); + + // See if we need to re-allocate the byte string. + if (BytesBuilder->AllocatedLength < currentLength + Length) + PhpResizeBytesBuilder(BytesBuilder, currentLength + Length); + + // Copy the byte string, add the length, then write the null terminator. + + if (Buffer) + memcpy(BytesBuilder->Bytes->Buffer + currentLength, Buffer, Length); + + BytesBuilder->Bytes->Length = currentLength + Length; + PhpWriteNullTerminatorBytesBuilder(BytesBuilder); + +Done: + if (Offset) + *Offset = currentLength; + + return BytesBuilder->Bytes->Buffer + currentLength; +} + +/** + * Creates an array object. + * + * \param Array An array object. + * \param ItemSize The size of each item, in bytes. + * \param InitialCapacity The number of elements to allocate storage for, initially. + */ +VOID PhInitializeArray( + _Out_ PPH_ARRAY Array, + _In_ SIZE_T ItemSize, + _In_ SIZE_T InitialCapacity + ) +{ + // Initial capacity of 0 is not allowed. + if (InitialCapacity == 0) + InitialCapacity = 1; + + Array->Count = 0; + Array->AllocatedCount = InitialCapacity; + Array->ItemSize = ItemSize; + Array->Items = PhAllocate(Array->AllocatedCount * ItemSize); +} + +/** + * Frees resources used by an array object. + * + * \param Array An array object. + */ +VOID PhDeleteArray( + _Inout_ PPH_ARRAY Array + ) +{ + PhFree(Array->Items); +} + +/** + * Obtains a copy of the array constructed by an array object and frees resources used by the + * object. + * + * \param Array An array object. + * + * \return The array buffer. + */ +PVOID PhFinalArrayItems( + _Inout_ PPH_ARRAY Array + ) +{ + return Array->Items; +} + +/** + * Resizes an array. + * + * \param Array An array object. + * \param NewCapacity The new required number of elements for which storage has been reserved. This + * must not be smaller than the current number of items in the array. + */ +VOID PhResizeArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T NewCapacity + ) +{ + if (Array->Count > NewCapacity) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + Array->AllocatedCount = NewCapacity; + Array->Items = PhReAllocate(Array->Items, Array->AllocatedCount * Array->ItemSize); +} + +/** + * Adds an item to an array. + * + * \param Array An array object. + * \param Item The item to add. + */ +VOID PhAddItemArray( + _Inout_ PPH_ARRAY Array, + _In_ PVOID Item + ) +{ + // See if we need to resize the list. + if (Array->Count == Array->AllocatedCount) + { + Array->AllocatedCount *= 2; + Array->Items = PhReAllocate(Array->Items, Array->AllocatedCount * Array->ItemSize); + } + + memcpy(PhItemArray(Array, Array->Count), Item, Array->ItemSize); + Array->Count++; +} + +/** + * Adds items to an array. + * + * \param Array An array object. + * \param Items An array containing the items to add. + * \param Count The number of items to add. + */ +VOID PhAddItemsArray( + _Inout_ PPH_ARRAY Array, + _In_ PVOID Items, + _In_ SIZE_T Count + ) +{ + // See if we need to resize the list. + if (Array->AllocatedCount < Array->Count + Count) + { + Array->AllocatedCount *= 2; + + if (Array->AllocatedCount < Array->Count + Count) + Array->AllocatedCount = Array->Count + Count; + + Array->Items = PhReAllocate(Array->Items, Array->AllocatedCount * Array->ItemSize); + } + + memcpy( + PhItemArray(Array, Array->Count), + Items, + Count * Array->ItemSize + ); + Array->Count += Count; +} + +/** + * Clears an array. + * + * \param Array An array object. + */ +VOID PhClearArray( + _Inout_ PPH_ARRAY Array + ) +{ + Array->Count = 0; +} + +/** + * Removes an item from an array. + * + * \param Array An array object. + * \param Index The index of the item. + */ +VOID PhRemoveItemArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T Index + ) +{ + PhRemoveItemsArray(Array, Index, 1); +} + +/** + * Removes items from an array. + * + * \param Array An array object. + * \param StartIndex The index at which to begin removing items. + * \param Count The number of items to remove. + */ +VOID PhRemoveItemsArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ) +{ + // Shift the items after the items forward. + memmove( + PhItemArray(Array, StartIndex), + PhItemArray(Array, StartIndex + Count), + (Array->Count - StartIndex - Count) * Array->ItemSize + ); + Array->Count -= Count; +} + +/** + * Creates a list object. + * + * \param InitialCapacity The number of elements to allocate storage for, initially. + */ +PPH_LIST PhCreateList( + _In_ ULONG InitialCapacity + ) +{ + PPH_LIST list; + + list = PhCreateObject(sizeof(PH_LIST), PhListType); + + // Initial capacity of 0 is not allowed. + if (InitialCapacity == 0) + InitialCapacity = 1; + + list->Count = 0; + list->AllocatedCount = InitialCapacity; + list->Items = PhAllocate(list->AllocatedCount * sizeof(PVOID)); + + return list; +} + +VOID PhpListDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_LIST list = (PPH_LIST)Object; + + PhFree(list->Items); +} + +/** + * Resizes a list. + * + * \param List A list object. + * \param NewCapacity The new required number of elements for which storage has been reserved. This + * must not be smaller than the current number of items in the list. + */ +VOID PhResizeList( + _Inout_ PPH_LIST List, + _In_ ULONG NewCapacity + ) +{ + if (List->Count > NewCapacity) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + List->AllocatedCount = NewCapacity; + List->Items = PhReAllocate(List->Items, List->AllocatedCount * sizeof(PVOID)); +} + +/** + * Adds an item to a list. + * + * \param List A list object. + * \param Item The item to add. + */ +VOID PhAddItemList( + _Inout_ PPH_LIST List, + _In_ PVOID Item + ) +{ + // See if we need to resize the list. + if (List->Count == List->AllocatedCount) + { + List->AllocatedCount *= 2; + List->Items = PhReAllocate(List->Items, List->AllocatedCount * sizeof(PVOID)); + } + + List->Items[List->Count++] = Item; +} + +/** + * Adds items to a list. + * + * \param List A list object. + * \param Items An array containing the items to add. + * \param Count The number of items to add. + */ +VOID PhAddItemsList( + _Inout_ PPH_LIST List, + _In_ PVOID *Items, + _In_ ULONG Count + ) +{ + // See if we need to resize the list. + if (List->AllocatedCount < List->Count + Count) + { + List->AllocatedCount *= 2; + + if (List->AllocatedCount < List->Count + Count) + List->AllocatedCount = List->Count + Count; + + List->Items = PhReAllocate(List->Items, List->AllocatedCount * sizeof(PVOID)); + } + + memcpy( + &List->Items[List->Count], + Items, + Count * sizeof(PVOID) + ); + List->Count += Count; +} + +/** + * Clears a list. + * + * \param List A list object. + */ +VOID PhClearList( + _Inout_ PPH_LIST List + ) +{ + List->Count = 0; +} + +_Success_(return != -1) +/** + * Locates an item in a list. + * + * \param List A list object. + * \param Item The item to search for. + * + * \return The index of the item. If the + * item was not found, -1 is returned. + */ +ULONG PhFindItemList( + _In_ PPH_LIST List, + _In_ PVOID Item + ) +{ + ULONG i; + + for (i = 0; i < List->Count; i++) + { + if (List->Items[i] == Item) + return i; + } + + return -1; +} + +/** + * Inserts an item into a list. + * + * \param List A list object. + * \param Index The index at which to insert the item. + * \param Item The item to add. + */ +VOID PhInsertItemList( + _Inout_ PPH_LIST List, + _In_ ULONG Index, + _In_ PVOID Item + ) +{ + PhInsertItemsList(List, Index, &Item, 1); +} + +/** + * Inserts items into a list. + * + * \param List A list object. + * \param Index The index at which to insert the items. + * \param Items An array containing the items to add. + * \param Count The number of items to add. + */ +VOID PhInsertItemsList( + _Inout_ PPH_LIST List, + _In_ ULONG Index, + _In_ PVOID *Items, + _In_ ULONG Count + ) +{ + // See if we need to resize the list. + if (List->AllocatedCount < List->Count + Count) + { + List->AllocatedCount *= 2; + + if (List->AllocatedCount < List->Count + Count) + List->AllocatedCount = List->Count + Count; + + List->Items = PhReAllocate(List->Items, List->AllocatedCount * sizeof(PVOID)); + } + + if (Index < List->Count) + { + // Shift the existing items backward. + memmove( + &List->Items[Index + Count], + &List->Items[Index], + (List->Count - Index) * sizeof(PVOID) + ); + } + + // Copy the new items into the list. + memcpy( + &List->Items[Index], + Items, + Count * sizeof(PVOID) + ); + + List->Count += Count; +} + +/** + * Removes an item from a list. + * + * \param List A list object. + * \param Index The index of the item. + */ +VOID PhRemoveItemList( + _Inout_ PPH_LIST List, + _In_ ULONG Index + ) +{ + PhRemoveItemsList(List, Index, 1); +} + +/** + * Removes items from a list. + * + * \param List A list object. + * \param StartIndex The index at which to begin removing items. + * \param Count The number of items to remove. + */ +VOID PhRemoveItemsList( + _Inout_ PPH_LIST List, + _In_ ULONG StartIndex, + _In_ ULONG Count + ) +{ + // Shift the items after the items forward. + memmove( + &List->Items[StartIndex], + &List->Items[StartIndex + Count], + (List->Count - StartIndex - Count) * sizeof(PVOID) + ); + List->Count -= Count; +} + +/** + * Creates a pointer list object. + * + * \param InitialCapacity The number of elements to allocate storage for initially. + */ +PPH_POINTER_LIST PhCreatePointerList( + _In_ ULONG InitialCapacity + ) +{ + PPH_POINTER_LIST pointerList; + + pointerList = PhCreateObject(sizeof(PH_POINTER_LIST), PhPointerListType); + + // Initial capacity of 0 is not allowed. + if (InitialCapacity == 0) + InitialCapacity = 1; + + pointerList->Count = 0; + pointerList->AllocatedCount = InitialCapacity; + pointerList->FreeEntry = -1; + pointerList->NextEntry = 0; + pointerList->Items = PhAllocate(pointerList->AllocatedCount * sizeof(PVOID)); + + return pointerList; +} + +VOID NTAPI PhpPointerListDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_POINTER_LIST pointerList = (PPH_POINTER_LIST)Object; + + PhFree(pointerList->Items); +} + +/** + * Decodes an index stored in a free entry. + */ +FORCEINLINE ULONG PhpDecodePointerListIndex( + _In_ PVOID Index + ) +{ + // At least with Microsoft's compiler, shift right on a signed value preserves the sign. This is + // important because we want decode(encode(-1)) = ((-1 << 1) | 1) >> 1 = -1. + return (ULONG)((LONG_PTR)Index >> 1); +} + +/** + * Encodes an index for storage in a free entry. + */ +FORCEINLINE PVOID PhpEncodePointerListIndex( + _In_ ULONG Index + ) +{ + return (PVOID)(((ULONG_PTR)Index << 1) | 0x1); +} + +FORCEINLINE HANDLE PhpPointerListIndexToHandle( + _In_ ULONG Index + ) +{ + // Add one to allow NULL handles to indicate failure/an invalid index. + return UlongToHandle(Index + 1); +} + +FORCEINLINE ULONG PhpPointerListHandleToIndex( + _In_ HANDLE Handle + ) +{ + return HandleToUlong(Handle) - 1; +} + +/** + * Adds a pointer to a pointer list. + * + * \param PointerList A pointer list object. + * \param Pointer The pointer to add. The pointer must be at least 2 byte aligned. + * + * \return A handle to the pointer, valid until the pointer is removed from the pointer list. + */ +HANDLE PhAddItemPointerList( + _Inout_ PPH_POINTER_LIST PointerList, + _In_ PVOID Pointer + ) +{ + ULONG index; + + assert(PH_IS_LIST_POINTER_VALID(Pointer)); + + // Use a free entry if possible. + if (PointerList->FreeEntry != -1) + { + PVOID oldPointer; + + index = PointerList->FreeEntry; + oldPointer = PointerList->Items[index]; + PointerList->Items[index] = Pointer; + PointerList->FreeEntry = PhpDecodePointerListIndex(oldPointer); + } + else + { + // Use the next entry. + if (PointerList->NextEntry == PointerList->AllocatedCount) + { + PointerList->AllocatedCount *= 2; + PointerList->Items = PhReAllocate(PointerList->Items, PointerList->AllocatedCount * sizeof(PVOID)); + } + + index = PointerList->NextEntry++; + PointerList->Items[index] = Pointer; + } + + PointerList->Count++; + + return PhpPointerListIndexToHandle(index); +} + +BOOLEAN PhEnumPointerListEx( + _In_ PPH_POINTER_LIST PointerList, + _Inout_ PULONG EnumerationKey, + _Out_ PVOID *Pointer, + _Out_ PHANDLE PointerHandle + ) +{ + ULONG index; + + while ((index = *EnumerationKey) < PointerList->NextEntry) + { + PVOID pointer = PointerList->Items[index]; + + (*EnumerationKey)++; + + if (PH_IS_LIST_POINTER_VALID(pointer)) + { + *Pointer = pointer; + *PointerHandle = PhpPointerListIndexToHandle(index); + + return TRUE; + } + } + + return FALSE; +} + +/** + * Locates a pointer in a pointer list. + * + * \param PointerList A pointer list object. + * \param Pointer The pointer to find. The pointer must be at least 2 byte aligned. + * + * \return A handle to the pointer, valid until the pointer is removed from the pointer list. If the + * pointer is not contained in the pointer list, NULL is returned. + */ +HANDLE PhFindItemPointerList( + _In_ PPH_POINTER_LIST PointerList, + _In_ PVOID Pointer + ) +{ + ULONG i; + + assert(PH_IS_LIST_POINTER_VALID(Pointer)); + + for (i = 0; i < PointerList->NextEntry; i++) + { + if (PointerList->Items[i] == Pointer) + return PhpPointerListIndexToHandle(i); + } + + return NULL; +} + +/** + * Removes a pointer from a pointer list. + * + * \param PointerList A pointer list object. + * \param PointerHandle A handle to the pointer to remove. + * + * \remarks No checking is performed on the pointer handle. Make sure the handle is valid before + * calling the function. + */ +VOID PhRemoveItemPointerList( + _Inout_ PPH_POINTER_LIST PointerList, + _In_ HANDLE PointerHandle + ) +{ + ULONG index; + + assert(PointerHandle); + + index = PhpPointerListHandleToIndex(PointerHandle); + + PointerList->Items[index] = PhpEncodePointerListIndex(PointerList->FreeEntry); + PointerList->FreeEntry = index; + + PointerList->Count--; +} + +FORCEINLINE ULONG PhpValidateHash( + _In_ ULONG Hash + ) +{ + // No point in using a full hash when we're going to AND with size minus one anyway. +#if defined(PH_HASHTABLE_FULL_HASH) && !defined(PH_HASHTABLE_POWER_OF_TWO_SIZE) + if (Hash != -1) + return Hash; + else + return 0; +#else + return Hash & MAXLONG; +#endif +} + +FORCEINLINE ULONG PhpIndexFromHash( + _In_ PPH_HASHTABLE Hashtable, + _In_ ULONG Hash + ) +{ +#ifdef PH_HASHTABLE_POWER_OF_TWO_SIZE + return Hash & (Hashtable->AllocatedBuckets - 1); +#else + return Hash % Hashtable->AllocatedBuckets; +#endif +} + +FORCEINLINE ULONG PhpGetNumberOfBuckets( + _In_ ULONG Capacity + ) +{ +#ifdef PH_HASHTABLE_POWER_OF_TWO_SIZE + return PhRoundUpToPowerOfTwo(Capacity); +#else + return PhGetPrimeNumber(Capacity); +#endif +} + +/** + * Creates a hashtable object. + * + * \param EntrySize The size of each hashtable entry, in bytes. + * \param EqualFunction A comparison function that is executed to compare two hashtable entries. + * \param HashFunction A hash function that is executed to generate a hash code for a hashtable + * entry. + * \param InitialCapacity The number of entries to allocate storage for initially. + */ +PPH_HASHTABLE PhCreateHashtable( + _In_ ULONG EntrySize, + _In_ PPH_HASHTABLE_EQUAL_FUNCTION EqualFunction, + _In_ PPH_HASHTABLE_HASH_FUNCTION HashFunction, + _In_ ULONG InitialCapacity + ) +{ + PPH_HASHTABLE hashtable; + + hashtable = PhCreateObject(sizeof(PH_HASHTABLE), PhHashtableType); + + // Initial capacity of 0 is not allowed. + if (InitialCapacity == 0) + InitialCapacity = 1; + + hashtable->EntrySize = EntrySize; + hashtable->EqualFunction = EqualFunction; + hashtable->HashFunction = HashFunction; + + // Allocate the buckets. + hashtable->AllocatedBuckets = PhpGetNumberOfBuckets(InitialCapacity); + hashtable->Buckets = PhAllocate(sizeof(ULONG) * hashtable->AllocatedBuckets); + // Set all bucket values to -1. + memset(hashtable->Buckets, 0xff, sizeof(ULONG) * hashtable->AllocatedBuckets); + + // Allocate the entries. + hashtable->AllocatedEntries = hashtable->AllocatedBuckets; + hashtable->Entries = PhAllocate(PH_HASHTABLE_ENTRY_SIZE(EntrySize) * hashtable->AllocatedEntries); + + hashtable->Count = 0; + hashtable->FreeEntry = -1; + hashtable->NextEntry = 0; + + return hashtable; +} + +VOID PhpHashtableDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_HASHTABLE hashtable = (PPH_HASHTABLE)Object; + + PhFree(hashtable->Buckets); + PhFree(hashtable->Entries); +} + +VOID PhpResizeHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ ULONG NewCapacity + ) +{ + PPH_HASHTABLE_ENTRY entry; + ULONG i; + + // Re-allocate the buckets. Note that we don't need to keep the contents. + Hashtable->AllocatedBuckets = PhpGetNumberOfBuckets(NewCapacity); + PhFree(Hashtable->Buckets); + Hashtable->Buckets = PhAllocate(sizeof(ULONG) * Hashtable->AllocatedBuckets); + // Set all bucket values to -1. + memset(Hashtable->Buckets, 0xff, sizeof(ULONG) * Hashtable->AllocatedBuckets); + + // Re-allocate the entries. + Hashtable->AllocatedEntries = Hashtable->AllocatedBuckets; + Hashtable->Entries = PhReAllocate( + Hashtable->Entries, + PH_HASHTABLE_ENTRY_SIZE(Hashtable->EntrySize) * Hashtable->AllocatedEntries + ); + + // Re-distribute the entries among the buckets. + + // PH_HASHTABLE_GET_ENTRY is quite slow (it involves a multiply), so we use a pointer here. + entry = Hashtable->Entries; + + for (i = 0; i < Hashtable->NextEntry; i++) + { + if (entry->HashCode != -1) + { + ULONG index = PhpIndexFromHash(Hashtable, entry->HashCode); + + entry->Next = Hashtable->Buckets[index]; + Hashtable->Buckets[index] = i; + } + + entry = (PPH_HASHTABLE_ENTRY)((ULONG_PTR)entry + PH_HASHTABLE_ENTRY_SIZE(Hashtable->EntrySize)); + } +} + +FORCEINLINE PVOID PhpAddEntryHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry, + _In_ BOOLEAN CheckForDuplicate, + _Out_opt_ PBOOLEAN Added + ) +{ + ULONG hashCode; // hash code of the new entry + ULONG index; // bucket index of the new entry + ULONG freeEntry; // index of new entry in entry array + PPH_HASHTABLE_ENTRY entry; // pointer to new entry in entry array + + hashCode = PhpValidateHash(Hashtable->HashFunction(Entry)); + index = PhpIndexFromHash(Hashtable, hashCode); + + if (CheckForDuplicate) + { + ULONG i; + + for (i = Hashtable->Buckets[index]; i != -1; i = entry->Next) + { + entry = PH_HASHTABLE_GET_ENTRY(Hashtable, i); + + if (entry->HashCode == hashCode && Hashtable->EqualFunction(&entry->Body, Entry)) + { + if (Added) + *Added = FALSE; + + return &entry->Body; + } + } + } + + // Use a free entry if possible. + if (Hashtable->FreeEntry != -1) + { + freeEntry = Hashtable->FreeEntry; + entry = PH_HASHTABLE_GET_ENTRY(Hashtable, freeEntry); + Hashtable->FreeEntry = entry->Next; + } + else + { + // Use the next entry in the entry array. + + if (Hashtable->NextEntry == Hashtable->AllocatedEntries) + { + // Resize the hashtable. + PhpResizeHashtable(Hashtable, Hashtable->AllocatedBuckets * 2); + index = PhpIndexFromHash(Hashtable, hashCode); + } + + freeEntry = Hashtable->NextEntry++; + entry = PH_HASHTABLE_GET_ENTRY(Hashtable, freeEntry); + } + + // Initialize the entry. + entry->HashCode = hashCode; + entry->Next = Hashtable->Buckets[index]; + Hashtable->Buckets[index] = freeEntry; + // Copy the user-supplied data to the entry. + memcpy(&entry->Body, Entry, Hashtable->EntrySize); + + Hashtable->Count++; + + if (Added) + *Added = TRUE; + + return &entry->Body; +} + +/** + * Adds an entry to a hashtable. + * + * \param Hashtable A hashtable object. + * \param Entry The entry to add. + * + * \return A pointer to the entry as stored in the hashtable. This pointer is valid until the + * hashtable is modified. If the hashtable already contained an equal entry, NULL is returned. + * + * \remarks Entries are only guaranteed to be 8 byte aligned, even on 64-bit systems. + */ +PVOID PhAddEntryHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ) +{ + PVOID entry; + BOOLEAN added; + + entry = PhpAddEntryHashtable(Hashtable, Entry, TRUE, &added); + + if (added) + return entry; + else + return NULL; +} + +/** + * Adds an entry to a hashtable or returns an existing one. + * + * \param Hashtable A hashtable object. + * \param Entry The entry to add. + * \param Added A variable which receives TRUE if a new entry was created, and FALSE if an existing + * entry was returned. + * + * \return A pointer to the entry as stored in the hashtable. This pointer is valid until the + * hashtable is modified. If the hashtable already contained an equal entry, the existing entry is + * returned. Check the value of \a Added to determine whether the returned entry is new or existing. + * + * \remarks Entries are only guaranteed to be 8 byte aligned, even on 64-bit systems. + */ +PVOID PhAddEntryHashtableEx( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry, + _Out_opt_ PBOOLEAN Added + ) +{ + return PhpAddEntryHashtable(Hashtable, Entry, TRUE, Added); +} + +/** + * Clears a hashtable. + * + * \param Hashtable A hashtable object. + */ +VOID PhClearHashtable( + _Inout_ PPH_HASHTABLE Hashtable + ) +{ + if (Hashtable->Count > 0) + { + memset(Hashtable->Buckets, 0xff, sizeof(ULONG) * Hashtable->AllocatedBuckets); + Hashtable->Count = 0; + Hashtable->FreeEntry = -1; + Hashtable->NextEntry = 0; + } +} + +/** + * Enumerates the entries in a hashtable. + * + * \param Hashtable A hashtable object. + * \param Entry A variable which receives a pointer to the hashtable entry. The pointer is valid + * until the hashtable is modified. + * \param EnumerationKey A variable which is initialized to 0 before first calling this function. + * + * \return TRUE if an entry pointer was stored in \a Entry, FALSE if there are no more entries. + * + * \remarks Do not modify the hashtable while the hashtable is being enumerated (between calls to + * this function). Otherwise, the function may behave unexpectedly. You may reset the + * \a EnumerationKey variable to 0 if you wish to restart the enumeration. + */ +BOOLEAN PhEnumHashtable( + _In_ PPH_HASHTABLE Hashtable, + _Out_ PVOID *Entry, + _Inout_ PULONG EnumerationKey + ) +{ + while (*EnumerationKey < Hashtable->NextEntry) + { + PPH_HASHTABLE_ENTRY entry = PH_HASHTABLE_GET_ENTRY(Hashtable, *EnumerationKey); + + (*EnumerationKey)++; + + if (entry->HashCode != -1) + { + *Entry = &entry->Body; + return TRUE; + } + } + + return FALSE; +} + +/** + * Locates an entry in a hashtable. + * + * \param Hashtable A hashtable object. + * \param Entry An entry representing the entry to find. + * + * \return A pointer to the entry as stored in the hashtable. This pointer is valid until the + * hashtable is modified. If the entry could not be found, NULL is returned. + * + * \remarks The entry specified in \a Entry can be a partial entry that is filled in enough so that + * the comparison and hash functions can work with them. + */ +PVOID PhFindEntryHashtable( + _In_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ) +{ + ULONG hashCode; + ULONG index; + ULONG i; + PPH_HASHTABLE_ENTRY entry; + + hashCode = PhpValidateHash(Hashtable->HashFunction(Entry)); + index = PhpIndexFromHash(Hashtable, hashCode); + + for (i = Hashtable->Buckets[index]; i != -1; i = entry->Next) + { + entry = PH_HASHTABLE_GET_ENTRY(Hashtable, i); + + if (entry->HashCode == hashCode && Hashtable->EqualFunction(&entry->Body, Entry)) + { + return &entry->Body; + } + } + + return NULL; +} + +/** + * Removes an entry from a hashtable. + * + * \param Hashtable A hashtable object. + * \param Entry The entry to remove. + * + * \return TRUE if the entry was removed, FALSE if the entry could not be found. + * + * \remarks The entry specified in \a Entry can be an actual entry pointer returned by + * PhFindEntryHashtable, or a partial entry. + */ +BOOLEAN PhRemoveEntryHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ) +{ + ULONG hashCode; + ULONG index; + ULONG i; + ULONG previousIndex; + PPH_HASHTABLE_ENTRY entry; + + hashCode = PhpValidateHash(Hashtable->HashFunction(Entry)); + index = PhpIndexFromHash(Hashtable, hashCode); + previousIndex = -1; + + for (i = Hashtable->Buckets[index]; i != -1; i = entry->Next) + { + entry = PH_HASHTABLE_GET_ENTRY(Hashtable, i); + + if (entry->HashCode == hashCode && Hashtable->EqualFunction(&entry->Body, Entry)) + { + // Unlink the entry from the bucket. + if (previousIndex == -1) + { + Hashtable->Buckets[index] = entry->Next; + } + else + { + PH_HASHTABLE_GET_ENTRY(Hashtable, previousIndex)->Next = entry->Next; + } + + entry->HashCode = -1; // indicates the entry is not being used + entry->Next = Hashtable->FreeEntry; + Hashtable->FreeEntry = i; + + Hashtable->Count--; + + return TRUE; + } + + previousIndex = i; + } + + return FALSE; +} + +/** + * Generates a hash code for a sequence of bytes. + * + * \param Bytes A pointer to a byte array. + * \param Length The number of bytes to hash. + */ +ULONG PhHashBytes( + _In_reads_(Length) PUCHAR Bytes, + _In_ SIZE_T Length + ) +{ + ULONG hash = 0; + + if (Length == 0) + return hash; + + // FNV-1a algorithm: http://www.isthe.com/chongo/src/fnv/hash_32a.c + + do + { + hash ^= *Bytes++; + hash *= 0x01000193; + } while (--Length != 0); + + return hash; +} + +/** + * Generates a hash code for a string. + * + * \param String The string to hash. + * \param IgnoreCase TRUE for a case-insensitive hash function, otherwise FALSE. + */ +ULONG PhHashStringRef( + _In_ PPH_STRINGREF String, + _In_ BOOLEAN IgnoreCase + ) +{ + ULONG hash = 0; + SIZE_T count; + PWCHAR p; + + if (String->Length == 0) + return 0; + + count = String->Length / sizeof(WCHAR); + p = String->Buffer; + + if (!IgnoreCase) + { + return PhHashBytes((PUCHAR)String->Buffer, String->Length); + } + else + { + do + { + hash ^= (USHORT)RtlUpcaseUnicodeChar(*p++); + hash *= 0x01000193; + } while (--count != 0); + } + + return hash; +} + +BOOLEAN NTAPI PhpSimpleHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPH_KEY_VALUE_PAIR entry1 = Entry1; + PPH_KEY_VALUE_PAIR entry2 = Entry2; + + return entry1->Key == entry2->Key; +} + +ULONG NTAPI PhpSimpleHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPH_KEY_VALUE_PAIR entry = Entry; + + return PhHashIntPtr((ULONG_PTR)entry->Key); +} + +PPH_HASHTABLE PhCreateSimpleHashtable( + _In_ ULONG InitialCapacity + ) +{ + return PhCreateHashtable( + sizeof(PH_KEY_VALUE_PAIR), + PhpSimpleHashtableEqualFunction, + PhpSimpleHashtableHashFunction, + InitialCapacity + ); +} + +PVOID PhAddItemSimpleHashtable( + _Inout_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key, + _In_opt_ PVOID Value + ) +{ + PH_KEY_VALUE_PAIR entry; + + entry.Key = Key; + entry.Value = Value; + + if (PhAddEntryHashtable(SimpleHashtable, &entry)) + return Value; + else + return NULL; +} + +PVOID *PhFindItemSimpleHashtable( + _In_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key + ) +{ + PH_KEY_VALUE_PAIR lookupEntry; + PPH_KEY_VALUE_PAIR entry; + + lookupEntry.Key = Key; + entry = PhFindEntryHashtable(SimpleHashtable, &lookupEntry); + + if (entry) + return &entry->Value; + else + return NULL; +} + +BOOLEAN PhRemoveItemSimpleHashtable( + _Inout_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key + ) +{ + PH_KEY_VALUE_PAIR lookupEntry; + + lookupEntry.Key = Key; + + return PhRemoveEntryHashtable(SimpleHashtable, &lookupEntry); +} + +/** + * Initializes a free list object. + * + * \param FreeList A pointer to the free list object. + * \param Size The number of bytes in each allocation. + * \param MaximumCount The number of unused allocations to store. + */ +VOID PhInitializeFreeList( + _Out_ PPH_FREE_LIST FreeList, + _In_ SIZE_T Size, + _In_ ULONG MaximumCount + ) +{ + RtlInitializeSListHead(&FreeList->ListHead); + FreeList->Count = 0; + FreeList->MaximumCount = MaximumCount; + FreeList->Size = Size; +} + +/** + * Frees resources used by a free list object. + * + * \param FreeList A pointer to the free list object. + */ +VOID PhDeleteFreeList( + _Inout_ PPH_FREE_LIST FreeList + ) +{ + PPH_FREE_LIST_ENTRY entry; + PSLIST_ENTRY listEntry; + + listEntry = RtlInterlockedFlushSList(&FreeList->ListHead); + + while (listEntry) + { + entry = CONTAINING_RECORD(listEntry, PH_FREE_LIST_ENTRY, ListEntry); + listEntry = listEntry->Next; + PhFree(entry); + } +} + +/** + * Allocates a block of memory from a free list. + * + * \param FreeList A pointer to a free list object. + * + * \return A pointer to the allocated block of memory. The memory must be freed using + * PhFreeToFreeList(). The block is guaranteed to be aligned at MEMORY_ALLOCATION_ALIGNMENT bytes. + */ +PVOID PhAllocateFromFreeList( + _Inout_ PPH_FREE_LIST FreeList + ) +{ + PPH_FREE_LIST_ENTRY entry; + PSLIST_ENTRY listEntry; + + listEntry = RtlInterlockedPopEntrySList(&FreeList->ListHead); + + if (listEntry) + { + _InterlockedDecrement((PLONG)&FreeList->Count); + entry = CONTAINING_RECORD(listEntry, PH_FREE_LIST_ENTRY, ListEntry); + } + else + { + entry = PhAllocate(FIELD_OFFSET(PH_FREE_LIST_ENTRY, Body) + FreeList->Size); + } + + return &entry->Body; +} + +/** + * Frees a block of memory to a free list. + * + * \param FreeList A pointer to a free list object. + * \param Memory A pointer to a block of memory. + */ +VOID PhFreeToFreeList( + _Inout_ PPH_FREE_LIST FreeList, + _In_ PVOID Memory + ) +{ + PPH_FREE_LIST_ENTRY entry; + + entry = CONTAINING_RECORD(Memory, PH_FREE_LIST_ENTRY, Body); + + // We don't enforce Count <= MaximumCount (that would require locking), + // but we do check it. + if (FreeList->Count < FreeList->MaximumCount) + { + RtlInterlockedPushEntrySList(&FreeList->ListHead, &entry->ListEntry); + _InterlockedIncrement((PLONG)&FreeList->Count); + } + else + { + PhFree(entry); + } +} + +/** + * Initializes a callback object. + * + * \param Callback A pointer to a callback object. + */ +VOID PhInitializeCallback( + _Out_ PPH_CALLBACK Callback + ) +{ + InitializeListHead(&Callback->ListHead); + PhInitializeQueuedLock(&Callback->ListLock); + PhInitializeCondition(&Callback->BusyCondition); +} + +/** + * Frees resources used by a callback object. + * + * \param Callback A pointer to a callback object. + */ +VOID PhDeleteCallback( + _Inout_ PPH_CALLBACK Callback + ) +{ + // Nothing for now +} + +/** + * Registers a callback function to be notified. + * + * \param Callback A pointer to a callback object. + * \param Function The callback function. + * \param Context A user-defined value to pass to the callback function. + * \param Registration A variable which receives registration information for the callback. Do not + * modify the contents of this structure and do not free the storage for this structure until you + * have unregistered the callback. + */ +VOID PhRegisterCallback( + _Inout_ PPH_CALLBACK Callback, + _In_ PPH_CALLBACK_FUNCTION Function, + _In_opt_ PVOID Context, + _Out_ PPH_CALLBACK_REGISTRATION Registration + ) +{ + PhRegisterCallbackEx( + Callback, + Function, + Context, + 0, + Registration + ); +} + +/** + * Registers a callback function to be notified. + * + * \param Callback A pointer to a callback object. + * \param Function The callback function. + * \param Context A user-defined value to pass to the callback function. + * \param Flags A combination of flags controlling the callback. Set this parameter to 0. + * \param Registration A variable which receives registration information for the callback. Do not + * modify the contents of this structure and do not free the storage for this structure until you + * have unregistered the callback. + */ +VOID PhRegisterCallbackEx( + _Inout_ PPH_CALLBACK Callback, + _In_ PPH_CALLBACK_FUNCTION Function, + _In_opt_ PVOID Context, + _In_ USHORT Flags, + _Out_ PPH_CALLBACK_REGISTRATION Registration + ) +{ + Registration->Function = Function; + Registration->Context = Context; + Registration->Busy = 0; + Registration->Unregistering = FALSE; + Registration->Flags = Flags; + + PhAcquireQueuedLockExclusive(&Callback->ListLock); + InsertTailList(&Callback->ListHead, &Registration->ListEntry); + PhReleaseQueuedLockExclusive(&Callback->ListLock); +} + +/** + * Unregisters a callback function. + * + * \param Callback A pointer to a callback object. + * \param Registration The structure returned by PhRegisterCallback(). + * + * \remarks It is guaranteed that the callback function will not be in execution once this function + * returns. Attempting to unregister a callback function from within the same function will result + * in a deadlock. + */ +VOID PhUnregisterCallback( + _Inout_ PPH_CALLBACK Callback, + _Inout_ PPH_CALLBACK_REGISTRATION Registration + ) +{ + Registration->Unregistering = TRUE; + + PhAcquireQueuedLockExclusive(&Callback->ListLock); + + // Wait for the callback to be unbusy. + while (Registration->Busy) + PhWaitForCondition(&Callback->BusyCondition, &Callback->ListLock, NULL); + + RemoveEntryList(&Registration->ListEntry); + + PhReleaseQueuedLockExclusive(&Callback->ListLock); +} + +/** + * Notifies all registered callback functions. + * + * \param Callback A pointer to a callback object. + * \param Parameter A value to pass to all callback functions. + */ +VOID PhInvokeCallback( + _In_ PPH_CALLBACK Callback, + _In_opt_ PVOID Parameter + ) +{ + PLIST_ENTRY listEntry; + + PhAcquireQueuedLockShared(&Callback->ListLock); + + listEntry = Callback->ListHead.Flink; + + while (listEntry != &Callback->ListHead) + { + PPH_CALLBACK_REGISTRATION registration; + LONG busy; + + registration = CONTAINING_RECORD(listEntry, PH_CALLBACK_REGISTRATION, ListEntry); + + // Don't bother executing the callback function if it is being unregistered. + if (registration->Unregistering) + continue; + + _InterlockedIncrement(®istration->Busy); + + // Execute the callback function. + + PhReleaseQueuedLockShared(&Callback->ListLock); + registration->Function( + Parameter, + registration->Context + ); + PhAcquireQueuedLockShared(&Callback->ListLock); + + busy = _InterlockedDecrement(®istration->Busy); + + if (registration->Unregistering && busy == 0) + { + // Someone started unregistering while the callback function was executing, and we must + // wake them. + PhPulseAllCondition(&Callback->BusyCondition); + } + + listEntry = listEntry->Flink; + } + + PhReleaseQueuedLockShared(&Callback->ListLock); +} + +/** + * Retrieves a prime number bigger than or equal to the specified number. + */ +ULONG PhGetPrimeNumber( + _In_ ULONG Minimum + ) +{ + ULONG i, j; + + for (i = 0; i < sizeof(PhpPrimeNumbers) / sizeof(ULONG); i++) + { + if (PhpPrimeNumbers[i] >= Minimum) + return PhpPrimeNumbers[i]; + } + + for (i = Minimum | 1; i < MAXLONG; i += 2) + { + ULONG sqrtI = (ULONG)sqrt(i); + + for (j = 3; j <= sqrtI; j += 2) + { + if (i % j == 0) + { + // Not a prime. + goto NextPrime; + } + } + + // Success. + return i; +NextPrime: + NOTHING; + } + + return Minimum; +} + +/** + * Rounds up a number to the next power of two. + */ +ULONG PhRoundUpToPowerOfTwo( + _In_ ULONG Number + ) +{ + Number--; + Number |= Number >> 1; + Number |= Number >> 2; + Number |= Number >> 4; + Number |= Number >> 8; + Number |= Number >> 16; + Number++; + + return Number; +} + +/** + * Performs exponentiation. + */ +ULONG PhExponentiate( + _In_ ULONG Base, + _In_ ULONG Exponent + ) +{ + ULONG result = 1; + + while (Exponent) + { + if (Exponent & 1) + result *= Base; + + Exponent >>= 1; + Base *= Base; + } + + return result; +} + +/** + * Performs 64-bit exponentiation. + */ +ULONG64 PhExponentiate64( + _In_ ULONG64 Base, + _In_ ULONG Exponent + ) +{ + ULONG64 result = 1; + + while (Exponent) + { + if (Exponent & 1) + result *= Base; + + Exponent >>= 1; + Base *= Base; + } + + return result; +} + +/** + * Converts a sequence of hexadecimal digits into a byte array. + * + * \param String A string containing hexadecimal digits to convert. The string must have an even + * number of digits, because each pair of hexadecimal digits represents one byte. Example: + * "129a2eff5c0b". + * \param Buffer The output buffer. + * + * \return TRUE if the string was successfully converted, otherwise FALSE. + */ +BOOLEAN PhHexStringToBuffer( + _In_ PPH_STRINGREF String, + _Out_writes_bytes_(String->Length / sizeof(WCHAR) / 2) PUCHAR Buffer + ) +{ + SIZE_T i; + SIZE_T length; + + // The string must have an even length. + if ((String->Length / sizeof(WCHAR)) & 1) + return FALSE; + + length = String->Length / sizeof(WCHAR) / 2; + + for (i = 0; i < length; i++) + { + Buffer[i] = + (UCHAR)(PhCharToInteger[(UCHAR)String->Buffer[i * 2]] << 4) + + (UCHAR)PhCharToInteger[(UCHAR)String->Buffer[i * 2 + 1]]; + } + + return TRUE; +} + +/** + * Converts a byte array into a sequence of hexadecimal digits. + * + * \param Buffer The input buffer. + * \param Length The number of bytes to convert. + * + * \return A string containing a sequence of hexadecimal digits. + */ +PPH_STRING PhBufferToHexString( + _In_reads_bytes_(Length) PUCHAR Buffer, + _In_ ULONG Length + ) +{ + return PhBufferToHexStringEx(Buffer, Length, FALSE); +} + +/** + * Converts a byte array into a sequence of hexadecimal digits. + * + * \param Buffer The input buffer. + * \param Length The number of bytes to convert. + * \param UpperCase TRUE to use uppercase characters, otherwise FALSE. + * + * \return A string containing a sequence of hexadecimal digits. + */ +PPH_STRING PhBufferToHexStringEx( + _In_reads_bytes_(Length) PUCHAR Buffer, + _In_ ULONG Length, + _In_ BOOLEAN UpperCase + ) +{ + PCHAR table; + PPH_STRING string; + ULONG i; + + if (UpperCase) + table = PhIntegerToCharUpper; + else + table = PhIntegerToChar; + + string = PhCreateStringEx(NULL, Length * 2 * sizeof(WCHAR)); + + for (i = 0; i < Length; i++) + { + string->Buffer[i * 2] = table[Buffer[i] >> 4]; + string->Buffer[i * 2 + 1] = table[Buffer[i] & 0xf]; + } + + return string; +} + +/** + * Converts a string to an integer. + * + * \param String The string to process. + * \param Base The base which the string uses to represent the integer. The maximum value is 69. + * \param Integer The resulting integer. + */ +BOOLEAN PhpStringToInteger64( + _In_ PPH_STRINGREF String, + _In_ ULONG Base, + _Out_ PULONG64 Integer + ) +{ + BOOLEAN valid = TRUE; + ULONG64 result; + SIZE_T length; + SIZE_T i; + + length = String->Length / sizeof(WCHAR); + result = 0; + + for (i = 0; i < length; i++) + { + ULONG value; + + value = PhCharToInteger[(UCHAR)String->Buffer[i]]; + + if (value < Base) + result = result * Base + value; + else + valid = FALSE; + } + + *Integer = result; + + return valid; +} + +/** + * Converts a string to an integer. + * + * \param String The string to process. + * \param Base The base which the string uses to represent the integer. The maximum value is 69. If + * the parameter is 0, the base is inferred from the string. + * \param Integer The resulting integer. + * + * \remarks If \a Base is 0, the following prefixes may be used to indicate bases: + * + * \li \c 0x Base 16. + * \li \c 0o Base 8. + * \li \c 0b Base 2. + * \li \c 0t Base 3. + * \li \c 0q Base 4. + * \li \c 0w Base 12. + * \li \c 0r Base 32. + * + * If there is no recognized prefix, base 10 is used. + */ +BOOLEAN PhStringToInteger64( + _In_ PPH_STRINGREF String, + _In_opt_ ULONG Base, + _Out_opt_ PLONG64 Integer + ) +{ + BOOLEAN valid; + ULONG64 result; + PH_STRINGREF string; + BOOLEAN negative; + ULONG base; + + if (Base > 69) + return FALSE; + + string = *String; + negative = FALSE; + + if (string.Length != 0 && (string.Buffer[0] == '-' || string.Buffer[0] == '+')) + { + if (string.Buffer[0] == '-') + negative = TRUE; + + PhSkipStringRef(&string, sizeof(WCHAR)); + } + + // If the caller specified a base, don't perform any additional processing. + + if (Base) + { + base = Base; + } + else + { + base = 10; + + if (string.Length >= 2 * sizeof(WCHAR) && string.Buffer[0] == '0') + { + switch (string.Buffer[1]) + { + case 'x': + case 'X': + base = 16; + break; + case 'o': + case 'O': + base = 8; + break; + case 'b': + case 'B': + base = 2; + break; + case 't': // ternary + case 'T': + base = 3; + break; + case 'q': // quaternary + case 'Q': + base = 4; + break; + case 'w': // base 12 + case 'W': + base = 12; + break; + case 'r': // base 32 + case 'R': + base = 32; + break; + } + + if (base != 10) + PhSkipStringRef(&string, 2 * sizeof(WCHAR)); + } + } + + valid = PhpStringToInteger64(&string, base, &result); + + if (Integer) + *Integer = negative ? -(LONG64)result : result; + + return valid; +} + +BOOLEAN PhpStringToDouble( + _In_ PPH_STRINGREF String, + _In_ ULONG Base, + _Out_ DOUBLE *Double + ) +{ + BOOLEAN valid = TRUE; + BOOLEAN dotSeen = FALSE; + DOUBLE result; + DOUBLE fraction; + SIZE_T length; + SIZE_T i; + + length = String->Length / sizeof(WCHAR); + result = 0; + fraction = 1; + + for (i = 0; i < length; i++) + { + if (String->Buffer[i] == '.') + { + if (!dotSeen) + dotSeen = TRUE; + else + valid = FALSE; + } + else + { + ULONG value; + + value = PhCharToInteger[(UCHAR)String->Buffer[i]]; + + if (value < Base) + { + if (!dotSeen) + { + result = result * Base + value; + } + else + { + fraction /= Base; + result = result + value * fraction; + } + } + else + { + valid = FALSE; + } + } + } + + *Double = result; + + return valid; +} + +/** + * Converts a string to a double-precision floating point value. + * + * \param String The string to process. + * \param Base Reserved. + * \param Double The resulting double value. + */ +BOOLEAN PhStringToDouble( + _In_ PPH_STRINGREF String, + _Reserved_ ULONG Base, + _Out_opt_ DOUBLE *Double + ) +{ + BOOLEAN valid; + DOUBLE result; + PH_STRINGREF string; + BOOLEAN negative; + + string = *String; + negative = FALSE; + + if (string.Length != 0 && (string.Buffer[0] == '-' || string.Buffer[0] == '+')) + { + if (string.Buffer[0] == '-') + negative = TRUE; + + PhSkipStringRef(&string, sizeof(WCHAR)); + } + + valid = PhpStringToDouble(&string, 10, &result); + + if (Double) + *Double = negative ? -result : result; + + return valid; +} + +/** + * Converts an integer to a string. + * + * \param Integer The integer to process. + * \param Base The base which the integer is represented with. The maximum value is 69. The base + * cannot be 1. If the parameter is 0, the base used is 10. + * \param Signed TRUE if \a Integer is a signed value, otherwise FALSE. + * + * \return The resulting string, or NULL if an error occurred. + */ +PPH_STRING PhIntegerToString64( + _In_ LONG64 Integer, + _In_opt_ ULONG Base, + _In_ BOOLEAN Signed + ) +{ + PH_FORMAT format; + + if (Base == 1 || Base > 69) + return NULL; + + if (Signed) + PhInitFormatI64D(&format, Integer); + else + PhInitFormatI64U(&format, Integer); + + if (Base != 0) + { + format.Type |= FormatUseRadix; + format.Radix = (UCHAR)Base; + } + + return PhFormat(&format, 1, 0); +} + +VOID PhPrintTimeSpan( + _Out_writes_(PH_TIMESPAN_STR_LEN_1) PWSTR Destination, + _In_ ULONG64 Ticks, + _In_opt_ ULONG Mode + ) +{ + switch (Mode) + { + case PH_TIMESPAN_HMSM: + _snwprintf( + Destination, + PH_TIMESPAN_STR_LEN, + L"%02I64u:%02I64u:%02I64u.%03I64u", + PH_TICKS_PARTIAL_HOURS(Ticks), + PH_TICKS_PARTIAL_MIN(Ticks), + PH_TICKS_PARTIAL_SEC(Ticks), + PH_TICKS_PARTIAL_MS(Ticks) + ); + break; + case PH_TIMESPAN_DHMS: + _snwprintf( + Destination, + PH_TIMESPAN_STR_LEN, + L"%I64u:%02I64u:%02I64u:%02I64u", + PH_TICKS_PARTIAL_DAYS(Ticks), + PH_TICKS_PARTIAL_HOURS(Ticks), + PH_TICKS_PARTIAL_MIN(Ticks), + PH_TICKS_PARTIAL_SEC(Ticks) + ); + break; + default: + _snwprintf( + Destination, + PH_TIMESPAN_STR_LEN, + L"%02I64u:%02I64u:%02I64u", + PH_TICKS_PARTIAL_HOURS(Ticks), + PH_TICKS_PARTIAL_MIN(Ticks), + PH_TICKS_PARTIAL_SEC(Ticks) + ); + break; + } +} + +/** + * Fills a memory block with a ULONG pattern. + * + * \param Memory The memory block. The block must be 4 byte aligned. + * \param Value The ULONG pattern. + * \param Count The number of elements. + */ +VOID PhFillMemoryUlong( + _Inout_updates_(Count) _Needs_align_(4) PULONG Memory, + _In_ ULONG Value, + _In_ SIZE_T Count + ) +{ + __m128i pattern; + SIZE_T count; + + if (PhpVectorLevel < PH_VECTOR_LEVEL_SSE2) + { + if (Count != 0) + { + do + { + *Memory++ = Value; + } while (--Count != 0); + } + + return; + } + + if ((ULONG_PTR)Memory & 0xf) + { + switch ((ULONG_PTR)Memory & 0xf) + { + case 0x4: + if (Count >= 1) + { + *Memory++ = Value; + Count--; + } + __fallthrough; + case 0x8: + if (Count >= 1) + { + *Memory++ = Value; + Count--; + } + __fallthrough; + case 0xc: + if (Count >= 1) + { + *Memory++ = Value; + Count--; + } + break; + } + } + + pattern = _mm_set1_epi32(Value); + count = Count / 4; + + if (count != 0) + { + do + { + _mm_store_si128((__m128i *)Memory, pattern); + Memory += 4; + } while (--count != 0); + } + + switch (Count & 0x3) + { + case 0x3: + *Memory++ = Value; + __fallthrough; + case 0x2: + *Memory++ = Value; + __fallthrough; + case 0x1: + *Memory++ = Value; + break; + } +} + +/** + * Divides an array of numbers by a number. + * + * \param A The destination array, divided by \a B. + * \param B The number. + * \param Count The number of elements. + */ +VOID PhDivideSinglesBySingle( + _Inout_updates_(Count) PFLOAT A, + _In_ FLOAT B, + _In_ SIZE_T Count + ) +{ + PFLOAT endA; + __m128 b; + + if (PhpVectorLevel < PH_VECTOR_LEVEL_SSE2) + { + while (Count--) + *A++ /= B; + + return; + } + + if ((ULONG_PTR)A & 0xf) + { + switch ((ULONG_PTR)A & 0xf) + { + case 0x4: + if (Count >= 1) + { + *A++ /= B; + Count--; + } + __fallthrough; + case 0x8: + if (Count >= 1) + { + *A++ /= B; + Count--; + } + __fallthrough; + case 0xc: + if (Count >= 1) + { + *A++ /= B; + Count--; + } + else + { + return; // essential; A may not be aligned properly + } + break; + } + } + + endA = (PFLOAT)((ULONG_PTR)(A + Count) & ~0xf); + b = _mm_load1_ps(&B); + + while (A != endA) + { + __m128 a; + + a = _mm_load_ps(A); + a = _mm_div_ps(a, b); + _mm_store_ps(A, a); + + A += 4; + } + + switch (Count & 0x3) + { + case 0x3: + *A++ /= B; + __fallthrough; + case 0x2: + *A++ /= B; + __fallthrough; + case 0x1: + *A++ /= B; + break; + } +} diff --git a/phlib/circbuf.c b/phlib/circbuf.c new file mode 100644 index 0000000..1f9efb8 --- /dev/null +++ b/phlib/circbuf.c @@ -0,0 +1,22 @@ +#include +#include + +#undef T +#define T ULONG +#include "circbuf_i.h" + +#undef T +#define T ULONG64 +#include "circbuf_i.h" + +#undef T +#define T PVOID +#include "circbuf_i.h" + +#undef T +#define T SIZE_T +#include "circbuf_i.h" + +#undef T +#define T FLOAT +#include "circbuf_i.h" diff --git a/phlib/circbuf_i.h b/phlib/circbuf_i.h new file mode 100644 index 0000000..fd84c14 --- /dev/null +++ b/phlib/circbuf_i.h @@ -0,0 +1,121 @@ +#ifdef T + +#include + +VOID T___(PhInitializeCircularBuffer, T)( + _Out_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ ULONG Size + ) +{ +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + Buffer->Size = PhRoundUpToPowerOfTwo(Size); + Buffer->SizeMinusOne = Buffer->Size - 1; +#else + Buffer->Size = Size; +#endif + + Buffer->Count = 0; + Buffer->Index = 0; + Buffer->Data = PhAllocate(sizeof(T) * Buffer->Size); +} + +VOID T___(PhDeleteCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer + ) +{ + PhFree(Buffer->Data); +} + +VOID T___(PhResizeCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ ULONG NewSize + ) +{ + T *newData; + ULONG tailSize; + ULONG headSize; + +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + NewSize = PhRoundUpToPowerOfTwo(NewSize); +#endif + + // If we're not actually resizing it, return. + if (NewSize == Buffer->Size) + return; + + newData = PhAllocate(sizeof(T) * NewSize); + tailSize = (ULONG)(Buffer->Size - Buffer->Index); + headSize = Buffer->Count - tailSize; + + if (NewSize > Buffer->Size) + { + // Copy the tail, then the head. + memcpy(newData, &Buffer->Data[Buffer->Index], sizeof(T) * tailSize); + memcpy(&newData[tailSize], Buffer->Data, sizeof(T) * headSize); + Buffer->Index = 0; + } + else + { + if (tailSize >= NewSize) + { + // Copy only a part of the tail. + memcpy(newData, &Buffer->Data[Buffer->Index], sizeof(T) * NewSize); + Buffer->Index = 0; + } + else + { + // Copy the tail, then only part of the head. + memcpy(newData, &Buffer->Data[Buffer->Index], sizeof(T) * tailSize); + memcpy(&newData[tailSize], Buffer->Data, sizeof(T) * (NewSize - tailSize)); + Buffer->Index = 0; + } + + // Since we're making the circular buffer smaller, limit the count. + if (Buffer->Count > NewSize) + Buffer->Count = NewSize; + } + + Buffer->Data = newData; + Buffer->Size = NewSize; +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + Buffer->SizeMinusOne = NewSize - 1; +#endif +} + +VOID T___(PhClearCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer + ) +{ + Buffer->Count = 0; + Buffer->Index = 0; +} + +VOID T___(PhCopyCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _Out_writes_(Count) T *Destination, + _In_ ULONG Count + ) +{ + ULONG tailSize; + ULONG headSize; + + tailSize = (ULONG)(Buffer->Size - Buffer->Index); + headSize = Buffer->Count - tailSize; + + if (Count > Buffer->Count) + Count = Buffer->Count; + + if (tailSize >= Count) + { + // Copy only a part of the tail. + memcpy(Destination, &Buffer->Data[Buffer->Index], sizeof(T) * Count); + } + else + { + // Copy the tail, then only part of the head. + memcpy(Destination, &Buffer->Data[Buffer->Index], sizeof(T) * tailSize); + memcpy(&Destination[tailSize], Buffer->Data, sizeof(T) * (Count - tailSize)); + } +} + +#endif diff --git a/phlib/colorbox.c b/phlib/colorbox.c new file mode 100644 index 0000000..ef4b526 --- /dev/null +++ b/phlib/colorbox.c @@ -0,0 +1,228 @@ +/* + * Process Hacker - + * color picker + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +typedef struct _PHP_COLORBOX_CONTEXT +{ + COLORREF SelectedColor; + BOOLEAN Hot; + BOOLEAN HasFocus; +} PHP_COLORBOX_CONTEXT, *PPHP_COLORBOX_CONTEXT; + +LRESULT CALLBACK PhpColorBoxWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +BOOLEAN PhColorBoxInitialization( + VOID + ) +{ + WNDCLASSEX c = { sizeof(c) }; + + c.style = CS_GLOBALCLASS; + c.lpfnWndProc = PhpColorBoxWndProc; + c.cbClsExtra = 0; + c.cbWndExtra = sizeof(PVOID); + c.hInstance = PhLibImageBase; + c.hIcon = NULL; + c.hCursor = LoadCursor(NULL, IDC_ARROW); + c.hbrBackground = NULL; + c.lpszMenuName = NULL; + c.lpszClassName = PH_COLORBOX_CLASSNAME; + c.hIconSm = NULL; + + if (!RegisterClassEx(&c)) + return FALSE; + + return TRUE; +} + +VOID PhpCreateColorBoxContext( + _Out_ PPHP_COLORBOX_CONTEXT *Context + ) +{ + PPHP_COLORBOX_CONTEXT context; + + context = PhAllocate(sizeof(PHP_COLORBOX_CONTEXT)); + memset(context, 0, sizeof(PHP_COLORBOX_CONTEXT)); + *Context = context; +} + +VOID PhpFreeColorBoxContext( + _In_ _Post_invalid_ PPHP_COLORBOX_CONTEXT Context + ) +{ + PhFree(Context); +} + +VOID PhpChooseColor( + _In_ HWND hwnd, + _In_ PPHP_COLORBOX_CONTEXT Context + ) +{ + CHOOSECOLOR chooseColor = { sizeof(chooseColor) }; + COLORREF customColors[16] = { 0 }; + + chooseColor.hwndOwner = hwnd; + chooseColor.rgbResult = Context->SelectedColor; + chooseColor.lpCustColors = customColors; + chooseColor.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT; + + if (ChooseColor(&chooseColor)) + { + Context->SelectedColor = chooseColor.rgbResult; + InvalidateRect(hwnd, NULL, TRUE); + } +} + +LRESULT CALLBACK PhpColorBoxWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPHP_COLORBOX_CONTEXT context; + + context = (PPHP_COLORBOX_CONTEXT)GetWindowLongPtr(hwnd, 0); + + if (uMsg == WM_CREATE) + { + PhpCreateColorBoxContext(&context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)context); + } + + if (!context) + return DefWindowProc(hwnd, uMsg, wParam, lParam); + + switch (uMsg) + { + case WM_CREATE: + { + // Nothing + } + break; + case WM_DESTROY: + { + PhpFreeColorBoxContext(context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL); + } + break; + case WM_PAINT: + { + PAINTSTRUCT paintStruct; + RECT clientRect; + HDC hdc; + + if (hdc = BeginPaint(hwnd, &paintStruct)) + { + GetClientRect(hwnd, &clientRect); + + // Border color + SetDCPenColor(hdc, RGB(0x44, 0x44, 0x44)); + + // Fill color + if (!context->Hot && !context->HasFocus) + SetDCBrushColor(hdc, context->SelectedColor); + else + SetDCBrushColor(hdc, PhMakeColorBrighter(context->SelectedColor, 64)); + + // Draw the rectangle. + SelectObject(hdc, GetStockObject(DC_PEN)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + Rectangle(hdc, clientRect.left, clientRect.top, clientRect.right, clientRect.bottom); + + EndPaint(hwnd, &paintStruct); + } + } + return 0; + case WM_ERASEBKGND: + return 1; + case WM_MOUSEMOVE: + { + if (!context->Hot) + { + TRACKMOUSEEVENT trackMouseEvent = { sizeof(trackMouseEvent) }; + + context->Hot = TRUE; + InvalidateRect(hwnd, NULL, TRUE); + + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = hwnd; + TrackMouseEvent(&trackMouseEvent); + } + } + break; + case WM_MOUSELEAVE: + { + context->Hot = FALSE; + InvalidateRect(hwnd, NULL, TRUE); + } + break; + case WM_LBUTTONDOWN: + { + PhpChooseColor(hwnd, context); + } + break; + case WM_SETFOCUS: + { + context->HasFocus = TRUE; + InvalidateRect(hwnd, NULL, TRUE); + } + return 0; + case WM_KILLFOCUS: + { + context->HasFocus = FALSE; + InvalidateRect(hwnd, NULL, TRUE); + } + return 0; + case WM_GETDLGCODE: + if (wParam == VK_RETURN) + return DLGC_WANTMESSAGE; + return 0; + case WM_KEYDOWN: + { + switch (wParam) + { + case VK_SPACE: + case VK_RETURN: + PhpChooseColor(hwnd, context); + break; + } + } + break; + case CBCM_SETCOLOR: + context->SelectedColor = (COLORREF)wParam; + return TRUE; + case CBCM_GETCOLOR: + return (LRESULT)context->SelectedColor; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} diff --git a/phlib/cpysave.c b/phlib/cpysave.c new file mode 100644 index 0000000..685d04e --- /dev/null +++ b/phlib/cpysave.c @@ -0,0 +1,573 @@ +/* + * Process Hacker - + * copy/save code for listviews and treelists + * + * Copyright (C) 2010-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define TAB_SIZE 8 + +VOID PhpEscapeStringForCsv( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PPH_STRING String + ) +{ + SIZE_T i; + SIZE_T length; + PWCHAR runStart; + SIZE_T runLength; + + length = String->Length / sizeof(WCHAR); + runStart = NULL; + + for (i = 0; i < length; i++) + { + switch (String->Buffer[i]) + { + case '\"': + if (runStart) + { + PhAppendStringBuilderEx(StringBuilder, runStart, runLength * sizeof(WCHAR)); + runStart = NULL; + } + + PhAppendStringBuilder2(StringBuilder, L"\"\""); + + break; + default: + if (runStart) + { + runLength++; + } + else + { + runStart = &String->Buffer[i]; + runLength = 1; + } + + break; + } + } + + if (runStart) + PhAppendStringBuilderEx(StringBuilder, runStart, runLength * sizeof(WCHAR)); +} + +/** + * Allocates a text table. + * + * \param Table A variable which receives a pointer to the text table. + * \param Rows The number of rows in the table. + * \param Columns The number of columns in the table. + */ +VOID PhaCreateTextTable( + _Out_ PPH_STRING ***Table, + _In_ ULONG Rows, + _In_ ULONG Columns + ) +{ + PPH_STRING **table; + ULONG i; + + table = PH_AUTO(PhCreateAlloc(sizeof(PPH_STRING *) * Rows)); + + for (i = 0; i < Rows; i++) + { + table[i] = PH_AUTO(PhCreateAlloc(sizeof(PPH_STRING) * Columns)); + memset(table[i], 0, sizeof(PPH_STRING) * Columns); + } + + *Table = table; +} + +/** + * Formats a text table to a list of lines. + * + * \param Table A pointer to the text table. + * \param Rows The number of rows in the table. + * \param Columns The number of columns in the table. + * \param Mode The export formatting mode. + * + * \return A list of strings for each line in the output. The list object and + * string objects are not auto-dereferenced. + */ +PPH_LIST PhaFormatTextTable( + _In_ PPH_STRING **Table, + _In_ ULONG Rows, + _In_ ULONG Columns, + _In_ ULONG Mode + ) +{ + PPH_LIST lines; + // The tab count array contains the number of tabs need to fill the biggest + // row cell in each column. + PULONG tabCount; + ULONG i; + ULONG j; + + if (Mode == PH_EXPORT_MODE_TABS || Mode == PH_EXPORT_MODE_SPACES) + { + // Create the tab count array. + + tabCount = PH_AUTO(PhCreateAlloc(sizeof(ULONG) * Columns)); + memset(tabCount, 0, sizeof(ULONG) * Columns); // zero all values + + for (i = 0; i < Rows; i++) + { + for (j = 0; j < Columns; j++) + { + ULONG newCount; + + if (Table[i][j]) + newCount = (ULONG)(Table[i][j]->Length / sizeof(WCHAR) / TAB_SIZE); + else + newCount = 0; + + // Replace the existing count if this tab count is bigger. + if (tabCount[j] < newCount) + tabCount[j] = newCount; + } + } + } + + // Create the final list of lines by going through each cell and appending + // the proper tab count (if we are using tabs). This will make sure each column + // is properly aligned. + + lines = PhCreateList(Rows); + + for (i = 0; i < Rows; i++) + { + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 100); + + switch (Mode) + { + case PH_EXPORT_MODE_TABS: + { + for (j = 0; j < Columns; j++) + { + ULONG k; + + if (Table[i][j]) + { + // Calculate the number of tabs needed. + k = (ULONG)(tabCount[j] + 1 - Table[i][j]->Length / sizeof(WCHAR) / TAB_SIZE); + + PhAppendStringBuilder(&stringBuilder, &Table[i][j]->sr); + } + else + { + k = tabCount[j] + 1; + } + + PhAppendCharStringBuilder2(&stringBuilder, '\t', k); + } + } + break; + case PH_EXPORT_MODE_SPACES: + { + for (j = 0; j < Columns; j++) + { + ULONG k; + + if (Table[i][j]) + { + // Calculate the number of spaces needed. + k = (ULONG)((tabCount[j] + 1) * TAB_SIZE - Table[i][j]->Length / sizeof(WCHAR)); + + PhAppendStringBuilder(&stringBuilder, &Table[i][j]->sr); + } + else + { + k = (tabCount[j] + 1) * TAB_SIZE; + } + + PhAppendCharStringBuilder2(&stringBuilder, ' ', k); + } + } + break; + case PH_EXPORT_MODE_CSV: + { + for (j = 0; j < Columns; j++) + { + PhAppendCharStringBuilder(&stringBuilder, '\"'); + + if (Table[i][j]) + { + PhpEscapeStringForCsv(&stringBuilder, Table[i][j]); + } + + PhAppendCharStringBuilder(&stringBuilder, '\"'); + + if (j != Columns - 1) + PhAppendCharStringBuilder(&stringBuilder, ','); + } + } + break; + } + + PhAddItemList(lines, PhFinalStringBuilderString(&stringBuilder)); + } + + return lines; +} + +VOID PhMapDisplayIndexTreeNew( + _In_ HWND TreeNewHandle, + _Out_opt_ PULONG *DisplayToId, + _Out_opt_ PWSTR **DisplayToText, + _Out_ PULONG NumberOfColumns + ) +{ + PPH_TREENEW_COLUMN fixedColumn; + ULONG numberOfColumns; + PULONG displayToId; + PWSTR *displayToText; + ULONG i; + PH_TREENEW_COLUMN column; + + fixedColumn = TreeNew_GetFixedColumn(TreeNewHandle); + numberOfColumns = TreeNew_GetVisibleColumnCount(TreeNewHandle); + + displayToId = PhAllocate(numberOfColumns * sizeof(ULONG)); + + if (fixedColumn) + { + TreeNew_GetColumnOrderArray(TreeNewHandle, numberOfColumns - 1, displayToId + 1); + displayToId[0] = fixedColumn->Id; + } + else + { + TreeNew_GetColumnOrderArray(TreeNewHandle, numberOfColumns, displayToId); + } + + if (DisplayToText) + { + displayToText = PhAllocate(numberOfColumns * sizeof(PWSTR)); + + for (i = 0; i < numberOfColumns; i++) + { + if (TreeNew_GetColumn(TreeNewHandle, displayToId[i], &column)) + { + displayToText[i] = column.Text; + } + } + + *DisplayToText = displayToText; + } + + if (DisplayToId) + *DisplayToId = displayToId; + else + PhFree(displayToId); + + *NumberOfColumns = numberOfColumns; +} + +PPH_STRING PhGetTreeNewText( + _In_ HWND TreeNewHandle, + _Reserved_ ULONG Reserved + ) +{ + PH_STRING_BUILDER stringBuilder; + PULONG displayToId; + ULONG rows; + ULONG columns; + ULONG i; + ULONG j; + + PhMapDisplayIndexTreeNew(TreeNewHandle, &displayToId, NULL, &columns); + rows = TreeNew_GetFlatNodeCount(TreeNewHandle); + + PhInitializeStringBuilder(&stringBuilder, 0x100); + + for (i = 0; i < rows; i++) + { + PH_TREENEW_GET_CELL_TEXT getCellText; + + getCellText.Node = TreeNew_GetFlatNode(TreeNewHandle, i); + assert(getCellText.Node); + + if (!getCellText.Node->Selected) + continue; + + for (j = 0; j < columns; j++) + { + getCellText.Id = displayToId[j]; + PhInitializeEmptyStringRef(&getCellText.Text); + TreeNew_GetCellText(TreeNewHandle, &getCellText); + + PhAppendStringBuilder(&stringBuilder, &getCellText.Text); + PhAppendStringBuilder2(&stringBuilder, L", "); + } + + // Remove the trailing comma and space. + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + PhAppendStringBuilder2(&stringBuilder, L"\r\n"); + } + + PhFree(displayToId); + + return PhFinalStringBuilderString(&stringBuilder); +} + +PPH_LIST PhGetGenericTreeNewLines( + _In_ HWND TreeNewHandle, + _In_ ULONG Mode + ) +{ + PH_AUTO_POOL autoPool; + PPH_LIST lines; + ULONG rows; + ULONG columns; + ULONG numberOfNodes; + PULONG displayToId; + PWSTR *displayToText; + PPH_STRING **table; + ULONG i; + ULONG j; + + PhInitializeAutoPool(&autoPool); + + numberOfNodes = TreeNew_GetFlatNodeCount(TreeNewHandle); + + rows = numberOfNodes + 1; + PhMapDisplayIndexTreeNew(TreeNewHandle, &displayToId, &displayToText, &columns); + + PhaCreateTextTable(&table, rows, columns); + + for (i = 0; i < columns; i++) + table[0][i] = PhaCreateString(displayToText[i]); + + for (i = 0; i < numberOfNodes; i++) + { + PPH_TREENEW_NODE node; + + node = TreeNew_GetFlatNode(TreeNewHandle, i); + + if (node) + { + for (j = 0; j < columns; j++) + { + PH_TREENEW_GET_CELL_TEXT getCellText; + + getCellText.Node = node; + getCellText.Id = displayToId[j]; + PhInitializeEmptyStringRef(&getCellText.Text); + TreeNew_GetCellText(TreeNewHandle, &getCellText); + + table[i + 1][j] = PhaCreateStringEx(getCellText.Text.Buffer, getCellText.Text.Length); + } + } + else + { + for (j = 0; j < columns; j++) + { + table[i + 1][j] = PH_AUTO(PhReferenceEmptyString()); + } + } + } + + PhFree(displayToText); + PhFree(displayToId); + + lines = PhaFormatTextTable(table, rows, columns, Mode); + + PhDeleteAutoPool(&autoPool); + + return lines; +} + +VOID PhaMapDisplayIndexListView( + _In_ HWND ListViewHandle, + _Out_writes_(Count) PULONG DisplayToId, + _Out_writes_opt_(Count) PPH_STRING *DisplayToText, + _In_ ULONG Count, + _Out_ PULONG NumberOfColumns + ) +{ + LVCOLUMN lvColumn; + ULONG i; + ULONG count; + WCHAR buffer[128]; + + count = 0; + lvColumn.mask = LVCF_ORDER | LVCF_TEXT; + lvColumn.pszText = buffer; + lvColumn.cchTextMax = sizeof(buffer) / sizeof(WCHAR); + + for (i = 0; i < Count; i++) + { + if (ListView_GetColumn(ListViewHandle, i, &lvColumn)) + { + ULONG displayIndex; + + displayIndex = (ULONG)lvColumn.iOrder; + assert(displayIndex < Count); + DisplayToId[displayIndex] = i; + + if (DisplayToText) + DisplayToText[displayIndex] = PhaCreateString(buffer); + + count++; + } + else + { + break; + } + } + + *NumberOfColumns = count; +} + +PPH_STRING PhaGetListViewItemText( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT SubItemIndex + ) +{ + PPH_STRING buffer; + SIZE_T allocatedCount; + SIZE_T count; + LVITEM lvItem; + + // Unfortunately LVM_GETITEMTEXT doesn't want to return the actual length of the text. + // Keep doubling the buffer size until we get a return count that is strictly less than + // the amount we allocated. + + buffer = NULL; + allocatedCount = 256; + count = allocatedCount; + + while (count >= allocatedCount) + { + if (buffer) + PhDereferenceObject(buffer); + + allocatedCount *= 2; + buffer = PhCreateStringEx(NULL, allocatedCount * sizeof(WCHAR)); + buffer->Buffer[0] = 0; + + lvItem.iSubItem = SubItemIndex; + lvItem.cchTextMax = (INT)allocatedCount + 1; + lvItem.pszText = buffer->Buffer; + count = SendMessage(ListViewHandle, LVM_GETITEMTEXT, Index, (LPARAM)&lvItem); + } + + PhTrimToNullTerminatorString(buffer); + PH_AUTO(buffer); + + return buffer; +} + +PPH_STRING PhGetListViewText( + _In_ HWND ListViewHandle + ) +{ + PH_AUTO_POOL autoPool; + PH_STRING_BUILDER stringBuilder; + ULONG displayToId[100]; + ULONG rows; + ULONG columns; + ULONG i; + ULONG j; + + PhInitializeAutoPool(&autoPool); + + PhaMapDisplayIndexListView(ListViewHandle, displayToId, NULL, 100, &columns); + rows = ListView_GetItemCount(ListViewHandle); + + PhInitializeStringBuilder(&stringBuilder, 0x100); + + for (i = 0; i < rows; i++) + { + if (!(ListView_GetItemState(ListViewHandle, i, LVIS_SELECTED) & LVIS_SELECTED)) + continue; + + for (j = 0; j < columns; j++) + { + PhAppendStringBuilder(&stringBuilder, &PhaGetListViewItemText(ListViewHandle, i, j)->sr); + PhAppendStringBuilder2(&stringBuilder, L", "); + } + + // Remove the trailing comma and space. + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + PhAppendStringBuilder2(&stringBuilder, L"\r\n"); + } + + PhDeleteAutoPool(&autoPool); + + return PhFinalStringBuilderString(&stringBuilder); +} + +PPH_LIST PhGetListViewLines( + _In_ HWND ListViewHandle, + _In_ ULONG Mode + ) +{ + PH_AUTO_POOL autoPool; + PPH_LIST lines; + ULONG rows; + ULONG columns; + ULONG displayToId[100]; + PPH_STRING displayToText[100]; + PPH_STRING **table; + ULONG i; + ULONG j; + + PhInitializeAutoPool(&autoPool); + + rows = ListView_GetItemCount(ListViewHandle) + 1; // +1 for column headers + + // Create the display index/text to ID map. + PhaMapDisplayIndexListView(ListViewHandle, displayToId, displayToText, 100, &columns); + + PhaCreateTextTable(&table, rows, columns); + + // Populate the first row with the column headers. + for (i = 0; i < columns; i++) + table[0][i] = displayToText[i]; + + // Populate the other rows with text. + for (i = 1; i < rows; i++) + { + for (j = 0; j < columns; j++) + { + // Important: use this to bypass extlv's hooking. + // extlv only hooks LVM_GETITEM, not LVM_GETITEMTEXT. + table[i][j] = PhaGetListViewItemText(ListViewHandle, i - 1, j); + } + } + + lines = PhaFormatTextTable(table, rows, columns, Mode); + + PhDeleteAutoPool(&autoPool); + + return lines; +} diff --git a/phlib/data.c b/phlib/data.c new file mode 100644 index 0000000..0389dd5 --- /dev/null +++ b/phlib/data.c @@ -0,0 +1,219 @@ +#include +#include + +// SIDs + +SID PhSeNobodySid = { SID_REVISION, 1, SECURITY_NULL_SID_AUTHORITY, { SECURITY_NULL_RID } }; + +SID PhSeEveryoneSid = { SID_REVISION, 1, SECURITY_WORLD_SID_AUTHORITY, { SECURITY_WORLD_RID } }; + +SID PhSeLocalSid = { SID_REVISION, 1, SECURITY_LOCAL_SID_AUTHORITY, { SECURITY_LOCAL_RID } }; + +SID PhSeCreatorOwnerSid = { SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, { SECURITY_CREATOR_OWNER_RID } }; +SID PhSeCreatorGroupSid = { SID_REVISION, 1, SECURITY_CREATOR_SID_AUTHORITY, { SECURITY_CREATOR_GROUP_RID } }; + +SID PhSeDialupSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_DIALUP_RID } }; +SID PhSeNetworkSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_NETWORK_RID } }; +SID PhSeBatchSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_BATCH_RID } }; +SID PhSeInteractiveSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_INTERACTIVE_RID } }; +SID PhSeServiceSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_SERVICE_RID } }; +SID PhSeAnonymousLogonSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_ANONYMOUS_LOGON_RID } }; +SID PhSeProxySid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_PROXY_RID } }; +SID PhSeAuthenticatedUserSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_AUTHENTICATED_USER_RID } }; +SID PhSeRestrictedCodeSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_RESTRICTED_CODE_RID } }; +SID PhSeTerminalServerUserSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_TERMINAL_SERVER_RID } }; +SID PhSeRemoteInteractiveLogonSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_REMOTE_LOGON_RID } }; +SID PhSeLocalSystemSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_LOCAL_SYSTEM_RID } }; +SID PhSeLocalServiceSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_LOCAL_SERVICE_RID } }; +SID PhSeNetworkServiceSid = { SID_REVISION, 1, SECURITY_NT_AUTHORITY, { SECURITY_NETWORK_SERVICE_RID } }; + +// Unicode + +PH_STRINGREF PhUnicodeByteOrderMark = PH_STRINGREF_INIT(L"\ufeff"); + +// Characters + +DECLSPEC_SELECTANY +BOOLEAN PhCharIsPrintable[256] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 - 15 */ // TAB, LF and CR are printable + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16 - 31 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ' ' - '/' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* '0' - '9' */ + 1, 1, 1, 1, 1, 1, 1, /* ':' - '@' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 'A' - 'Z' */ + 1, 1, 1, 1, 1, 1, /* '[' - '`' */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 'a' - 'z' */ + 1, 1, 1, 1, 0, /* '{' - 127 */ // DEL is not printable + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128 - 143 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 144 - 159 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 160 - 175 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 176 - 191 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 192 - 207 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 208 - 223 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 224 - 239 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 240 - 255 */ +}; + +DECLSPEC_SELECTANY +ULONG PhCharToInteger[256] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0 - 15 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 16 - 31 */ + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* ' ' - '/' */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, /* '0' - '9' */ + 52, 53, 54, 55, 56, 57, 58, /* ':' - '@' */ + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, /* 'A' - 'Z' */ + 59, 60, 61, 62, 63, 64, /* '[' - '`' */ + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, /* 'a' - 'z' */ + 65, 66, 67, 68, -1, /* '{' - 127 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 128 - 143 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 144 - 159 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 160 - 175 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 176 - 191 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 192 - 207 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 208 - 223 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 224 - 239 */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* 240 - 255 */ +}; + +DECLSPEC_SELECTANY +CHAR PhIntegerToChar[69] = + "0123456789" /* 0 - 9 */ + "abcdefghijklmnopqrstuvwxyz" /* 10 - 35 */ + " !\"#$%&'()*+,-./" /* 36 - 51 */ + ":;<=>?@" /* 52 - 58 */ + "[\\]^_`" /* 59 - 64 */ + "{|}~" /* 65 - 68 */ + ; + +DECLSPEC_SELECTANY +CHAR PhIntegerToCharUpper[69] = + "0123456789" /* 0 - 9 */ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* 10 - 35 */ + " !\"#$%&'()*+,-./" /* 36 - 51 */ + ":;<=>?@" /* 52 - 58 */ + "[\\]^_`" /* 59 - 64 */ + "{|}~" /* 65 - 68 */ + ; + +// CRC32 (IEEE 802.3) + +DECLSPEC_SELECTANY +ULONG PhCrc32Table[256] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +// Enums + +DECLSPEC_SELECTANY +WCHAR *PhIoPriorityHintNames[MaxIoPriorityTypes] = +{ + L"Very low", + L"Low", + L"Normal", + L"High", + L"Critical" +}; + +DECLSPEC_SELECTANY +WCHAR *PhPagePriorityNames[MEMORY_PRIORITY_NORMAL + 1] = +{ + L"Lowest", + L"Very low", + L"Low", + L"Medium", + L"Below normal", + L"Normal" +}; + +DECLSPEC_SELECTANY +WCHAR *PhKThreadStateNames[MaximumThreadState] = +{ + L"Initialized", + L"Ready", + L"Running", + L"Standby", + L"Terminated", + L"Waiting", + L"Transition", + L"DeferredReady", + L"GateWait", + L"WaitingForProcessInSwap" +}; + +DECLSPEC_SELECTANY +WCHAR *PhKWaitReasonNames[MaximumWaitReason] = +{ + L"Executive", + L"FreePage", + L"PageIn", + L"PoolAllocation", + L"DelayExecution", + L"Suspended", + L"UserRequest", + L"WrExecutive", + L"WrFreePage", + L"WrPageIn", + L"WrPoolAllocation", + L"WrDelayExecution", + L"WrSuspended", + L"WrUserRequest", + L"WrEventPair", + L"WrQueue", + L"WrLpcReceive", + L"WrLpcReply", + L"WrVirtualMemory", + L"WrPageOut", + L"WrRendezvous", + L"WrKeyedEvent", + L"WrTerminated", + L"WrProcessInSwap", + L"WrCpuRateControl", + L"WrCalloutStack", + L"WrKernel", + L"WrResource", + L"WrPushLock", + L"WrMutex", + L"WrQuantumEnd", + L"WrDispatchInt", + L"WrPreempted", + L"WrYieldExecution", + L"WrFastMutex", + L"WrGuardedMutex", + L"WrRundown", + L"WrAlertByThreadId", + L"WrDeferredPreempt" +}; diff --git a/phlib/dspick.c b/phlib/dspick.c new file mode 100644 index 0000000..697445d --- /dev/null +++ b/phlib/dspick.c @@ -0,0 +1,249 @@ +/* + * Process Hacker - + * DS object picker wrapper + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#define CINTERFACE +#define COBJMACROS +#include + +#define IDataObject_AddRef(This) ((This)->lpVtbl->AddRef(This)) +#define IDataObject_Release(This) ((This)->lpVtbl->Release(This)) +#define IDataObject_GetData(This, pformatetcIn, pmedium) ((This)->lpVtbl->GetData(This, pformatetcIn, pmedium)) + +#define IDsObjectPicker_QueryInterface(This, riid, ppvObject) ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) +#define IDsObjectPicker_AddRef(This) ((This)->lpVtbl->AddRef(This)) +#define IDsObjectPicker_Release(This) ((This)->lpVtbl->Release(This)) +#define IDsObjectPicker_Initialize(This, pInitInfo) ((This)->lpVtbl->Initialize(This, pInitInfo)) +#define IDsObjectPicker_InvokeDialog(This, hwndParent, ppdoSelections) ((This)->lpVtbl->InvokeDialog(This, hwndParent, ppdoSelections)) + +IDsObjectPicker *PhpCreateDsObjectPicker( + VOID + ) +{ + static CLSID CLSID_DsObjectPicker_I = { 0x17d6ccd8, 0x3b7b, 0x11d2, { 0xb9, 0xe0, 0x00, 0xc0, 0x4f, 0xd8, 0xdb, 0xf7 } }; + static IID IID_IDsObjectPicker_I = { 0x0c87e64e, 0x3b7a, 0x11d2, { 0xb9, 0xe0, 0x00, 0xc0, 0x4f, 0xd8, 0xdb, 0xf7 } }; + + IDsObjectPicker *picker; + + if (SUCCEEDED(CoCreateInstance( + &CLSID_DsObjectPicker_I, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IDsObjectPicker_I, + &picker + ))) + { + return picker; + } + else + { + return NULL; + } +} + +VOID PhFreeDsObjectPickerDialog( + _In_ PVOID PickerDialog + ) +{ + IDsObjectPicker_Release((IDsObjectPicker *)PickerDialog); +} + +PVOID PhCreateDsObjectPickerDialog( + _In_ ULONG Flags + ) +{ + IDsObjectPicker *picker; + DSOP_INIT_INFO initInfo; + DSOP_SCOPE_INIT_INFO scopeInit[1]; + + picker = PhpCreateDsObjectPicker(); + + if (!picker) + return NULL; + + memset(scopeInit, 0, sizeof(scopeInit)); + + scopeInit[0].cbSize = sizeof(DSOP_SCOPE_INIT_INFO); + scopeInit[0].flType = DSOP_SCOPE_TYPE_TARGET_COMPUTER; + scopeInit[0].flScope = + DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT | + DSOP_SCOPE_FLAG_WANT_SID_PATH | + DSOP_SCOPE_FLAG_DEFAULT_FILTER_USERS | + DSOP_SCOPE_FLAG_DEFAULT_FILTER_GROUPS; + scopeInit[0].FilterFlags.Uplevel.flBothModes = + DSOP_FILTER_INCLUDE_ADVANCED_VIEW | + DSOP_FILTER_USERS | + DSOP_FILTER_BUILTIN_GROUPS | + DSOP_FILTER_WELL_KNOWN_PRINCIPALS; + scopeInit[0].FilterFlags.flDownlevel = + DSOP_DOWNLEVEL_FILTER_USERS | + DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS | + DSOP_DOWNLEVEL_FILTER_GLOBAL_GROUPS | + DSOP_DOWNLEVEL_FILTER_ALL_WELLKNOWN_SIDS; + + memset(&initInfo, 0, sizeof(DSOP_INIT_INFO)); + initInfo.cbSize = sizeof(DSOP_INIT_INFO); + initInfo.pwzTargetComputer = NULL; + initInfo.cDsScopeInfos = 1; + initInfo.aDsScopeInfos = scopeInit; + initInfo.flOptions = DSOP_FLAG_SKIP_TARGET_COMPUTER_DC_CHECK; + + if (Flags & PH_DSPICK_MULTISELECT) + initInfo.flOptions |= DSOP_FLAG_MULTISELECT; + + if (!SUCCEEDED(IDsObjectPicker_Initialize(picker, &initInfo))) + { + IDsObjectPicker_Release(picker); + return NULL; + } + + return picker; +} + +PDS_SELECTION_LIST PhpGetDsSelectionList( + _In_ IDataObject *Selections + ) +{ + FORMATETC format; + STGMEDIUM medium; + + format.cfFormat = (CLIPFORMAT)RegisterClipboardFormat(L"CFSTR_DSOP_DS_SELECTION_LIST"); + format.ptd = NULL; + format.dwAspect = -1; + format.lindex = -1; + format.tymed = TYMED_HGLOBAL; + + if (SUCCEEDED(IDataObject_GetData(Selections, &format, &medium))) + { + if (medium.tymed != TYMED_HGLOBAL) + return NULL; + + return (PDS_SELECTION_LIST)GlobalLock(medium.hGlobal); + } + else + { + return NULL; + } +} + +BOOLEAN PhShowDsObjectPickerDialog( + _In_ HWND hWnd, + _In_ PVOID PickerDialog, + _Out_ PPH_DSPICK_OBJECTS *Objects + ) +{ + IDsObjectPicker *picker; + IDataObject *dataObject; + PDS_SELECTION_LIST selections; + PPH_DSPICK_OBJECTS objects; + ULONG i; + + picker = (IDsObjectPicker *)PickerDialog; + + if (!SUCCEEDED(IDsObjectPicker_InvokeDialog(picker, hWnd, &dataObject))) + return FALSE; + + if (!dataObject) + return FALSE; + + selections = PhpGetDsSelectionList(dataObject); + IDataObject_Release(dataObject); + + if (!selections) + return FALSE; + + objects = PhAllocate( + FIELD_OFFSET(PH_DSPICK_OBJECTS, Objects) + + selections->cItems * sizeof(PH_DSPICK_OBJECT) + ); + + objects->NumberOfObjects = selections->cItems; + + for (i = 0; i < selections->cItems; i++) + { + PDS_SELECTION selection; + PSID sid; + PH_STRINGREF path; + PH_STRINGREF prefix; + + selection = &selections->aDsSelection[i]; + + objects->Objects[i].Name = PhCreateString(selection->pwzName); + objects->Objects[i].Sid = NULL; + + if (selection->pwzADsPath && selection->pwzADsPath[0] != 0) + { + PhInitializeStringRef(&path, selection->pwzADsPath); + PhInitializeStringRef(&prefix, L"LDAP://" at end + + sid = PhAllocate(path.Length / sizeof(WCHAR) / 2); + + if (PhHexStringToBuffer(&path, (PUCHAR)sid)) + { + if (RtlValidSid(sid)) + objects->Objects[i].Sid = sid; + else + PhFree(sid); + } + else + { + PhFree(sid); + } + } + } + else + { + // Try to get the SID. + PhLookupName(&objects->Objects[i].Name->sr, &objects->Objects[i].Sid, NULL, NULL); + } + } + + *Objects = objects; + + return TRUE; +} + +VOID PhFreeDsObjectPickerObjects( + _In_ PPH_DSPICK_OBJECTS Objects + ) +{ + ULONG i; + + for (i = 0; i < Objects->NumberOfObjects; i++) + { + PhDereferenceObject(Objects->Objects[i].Name); + + if (Objects->Objects[i].Sid) + PhFree(Objects->Objects[i].Sid); + } + + PhFree(Objects); +} diff --git a/phlib/emenu.c b/phlib/emenu.c new file mode 100644 index 0000000..8170389 --- /dev/null +++ b/phlib/emenu.c @@ -0,0 +1,856 @@ +/* + * Process Hacker - + * extended menus + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +static const PH_FLAG_MAPPING EMenuTypeMappings[] = +{ + { PH_EMENU_MENUBARBREAK, MFT_MENUBARBREAK }, + { PH_EMENU_MENUBREAK, MFT_MENUBREAK }, + { PH_EMENU_RADIOCHECK, MFT_RADIOCHECK } +}; + +static const PH_FLAG_MAPPING EMenuStateMappings[] = +{ + { PH_EMENU_CHECKED, MFS_CHECKED }, + { PH_EMENU_DEFAULT, MFS_DEFAULT }, + { PH_EMENU_DISABLED, MFS_DISABLED }, + { PH_EMENU_HIGHLIGHT, MFS_HILITE } +}; + +PPH_EMENU_ITEM PhAllocateEMenuItem( + VOID + ) +{ + PPH_EMENU_ITEM item; + + item = PhAllocate(sizeof(PH_EMENU_ITEM)); + memset(item, 0, sizeof(PH_EMENU_ITEM)); + + return item; +} + +/** + * Creates a menu item. + * + * \param Flags A combination of the following: + * \li \c PH_EMENU_DISABLED The menu item is greyed and cannot be selected. + * \li \c PH_EMENU_CHECKED A check mark is displayed. + * \li \c PH_EMENU_HIGHLIGHT The menu item is highlighted. + * \li \c PH_EMENU_MENUBARBREAK Places the menu item in a new column, separated by a vertical line. + * \li \c PH_EMENU_MENUBREAK Places the menu item in a new column, with no vertical line. + * \li \c PH_EMENU_DEFAULT The menu item is displayed as the default item. This causes the text to + * be bolded. + * \li \c PH_EMENU_RADIOCHECK Uses a radio-button mark instead of a check mark. + * \param Id A unique identifier for the menu item. + * \param Text The text displayed for the menu item. + * \param Bitmap A bitmap image for the menu item. + * \param Context A user-defined value. + */ +PPH_EMENU_ITEM PhCreateEMenuItem( + _In_ ULONG Flags, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ HBITMAP Bitmap, + _In_opt_ PVOID Context + ) +{ + PPH_EMENU_ITEM item; + + item = PhAllocateEMenuItem(); + + item->Flags = Flags; + item->Id = Id; + item->Text = Text; + item->Bitmap = Bitmap; + + item->Context = Context; + + return item; +} + +/** + * Frees resources used by a menu item and its children. + * + * \param Item The menu item. + * + * \remarks The menu item is NOT automatically removed from its parent. It is safe to call this + * function while enumerating menu items. + */ +VOID PhpDestroyEMenuItem( + _In_ PPH_EMENU_ITEM Item + ) +{ + if (Item->DeleteFunction) + Item->DeleteFunction(Item); + + if ((Item->Flags & PH_EMENU_TEXT_OWNED) && Item->Text) + PhFree(Item->Text); + if ((Item->Flags & PH_EMENU_BITMAP_OWNED) && Item->Bitmap) + DeleteObject(Item->Bitmap); + + if (Item->Items) + { + ULONG i; + + for (i = 0; i < Item->Items->Count; i++) + { + PhpDestroyEMenuItem(Item->Items->Items[i]); + } + + PhDereferenceObject(Item->Items); + } + + PhFree(Item); +} + +/** + * Frees resources used by a menu item and its children. + * + * \param Item The menu item. + * + * \remarks The menu item is automatically removed from its parent. + */ +VOID PhDestroyEMenuItem( + _In_ PPH_EMENU_ITEM Item + ) +{ + // Remove the item from its parent, if it has one. + if (Item->Parent) + PhRemoveEMenuItem(NULL, Item, -1); + + PhpDestroyEMenuItem(Item); +} + +/** + * Finds a child menu item. + * + * \param Item The parent menu item. + * \param Flags A combination of the following: + * \li \c PH_EMENU_FIND_DESCEND Searches recursively within child menu items. + * \li \c PH_EMENU_FIND_STARTSWITH Performs a partial text search instead of an exact search. + * \li \c PH_EMENU_FIND_LITERAL Performs a literal search instead of ignoring prefix characters + * (ampersands). + * \param Text The text of the menu item to find. If NULL, the text is ignored. + * \param Id The identifier of the menu item to find. If 0, the identifier is ignored. + * + * \return The found menu item, or NULL if the menu item could not be found. + */ +PPH_EMENU_ITEM PhFindEMenuItem( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Flags, + _In_opt_ PWSTR Text, + _In_opt_ ULONG Id + ) +{ + return PhFindEMenuItemEx(Item, Flags, Text, Id, NULL, NULL); +} + +/** + * Finds a child menu item. + * + * \param Item The parent menu item. + * \param Flags A combination of the following: + * \li \c PH_EMENU_FIND_DESCEND Searches recursively within child menu items. + * \li \c PH_EMENU_FIND_STARTSWITH Performs a partial text search instead of an exact search. + * \li \c PH_EMENU_FIND_LITERAL Performs a literal search instead of ignoring prefix characters + * (ampersands). + * \param Text The text of the menu item to find. If NULL, the text is ignored. + * \param Id The identifier of the menu item to find. If 0, the identifier is ignored. + * \param FoundParent A variable which receives the parent of the found menu item. + * \param FoundIndex A variable which receives the index of the found menu item. + * + * \return The found menu item, or NULL if the menu item could not be found. + */ +PPH_EMENU_ITEM PhFindEMenuItemEx( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Flags, + _In_opt_ PWSTR Text, + _In_opt_ ULONG Id, + _Out_opt_ PPH_EMENU_ITEM *FoundParent, + _Out_opt_ PULONG FoundIndex + ) +{ + PH_STRINGREF searchText; + ULONG i; + PPH_EMENU_ITEM item; + + if (!Item->Items) + return NULL; + + if (Text && (Flags & PH_EMENU_FIND_LITERAL)) + PhInitializeStringRef(&searchText, Text); + + for (i = 0; i < Item->Items->Count; i++) + { + item = Item->Items->Items[i]; + + if (Text) + { + if (Flags & PH_EMENU_FIND_LITERAL) + { + PH_STRINGREF text; + + PhInitializeStringRef(&text, item->Text); + + if (Flags & PH_EMENU_FIND_STARTSWITH) + { + if (PhStartsWithStringRef(&text, &searchText, TRUE)) + goto FoundItemHere; + } + else + { + if (PhEqualStringRef(&text, &searchText, TRUE)) + goto FoundItemHere; + } + } + else + { + if (PhCompareUnicodeStringZIgnoreMenuPrefix(Text, item->Text, + TRUE, !!(Flags & PH_EMENU_FIND_STARTSWITH)) == 0) + goto FoundItemHere; + } + } + + if (Id && item->Id == Id) + goto FoundItemHere; + + if (Flags & PH_EMENU_FIND_DESCEND) + { + PPH_EMENU_ITEM foundItem; + PPH_EMENU_ITEM foundParent; + ULONG foundIndex; + + foundItem = PhFindEMenuItemEx(item, Flags, Text, Id, &foundParent, &foundIndex); + + if (foundItem) + { + if (FoundParent) + *FoundParent = foundParent; + if (FoundIndex) + *FoundIndex = foundIndex; + + return foundItem; + } + } + } + + return NULL; + +FoundItemHere: + if (FoundParent) + *FoundParent = Item; + if (FoundIndex) + *FoundIndex = i; + + return item; +} + +/** + * Determines the index of a menu item. + * + * \param Parent The parent menu item. + * \param Item The child menu item. + * + * \return The index of the menu item, or -1 if the menu item was not found in the parent menu item. + */ +ULONG PhIndexOfEMenuItem( + _In_ PPH_EMENU_ITEM Parent, + _In_ PPH_EMENU_ITEM Item + ) +{ + if (!Parent->Items) + return -1; + + return PhFindItemList(Parent->Items, Item); +} + +/** + * Inserts a menu item into a parent menu item. + * + * \param Parent The parent menu item. + * \param Item The menu item to insert. + * \param Index The index at which to insert the menu item. If the index is too large, the menu item + * is inserted at the last position. + */ +VOID PhInsertEMenuItem( + _Inout_ PPH_EMENU_ITEM Parent, + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG Index + ) +{ + // Remove the item from its old parent if it has one. + if (Item->Parent) + PhRemoveEMenuItem(Item->Parent, Item, 0); + + if (!Parent->Items) + Parent->Items = PhCreateList(16); + + if (Index > Parent->Items->Count) + Index = Parent->Items->Count; + + if (Index == -1) + PhAddItemList(Parent->Items, Item); + else + PhInsertItemList(Parent->Items, Index, Item); + + Item->Parent = Parent; +} + +/** + * Removes a menu item from its parent. + * + * \param Parent The parent menu item. If \a Item is NULL, this parameter must be specified. + * \param Item The child menu item. This may be NULL if \a Index is specified. + * \param Index The index of the menu item to remove. If \a Item is specified, this parameter is + * ignored. + */ +BOOLEAN PhRemoveEMenuItem( + _Inout_opt_ PPH_EMENU_ITEM Parent, + _In_opt_ PPH_EMENU_ITEM Item, + _In_opt_ ULONG Index + ) +{ + if (Item) + { + if (!Parent) + Parent = Item->Parent; + if (!Parent->Items) + return FALSE; + + Index = PhFindItemList(Parent->Items, Item); + + if (Index == -1) + return FALSE; + } + else + { + if (!Parent) + return FALSE; + if (!Parent->Items) + return FALSE; + } + + Item = Parent->Items->Items[Index]; + PhRemoveItemList(Parent->Items, Index); + Item->Parent = NULL; + + return TRUE; +} + +/** + * Removes all children from a menu item. + * + * \param Parent The parent menu item. + */ +VOID PhRemoveAllEMenuItems( + _Inout_ PPH_EMENU_ITEM Parent + ) +{ + ULONG i; + + if (!Parent->Items) + return; + + for (i = 0; i < Parent->Items->Count; i++) + { + PhpDestroyEMenuItem(Parent->Items->Items[i]); + } + + PhClearList(Parent->Items); +} + +/** + * Creates a root menu. + */ +PPH_EMENU PhCreateEMenu( + VOID + ) +{ + PPH_EMENU menu; + + menu = PhAllocate(sizeof(PH_EMENU)); + memset(menu, 0, sizeof(PH_EMENU)); + menu->Items = PhCreateList(16); + + return menu; +} + +/** + * Frees resources used by a root menu and its children. + * + * \param Menu A root menu. + */ +VOID PhDestroyEMenu( + _In_ PPH_EMENU Menu + ) +{ + ULONG i; + + for (i = 0; i < Menu->Items->Count; i++) + { + PhpDestroyEMenuItem(Menu->Items->Items[i]); + } + + PhDereferenceObject(Menu->Items); + PhFree(Menu); +} + +/** + * Initializes a data structure containing additional information resulting from a call to + * PhEMenuToHMenu(). + */ +VOID PhInitializeEMenuData( + _Out_ PPH_EMENU_DATA Data + ) +{ + Data->IdToItem = PhCreateList(16); +} + +/** + * Frees resources used by a data structure initialized by PhInitializeEMenuData(). + */ +VOID PhDeleteEMenuData( + _Inout_ PPH_EMENU_DATA Data + ) +{ + PhDereferenceObject(Data->IdToItem); +} + +/** + * Converts an EMENU to a Windows menu object. + * + * \param Menu The menu item to convert. + * \param Flags A combination of the following: + * \li \c PH_EMENU_CONVERT_ID Automatically assigns a unique identifier to each converted menu item. + * The resulting mappings are placed in \a Data. + * \param Data Additional data resulting from the conversion. The data structure must be initialized + * by PhInitializeEMenuData() prior to calling this function. + * + * \return A menu handle. The menu object must be destroyed using DestroyMenu() when it is no longer + * needed. + */ +HMENU PhEMenuToHMenu( + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG Flags, + _Inout_opt_ PPH_EMENU_DATA Data + ) +{ + HMENU menuHandle; + + menuHandle = CreatePopupMenu(); + + if (!menuHandle) + return NULL; + + PhEMenuToHMenu2(menuHandle, Menu, Flags, Data); + + if (!(Menu->Flags & PH_EMENU_SEPARATECHECKSPACE)) + { + MENUINFO menuInfo; + + memset(&menuInfo, 0, sizeof(MENUINFO)); + menuInfo.cbSize = sizeof(MENUINFO); + menuInfo.fMask = MIM_STYLE; + menuInfo.dwStyle = MNS_CHECKORBMP; + SetMenuInfo(menuHandle, &menuInfo); + } + + return menuHandle; +} + +/** + * Converts an EMENU to a Windows menu object. + * + * \param MenuHandle A handle to a Windows menu object. + * \param Menu The menu item to convert. The items are appended to \a MenuHandle. + * \param Flags A combination of the following: + * \li \c PH_EMENU_CONVERT_ID Automatically assigns a unique identifier to each converted menu item. + * The resulting mappings are placed in \a Data. + * \param Data Additional data resulting from the conversion. The data structure must be initialized + * by PhInitializeEMenuData() prior to calling this function. + */ +VOID PhEMenuToHMenu2( + _In_ HMENU MenuHandle, + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG Flags, + _Inout_opt_ PPH_EMENU_DATA Data + ) +{ + ULONG i; + PPH_EMENU_ITEM item; + MENUITEMINFO menuItemInfo; + + for (i = 0; i < Menu->Items->Count; i++) + { + item = Menu->Items->Items[i]; + + memset(&menuItemInfo, 0, sizeof(MENUITEMINFO)); + menuItemInfo.cbSize = sizeof(MENUITEMINFO); + + // Type + + menuItemInfo.fMask = MIIM_FTYPE | MIIM_STATE; + + if (item->Flags & PH_EMENU_SEPARATOR) + { + menuItemInfo.fType = MFT_SEPARATOR; + } + else + { + menuItemInfo.fType = MFT_STRING; + menuItemInfo.fMask |= MIIM_STRING; + menuItemInfo.dwTypeData = item->Text; + } + + PhMapFlags1( + &menuItemInfo.fType, + item->Flags, + EMenuTypeMappings, + sizeof(EMenuTypeMappings) / sizeof(PH_FLAG_MAPPING) + ); + + // Bitmap + + if (item->Bitmap) + { + menuItemInfo.fMask |= MIIM_BITMAP; + menuItemInfo.hbmpItem = item->Bitmap; + } + + // Id + + if (Flags & PH_EMENU_CONVERT_ID) + { + ULONG id; + + if (Data) + { + PhAddItemList(Data->IdToItem, item); + id = Data->IdToItem->Count; + + menuItemInfo.fMask |= MIIM_ID; + menuItemInfo.wID = id; + } + } + else + { + if (item->Id) + { + menuItemInfo.fMask |= MIIM_ID; + menuItemInfo.wID = item->Id; + } + } + + // State + + PhMapFlags1( + &menuItemInfo.fState, + item->Flags, + EMenuStateMappings, + sizeof(EMenuStateMappings) / sizeof(PH_FLAG_MAPPING) + ); + + // Context + + menuItemInfo.fMask |= MIIM_DATA; + menuItemInfo.dwItemData = (ULONG_PTR)item; + + // Submenu + + if (item->Items && item->Items->Count != 0) + { + menuItemInfo.fMask |= MIIM_SUBMENU; + menuItemInfo.hSubMenu = PhEMenuToHMenu(item, Flags, Data); + } + + InsertMenuItem(MenuHandle, MAXINT, TRUE, &menuItemInfo); + } +} + +/** + * Converts a Windows menu object to an EMENU. + * + * \param MenuItem The menu item in which the converted menu items will be placed. + * \param MenuHandle A menu handle. + */ +VOID PhHMenuToEMenuItem( + _Inout_ PPH_EMENU_ITEM MenuItem, + _In_ HMENU MenuHandle + ) +{ + ULONG i; + ULONG count; + + count = GetMenuItemCount(MenuHandle); + + if (count != -1) + { + for (i = 0; i < count; i++) + { + MENUITEMINFO menuItemInfo; + WCHAR buffer[256]; + PPH_EMENU_ITEM menuItem; + + menuItemInfo.cbSize = sizeof(menuItemInfo); + menuItemInfo.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING | MIIM_SUBMENU; + menuItemInfo.cch = sizeof(buffer) / sizeof(WCHAR); + menuItemInfo.dwTypeData = buffer; + + if (!GetMenuItemInfo(MenuHandle, i, TRUE, &menuItemInfo)) + continue; + + menuItem = PhCreateEMenuItem( + PH_EMENU_TEXT_OWNED, + menuItemInfo.wID, + PhDuplicateStringZ(buffer), + NULL, + NULL + ); + + if (menuItemInfo.fType & MFT_SEPARATOR) + menuItem->Flags |= PH_EMENU_SEPARATOR; + + PhMapFlags2( + &menuItem->Flags, + menuItemInfo.fType, + EMenuTypeMappings, + sizeof(EMenuTypeMappings) / sizeof(PH_FLAG_MAPPING) + ); + PhMapFlags2( + &menuItem->Flags, + menuItemInfo.fState, + EMenuStateMappings, + sizeof(EMenuStateMappings) / sizeof(PH_FLAG_MAPPING) + ); + + if (menuItemInfo.hSubMenu) + PhHMenuToEMenuItem(menuItem, menuItemInfo.hSubMenu); + + PhInsertEMenuItem(MenuItem, menuItem, -1); + } + } +} + +/** + * Loads a menu resource and converts it to an EMENU. + * + * \param MenuItem The menu item in which the converted menu items will be placed. + * \param InstanceHandle The module containing the menu resource. + * \param Resource The resource identifier. + * \param SubMenuIndex The index of the sub menu to use, or -1 to use the root menu. + */ +VOID PhLoadResourceEMenuItem( + _Inout_ PPH_EMENU_ITEM MenuItem, + _In_ HINSTANCE InstanceHandle, + _In_ PWSTR Resource, + _In_ ULONG SubMenuIndex + ) +{ + HMENU menu; + HMENU realMenu; + + menu = LoadMenu(InstanceHandle, Resource); + + if (SubMenuIndex != -1) + realMenu = GetSubMenu(menu, SubMenuIndex); + else + realMenu = menu; + + PhHMenuToEMenuItem(MenuItem, realMenu); + + DestroyMenu(menu); +} + +/** + * Displays a menu. + * + * \param Menu A menu. + * \param WindowHandle The window that owns the popup menu. + * \param Flags A combination of the following: + * \li \c PH_EMENU_SHOW_SEND_COMMAND A WM_COMMAND message is sent to the window when the user clicks + * on a menu item. + * \li \c PH_EMENU_SHOW_LEFTRIGHT The user can select menu items with both the left and right mouse + * buttons. + * \param Align The alignment of the menu. + * \param X The horizontal location of the menu. + * \param Y The vertical location of the menu. + * + * \return The selected menu item, or NULL if the menu was cancelled. + */ +PPH_EMENU_ITEM PhShowEMenu( + _In_ PPH_EMENU Menu, + _In_ HWND WindowHandle, + _In_ ULONG Flags, + _In_ ULONG Align, + _In_ ULONG X, + _In_ ULONG Y + ) +{ + PPH_EMENU_ITEM selectedItem; + ULONG result; + ULONG flags; + PH_EMENU_DATA data; + HMENU popupMenu; + + selectedItem = NULL; + flags = TPM_RETURNCMD | TPM_NONOTIFY; + + // Flags + + if (Flags & PH_EMENU_SHOW_LEFTRIGHT) + flags |= TPM_RIGHTBUTTON; + else + flags |= TPM_LEFTBUTTON; + + // Align + + if (Align & PH_ALIGN_LEFT) + flags |= TPM_LEFTALIGN; + else if (Align & PH_ALIGN_RIGHT) + flags |= TPM_RIGHTALIGN; + else + flags |= TPM_CENTERALIGN; + + if (Align & PH_ALIGN_TOP) + flags |= TPM_TOPALIGN; + else if (Align & PH_ALIGN_BOTTOM) + flags |= TPM_BOTTOMALIGN; + else + flags |= TPM_VCENTERALIGN; + + PhInitializeEMenuData(&data); + + if (popupMenu = PhEMenuToHMenu(Menu, PH_EMENU_CONVERT_ID, &data)) + { + result = TrackPopupMenu( + popupMenu, + flags, + X, + Y, + 0, + WindowHandle, + NULL + ); + + if (result != 0) + { + selectedItem = data.IdToItem->Items[result - 1]; + } + + DestroyMenu(popupMenu); + } + + PhDeleteEMenuData(&data); + + if ((Flags & PH_EMENU_SHOW_SEND_COMMAND) && selectedItem && selectedItem->Id != 0) + SendMessage(WindowHandle, WM_COMMAND, MAKEWPARAM(selectedItem->Id, 0), 0); + + return selectedItem; +} + +/** + * Sets the flags of a menu item. + * + * \param Item The parent menu item. + * \param Id The identifier of the child menu item. + * \param Mask The flags to modify. + * \param Value The new value of the flags. + */ +BOOLEAN PhSetFlagsEMenuItem( + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG Id, + _In_ ULONG Mask, + _In_ ULONG Value + ) +{ + PPH_EMENU_ITEM item; + + item = PhFindEMenuItem(Item, PH_EMENU_FIND_DESCEND, NULL, Id); + + if (item) + { + item->Flags &= ~Mask; + item->Flags |= Value; + + return TRUE; + } + else + { + return FALSE; + } +} + +/** + * Sets flags for all children of a menu item. + * + * \param Item The parent menu item. + * \param Mask The flags to modify. + * \param Value The new value of the flags. + */ +VOID PhSetFlagsAllEMenuItems( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Mask, + _In_ ULONG Value + ) +{ + ULONG i; + + for (i = 0; i < Item->Items->Count; i++) + { + PPH_EMENU_ITEM item = Item->Items->Items[i]; + + item->Flags &= ~Mask; + item->Flags |= Value; + } +} + +VOID PhModifyEMenuItem( + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG ModifyFlags, + _In_ ULONG OwnedFlags, + _In_opt_ PWSTR Text, + _In_opt_ HBITMAP Bitmap + ) +{ + if (ModifyFlags & PH_EMENU_MODIFY_TEXT) + { + if ((Item->Flags & PH_EMENU_TEXT_OWNED) && Item->Text) + PhFree(Item->Text); + + Item->Text = Text; + Item->Flags &= ~PH_EMENU_TEXT_OWNED; + Item->Flags |= OwnedFlags & PH_EMENU_TEXT_OWNED; + } + + if (ModifyFlags & PH_EMENU_MODIFY_BITMAP) + { + if ((Item->Flags & PH_EMENU_BITMAP_OWNED) && Item->Bitmap) + DeleteObject(Item->Bitmap); + + Item->Bitmap = Bitmap; + Item->Flags &= ~PH_EMENU_BITMAP_OWNED; + Item->Flags |= OwnedFlags & PH_EMENU_BITMAP_OWNED; + } +} diff --git a/phlib/error.c b/phlib/error.c new file mode 100644 index 0000000..b0c27c4 --- /dev/null +++ b/phlib/error.c @@ -0,0 +1,92 @@ +/* + * Process Hacker - + * error codes + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include + +/** + * Converts a NTSTATUS value to a Win32 error code. + * + * \remarks This function handles FACILITY_NTWIN32 status values properly, unlike + * RtlNtStatusToDosError. + */ +ULONG PhNtStatusToDosError( + _In_ NTSTATUS Status + ) +{ + if (NT_NTWIN32(Status)) // RtlNtStatusToDosError doesn't seem to handle these cases correctly + return WIN32_FROM_NTSTATUS(Status); + else + return RtlNtStatusToDosError(Status); +} + +/** + * Converts a Win32 error code to a NTSTATUS value. + * + * \remarks Only a small number of cases are currently supported. Other status values are wrapped + * using FACILITY_NTWIN32. + */ +NTSTATUS PhDosErrorToNtStatus( + _In_ ULONG DosError + ) +{ + switch (DosError) + { + case ERROR_INVALID_FUNCTION: return STATUS_ILLEGAL_FUNCTION; + case ERROR_FILE_NOT_FOUND: return STATUS_NO_SUCH_FILE; + case ERROR_ACCESS_DENIED: return STATUS_ACCESS_DENIED; + case ERROR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; + case ERROR_HANDLE_EOF: return STATUS_END_OF_FILE; + case ERROR_NOT_SUPPORTED: return STATUS_NOT_SUPPORTED; + case ERROR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; + case ERROR_NOT_LOCKED: return STATUS_NOT_LOCKED; + case ERROR_MORE_DATA: return STATUS_MORE_ENTRIES; + case ERROR_NOACCESS: return STATUS_ACCESS_VIOLATION; + case ERROR_STACK_OVERFLOW: return STATUS_STACK_OVERFLOW; + case ERROR_INTERNAL_ERROR: return STATUS_INTERNAL_ERROR; + default: return NTSTATUS_FROM_WIN32(DosError); + } +} + +/** + * Determines whether a NTSTATUS value indicates that a file cannot be not found. + */ +BOOLEAN PhNtStatusFileNotFound( + _In_ NTSTATUS Status + ) +{ + switch (Status) + { + case STATUS_NO_SUCH_FILE: + return TRUE; + case STATUS_OBJECT_NAME_INVALID: + return TRUE; + case STATUS_OBJECT_NAME_NOT_FOUND: + return TRUE; + case STATUS_OBJECT_NO_LONGER_EXISTS: + return TRUE; + case STATUS_OBJECT_PATH_INVALID: + return TRUE; + case STATUS_OBJECT_PATH_NOT_FOUND: + return TRUE; + default: return FALSE; + } +} diff --git a/phlib/extlv.c b/phlib/extlv.c new file mode 100644 index 0000000..a091018 --- /dev/null +++ b/phlib/extlv.c @@ -0,0 +1,748 @@ +/* + * Process Hacker - + * extended list view + * + * Copyright (C) 2010-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The extended list view adds some functionality to the default list view control, such as sorting, + * item colors and fonts, better redraw disabling, and the ability to change the cursor. This is + * currently implemented by hooking the window procedure. + */ + +#include +#include +#include + +#define PH_MAX_COMPARE_FUNCTIONS 16 + +typedef struct _PH_EXTLV_CONTEXT +{ + HWND Handle; + WNDPROC OldWndProc; + PVOID Context; + + // Sorting + + BOOLEAN TriState; + ULONG SortColumn; + PH_SORT_ORDER SortOrder; + BOOLEAN SortFast; + + PPH_COMPARE_FUNCTION TriStateCompareFunction; + PPH_COMPARE_FUNCTION CompareFunctions[PH_MAX_COMPARE_FUNCTIONS]; + ULONG FallbackColumns[PH_MAX_COMPARE_FUNCTIONS]; + ULONG NumberOfFallbackColumns; + + // Color and Font + PPH_EXTLV_GET_ITEM_COLOR ItemColorFunction; + PPH_EXTLV_GET_ITEM_FONT ItemFontFunction; + + // Misc. + + LONG EnableRedraw; + HCURSOR Cursor; +} PH_EXTLV_CONTEXT, *PPH_EXTLV_CONTEXT; + +LRESULT CALLBACK PhpExtendedListViewWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT PhpExtendedListViewCompareFunc( + _In_ LPARAM lParam1, + _In_ LPARAM lParam2, + _In_ LPARAM lParamSort + ); + +INT PhpExtendedListViewCompareFastFunc( + _In_ LPARAM lParam1, + _In_ LPARAM lParam2, + _In_ LPARAM lParamSort + ); + +INT PhpCompareListViewItems( + _In_ PPH_EXTLV_CONTEXT Context, + _In_ INT X, + _In_ INT Y, + _In_ PVOID XParam, + _In_ PVOID YParam, + _In_ ULONG Column, + _In_ BOOLEAN EnableDefault + ); + +INT PhpDefaultCompareListViewItems( + _In_ PPH_EXTLV_CONTEXT Context, + _In_ INT X, + _In_ INT Y, + _In_ ULONG Column + ); + +static PWSTR PhpMakeExtLvContextAtom( + VOID + ) +{ + PH_DEFINE_MAKE_ATOM(L"PhLib_ExtLvContext"); +} + +/** + * Enables extended list view support for a list view control. + * + * \param hWnd A handle to the list view control. + */ +VOID PhSetExtendedListView( + _In_ HWND hWnd + ) +{ + WNDPROC oldWndProc; + PPH_EXTLV_CONTEXT context; + + oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PhpExtendedListViewWndProc); + + context = PhAllocate(sizeof(PH_EXTLV_CONTEXT)); + + context->Handle = hWnd; + context->OldWndProc = oldWndProc; + context->Context = NULL; + context->TriState = FALSE; + context->SortColumn = 0; + context->SortOrder = AscendingSortOrder; + context->SortFast = FALSE; + context->TriStateCompareFunction = NULL; + memset(context->CompareFunctions, 0, sizeof(context->CompareFunctions)); + context->NumberOfFallbackColumns = 0; + + context->ItemColorFunction = NULL; + context->ItemFontFunction = NULL; + + context->EnableRedraw = 1; + context->Cursor = NULL; + + SetProp(hWnd, PhpMakeExtLvContextAtom(), (HANDLE)context); + + ExtendedListView_Init(hWnd); +} + +LRESULT CALLBACK PhpExtendedListViewWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_EXTLV_CONTEXT context; + WNDPROC oldWndProc; + + context = (PPH_EXTLV_CONTEXT)GetProp(hwnd, PhpMakeExtLvContextAtom()); + oldWndProc = context->OldWndProc; + + switch (uMsg) + { + case WM_DESTROY: + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + PhFree(context); + RemoveProp(hwnd, PhpMakeExtLvContextAtom()); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case HDN_ITEMCLICK: + { + HWND headerHandle; + + headerHandle = (HWND)CallWindowProc(context->OldWndProc, hwnd, LVM_GETHEADER, 0, 0); + + if (header->hwndFrom == headerHandle) + { + LPNMHEADER header2 = (LPNMHEADER)header; + + if (header2->iItem == context->SortColumn) + { + if (context->TriState) + { + if (context->SortOrder == AscendingSortOrder) + context->SortOrder = DescendingSortOrder; + else if (context->SortOrder == DescendingSortOrder) + context->SortOrder = NoSortOrder; + else + context->SortOrder = AscendingSortOrder; + } + else + { + if (context->SortOrder == AscendingSortOrder) + context->SortOrder = DescendingSortOrder; + else + context->SortOrder = AscendingSortOrder; + } + } + else + { + context->SortColumn = header2->iItem; + context->SortOrder = AscendingSortOrder; + } + + PhSetHeaderSortIcon(headerHandle, context->SortColumn, context->SortOrder); + ExtendedListView_SortItems(hwnd); + } + } + break; + } + } + break; + case WM_REFLECT + WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case NM_CUSTOMDRAW: + { + if (header->hwndFrom == hwnd) + { + LPNMLVCUSTOMDRAW customDraw = (LPNMLVCUSTOMDRAW)header; + + switch (customDraw->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + { + BOOLEAN colorChanged = FALSE; + HFONT newFont = NULL; + + if (context->ItemColorFunction) + { + customDraw->clrTextBk = context->ItemColorFunction( + (INT)customDraw->nmcd.dwItemSpec, + (PVOID)customDraw->nmcd.lItemlParam, + context->Context + ); + colorChanged = TRUE; + } + + if (context->ItemFontFunction) + { + newFont = context->ItemFontFunction( + (INT)customDraw->nmcd.dwItemSpec, + (PVOID)customDraw->nmcd.lItemlParam, + context->Context + ); + } + + if (newFont) + SelectObject(customDraw->nmcd.hdc, newFont); + + if (colorChanged) + { + if (PhGetColorBrightness(customDraw->clrTextBk) > 100) // slightly less than half + customDraw->clrText = RGB(0x00, 0x00, 0x00); + else + customDraw->clrText = RGB(0xff, 0xff, 0xff); + } + + if (!newFont) + return CDRF_DODEFAULT; + else + return CDRF_NEWFONT; + } + break; + } + } + } + break; + } + } + break; + case WM_SETCURSOR: + { + if (context->Cursor) + { + SetCursor(context->Cursor); + return TRUE; + } + } + break; + case WM_UPDATEUISTATE: + { + // Disable focus rectangles by setting or masking out the flag where appropriate. + switch (LOWORD(wParam)) + { + case UIS_SET: + wParam |= UISF_HIDEFOCUS << 16; + break; + case UIS_CLEAR: + case UIS_INITIALIZE: + wParam &= ~(UISF_HIDEFOCUS << 16); + break; + } + } + break; + case ELVM_ADDFALLBACKCOLUMN: + { + if (context->NumberOfFallbackColumns < PH_MAX_COMPARE_FUNCTIONS) + context->FallbackColumns[context->NumberOfFallbackColumns++] = (ULONG)wParam; + else + return FALSE; + } + return TRUE; + case ELVM_ADDFALLBACKCOLUMNS: + { + ULONG numberOfColumns = (ULONG)wParam; + PULONG columns = (PULONG)lParam; + + if (context->NumberOfFallbackColumns + numberOfColumns <= PH_MAX_COMPARE_FUNCTIONS) + { + memcpy( + &context->FallbackColumns[context->NumberOfFallbackColumns], + columns, + numberOfColumns * sizeof(ULONG) + ); + context->NumberOfFallbackColumns += numberOfColumns; + } + else + { + return FALSE; + } + } + return TRUE; + case ELVM_INIT: + { + PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); + + // HACK to fix tooltips showing behind Always On Top windows. + SetWindowPos(ListView_GetToolTips(hwnd), HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + + // Make sure focus rectangles are disabled. + SendMessage(hwnd, WM_CHANGEUISTATE, MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0); + } + return TRUE; + case ELVM_SETCOLUMNWIDTH: + { + ULONG column = (ULONG)wParam; + LONG width = (LONG)lParam; + + if (width == ELVSCW_AUTOSIZE_REMAININGSPACE) + { + RECT clientRect; + LONG availableWidth; + ULONG i; + LVCOLUMN lvColumn; + + GetClientRect(hwnd, &clientRect); + availableWidth = clientRect.right; + i = 0; + lvColumn.mask = LVCF_WIDTH; + + while (TRUE) + { + if (i != column) + { + if (CallWindowProc(oldWndProc, hwnd, LVM_GETCOLUMN, i, (LPARAM)&lvColumn)) + { + availableWidth -= lvColumn.cx; + } + else + { + break; + } + } + + i++; + } + + if (availableWidth >= 40) + return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, availableWidth); + } + + return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, width); + } + break; + case ELVM_SETCOMPAREFUNCTION: + { + ULONG column = (ULONG)wParam; + PPH_COMPARE_FUNCTION compareFunction = (PPH_COMPARE_FUNCTION)lParam; + + if (column >= PH_MAX_COMPARE_FUNCTIONS) + return FALSE; + + context->CompareFunctions[column] = compareFunction; + } + return TRUE; + case ELVM_SETCONTEXT: + { + context->Context = (PVOID)lParam; + } + return TRUE; + case ELVM_SETCURSOR: + { + context->Cursor = (HCURSOR)lParam; + } + return TRUE; + case ELVM_SETITEMCOLORFUNCTION: + { + context->ItemColorFunction = (PPH_EXTLV_GET_ITEM_COLOR)lParam; + } + return TRUE; + case ELVM_SETITEMFONTFUNCTION: + { + context->ItemFontFunction = (PPH_EXTLV_GET_ITEM_FONT)lParam; + } + return TRUE; + case ELVM_SETREDRAW: + { + if (wParam) + context->EnableRedraw++; + else + context->EnableRedraw--; + + if (context->EnableRedraw == 1) + { + SendMessage(hwnd, WM_SETREDRAW, TRUE, 0); + InvalidateRect(hwnd, NULL, FALSE); + } + else if (context->EnableRedraw == 0) + { + SendMessage(hwnd, WM_SETREDRAW, FALSE, 0); + } + } + return TRUE; + case ELVM_SETSORT: + { + context->SortColumn = (ULONG)wParam; + context->SortOrder = (PH_SORT_ORDER)lParam; + + PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); + } + return TRUE; + case ELVM_SETSORTFAST: + { + context->SortFast = !!wParam; + } + return TRUE; + case ELVM_SETTRISTATE: + { + context->TriState = !!wParam; + } + return TRUE; + case ELVM_SETTRISTATECOMPAREFUNCTION: + { + context->TriStateCompareFunction = (PPH_COMPARE_FUNCTION)lParam; + } + return TRUE; + case ELVM_SORTITEMS: + { + if (context->SortFast) + { + // This sort method is faster than the normal sort because our comparison function + // doesn't have to call the list view window procedure to get the item lParam + // values. The disadvantage of this method is that default sorting is not available + // - if a column doesn't have a comparison function, it doesn't get sorted at all. + + ListView_SortItems( + hwnd, + PhpExtendedListViewCompareFastFunc, + (LPARAM)context + ); + } + else + { + ListView_SortItemsEx( + hwnd, + PhpExtendedListViewCompareFunc, + (LPARAM)context + ); + } + } + return TRUE; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +/** + * Visually indicates the sort order of a header control item. + * + * \param hwnd A handle to the header control. + * \param Index The index of the item. + * \param Order The sort order of the item. + */ +VOID PhSetHeaderSortIcon( + _In_ HWND hwnd, + _In_ INT Index, + _In_ PH_SORT_ORDER Order + ) +{ + ULONG count; + ULONG i; + + count = Header_GetItemCount(hwnd); + + if (count == -1) + return; + + for (i = 0; i < count; i++) + { + HDITEM item; + + item.mask = HDI_FORMAT; + Header_GetItem(hwnd, i, &item); + + if (Order != NoSortOrder && i == Index) + { + if (Order == AscendingSortOrder) + { + item.fmt &= ~HDF_SORTDOWN; + item.fmt |= HDF_SORTUP; + } + else if (Order == DescendingSortOrder) + { + item.fmt &= ~HDF_SORTUP; + item.fmt |= HDF_SORTDOWN; + } + } + else + { + item.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); + } + + Header_SetItem(hwnd, i, &item); + } +} + +static INT PhpExtendedListViewCompareFunc( + _In_ LPARAM lParam1, + _In_ LPARAM lParam2, + _In_ LPARAM lParamSort + ) +{ + PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort; + INT result; + INT x = (INT)lParam1; + INT y = (INT)lParam2; + ULONG i; + PULONG fallbackColumns; + LVITEM xItem; + LVITEM yItem; + + // Get the param values. + + xItem.mask = LVIF_PARAM | LVIF_STATE; + xItem.iItem = x; + xItem.iSubItem = 0; + + yItem.mask = LVIF_PARAM | LVIF_STATE; + yItem.iItem = y; + yItem.iSubItem = 0; + + // Don't use SendMessage/ListView_* because it will call our new window procedure, which will + // use GetProp. This calls NtUserGetProp, and obviously having a system call in a comparison + // function is very, very bad for performance. + + if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&xItem)) + return 0; + if (!CallWindowProc(context->OldWndProc, context->Handle, LVM_GETITEM, 0, (LPARAM)&yItem)) + return 0; + + // First, do tri-state sorting. + + if ( + context->TriState && + context->SortOrder == NoSortOrder && + context->TriStateCompareFunction + ) + { + result = context->TriStateCompareFunction( + (PVOID)xItem.lParam, + (PVOID)yItem.lParam, + context->Context + ); + + if (result != 0) + return result; + } + + // Compare using the user-selected column and move on to the fallback columns if necessary. + + result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, context->SortColumn, TRUE); + + if (result != 0) + return result; + + fallbackColumns = context->FallbackColumns; + + for (i = context->NumberOfFallbackColumns; i != 0; i--) + { + ULONG fallbackColumn = *fallbackColumns++; + + if (fallbackColumn == context->SortColumn) + continue; + + result = PhpCompareListViewItems(context, x, y, (PVOID)xItem.lParam, (PVOID)yItem.lParam, fallbackColumn, TRUE); + + if (result != 0) + return result; + } + + return 0; +} + +static INT PhpExtendedListViewCompareFastFunc( + _In_ LPARAM lParam1, + _In_ LPARAM lParam2, + _In_ LPARAM lParamSort + ) +{ + PPH_EXTLV_CONTEXT context = (PPH_EXTLV_CONTEXT)lParamSort; + INT result; + ULONG i; + PULONG fallbackColumns; + + if (!lParam1 || !lParam2) + return 0; + + // First, do tri-state sorting. + + if ( + context->TriState && + context->SortOrder == NoSortOrder && + context->TriStateCompareFunction + ) + { + result = context->TriStateCompareFunction( + (PVOID)lParam1, + (PVOID)lParam2, + context->Context + ); + + if (result != 0) + return result; + } + + // Compare using the user-selected column and move on to the fallback columns if necessary. + + result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, context->SortColumn, FALSE); + + if (result != 0) + return result; + + fallbackColumns = context->FallbackColumns; + + for (i = context->NumberOfFallbackColumns; i != 0; i--) + { + ULONG fallbackColumn = *fallbackColumns++; + + if (fallbackColumn == context->SortColumn) + continue; + + result = PhpCompareListViewItems(context, 0, 0, (PVOID)lParam1, (PVOID)lParam2, fallbackColumn, FALSE); + + if (result != 0) + return result; + } + + return 0; +} + +static FORCEINLINE INT PhpCompareListViewItems( + _In_ PPH_EXTLV_CONTEXT Context, + _In_ INT X, + _In_ INT Y, + _In_ PVOID XParam, + _In_ PVOID YParam, + _In_ ULONG Column, + _In_ BOOLEAN EnableDefault + ) +{ + INT result = 0; + + if ( + Column < PH_MAX_COMPARE_FUNCTIONS && + Context->CompareFunctions[Column] + ) + { + result = PhModifySort( + Context->CompareFunctions[Column](XParam, YParam, Context->Context), + Context->SortOrder + ); + + if (result != 0) + return result; + } + + if (EnableDefault) + { + return PhModifySort( + PhpDefaultCompareListViewItems(Context, X, Y, Column), + Context->SortOrder + ); + } + else + { + return 0; + } +} + +static INT PhpDefaultCompareListViewItems( + _In_ PPH_EXTLV_CONTEXT Context, + _In_ INT X, + _In_ INT Y, + _In_ ULONG Column + ) +{ + WCHAR xText[261]; + WCHAR yText[261]; + LVITEM item; + + // Get the X item text. + + item.mask = LVIF_TEXT; + item.iItem = X; + item.iSubItem = Column; + item.pszText = xText; + item.cchTextMax = 260; + + xText[0] = 0; + CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item); + + // Get the Y item text. + + item.iItem = Y; + item.pszText = yText; + item.cchTextMax = 260; + + yText[0] = 0; + CallWindowProc(Context->OldWndProc, Context->Handle, LVM_GETITEM, 0, (LPARAM)&item); + + // Compare them. + +#if 1 + return PhCompareStringZNatural(xText, yText, TRUE); +#else + return _wcsicmp(xText, yText); +#endif +} diff --git a/phlib/fastlock.c b/phlib/fastlock.c new file mode 100644 index 0000000..87b5db3 --- /dev/null +++ b/phlib/fastlock.c @@ -0,0 +1,384 @@ +/* + * Process Hacker - + * fast resource lock + * + * Copyright (C) 2009-2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +// FastLock is a port of FastResourceLock from PH 1.x. +// +// The code contains no comments because it is a direct port. Please see FastResourceLock.cs in PH +// 1.x for details. + +// The fast lock is around 7% faster than the critical section when there is no contention, when +// used solely for mutual exclusion. It is also much smaller than the critical section. + +#define PH_LOCK_OWNED 0x1 +#define PH_LOCK_EXCLUSIVE_WAKING 0x2 + +#define PH_LOCK_SHARED_OWNERS_SHIFT 2 +#define PH_LOCK_SHARED_OWNERS_MASK 0x3ff +#define PH_LOCK_SHARED_OWNERS_INC 0x4 + +#define PH_LOCK_SHARED_WAITERS_SHIFT 12 +#define PH_LOCK_SHARED_WAITERS_MASK 0x3ff +#define PH_LOCK_SHARED_WAITERS_INC 0x1000 + +#define PH_LOCK_EXCLUSIVE_WAITERS_SHIFT 22 +#define PH_LOCK_EXCLUSIVE_WAITERS_MASK 0x3ff +#define PH_LOCK_EXCLUSIVE_WAITERS_INC 0x400000 + +#define PH_LOCK_EXCLUSIVE_MASK \ + (PH_LOCK_EXCLUSIVE_WAKING | \ + (PH_LOCK_EXCLUSIVE_WAITERS_MASK << PH_LOCK_EXCLUSIVE_WAITERS_SHIFT)) + +VOID PhInitializeFastLock( + _Out_ PPH_FAST_LOCK FastLock + ) +{ + FastLock->Value = 0; + FastLock->ExclusiveWakeEvent = NULL; + FastLock->SharedWakeEvent = NULL; +} + +VOID PhDeleteFastLock( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + if (FastLock->ExclusiveWakeEvent) + { + NtClose(FastLock->ExclusiveWakeEvent); + FastLock->ExclusiveWakeEvent = NULL; + } + + if (FastLock->SharedWakeEvent) + { + NtClose(FastLock->SharedWakeEvent); + FastLock->SharedWakeEvent = NULL; + } +} + +FORCEINLINE VOID PhpEnsureEventCreated( + _Inout_ PHANDLE Handle + ) +{ + HANDLE handle; + + if (*Handle != NULL) + return; + + NtCreateSemaphore(&handle, SEMAPHORE_ALL_ACCESS, NULL, 0, MAXLONG); + + if (_InterlockedCompareExchangePointer( + Handle, + handle, + NULL + ) != NULL) + { + NtClose(handle); + } +} + +FORCEINLINE ULONG PhpGetSpinCount( + VOID + ) +{ + if ((ULONG)PhSystemBasicInformation.NumberOfProcessors > 1) + return 4000; + else + return 0; +} + +_May_raise_ +_Acquires_exclusive_lock_(*FastLock) +VOID FASTCALL PhfAcquireFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + ULONG i = 0; + ULONG spinCount; + + spinCount = PhpGetSpinCount(); + + while (TRUE) + { + value = FastLock->Value; + + if (!(value & (PH_LOCK_OWNED | PH_LOCK_EXCLUSIVE_WAKING))) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_OWNED, + value + ) == value) + break; + } + else if (i >= spinCount) + { + PhpEnsureEventCreated(&FastLock->ExclusiveWakeEvent); + + if (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_EXCLUSIVE_WAITERS_INC, + value + ) == value) + { + if (NtWaitForSingleObject( + FastLock->ExclusiveWakeEvent, + FALSE, + NULL + ) != STATUS_WAIT_0) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + + do + { + value = FastLock->Value; + } while (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_OWNED - PH_LOCK_EXCLUSIVE_WAKING, + value + ) != value); + + break; + } + } + + i++; + YieldProcessor(); + } +} + +_May_raise_ +_Acquires_shared_lock_(*FastLock) +VOID FASTCALL PhfAcquireFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + ULONG i = 0; + ULONG spinCount; + + spinCount = PhpGetSpinCount(); + + while (TRUE) + { + value = FastLock->Value; + + if (!(value & ( + PH_LOCK_OWNED | + (PH_LOCK_SHARED_OWNERS_MASK << PH_LOCK_SHARED_OWNERS_SHIFT) | + PH_LOCK_EXCLUSIVE_MASK + ))) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_OWNED + PH_LOCK_SHARED_OWNERS_INC, + value + ) == value) + break; + } + else if ( + (value & PH_LOCK_OWNED) && + ((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK) > 0 && + !(value & PH_LOCK_EXCLUSIVE_MASK) + ) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_SHARED_OWNERS_INC, + value + ) == value) + break; + } + else if (i >= spinCount) + { + PhpEnsureEventCreated(&FastLock->SharedWakeEvent); + + if (_InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_SHARED_WAITERS_INC, + value + ) == value) + { + if (NtWaitForSingleObject( + FastLock->SharedWakeEvent, + FALSE, + NULL + ) != STATUS_WAIT_0) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + + continue; + } + } + + i++; + YieldProcessor(); + } +} + +_Releases_exclusive_lock_(*FastLock) +VOID FASTCALL PhfReleaseFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + + while (TRUE) + { + value = FastLock->Value; + + if ((value >> PH_LOCK_EXCLUSIVE_WAITERS_SHIFT) & PH_LOCK_EXCLUSIVE_WAITERS_MASK) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value - PH_LOCK_OWNED + PH_LOCK_EXCLUSIVE_WAKING - PH_LOCK_EXCLUSIVE_WAITERS_INC, + value + ) == value) + { + NtReleaseSemaphore(FastLock->ExclusiveWakeEvent, 1, NULL); + + break; + } + } + else + { + ULONG sharedWaiters; + + sharedWaiters = (value >> PH_LOCK_SHARED_WAITERS_SHIFT) & PH_LOCK_SHARED_WAITERS_MASK; + + if (_InterlockedCompareExchange( + &FastLock->Value, + value & ~(PH_LOCK_OWNED | (PH_LOCK_SHARED_WAITERS_MASK << PH_LOCK_SHARED_WAITERS_SHIFT)), + value + ) == value) + { + if (sharedWaiters) + NtReleaseSemaphore(FastLock->SharedWakeEvent, sharedWaiters, 0); + + break; + } + } + + YieldProcessor(); + } +} + +_Releases_shared_lock_(*FastLock) +VOID FASTCALL PhfReleaseFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + + while (TRUE) + { + value = FastLock->Value; + + if (((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK) > 1) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value - PH_LOCK_SHARED_OWNERS_INC, + value + ) == value) + break; + } + else if ((value >> PH_LOCK_EXCLUSIVE_WAITERS_SHIFT) & PH_LOCK_EXCLUSIVE_WAITERS_MASK) + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value - PH_LOCK_OWNED + PH_LOCK_EXCLUSIVE_WAKING - + PH_LOCK_SHARED_OWNERS_INC - PH_LOCK_EXCLUSIVE_WAITERS_INC, + value + ) == value) + { + NtReleaseSemaphore(FastLock->ExclusiveWakeEvent, 1, NULL); + + break; + } + } + else + { + if (_InterlockedCompareExchange( + &FastLock->Value, + value - PH_LOCK_OWNED - PH_LOCK_SHARED_OWNERS_INC, + value + ) == value) + break; + } + + YieldProcessor(); + } +} + +_When_(return != 0, _Acquires_exclusive_lock_(*FastLock)) +BOOLEAN FASTCALL PhfTryAcquireFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + + value = FastLock->Value; + + if (value & (PH_LOCK_OWNED | PH_LOCK_EXCLUSIVE_WAKING)) + return FALSE; + + return _InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_OWNED, + value + ) == value; +} + +_When_(return != 0, _Acquires_shared_lock_(*FastLock)) +BOOLEAN FASTCALL PhfTryAcquireFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ) +{ + ULONG value; + + value = FastLock->Value; + + if (value & PH_LOCK_EXCLUSIVE_MASK) + return FALSE; + + if (!(value & PH_LOCK_OWNED)) + { + return _InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_OWNED + PH_LOCK_SHARED_OWNERS_INC, + value + ) == value; + } + else if ((value >> PH_LOCK_SHARED_OWNERS_SHIFT) & PH_LOCK_SHARED_OWNERS_MASK) + { + return _InterlockedCompareExchange( + &FastLock->Value, + value + PH_LOCK_SHARED_OWNERS_INC, + value + ) == value; + } + else + { + return FALSE; + } +} diff --git a/phlib/filepool.c b/phlib/filepool.c new file mode 100644 index 0000000..2b93aaa --- /dev/null +++ b/phlib/filepool.c @@ -0,0 +1,1510 @@ +/* + * Process Hacker - + * file-based allocator + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * File pool allows blocks of storage to be allocated from a file. Each file looks like this: + * + * Segment 0 __________________________________________________________ + * | | | | | + * | Block Header | File Header | Block Header | Segment Header | + * |______________|________________|______________|________________| + * | | | | | + * | Block Header | User Data | Block Header | User Data | + * |______________|________________|______________|________________| + * | | | | | + * | ... | ... | ... | ... | + * |______________|________________|______________|________________| + * Segment 1 __________________________________________________________ + * | | | | | + * | Block Header | Segment Header | Block Header | User Data | + * |______________|________________|______________|________________| + * | | | | | + * | ... | ... | ... | ... | + * |______________|________________|______________|________________| + * Segment 2 __________________________________________________________ + * | | | | | + * | ... | ... | ... | ... | + * |______________|________________|______________|________________| + * + */ + +/* + * A file consists of a variable number of segments, with the segment size specified as a power of + * two. Each segment contains a fixed number of blocks, leading to a variable block size. Every + * allocation made by the user is an allocation of a certain number of blocks, with enough space for + * the block header. This is placed at the beginning of each allocation and contains the number of + * blocks in the allocation (a better name for it would be the allocation header). + * + * Block management in each segment is handled by a bitmap which is stored in the segment header at + * the beginning of each segment. The first segment (segment 0) is special with the file header + * being placed immediately after an initial block header. This is because the segment size is + * stored in the file header, and without it we cannot calculate the block size, which is used to + * locate everything else in the file. + * + * To speed up allocations a number of free lists are maintained which categorize each segment based + * on how many free blocks they have. This means we can avoid trying to allocate from every existing + * segment before finding out we have to allocate a new segment, or trying to allocate from segments + * without the required number of free blocks. The downside of this technique is that it doesn't + * account for fragmentation within the allocation bitmap. + * + * Each segment is mapped in separately, and each view is cached. Even after a view becomes inactive + * (has a reference count of 0) it remains mapped in until the maximum number of inactive views is + * reached. + */ + +#include +#include +#include + +/** + * Creates or opens a file pool. + * + * \param Pool A variable which receives the file pool instance. + * \param FileHandle A handle to the file. + * \param ReadOnly TRUE to disallow writes to the file. + * \param Parameters Parameters for on-disk and runtime structures. + */ +NTSTATUS PhCreateFilePool( + _Out_ PPH_FILE_POOL *Pool, + _In_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters + ) +{ + NTSTATUS status; + PPH_FILE_POOL pool; + LARGE_INTEGER fileSize; + PH_FILE_POOL_PARAMETERS localParameters; + BOOLEAN creating; + HANDLE sectionHandle; + PPH_FP_BLOCK_HEADER initialBlock; + PPH_FP_FILE_HEADER header; + ULONG i; + + if (Parameters) + { + PhpValidateFilePoolParameters(Parameters); + } + else + { + PhpSetDefaultFilePoolParameters(&localParameters); + Parameters = &localParameters; + } + + pool = PhAllocate(sizeof(PH_FILE_POOL)); + memset(pool, 0, sizeof(PH_FILE_POOL)); + + pool->FileHandle = FileHandle; + pool->ReadOnly = ReadOnly; + + if (!NT_SUCCESS(status = PhGetFileSize(FileHandle, &fileSize))) + goto CleanupExit; + + creating = FALSE; + + // If the file is smaller than the page size, assume we're creating a new file. + if (fileSize.QuadPart < PAGE_SIZE) + { + if (ReadOnly) + { + status = STATUS_BAD_FILE_TYPE; + goto CleanupExit; + } + + fileSize.QuadPart = PAGE_SIZE; + + if (!NT_SUCCESS(status = PhSetFileSize(FileHandle, &fileSize))) + goto CleanupExit; + + creating = TRUE; + } + + // Create a section. + status = NtCreateSection( + §ionHandle, + SECTION_ALL_ACCESS, + NULL, + &fileSize, + !ReadOnly ? PAGE_READWRITE : PAGE_READONLY, + SEC_COMMIT, + FileHandle + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + pool->SectionHandle = sectionHandle; + + // Map in the first segment, set up initial parameters, then remap the first segment. + + if (!NT_SUCCESS(status = PhFppMapRange(pool, 0, PAGE_SIZE, &initialBlock))) + goto CleanupExit; + + header = (PPH_FP_FILE_HEADER)&initialBlock->Body; + + if (creating) + { + header->Magic = PH_FP_MAGIC; + header->SegmentShift = Parameters->SegmentShift; + header->SegmentCount = 1; + + for (i = 0; i < PH_FP_FREE_LIST_COUNT; i++) + header->FreeLists[i] = -1; + } + else + { + if (header->Magic != PH_FP_MAGIC) + { + PhFppUnmapRange(pool, initialBlock); + status = STATUS_BAD_FILE_TYPE; + goto CleanupExit; + } + } + + pool->SegmentShift = header->SegmentShift; + pool->SegmentSize = 1 << pool->SegmentShift; + + pool->BlockShift = pool->SegmentShift - PH_FP_BLOCK_COUNT_SHIFT; + pool->BlockSize = 1 << pool->BlockShift; + pool->FileHeaderBlockSpan = (sizeof(PH_FP_FILE_HEADER) + pool->BlockSize - 1) >> pool->BlockShift; + pool->SegmentHeaderBlockSpan = (sizeof(PH_FP_SEGMENT_HEADER) + pool->BlockSize - 1) >> pool->BlockShift; + + // Unmap the first segment and remap with the new segment size. + + PhFppUnmapRange(pool, initialBlock); + + if (creating) + { + // Extend the section so it fits the entire first segment. + if (!NT_SUCCESS(status = PhFppExtendRange(pool, pool->SegmentSize))) + goto CleanupExit; + } + + // Runtime structure initialization + + PhInitializeFreeList(&pool->ViewFreeList, sizeof(PH_FILE_POOL_VIEW), 32); + pool->ByIndexSize = 32; + pool->ByIndexBuckets = PhAllocate(sizeof(PPH_FILE_POOL_VIEW) * pool->ByIndexSize); + memset(pool->ByIndexBuckets, 0, sizeof(PPH_FILE_POOL_VIEW) * pool->ByIndexSize); + PhInitializeAvlTree(&pool->ByBaseSet, PhpFilePoolViewByBaseCompareFunction); + + pool->MaximumInactiveViews = Parameters->MaximumInactiveViews; + InitializeListHead(&pool->InactiveViewsListHead); + + // File structure initialization + + pool->FirstBlockOfFirstSegment = PhFppReferenceSegment(pool, 0); + pool->Header = (PPH_FP_FILE_HEADER)&pool->FirstBlockOfFirstSegment->Body; + + if (creating) + { + PPH_FP_BLOCK_HEADER segmentHeaderBlock; + + // Set up the first segment properly. + + pool->FirstBlockOfFirstSegment->Span = pool->FileHeaderBlockSpan; + segmentHeaderBlock = (PPH_FP_BLOCK_HEADER)((PCHAR)pool->FirstBlockOfFirstSegment + (pool->FileHeaderBlockSpan << pool->BlockShift)); + PhFppInitializeSegment(pool, segmentHeaderBlock, pool->FileHeaderBlockSpan); + + pool->Header->FreeLists[1] = 0; + } + +CleanupExit: + if (NT_SUCCESS(status)) + { + *Pool = pool; + } + else + { + // Don't close the file handle the user passed in. + pool->FileHandle = NULL; + PhDestroyFilePool(pool); + } + + return status; +} + +/** + * Creates or opens a file pool. + * + * \param Pool A variable which receives the file pool instance. + * \param FileName The file name of the file pool. + * \param ReadOnly TRUE to disallow writes to the file. + * \param ShareAccess The file access granted to other threads. + * \param CreateDisposition The action to perform if the file does or does not exist. See + * PhCreateFileWin32() for more information. + * \param Parameters Parameters for on-disk and runtime structures. + */ +NTSTATUS PhCreateFilePool2( + _Out_ PPH_FILE_POOL *Pool, + _In_ PWSTR FileName, + _In_ BOOLEAN ReadOnly, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters + ) +{ + NTSTATUS status; + PPH_FILE_POOL pool; + HANDLE fileHandle; + ULONG createStatus; + + if (!NT_SUCCESS(status = PhCreateFileWin32Ex( + &fileHandle, + FileName, + !ReadOnly ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE) : FILE_GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + ShareAccess, + CreateDisposition, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + &createStatus + ))) + { + return status; + } + + status = PhCreateFilePool(&pool, fileHandle, ReadOnly, Parameters); + + if (NT_SUCCESS(status)) + { + *Pool = pool; + } + else + { + if (!ReadOnly && createStatus == FILE_CREATED) + { + FILE_DISPOSITION_INFORMATION dispositionInfo; + IO_STATUS_BLOCK isb; + + dispositionInfo.DeleteFile = TRUE; + NtSetInformationFile(fileHandle, &isb, &dispositionInfo, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation); + } + + NtClose(fileHandle); + } + + return status; +} + +/** + * Frees resources used by a file pool instance. + * + * \param Pool The file pool. + */ +VOID PhDestroyFilePool( + _In_ _Post_invalid_ PPH_FILE_POOL Pool + ) +{ + ULONG i; + PLIST_ENTRY head; + PLIST_ENTRY entry; + PPH_FILE_POOL_VIEW view; + + // Unmap all views. + for (i = 0; i < Pool->ByIndexSize; i++) + { + if (head = Pool->ByIndexBuckets[i]) + { + entry = head; + + do + { + view = CONTAINING_RECORD(entry, PH_FILE_POOL_VIEW, ByIndexListEntry); + entry = entry->Flink; + PhFppUnmapRange(Pool, view->Base); + PhFreeToFreeList(&Pool->ViewFreeList, view); + } while (entry != head); + } + } + + if (Pool->ByIndexBuckets) + PhFree(Pool->ByIndexBuckets); + + PhDeleteFreeList(&Pool->ViewFreeList); + + if (Pool->SectionHandle) + NtClose(Pool->SectionHandle); + if (Pool->FileHandle) + NtClose(Pool->FileHandle); + + PhFree(Pool); +} + +/** + * Validates and corrects file pool parameters. + * + * \param Parameters The parameters structure which is validated and modified if necessary. + */ +NTSTATUS PhpValidateFilePoolParameters( + _Inout_ PPH_FILE_POOL_PARAMETERS Parameters + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + // 16 <= SegmentShift <= 28 + + if (Parameters->SegmentShift < 16) + { + Parameters->SegmentShift = 16; + status = STATUS_SOME_NOT_MAPPED; + } + + if (Parameters->SegmentShift > 28) + { + Parameters->SegmentShift = 28; + status = STATUS_SOME_NOT_MAPPED; + } + + return status; +} + +/** + * Creates default file pool parameters. + * + * \param Parameters The parameters structure which receives the default parameter values. + */ +VOID PhpSetDefaultFilePoolParameters( + _Out_ PPH_FILE_POOL_PARAMETERS Parameters + ) +{ + Parameters->SegmentShift = 18; // 256kB + Parameters->MaximumInactiveViews = 128; +} + +/** + * Allocates a block from a file pool. + * + * \param Pool The file pool. + * \param Size The number of bytes to allocate. + * \param Rva A variable which receives the relative virtual address of the allocated block. + * + * \return A pointer to the allocated block. You must call PhDereferenceFilePool() or + * PhDereferenceFilePoolByRva() when you no longer need a reference to the block. + * + * \remarks The returned pointer is not valid beyond the lifetime of the file pool instance. Use the + * relative virtual address if you need a permanent reference to the allocated block. + */ +PVOID PhAllocateFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Size, + _Out_opt_ PULONG Rva + ) +{ + PPH_FP_BLOCK_HEADER blockHeader; + ULONG numberOfBlocks; + PPH_FP_BLOCK_HEADER firstBlock; + PPH_FP_SEGMENT_HEADER segmentHeader; + ULONG freeListIndex; + ULONG freeListLimit; + ULONG segmentIndex; + ULONG nextSegmentIndex; + ULONG newFreeListIndex; + + // Calculate the number of blocks needed for the allocation. + numberOfBlocks = (FIELD_OFFSET(PH_FP_BLOCK_HEADER, Body) + Size + Pool->BlockSize - 1) >> Pool->BlockShift; + + if (numberOfBlocks > PH_FP_BLOCK_COUNT - Pool->SegmentHeaderBlockSpan) + { + // TODO: Perform a large allocation. + return NULL; + } + + // Scan each applicable free list and try to allocate from those segments. + + freeListLimit = PhFppComputeFreeListIndex(Pool, numberOfBlocks); + + for (freeListIndex = 0; freeListIndex <= freeListLimit; freeListIndex++) + { + segmentIndex = Pool->Header->FreeLists[freeListIndex]; + + while (segmentIndex != -1) + { + firstBlock = PhFppReferenceSegment(Pool, segmentIndex); + + if (!firstBlock) + return NULL; + + segmentHeader = PhFppGetHeaderSegment(Pool, firstBlock); + nextSegmentIndex = segmentHeader->FreeFlink; + + blockHeader = PhFppAllocateBlocks(Pool, firstBlock, segmentHeader, numberOfBlocks); + + if (blockHeader) + goto BlockAllocated; + + PhFppDereferenceSegment(Pool, segmentIndex); + segmentIndex = nextSegmentIndex; + } + } + + // No segments have the required number of contiguous free blocks. Allocate a new one. + + firstBlock = PhFppAllocateSegment(Pool, &segmentIndex); + + if (!firstBlock) + return NULL; + + freeListIndex = 0; + segmentHeader = PhFppGetHeaderSegment(Pool, firstBlock); + blockHeader = PhFppAllocateBlocks(Pool, firstBlock, segmentHeader, numberOfBlocks); + + if (!blockHeader) + { + PhFppDereferenceSegment(Pool, segmentIndex); + return NULL; + } + +BlockAllocated: + + // Compute the new free list index of the segment and move it to another free list if necessary. + + newFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); + + if (newFreeListIndex != freeListIndex) + { + PhFppRemoveFreeList(Pool, freeListIndex, segmentIndex, segmentHeader); + PhFppInsertFreeList(Pool, newFreeListIndex, segmentIndex, segmentHeader); + } + + if (Rva) + { + *Rva = PhFppEncodeRva(Pool, segmentIndex, firstBlock, &blockHeader->Body); + } + + return &blockHeader->Body; +} + +/** + * Frees a block. + * + * \param Pool The file pool. + * \param SegmentIndex The index of the segment containing the block. + * \param FirstBlock The first block of the segment containing the block. + * \param Block A pointer to the block. + */ +VOID PhpFreeFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _In_ PVOID Block + ) +{ + PPH_FP_SEGMENT_HEADER segmentHeader; + ULONG oldFreeListIndex; + ULONG newFreeListIndex; + + segmentHeader = PhFppGetHeaderSegment(Pool, FirstBlock); + oldFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); + PhFppFreeBlocks(Pool, FirstBlock, segmentHeader, PhFppGetHeaderBlock(Pool, Block)); + newFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); + + // Move the segment into another free list if needed. + if (newFreeListIndex != oldFreeListIndex) + { + PhFppRemoveFreeList(Pool, oldFreeListIndex, SegmentIndex, segmentHeader); + PhFppInsertFreeList(Pool, newFreeListIndex, SegmentIndex, segmentHeader); + } +} + +/** + * Frees a block allocated by PhAllocateFilePool(). + * + * \param Pool The file pool. + * \param Block A pointer to the block. The pointer is no longer valid after you call this function. + * Do not use PhDereferenceFilePool() or PhDereferenceFilePoolByRva(). + */ +VOID PhFreeFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Block + ) +{ + PPH_FILE_POOL_VIEW view; + PPH_FP_BLOCK_HEADER firstBlock; + + view = PhFppFindViewByBase(Pool, Block); + + if (!view) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + firstBlock = view->Base; + PhpFreeFilePool(Pool, view->SegmentIndex, firstBlock, Block); + PhFppDereferenceView(Pool, view); +} + +/** + * Frees a block allocated by PhAllocateFilePool(). + * + * \param Pool The file pool. + * \param Rva The relative virtual address of the block. + */ +BOOLEAN PhFreeFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ) +{ + ULONG segmentIndex; + ULONG offset; + PPH_FP_BLOCK_HEADER firstBlock; + + offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); + + if (offset == -1) + return FALSE; + + firstBlock = PhFppReferenceSegment(Pool, segmentIndex); + + if (!firstBlock) + return FALSE; + + PhpFreeFilePool(Pool, segmentIndex, firstBlock, (PCHAR)firstBlock + offset); + PhFppDereferenceSegment(Pool, segmentIndex); + + return TRUE; +} + +/** + * Increments the reference count for the specified address. + * + * \param Pool The file pool. + * \param Address An address. + */ +VOID PhReferenceFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ) +{ + PhFppReferenceSegmentByBase(Pool, Address); +} + +/** + * Decrements the reference count for the specified address. + * + * \param Pool The file pool. + * \param Address An address. + */ +VOID PhDereferenceFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ) +{ + PhFppDereferenceSegmentByBase(Pool, Address); +} + +/** + * Obtains a pointer for a relative virtual address, incrementing the reference count of the + * address. + * + * \param Pool The file pool. + * \param Rva A relative virtual address. + */ +PVOID PhReferenceFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ) +{ + ULONG segmentIndex; + ULONG offset; + PPH_FP_BLOCK_HEADER firstBlock; + + if (Rva == 0) + return NULL; + + offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); + + if (offset == -1) + return NULL; + + firstBlock = PhFppReferenceSegment(Pool, segmentIndex); + + if (!firstBlock) + return NULL; + + return (PCHAR)firstBlock + offset; +} + +/** + * Decrements the reference count for the specified relative virtual address. + * + * \param Pool The file pool. + * \param Rva A relative virtual address. + */ +BOOLEAN PhDereferenceFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ) +{ + ULONG segmentIndex; + ULONG offset; + + offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); + + if (offset == -1) + return FALSE; + + PhFppDereferenceSegment(Pool, segmentIndex); + + return TRUE; +} + +/** + * Obtains a relative virtual address for a pointer. + * + * \param Pool The file pool. + * \param Address A pointer. + * + * \return The relative virtual address. + * + * \remarks No reference counts are changed. + */ +ULONG PhEncodeRvaFilePool( + _In_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ) +{ + PPH_FILE_POOL_VIEW view; + + if (!Address) + return 0; + + view = PhFppFindViewByBase(Pool, Address); + + if (!view) + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + + return PhFppEncodeRva(Pool, view->SegmentIndex, view->Base, Address); +} + +/** + * Retrieves user data. + * + * \param Pool The file pool. + * \param Context A variable which receives the user data. + */ +VOID PhGetUserContextFilePool( + _In_ PPH_FILE_POOL Pool, + _Out_ PULONGLONG Context + ) +{ + *Context = Pool->Header->UserContext; +} + +/** + * Stores user data. + * + * \param Pool The file pool. + * \param Context A variable which contains the user data. + */ +VOID PhSetUserContextFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PULONGLONG Context + ) +{ + Pool->Header->UserContext = *Context; +} + +/** + * Extends a file pool. + * + * \param Pool The file pool. + * \param NewSize The new size of the file, in bytes. + */ +NTSTATUS PhFppExtendRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG NewSize + ) +{ + LARGE_INTEGER newSectionSize; + + newSectionSize.QuadPart = NewSize; + + return NtExtendSection(Pool->SectionHandle, &newSectionSize); +} + +/** + * Maps in a view of a file pool. + * + * \param Pool The file pool. + * \param Offset The offset of the view, in bytes. + * \param Size The size of the view, in bytes. + * \param Base A variable which receives the base address of the view. + */ +NTSTATUS PhFppMapRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Offset, + _In_ ULONG Size, + _Out_ PVOID *Base + ) +{ + NTSTATUS status; + PVOID baseAddress; + LARGE_INTEGER sectionOffset; + SIZE_T viewSize; + + baseAddress = NULL; + sectionOffset.QuadPart = Offset; + viewSize = Size; + + status = NtMapViewOfSection( + Pool->SectionHandle, + NtCurrentProcess(), + &baseAddress, + 0, + viewSize, + §ionOffset, + &viewSize, + ViewShare, + 0, + !Pool->ReadOnly ? PAGE_READWRITE : PAGE_READONLY + ); + + if (NT_SUCCESS(status)) + *Base = baseAddress; + + return status; +} + +/** + * Unmaps a view of a file pool. + * + * \param Pool The file pool. + * \param Base The base address of the view. + */ +NTSTATUS PhFppUnmapRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ) +{ + return NtUnmapViewOfSection(NtCurrentProcess(), Base); +} + +/** + * Initializes a segment. + * + * \param Pool The file pool. + * \param BlockOfSegmentHeader The block header of the span containing the segment header. + * \param AdditionalBlocksUsed The number of blocks already allocated from the segment, excluding + * the blocks comprising the segment header. + */ +VOID PhFppInitializeSegment( + _Inout_ PPH_FILE_POOL Pool, + _Out_ PPH_FP_BLOCK_HEADER BlockOfSegmentHeader, + _In_ ULONG AdditionalBlocksUsed + ) +{ + PPH_FP_SEGMENT_HEADER segmentHeader; + RTL_BITMAP bitmap; + + BlockOfSegmentHeader->Span = Pool->SegmentHeaderBlockSpan; + segmentHeader = (PPH_FP_SEGMENT_HEADER)&BlockOfSegmentHeader->Body; + + RtlInitializeBitMap(&bitmap, segmentHeader->Bitmap, PH_FP_BLOCK_COUNT); + RtlSetBits(&bitmap, 0, Pool->SegmentHeaderBlockSpan + AdditionalBlocksUsed); + segmentHeader->FreeBlocks = PH_FP_BLOCK_COUNT - (Pool->SegmentHeaderBlockSpan + AdditionalBlocksUsed); + segmentHeader->FreeFlink = -1; + segmentHeader->FreeBlink = -1; +} + +/** + * Allocates a segment. + * + * \param Pool The file pool. + * \param NewSegmentIndex A variable which receives the index of the new segment. + * + * \return A pointer to the first block of the segment. + */ +PPH_FP_BLOCK_HEADER PhFppAllocateSegment( + _Inout_ PPH_FILE_POOL Pool, + _Out_ PULONG NewSegmentIndex + ) +{ + ULONG newSize; + ULONG segmentIndex; + PPH_FP_BLOCK_HEADER firstBlock; + PPH_FP_SEGMENT_HEADER segmentHeader; + + newSize = (Pool->Header->SegmentCount + 1) << Pool->SegmentShift; + + if (!NT_SUCCESS(PhFppExtendRange(Pool, newSize))) + return NULL; + + segmentIndex = Pool->Header->SegmentCount++; + firstBlock = PhFppReferenceSegment(Pool, segmentIndex); + PhFppInitializeSegment(Pool, firstBlock, 0); + segmentHeader = (PPH_FP_SEGMENT_HEADER)&firstBlock->Body; + + PhFppInsertFreeList(Pool, 0, segmentIndex, segmentHeader); + + *NewSegmentIndex = segmentIndex; + + return firstBlock; +} + +/** + * Retrieves the header of a segment. + * + * \param Pool The file pool. + * \param FirstBlock The first block of the segment. + */ +PPH_FP_SEGMENT_HEADER PhFppGetHeaderSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock + ) +{ + if (FirstBlock != Pool->FirstBlockOfFirstSegment) + { + return (PPH_FP_SEGMENT_HEADER)&FirstBlock->Body; + } + else + { + // In the first segment, the segment header is after the file header. + return (PPH_FP_SEGMENT_HEADER)&((PPH_FP_BLOCK_HEADER)((PCHAR)FirstBlock + (Pool->FileHeaderBlockSpan << Pool->BlockShift)))->Body; + } +} + +VOID PhFppAddViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + ULONG index; + PLIST_ENTRY head; + + index = View->SegmentIndex & (Pool->ByIndexSize - 1); + head = Pool->ByIndexBuckets[index]; + + if (head) + { + InsertHeadList(head, &View->ByIndexListEntry); + } + else + { + InitializeListHead(&View->ByIndexListEntry); + Pool->ByIndexBuckets[index] = &View->ByIndexListEntry; + } +} + +VOID PhFppRemoveViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + ULONG index; + PLIST_ENTRY head; + + index = View->SegmentIndex & (Pool->ByIndexSize - 1); + head = Pool->ByIndexBuckets[index]; + assert(head); + + // Unlink the entry from the chain. + RemoveEntryList(&View->ByIndexListEntry); + + if (&View->ByIndexListEntry == head) + { + // This entry is currently the chain head. + + // If this was the last entry in the chain, then indicate that the chain is empty. + // Otherwise, choose a new head. + + if (IsListEmpty(head)) + Pool->ByIndexBuckets[index] = NULL; + else + Pool->ByIndexBuckets[index] = head->Flink; + } +} + +/** + * Finds a view for the specified segment. + * + * \param Pool The file pool. + * \param SegmentIndex The index of the segment. + * + * \return The view for the segment, or NULL if no view is present for the segment. + */ +PPH_FILE_POOL_VIEW PhFppFindViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ) +{ + ULONG index; + PLIST_ENTRY head; + PLIST_ENTRY entry; + PPH_FILE_POOL_VIEW view; + + index = SegmentIndex & (Pool->ByIndexSize - 1); + head = Pool->ByIndexBuckets[index]; + + if (!head) + return NULL; + + entry = head; + + do + { + view = CONTAINING_RECORD(entry, PH_FILE_POOL_VIEW, ByIndexListEntry); + + if (view->SegmentIndex == SegmentIndex) + return view; + + entry = entry->Flink; + } while (entry != head); + + return NULL; +} + +LONG NTAPI PhpFilePoolViewByBaseCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PPH_FILE_POOL_VIEW view1 = CONTAINING_RECORD(Links1, PH_FILE_POOL_VIEW, ByBaseLinks); + PPH_FILE_POOL_VIEW view2 = CONTAINING_RECORD(Links2, PH_FILE_POOL_VIEW, ByBaseLinks); + + return uintptrcmp((ULONG_PTR)view1->Base, (ULONG_PTR)view2->Base); +} + +VOID PhFppAddViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + PhAddElementAvlTree(&Pool->ByBaseSet, &View->ByBaseLinks); +} + +VOID PhFppRemoveViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + PhRemoveElementAvlTree(&Pool->ByBaseSet, &View->ByBaseLinks); +} + +/** + * Finds a view containing the specified address. + * + * \param Pool The file pool. + * \param Base The address. + * + * \return The view containing the address, or NULL if no view is present for the address. + */ +PPH_FILE_POOL_VIEW PhFppFindViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ) +{ + PPH_FILE_POOL_VIEW view; + PPH_AVL_LINKS links; + PH_FILE_POOL_VIEW lookupView; + + // This is an approximate search to find the target view in which the specified address lies. + + lookupView.Base = Base; + links = PhUpperDualBoundElementAvlTree(&Pool->ByBaseSet, &lookupView.ByBaseLinks); + + if (!links) + return NULL; + + view = CONTAINING_RECORD(links, PH_FILE_POOL_VIEW, ByBaseLinks); + + if ((ULONG_PTR)Base < (ULONG_PTR)view->Base + Pool->SegmentSize) + return view; + + return NULL; +} + +PPH_FILE_POOL_VIEW PhFppCreateView( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ) +{ + PPH_FILE_POOL_VIEW view; + PVOID base; + + // Map in the segment. + if (!NT_SUCCESS(PhFppMapRange(Pool, SegmentIndex << Pool->SegmentShift, Pool->SegmentSize, &base))) + return NULL; + + // Create and add the view entry. + + view = PhAllocateFromFreeList(&Pool->ViewFreeList); + memset(view, 0, sizeof(PH_FILE_POOL_VIEW)); + + view->RefCount = 1; + view->SegmentIndex = SegmentIndex; + view->Base = base; + + PhFppAddViewByIndex(Pool, view); + PhFppAddViewByBase(Pool, view); + + return view; +} + +VOID PhFppDestroyView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + PhFppUnmapRange(Pool, View->Base); + PhFppRemoveViewByIndex(Pool, View); + PhFppRemoveViewByBase(Pool, View); + + PhFreeToFreeList(&Pool->ViewFreeList, View); +} + +VOID PhFppActivateView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + RemoveEntryList(&View->InactiveViewsListEntry); + Pool->NumberOfInactiveViews--; +} + +VOID PhFppDeactivateView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + InsertHeadList(&Pool->InactiveViewsListHead, &View->InactiveViewsListEntry); + Pool->NumberOfInactiveViews++; + + // If we have too many inactive views, destroy the least recent ones. + while (Pool->NumberOfInactiveViews > Pool->MaximumInactiveViews) + { + PLIST_ENTRY lruEntry; + PPH_FILE_POOL_VIEW lruView; + + lruEntry = RemoveTailList(&Pool->InactiveViewsListHead); + Pool->NumberOfInactiveViews--; + + assert(lruEntry != &Pool->InactiveViewsListHead); + lruView = CONTAINING_RECORD(lruEntry, PH_FILE_POOL_VIEW, InactiveViewsListEntry); + PhFppDestroyView(Pool, lruView); + } +} + +VOID PhFppReferenceView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + if (View->RefCount == 0) + { + // The view is inactive, so make it active. + PhFppActivateView(Pool, View); + } + + View->RefCount++; +} + +VOID PhFppDereferenceView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ) +{ + if (--View->RefCount == 0) + { + if (View->SegmentIndex == 0) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + + PhFppDeactivateView(Pool, View); + } +} + +PPH_FP_BLOCK_HEADER PhFppReferenceSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ) +{ + PPH_FILE_POOL_VIEW view; + + // Validate parameters. + if (SegmentIndex != 0 && SegmentIndex >= Pool->Header->SegmentCount) + return NULL; + + // Try to get a cached view. + + view = PhFppFindViewByIndex(Pool, SegmentIndex); + + if (view) + { + PhFppReferenceView(Pool, view); + + return (PPH_FP_BLOCK_HEADER)view->Base; + } + + // No cached view, so create one. + + view = PhFppCreateView(Pool, SegmentIndex); + + if (!view) + return NULL; + + return (PPH_FP_BLOCK_HEADER)view->Base; +} + +VOID PhFppDereferenceSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ) +{ + PPH_FILE_POOL_VIEW view; + + view = PhFppFindViewByIndex(Pool, SegmentIndex); + + if (!view) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + + PhFppDereferenceView(Pool, view); +} + +VOID PhFppReferenceSegmentByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ) +{ + PPH_FILE_POOL_VIEW view; + + view = PhFppFindViewByBase(Pool, Base); + + if (!view) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + + PhFppReferenceView(Pool, view); +} + +VOID PhFppDereferenceSegmentByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ) +{ + PPH_FILE_POOL_VIEW view; + + view = PhFppFindViewByBase(Pool, Base); + + if (!view) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + + PhFppDereferenceView(Pool, view); +} + +/** + * Allocates blocks from a segment. + * + * \param Pool The file pool. + * \param FirstBlock The first block of the segment. + * \param SegmentHeader The header of the segment. + * \param NumberOfBlocks The number of blocks to allocate. + * + * \return The header of the allocated span, or NULL if there is an insufficient number of + * contiguous free blocks for the allocation. + */ +PPH_FP_BLOCK_HEADER PhFppAllocateBlocks( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, + _In_ ULONG NumberOfBlocks + ) +{ + RTL_BITMAP bitmap; + ULONG hintIndex; + ULONG foundIndex; + PPH_FP_BLOCK_HEADER blockHeader; + + if (FirstBlock != Pool->FirstBlockOfFirstSegment) + hintIndex = Pool->SegmentHeaderBlockSpan; + else + hintIndex = Pool->SegmentHeaderBlockSpan + Pool->FileHeaderBlockSpan; + + RtlInitializeBitMap(&bitmap, SegmentHeader->Bitmap, PH_FP_BLOCK_COUNT); + + // Find a range of free blocks and mark them as in-use. + foundIndex = RtlFindClearBitsAndSet(&bitmap, NumberOfBlocks, hintIndex); + + if (foundIndex == -1) + { + // No more space. + return NULL; + } + + SegmentHeader->FreeBlocks -= NumberOfBlocks; + + blockHeader = (PPH_FP_BLOCK_HEADER)((PCHAR)FirstBlock + (foundIndex << Pool->BlockShift)); + blockHeader->Flags = 0; + blockHeader->Span = NumberOfBlocks; + + return blockHeader; +} + +/** + * Frees blocks in a segment. + * + * \param Pool The file pool. + * \param FirstBlock The first block of the segment. + * \param SegmentHeader The header of the segment. + * \param BlockHeader The header of the allocated span. + */ +VOID PhFppFreeBlocks( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, + _In_ PPH_FP_BLOCK_HEADER BlockHeader + ) +{ + RTL_BITMAP bitmap; + ULONG startIndex; + ULONG blockSpan; + + RtlInitializeBitMap(&bitmap, SegmentHeader->Bitmap, PH_FP_BLOCK_COUNT); + + // Mark the blocks as free. + startIndex = (ULONG)((PCHAR)BlockHeader - (PCHAR)FirstBlock) >> Pool->BlockShift; + blockSpan = BlockHeader->Span; + RtlClearBits(&bitmap, startIndex, blockSpan); + SegmentHeader->FreeBlocks += blockSpan; +} + +/** + * Computes the free list index (category) for a specified number of blocks. + * + * \param Pool The file pool. + * \param NumberOfBlocks The number of free or required blocks. + */ +ULONG PhFppComputeFreeListIndex( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG NumberOfBlocks + ) +{ + // Use a binary tree to speed up comparison. + + if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 64) + { + if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 2) + { + if (NumberOfBlocks >= PH_FP_BLOCK_COUNT - Pool->SegmentHeaderBlockSpan) + return 0; + else + return 1; + } + else + { + if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 16) + return 2; + else + return 3; + } + } + else + { + if (NumberOfBlocks >= 4) + { + if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 256) + return 4; + else + return 5; + } + else + { + if (NumberOfBlocks >= 1) + return 6; + else + return 7; + } + } +} + +/** + * Inserts a segment into a free list. + * + * \param Pool The file pool. + * \param FreeListIndex The index of a free list. + * \param SegmentIndex The index of the segment. + * \param SegmentHeader The header of the segment. + */ +BOOLEAN PhFppInsertFreeList( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG FreeListIndex, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_SEGMENT_HEADER SegmentHeader + ) +{ + ULONG oldSegmentIndex; + PPH_FP_BLOCK_HEADER oldSegmentFirstBlock; + PPH_FP_SEGMENT_HEADER oldSegmentHeader; + + oldSegmentIndex = Pool->Header->FreeLists[FreeListIndex]; + + // Try to reference the segment before we commit any changes. + + if (oldSegmentIndex != -1) + { + oldSegmentFirstBlock = PhFppReferenceSegment(Pool, oldSegmentIndex); + + if (!oldSegmentFirstBlock) + return FALSE; + } + + // Insert the segment into the list. + + SegmentHeader->FreeBlink = -1; + SegmentHeader->FreeFlink = oldSegmentIndex; + Pool->Header->FreeLists[FreeListIndex] = SegmentIndex; + + if (oldSegmentIndex != -1) + { + oldSegmentHeader = PhFppGetHeaderSegment(Pool, oldSegmentFirstBlock); + oldSegmentHeader->FreeBlink = SegmentIndex; + PhFppDereferenceSegment(Pool, oldSegmentIndex); + } + + return TRUE; +} + +/** + * Removes a segment from a free list. + * + * \param Pool The file pool. + * \param FreeListIndex The index of a free list. + * \param SegmentIndex The index of the segment. + * \param SegmentHeader The header of the segment. + */ +BOOLEAN PhFppRemoveFreeList( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG FreeListIndex, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_SEGMENT_HEADER SegmentHeader + ) +{ + ULONG flinkSegmentIndex; + PPH_FP_BLOCK_HEADER flinkSegmentFirstBlock; + PPH_FP_SEGMENT_HEADER flinkSegmentHeader; + ULONG blinkSegmentIndex; + PPH_FP_BLOCK_HEADER blinkSegmentFirstBlock; + PPH_FP_SEGMENT_HEADER blinkSegmentHeader; + + flinkSegmentIndex = SegmentHeader->FreeFlink; + blinkSegmentIndex = SegmentHeader->FreeBlink; + + // Try to reference the segments before we commit any changes. + + if (flinkSegmentIndex != -1) + { + flinkSegmentFirstBlock = PhFppReferenceSegment(Pool, flinkSegmentIndex); + + if (!flinkSegmentFirstBlock) + return FALSE; + } + + if (blinkSegmentIndex != -1) + { + blinkSegmentFirstBlock = PhFppReferenceSegment(Pool, blinkSegmentIndex); + + if (!blinkSegmentFirstBlock) + { + if (flinkSegmentIndex != -1) + PhFppDereferenceSegment(Pool, flinkSegmentIndex); + + return FALSE; + } + } + + // Unlink the segment from the list. + + if (flinkSegmentIndex != -1) + { + flinkSegmentHeader = PhFppGetHeaderSegment(Pool, flinkSegmentFirstBlock); + flinkSegmentHeader->FreeBlink = blinkSegmentIndex; + PhFppDereferenceSegment(Pool, flinkSegmentIndex); + } + + if (blinkSegmentIndex != -1) + { + blinkSegmentHeader = PhFppGetHeaderSegment(Pool, blinkSegmentFirstBlock); + blinkSegmentHeader->FreeFlink = flinkSegmentIndex; + PhFppDereferenceSegment(Pool, blinkSegmentIndex); + } + else + { + // The segment was the list head; select a new one. + Pool->Header->FreeLists[FreeListIndex] = flinkSegmentIndex; + } + + return TRUE; +} + +/** + * Retrieves the header of a block. + * + * \param Pool The file pool. + * \param Block A pointer to the body of the block. + */ +PPH_FP_BLOCK_HEADER PhFppGetHeaderBlock( + _In_ PPH_FILE_POOL Pool, + _In_ PVOID Block + ) +{ + return CONTAINING_RECORD(Block, PH_FP_BLOCK_HEADER, Body); +} + +/** + * Creates a relative virtual address. + * + * \param Pool The file pool. + * \param SegmentIndex The index of the segment containing \a Address. + * \param FirstBlock The first block of the segment containing \a Address. + * \param Address An address. + */ +ULONG PhFppEncodeRva( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _In_ PVOID Address + ) +{ + return (SegmentIndex << Pool->SegmentShift) + (ULONG)((PCHAR)Address - (PCHAR)FirstBlock); +} + +/** + * Decodes a relative virtual address. + * + * \param Pool The file pool. + * \param Rva The relative virtual address. + * \param SegmentIndex A variable which receives the segment index. + * + * \return An offset into the segment specified by \a SegmentIndex, or -1 if \a Rva is invalid. + */ +ULONG PhFppDecodeRva( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG Rva, + _Out_ PULONG SegmentIndex + ) +{ + ULONG segmentIndex; + + segmentIndex = Rva >> Pool->SegmentShift; + + if (segmentIndex >= Pool->Header->SegmentCount) + return -1; + + *SegmentIndex = segmentIndex; + + return Rva & (Pool->SegmentSize - 1); +} diff --git a/phlib/filestream.c b/phlib/filestream.c new file mode 100644 index 0000000..3e7552d --- /dev/null +++ b/phlib/filestream.c @@ -0,0 +1,876 @@ +/* + * Process Hacker - + * file stream object + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +PPH_OBJECT_TYPE PhFileStreamType; + +BOOLEAN PhFileStreamInitialization( + VOID + ) +{ + PH_OBJECT_TYPE_PARAMETERS parameters; + + parameters.FreeListSize = sizeof(PH_FILE_STREAM); + parameters.FreeListCount = 16; + + PhFileStreamType = PhCreateObjectTypeEx(L"FileStream", PH_OBJECT_TYPE_USE_FREE_LIST, PhpFileStreamDeleteProcedure, ¶meters); + + return TRUE; +} + +NTSTATUS PhCreateFileStream( + _Out_ PPH_FILE_STREAM *FileStream, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG Flags + ) +{ + NTSTATUS status; + PPH_FILE_STREAM fileStream; + HANDLE fileHandle; + ULONG createOptions; + + if (Flags & PH_FILE_STREAM_ASYNCHRONOUS) + createOptions = FILE_NON_DIRECTORY_FILE; + else + createOptions = FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT; + + if (!NT_SUCCESS(status = PhCreateFileWin32( + &fileHandle, + FileName, + DesiredAccess, + 0, + ShareAccess, + CreateDisposition, + createOptions + ))) + return status; + + if (!NT_SUCCESS(status = PhCreateFileStream2( + &fileStream, + fileHandle, + Flags, + PAGE_SIZE + ))) + { + NtClose(fileHandle); + return status; + } + + if (Flags & PH_FILE_STREAM_APPEND) + { + LARGE_INTEGER zero; + + zero.QuadPart = 0; + + if (!NT_SUCCESS(PhSeekFileStream( + fileStream, + &zero, + SeekEnd + ))) + { + PhDereferenceObject(fileStream); + return status; + } + } + + *FileStream = fileStream; + + return status; +} + +NTSTATUS PhCreateFileStream2( + _Out_ PPH_FILE_STREAM *FileStream, + _In_ HANDLE FileHandle, + _In_ ULONG Flags, + _In_ ULONG BufferLength + ) +{ + PPH_FILE_STREAM fileStream; + + fileStream = PhCreateObject(sizeof(PH_FILE_STREAM), PhFileStreamType); + fileStream->FileHandle = FileHandle; + fileStream->Flags = Flags; + fileStream->Position.QuadPart = 0; + + if (!(Flags & PH_FILE_STREAM_UNBUFFERED)) + { + fileStream->Buffer = NULL; + fileStream->BufferLength = BufferLength; + } + else + { + fileStream->Buffer = NULL; + fileStream->BufferLength = 0; + } + + fileStream->ReadPosition = 0; + fileStream->ReadLength = 0; + fileStream->WritePosition = 0; + + *FileStream = fileStream; + + return STATUS_SUCCESS; +} + +VOID NTAPI PhpFileStreamDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_FILE_STREAM fileStream = (PPH_FILE_STREAM)Object; + + PhFlushFileStream(fileStream, FALSE); + + if (!(fileStream->Flags & PH_FILE_STREAM_HANDLE_UNOWNED)) + NtClose(fileStream->FileHandle); + + if (fileStream->Buffer) + PhFreePage(fileStream->Buffer); +} + +/** + * Verifies that a file stream's position matches the position held by the file object. + */ +VOID PhVerifyFileStream( + _In_ PPH_FILE_STREAM FileStream + ) +{ + NTSTATUS status; + + // If the file object is asynchronous, the file object doesn't maintain its position. + if (!(FileStream->Flags & ( + PH_FILE_STREAM_OWN_POSITION | + PH_FILE_STREAM_ASYNCHRONOUS + ))) + { + FILE_POSITION_INFORMATION positionInfo; + IO_STATUS_BLOCK isb; + + if (!NT_SUCCESS(status = NtQueryInformationFile( + FileStream->FileHandle, + &isb, + &positionInfo, + sizeof(FILE_POSITION_INFORMATION), + FilePositionInformation + ))) + PhRaiseStatus(status); + + if (FileStream->Position.QuadPart != positionInfo.CurrentByteOffset.QuadPart) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + } +} + +NTSTATUS PhpAllocateBufferFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ) +{ + FileStream->Buffer = PhAllocatePage(FileStream->BufferLength, NULL); + + if (FileStream->Buffer) + return STATUS_SUCCESS; + else + return STATUS_NO_MEMORY; +} + +NTSTATUS PhpReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReadLength + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + PLARGE_INTEGER position; + + position = NULL; + + if (FileStream->Flags & PH_FILE_STREAM_OWN_POSITION) + position = &FileStream->Position; + + status = NtReadFile( + FileStream->FileHandle, + NULL, + NULL, + NULL, + &isb, + Buffer, + Length, + position, + NULL + ); + + if (status == STATUS_PENDING) + { + // Wait for the operation to finish. This probably means we got called on an asynchronous + // file object. + status = NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); + + if (NT_SUCCESS(status)) + status = isb.Status; + } + + if (NT_SUCCESS(status)) + { + FileStream->Position.QuadPart += isb.Information; + + if (ReadLength) + *ReadLength = (ULONG)isb.Information; + } + + return status; +} + +NTSTATUS PhReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReadLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG availableLength; + ULONG readLength; + + if (FileStream->Flags & PH_FILE_STREAM_UNBUFFERED) + { + return PhpReadFileStream( + FileStream, + Buffer, + Length, + ReadLength + ); + } + + // How much do we have available to copy out of the buffer? + availableLength = FileStream->ReadLength - FileStream->ReadPosition; + + if (availableLength == 0) + { + // Make sure buffered writes are flushed. + if (FileStream->WritePosition != 0) + { + if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) + return status; + } + + // If this read is too big, pass it through. + if (Length >= FileStream->BufferLength) + { + // These are now invalid. + FileStream->ReadPosition = 0; + FileStream->ReadLength = 0; + + return PhpReadFileStream( + FileStream, + Buffer, + Length, + ReadLength + ); + } + + if (!FileStream->Buffer) + { + if (!NT_SUCCESS(status = PhpAllocateBufferFileStream(FileStream))) + return status; + } + + // Read as much as we can into our buffer. + if (!NT_SUCCESS(status = PhpReadFileStream( + FileStream, + FileStream->Buffer, + FileStream->BufferLength, + &readLength + ))) + return status; + + if (readLength == 0) + { + // No data read. + if (ReadLength) + *ReadLength = readLength; + + return status; + } + + FileStream->ReadPosition = 0; + FileStream->ReadLength = readLength; + } + else + { + readLength = availableLength; + } + + if (readLength > Length) + readLength = Length; + + // Try to satisfy the request from the buffer. + memcpy( + Buffer, + (PCHAR)FileStream->Buffer + FileStream->ReadPosition, + readLength + ); + FileStream->ReadPosition += readLength; + + // If we didn't completely satisfy the request, read some more. + if ( + readLength < Length && + // Don't try to read more if the buffer wasn't even filled up last time. (No more to read.) + FileStream->ReadLength == FileStream->BufferLength + ) + { + ULONG readLength2; + + if (NT_SUCCESS(status = PhpReadFileStream( + FileStream, + (PCHAR)Buffer + readLength, + Length - readLength, + &readLength2 + ))) + { + readLength += readLength2; + // These are now invalid. + FileStream->ReadPosition = 0; + FileStream->ReadLength = 0; + } + } + + if (NT_SUCCESS(status)) + { + if (ReadLength) + *ReadLength = readLength; + } + + return status; +} + +NTSTATUS PhpWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + PLARGE_INTEGER position; + + position = NULL; + + if (FileStream->Flags & PH_FILE_STREAM_OWN_POSITION) + position = &FileStream->Position; + + status = NtWriteFile( + FileStream->FileHandle, + NULL, + NULL, + NULL, + &isb, + Buffer, + Length, + position, + NULL + ); + + if (status == STATUS_PENDING) + { + // Wait for the operation to finish. This probably means we got called on an asynchronous + // file object. + status = NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); + + if (NT_SUCCESS(status)) + status = isb.Status; + } + + if (NT_SUCCESS(status)) + { + FileStream->Position.QuadPart += isb.Information; + FileStream->Flags |= PH_FILE_STREAM_WRITTEN; + } + + return status; +} + +NTSTATUS PhWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG availableLength; + ULONG writtenLength; + + if (FileStream->Flags & PH_FILE_STREAM_UNBUFFERED) + { + return PhpWriteFileStream( + FileStream, + Buffer, + Length + ); + } + + if (FileStream->WritePosition == 0) + { + // Make sure buffered reads are flushed. + if (!NT_SUCCESS(status = PhpFlushReadFileStream(FileStream))) + return status; + } + + if (FileStream->WritePosition != 0) + { + availableLength = FileStream->BufferLength - FileStream->WritePosition; + + // Try to satisfy the request by copying the data to the buffer. + if (availableLength != 0) + { + writtenLength = availableLength; + + if (writtenLength > Length) + writtenLength = Length; + + memcpy( + (PCHAR)FileStream->Buffer + FileStream->WritePosition, + Buffer, + writtenLength + ); + FileStream->WritePosition += writtenLength; + + if (writtenLength == Length) + { + // The request has been completely satisfied. + return status; + } + + Buffer = (PCHAR)Buffer + writtenLength; + Length -= writtenLength; + } + + // If we didn't completely satisfy the request, it's because the buffer is full. Flush it. + if (!NT_SUCCESS(status = PhpWriteFileStream( + FileStream, + FileStream->Buffer, + FileStream->WritePosition + ))) + return status; + + FileStream->WritePosition = 0; + } + + // If the write is too big, pass it through. + if (Length >= FileStream->BufferLength) + { + if (!NT_SUCCESS(status = PhpWriteFileStream( + FileStream, + Buffer, + Length + ))) + return status; + } + else if (Length != 0) + { + if (!FileStream->Buffer) + { + if (!NT_SUCCESS(status = PhpAllocateBufferFileStream(FileStream))) + return status; + } + + // Completely satisfy the request by copying the data to the buffer. + memcpy( + FileStream->Buffer, + Buffer, + Length + ); + FileStream->WritePosition = Length; + } + + return status; +} + +NTSTATUS PhpFlushReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + if (FileStream->ReadLength - FileStream->ReadPosition != 0) + { + LARGE_INTEGER offset; + + // We have some buffered read data, so our position is too far ahead. We need to move it + // back to the first unused byte. + offset.QuadPart = -(LONG)(FileStream->ReadLength - FileStream->ReadPosition); + + if (!NT_SUCCESS(status = PhpSeekFileStream( + FileStream, + &offset, + SeekCurrent + ))) + return status; + } + + FileStream->ReadPosition = 0; + FileStream->ReadLength = 0; + + return status; +} + +NTSTATUS PhpFlushWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + if (!NT_SUCCESS(status = PhpWriteFileStream( + FileStream, + FileStream->Buffer, + FileStream->WritePosition + ))) + return status; + + FileStream->WritePosition = 0; + + return status; +} + +/** + * Flushes the file stream. + * + * \param FileStream A file stream object. + * \param Full TRUE to flush the file object through the operating system, otherwise FALSE to only + * ensure the buffer is flushed to the operating system. + */ +NTSTATUS PhFlushFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ BOOLEAN Full + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + if (FileStream->WritePosition != 0) + { + if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) + return status; + } + + if (FileStream->ReadPosition != 0) + { + if (!NT_SUCCESS(status = PhpFlushReadFileStream(FileStream))) + return status; + } + + if (Full && (FileStream->Flags & PH_FILE_STREAM_WRITTEN)) + { + IO_STATUS_BLOCK isb; + + if (!NT_SUCCESS(status = NtFlushBuffersFile( + FileStream->FileHandle, + &isb + ))) + return status; + } + + return status; +} + +VOID PhGetPositionFileStream( + _In_ PPH_FILE_STREAM FileStream, + _Out_ PLARGE_INTEGER Position + ) +{ + Position->QuadPart = + FileStream->Position.QuadPart + + (FileStream->ReadPosition - FileStream->ReadLength) + + FileStream->WritePosition; +} + +NTSTATUS PhpSeekFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Offset, + _In_ PH_SEEK_ORIGIN Origin + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + switch (Origin) + { + case SeekStart: + { + FileStream->Position = *Offset; + } + break; + case SeekCurrent: + { + FileStream->Position.QuadPart += Offset->QuadPart; + } + break; + case SeekEnd: + { + if (!NT_SUCCESS(status = PhGetFileSize( + FileStream->FileHandle, + &FileStream->Position + ))) + return status; + + FileStream->Position.QuadPart += Offset->QuadPart; + } + break; + } + + if (!(FileStream->Flags & PH_FILE_STREAM_OWN_POSITION)) + { + FILE_POSITION_INFORMATION positionInfo; + IO_STATUS_BLOCK isb; + + positionInfo.CurrentByteOffset = FileStream->Position; + + if (!NT_SUCCESS(status = NtSetInformationFile( + FileStream->FileHandle, + &isb, + &positionInfo, + sizeof(FILE_POSITION_INFORMATION), + FilePositionInformation + ))) + return status; + } + + return status; +} + +NTSTATUS PhSeekFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Offset, + _In_ PH_SEEK_ORIGIN Origin + ) +{ + NTSTATUS status = STATUS_SUCCESS; + LARGE_INTEGER offset; + + offset = *Offset; + + if (FileStream->WritePosition != 0) + { + if (!NT_SUCCESS(status = PhpFlushWriteFileStream(FileStream))) + return status; + } + else if (FileStream->ReadPosition != 0) + { + if (Origin == SeekCurrent) + { + // We have buffered read data, which means our position is too far ahead. Subtract this + // difference from the offset (which will affect the position accordingly). + offset.QuadPart -= FileStream->ReadLength - FileStream->ReadPosition; + } + + // TODO: Try to keep some of the read buffer. + FileStream->ReadPosition = 0; + FileStream->ReadLength = 0; + } + + if (!NT_SUCCESS(status = PhpSeekFileStream( + FileStream, + &offset, + Origin + ))) + return status; + + return status; +} + +NTSTATUS PhLockFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Position, + _In_ PLARGE_INTEGER Length, + _In_ BOOLEAN Wait, + _In_ BOOLEAN Shared + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + + status = NtLockFile( + FileStream->FileHandle, + NULL, + NULL, + NULL, + &isb, + Position, + Length, + 0, + !Wait, + !Shared + ); + + if (status == STATUS_PENDING) + { + // Wait for the operation to finish. This probably means we got called on an asynchronous + // file object. + NtWaitForSingleObject(FileStream->FileHandle, FALSE, NULL); + status = isb.Status; + } + + return status; +} + +NTSTATUS PhUnlockFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Position, + _In_ PLARGE_INTEGER Length + ) +{ + IO_STATUS_BLOCK isb; + + return NtUnlockFile( + FileStream->FileHandle, + &isb, + Position, + Length, + 0 + ); +} + +NTSTATUS PhWriteStringAsUtf8FileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PPH_STRINGREF String + ) +{ + return PhWriteStringAsUtf8FileStreamEx(FileStream, String->Buffer, String->Length); +} + +NTSTATUS PhWriteStringAsUtf8FileStream2( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PWSTR String + ) +{ + PH_STRINGREF string; + + PhInitializeStringRef(&string, String); + + return PhWriteStringAsUtf8FileStream(FileStream, &string); +} + +NTSTATUS PhWriteStringAsUtf8FileStreamEx( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PWSTR Buffer, + _In_ SIZE_T Length + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PH_STRINGREF block; + SIZE_T inPlaceUtf8Size; + PCHAR inPlaceUtf8 = NULL; + PPH_BYTES utf8 = NULL; + + if (Length > PAGE_SIZE) + { + // In UTF-8, the maximum number of bytes per code point is 4. + inPlaceUtf8Size = PAGE_SIZE / sizeof(WCHAR) * 4; + inPlaceUtf8 = PhAllocatePage(inPlaceUtf8Size, NULL); + } + + while (Length != 0) + { + block.Buffer = Buffer; + block.Length = PAGE_SIZE; + + if (block.Length > Length) + block.Length = Length; + + if (inPlaceUtf8) + { + SIZE_T bytesInUtf8String; + + if (!PhConvertUtf16ToUtf8Buffer( + inPlaceUtf8, + inPlaceUtf8Size, + &bytesInUtf8String, + block.Buffer, + block.Length + )) + { + status = STATUS_INVALID_PARAMETER; + goto CleanupExit; + } + + status = PhWriteFileStream(FileStream, inPlaceUtf8, (ULONG)bytesInUtf8String); + } + else + { + utf8 = PhConvertUtf16ToUtf8Ex(block.Buffer, block.Length); + + if (!utf8) + { + status = STATUS_INVALID_PARAMETER; + goto CleanupExit; + } + + status = PhWriteFileStream(FileStream, utf8->Buffer, (ULONG)utf8->Length); + PhDereferenceObject(utf8); + } + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + Buffer += block.Length / sizeof(WCHAR); + Length -= block.Length; + } + +CleanupExit: + if (inPlaceUtf8) + PhFreePage(inPlaceUtf8); + + return status; +} + +NTSTATUS PhWriteStringFormatAsUtf8FileStream_V( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ) +{ + NTSTATUS status; + PPH_STRING string; + + string = PhFormatString_V(Format, ArgPtr); + status = PhWriteStringAsUtf8FileStream(FileStream, &string->sr); + PhDereferenceObject(string); + + return status; +} + +NTSTATUS PhWriteStringFormatAsUtf8FileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ _Printf_format_string_ PWSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + + return PhWriteStringFormatAsUtf8FileStream_V(FileStream, Format, argptr); +} diff --git a/phlib/format.c b/phlib/format.c new file mode 100644 index 0000000..d95d5fd --- /dev/null +++ b/phlib/format.c @@ -0,0 +1,276 @@ +/* + * Process Hacker - + * string formatting + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This module provides a high-performance string formatting mechanism. Instead of using format + * strings, the user supplies an array of structures. This system is 2-5 times faster than + * printf-based functions. + * + * This file contains the public interfaces, while including the real formatting code from + * elsewhere. There are currently two functions: PhFormat, which returns a string object containing + * the formatted string, and PhFormatToBuffer, which writes the formatted string to a buffer. The + * latter is a bit faster due to the lack of resizing logic. + */ + +#include +#include + +extern ULONG PhMaxSizeUnit; + +#define SMALL_BUFFER_LENGTH (PH_OBJECT_SMALL_OBJECT_SIZE - FIELD_OFFSET(PH_STRING, Data) - sizeof(WCHAR)) +#define BUFFER_SIZE 512 + +#define PHP_FORMAT_NEGATIVE 0x1 +#define PHP_FORMAT_POSITIVE 0x2 +#define PHP_FORMAT_PAD 0x4 + +// Internal CRT routine needed for floating-point conversion + +errno_t __cdecl _cfltcvt_l(double *arg, char *buffer, size_t sizeInBytes, + int format, int precision, int caps, _locale_t plocinfo); + +// Keep in sync with PhSizeUnitNames +static PH_STRINGREF PhpSizeUnitNamesCounted[7] = +{ + PH_STRINGREF_INIT(L"B"), + PH_STRINGREF_INIT(L"kB"), + PH_STRINGREF_INIT(L"MB"), + PH_STRINGREF_INIT(L"GB"), + PH_STRINGREF_INIT(L"TB"), + PH_STRINGREF_INIT(L"PB"), + PH_STRINGREF_INIT(L"EB") +}; + +static PH_INITONCE PhpFormatInitOnce = PH_INITONCE_INIT; +static WCHAR PhpFormatDecimalSeparator = '.'; +static WCHAR PhpFormatThousandSeparator = ','; +static _locale_t PhpFormatUserLocale = NULL; + +#if (_MSC_VER >= 1900) + +// See Source\10.0.10150.0\ucrt\convert\cvt.cpp in SDK v10. +errno_t __cdecl __acrt_fp_format( + double const* const value, + char* const result_buffer, + size_t const result_buffer_count, + char* const scratch_buffer, + size_t const scratch_buffer_count, + int const format, + int const precision, + UINT64 const options, + _locale_t const locale + ); + +static errno_t __cdecl _cfltcvt_l(double *arg, char *buffer, size_t sizeInBytes, + int format, int precision, int caps, _locale_t plocinfo) +{ + char scratch_buffer[_CVTBUFSIZE + 1]; + + if (caps & 1) + format -= 32; // Make uppercase + + return __acrt_fp_format(arg, buffer, sizeInBytes, scratch_buffer, sizeof(scratch_buffer), + format, precision, 0, plocinfo); +} + +#endif + +// From Source\10.0.10150.0\ucrt\inc\corecrt_internal_stdio_output.h in SDK v10. +VOID PhpCropZeros( + _Inout_ PCHAR Buffer, + _In_ _locale_t Locale + ) +{ + CHAR decimalSeparator = (CHAR)PhpFormatDecimalSeparator; + + while (*Buffer && *Buffer != decimalSeparator) + ++Buffer; + + if (*Buffer++) + { + while (*Buffer && *Buffer != 'e' && *Buffer != 'E') + ++Buffer; + + PCHAR stop = Buffer--; + + while (*Buffer == '0') + --Buffer; + + if (*Buffer == decimalSeparator) + --Buffer; + + while ((*++Buffer = *stop++) != '\0') + NOTHING; + } +} + +PPH_STRING PhpResizeFormatBuffer( + _In_ PPH_STRING String, + _Inout_ PSIZE_T AllocatedLength, + _In_ SIZE_T UsedLength, + _In_ SIZE_T NeededLength + ) +{ + PPH_STRING newString; + SIZE_T allocatedLength; + + allocatedLength = *AllocatedLength; + allocatedLength *= 2; + + if (allocatedLength < UsedLength + NeededLength) + allocatedLength = UsedLength + NeededLength; + + newString = PhCreateStringEx(NULL, allocatedLength); + memcpy(newString->Buffer, String->Buffer, UsedLength); + PhDereferenceObject(String); + + *AllocatedLength = allocatedLength; + + return newString; +} + +/** + * Creates a formatted string. + * + * \param Format An array of format structures. + * \param Count The number of structures supplied in \a Format. + * \param InitialCapacity The number of bytes to reserve initially for the string. If 0 is + * specified, a default value is used. + */ +PPH_STRING PhFormat( + _In_reads_(Count) PPH_FORMAT Format, + _In_ ULONG Count, + _In_opt_ SIZE_T InitialCapacity + ) +{ + PPH_STRING string; + SIZE_T allocatedLength; + PWSTR buffer; + SIZE_T usedLength; + + // Set up the buffer. + + // If the specified initial capacity is too small (or zero), use the largest buffer size which + // will still be eligible for allocation from the small object free list. + if (InitialCapacity < SMALL_BUFFER_LENGTH) + InitialCapacity = SMALL_BUFFER_LENGTH; + + string = PhCreateStringEx(NULL, InitialCapacity); + allocatedLength = InitialCapacity; + buffer = string->Buffer; + usedLength = 0; + +#undef ENSURE_BUFFER +#undef OK_BUFFER +#undef ADVANCE_BUFFER + +#define ENSURE_BUFFER(NeededLength) \ + do { \ + if (allocatedLength < usedLength + (NeededLength)) \ + { \ + string = PhpResizeFormatBuffer(string, &allocatedLength, usedLength, (NeededLength)); \ + buffer = string->Buffer + usedLength / sizeof(WCHAR); \ + } \ + } while (0) + +#define OK_BUFFER (TRUE) + +#define ADVANCE_BUFFER(Length) \ + do { buffer += (Length) / sizeof(WCHAR); usedLength += (Length); } while (0) + +#include "format_i.h" + + string->Length = usedLength; + // Null-terminate the string. + string->Buffer[usedLength / sizeof(WCHAR)] = 0; + + return string; +} + +/** + * Writes a formatted string to a buffer. + * + * \param Format An array of format structures. + * \param Count The number of structures supplied in \a Format. + * \param Buffer A buffer. If NULL, no data is written. + * \param BufferLength The number of bytes available in \a Buffer, including space for the null + * terminator. + * \param ReturnLength The number of bytes required to hold the string, including the null + * terminator. + * + * \return TRUE if the buffer was large enough and the string was written (i.e. \a BufferLength >= + * \a ReturnLength), otherwise FALSE. In either case, the required number of bytes is stored in + * \a ReturnLength. + * + * \remarks If the function fails but \a BufferLength != 0, a single null byte is written to the + * start of \a Buffer. + */ +BOOLEAN PhFormatToBuffer( + _In_reads_(Count) PPH_FORMAT Format, + _In_ ULONG Count, + _Out_writes_bytes_opt_(BufferLength) PWSTR Buffer, + _In_opt_ SIZE_T BufferLength, + _Out_opt_ PSIZE_T ReturnLength + ) +{ + PWSTR buffer; + SIZE_T usedLength; + BOOLEAN overrun; + + buffer = Buffer; + usedLength = 0; + overrun = FALSE; + + // Make sure we don't try to write anything if we don't have a buffer. + if (!Buffer) + overrun = TRUE; + +#undef ENSURE_BUFFER +#undef OK_BUFFER +#undef ADVANCE_BUFFER + +#define ENSURE_BUFFER(NeededLength) \ + do { \ + if (!overrun && (BufferLength < usedLength + (NeededLength))) \ + overrun = TRUE; \ + } while (0) + +#define OK_BUFFER (!overrun) + +#define ADVANCE_BUFFER(Length) \ + do { buffer += (Length) / sizeof(WCHAR); usedLength += (Length); } while (0) + +#include "format_i.h" + + // Write the null-terminator. + ENSURE_BUFFER(sizeof(WCHAR)); + if (OK_BUFFER) + *buffer = 0; + else if (Buffer && BufferLength != 0) // try to null-terminate even if this function fails + *Buffer = 0; + ADVANCE_BUFFER(sizeof(WCHAR)); + + if (ReturnLength) + *ReturnLength = usedLength; + + return OK_BUFFER; +} diff --git a/phlib/format_i.h b/phlib/format_i.h new file mode 100644 index 0000000..31a5771 --- /dev/null +++ b/phlib/format_i.h @@ -0,0 +1,568 @@ +/* + * This file contains the actual formatting code used by various public interface functions. + * + * There are three macros defined by the parent function which control how this code writes the + * formatted string: + * * ENSURE_BUFFER - This macro is passed the number of bytes required whenever characters need to + * be written to the buffer. The macro can resize the buffer if needed. + * * OK_BUFFER - This macro returns TRUE if it is OK to write to the buffer, otherwise FALSE when + * the buffer is too large, is not specified, or some other error has occurred. + * * ADVANCE_BUFFER - This macro is passed the number of bytes written to the buffer and should + * increment the "buffer" pointer and "usedLength" counter. + * In addition to these macros, the "buffer" and "usedLength" variables are assumed to be present. + * + * The below code defines many macros; this is so that composite formatting types can be constructed + * (e.g. the "size" type). + */ + +{ + if (PhBeginInitOnce(&PhpFormatInitOnce)) + { + WCHAR localeBuffer[4]; + + if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, localeBuffer, 4) && + (localeBuffer[0] != 0 && localeBuffer[1] == 0)) + { + PhpFormatDecimalSeparator = localeBuffer[0]; + } + + if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, localeBuffer, 4) && + (localeBuffer[0] != 0 && localeBuffer[1] == 0)) + { + PhpFormatThousandSeparator = localeBuffer[0]; + } + + if (PhpFormatDecimalSeparator != '.') + PhpFormatUserLocale = _create_locale(LC_ALL, ""); + + PhEndInitOnce(&PhpFormatInitOnce); + } + + while (Count--) + { + PPH_FORMAT format; + SIZE_T partLength; + WCHAR tempBuffer[BUFFER_SIZE]; + ULONG flags; + ULONG int32; + ULONG64 int64; + + format = Format++; + + // Save the currently used length so we can compute the part length later. + partLength = usedLength; + + flags = 0; + + switch (format->Type & FormatTypeMask) + { + + // Characters and Strings + + case CharFormatType: + ENSURE_BUFFER(sizeof(WCHAR)); + if (OK_BUFFER) + *buffer = format->u.Char; + ADVANCE_BUFFER(sizeof(WCHAR)); + break; + case StringFormatType: + ENSURE_BUFFER(format->u.String.Length); + if (OK_BUFFER) + memcpy(buffer, format->u.String.Buffer, format->u.String.Length); + ADVANCE_BUFFER(format->u.String.Length); + break; + case StringZFormatType: + { + SIZE_T count; + + count = PhCountStringZ(format->u.StringZ); + ENSURE_BUFFER(count * sizeof(WCHAR)); + if (OK_BUFFER) + memcpy(buffer, format->u.StringZ, count * sizeof(WCHAR)); + ADVANCE_BUFFER(count * sizeof(WCHAR)); + } + break; + case MultiByteStringFormatType: + case MultiByteStringZFormatType: + { + ULONG bytesInUnicodeString; + PSTR multiByteBuffer; + SIZE_T multiByteLength; + + if (format->Type == MultiByteStringFormatType) + { + multiByteBuffer = format->u.MultiByteString.Buffer; + multiByteLength = format->u.MultiByteString.Length; + } + else + { + multiByteBuffer = format->u.MultiByteStringZ; + multiByteLength = strlen(multiByteBuffer); + } + + if (NT_SUCCESS(RtlMultiByteToUnicodeSize( + &bytesInUnicodeString, + multiByteBuffer, + (ULONG)multiByteLength + ))) + { + ENSURE_BUFFER(bytesInUnicodeString); + + if (!OK_BUFFER || NT_SUCCESS(RtlMultiByteToUnicodeN( + buffer, + bytesInUnicodeString, + NULL, + multiByteBuffer, + (ULONG)multiByteLength + ))) + { + ADVANCE_BUFFER(bytesInUnicodeString); + } + } + } + break; + + // Integers + +#define PROCESS_DIGIT(Input) \ + do { \ + r = (ULONG)(Input % radix); \ + Input /= radix; \ + *temp-- = integerToChar[r]; \ + tempCount++; \ + } while (0) + +#define COMMON_INTEGER_FORMAT(Input, Format) \ + do { \ + ULONG radix; \ + PCHAR integerToChar; \ + PWSTR temp; \ + ULONG tempCount; \ + ULONG r; \ + ULONG preCount; \ + ULONG padCount; \ + \ + radix = 10; \ + if (((Format)->Type & FormatUseRadix) && (Format)->Radix >= 2 && (Format)->Radix <= 69) \ + radix = (Format)->Radix; \ + integerToChar = PhIntegerToChar; \ + if ((Format)->Type & FormatUpperCase) \ + integerToChar = PhIntegerToCharUpper; \ + temp = tempBuffer + BUFFER_SIZE - 1; \ + tempCount = 0; \ + \ + if (Input != 0) \ + { \ + if ((Format)->Type & FormatGroupDigits) \ + { \ + ULONG needsSep = 0; \ + \ + do \ + { \ + PROCESS_DIGIT(Input); \ + \ + if (++needsSep == 3 && Input != 0) /* get rid of trailing separator */ \ + { \ + *temp-- = PhpFormatThousandSeparator; \ + tempCount++; \ + needsSep = 0; \ + } \ + } while (Input != 0); \ + } \ + else \ + { \ + do \ + { \ + PROCESS_DIGIT(Input); \ + } while (Input != 0); \ + } \ + } \ + else \ + { \ + *temp-- = '0'; \ + tempCount++; \ + } \ + \ + preCount = 0; \ + \ + if (flags & PHP_FORMAT_NEGATIVE) \ + preCount++; \ + else if ((Format)->Type & FormatPrefixSign) \ + preCount++; \ + \ + if (((Format)->Type & FormatPadZeros) && !((Format)->Type & FormatGroupDigits)) \ + { \ + if (preCount + tempCount < (Format)->Width) \ + { \ + flags |= PHP_FORMAT_PAD; \ + padCount = (Format)->Width - (preCount + tempCount); \ + preCount += padCount; \ + } \ + } \ + \ + temp++; \ + ENSURE_BUFFER((preCount + tempCount) * sizeof(WCHAR)); \ + if (OK_BUFFER) \ + { \ + if (flags & PHP_FORMAT_NEGATIVE) \ + *buffer++ = '-'; \ + else if ((Format)->Type & FormatPrefixSign) \ + *buffer++ = '+'; \ + \ + if (flags & PHP_FORMAT_PAD) \ + { \ + wmemset(buffer, '0', padCount); \ + buffer += padCount; \ + } \ + \ + memcpy(buffer, temp, tempCount * sizeof(WCHAR)); \ + buffer += tempCount; \ + } \ + usedLength += (preCount + tempCount) * sizeof(WCHAR); \ + } while (0) + +#ifndef _WIN64 + case IntPtrFormatType: + int32 = format->u.IntPtr; + goto CommonMaybeNegativeInt32Format; +#endif + case Int32FormatType: + int32 = format->u.Int32; + +#ifndef _WIN64 +CommonMaybeNegativeInt32Format: +#endif + if ((LONG)int32 < 0) + { + int32 = -(LONG)int32; + flags |= PHP_FORMAT_NEGATIVE; + } + + goto CommonInt32Format; +#ifndef _WIN64 + case UIntPtrFormatType: + int32 = format->u.UIntPtr; + goto CommonInt32Format; +#endif + case UInt32FormatType: + int32 = format->u.UInt32; +CommonInt32Format: + COMMON_INTEGER_FORMAT(int32, format); + break; +#ifdef _WIN64 + case IntPtrFormatType: + int64 = format->u.IntPtr; + goto CommonMaybeNegativeInt64Format; +#endif + case Int64FormatType: + int64 = format->u.Int64; + +#ifdef _WIN64 +CommonMaybeNegativeInt64Format: +#endif + if ((LONG64)int64 < 0) + { + int64 = -(LONG64)int64; + flags |= PHP_FORMAT_NEGATIVE; + } + + goto CommonInt64Format; +#ifdef _WIN64 + case UIntPtrFormatType: + int64 = format->u.UIntPtr; + goto CommonInt64Format; +#endif + case UInt64FormatType: + int64 = format->u.UInt64; +CommonInt64Format: + COMMON_INTEGER_FORMAT(int64, format); + break; + + // Floating point numbers + +#define COMMON_DOUBLE_FORMAT(Format) \ + do { \ + ULONG precision; \ + DOUBLE value; \ + CHAR c; \ + PSTR temp; \ + ULONG length; \ + \ + if ((Format)->Type & FormatUsePrecision) \ + { \ + precision = (Format)->Precision; \ + \ + if (precision > BUFFER_SIZE - 1 - _CVTBUFSIZE) \ + precision = BUFFER_SIZE - 1 - _CVTBUFSIZE; \ + } \ + else \ + { \ + precision = 6; \ + } \ + \ + c = 'f'; \ + \ + if ((Format)->Type & FormatStandardForm) \ + c = 'e'; \ + else if ((Format)->Type & FormatHexadecimalForm) \ + c = 'a'; \ + \ + /* Use MS CRT routines to do the work. */ \ + \ + value = (Format)->u.Double; \ + temp = (PSTR)tempBuffer + 1; /* leave one character so we can insert a prefix if needed */ \ + _cfltcvt_l( \ + &value, \ + temp, \ + sizeof(tempBuffer) - 1, \ + c, \ + precision, \ + !!((Format)->Type & FormatUpperCase), \ + PhpFormatUserLocale \ + ); \ + \ + /* if (((Format)->Type & FormatForceDecimalPoint) && precision == 0) */ \ + /* _forcdecpt_l(tempBufferAnsi, PhpFormatUserLocale); */ \ + if ((Format)->Type & FormatCropZeros) \ + PhpCropZeros(temp, PhpFormatUserLocale); \ + \ + length = (ULONG)strlen(temp); \ + \ + if (temp[0] == '-') \ + { \ + flags |= PHP_FORMAT_NEGATIVE; \ + temp++; \ + length--; \ + } \ + else if ((Format)->Type & FormatPrefixSign) \ + { \ + flags |= PHP_FORMAT_POSITIVE; \ + } \ + \ + if (((Format)->Type & FormatGroupDigits) && !((Format)->Type & (FormatStandardForm | FormatHexadecimalForm))) \ + { \ + PSTR whole; \ + PSTR decimalPoint; \ + ULONG wholeCount; \ + ULONG sepsCount; \ + ULONG ensureLength; \ + ULONG copyCount; \ + ULONG needsSep; \ + \ + /* Find the first non-digit character and assume that is the */ \ + /* decimal point (or the end of the string). */ \ + \ + whole = temp; \ + decimalPoint = temp; \ + \ + while ((UCHAR)(*decimalPoint - '0') < 10) \ + decimalPoint++; \ + \ + /* Copy the characters to the output buffer, and at the same time */ \ + /* insert the separators. */ \ + \ + wholeCount = (ULONG)(decimalPoint - temp); \ + \ + if (wholeCount != 0) \ + sepsCount = (wholeCount + 2) / 3 - 1; \ + else \ + sepsCount = 0; \ + \ + ensureLength = (length + sepsCount) * sizeof(WCHAR); \ + if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \ + ensureLength += sizeof(WCHAR); \ + ENSURE_BUFFER(ensureLength); \ + \ + copyCount = wholeCount; \ + needsSep = (wholeCount + 2) % 3; \ + \ + if (OK_BUFFER) \ + { \ + if (flags & PHP_FORMAT_NEGATIVE) \ + *buffer++ = '-'; \ + else if (flags & PHP_FORMAT_POSITIVE) \ + *buffer++ = '+'; \ + \ + while (copyCount--) \ + { \ + *buffer++ = *whole++; \ + \ + if (needsSep-- == 0 && copyCount != 0) /* get rid of trailing separator */ \ + { \ + *buffer++ = PhpFormatThousandSeparator; \ + needsSep = 2; \ + } \ + } \ + } \ + \ + if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \ + usedLength += sizeof(WCHAR); \ + usedLength += (wholeCount + sepsCount) * sizeof(WCHAR); \ + \ + /* Copy the rest. */ \ + \ + copyCount = length - wholeCount; \ + \ + if (OK_BUFFER) \ + { \ + PhZeroExtendToUtf16Buffer(decimalPoint, copyCount, buffer); \ + ADVANCE_BUFFER(copyCount * sizeof(WCHAR)); \ + } \ + } \ + else \ + { \ + SIZE_T preLength; \ + SIZE_T padLength; \ + \ + /* Take care of the sign and zero padding. */ \ + preLength = 0; \ + \ + if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \ + preLength++; \ + \ + if ((Format)->Type & FormatPadZeros) \ + { \ + if (preLength + length < (Format)->Width) \ + { \ + flags |= PHP_FORMAT_PAD; \ + padLength = (Format)->Width - (preLength + length); \ + preLength += padLength; \ + } \ + } \ + /* We don't need to group digits, so directly copy the characters */ \ + /* to the output buffer. */ \ + \ + ENSURE_BUFFER((preLength + length) * sizeof(WCHAR)); \ + \ + if (OK_BUFFER) \ + { \ + if (flags & PHP_FORMAT_NEGATIVE) \ + *buffer++ = '-'; \ + else if (flags & PHP_FORMAT_POSITIVE) \ + *buffer++ = '+'; \ + \ + if (flags & PHP_FORMAT_PAD) \ + { \ + wmemset(buffer, '0', padLength); \ + buffer += padLength; \ + } \ + } \ + \ + usedLength += preLength * sizeof(WCHAR); \ + \ + if (OK_BUFFER) \ + { \ + PhZeroExtendToUtf16Buffer((PSTR)temp, length, buffer); \ + ADVANCE_BUFFER(length * sizeof(WCHAR)); \ + } \ + } \ + } while (0) + + case DoubleFormatType: + flags = 0; + COMMON_DOUBLE_FORMAT(format); + break; + + // Additional types + + case SizeFormatType: + { + ULONG i = 0; + ULONG maxSizeUnit; + DOUBLE s; + PH_FORMAT doubleFormat; + + s = (DOUBLE)format->u.Size; + + if (format->u.Size == 0) + { + ENSURE_BUFFER(sizeof(WCHAR)); + if (OK_BUFFER) + *buffer = '0'; + ADVANCE_BUFFER(sizeof(WCHAR)); + goto ContinueLoop; + } + + if (format->Type & FormatUseRadix) + maxSizeUnit = format->Radix; + else + maxSizeUnit = PhMaxSizeUnit; + + while ( + s >= 1000 && + i < sizeof(PhpSizeUnitNamesCounted) / sizeof(PH_STRINGREF) && + i < maxSizeUnit + ) + { + s /= 1024; + i++; + } + + // Format the number, then append the unit name. + + doubleFormat.Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros | FormatGroupDigits; + doubleFormat.Precision = (format->Type & FormatUsePrecision) ? format->Precision : 2; + doubleFormat.Width = 0; // stupid compiler + doubleFormat.u.Double = s; + flags = 0; + COMMON_DOUBLE_FORMAT(&doubleFormat); + + ENSURE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length); + if (OK_BUFFER) + { + *buffer = ' '; + memcpy(buffer + 1, PhpSizeUnitNamesCounted[i].Buffer, PhpSizeUnitNamesCounted[i].Length); + } + ADVANCE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length); + } + break; + } + +ContinueLoop: + partLength = usedLength - partLength; + + if (format->Type & (FormatLeftAlign | FormatRightAlign)) + { + SIZE_T newLength; + SIZE_T addLength; + + newLength = format->Width * sizeof(WCHAR); + + // We only pad and never truncate. + if (partLength < newLength) + { + addLength = newLength - partLength; + ENSURE_BUFFER(addLength); + + if (OK_BUFFER) + { + WCHAR pad; + + if (format->Type & FormatUsePad) + pad = format->Pad; + else + pad = ' '; + + if (format->Type & FormatLeftAlign) + { + // Left alignment is easy; we just fill the remaining space with the pad + // character. + wmemset(buffer, pad, addLength / sizeof(WCHAR)); + } + else + { + PWSTR start; + + // Right alignment is much slower and involves moving the text forward, then + // filling in the space before it. + start = buffer - partLength / sizeof(WCHAR); + memmove(start + addLength / sizeof(WCHAR), start, partLength); + wmemset(start, pad, addLength / sizeof(WCHAR)); + } + } + + ADVANCE_BUFFER(addLength); + } + } + } +} diff --git a/phlib/global.c b/phlib/global.c new file mode 100644 index 0000000..64c0341 --- /dev/null +++ b/phlib/global.c @@ -0,0 +1,238 @@ +/* + * Process Hacker - + * global variables and initialization functions + * + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +BOOLEAN PhInitializeSystem( + _In_ ULONG Flags + ); + +VOID PhInitializeSystemInformation( + VOID + ); + +VOID PhInitializeWindowsVersion( + VOID + ); + +PHLIBAPI PVOID PhLibImageBase; + +PHLIBAPI PWSTR PhApplicationName = L"Application"; +PHLIBAPI ULONG PhGlobalDpi = 96; +PHLIBAPI PVOID PhHeapHandle; +PHLIBAPI RTL_OSVERSIONINFOEXW PhOsVersion; +PHLIBAPI SYSTEM_BASIC_INFORMATION PhSystemBasicInformation; +PHLIBAPI ULONG WindowsVersion; + +PHLIBAPI ACCESS_MASK ProcessQueryAccess; +PHLIBAPI ACCESS_MASK ProcessAllAccess; +PHLIBAPI ACCESS_MASK ThreadQueryAccess; +PHLIBAPI ACCESS_MASK ThreadSetAccess; +PHLIBAPI ACCESS_MASK ThreadAllAccess; + +// Internal data +#ifdef DEBUG +PHLIB_STATISTICS_BLOCK PhLibStatisticsBlock; +#endif + +NTSTATUS PhInitializePhLib( + VOID + ) +{ + return PhInitializePhLibEx( + 0xffffffff, // all possible features + 0, + 0 + ); +} + +NTSTATUS PhInitializePhLibEx( + _In_ ULONG Flags, + _In_opt_ SIZE_T HeapReserveSize, + _In_opt_ SIZE_T HeapCommitSize + ) +{ + PhHeapHandle = RtlCreateHeap( + HEAP_GROWABLE | HEAP_CLASS_1, + NULL, + HeapReserveSize ? HeapReserveSize : 2 * 1024 * 1024, // 2 MB + HeapCommitSize ? HeapCommitSize : 1024 * 1024, // 1 MB + NULL, + NULL + ); + + if (!PhHeapHandle) + return STATUS_INSUFFICIENT_RESOURCES; + + PhLibImageBase = NtCurrentPeb()->ImageBaseAddress; + + PhInitializeWindowsVersion(); + PhInitializeSystemInformation(); + + if (!PhQueuedLockInitialization()) + return STATUS_UNSUCCESSFUL; + + if (!NT_SUCCESS(PhRefInitialization())) + return STATUS_UNSUCCESSFUL; + if (!PhBaseInitialization()) + return STATUS_UNSUCCESSFUL; + + if (!PhInitializeSystem(Flags)) + return STATUS_UNSUCCESSFUL; + + return STATUS_SUCCESS; +} + +#ifndef _WIN64 +BOOLEAN PhIsExecutingInWow64( + VOID + ) +{ + static BOOLEAN valid = FALSE; + static BOOLEAN isWow64; + + if (!valid) + { + PhGetProcessIsWow64(NtCurrentProcess(), &isWow64); + MemoryBarrier(); + valid = TRUE; + } + + return isWow64; +} +#endif + +static BOOLEAN PhInitializeSystem( + _In_ ULONG Flags + ) +{ + if (Flags & PHLIB_INIT_MODULE_FILE_STREAM) + { + if (!PhFileStreamInitialization()) + return FALSE; + } + + if (Flags & PHLIB_INIT_MODULE_SYMBOL_PROVIDER) + { + if (!PhSymbolProviderInitialization()) + return FALSE; + } + + return TRUE; +} + +static VOID PhInitializeSystemInformation( + VOID + ) +{ + NtQuerySystemInformation( + SystemBasicInformation, + &PhSystemBasicInformation, + sizeof(SYSTEM_BASIC_INFORMATION), + NULL + ); +} + +static VOID PhInitializeWindowsVersion( + VOID + ) +{ + RTL_OSVERSIONINFOEXW versionInfo; + ULONG majorVersion; + ULONG minorVersion; + + versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + + if (!NT_SUCCESS(RtlGetVersion((PRTL_OSVERSIONINFOW)&versionInfo))) + { + WindowsVersion = WINDOWS_NEW; + return; + } + + memcpy(&PhOsVersion, &versionInfo, sizeof(RTL_OSVERSIONINFOEXW)); + majorVersion = versionInfo.dwMajorVersion; + minorVersion = versionInfo.dwMinorVersion; + + if (majorVersion == 5 && minorVersion < 1 || majorVersion < 5) + { + WindowsVersion = WINDOWS_ANCIENT; + } + /* Windows XP */ + else if (majorVersion == 5 && minorVersion == 1) + { + WindowsVersion = WINDOWS_XP; + } + /* Windows Server 2003 */ + else if (majorVersion == 5 && minorVersion == 2) + { + WindowsVersion = WINDOWS_SERVER_2003; + } + /* Windows Vista, Windows Server 2008 */ + else if (majorVersion == 6 && minorVersion == 0) + { + WindowsVersion = WINDOWS_VISTA; + } + /* Windows 7, Windows Server 2008 R2 */ + else if (majorVersion == 6 && minorVersion == 1) + { + WindowsVersion = WINDOWS_7; + } + /* Windows 8 */ + else if (majorVersion == 6 && minorVersion == 2) + { + WindowsVersion = WINDOWS_8; + } + /* Windows 8.1 */ + else if (majorVersion == 6 && minorVersion == 3) + { + WindowsVersion = WINDOWS_8_1; + } + /* Windows 10 */ + else if (majorVersion == 10 && minorVersion == 0) + { + WindowsVersion = WINDOWS_10; + } + else if (majorVersion == 10 && minorVersion > 0 || majorVersion > 10) + { + WindowsVersion = WINDOWS_NEW; + } + + if (WINDOWS_HAS_LIMITED_ACCESS) + { + ProcessQueryAccess = PROCESS_QUERY_LIMITED_INFORMATION; + ProcessAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1fff; + ThreadQueryAccess = THREAD_QUERY_LIMITED_INFORMATION; + ThreadSetAccess = THREAD_SET_LIMITED_INFORMATION; + ThreadAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff; + } + else + { + ProcessQueryAccess = PROCESS_QUERY_INFORMATION; + ProcessAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff; + ThreadQueryAccess = THREAD_QUERY_INFORMATION; + ThreadSetAccess = THREAD_SET_INFORMATION; + ThreadAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3ff; + } +} diff --git a/phlib/graph.c b/phlib/graph.c new file mode 100644 index 0000000..43fa7b3 --- /dev/null +++ b/phlib/graph.c @@ -0,0 +1,1332 @@ +/* + * Process Hacker - + * graph control + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define COLORREF_TO_BITS(Color) (_byteswap_ulong(Color) >> 8) + +typedef struct _PHP_GRAPH_CONTEXT +{ + HWND Handle; + ULONG Style; + ULONG_PTR Id; + PH_GRAPH_DRAW_INFO DrawInfo; + PH_GRAPH_OPTIONS Options; + BOOLEAN NeedsUpdate; + BOOLEAN NeedsDraw; + + HDC BufferedContext; + HBITMAP BufferedOldBitmap; + HBITMAP BufferedBitmap; + PVOID BufferedBits; + RECT BufferedContextRect; + + HDC FadeOutContext; + HBITMAP FadeOutOldBitmap; + HBITMAP FadeOutBitmap; + PVOID FadeOutBits; + RECT FadeOutContextRect; + + HWND TooltipHandle; + WNDPROC TooltipOldWndProc; + POINT LastCursorLocation; +} PHP_GRAPH_CONTEXT, *PPHP_GRAPH_CONTEXT; + +LRESULT CALLBACK PhpGraphWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +RECT PhNormalGraphTextMargin = { 5, 5, 5, 5 }; +RECT PhNormalGraphTextPadding = { 3, 3, 3, 3 }; + +BOOLEAN PhGraphControlInitialization( + VOID + ) +{ + WNDCLASSEX c = { sizeof(c) }; + + c.style = CS_GLOBALCLASS | CS_DBLCLKS; + c.lpfnWndProc = PhpGraphWndProc; + c.cbClsExtra = 0; + c.cbWndExtra = sizeof(PVOID); + c.hInstance = PhLibImageBase; + c.hIcon = NULL; + c.hCursor = LoadCursor(NULL, IDC_ARROW); + c.hbrBackground = NULL; + c.lpszMenuName = NULL; + c.lpszClassName = PH_GRAPH_CLASSNAME; + c.hIconSm = NULL; + + if (!RegisterClassEx(&c)) + return FALSE; + + return TRUE; +} + +FORCEINLINE VOID PhpGetGraphPoint( + _In_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ ULONG Index, + _Out_ PULONG H1, + _Out_ PULONG H2 + ) +{ + if (Index < DrawInfo->LineDataCount) + { + FLOAT f1; + FLOAT f2; + + f1 = DrawInfo->LineData1[Index]; + + if (f1 < 0) + f1 = 0; + if (f1 > 1) + f1 = 1; + + *H1 = (ULONG)(f1 * (DrawInfo->Height - 1)); + + if (DrawInfo->Flags & PH_GRAPH_USE_LINE_2) + { + f2 = f1 + DrawInfo->LineData2[Index]; + + if (f2 < 0) + f2 = 0; + if (f2 > 1) + f2 = 1; + + *H2 = (ULONG)(f2 * (DrawInfo->Height - 1)); + } + else + { + *H2 = *H1; + } + } + else + { + *H1 = 0; + *H2 = 0; + } +} + +/** + * Draws a graph directly to memory. + * + * \param hdc The DC to draw to. This is only used when drawing text. + * \param Bits The bits in a bitmap. + * \param DrawInfo A structure which contains graphing information. + * + * \remarks The following information is fixed: + * \li The graph is fixed to the origin (0, 0). + * \li The total size of the bitmap is assumed to be \a Width and \a Height in \a DrawInfo. + * \li \a Step is fixed at 2. + * \li If \ref PH_GRAPH_USE_LINE_2 is specified in \a Flags, \ref PH_GRAPH_OVERLAY_LINE_2 is never + * used. + */ +VOID PhDrawGraphDirect( + _In_ HDC hdc, + _In_ PVOID Bits, + _In_ PPH_GRAPH_DRAW_INFO DrawInfo + ) +{ + PULONG bits = Bits; + LONG width = DrawInfo->Width; + LONG height = DrawInfo->Height; + LONG numberOfPixels = width * height; + ULONG flags = DrawInfo->Flags; + LONG i; + LONG x; + + BOOLEAN intermediate = FALSE; // whether we are currently between two data positions + ULONG dataIndex = 0; // the data index of the current position + ULONG h1_i; // the line 1 height value to the left of the current position + ULONG h1_o; // the line 1 height value at the current position + ULONG h2_i; // the line 1 + line 2 height value to the left of the current position + ULONG h2_o; // the line 1 + line 2 height value at the current position + ULONG h1; // current pixel + ULONG h1_left; // current pixel + ULONG h2; // current pixel + ULONG h2_left; // current pixel + + LONG mid; + LONG h1_low1; + LONG h1_high1; + LONG h1_low2; + LONG h1_high2; + LONG h2_low1; + LONG h2_high1; + LONG h2_low2; + LONG h2_high2; + LONG old_low2; + LONG old_high2; + + ULONG lineColor1; + ULONG lineBackColor1; + ULONG lineColor2; + ULONG lineBackColor2; + FLOAT gridHeight; + LONG gridYThreshold; + ULONG gridYCounter; + ULONG gridColor; + FLOAT gridBase; + FLOAT gridLevel; + + ULONG yLabelMax; + ULONG yLabelDataIndex; + + lineColor1 = COLORREF_TO_BITS(DrawInfo->LineColor1); + lineBackColor1 = COLORREF_TO_BITS(DrawInfo->LineBackColor1); + lineColor2 = COLORREF_TO_BITS(DrawInfo->LineColor2); + lineBackColor2 = COLORREF_TO_BITS(DrawInfo->LineBackColor2); + + if (DrawInfo->BackColor == 0) + { + memset(bits, 0, numberOfPixels * 4); + } + else + { + PhFillMemoryUlong(bits, COLORREF_TO_BITS(DrawInfo->BackColor), numberOfPixels); + } + + x = width - 1; + h1_low2 = MAXLONG; + h1_high2 = 0; + h2_low2 = MAXLONG; + h2_high2 = 0; + + PhpGetGraphPoint(DrawInfo, 0, &h1_i, &h2_i); + + if (flags & (PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y)) + { + gridHeight = max(DrawInfo->GridHeight, 0); + gridLevel = gridHeight; + gridYThreshold = DrawInfo->GridYThreshold; + gridYCounter = DrawInfo->GridWidth - (DrawInfo->GridXOffset * DrawInfo->Step) % DrawInfo->GridWidth - 1; + gridColor = COLORREF_TO_BITS(DrawInfo->GridColor); + } + + if ((flags & (PH_GRAPH_USE_GRID_Y | PH_GRAPH_LOGARITHMIC_GRID_Y)) == (PH_GRAPH_USE_GRID_Y | PH_GRAPH_LOGARITHMIC_GRID_Y)) + { + // Pre-process to find the largest integer n such that GridHeight*GridBase^n < 1. + + gridBase = DrawInfo->GridBase; + + if (gridBase > 1) + { + DOUBLE logBase; + DOUBLE exponent; + DOUBLE high; + + logBase = log(gridBase); + exponent = ceil(-log(gridHeight) / logBase) - 1; // Works for both GridHeight > 1 and GridHeight < 1 + high = exp(exponent * logBase); + gridLevel = (FLOAT)(gridHeight * high); + + if (gridLevel < 0 || !isfinite(gridLevel)) + gridLevel = 0; + if (gridLevel > 1) + gridLevel = 1; + } + else + { + // This is an error. + gridLevel = 0; + } + } + + if (flags & PH_GRAPH_LABEL_MAX_Y) + { + yLabelMax = h2_i; + yLabelDataIndex = 0; + } + + while (x >= 0) + { + // Calculate the height of the graph at this point. + + if (!intermediate) + { + h1_o = h1_i; + h2_o = h2_i; + + // Pull in new data. + dataIndex++; + PhpGetGraphPoint(DrawInfo, dataIndex, &h1_i, &h2_i); + + h1 = h1_o; + h1_left = (h1_i + h1_o) / 2; + h2 = h2_o; + h2_left = (h2_i + h2_o) / 2; + + if ((flags & PH_GRAPH_LABEL_MAX_Y) && dataIndex < DrawInfo->LabelMaxYIndexLimit) + { + if (yLabelMax <= h2_i) + { + yLabelMax = h2_i; + yLabelDataIndex = dataIndex; + } + } + } + else + { + h1 = h1_left; + h1_left = h1_i; + h2 = h2_left; + h2_left = h2_i; + } + + // The graph is drawn right-to-left. There is one iteration of the loop per horizontal pixel. + // There is a fixed step value of 2, so every other iteration is a mid-point (intermediate) + // iteration with a height value of (left + right) / 2. In order to rasterize the outline, + // effectively in each iteration half of the line is drawn at the current column and the other + // half is drawn in the column to the left. + + // Rasterize the data outline. + // h?_low2 to h?_high2 is the vertical line to the left of the current pixel. + // h?_low1 to h?_high1 is the vertical line at the current pixel. + // We merge (union) the old h?_low2 to h?_high2 line with the current line in each iteration. + // + // For example: + // + // X represents a data point. M represents the mid-point between two data points ("intermediate"). + // X, M and x are all part of the outline. # represents the background filled in during + // the current loop iteration. + // + // slope > 0: slope < 0: + // + // X < high1 X < high2 (of next loop iteration) + // x x + // x < low1 x < low2 (of next loop iteration) + // x# < high2 x < high1 (of next loop iteration) + // M# < low2 M < low1 (of next loop iteration) + // x# < high1 (of next loop iteration) x < high2 + // x# < low1 (of next loop iteration) x < low2 + // x # < high2 (of next loop iteration) x < high1 + // x # x + // X # < low2 (of next loop iteration) X < low1 + // # # + // ^ ^ + // ^| current pixel ^| current pixel + // | | + // | left of current pixel | left of current pixel + // + // In both examples above, the line low2-high2 will be merged with the line low1-high1 of + // the next iteration. + + mid = ((h1_left + h1) / 2) * width; + old_low2 = h1_low2; + old_high2 = h1_high2; + + if (h1_left < h1) // slope > 0 + { + h1_low2 = h1_left * width; + h1_high2 = mid; + h1_low1 = mid + width; + h1_high1 = h1 * width; + } + else // slope < 0 + { + h1_high2 = h1_left * width; + h1_low2 = mid + width; + h1_high1 = mid; + h1_low1 = h1 * width; + } + + // Merge the lines. + if (h1_low1 > old_low2) + h1_low1 = old_low2; + if (h1_high1 < old_high2) + h1_high1 = old_high2; + + // Fix up values for the current horizontal offset. + h1_low1 += x; + h1_high1 += x; + + if (flags & PH_GRAPH_USE_LINE_2) + { + mid = ((h2_left + h2) / 2) * width; + old_low2 = h2_low2; + old_high2 = h2_high2; + + if (h2_left < h2) // slope > 0 + { + h2_low2 = h2_left * width; + h2_high2 = mid; + h2_low1 = mid + width; + h2_high1 = h2 * width; + } + else // slope < 0 + { + h2_high2 = h2_left * width; + h2_low2 = mid + width; + h2_high1 = mid; + h2_low1 = h2 * width; + } + + // Merge the lines. + if (h2_low1 > old_low2) + h2_low1 = old_low2; + if (h2_high1 < old_high2) + h2_high1 = old_high2; + + // Fix up values for the current horizontal offset. + h2_low1 += x; + h2_high1 += x; + } + + // Fill in the background. + + if (flags & PH_GRAPH_USE_LINE_2) + { + for (i = h1_high1 + width; i < h2_low1; i += width) + { + bits[i] = lineBackColor2; + } + } + + for (i = x; i < h1_low1; i += width) + { + bits[i] = lineBackColor1; + } + + // Draw the grid. + + if (flags & PH_GRAPH_USE_GRID_X) + { + // Draw the vertical grid line. + if (gridYCounter == 0) + { + for (i = x; i < numberOfPixels; i += width) + { + bits[i] = gridColor; + } + } + + gridYCounter++; + + if (gridYCounter == DrawInfo->GridWidth) + gridYCounter = 0; + } + + if (flags & PH_GRAPH_USE_GRID_Y) + { + FLOAT level; + LONG h; + LONG h_last; + + // Draw the horizontal grid line. + if (flags & PH_GRAPH_LOGARITHMIC_GRID_Y) + { + level = gridLevel; + h = (LONG)(level * (height - 1)); + h_last = height + gridYThreshold - 1; + + while (TRUE) + { + if (h <= h_last - gridYThreshold) + { + bits[x + h * width] = gridColor; + h_last = h; + } + else + { + break; + } + + level /= gridBase; + h = (LONG)(level * (height - 1)); + } + } + else + { + level = gridHeight; + h = (LONG)(level * (height - 1)); + h_last = 0; + + while (h < height - 1) + { + if (h >= h_last + gridYThreshold) + { + bits[x + h * width] = gridColor; + h_last = h; + } + + level += gridHeight; + h = (LONG)(level * (height - 1)); + } + } + } + + // Draw the outline (line 1 is allowed to paint over line 2). + + if (flags & PH_GRAPH_USE_LINE_2) + { + for (i = h2_low1; i <= h2_high1; i += width) // exclude pixel in the middle + { + bits[i] = lineColor2; + } + } + + for (i = h1_low1; i <= h1_high1; i += width) + { + bits[i] = lineColor1; + } + + intermediate = !intermediate; + x--; + } + + if ((flags & PH_GRAPH_LABEL_MAX_Y) && yLabelDataIndex < DrawInfo->LineDataCount) + { + FLOAT value; + PPH_STRING label; + + value = DrawInfo->LineData1[yLabelDataIndex]; + + if (flags & PH_GRAPH_USE_LINE_2) + value += DrawInfo->LineData2[yLabelDataIndex]; + + if (label = DrawInfo->LabelYFunction(DrawInfo, yLabelDataIndex, value, DrawInfo->LabelYFunctionParameter)) + { + HFONT oldFont = NULL; + SIZE textSize; + RECT rect; + + if (DrawInfo->LabelYFont) + oldFont = SelectObject(hdc, DrawInfo->LabelYFont); + + SetTextColor(hdc, DrawInfo->LabelYColor); + SetBkMode(hdc, TRANSPARENT); + + GetTextExtentPoint32(hdc, label->Buffer, (ULONG)label->Length / 2, &textSize); + + rect.bottom = height - yLabelMax - PhNormalGraphTextPadding.top; + rect.top = rect.bottom - textSize.cy; + + if (rect.top < PhNormalGraphTextPadding.top) + { + rect.top = PhNormalGraphTextPadding.top; + rect.bottom = rect.top + textSize.cy; + } + + rect.left = 0; + rect.right = width - min((LONG)yLabelDataIndex * 2, width) - PhNormalGraphTextPadding.right; + DrawText(hdc, label->Buffer, (ULONG)label->Length / 2, &rect, DT_NOCLIP | DT_RIGHT); + + if (oldFont) + SelectObject(hdc, oldFont); + + PhDereferenceObject(label); + } + } + + if (DrawInfo->Text.Buffer) + { + HFONT oldFont = NULL; + + if (DrawInfo->TextFont) + oldFont = SelectObject(hdc, DrawInfo->TextFont); + + // Fill in the text box. + SetDCBrushColor(hdc, DrawInfo->TextBoxColor); + FillRect(hdc, &DrawInfo->TextBoxRect, GetStockObject(DC_BRUSH)); + + // Draw the text. + SetTextColor(hdc, DrawInfo->TextColor); + SetBkMode(hdc, TRANSPARENT); + DrawText(hdc, DrawInfo->Text.Buffer, (ULONG)DrawInfo->Text.Length / 2, &DrawInfo->TextRect, DT_NOCLIP); + + if (oldFont) + SelectObject(hdc, oldFont); + } +} + +/** + * Sets the text in a graphing information structure. + * + * \param hdc The DC to perform calculations from. + * \param DrawInfo A structure which contains graphing information. The structure is modified to + * contain the new text information. + * \param Text The text. + * \param Margin The margins of the text box from the edges of the graph. + * \param Padding The padding within the text box. + * \param Align The alignment of the text box. + */ +VOID PhSetGraphText( + _In_ HDC hdc, + _Inout_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ PPH_STRINGREF Text, + _In_ PRECT Margin, + _In_ PRECT Padding, + _In_ ULONG Align + ) +{ + HFONT oldFont = NULL; + SIZE textSize; + PH_RECTANGLE boxRectangle; + PH_RECTANGLE textRectangle; + + if (DrawInfo->TextFont) + oldFont = SelectObject(hdc, DrawInfo->TextFont); + + DrawInfo->Text = *Text; + GetTextExtentPoint32(hdc, Text->Buffer, (ULONG)Text->Length / 2, &textSize); + + if (oldFont) + SelectObject(hdc, oldFont); + + // Calculate the box rectangle. + + boxRectangle.Width = textSize.cx + Padding->left + Padding->right; + boxRectangle.Height = textSize.cy + Padding->top + Padding->bottom; + + if (Align & PH_ALIGN_LEFT) + boxRectangle.Left = Margin->left; + else if (Align & PH_ALIGN_RIGHT) + boxRectangle.Left = DrawInfo->Width - boxRectangle.Width - Margin->right; + else + boxRectangle.Left = (DrawInfo->Width - boxRectangle.Width) / 2; + + if (Align & PH_ALIGN_TOP) + boxRectangle.Top = Margin->top; + else if (Align & PH_ALIGN_BOTTOM) + boxRectangle.Top = DrawInfo->Height - boxRectangle.Height - Margin->bottom; + else + boxRectangle.Top = (DrawInfo->Height - boxRectangle.Height) / 2; + + // Calculate the text rectangle. + + textRectangle.Left = boxRectangle.Left + Padding->left; + textRectangle.Top = boxRectangle.Top + Padding->top; + textRectangle.Width = textSize.cx; + textRectangle.Height = textSize.cy; + + // Save the rectangles. + DrawInfo->TextRect = PhRectangleToRect(textRectangle); + DrawInfo->TextBoxRect = PhRectangleToRect(boxRectangle); +} + +VOID PhpCreateGraphContext( + _Out_ PPHP_GRAPH_CONTEXT *Context + ) +{ + PPHP_GRAPH_CONTEXT context; + + context = PhAllocate(sizeof(PHP_GRAPH_CONTEXT)); + memset(context, 0, sizeof(PHP_GRAPH_CONTEXT)); + + context->DrawInfo.Width = 3; + context->DrawInfo.Height = 3; + context->DrawInfo.Flags = PH_GRAPH_USE_GRID_X; + context->DrawInfo.Step = 2; + context->DrawInfo.BackColor = RGB(0xef, 0xef, 0xef); + context->DrawInfo.LineDataCount = 0; + context->DrawInfo.LineData1 = NULL; + context->DrawInfo.LineData2 = NULL; + context->DrawInfo.LineColor1 = RGB(0x00, 0xff, 0x00); + context->DrawInfo.LineColor2 = RGB(0xff, 0x00, 0x00); + context->DrawInfo.LineBackColor1 = RGB(0x00, 0x77, 0x00); + context->DrawInfo.LineBackColor2 = RGB(0x77, 0x00, 0x00); + context->DrawInfo.GridColor = RGB(0xc7, 0xc7, 0xc7); + context->DrawInfo.GridWidth = 20; + context->DrawInfo.GridHeight = 0.25f; + context->DrawInfo.GridXOffset = 0; + context->DrawInfo.GridYThreshold = 10; + context->DrawInfo.GridBase = 2.0f; + context->DrawInfo.LabelYColor = RGB(0x77, 0x77, 0x77); + context->DrawInfo.LabelMaxYIndexLimit = -1; + context->DrawInfo.TextColor = RGB(0x00, 0xff, 0x00); + context->DrawInfo.TextBoxColor = RGB(0x00, 0x22, 0x00); + + context->Options.FadeOutBackColor = RGB(0xef, 0xef, 0xef); + context->Options.FadeOutWidth = 100; + + *Context = context; +} + +VOID PhpFreeGraphContext( + _Inout_ _Post_invalid_ PPHP_GRAPH_CONTEXT Context + ) +{ + PhFree(Context); +} + +static PWSTR PhpMakeGraphTooltipContextAtom( + VOID + ) +{ + PH_DEFINE_MAKE_ATOM(L"PhLib_GraphTooltipContext"); +} + +static VOID PhpDeleteBufferedContext( + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + if (Context->BufferedContext) + { + // The original bitmap must be selected back into the context, otherwise the bitmap can't be + // deleted. + SelectObject(Context->BufferedContext, Context->BufferedOldBitmap); + DeleteObject(Context->BufferedBitmap); + DeleteDC(Context->BufferedContext); + + Context->BufferedContext = NULL; + Context->BufferedBitmap = NULL; + Context->BufferedBits = NULL; + } +} + +static VOID PhpCreateBufferedContext( + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + HDC hdc; + BITMAPINFOHEADER header; + + PhpDeleteBufferedContext(Context); + + GetClientRect(Context->Handle, &Context->BufferedContextRect); + + hdc = GetDC(Context->Handle); + Context->BufferedContext = CreateCompatibleDC(hdc); + + memset(&header, 0, sizeof(BITMAPINFOHEADER)); + header.biSize = sizeof(BITMAPINFOHEADER); + header.biWidth = Context->BufferedContextRect.right; + header.biHeight = Context->BufferedContextRect.bottom; + header.biPlanes = 1; + header.biBitCount = 32; + + Context->BufferedBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &Context->BufferedBits, NULL, 0); + + ReleaseDC(Context->Handle, hdc); + Context->BufferedOldBitmap = SelectObject(Context->BufferedContext, Context->BufferedBitmap); +} + +static VOID PhpDeleteFadeOutContext( + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + if (Context->FadeOutContext) + { + SelectObject(Context->FadeOutContext, Context->FadeOutOldBitmap); + DeleteObject(Context->FadeOutBitmap); + DeleteDC(Context->FadeOutContext); + + Context->FadeOutContext = NULL; + Context->FadeOutBitmap = NULL; + Context->FadeOutBits = NULL; + } +} + +static VOID PhpCreateFadeOutContext( + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + HDC hdc; + BITMAPINFOHEADER header; + ULONG i; + ULONG j; + ULONG height; + COLORREF backColor; + ULONG fadeOutWidth; + FLOAT fadeOutWidthSquared; + ULONG currentAlpha; + ULONG currentColor; + + PhpDeleteFadeOutContext(Context); + + GetClientRect(Context->Handle, &Context->FadeOutContextRect); + Context->FadeOutContextRect.right = Context->Options.FadeOutWidth; + + hdc = GetDC(Context->Handle); + Context->FadeOutContext = CreateCompatibleDC(hdc); + + memset(&header, 0, sizeof(BITMAPINFOHEADER)); + header.biSize = sizeof(BITMAPINFOHEADER); + header.biWidth = Context->FadeOutContextRect.right; + header.biHeight = Context->FadeOutContextRect.bottom; + header.biPlanes = 1; + header.biBitCount = 32; + + Context->FadeOutBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &Context->FadeOutBits, NULL, 0); + + ReleaseDC(Context->Handle, hdc); + Context->FadeOutOldBitmap = SelectObject(Context->FadeOutContext, Context->FadeOutBitmap); + + if (!Context->FadeOutBits) + return; + + height = Context->FadeOutContextRect.bottom; + backColor = Context->Options.FadeOutBackColor; + fadeOutWidth = Context->Options.FadeOutWidth; + fadeOutWidthSquared = (FLOAT)fadeOutWidth * fadeOutWidth; + + for (i = 0; i < fadeOutWidth; i++) + { + currentAlpha = 255 - (ULONG)((FLOAT)(i * i) / fadeOutWidthSquared * 255); + currentColor = + ((backColor & 0xff) * currentAlpha / 255) + + ((((backColor >> 8) & 0xff) * currentAlpha / 255) << 8) + + ((((backColor >> 16) & 0xff) * currentAlpha / 255) << 16) + + (currentAlpha << 24); + + for (j = i; j < height * fadeOutWidth; j += fadeOutWidth) + { + ((PULONG)Context->FadeOutBits)[j] = currentColor; + } + } +} + +VOID PhpUpdateDrawInfo( + _In_ HWND hwnd, + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + PH_GRAPH_GETDRAWINFO getDrawInfo; + + Context->DrawInfo.Width = Context->BufferedContextRect.right; + Context->DrawInfo.Height = Context->BufferedContextRect.bottom; + + getDrawInfo.Header.hwndFrom = hwnd; + getDrawInfo.Header.idFrom = Context->Id; + getDrawInfo.Header.code = GCN_GETDRAWINFO; + getDrawInfo.DrawInfo = &Context->DrawInfo; + + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&getDrawInfo); +} + +VOID PhpDrawGraphControl( + _In_ HWND hwnd, + _In_ PPHP_GRAPH_CONTEXT Context + ) +{ + if (Context->BufferedBits) + PhDrawGraphDirect(Context->BufferedContext, Context->BufferedBits, &Context->DrawInfo); + + if (Context->Style & GC_STYLE_FADEOUT) + { + BLENDFUNCTION blendFunction; + + if (!Context->FadeOutContext) + PhpCreateFadeOutContext(Context); + + blendFunction.BlendOp = AC_SRC_OVER; + blendFunction.BlendFlags = 0; + blendFunction.SourceConstantAlpha = 255; + blendFunction.AlphaFormat = AC_SRC_ALPHA; + GdiAlphaBlend( + Context->BufferedContext, + 0, + 0, + Context->Options.FadeOutWidth, + Context->FadeOutContextRect.bottom, + Context->FadeOutContext, + 0, + 0, + Context->Options.FadeOutWidth, + Context->FadeOutContextRect.bottom, + blendFunction + ); + } + + if (Context->Style & GC_STYLE_DRAW_PANEL) + { + PH_GRAPH_DRAWPANEL drawPanel; + + drawPanel.Header.hwndFrom = hwnd; + drawPanel.Header.idFrom = Context->Id; + drawPanel.Header.code = GCN_DRAWPANEL; + drawPanel.hdc = Context->BufferedContext; + drawPanel.Rect = Context->BufferedContextRect; + + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&drawPanel); + } +} + +LRESULT CALLBACK PhpGraphWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPHP_GRAPH_CONTEXT context; + + context = (PPHP_GRAPH_CONTEXT)GetWindowLongPtr(hwnd, 0); + + if (uMsg == WM_CREATE) + { + PhpCreateGraphContext(&context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)context); + } + + if (!context) + return DefWindowProc(hwnd, uMsg, wParam, lParam); + + switch (uMsg) + { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + { + if (context->TooltipHandle) + { + MSG message; + + message.hwnd = hwnd; + message.message = uMsg; + message.wParam = wParam; + message.lParam = lParam; + SendMessage(context->TooltipHandle, TTM_RELAYEVENT, 0, (LPARAM)&message); + } + } + break; + } + + switch (uMsg) + { + case WM_CREATE: + { + CREATESTRUCT *createStruct = (CREATESTRUCT *)lParam; + + context->Handle = hwnd; + context->Style = createStruct->style; + context->Id = (ULONG_PTR)createStruct->hMenu; + } + break; + case WM_DESTROY: + { + if (context->TooltipHandle) + DestroyWindow(context->TooltipHandle); + + PhpDeleteFadeOutContext(context); + PhpDeleteBufferedContext(context); + PhpFreeGraphContext(context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL); + } + break; + case WM_STYLECHANGED: + { + STYLESTRUCT *styleStruct = (STYLESTRUCT *)lParam; + + if (wParam == GWL_STYLE) + { + context->Style = styleStruct->styleNew; + context->NeedsDraw = TRUE; + } + } + break; + case WM_SIZE: + { + // Force a re-create of the buffered context. + PhpCreateBufferedContext(context); + PhpDeleteFadeOutContext(context); + + PhpUpdateDrawInfo(hwnd, context); + context->NeedsDraw = TRUE; + InvalidateRect(hwnd, NULL, FALSE); + + if (context->TooltipHandle) + { + TOOLINFO toolInfo; + + memset(&toolInfo, 0, sizeof(TOOLINFO)); + toolInfo.cbSize = sizeof(TOOLINFO); + toolInfo.hwnd = hwnd; + toolInfo.uId = 1; + GetClientRect(hwnd, &toolInfo.rect); + SendMessage(context->TooltipHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo); + } + } + break; + case WM_PAINT: + { + PAINTSTRUCT paintStruct; + HDC hdc; + + if (hdc = BeginPaint(hwnd, &paintStruct)) + { + if (!context->BufferedContext) + PhpCreateBufferedContext(context); + + if (context->NeedsUpdate) + { + PhpUpdateDrawInfo(hwnd, context); + context->NeedsUpdate = FALSE; + } + + if (context->NeedsDraw) + { + PhpDrawGraphControl(hwnd, context); + context->NeedsDraw = FALSE; + } + + BitBlt( + hdc, + paintStruct.rcPaint.left, + paintStruct.rcPaint.top, + paintStruct.rcPaint.right - paintStruct.rcPaint.left, + paintStruct.rcPaint.bottom - paintStruct.rcPaint.top, + context->BufferedContext, + paintStruct.rcPaint.left, + paintStruct.rcPaint.top, + SRCCOPY + ); + + EndPaint(hwnd, &paintStruct); + } + } + return 0; + case WM_ERASEBKGND: + return 1; + case WM_NCPAINT: + { + HRGN updateRegion; + + updateRegion = (HRGN)wParam; + + if (updateRegion == (HRGN)1) // HRGN_FULL + updateRegion = NULL; + + // Themed border + if (context->Style & WS_BORDER) + { + HDC hdc; + ULONG flags; + RECT rect; + + // Note the use of undocumented flags below. GetDCEx doesn't work without these. + + flags = DCX_WINDOW | DCX_LOCKWINDOWUPDATE | 0x10000; + + if (updateRegion) + flags |= DCX_INTERSECTRGN | 0x40000; + + if (hdc = GetDCEx(hwnd, updateRegion, flags)) + { + GetClientRect(hwnd, &rect); + rect.right += 2; + rect.bottom += 2; + SetDCBrushColor(hdc, RGB(0x8f, 0x8f, 0x8f)); + FrameRect(hdc, &rect, GetStockObject(DC_BRUSH)); + + ReleaseDC(hwnd, hdc); + return 0; + } + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + if (header->hwndFrom == context->TooltipHandle) + { + switch (header->code) + { + case TTN_GETDISPINFO: + { + LPNMTTDISPINFO dispInfo = (LPNMTTDISPINFO)header; + POINT point; + RECT clientRect; + PH_GRAPH_GETTOOLTIPTEXT getTooltipText; + + GetCursorPos(&point); + ScreenToClient(hwnd, &point); + GetClientRect(hwnd, &clientRect); + + getTooltipText.Header.hwndFrom = hwnd; + getTooltipText.Header.idFrom = context->Id; + getTooltipText.Header.code = GCN_GETTOOLTIPTEXT; + getTooltipText.Index = (clientRect.right - point.x - 1) / context->DrawInfo.Step; + getTooltipText.TotalCount = context->DrawInfo.LineDataCount; + getTooltipText.Text.Buffer = NULL; + getTooltipText.Text.Length = 0; + + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&getTooltipText); + + if (getTooltipText.Text.Buffer) + { + dispInfo->lpszText = getTooltipText.Text.Buffer; + } + } + break; + } + } + } + break; + case WM_SETCURSOR: + { + if (context->Options.DefaultCursor) + { + SetCursor(context->Options.DefaultCursor); + return TRUE; + } + } + break; + case WM_MOUSEMOVE: + { + if (context->TooltipHandle) + { + POINT point; + + GetCursorPos(&point); + ScreenToClient(hwnd, &point); + + if (context->LastCursorLocation.x != point.x || context->LastCursorLocation.y != point.y) + { + SendMessage(context->TooltipHandle, TTM_UPDATE, 0, 0); + context->LastCursorLocation = point; + } + } + } + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + { + PH_GRAPH_MOUSEEVENT mouseEvent; + RECT clientRect; + + GetClientRect(hwnd, &clientRect); + + mouseEvent.Header.hwndFrom = hwnd; + mouseEvent.Header.idFrom = context->Id; + mouseEvent.Header.code = GCN_MOUSEEVENT; + mouseEvent.Message = uMsg; + mouseEvent.Keys = (ULONG)wParam; + mouseEvent.Point.x = LOWORD(lParam); + mouseEvent.Point.y = HIWORD(lParam); + + mouseEvent.Index = (clientRect.right - mouseEvent.Point.x - 1) / context->DrawInfo.Step; + mouseEvent.TotalCount = context->DrawInfo.LineDataCount; + + SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&mouseEvent); + } + break; + case GCM_GETDRAWINFO: + { + PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)lParam; + + memcpy(drawInfo, &context->DrawInfo, sizeof(PH_GRAPH_DRAW_INFO)); + } + return TRUE; + case GCM_SETDRAWINFO: + { + PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)lParam; + ULONG width; + ULONG height; + + width = context->DrawInfo.Width; + height = context->DrawInfo.Height; + memcpy(&context->DrawInfo, drawInfo, sizeof(PH_GRAPH_DRAW_INFO)); + context->DrawInfo.Width = width; + context->DrawInfo.Height = height; + } + return TRUE; + case GCM_DRAW: + { + PhpUpdateDrawInfo(hwnd, context); + context->NeedsDraw = TRUE; + } + return TRUE; + case GCM_MOVEGRID: + { + LONG increment = (LONG)wParam; + + context->DrawInfo.GridXOffset += increment; + } + return TRUE; + case GCM_GETBUFFEREDCONTEXT: + return (LRESULT)context->BufferedContext; + case GCM_SETTOOLTIP: + { + if (wParam) + { + TOOLINFO toolInfo = { sizeof(toolInfo) }; + + context->TooltipHandle = CreateWindow( + TOOLTIPS_CLASS, + NULL, + WS_POPUP | WS_EX_TRANSPARENT | TTS_NOPREFIX, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + NULL, + NULL, + PhLibImageBase, + NULL + ); + + SetWindowPos(context->TooltipHandle, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + + toolInfo.uFlags = 0; + toolInfo.hwnd = hwnd; + toolInfo.uId = 1; + toolInfo.lpszText = LPSTR_TEXTCALLBACK; + GetClientRect(hwnd, &toolInfo.rect); + SendMessage(context->TooltipHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + + SendMessage(context->TooltipHandle, TTM_SETDELAYTIME, TTDT_INITIAL, 0); + SendMessage(context->TooltipHandle, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT); + // Allow newlines (-1 doesn't work) + SendMessage(context->TooltipHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT); + } + else + { + DestroyWindow(context->TooltipHandle); + context->TooltipHandle = NULL; + } + } + return TRUE; + case GCM_UPDATETOOLTIP: + { + if (!context->TooltipHandle) + return FALSE; + + SendMessage(context->TooltipHandle, TTM_UPDATE, 0, 0); + } + return TRUE; + case GCM_GETOPTIONS: + memcpy((PPH_GRAPH_OPTIONS)lParam, &context->Options, sizeof(PH_GRAPH_OPTIONS)); + return TRUE; + case GCM_SETOPTIONS: + memcpy(&context->Options, (PPH_GRAPH_OPTIONS)lParam, sizeof(PH_GRAPH_OPTIONS)); + PhpDeleteFadeOutContext(context); + return TRUE; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +/** + * Initializes a graph buffer management structure. + * + * \param Buffers The buffer management structure. + */ +VOID PhInitializeGraphBuffers( + _Out_ PPH_GRAPH_BUFFERS Buffers + ) +{ + Buffers->AllocatedCount = 0; + Buffers->Data1 = NULL; + Buffers->Data2 = NULL; + Buffers->Valid = FALSE; +} + +/** + * Frees resources used by a graph buffer management structure. + * + * \param Buffers The buffer management structure. + */ +VOID PhDeleteGraphBuffers( + _Inout_ PPH_GRAPH_BUFFERS Buffers + ) +{ + if (Buffers->Data1) PhFree(Buffers->Data1); + if (Buffers->Data2) PhFree(Buffers->Data2); +} + +/** + * Sets up a graphing information structure with information from a graph buffer management + * structure. + * + * \param Buffers The buffer management structure. + * \param DrawInfo The graphing information structure. + * \param DataCount The number of data points currently required. The buffers are resized if needed. + */ +VOID PhGetDrawInfoGraphBuffers( + _Inout_ PPH_GRAPH_BUFFERS Buffers, + _Inout_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ ULONG DataCount + ) +{ + DrawInfo->LineDataCount = min(DataCount, PH_GRAPH_DATA_COUNT(DrawInfo->Width, DrawInfo->Step)); + + // Do we need to allocate or re-allocate the data buffers? + if (Buffers->AllocatedCount < DrawInfo->LineDataCount) + { + if (Buffers->Data1) + PhFree(Buffers->Data1); + if ((DrawInfo->Flags & PH_GRAPH_USE_LINE_2) && Buffers->Data2) + PhFree(Buffers->Data2); + + Buffers->AllocatedCount *= 2; + + if (Buffers->AllocatedCount < DrawInfo->LineDataCount) + Buffers->AllocatedCount = DrawInfo->LineDataCount; + + Buffers->Data1 = PhAllocate(Buffers->AllocatedCount * sizeof(FLOAT)); + + if (DrawInfo->Flags & PH_GRAPH_USE_LINE_2) + { + Buffers->Data2 = PhAllocate(Buffers->AllocatedCount * sizeof(FLOAT)); + } + + Buffers->Valid = FALSE; + } + + DrawInfo->LineData1 = Buffers->Data1; + DrawInfo->LineData2 = Buffers->Data2; +} + +VOID PhInitializeGraphState( + _Out_ PPH_GRAPH_STATE State + ) +{ + PhInitializeGraphBuffers(&State->Buffers); + State->Text = NULL; + State->TooltipText = NULL; + State->TooltipIndex = -1; +} + +VOID PhDeleteGraphState( + _Inout_ PPH_GRAPH_STATE State + ) +{ + PhDeleteGraphBuffers(&State->Buffers); + if (State->Text) PhDereferenceObject(State->Text); + if (State->TooltipText) PhDereferenceObject(State->TooltipText); +} + +VOID PhGraphStateGetDrawInfo( + _Inout_ PPH_GRAPH_STATE State, + _In_ PPH_GRAPH_GETDRAWINFO GetDrawInfo, + _In_ ULONG DataCount + ) +{ + PhGetDrawInfoGraphBuffers(&State->Buffers, GetDrawInfo->DrawInfo, DataCount); +} diff --git a/phlib/guisup.c b/phlib/guisup.c new file mode 100644 index 0000000..f948223 --- /dev/null +++ b/phlib/guisup.c @@ -0,0 +1,1372 @@ +/* + * Process Hacker - + * GUI support functions + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#define SCALE_DPI(Value) PhMultiplyDivide(Value, PhGlobalDpi, 96) + +_ChangeWindowMessageFilter ChangeWindowMessageFilter_I; +_IsImmersiveProcess IsImmersiveProcess_I; +_RunFileDlg RunFileDlg; +_SHAutoComplete SHAutoComplete_I; + +static PH_INITONCE SharedIconCacheInitOnce = PH_INITONCE_INIT; +static PPH_HASHTABLE SharedIconCacheHashtable; +static PH_QUEUED_LOCK SharedIconCacheLock = PH_QUEUED_LOCK_INIT; + +VOID PhGuiSupportInitialization( + VOID + ) +{ + HMODULE shell32Handle; + HMODULE shlwapiHandle; + + shell32Handle = LoadLibrary(L"shell32.dll"); + shlwapiHandle = LoadLibrary(L"shlwapi.dll"); + + if (WINDOWS_HAS_UAC) + ChangeWindowMessageFilter_I = PhGetModuleProcAddress(L"user32.dll", "ChangeWindowMessageFilter"); + if (WINDOWS_HAS_IMMERSIVE) + IsImmersiveProcess_I = PhGetModuleProcAddress(L"user32.dll", "IsImmersiveProcess"); + RunFileDlg = PhGetProcedureAddress(shell32Handle, NULL, 61); + SHAutoComplete_I = PhGetProcedureAddress(shlwapiHandle, "SHAutoComplete", 0); +} + +VOID PhSetControlTheme( + _In_ HWND Handle, + _In_ PWSTR Theme + ) +{ + if (WindowsVersion >= WINDOWS_VISTA) + { + SetWindowTheme(Handle, Theme, NULL); + } +} + +INT PhAddListViewColumn( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT DisplayIndex, + _In_ INT SubItemIndex, + _In_ INT Format, + _In_ INT Width, + _In_ PWSTR Text + ) +{ + LVCOLUMN column; + + column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_ORDER; + column.fmt = Format; + column.cx = Width < 0 ? -Width : SCALE_DPI(Width); + column.pszText = Text; + column.iSubItem = SubItemIndex; + column.iOrder = DisplayIndex; + + return ListView_InsertColumn(ListViewHandle, Index, &column); +} + +INT PhAddListViewItem( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ PWSTR Text, + _In_opt_ PVOID Param + ) +{ + LVITEM item; + + item.mask = LVIF_TEXT | LVIF_PARAM; + item.iItem = Index; + item.iSubItem = 0; + item.pszText = Text; + item.lParam = (LPARAM)Param; + + return ListView_InsertItem(ListViewHandle, &item); +} + +INT PhFindListViewItemByFlags( + _In_ HWND ListViewHandle, + _In_ INT StartIndex, + _In_ ULONG Flags + ) +{ + return ListView_GetNextItem(ListViewHandle, StartIndex, Flags); +} + +INT PhFindListViewItemByParam( + _In_ HWND ListViewHandle, + _In_ INT StartIndex, + _In_opt_ PVOID Param + ) +{ + LVFINDINFO findInfo; + + findInfo.flags = LVFI_PARAM; + findInfo.lParam = (LPARAM)Param; + + return ListView_FindItem(ListViewHandle, StartIndex, &findInfo); +} + +LOGICAL PhGetListViewItemImageIndex( + _In_ HWND ListViewHandle, + _In_ INT Index, + _Out_ PINT ImageIndex + ) +{ + LOGICAL result; + LVITEM item; + + item.mask = LVIF_IMAGE; + item.iItem = Index; + item.iSubItem = 0; + + result = ListView_GetItem(ListViewHandle, &item); + + if (!result) + return result; + + *ImageIndex = item.iImage; + + return result; +} + +LOGICAL PhGetListViewItemParam( + _In_ HWND ListViewHandle, + _In_ INT Index, + _Out_ PVOID *Param + ) +{ + LOGICAL result; + LVITEM item; + + item.mask = LVIF_PARAM; + item.iItem = Index; + item.iSubItem = 0; + + result = ListView_GetItem(ListViewHandle, &item); + + if (!result) + return result; + + *Param = (PVOID)item.lParam; + + return result; +} + +VOID PhRemoveListViewItem( + _In_ HWND ListViewHandle, + _In_ INT Index + ) +{ + ListView_DeleteItem(ListViewHandle, Index); +} + +VOID PhSetListViewItemImageIndex( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT ImageIndex + ) +{ + LVITEM item; + + item.mask = LVIF_IMAGE; + item.iItem = Index; + item.iSubItem = 0; + item.iImage = ImageIndex; + + ListView_SetItem(ListViewHandle, &item); +} + +VOID PhSetListViewSubItem( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT SubItemIndex, + _In_ PWSTR Text + ) +{ + LVITEM item; + + item.mask = LVIF_TEXT; + item.iItem = Index; + item.iSubItem = SubItemIndex; + item.pszText = Text; + + ListView_SetItem(ListViewHandle, &item); +} + +BOOLEAN PhLoadListViewColumnSettings( + _In_ HWND ListViewHandle, + _In_ PPH_STRING Settings + ) +{ +#define ORDER_LIMIT 50 + PH_STRINGREF remainingPart; + ULONG columnIndex; + ULONG orderArray[ORDER_LIMIT]; // HACK, but reasonable limit + ULONG maxOrder; + ULONG scale; + + if (Settings->Length == 0) + return FALSE; + + remainingPart = Settings->sr; + columnIndex = 0; + memset(orderArray, 0, sizeof(orderArray)); + maxOrder = 0; + + if (remainingPart.Length != 0 && remainingPart.Buffer[0] == '@') + { + PH_STRINGREF scalePart; + ULONG64 integer; + + PhSkipStringRef(&remainingPart, sizeof(WCHAR)); + PhSplitStringRefAtChar(&remainingPart, '|', &scalePart, &remainingPart); + + if (scalePart.Length == 0 || !PhStringToInteger64(&scalePart, 10, &integer)) + return FALSE; + + scale = (ULONG)integer; + } + else + { + scale = PhGlobalDpi; + } + + while (remainingPart.Length != 0) + { + PH_STRINGREF columnPart; + PH_STRINGREF orderPart; + PH_STRINGREF widthPart; + ULONG64 integer; + ULONG order; + ULONG width; + LVCOLUMN lvColumn; + + PhSplitStringRefAtChar(&remainingPart, '|', &columnPart, &remainingPart); + + if (columnPart.Length == 0) + return FALSE; + + PhSplitStringRefAtChar(&columnPart, ',', &orderPart, &widthPart); + + if (orderPart.Length == 0 || widthPart.Length == 0) + return FALSE; + + // Order + + if (!PhStringToInteger64(&orderPart, 10, &integer)) + return FALSE; + + order = (ULONG)integer; + + if (order < ORDER_LIMIT) + { + orderArray[order] = columnIndex; + + if (maxOrder < order + 1) + maxOrder = order + 1; + } + + // Width + + if (!PhStringToInteger64(&widthPart, 10, &integer)) + return FALSE; + + width = (ULONG)integer; + + if (scale != PhGlobalDpi && scale != 0) + width = PhMultiplyDivide(width, PhGlobalDpi, scale); + + lvColumn.mask = LVCF_WIDTH; + lvColumn.cx = width; + ListView_SetColumn(ListViewHandle, columnIndex, &lvColumn); + + columnIndex++; + } + + ListView_SetColumnOrderArray(ListViewHandle, maxOrder, orderArray); + + return TRUE; +} + +PPH_STRING PhSaveListViewColumnSettings( + _In_ HWND ListViewHandle + ) +{ + PH_STRING_BUILDER stringBuilder; + ULONG i = 0; + LVCOLUMN lvColumn; + + PhInitializeStringBuilder(&stringBuilder, 20); + + PhAppendFormatStringBuilder(&stringBuilder, L"@%u|", PhGlobalDpi); + + lvColumn.mask = LVCF_WIDTH | LVCF_ORDER; + + while (ListView_GetColumn(ListViewHandle, i, &lvColumn)) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%u,%u|", + lvColumn.iOrder, + lvColumn.cx + ); + i++; + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} + +INT PhAddTabControlTab( + _In_ HWND TabControlHandle, + _In_ INT Index, + _In_ PWSTR Text + ) +{ + TCITEM item; + + item.mask = TCIF_TEXT; + item.pszText = Text; + + return TabCtrl_InsertItem(TabControlHandle, Index, &item); +} + +PPH_STRING PhGetWindowText( + _In_ HWND hwnd + ) +{ + PPH_STRING text; + + PhGetWindowTextEx(hwnd, 0, &text); + return text; +} + +ULONG PhGetWindowTextEx( + _In_ HWND hwnd, + _In_ ULONG Flags, + _Out_opt_ PPH_STRING *Text + ) +{ + PPH_STRING string; + ULONG length; + + if (Flags & PH_GET_WINDOW_TEXT_INTERNAL) + { + if (Flags & PH_GET_WINDOW_TEXT_LENGTH_ONLY) + { + WCHAR buffer[32]; + length = InternalGetWindowText(hwnd, buffer, sizeof(buffer) / sizeof(WCHAR)); + } + else + { + // TODO: Resize the buffer until we get the entire thing. + string = PhCreateStringEx(NULL, 256 * sizeof(WCHAR)); + length = InternalGetWindowText(hwnd, string->Buffer, (ULONG)string->Length / sizeof(WCHAR) + 1); + string->Length = length * sizeof(WCHAR); + + if (Text) + *Text = string; + else + PhDereferenceObject(string); + } + + return length; + } + else + { + length = GetWindowTextLength(hwnd); + + if (length == 0 || (Flags & PH_GET_WINDOW_TEXT_LENGTH_ONLY)) + { + if (Text) + *Text = PhReferenceEmptyString(); + + return length; + } + + string = PhCreateStringEx(NULL, length * sizeof(WCHAR)); + + if (GetWindowText(hwnd, string->Buffer, (ULONG)string->Length / sizeof(WCHAR) + 1)) + { + if (Text) + *Text = string; + else + PhDereferenceObject(string); + + return length; + } + else + { + if (Text) + *Text = PhReferenceEmptyString(); + + PhDereferenceObject(string); + + return 0; + } + } +} + +VOID PhAddComboBoxStrings( + _In_ HWND hWnd, + _In_ PWSTR *Strings, + _In_ ULONG NumberOfStrings + ) +{ + ULONG i; + + for (i = 0; i < NumberOfStrings; i++) + ComboBox_AddString(hWnd, Strings[i]); +} + +PPH_STRING PhGetComboBoxString( + _In_ HWND hwnd, + _In_ INT Index + ) +{ + PPH_STRING string; + ULONG length; + + if (Index == -1) + { + Index = ComboBox_GetCurSel(hwnd); + + if (Index == -1) + return NULL; + } + + length = ComboBox_GetLBTextLen(hwnd, Index); + + if (length == CB_ERR) + return NULL; + if (length == 0) + return PhReferenceEmptyString(); + + string = PhCreateStringEx(NULL, length * 2); + + if (ComboBox_GetLBText(hwnd, Index, string->Buffer) != CB_ERR) + { + return string; + } + else + { + PhDereferenceObject(string); + return NULL; + } +} + +INT PhSelectComboBoxString( + _In_ HWND hwnd, + _In_ PWSTR String, + _In_ BOOLEAN Partial + ) +{ + if (Partial) + { + return ComboBox_SelectString(hwnd, -1, String); + } + else + { + INT index; + + index = ComboBox_FindStringExact(hwnd, -1, String); + + if (index == CB_ERR) + return CB_ERR; + + ComboBox_SetCurSel(hwnd, index); + + return index; + } +} + +PPH_STRING PhGetListBoxString( + _In_ HWND hwnd, + _In_ INT Index + ) +{ + PPH_STRING string; + ULONG length; + + if (Index == -1) + { + Index = ListBox_GetCurSel(hwnd); + + if (Index == -1) + return NULL; + } + + length = ListBox_GetTextLen(hwnd, Index); + + if (length == LB_ERR) + return NULL; + if (length == 0) + return PhReferenceEmptyString(); + + string = PhCreateStringEx(NULL, length * 2); + + if (ListBox_GetText(hwnd, Index, string->Buffer) != LB_ERR) + { + return string; + } + else + { + PhDereferenceObject(string); + return NULL; + } +} + +VOID PhSetStateAllListViewItems( + _In_ HWND hWnd, + _In_ ULONG State, + _In_ ULONG Mask + ) +{ + ULONG i; + ULONG count; + + count = ListView_GetItemCount(hWnd); + + if (count == -1) + return; + + for (i = 0; i < count; i++) + { + ListView_SetItemState(hWnd, i, State, Mask); + } +} + +PVOID PhGetSelectedListViewItemParam( + _In_ HWND hWnd + ) +{ + INT index; + PVOID param; + + index = PhFindListViewItemByFlags( + hWnd, + -1, + LVNI_SELECTED + ); + + if (index != -1) + { + if (PhGetListViewItemParam( + hWnd, + index, + ¶m + )) + { + return param; + } + } + + return NULL; +} + +VOID PhGetSelectedListViewItemParams( + _In_ HWND hWnd, + _Out_ PVOID **Items, + _Out_ PULONG NumberOfItems + ) +{ + PH_ARRAY array; + ULONG index; + PVOID param; + + PhInitializeArray(&array, sizeof(PVOID), 2); + index = -1; + + while ((index = PhFindListViewItemByFlags( + hWnd, + index, + LVNI_SELECTED + )) != -1) + { + if (PhGetListViewItemParam(hWnd, index, ¶m)) + PhAddItemArray(&array, ¶m); + } + + *NumberOfItems = (ULONG)array.Count; + *Items = PhFinalArrayItems(&array); +} + +VOID PhSetImageListBitmap( + _In_ HIMAGELIST ImageList, + _In_ INT Index, + _In_ HINSTANCE InstanceHandle, + _In_ LPCWSTR BitmapName + ) +{ + HBITMAP bitmap; + + bitmap = LoadImage(InstanceHandle, BitmapName, IMAGE_BITMAP, 0, 0, 0); + + if (bitmap) + { + ImageList_Replace(ImageList, Index, bitmap, NULL); + DeleteObject(bitmap); + } +} + +static BOOLEAN SharedIconCacheHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PPHP_ICON_ENTRY entry1 = Entry1; + PPHP_ICON_ENTRY entry2 = Entry2; + + if (entry1->InstanceHandle != entry2->InstanceHandle || + entry1->Width != entry2->Width || + entry1->Height != entry2->Height) + { + return FALSE; + } + + if (IS_INTRESOURCE(entry1->Name)) + { + if (IS_INTRESOURCE(entry2->Name)) + return entry1->Name == entry2->Name; + else + return FALSE; + } + else + { + if (!IS_INTRESOURCE(entry2->Name)) + return PhEqualStringZ(entry1->Name, entry2->Name, FALSE); + else + return FALSE; + } +} + +static ULONG SharedIconCacheHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PPHP_ICON_ENTRY entry = Entry; + ULONG nameHash; + + if (IS_INTRESOURCE(entry->Name)) + nameHash = PtrToUlong(entry->Name); + else + nameHash = PhHashBytes((PUCHAR)entry->Name, PhCountStringZ(entry->Name)); + + return nameHash ^ (PtrToUlong(entry->InstanceHandle) >> 5) ^ (entry->Width << 3) ^ entry->Height; +} + +HICON PhLoadIcon( + _In_opt_ HINSTANCE InstanceHandle, + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ ULONG Width, + _In_opt_ ULONG Height + ) +{ + static _LoadIconMetric loadIconMetric; + static _LoadIconWithScaleDown loadIconWithScaleDown; + + PHP_ICON_ENTRY entry; + PPHP_ICON_ENTRY actualEntry; + HICON icon = NULL; + + if (PhBeginInitOnce(&SharedIconCacheInitOnce)) + { + loadIconMetric = (_LoadIconMetric)PhGetModuleProcAddress(L"comctl32.dll", "LoadIconMetric"); + loadIconWithScaleDown = (_LoadIconWithScaleDown)PhGetModuleProcAddress(L"comctl32.dll", "LoadIconWithScaleDown"); + SharedIconCacheHashtable = PhCreateHashtable(sizeof(PHP_ICON_ENTRY), + SharedIconCacheHashtableEqualFunction, SharedIconCacheHashtableHashFunction, 10); + PhEndInitOnce(&SharedIconCacheInitOnce); + } + + if (Flags & PH_LOAD_ICON_SHARED) + { + PhAcquireQueuedLockExclusive(&SharedIconCacheLock); + + entry.InstanceHandle = InstanceHandle; + entry.Name = Name; + entry.Width = PhpGetIconEntrySize(Width, Flags); + entry.Height = PhpGetIconEntrySize(Height, Flags); + actualEntry = PhFindEntryHashtable(SharedIconCacheHashtable, &entry); + + if (actualEntry) + { + icon = actualEntry->Icon; + PhReleaseQueuedLockExclusive(&SharedIconCacheLock); + return icon; + } + } + + if (Flags & (PH_LOAD_ICON_SIZE_SMALL | PH_LOAD_ICON_SIZE_LARGE)) + { + if (loadIconMetric) + loadIconMetric(InstanceHandle, Name, (Flags & PH_LOAD_ICON_SIZE_SMALL) ? LIM_SMALL : LIM_LARGE, &icon); + } + else + { + if (loadIconWithScaleDown) + loadIconWithScaleDown(InstanceHandle, Name, Width, Height, &icon); + } + + if (!icon && !(Flags & PH_LOAD_ICON_STRICT)) + { + if (Flags & PH_LOAD_ICON_SIZE_SMALL) + { + static ULONG smallWidth = 0; + static ULONG smallHeight = 0; + + if (!smallWidth) + smallWidth = GetSystemMetrics(SM_CXSMICON); + if (!smallHeight) + smallHeight = GetSystemMetrics(SM_CYSMICON); + + Width = smallWidth; + Height = smallHeight; + } + else if (Flags & PH_LOAD_ICON_SIZE_LARGE) + { + static ULONG largeWidth = 0; + static ULONG largeHeight = 0; + + if (!largeWidth) + largeWidth = GetSystemMetrics(SM_CXICON); + if (!largeHeight) + largeHeight = GetSystemMetrics(SM_CYICON); + + Width = largeWidth; + Height = largeHeight; + } + + icon = LoadImage(InstanceHandle, Name, IMAGE_ICON, Width, Height, 0); + } + + if (Flags & PH_LOAD_ICON_SHARED) + { + if (icon) + { + if (!IS_INTRESOURCE(Name)) + entry.Name = PhDuplicateStringZ(Name); + entry.Icon = icon; + PhAddEntryHashtable(SharedIconCacheHashtable, &entry); + } + + PhReleaseQueuedLockExclusive(&SharedIconCacheLock); + } + + return icon; +} + +/** + * Gets the default icon used for executable files. + * + * \param SmallIcon A variable which receives the small default executable icon. Do not destroy the + * icon using DestroyIcon(); it is shared between callers. + * \param LargeIcon A variable which receives the large default executable icon. Do not destroy the + * icon using DestroyIcon(); it is shared between callers. + */ +VOID PhGetStockApplicationIcon( + _Out_opt_ HICON *SmallIcon, + _Out_opt_ HICON *LargeIcon + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static HICON smallIcon = NULL; + static HICON largeIcon = NULL; + + // This no longer uses SHGetFileInfo because it is *very* slow and causes many other DLLs to be + // loaded, increasing memory usage. The worst thing about it, however, is that it is horribly + // incompatible with multi-threading. The first time it is called, it tries to perform some + // one-time initialization. It guards this with a lock, but when multiple threads try to call + // the function at the same time, instead of waiting for initialization to finish it simply + // fails the other threads. + + if (PhBeginInitOnce(&initOnce)) + { + PPH_STRING systemDirectory; + PPH_STRING dllFileName; + + // imageres,11 (Windows 10 and above), user32,0 (Vista and above) or shell32,2 (XP) contains + // the default application icon. + + if (systemDirectory = PhGetSystemDirectory()) + { + PH_STRINGREF dllBaseName; + ULONG index; + + // TODO: Find a better solution. + if (WindowsVersion >= WINDOWS_10) + { + PhInitializeStringRef(&dllBaseName, L"\\imageres.dll"); + index = 11; + } + else if (WindowsVersion >= WINDOWS_VISTA) + { + PhInitializeStringRef(&dllBaseName, L"\\user32.dll"); + index = 0; + } + else + { + PhInitializeStringRef(&dllBaseName, L"\\shell32.dll"); + index = 2; + } + + dllFileName = PhConcatStringRef2(&systemDirectory->sr, &dllBaseName); + PhDereferenceObject(systemDirectory); + + ExtractIconEx(dllFileName->Buffer, index, &largeIcon, &smallIcon, 1); + PhDereferenceObject(dllFileName); + } + + // Fallback icons - this is bad, because the icon isn't scaled correctly. + if (!smallIcon) + smallIcon = LoadIcon(NULL, IDI_APPLICATION); + if (!largeIcon) + largeIcon = LoadIcon(NULL, IDI_APPLICATION); + + PhEndInitOnce(&initOnce); + } + + if (SmallIcon) + *SmallIcon = smallIcon; + if (LargeIcon) + *LargeIcon = largeIcon; +} + +HICON PhGetFileShellIcon( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR DefaultExtension, + _In_ BOOLEAN LargeIcon + ) +{ + SHFILEINFO fileInfo; + ULONG iconFlag; + HICON icon; + + if (DefaultExtension && PhEqualStringZ(DefaultExtension, L".exe", TRUE)) + { + // Special case for executable files (see above for reasoning). + + icon = NULL; + + if (FileName) + { + ExtractIconEx( + FileName, + 0, + LargeIcon ? &icon : NULL, + !LargeIcon ? &icon : NULL, + 1 + ); + } + + if (!icon) + { + PhGetStockApplicationIcon( + !LargeIcon ? &icon : NULL, + LargeIcon ? &icon : NULL + ); + + if (icon) + icon = DuplicateIcon(NULL, icon); + } + + return icon; + } + + iconFlag = LargeIcon ? SHGFI_LARGEICON : SHGFI_SMALLICON; + icon = NULL; + + if (FileName && SHGetFileInfo( + FileName, + 0, + &fileInfo, + sizeof(SHFILEINFO), + SHGFI_ICON | iconFlag + )) + { + icon = fileInfo.hIcon; + } + + if (!icon && DefaultExtension) + { + if (SHGetFileInfo( + DefaultExtension, + FILE_ATTRIBUTE_NORMAL, + &fileInfo, + sizeof(SHFILEINFO), + SHGFI_ICON | iconFlag | SHGFI_USEFILEATTRIBUTES + )) + icon = fileInfo.hIcon; + } + + return icon; +} + +VOID PhpSetClipboardData( + _In_ HWND hWnd, + _In_ ULONG Format, + _In_ HANDLE Data + ) +{ + if (OpenClipboard(hWnd)) + { + if (!EmptyClipboard()) + goto Fail; + + if (!SetClipboardData(Format, Data)) + goto Fail; + + CloseClipboard(); + + return; + } + +Fail: + GlobalFree(Data); +} + +VOID PhSetClipboardString( + _In_ HWND hWnd, + _In_ PPH_STRINGREF String + ) +{ + HANDLE data; + PVOID memory; + + data = GlobalAlloc(GMEM_MOVEABLE, String->Length + sizeof(WCHAR)); + memory = GlobalLock(data); + + memcpy(memory, String->Buffer, String->Length); + *(PWCHAR)((PCHAR)memory + String->Length) = 0; + + GlobalUnlock(memory); + + PhpSetClipboardData(hWnd, CF_UNICODETEXT, data); +} + +HWND PhCreateDialogFromTemplate( + _In_ HWND Parent, + _In_ ULONG Style, + _In_ PVOID Instance, + _In_ PWSTR Template, + _In_ DLGPROC DialogProc, + _In_ PVOID Parameter + ) +{ + HRSRC resourceInfo; + ULONG resourceSize; + HGLOBAL resourceHandle; + PDLGTEMPLATEEX dialog; + PDLGTEMPLATEEX dialogCopy; + HWND dialogHandle; + + resourceInfo = FindResource(Instance, Template, MAKEINTRESOURCE(RT_DIALOG)); + + if (!resourceInfo) + return NULL; + + resourceSize = SizeofResource(Instance, resourceInfo); + + if (resourceSize == 0) + return NULL; + + resourceHandle = LoadResource(Instance, resourceInfo); + + if (!resourceHandle) + return NULL; + + dialog = LockResource(resourceHandle); + + if (!dialog) + return NULL; + + dialogCopy = PhAllocateCopy(dialog, resourceSize); + + if (dialogCopy->signature == 0xffff) + { + dialogCopy->style = Style; + } + else + { + ((DLGTEMPLATE *)dialogCopy)->style = Style; + } + + dialogHandle = CreateDialogIndirectParam(Instance, (DLGTEMPLATE *)dialogCopy, Parent, DialogProc, (LPARAM)Parameter); + + PhFree(dialogCopy); + + return dialogHandle; +} + +BOOLEAN PhModalPropertySheet( + _Inout_ PROPSHEETHEADER *Header + ) +{ + // PropertySheet incorrectly discards WM_QUIT messages in certain cases, so we will use our own + // message loop. An example of this is when GetMessage (called by PropertySheet's message loop) + // dispatches a message directly from kernel-mode that causes the property sheet to close. In + // that case PropertySheet will retrieve the WM_QUIT message but will ignore it because of its + // buggy logic. + + // This is also a good opportunity to introduce an auto-pool. + + PH_AUTO_POOL autoPool; + HWND oldFocus; + HWND topLevelOwner; + HWND hwnd; + BOOL result; + MSG message; + + PhInitializeAutoPool(&autoPool); + + oldFocus = GetFocus(); + topLevelOwner = Header->hwndParent; + + while (topLevelOwner && (GetWindowLong(topLevelOwner, GWL_STYLE) & WS_CHILD)) + topLevelOwner = GetParent(topLevelOwner); + + if (topLevelOwner && (topLevelOwner == GetDesktopWindow() || EnableWindow(topLevelOwner, FALSE))) + topLevelOwner = NULL; + + Header->dwFlags |= PSH_MODELESS; + hwnd = (HWND)PropertySheet(Header); + + if (!hwnd) + { + if (topLevelOwner) + EnableWindow(topLevelOwner, TRUE); + + return FALSE; + } + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!PropSheet_IsDialogMessage(hwnd, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + + // Destroy the window when necessary. + if (!PropSheet_GetCurrentPageHwnd(hwnd)) + break; + } + + if (result == 0) + PostQuitMessage((INT)message.wParam); + if (Header->hwndParent && GetActiveWindow() == hwnd) + SetActiveWindow(Header->hwndParent); + if (topLevelOwner) + EnableWindow(topLevelOwner, TRUE); + if (oldFocus && IsWindow(oldFocus)) + SetFocus(oldFocus); + + DestroyWindow(hwnd); + PhDeleteAutoPool(&autoPool); + + return TRUE; +} + +VOID PhInitializeLayoutManager( + _Out_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND RootWindowHandle + ) +{ + Manager->List = PhCreateList(4); + Manager->LayoutNumber = 0; + + Manager->RootItem.Handle = RootWindowHandle; + GetClientRect(Manager->RootItem.Handle, &Manager->RootItem.Rect); + Manager->RootItem.OrigRect = Manager->RootItem.Rect; + Manager->RootItem.ParentItem = NULL; + Manager->RootItem.LayoutParentItem = NULL; + Manager->RootItem.LayoutNumber = 0; + Manager->RootItem.NumberOfChildren = 0; + Manager->RootItem.DeferHandle = NULL; +} + +VOID PhDeleteLayoutManager( + _Inout_ PPH_LAYOUT_MANAGER Manager + ) +{ + ULONG i; + + for (i = 0; i < Manager->List->Count; i++) + PhFree(Manager->List->Items[i]); + + PhDereferenceObject(Manager->List); +} + +// HACK: The math below is all horribly broken, especially the HACK for multiline tab controls. + +PPH_LAYOUT_ITEM PhAddLayoutItem( + _Inout_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND Handle, + _In_opt_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor + ) +{ + PPH_LAYOUT_ITEM layoutItem; + RECT dummy = { 0 }; + + layoutItem = PhAddLayoutItemEx( + Manager, + Handle, + ParentItem, + Anchor, + dummy + ); + + layoutItem->Margin = layoutItem->Rect; + PhConvertRect(&layoutItem->Margin, &layoutItem->ParentItem->Rect); + + if (layoutItem->ParentItem != layoutItem->LayoutParentItem) + { + // Fix the margin because the item has a dummy parent. They share the same layout parent + // item. + layoutItem->Margin.top -= layoutItem->ParentItem->Rect.top; + layoutItem->Margin.left -= layoutItem->ParentItem->Rect.left; + layoutItem->Margin.right = layoutItem->ParentItem->Margin.right; + layoutItem->Margin.bottom = layoutItem->ParentItem->Margin.bottom; + } + + return layoutItem; +} + +PPH_LAYOUT_ITEM PhAddLayoutItemEx( + _Inout_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND Handle, + _In_opt_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor, + _In_ RECT Margin + ) +{ + PPH_LAYOUT_ITEM item; + + if (!ParentItem) + ParentItem = &Manager->RootItem; + + item = PhAllocate(sizeof(PH_LAYOUT_ITEM)); + item->Handle = Handle; + item->ParentItem = ParentItem; + item->LayoutNumber = Manager->LayoutNumber; + item->NumberOfChildren = 0; + item->DeferHandle = NULL; + item->Anchor = Anchor; + + item->LayoutParentItem = item->ParentItem; + + while ((item->LayoutParentItem->Anchor & PH_LAYOUT_DUMMY_MASK) && + item->LayoutParentItem->LayoutParentItem) + { + item->LayoutParentItem = item->LayoutParentItem->LayoutParentItem; + } + + item->LayoutParentItem->NumberOfChildren++; + + GetWindowRect(Handle, &item->Rect); + MapWindowPoints(NULL, item->LayoutParentItem->Handle, (POINT *)&item->Rect, 2); + + if (item->Anchor & PH_LAYOUT_TAB_CONTROL) + { + // We want to convert the tab control rectangle to the tab page display rectangle. + TabCtrl_AdjustRect(Handle, FALSE, &item->Rect); + } + + item->Margin = Margin; + + item->OrigRect = item->Rect; + + PhAddItemList(Manager->List, item); + + return item; +} + +VOID PhpLayoutItemLayout( + _Inout_ PPH_LAYOUT_MANAGER Manager, + _Inout_ PPH_LAYOUT_ITEM Item + ) +{ + RECT rect; + BOOLEAN hasDummyParent; + + if (Item->NumberOfChildren > 0 && !Item->DeferHandle) + Item->DeferHandle = BeginDeferWindowPos(Item->NumberOfChildren); + + if (Item->LayoutNumber == Manager->LayoutNumber) + return; + + // If this is the root item we must stop here. + if (!Item->ParentItem) + return; + + PhpLayoutItemLayout(Manager, Item->ParentItem); + + if (Item->ParentItem != Item->LayoutParentItem) + { + PhpLayoutItemLayout(Manager, Item->LayoutParentItem); + hasDummyParent = TRUE; + } + else + { + hasDummyParent = FALSE; + } + + GetWindowRect(Item->Handle, &Item->Rect); + MapWindowPoints(NULL, Item->LayoutParentItem->Handle, (POINT *)&Item->Rect, 2); + + if (Item->Anchor & PH_LAYOUT_TAB_CONTROL) + { + // We want to convert the tab control rectangle to the tab page display rectangle. + TabCtrl_AdjustRect(Item->Handle, FALSE, &Item->Rect); + } + + if (!(Item->Anchor & PH_LAYOUT_DUMMY_MASK)) + { + // Convert right/bottom into margins to make the calculations + // easier. + rect = Item->Rect; + PhConvertRect(&rect, &Item->LayoutParentItem->Rect); + + if (!(Item->Anchor & (PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT))) + { + // TODO + PhRaiseStatus(STATUS_NOT_IMPLEMENTED); + } + else if (Item->Anchor & PH_ANCHOR_RIGHT) + { + if (Item->Anchor & PH_ANCHOR_LEFT) + { + rect.left = (hasDummyParent ? Item->ParentItem->Rect.left : 0) + Item->Margin.left; + rect.right = Item->Margin.right; + } + else + { + ULONG diff = Item->Margin.right - rect.right; + + rect.left -= diff; + rect.right += diff; + } + } + + if (!(Item->Anchor & (PH_ANCHOR_TOP | PH_ANCHOR_BOTTOM))) + { + // TODO + PhRaiseStatus(STATUS_NOT_IMPLEMENTED); + } + else if (Item->Anchor & PH_ANCHOR_BOTTOM) + { + if (Item->Anchor & PH_ANCHOR_TOP) + { + // tab control hack + rect.top = (hasDummyParent ? Item->ParentItem->Rect.top : 0) + Item->Margin.top; + rect.bottom = Item->Margin.bottom; + } + else + { + ULONG diff = Item->Margin.bottom - rect.bottom; + + rect.top -= diff; + rect.bottom += diff; + } + } + + // Convert the right/bottom back into co-ordinates. + PhConvertRect(&rect, &Item->LayoutParentItem->Rect); + Item->Rect = rect; + + if (!(Item->Anchor & PH_LAYOUT_IMMEDIATE_RESIZE)) + { + Item->LayoutParentItem->DeferHandle = DeferWindowPos( + Item->LayoutParentItem->DeferHandle, Item->Handle, + NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER + ); + } + else + { + // This is needed for tab controls, so that TabCtrl_AdjustRect will give us an + // up-to-date result. + SetWindowPos( + Item->Handle, + NULL, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOZORDER + ); + } + } + + Item->LayoutNumber = Manager->LayoutNumber; +} + +VOID PhLayoutManagerLayout( + _Inout_ PPH_LAYOUT_MANAGER Manager + ) +{ + ULONG i; + + Manager->LayoutNumber++; + + GetClientRect(Manager->RootItem.Handle, &Manager->RootItem.Rect); + + for (i = 0; i < Manager->List->Count; i++) + { + PPH_LAYOUT_ITEM item = (PPH_LAYOUT_ITEM)Manager->List->Items[i]; + + PhpLayoutItemLayout(Manager, item); + } + + for (i = 0; i < Manager->List->Count; i++) + { + PPH_LAYOUT_ITEM item = (PPH_LAYOUT_ITEM)Manager->List->Items[i]; + + if (item->DeferHandle) + { + EndDeferWindowPos(item->DeferHandle); + item->DeferHandle = NULL; + } + + if (item->Anchor & PH_LAYOUT_FORCE_INVALIDATE) + { + InvalidateRect(item->Handle, NULL, FALSE); + } + } + + if (Manager->RootItem.DeferHandle) + { + EndDeferWindowPos(Manager->RootItem.DeferHandle); + Manager->RootItem.DeferHandle = NULL; + } +} diff --git a/phlib/handle.c b/phlib/handle.c new file mode 100644 index 0000000..6f7d5fe --- /dev/null +++ b/phlib/handle.c @@ -0,0 +1,1097 @@ +/* + * Process Hacker - + * handle table + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +static PH_INITONCE PhHandleTableInitOnce = PH_INITONCE_INIT; +static PH_FREE_LIST PhHandleTableLevel0FreeList; +static PH_FREE_LIST PhHandleTableLevel1FreeList; + +PPH_HANDLE_TABLE PhCreateHandleTable( + VOID + ) +{ + PPH_HANDLE_TABLE handleTable; + ULONG i; + + if (PhBeginInitOnce(&PhHandleTableInitOnce)) + { + PhInitializeFreeList( + &PhHandleTableLevel0FreeList, + sizeof(PH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES, + 64 + ); + PhInitializeFreeList( + &PhHandleTableLevel1FreeList, + sizeof(PPH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES, + 64 + ); + PhEndInitOnce(&PhHandleTableInitOnce); + } + +#ifdef PH_HANDLE_TABLE_SAFE + handleTable = PhAllocateSafe(sizeof(PH_HANDLE_TABLE)); + + if (!handleTable) + return NULL; +#else + handleTable = PhAllocate(sizeof(PH_HANDLE_TABLE)); +#endif + + PhInitializeQueuedLock(&handleTable->Lock); + PhInitializeWakeEvent(&handleTable->HandleWakeEvent); + + handleTable->NextValue = 0; + + handleTable->Count = 0; + handleTable->TableValue = (ULONG_PTR)PhpCreateHandleTableLevel0(handleTable, TRUE); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!handleTable->TableValue) + { + PhFree(handleTable); + return NULL; + } +#endif + + // We have now created the level 0 table. The free list can now be set up to point to handle 0, + // which points to the rest of the free list (1 -> 2 -> 3 -> ...). The next batch of handles + // that need to be created start at PH_HANDLE_TABLE_LEVEL_ENTRIES. + + handleTable->FreeValue = 0; + handleTable->NextValue = PH_HANDLE_TABLE_LEVEL_ENTRIES; + + handleTable->FreeValueAlt = PH_HANDLE_VALUE_INVALID; // no entries in alt. free list + + handleTable->Flags = 0; + + for (i = 0; i < PH_HANDLE_TABLE_LOCKS; i++) + PhInitializeQueuedLock(&handleTable->Locks[i]); + + return handleTable; +} + +VOID PhDestroyHandleTable( + _In_ _Post_invalid_ PPH_HANDLE_TABLE HandleTable + ) +{ + ULONG_PTR tableValue; + ULONG tableLevel; + PPH_HANDLE_TABLE_ENTRY table0; + PPH_HANDLE_TABLE_ENTRY *table1; + PPH_HANDLE_TABLE_ENTRY **table2; + ULONG i; + ULONG j; + + tableValue = HandleTable->TableValue; + tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK; + tableValue -= tableLevel; + + switch (tableLevel) + { + case 0: + { + table0 = (PPH_HANDLE_TABLE_ENTRY)tableValue; + + PhpFreeHandleTableLevel0(table0); + } + break; + case 1: + { + table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue; + + for (i = 0; i < PH_HANDLE_TABLE_LEVEL_ENTRIES; i++) + { + if (!table1[i]) + break; + + PhpFreeHandleTableLevel0(table1[i]); + } + + PhpFreeHandleTableLevel1(table1); + } + break; + case 2: + { + table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue; + + for (i = 0; i < PH_HANDLE_TABLE_LEVEL_ENTRIES; i++) + { + if (!table2[i]) + break; + + for (j = 0; j < PH_HANDLE_TABLE_LEVEL_ENTRIES; j++) + { + if (!table2[i][j]) + break; + + PhpFreeHandleTableLevel0(table2[i][j]); + } + + PhpFreeHandleTableLevel1(table2[i]); + } + + PhpFreeHandleTableLevel2(table2); + } + break; + default: + ASSUME_NO_DEFAULT; + } + + PhFree(HandleTable); +} + +VOID PhpBlockOnLockedHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + PH_QUEUED_WAIT_BLOCK waitBlock; + ULONG_PTR value; + + PhQueueWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock); + + value = HandleTableEntry->Value; + + if ( + (value & PH_HANDLE_TABLE_ENTRY_TYPE) != PH_HANDLE_TABLE_ENTRY_IN_USE || + (value & PH_HANDLE_TABLE_ENTRY_LOCKED) + ) + { + // Entry is not in use or has been unlocked; cancel the wait. + PhSetWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock); + } + else + { + PhWaitForWakeEvent(&HandleTable->HandleWakeEvent, &waitBlock, TRUE, NULL); + } +} + +BOOLEAN PhLockHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + ULONG_PTR value; + + while (TRUE) + { + value = HandleTableEntry->Value; + + if ((value & PH_HANDLE_TABLE_ENTRY_TYPE) != PH_HANDLE_TABLE_ENTRY_IN_USE) + return FALSE; + + if (value & PH_HANDLE_TABLE_ENTRY_LOCKED) + { + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&HandleTableEntry->Value, + (PVOID)(value - PH_HANDLE_TABLE_ENTRY_LOCKED), + (PVOID)value + ) == value) + { + return TRUE; + } + } + + PhpBlockOnLockedHandleTableEntry(HandleTable, HandleTableEntry); + } +} + +VOID PhUnlockHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + _interlockedbittestandset( + (PLONG)&HandleTableEntry->Value, + PH_HANDLE_TABLE_ENTRY_LOCKED_SHIFT + ); + PhSetWakeEvent(&HandleTable->HandleWakeEvent, NULL); +} + +HANDLE PhCreateHandle( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + PPH_HANDLE_TABLE_ENTRY entry; + ULONG handleValue; + + entry = PhpAllocateHandleTableEntry(HandleTable, &handleValue); + + if (!entry) + return NULL; + + // Copy the given handle table entry to the allocated entry. + + // All free entries have the Free type and have the (not) locked bit clear. There is no problem + // with setting the Type now; the entry is still locked, so they will block. + entry->TypeAndValue.Type = PH_HANDLE_TABLE_ENTRY_IN_USE; + entry->TypeAndValue.Value = HandleTableEntry->TypeAndValue.Value; + entry->Value2 = HandleTableEntry->Value2; + + // Now we unlock this entry, waking anyone who was caught back there before we had finished + // setting up the entry. + PhUnlockHandleTableEntry(HandleTable, entry); + + return PhpEncodeHandle(handleValue); +} + +BOOLEAN PhDestroyHandle( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ HANDLE Handle, + _In_opt_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + ULONG handleValue; + + handleValue = PhpDecodeHandle(Handle); + + if (!HandleTableEntry) + { + HandleTableEntry = PhpLookupHandleTableEntry(HandleTable, handleValue); + + if (!HandleTableEntry) + return FALSE; + + if (!PhLockHandleTableEntry(HandleTable, HandleTableEntry)) + return FALSE; + } + + _InterlockedExchangePointer( + (PVOID *)&HandleTableEntry->Value, + (PVOID)PH_HANDLE_TABLE_ENTRY_FREE + ); + + // The handle table entry is now free; wake any waiters because they can't lock the entry now. + // Any future lock attempts will fail because the entry is marked as being free. + PhSetWakeEvent(&HandleTable->HandleWakeEvent, NULL); + + PhpFreeHandleTableEntry(HandleTable, handleValue, HandleTableEntry); + + return TRUE; +} + +PPH_HANDLE_TABLE_ENTRY PhLookupHandleTableEntry( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ HANDLE Handle + ) +{ + PPH_HANDLE_TABLE_ENTRY entry; + + entry = PhpLookupHandleTableEntry(HandleTable, PhpDecodeHandle(Handle)); + + if (!entry) + return NULL; + + if (!PhLockHandleTableEntry(HandleTable, entry)) + return NULL; + + return entry; +} + +VOID PhEnumHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + ULONG handleValue; + PPH_HANDLE_TABLE_ENTRY entry; + BOOLEAN cont; + + handleValue = 0; + + while (entry = PhpLookupHandleTableEntry(HandleTable, handleValue)) + { + if (PhLockHandleTableEntry(HandleTable, entry)) + { + cont = Callback( + HandleTable, + PhpEncodeHandle(handleValue), + entry, + Context + ); + PhUnlockHandleTableEntry(HandleTable, entry); + + if (!cont) + break; + } + + handleValue++; + } +} + +VOID PhSweepHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + ULONG handleValue; + PPH_HANDLE_TABLE_ENTRY entry; + BOOLEAN cont; + + handleValue = 0; + + while (entry = PhpLookupHandleTableEntry(HandleTable, handleValue)) + { + if (entry->TypeAndValue.Type == PH_HANDLE_TABLE_ENTRY_IN_USE) + { + cont = Callback( + HandleTable, + PhpEncodeHandle(handleValue), + entry, + Context + ); + + if (!cont) + break; + } + + handleValue++; + } +} + +NTSTATUS PhQueryInformationHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass, + _Out_writes_bytes_opt_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength, + _Out_opt_ PULONG ReturnLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG returnLength; + + switch (InformationClass) + { + case HandleTableBasicInformation: + { + PPH_HANDLE_TABLE_BASIC_INFORMATION basicInfo = Buffer; + + if (BufferLength == sizeof(PH_HANDLE_TABLE_BASIC_INFORMATION)) + { + basicInfo->Count = HandleTable->Count; + basicInfo->Flags = HandleTable->Flags; + basicInfo->TableLevel = HandleTable->TableValue & PH_HANDLE_TABLE_LEVEL_MASK; + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + + returnLength = sizeof(PH_HANDLE_TABLE_BASIC_INFORMATION); + } + break; + case HandleTableFlagsInformation: + { + PPH_HANDLE_TABLE_FLAGS_INFORMATION flagsInfo = Buffer; + + if (BufferLength == sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION)) + { + flagsInfo->Flags = HandleTable->Flags; + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + + returnLength = sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION); + } + break; + default: + status = STATUS_INVALID_INFO_CLASS; + returnLength = 0; + break; + } + + if (ReturnLength) + *ReturnLength = returnLength; + + return status; +} + +NTSTATUS PhSetInformationHandleTable( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass, + _In_reads_bytes_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength + ) +{ + NTSTATUS status = STATUS_SUCCESS; + + switch (InformationClass) + { + case HandleTableFlagsInformation: + { + PPH_HANDLE_TABLE_FLAGS_INFORMATION flagsInfo = Buffer; + ULONG flags; + + if (BufferLength == sizeof(PH_HANDLE_TABLE_FLAGS_INFORMATION)) + { + flags = flagsInfo->Flags; + + if ((flags & PH_HANDLE_TABLE_VALID_FLAGS) == flags) + HandleTable->Flags = flags; + else + status = STATUS_INVALID_PARAMETER; + } + else + { + status = STATUS_INFO_LENGTH_MISMATCH; + } + } + break; + default: + status = STATUS_INVALID_INFO_CLASS; + } + + return status; +} + +PPH_HANDLE_TABLE_ENTRY PhpAllocateHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Out_ PULONG HandleValue + ) +{ + PPH_HANDLE_TABLE_ENTRY entry; + ULONG freeValue; + ULONG lockIndex; + ULONG nextFreeValue; + ULONG oldFreeValue; + BOOLEAN result; + + while (TRUE) + { + freeValue = HandleTable->FreeValue; + + while (freeValue == PH_HANDLE_VALUE_INVALID) + { + PhAcquireQueuedLockExclusive(&HandleTable->Lock); + + // Check again to see if we have free handles. + + freeValue = HandleTable->FreeValue; + + if (freeValue != PH_HANDLE_VALUE_INVALID) + { + PhReleaseQueuedLockExclusive(&HandleTable->Lock); + break; + } + + // Move handles from the alt. free list to the main free list, and check again. + + freeValue = PhpMoveFreeHandleTableEntries(HandleTable); + + if (freeValue != PH_HANDLE_VALUE_INVALID) + { + PhReleaseQueuedLockExclusive(&HandleTable->Lock); + break; + } + + result = PhpAllocateMoreHandleTableEntries(HandleTable, TRUE); + + PhReleaseQueuedLockExclusive(&HandleTable->Lock); + + freeValue = HandleTable->FreeValue; + + // Note that PhpAllocateMoreHandleTableEntries only returns FALSE if it failed to + // allocate memory. Success does not guarantee a free handle to be allocated, as they + // may have been all used up (however unlikely) when we reach this point. Success simply + // means to retry the allocation using the fast path. + + if (!result && freeValue == PH_HANDLE_VALUE_INVALID) + return NULL; + } + + entry = PhpLookupHandleTableEntry(HandleTable, freeValue); + lockIndex = PH_HANDLE_TABLE_LOCK_INDEX(freeValue); + + // To avoid the ABA problem, we would ideally have one queued lock per handle table entry. + // That would make the overhead too large, so instead there is a fixed number of locks, + // indexed by the handle value (mod no. locks). + + // Possibilities at this point: + // 1. freeValue != A (our copy), but the other thread has freed A, so FreeValue = A. No ABA + // problem since freeValue != A. + // 2. freeValue != A, and FreeValue != A. No ABA problem. + // 3. freeValue = A, and the other thread has freed A, so FreeValue = A. No ABA problem + // since we haven't read NextFreeValue yet. + // 4. freeValue = A, and FreeValue != A. No problem if this stays the same later, as the CAS + // will take care of it. + + PhpLockHandleTableShared(HandleTable, lockIndex); + + if (HandleTable->FreeValue != freeValue) + { + PhpUnlockHandleTableShared(HandleTable, lockIndex); + continue; + } + + MemoryBarrier(); + + nextFreeValue = entry->NextFreeValue; + + // Possibilities/non-possibilities at this point: + // 1. freeValue != A (our copy), but the other thread has freed A, so FreeValue = A. This is + // actually impossible since we have acquired the lock on A and the free code checks that + // and uses the alt. free list instead. + // 2. freeValue != A, and FreeValue != A. No ABA problem. + // 3. freeValue = A, and the other thread has freed A, so FreeValue = A. Impossible like + // above. This is *the* ABA problem which we have now prevented. + // 4. freeValue = A, and FreeValue != A. CAS will take care of it. + + oldFreeValue = _InterlockedCompareExchange( + &HandleTable->FreeValue, + nextFreeValue, + freeValue + ); + + PhpUnlockHandleTableShared(HandleTable, lockIndex); + + if (oldFreeValue == freeValue) + break; + } + + _InterlockedIncrement((PLONG)&HandleTable->Count); + + *HandleValue = freeValue; + + return entry; +} + +VOID PhpFreeHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG HandleValue, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ) +{ + PULONG freeList; + ULONG flags; + ULONG oldValue; + + _InterlockedDecrement((PLONG)&HandleTable->Count); + + flags = HandleTable->Flags; + + // Choose the free list to use depending on whether someone is popping from the main free list + // (see PhpAllocateHandleTableEntry for details). We always use the alt. free list if strict + // FIFO is enabled. + if (!(flags & PH_HANDLE_TABLE_STRICT_FIFO) && + PhTryAcquireReleaseQueuedLockExclusive( + &HandleTable->Locks[PH_HANDLE_TABLE_LOCK_INDEX(HandleValue)])) + { + freeList = &HandleTable->FreeValue; + } + else + { + freeList = &HandleTable->FreeValueAlt; + } + + while (TRUE) + { + oldValue = *freeList; + HandleTableEntry->NextFreeValue = oldValue; + + if (_InterlockedCompareExchange( + freeList, + HandleValue, + oldValue + ) == oldValue) + { + break; + } + } +} + +BOOLEAN PhpAllocateMoreHandleTableEntries( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ BOOLEAN Initialize + ) +{ + ULONG_PTR tableValue; + ULONG tableLevel; + PPH_HANDLE_TABLE_ENTRY table0; + PPH_HANDLE_TABLE_ENTRY *table1; + PPH_HANDLE_TABLE_ENTRY **table2; + ULONG i; + ULONG j; + ULONG oldNextValue; + ULONG freeValue; + + // Get a pointer to the table, and its level. + + tableValue = HandleTable->TableValue; + tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK; + tableValue -= tableLevel; + + switch (tableLevel) + { + case 0: + { + // Create a level 1 table. + + table1 = PhpCreateHandleTableLevel1(HandleTable); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table1) + return FALSE; +#endif + + // Create a new level 0 table and move the existing level into the new level 1 table. + + table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table0) + { + PhpFreeHandleTableLevel1(table1); + return FALSE; + } +#endif + + table1[0] = (PPH_HANDLE_TABLE_ENTRY)tableValue; + table1[1] = table0; + + tableValue = (ULONG_PTR)table1 | 1; + //_InterlockedExchangePointer((PVOID *)&HandleTable->TableValue, (PVOID)tableValue); + HandleTable->TableValue = tableValue; + } + break; + case 1: + { + table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue; + + // Determine whether we need to create a new level 0 table or create a level 2 table. + + i = HandleTable->NextValue / PH_HANDLE_TABLE_LEVEL_ENTRIES; + + if (i < PH_HANDLE_TABLE_LEVEL_ENTRIES) + { + table0 = PhpCreateHandleTableLevel0(HandleTable, TRUE); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table0) + return FALSE; +#endif + + //_InterlockedExchangePointer((PVOID *)&table1[i], table0); + table1[i] = table0; + } + else + { + // Create a level 2 table. + + table2 = PhpCreateHandleTableLevel2(HandleTable); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table2) + return FALSE; +#endif + + // Create a new level 1 table and move the existing level into the new level 2 + // table. + + table1 = PhpCreateHandleTableLevel1(HandleTable); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table1) + { + PhpFreeHandleTableLevel2(table2); + return FALSE; + } +#endif + + table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table0) + { + PhpFreeHandleTableLevel1(table1); + PhpFreeHandleTableLevel2(table2); + return FALSE; + } +#endif + + table1[0] = table0; + + table2[0] = (PPH_HANDLE_TABLE_ENTRY *)tableValue; + table2[1] = table1; + + tableValue = (ULONG_PTR)table2 | 2; + //_InterlockedExchangePointer((PVOID *)&HandleTable->TableValue, (PVOID)tableValue); + HandleTable->TableValue = tableValue; + } + } + break; + case 2: + { + table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue; + + i = HandleTable->NextValue / + (PH_HANDLE_TABLE_LEVEL_ENTRIES * PH_HANDLE_TABLE_LEVEL_ENTRIES); + // i contains an index into the level 2 table, of the containing level 1 table. + + // Check if we have exceeded the maximum number of handles. + + if (i >= PH_HANDLE_TABLE_LEVEL_ENTRIES) + return FALSE; + + // Check if we should create a new level 0 table or a new level 2 table. + if (table2[i]) + { + table0 = PhpCreateHandleTableLevel0(HandleTable, Initialize); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table0) + return FALSE; +#endif + + // Same as j = HandleTable->NextValue % (no. entries * no. entries), but we already + // calculated i so just use it. + j = HandleTable->NextValue - i * + (PH_HANDLE_TABLE_LEVEL_ENTRIES * PH_HANDLE_TABLE_LEVEL_ENTRIES); + j /= PH_HANDLE_TABLE_LEVEL_ENTRIES; + // j now contains an index into the level 1 table, of the containing level 0 table + // (the one which was created). + + //_InterlockedExchangePointer((PVOID *)&table2[i][j], table0); + table2[i][j] = table0; + } + else + { + table1 = PhpCreateHandleTableLevel1(HandleTable); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table1) + return FALSE; +#endif + + table0 = PhpCreateHandleTableLevel0(HandleTable, TRUE); + +#ifdef PH_HANDLE_TABLE_SAFE + if (!table0) + { + PhpFreeHandleTableLevel1(table1); + return FALSE; + } +#endif + + table1[0] = table0; + + //_InterlockedExchangePointer((PVOID *)&table2[i], table1); + table2[i] = table1; + } + } + break; + default: + ASSUME_NO_DEFAULT; + } + + // In each of the cases above, we allocated one additional level 0 table. + oldNextValue = _InterlockedExchangeAdd( + (PLONG)&HandleTable->NextValue, + PH_HANDLE_TABLE_LEVEL_ENTRIES + ); + + if (Initialize) + { + // No ABA problem since these are new handles being pushed. + + while (TRUE) + { + freeValue = HandleTable->FreeValue; + table0[PH_HANDLE_TABLE_LEVEL_ENTRIES - 1].NextFreeValue = freeValue; + + if (_InterlockedCompareExchange( + &HandleTable->FreeValue, + oldNextValue, + freeValue + ) == freeValue) + { + break; + } + } + } + + return TRUE; +} + +PPH_HANDLE_TABLE_ENTRY PhpLookupHandleTableEntry( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG HandleValue + ) +{ + ULONG_PTR tableValue; + ULONG tableLevel; + PPH_HANDLE_TABLE_ENTRY table0; + PPH_HANDLE_TABLE_ENTRY *table1; + PPH_HANDLE_TABLE_ENTRY **table2; + PPH_HANDLE_TABLE_ENTRY entry; + + if (HandleValue >= HandleTable->NextValue) + return NULL; + + // Get a pointer to the table, and its level. + + tableValue = HandleTable->TableValue; + tableLevel = tableValue & PH_HANDLE_TABLE_LEVEL_MASK; + tableValue -= tableLevel; + + // No additional checking needed; aleady checked against NextValue. + + switch (tableLevel) + { + case 0: + { + table0 = (PPH_HANDLE_TABLE_ENTRY)tableValue; + entry = &table0[HandleValue]; + } + break; + case 1: + { + table1 = (PPH_HANDLE_TABLE_ENTRY *)tableValue; + table0 = table1[PH_HANDLE_VALUE_LEVEL1_U(HandleValue)]; + entry = &table0[PH_HANDLE_VALUE_LEVEL0(HandleValue)]; + } + break; + case 2: + { + table2 = (PPH_HANDLE_TABLE_ENTRY **)tableValue; + table1 = table2[PH_HANDLE_VALUE_LEVEL2_U(HandleValue)]; + table0 = table1[PH_HANDLE_VALUE_LEVEL1(HandleValue)]; + entry = &table0[PH_HANDLE_VALUE_LEVEL0(HandleValue)]; + } + break; + default: + ASSUME_NO_DEFAULT; + } + + return entry; +} + +ULONG PhpMoveFreeHandleTableEntries( + _Inout_ PPH_HANDLE_TABLE HandleTable + ) +{ + ULONG freeValueAlt; + ULONG flags; + ULONG i; + ULONG index; + ULONG nextIndex; + ULONG lastIndex; + PPH_HANDLE_TABLE_ENTRY entry; + PPH_HANDLE_TABLE_ENTRY firstEntry; + ULONG count; + ULONG freeValue; + + // Remove all entries from the alt. free list. + freeValueAlt = _InterlockedExchange(&HandleTable->FreeValueAlt, PH_HANDLE_VALUE_INVALID); + + if (freeValueAlt == PH_HANDLE_VALUE_INVALID) + { + // No handles on the alt. free list. + return PH_HANDLE_VALUE_INVALID; + } + + // Avoid the ABA problem by testing all locks (see PhpAllocateHandleTableEntry for details). + // Unlike in PhpFreeHandleTableEntry we have no "alternative" list, so we must allow blocking. + for (i = 0; i < PH_HANDLE_TABLE_LOCKS; i++) + PhAcquireReleaseQueuedLockExclusive(&HandleTable->Locks[i]); + + flags = HandleTable->Flags; + + if (!(flags & PH_HANDLE_TABLE_STRICT_FIFO)) + { + // Shortcut: if there are no entries in the main free list and we don't need to reverse the + // chain, just return. + if (_InterlockedCompareExchange( + &HandleTable->FreeValue, + freeValueAlt, + PH_HANDLE_VALUE_INVALID + ) == PH_HANDLE_VALUE_INVALID) + return freeValueAlt; + } + + // Reverse the chain (even if strict FIFO is off; we have to traverse the list to find the last + // entry, so we might as well reverse it along the way). + + index = freeValueAlt; + lastIndex = PH_HANDLE_VALUE_INVALID; + count = 0; + + while (TRUE) + { + entry = PhpLookupHandleTableEntry(HandleTable, index); + count++; + + if (lastIndex == PH_HANDLE_VALUE_INVALID) + firstEntry = entry; + + nextIndex = entry->NextFreeValue; + entry->NextFreeValue = lastIndex; + lastIndex = index; + + if (nextIndex == PH_HANDLE_VALUE_INVALID) + break; + + index = nextIndex; + } + + // Note that firstEntry actually contains the last free entry, since we reversed the list. + // Similarly index/lastIndex both contain the index of the first free entry. + + // Push the entries onto the free list. + while (TRUE) + { + freeValue = HandleTable->FreeValue; + firstEntry->NextFreeValue = freeValue; + + if (_InterlockedCompareExchange( + &HandleTable->FreeValue, + index, + freeValue + ) == freeValue) + break; + } + + // Force expansion if we don't have enough free handles. + if ( + (flags & PH_HANDLE_TABLE_STRICT_FIFO) && + count < PH_HANDLE_TABLE_FREE_COUNT + ) + { + index = PH_HANDLE_VALUE_INVALID; + } + + return index; +} + +PPH_HANDLE_TABLE_ENTRY PhpCreateHandleTableLevel0( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ BOOLEAN Initialize + ) +{ + PPH_HANDLE_TABLE_ENTRY table; + PPH_HANDLE_TABLE_ENTRY entry; + ULONG baseValue; + ULONG i; + +#ifdef PH_HANDLE_TABLE_SAFE + __try + { + table = PhAllocateFromFreeList(&PhHandleTableLevel0FreeList); + } + __except (SIMPLE_EXCEPTION_FILTER(GetExceptionCode() == STATUS_NO_MEMORY)) + { + return NULL; + } +#else + table = PhAllocateFromFreeList(&PhHandleTableLevel0FreeList); +#endif + + if (Initialize) + { + entry = &table[0]; + baseValue = HandleTable->NextValue; + + for (i = baseValue + 1; i < baseValue + PH_HANDLE_TABLE_LEVEL_ENTRIES; i++) + { + entry->Value = PH_HANDLE_TABLE_ENTRY_FREE; + entry->NextFreeValue = i; + entry++; + } + + entry->Value = PH_HANDLE_TABLE_ENTRY_FREE; + entry->NextFreeValue = PH_HANDLE_VALUE_INVALID; + } + + return table; +} + +VOID PhpFreeHandleTableLevel0( + _In_ PPH_HANDLE_TABLE_ENTRY Table + ) +{ + PhFreeToFreeList(&PhHandleTableLevel0FreeList, Table); +} + +PPH_HANDLE_TABLE_ENTRY *PhpCreateHandleTableLevel1( + _In_ PPH_HANDLE_TABLE HandleTable + ) +{ + PPH_HANDLE_TABLE_ENTRY *table; + +#ifdef PH_HANDLE_TABLE_SAFE + __try + { + table = PhAllocateFromFreeList(&PhHandleTableLevel1FreeList); + } + __except (SIMPLE_EXCEPTION_FILTER(GetExceptionCode() == STATUS_NO_MEMORY)) + { + return NULL; + } +#else + table = PhAllocateFromFreeList(&PhHandleTableLevel1FreeList); +#endif + + memset(table, 0, sizeof(PPH_HANDLE_TABLE_ENTRY) * PH_HANDLE_TABLE_LEVEL_ENTRIES); + + return table; +} + +VOID PhpFreeHandleTableLevel1( + _In_ PPH_HANDLE_TABLE_ENTRY *Table + ) +{ + PhFreeToFreeList(&PhHandleTableLevel1FreeList, Table); +} + +PPH_HANDLE_TABLE_ENTRY **PhpCreateHandleTableLevel2( + _In_ PPH_HANDLE_TABLE HandleTable + ) +{ + PPH_HANDLE_TABLE_ENTRY **table; + +#ifdef PH_HANDLE_TABLE_SAFE + table = PhAllocateSafe(sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES); + + if (!table) + return NULL; +#else + table = PhAllocate(sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES); +#endif + + memset(table, 0, sizeof(PPH_HANDLE_TABLE_ENTRY *) * PH_HANDLE_TABLE_LEVEL_ENTRIES); + + return table; +} + +VOID PhpFreeHandleTableLevel2( + _In_ PPH_HANDLE_TABLE_ENTRY **Table + ) +{ + PhFree(Table); +} diff --git a/phlib/hexedit.c b/phlib/hexedit.c new file mode 100644 index 0000000..295171d --- /dev/null +++ b/phlib/hexedit.c @@ -0,0 +1,1865 @@ +/* + * Process Hacker - + * hex editor control + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +// Code originally from http://www.codeguru.com/Cpp/controls/editctrl/article.php/c539 + +BOOLEAN PhHexEditInitialization( + VOID + ) +{ + WNDCLASSEX c = { sizeof(c) }; + + c.style = CS_GLOBALCLASS; + c.lpfnWndProc = PhpHexEditWndProc; + c.cbClsExtra = 0; + c.cbWndExtra = sizeof(PVOID); + c.hInstance = PhLibImageBase; + c.hIcon = NULL; + c.hCursor = LoadCursor(NULL, IDC_ARROW); + c.hbrBackground = NULL; + c.lpszMenuName = NULL; + c.lpszClassName = PH_HEXEDIT_CLASSNAME; + c.hIconSm = NULL; + + if (!RegisterClassEx(&c)) + return FALSE; + + return TRUE; +} + +VOID PhpCreateHexEditContext( + _Out_ PPHP_HEXEDIT_CONTEXT *Context + ) +{ + PPHP_HEXEDIT_CONTEXT context; + + context = PhAllocate(sizeof(PHP_HEXEDIT_CONTEXT)); + memset(context, 0, sizeof(PHP_HEXEDIT_CONTEXT)); // important, set NullWidth to 0 + + context->Data = NULL; + context->Length = 0; + context->TopIndex = 0; + context->BytesPerRow = 16; + context->LinesPerPage = 1; + + context->ShowHex = TRUE; + context->ShowAscii = TRUE; + context->ShowAddress = TRUE; + context->AddressIsWide = TRUE; + context->AllowLengthChange = FALSE; + + context->AddressOffset = 0; + context->HexOffset = 0; + context->AsciiOffset = 0; + + context->Update = TRUE; + context->NoAddressChange = FALSE; + context->CurrentMode = EDIT_NONE; + + context->EditPosition.x = 0; + context->EditPosition.y = 0; + context->CurrentAddress = 0; + context->HalfPage = TRUE; + + context->SelStart = -1; + context->SelEnd = -1; + + *Context = context; +} + +VOID PhpFreeHexEditContext( + _In_ _Post_invalid_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (!Context->UserBuffer && Context->Data) PhFree(Context->Data); + if (Context->CharBuffer) PhFree(Context->CharBuffer); + if (Context->Font) DeleteObject(Context->Font); + PhFree(Context); +} + +LRESULT CALLBACK PhpHexEditWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPHP_HEXEDIT_CONTEXT context; + + context = (PPHP_HEXEDIT_CONTEXT)GetWindowLongPtr(hwnd, 0); + + if (uMsg == WM_CREATE) + { + PhpCreateHexEditContext(&context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)context); + } + + if (!context) + return DefWindowProc(hwnd, uMsg, wParam, lParam); + + switch (uMsg) + { + case WM_CREATE: + { + context->Font = CreateFont(-(LONG)PhMultiplyDivide(12, PhGlobalDpi, 96), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Courier New"); + } + break; + case WM_DESTROY: + { + PhpFreeHexEditContext(context); + } + break; + case WM_PAINT: + { + PAINTSTRUCT paintStruct; + HDC hdc; + + if (hdc = BeginPaint(hwnd, &paintStruct)) + { + PhpHexEditOnPaint(hwnd, context, &paintStruct, hdc); + EndPaint(hwnd, &paintStruct); + } + } + break; + case WM_SIZE: + { + PhpHexEditUpdateMetrics(hwnd, context, FALSE, NULL); + } + break; + case WM_SETFOCUS: + { + if (context->Data && !PhpHexEditHasSelected(context)) + { + if (context->EditPosition.x == 0 && context->ShowAddress) + PhpHexEditCreateAddressCaret(hwnd, context); + else + PhpHexEditCreateEditCaret(hwnd, context); + + SetCaretPos(context->EditPosition.x, context->EditPosition.y); + ShowCaret(hwnd); + } + } + break; + case WM_KILLFOCUS: + { + DestroyCaret(); + } + break; + case WM_VSCROLL: + { + SHORT scrollRequest = LOWORD(wParam); + LONG currentPosition; + LONG originalTopIndex; + SCROLLINFO scrollInfo = { sizeof(scrollInfo) }; + + originalTopIndex = context->TopIndex; + + scrollInfo.fMask = SIF_TRACKPOS; + GetScrollInfo(hwnd, SB_VERT, &scrollInfo); + currentPosition = scrollInfo.nTrackPos; + + if (context->Data) + { + LONG mult; + + mult = context->LinesPerPage * context->BytesPerRow; + + switch (scrollRequest) + { + case SB_LINEDOWN: + if (context->TopIndex < context->Length - mult) + { + context->TopIndex += context->BytesPerRow; + REDRAW_WINDOW(hwnd); + } + break; + case SB_LINEUP: + if (context->TopIndex >= context->BytesPerRow) + { + context->TopIndex -= context->BytesPerRow; + REDRAW_WINDOW(hwnd); + } + break; + case SB_PAGEDOWN: + if (context->TopIndex < context->Length - mult) + { + context->TopIndex += mult; + + if (context->TopIndex > context->Length - mult) + context->TopIndex = context->Length - mult; + + REDRAW_WINDOW(hwnd); + } + break; + case SB_PAGEUP: + if (context->TopIndex > 0) + { + context->TopIndex -= mult; + + if (context->TopIndex < 0) + context->TopIndex = 0; + + REDRAW_WINDOW(hwnd); + } + break; + case SB_THUMBTRACK: + context->TopIndex = currentPosition * context->BytesPerRow; + REDRAW_WINDOW(hwnd); + break; + } + + SetScrollPos(hwnd, SB_VERT, context->TopIndex / context->BytesPerRow, TRUE); + + if (!context->NoAddressChange && FALSE) // this behaviour sucks, so just leave it out + context->CurrentAddress += context->TopIndex - originalTopIndex; + + PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress); + } + } + break; + case WM_MOUSEWHEEL: + { + SHORT wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); + + if (context->Data) + { + ULONG wheelScrollLines; + + if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0)) + wheelScrollLines = 3; + + context->TopIndex += context->BytesPerRow * (LONG)wheelScrollLines * -wheelDelta / WHEEL_DELTA; + + if (context->TopIndex < 0) + context->TopIndex = 0; + + if (context->Length >= context->LinesPerPage * context->BytesPerRow) + { + if (context->TopIndex > context->Length - context->LinesPerPage * context->BytesPerRow) + context->TopIndex = context->Length - context->LinesPerPage * context->BytesPerRow; + } + + REDRAW_WINDOW(hwnd); + + SetScrollPos(hwnd, SB_VERT, context->TopIndex / context->BytesPerRow, TRUE); + + PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress); + } + } + break; + case WM_GETDLGCODE: + if (wParam != VK_ESCAPE) + return DLGC_WANTALLKEYS; + break; + case WM_ERASEBKGND: + return 1; + case WM_LBUTTONDOWN: + { + ULONG flags = (ULONG)wParam; + POINT cursorPos; + + cursorPos.x = (LONG)(SHORT)LOWORD(lParam); + cursorPos.y = (LONG)(SHORT)HIWORD(lParam); + + SetFocus(hwnd); + + if (context->Data) + { + POINT point; + + if (wParam & MK_SHIFT) + context->SelStart = context->CurrentAddress; + + PhpHexEditCalculatePosition(hwnd, context, cursorPos.x, cursorPos.y, &point); + + if (point.x > -1) + { + context->EditPosition = point; + + point.x *= context->NullWidth; + point.y *= context->LineHeight; + + if (point.x == 0 && context->ShowAddress) + PhpHexEditCreateAddressCaret(hwnd, context); + else + PhpHexEditCreateEditCaret(hwnd, context); + + SetCaretPos(point.x, point.y); + + if (flags & MK_SHIFT) + { + context->SelEnd = context->CurrentAddress; + + if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW) + context->SelEnd++; + + REDRAW_WINDOW(hwnd); + } + } + + if (!(flags & MK_SHIFT)) + { + if (DragDetect(hwnd, cursorPos)) + { + context->SelStart = context->CurrentAddress; + context->SelEnd = context->SelStart; + SetCapture(hwnd); + context->HasCapture = TRUE; + } + else + { + BOOLEAN selected; + + selected = context->SelStart != -1; + context->SelStart = -1; + context->SelEnd = -1; + + if (selected) + REDRAW_WINDOW(hwnd); + } + } + + if (!PhpHexEditHasSelected(context)) + ShowCaret(hwnd); + } + } + break; + case WM_LBUTTONUP: + { + if (context->HasCapture && PhpHexEditHasSelected(context)) + ReleaseCapture(); + + context->HasCapture = FALSE; + } + break; + case WM_MOUSEMOVE: + { + ULONG flags = (ULONG)wParam; + POINT cursorPos; + + cursorPos.x = (LONG)(SHORT)LOWORD(lParam); + cursorPos.y = (LONG)(SHORT)HIWORD(lParam); + + if ( + context->Data && + context->HasCapture && + context->SelStart != -1 + ) + { + RECT rect; + POINT point; + ULONG oldSelEnd; + + // User is dragging. + + GetClientRect(hwnd, &rect); + + if (!PtInRect(&rect, cursorPos)) + { + if (cursorPos.y < 0) + { + SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); + cursorPos.y = 0; + } + else if (cursorPos.y > rect.bottom) + { + SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); + cursorPos.y = rect.bottom - 1; + } + } + + oldSelEnd = context->SelEnd; + PhpHexEditCalculatePosition(hwnd, context, cursorPos.x, cursorPos.y, &point); + + if (point.x > -1) + { + context->SelEnd = context->CurrentAddress; + + if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW) + context->SelEnd++; + } + + if (PhpHexEditHasSelected(context)) + DestroyCaret(); + + if (context->SelEnd != oldSelEnd) + REDRAW_WINDOW(hwnd); + } + } + break; + case WM_CHAR: + { + ULONG c = (ULONG)wParam; + + if (!context->Data) + goto DefaultHandler; + if (c == '\t') + goto DefaultHandler; + + if (GetKeyState(VK_CONTROL) < 0) + { + switch (c) + { + case 0x3: + if (PhpHexEditHasSelected(context)) + PhpHexEditCopyEdit(hwnd, context); + goto DefaultHandler; + case 0x16: + PhpHexEditPasteEdit(hwnd, context); + goto DefaultHandler; + case 0x18: + if (PhpHexEditHasSelected(context)) + PhpHexEditCutEdit(hwnd, context); + goto DefaultHandler; + case 0x1a: + PhpHexEditUndoEdit(hwnd, context); + goto DefaultHandler; + } + } + + // Disallow editing beyond the end of the data. + if (context->CurrentAddress >= context->Length) + goto DefaultHandler; + + if (c == 0x8) + { + if (context->CurrentAddress != 0) + { + context->CurrentAddress--; + PhpHexEditSelDelete(hwnd, context, context->CurrentAddress, context->CurrentAddress + 1); + PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress); + REDRAW_WINDOW(hwnd); + } + + goto DefaultHandler; + } + + PhpHexEditSetSel(hwnd, context, -1, -1); + + switch (context->CurrentMode) + { + case EDIT_NONE: + goto DefaultHandler; + case EDIT_HIGH: + case EDIT_LOW: + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) + { + ULONG b = c - '0'; + + if (b > 9) + b = 10 + c - 'a'; + + if (context->CurrentMode == EDIT_HIGH) + { + context->Data[context->CurrentAddress] = + (UCHAR)((context->Data[context->CurrentAddress] & 0x0f) | (b << 4)); + } + else + { + context->Data[context->CurrentAddress] = + (UCHAR)((context->Data[context->CurrentAddress] & 0xf0) | b); + } + + PhpHexEditMove(hwnd, context, 1, 0); + } + break; + case EDIT_ASCII: + context->Data[context->CurrentAddress] = (UCHAR)c; + PhpHexEditMove(hwnd, context, 1, 0); + break; + } + + REDRAW_WINDOW(hwnd); + } + break; + case WM_KEYDOWN: + { + ULONG vk = (ULONG)wParam; + BOOLEAN shift = GetKeyState(VK_SHIFT) < 0; + BOOLEAN oldNoAddressChange = context->NoAddressChange; + BOOLEAN noScrollIntoView = FALSE; + + context->NoAddressChange = TRUE; + + switch (vk) + { + case VK_DOWN: + if (context->CurrentMode != EDIT_NONE) + { + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + PhpHexEditMove(hwnd, context, 0, 1); + context->SelEnd = context->CurrentAddress; + + if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW) + context->SelEnd++; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + PhpHexEditMove(hwnd, context, 0, 1); + noScrollIntoView = TRUE; + } + else + { + PhpHexEditMove(hwnd, context, 0, 1); + } + break; + case VK_UP: + if (context->CurrentMode != EDIT_NONE) + { + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + PhpHexEditMove(hwnd, context, 0, -1); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + PhpHexEditMove(hwnd, context, 0, -1); + noScrollIntoView = TRUE; + } + else + { + PhpHexEditMove(hwnd, context, 0, -1); + } + break; + case VK_LEFT: + if (context->CurrentMode != EDIT_NONE) + { + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + PhpHexEditMove(hwnd, context, -1, 0); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + PhpHexEditMove(hwnd, context, -1, 0); + noScrollIntoView = TRUE; + } + break; + case VK_RIGHT: + if (context->CurrentMode != EDIT_NONE) + { + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + PhpHexEditMove(hwnd, context, 1, 0); + context->SelEnd = context->CurrentAddress; + + if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW) + context->SelEnd++; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + PhpHexEditMove(hwnd, context, 1, 0); + noScrollIntoView = TRUE; + } + break; + case VK_PRIOR: + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0); + PhpHexEditMove(hwnd, context, 0, 0); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0); + PhpHexEditMove(hwnd, context, 0, 0); + noScrollIntoView = TRUE; + break; + case VK_NEXT: + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); + PhpHexEditMove(hwnd, context, 0, 0); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); + PhpHexEditMove(hwnd, context, 0, 0); + noScrollIntoView = TRUE; + break; + case VK_HOME: + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + if (GetKeyState(VK_CONTROL) < 0) + { + SendMessage(hwnd, WM_VSCROLL, SB_THUMBTRACK, 0); + } + else + { + // Round down. + context->CurrentAddress /= context->BytesPerRow; + context->CurrentAddress *= context->BytesPerRow; + } + + PhpHexEditMove(hwnd, context, 0, 0); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + if (GetKeyState(VK_CONTROL) < 0) + { + SendMessage(hwnd, WM_VSCROLL, SB_THUMBTRACK, 0); + context->CurrentAddress = 0; + } + else + { + // Round down. + context->CurrentAddress /= context->BytesPerRow; + context->CurrentAddress *= context->BytesPerRow; + } + + PhpHexEditMove(hwnd, context, 0, 0); + noScrollIntoView = TRUE; + + break; + case VK_END: + if (shift) + { + if (!PhpHexEditHasSelected(context)) + context->SelStart = context->CurrentAddress; + + if (GetKeyState(VK_CONTROL) < 0) + { + context->CurrentAddress = context->Length - 1; + SendMessage(hwnd, WM_VSCROLL, + MAKEWPARAM(SB_THUMBTRACK, ((context->Length + (context->BytesPerRow / 2)) / context->BytesPerRow) - context->LinesPerPage), + 0); + } + else + { + context->CurrentAddress /= context->BytesPerRow; + context->CurrentAddress *= context->BytesPerRow; + context->CurrentAddress += context->BytesPerRow - 1; + + if (context->CurrentAddress > context->Length) + context->CurrentAddress = context->Length - 1; + } + + PhpHexEditMove(hwnd, context, 0, 0); + context->SelEnd = context->CurrentAddress; + + REDRAW_WINDOW(hwnd); + break; + } + else + { + PhpHexEditSetSel(hwnd, context, -1, -1); + } + + if (GetKeyState(VK_CONTROL) < 0) + { + context->CurrentAddress = context->Length - 1; + + if (context->HalfPage) + { + SendMessage(hwnd, WM_VSCROLL, 0, 0); + } + else + { + SendMessage(hwnd, WM_VSCROLL, + MAKEWPARAM(SB_THUMBTRACK, ((context->Length + (context->BytesPerRow / 2)) / context->BytesPerRow) - context->LinesPerPage), + 0); + } + } + else + { + context->CurrentAddress /= context->BytesPerRow; + context->CurrentAddress *= context->BytesPerRow; + context->CurrentAddress += context->BytesPerRow - 1; + + if (context->CurrentAddress > context->Length) + context->CurrentAddress = context->Length - 1; + } + + PhpHexEditMove(hwnd, context, 0, 0); + noScrollIntoView = TRUE; + + break; + case VK_INSERT: + PhpHexEditSelInsert(hwnd, context, context->CurrentAddress, + max(1, context->SelEnd - context->SelStart)); + REDRAW_WINDOW(hwnd); + break; + case VK_DELETE: + if (PhpHexEditHasSelected(context)) + { + PhpHexEditClearEdit(hwnd, context); + } + else + { + PhpHexEditSelDelete(hwnd, context, context->CurrentAddress, context->CurrentAddress + 1); + REDRAW_WINDOW(hwnd); + } + break; + case '\t': + switch (context->CurrentMode) + { + case EDIT_NONE: + context->CurrentMode = EDIT_HIGH; + break; + case EDIT_HIGH: + case EDIT_LOW: + context->CurrentMode = EDIT_ASCII; + break; + case EDIT_ASCII: + context->CurrentMode = EDIT_HIGH; + break; + } + + PhpHexEditMove(hwnd, context, 0, 0); + + break; + } + + // Scroll into view if not in view. + if ( + !noScrollIntoView && + (context->CurrentAddress < context->TopIndex || + context->CurrentAddress >= context->TopIndex + context->LinesPerPage * context->BytesPerRow) + ) + { + PhpHexEditScrollTo(hwnd, context, context->CurrentAddress); + } + + context->NoAddressChange = oldNoAddressChange; + } + break; + case HEM_SETBUFFER: + { + PhpHexEditSetBuffer(hwnd, context, (PUCHAR)lParam, (ULONG)wParam); + } + return TRUE; + case HEM_SETDATA: + { + PhpHexEditSetData(hwnd, context, (PUCHAR)lParam, (ULONG)wParam); + } + return TRUE; + case HEM_GETBUFFER: + { + PULONG length = (PULONG)wParam; + + if (length) + *length = context->Length; + + return (LPARAM)context->Data; + } + case HEM_SETSEL: + { + LONG selStart = (LONG)wParam; + LONG selEnd = (LONG)lParam; + + if (selStart <= 0) + return FALSE; + if (selEnd > context->Length) + return FALSE; + + PhpHexEditScrollTo(hwnd, context, selStart); + PhpHexEditSetSel(hwnd, context, selStart, selEnd); + PhpHexEditRepositionCaret(hwnd, context, selStart); + REDRAW_WINDOW(hwnd); + } + return TRUE; + case HEM_SETEDITMODE: + { + context->CurrentMode = (LONG)wParam; + REDRAW_WINDOW(hwnd); + } + return TRUE; + case HEM_SETBYTESPERROW: + { + LONG bytesPerRow = (LONG)wParam; + + if (bytesPerRow >= 4) + { + context->BytesPerRow = bytesPerRow; + PhpHexEditUpdateMetrics(hwnd, context, TRUE, NULL); + PhpHexEditUpdateScrollbars(hwnd, context); + PhpHexEditScrollTo(hwnd, context, context->CurrentAddress); + PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress); + REDRAW_WINDOW(hwnd); + } + } + return TRUE; + } + +DefaultHandler: + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +FORCEINLINE VOID PhpPrintHex( + _In_ HDC hdc, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _Inout_ PWCHAR Buffer, + _In_ UCHAR Byte, + _Inout_ PLONG X, + _Inout_ PLONG Y, + _Inout_ PULONG N + ) +{ + PWCHAR p = Buffer; + + TO_HEX(p, Byte); + *p++ = ' '; + TextOut(hdc, *X, *Y, Buffer, 3); + *X += Context->NullWidth * 3; + (*N)++; + + if (*N == Context->BytesPerRow) + { + *N = 0; + *X = Context->HexOffset; + *Y += Context->LineHeight; + } +} + +FORCEINLINE VOID PhpPrintAscii( + _In_ HDC hdc, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ UCHAR Byte, + _Inout_ PLONG X, + _Inout_ PLONG Y, + _Inout_ PULONG N + ) +{ + WCHAR c; + + c = IS_PRINTABLE(Byte) ? Byte : '.'; + TextOut(hdc, *X, *Y, &c, 1); + *X += Context->NullWidth; + (*N)++; + + if (*N == Context->BytesPerRow) + { + *N = 0; + *X = Context->AsciiOffset; + *Y += Context->LineHeight; + } +} + +FORCEINLINE COLORREF GetLighterHighlightColor( + VOID + ) +{ + COLORREF color; + UCHAR r; + UCHAR g; + UCHAR b; + + color = GetSysColor(COLOR_HIGHLIGHT); + r = (UCHAR)color; + g = (UCHAR)(color >> 8); + b = (UCHAR)(color >> 16); + + if (r <= 255 - 64) + r += 64; + else + r = 255; + + if (g <= 255 - 64) + g += 64; + else + g = 255; + + if (b <= 255 - 64) + b += 64; + else + b = 255; + + return RGB(r, g, b); +} + +VOID PhpHexEditUpdateMetrics( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ BOOLEAN UpdateLineHeight, + _In_opt_ HDC hdc + ) +{ + BOOLEAN freeHdc = FALSE; + RECT clientRect; + SIZE size; + + if (!hdc && UpdateLineHeight) + { + hdc = CreateCompatibleDC(hdc); + SelectObject(hdc, Context->Font); + freeHdc = TRUE; + } + + GetClientRect(hwnd, &clientRect); + + if (UpdateLineHeight) + { + GetCharWidth(hdc, '0', '0', &Context->NullWidth); + GetTextExtentPoint32(hdc, L"0", 1, &size); + Context->LineHeight = size.cy; + } + + Context->HexOffset = Context->ShowAddress ? (Context->AddressIsWide ? Context->NullWidth * 9 : Context->NullWidth * 5) : 0; + Context->AsciiOffset = Context->HexOffset + (Context->ShowHex ? (Context->BytesPerRow * 3 * Context->NullWidth) : 0); + + if (Context->LineHeight != 0) + { + Context->LinesPerPage = clientRect.bottom / Context->LineHeight; + Context->HalfPage = FALSE; + + if (Context->LinesPerPage * Context->BytesPerRow > Context->Length) + { + Context->LinesPerPage = (Context->Length + Context->BytesPerRow / 2) / Context->BytesPerRow; + + if (Context->Length % Context->BytesPerRow != 0) + { + Context->HalfPage = TRUE; + Context->LinesPerPage++; + } + } + } + + if (freeHdc && hdc) + DeleteDC(hdc); +} + +VOID PhpHexEditOnPaint( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PAINTSTRUCT *PaintStruct, + _In_ HDC hdc + ) +{ + RECT clientRect; + HDC bufferDc; + HBITMAP bufferBitmap; + HBITMAP oldBufferBitmap; + LONG height; + LONG x; + LONG y; + LONG i; + ULONG requiredBufferLength; + PWCHAR buffer; + + GetClientRect(hwnd, &clientRect); + + bufferDc = CreateCompatibleDC(hdc); + bufferBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom); + oldBufferBitmap = SelectObject(bufferDc, bufferBitmap); + + SetDCBrushColor(bufferDc, GetSysColor(COLOR_WINDOW)); + FillRect(bufferDc, &clientRect, GetStockObject(DC_BRUSH)); + SelectObject(bufferDc, Context->Font); + SetBoundsRect(bufferDc, &clientRect, DCB_DISABLE); + + requiredBufferLength = (max(8, Context->BytesPerRow * 3) + 1) * sizeof(WCHAR); + + if (Context->CharBufferLength < requiredBufferLength) + { + if (Context->CharBuffer) + PhFree(Context->CharBuffer); + + Context->CharBuffer = PhAllocate(requiredBufferLength); + Context->CharBufferLength = requiredBufferLength; + buffer = Context->CharBuffer; + } + + buffer = Context->CharBuffer; + + if (Context->Data) + { + // Get character dimensions. + if (Context->Update) + { + PhpHexEditUpdateMetrics(hwnd, Context, TRUE, bufferDc); + Context->Update = FALSE; + PhpHexEditUpdateScrollbars(hwnd, Context); + } + + height = (clientRect.bottom + Context->LineHeight - 1) / Context->LineHeight * Context->LineHeight; // round up to height + + if (Context->ShowAddress) + { + PH_FORMAT format; + ULONG w; + RECT rect; + + PhInitFormatX(&format, 0); + format.Type |= FormatPadZeros; + format.Width = Context->AddressIsWide ? 8 : 4; + + w = Context->AddressIsWide ? 8 : 4; + + rect = clientRect; + rect.left = Context->AddressOffset; + rect.top = 0; + + for (i = Context->TopIndex; i < Context->Length && rect.top < height; i += Context->BytesPerRow) + { + format.u.Int32 = i; + PhFormatToBuffer(&format, 1, buffer, requiredBufferLength, NULL); + DrawText(bufferDc, buffer, w, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP); + rect.top += Context->LineHeight; + } + } + + if (Context->ShowHex) + { + RECT rect; + LONG n = 0; + + x = Context->HexOffset; + y = 0; + rect = clientRect; + rect.left = x; + rect.top = 0; + + if (Context->SelStart != -1) + { + COLORREF highlightColor; + LONG selStart; + LONG selEnd; + + if (Context->CurrentMode == EDIT_HIGH || Context->CurrentMode == EDIT_LOW) + highlightColor = GetSysColor(COLOR_HIGHLIGHT); + else + highlightColor = GetLighterHighlightColor(); + + selStart = Context->SelStart; + selEnd = Context->SelEnd; + + if (selStart > selEnd) + { + ULONG t; + + t = selEnd; + selEnd = selStart; + selStart = t; + } + + if (selStart >= Context->Length) + selStart = Context->Length - 1; + if (selEnd > Context->Length) + selEnd = Context->Length; + + // Bytes before the selection + + for (i = Context->TopIndex; i < selStart && y < height; i++) + { + PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n); + } + + // Bytes in the selection + + SetTextColor(bufferDc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkColor(bufferDc, highlightColor); + + for (; i < selEnd && i < Context->Length && y < height; i++) + { + PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n); + } + + // Bytes after the selection + + SetTextColor(bufferDc, GetSysColor(COLOR_WINDOWTEXT)); + SetBkColor(bufferDc, GetSysColor(COLOR_WINDOW)); + + for (; i < Context->Length && y < height; i++) + { + PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n); + } + } + else + { + i = Context->TopIndex; + + while (i < Context->Length && rect.top < height) + { + PWCHAR p = buffer; + + for (n = 0; n < Context->BytesPerRow && i < Context->Length; n++) + { + TO_HEX(p, Context->Data[i]); + *p++ = ' '; + i++; + } + + while (n < Context->BytesPerRow) + { + p[0] = ' '; + p[1] = ' '; + p[2] = ' '; + p += 3; + n++; + } + + DrawText(bufferDc, buffer, Context->BytesPerRow * 3, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP); + rect.top += Context->LineHeight; + } + } + } + + if (Context->ShowAscii) + { + RECT rect; + LONG n = 0; + + x = Context->AsciiOffset; + y = 0; + rect = clientRect; + rect.left = x; + rect.top = 0; + + if (Context->SelStart != -1) + { + COLORREF highlightColor; + LONG selStart; + LONG selEnd; + + if (Context->CurrentMode == EDIT_ASCII) + highlightColor = GetSysColor(COLOR_HIGHLIGHT); + else + highlightColor = GetLighterHighlightColor(); + + selStart = Context->SelStart; + selEnd = Context->SelEnd; + + if (selStart > selEnd) + { + LONG t; + + t = selEnd; + selEnd = selStart; + selStart = t; + } + + if (selStart >= Context->Length) + selStart = Context->Length - 1; + if (selEnd > Context->Length) + selEnd = Context->Length; + + // Bytes before the selection + + for (i = Context->TopIndex; i < selStart && y < height; i++) + { + PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n); + } + + // Bytes in the selection + + SetTextColor(bufferDc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + SetBkColor(bufferDc, highlightColor); + + for (; i < selEnd && i < Context->Length && y < height; i++) + { + PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n); + } + + // Bytes after the selection + + SetTextColor(bufferDc, GetSysColor(COLOR_WINDOWTEXT)); + SetBkColor(bufferDc, GetSysColor(COLOR_WINDOW)); + + for (; i < Context->Length && y < height; i++) + { + PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n); + } + } + else + { + i = Context->TopIndex; + + while (i < Context->Length && rect.top < height) + { + PWCHAR p = buffer; + + for (n = 0; n < Context->BytesPerRow && i < Context->Length; n++) + { + *p++ = IS_PRINTABLE(Context->Data[i]) ? Context->Data[i] : '.'; // 1 + i++; + } + + DrawText(bufferDc, buffer, n, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP); + rect.top += Context->LineHeight; + } + } + } + } + + BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, bufferDc, 0, 0, SRCCOPY); + SelectObject(bufferDc, oldBufferBitmap); + DeleteObject(bufferBitmap); + DeleteDC(bufferDc); +} + +VOID PhpHexEditUpdateScrollbars( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + SCROLLINFO si = { sizeof(si) }; + + si.fMask = SIF_ALL; + si.nMin = 0; + si.nMax = (Context->Length / Context->BytesPerRow) - 1; + si.nPage = Context->LinesPerPage; + si.nPos = Context->TopIndex / Context->BytesPerRow; + SetScrollInfo(hwnd, SB_VERT, &si, TRUE); + + if (si.nMax > (LONG)si.nPage) + EnableScrollBar(hwnd, SB_VERT, ESB_ENABLE_BOTH); + + // No horizontal scrollbar please. + /*si.nMin = 0; + si.nMax = ((Context->ShowAddress ? (Context->AddressIsWide ? 8 : 4) : 0) + + (Context->ShowHex ? Context->BytesPerRow * 3 : 0) + + (Context->ShowAscii ? Context->BytesPerRow : 0)) * Context->NullWidth; + si.nPage = 1; + si.nPos = 0; + SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);*/ +} + +VOID PhpHexEditCreateAddressCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + DestroyCaret(); + CreateCaret(hwnd, NULL, Context->NullWidth * (Context->AddressIsWide ? 8 : 4), Context->LineHeight); +} + +VOID PhpHexEditCreateEditCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + DestroyCaret(); + CreateCaret(hwnd, NULL, Context->NullWidth, Context->LineHeight); +} + +VOID PhpHexEditRepositionCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG Position + ) +{ + ULONG x; + ULONG y; + RECT rect; + + x = (Position - Context->TopIndex) % Context->BytesPerRow; + y = (Position - Context->TopIndex) / Context->BytesPerRow; + + switch (Context->CurrentMode) + { + case EDIT_NONE: + PhpHexEditCreateAddressCaret(hwnd, Context); + x = 0; + break; + case EDIT_HIGH: + PhpHexEditCreateEditCaret(hwnd, Context); + x *= Context->NullWidth * 3; + x += Context->HexOffset; + break; + case EDIT_LOW: + PhpHexEditCreateEditCaret(hwnd, Context); + x *= Context->NullWidth * 3; + x += Context->NullWidth; + x += Context->HexOffset; + break; + case EDIT_ASCII: + PhpHexEditCreateEditCaret(hwnd, Context); + x *= Context->NullWidth; + x += Context->AsciiOffset; + break; + } + + Context->EditPosition.x = x; + Context->EditPosition.y = y * Context->LineHeight; + + GetClientRect(hwnd, &rect); + + if (PtInRect(&rect, Context->EditPosition)) + { + SetCaretPos(Context->EditPosition.x, Context->EditPosition.y); + ShowCaret(hwnd); + } +} + +VOID PhpHexEditCalculatePosition( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG X, + _In_ LONG Y, + _Out_ POINT *Point + ) +{ + LONG xp; + + Y /= Context->LineHeight; + + if (Y < 0 || Y >= Context->LinesPerPage) + { + Point->x = -1; + Point->y = -1; + return; + } + + if (Y * Context->BytesPerRow >= Context->Length) + { + Point->x = -1; + Point->y = -1; + return; + } + + X += Context->NullWidth; + X /= Context->NullWidth; + + if (Context->ShowAddress && X <= (Context->AddressIsWide ? 8 : 4)) + { + Context->CurrentAddress = Context->TopIndex + Context->BytesPerRow * Y; + Context->CurrentMode = EDIT_NONE; + + Point->x = 0; + Point->y = Y; + return; + } + + xp = Context->HexOffset / Context->NullWidth + Context->BytesPerRow * 3; + + if (Context->ShowHex && X < xp) + { + if (X % 3) + X--; + + Context->CurrentAddress = Context->TopIndex + + Context->BytesPerRow * Y + + (X - (Context->HexOffset / Context->NullWidth)) / 3; + Context->CurrentMode = ((X % 3) & 1) ? EDIT_LOW : EDIT_HIGH; + + Point->x = X; + Point->y = Y; + return; + } + + X--; // fix selection problem + + xp = Context->AsciiOffset / Context->NullWidth + Context->BytesPerRow; + + if (Context->ShowAscii && X * Context->NullWidth >= Context->AsciiOffset && X <= xp) + { + Context->CurrentAddress = Context->TopIndex + + Context->BytesPerRow * Y + + (X - (Context->AsciiOffset / Context->NullWidth)); + Context->CurrentMode = EDIT_ASCII; + + Point->x = X; + Point->y = Y; + return; + } + + Point->x = -1; + Point->y = -1; +} + +VOID PhpHexEditMove( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG X, + _In_ LONG Y + ) +{ + switch (Context->CurrentMode) + { + case EDIT_NONE: + Context->CurrentAddress += Y * Context->BytesPerRow; + break; + case EDIT_HIGH: + if (X != 0) + Context->CurrentMode = EDIT_LOW; + if (X == -1) + Context->CurrentAddress--; + Context->CurrentAddress += Y * Context->BytesPerRow; + break; + case EDIT_LOW: + if (X != 0) + Context->CurrentMode = EDIT_HIGH; + if (X == 1) + Context->CurrentAddress++; + Context->CurrentAddress += Y * Context->BytesPerRow; + break; + case EDIT_ASCII: + Context->CurrentAddress += X; + Context->CurrentAddress += Y * Context->BytesPerRow; + break; + } + + if (Context->CurrentAddress < 0) + Context->CurrentAddress = 0; + + if (Context->CurrentAddress >= Context->Length) + { + Context->CurrentAddress -= X; + Context->CurrentAddress -= Y * Context->BytesPerRow; + } + + Context->NoAddressChange = TRUE; + + if (Context->CurrentAddress < Context->TopIndex) + SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); + if (Context->CurrentAddress >= Context->TopIndex + Context->LinesPerPage * Context->BytesPerRow) + SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); + + Context->NoAddressChange = FALSE; + PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress); +} + +VOID PhpHexEditSetSel( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG E + ) +{ + DestroyCaret(); + Context->SelStart = S; + Context->SelEnd = E; + REDRAW_WINDOW(hwnd); + + if (S != -1 && E != -1) + { + Context->CurrentAddress = S; + } + else + { + if (Context->EditPosition.x == 0 && Context->ShowAddress) + PhpHexEditCreateAddressCaret(hwnd, Context); + else + PhpHexEditCreateEditCaret(hwnd, Context); + + SetCaretPos(Context->EditPosition.x, Context->EditPosition.y); + ShowCaret(hwnd); + } +} + +VOID PhpHexEditScrollTo( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG Position + ) +{ + if (Position < Context->TopIndex || Position > Context->TopIndex + Context->LinesPerPage * Context->BytesPerRow) + { + Context->TopIndex = Position / Context->BytesPerRow * Context->BytesPerRow; // round down + Context->TopIndex -= Context->LinesPerPage / 3 * Context->BytesPerRow; + + if (Context->TopIndex < 0) + Context->TopIndex = 0; + + if (Context->Length >= Context->LinesPerPage * Context->BytesPerRow) + { + if (Context->TopIndex > Context->Length - Context->LinesPerPage * Context->BytesPerRow) + Context->TopIndex = Context->Length - Context->LinesPerPage * Context->BytesPerRow; + } + + PhpHexEditUpdateScrollbars(hwnd, Context); + REDRAW_WINDOW(hwnd); + } +} + +VOID PhpHexEditClearEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (Context->AllowLengthChange) + { + Context->CurrentAddress = Context->SelStart; + PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd); + PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress); + REDRAW_WINDOW(hwnd); + } +} + +VOID PhpHexEditCopyEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (OpenClipboard(hwnd)) + { + EmptyClipboard(); + PhpHexEditNormalizeSel(hwnd, Context); + + if (Context->CurrentMode != EDIT_ASCII) + { + ULONG length = Context->SelEnd - Context->SelStart; + HGLOBAL binaryMemory; + HGLOBAL hexMemory; + + binaryMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length); + + if (binaryMemory) + { + PUCHAR p = GlobalLock(binaryMemory); + memcpy(p, &Context->Data[Context->SelStart], length); + GlobalUnlock(binaryMemory); + + hexMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (length * 3 + 1) * sizeof(WCHAR)); + + if (hexMemory) + { + PWCHAR pw; + ULONG i; + + pw = GlobalLock(hexMemory); + + for (i = 0; i < length; i++) + { + TO_HEX(pw, Context->Data[Context->SelStart + i]); + *pw++ = ' '; + } + *pw = 0; + + GlobalUnlock(hexMemory); + + SetClipboardData(CF_UNICODETEXT, hexMemory); + } + + SetClipboardData(RegisterClipboardFormat(L"BinaryData"), binaryMemory); + } + } + else + { + ULONG length = Context->SelEnd - Context->SelStart; + HGLOBAL binaryMemory; + HGLOBAL asciiMemory; + + binaryMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length); + asciiMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length + 1); + + if (binaryMemory) + { + PUCHAR p = GlobalLock(binaryMemory); + memcpy(p, &Context->Data[Context->SelStart], length); + GlobalUnlock(binaryMemory); + + if (asciiMemory) + { + ULONG i; + + p = GlobalLock(asciiMemory); + memcpy(p, &Context->Data[Context->SelStart], length); + + for (i = 0; i < length; i++) + { + if (!IS_PRINTABLE(*p)) + *p = '.'; + p++; + } + *p = 0; + + GlobalUnlock(asciiMemory); + + SetClipboardData(CF_TEXT, asciiMemory); + } + + SetClipboardData(RegisterClipboardFormat(L"BinaryData"), binaryMemory); + } + } + + CloseClipboard(); + } +} + +VOID PhpHexEditCutEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (Context->AllowLengthChange) + { + PhpHexEditCopyEdit(hwnd, Context); + PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd); + REDRAW_WINDOW(hwnd); + } +} + +VOID PhpHexEditPasteEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (OpenClipboard(hwnd)) + { + HANDLE memory; + + memory = GetClipboardData(RegisterClipboardFormat(L"BinaryData")); + + if (!memory) + memory = GetClipboardData(CF_TEXT); + + if (memory) + { + PUCHAR p = GlobalLock(memory); + ULONG length = (ULONG)GlobalSize(memory); + ULONG paste; + ULONG oldCurrentAddress = Context->CurrentAddress; + + PhpHexEditNormalizeSel(hwnd, Context); + + if (Context->AllowLengthChange) + { + if (Context->SelStart == -1) + { + if (Context->CurrentMode == EDIT_LOW) + Context->CurrentAddress++; + + paste = Context->CurrentAddress; + PhpHexEditSelInsert(hwnd, Context, Context->CurrentAddress, length); + } + else + { + paste = Context->SelStart; + PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd); + PhpHexEditSelInsert(hwnd, Context, paste, length); + PhpHexEditSetSel(hwnd, Context, -1, -1); + } + } + else + { + if (Context->SelStart == -1) + { + if (Context->CurrentMode == EDIT_LOW) + Context->CurrentAddress++; + + paste = Context->CurrentAddress; + } + else + { + paste = Context->SelStart; + } + + if (length > Context->Length - paste) + length = Context->Length - paste; + } + + memcpy(&Context->Data[paste], p, length); + GlobalUnlock(memory); + + Context->CurrentAddress = oldCurrentAddress; + REDRAW_WINDOW(hwnd); + } + + CloseClipboard(); + } +} + +VOID PhpHexEditSelectAll( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + Context->SelStart = 0; + Context->SelEnd = Context->Length; + DestroyCaret(); + REDRAW_WINDOW(hwnd); +} + +VOID PhpHexEditUndoEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + // TODO +} + +VOID PhpHexEditNormalizeSel( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + if (Context->SelStart > Context->SelEnd) + { + LONG t; + + t = Context->SelEnd; + Context->SelEnd = Context->SelStart; + Context->SelStart = t; + } +} + +VOID PhpHexEditSelDelete( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG E + ) +{ + if (Context->AllowLengthChange && Context->Length > 0) + { + PUCHAR p = PhAllocate(Context->Length - (E - S) + 1); + + memcpy(p, Context->Data, S); + + if (S < Context->Length - (E - S)) + memcpy(&p[S], &Context->Data[E], Context->Length - E); + + PhFree(Context->Data); + Context->Data = p; + PhpHexEditSetSel(hwnd, Context, -1, -1); + Context->Length -= E - S; + + if (Context->CurrentAddress > Context->Length) + { + Context->CurrentAddress = Context->Length; + PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress); + } + + Context->Update = TRUE; + } +} + +VOID PhpHexEditSelInsert( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG L + ) +{ + if (Context->AllowLengthChange) + { + PUCHAR p = PhAllocate(Context->Length + L); + + memset(p, 0, Context->Length + L); + memcpy(p, Context->Data, S); + memcpy(&p[S + L], &Context->Data[S], Context->Length - S); + + PhFree(Context->Data); + Context->Data = p; + PhpHexEditSetSel(hwnd, Context, -1, -1); + Context->Length += L; + + Context->Update = TRUE; + } +} + +VOID PhpHexEditSetBuffer( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PUCHAR Data, + _In_ ULONG Length + ) +{ + Context->Data = Data; + PhpHexEditSetSel(hwnd, Context, -1, -1); + Context->Length = Length; + Context->CurrentAddress = 0; + Context->EditPosition.x = Context->EditPosition.y = 0; + Context->CurrentMode = EDIT_HIGH; + Context->TopIndex = 0; + Context->Update = TRUE; + + Context->UserBuffer = TRUE; + Context->AllowLengthChange = FALSE; +} + +VOID PhpHexEditSetData( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PUCHAR Data, + _In_ ULONG Length + ) +{ + if (Context->Data) PhFree(Context->Data); + Context->Data = PhAllocate(Length); + memcpy(Context->Data, Data, Length); + PhpHexEditSetBuffer(hwnd, Context, Context->Data, Length); + Context->UserBuffer = FALSE; + Context->AllowLengthChange = TRUE; +} diff --git a/phlib/hndlinfo.c b/phlib/hndlinfo.c new file mode 100644 index 0000000..a3b6728 --- /dev/null +++ b/phlib/hndlinfo.c @@ -0,0 +1,1763 @@ +/* + * Process Hacker - + * handle information + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define PH_QUERY_HACK_MAX_THREADS 20 + +typedef struct _PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT +{ + SLIST_ENTRY ListEntry; + + PUSER_THREAD_START_ROUTINE Routine; + PVOID Context; + + HANDLE StartEventHandle; + HANDLE CompletedEventHandle; + HANDLE ThreadHandle; +} PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT, *PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT; + +typedef enum _PHP_QUERY_OBJECT_WORK +{ + NtQueryObjectWork, + NtQuerySecurityObjectWork, + NtSetSecurityObjectWork +} PHP_QUERY_OBJECT_WORK; + +typedef struct _PHP_QUERY_OBJECT_COMMON_CONTEXT +{ + PHP_QUERY_OBJECT_WORK Work; + NTSTATUS Status; + + union + { + struct + { + HANDLE Handle; + OBJECT_INFORMATION_CLASS ObjectInformationClass; + PVOID ObjectInformation; + ULONG ObjectInformationLength; + PULONG ReturnLength; + } NtQueryObject; + struct + { + HANDLE Handle; + SECURITY_INFORMATION SecurityInformation; + PSECURITY_DESCRIPTOR SecurityDescriptor; + ULONG Length; + PULONG LengthNeeded; + } NtQuerySecurityObject; + struct + { + HANDLE Handle; + SECURITY_INFORMATION SecurityInformation; + PSECURITY_DESCRIPTOR SecurityDescriptor; + } NtSetSecurityObject; + } u; +} PHP_QUERY_OBJECT_COMMON_CONTEXT, *PPHP_QUERY_OBJECT_COMMON_CONTEXT; + +PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT PhpAcquireCallWithTimeoutThread( + _In_opt_ PLARGE_INTEGER Timeout + ); + +VOID PhpReleaseCallWithTimeoutThread( + _Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext + ); + +NTSTATUS PhpCallWithTimeout( + _Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext, + _In_ PUSER_THREAD_START_ROUTINE Routine, + _In_opt_ PVOID Context, + _In_ PLARGE_INTEGER Timeout + ); + +NTSTATUS PhpCallWithTimeoutThreadStart( + _In_ PVOID Parameter + ); + +static PPH_STRING PhObjectTypeNames[MAX_OBJECT_TYPE_NUMBER] = { 0 }; +static PPH_GET_CLIENT_ID_NAME PhHandleGetClientIdName = PhStdGetClientIdName; + +static SLIST_HEADER PhpCallWithTimeoutThreadListHead; +static PH_WAKE_EVENT PhpCallWithTimeoutThreadReleaseEvent = PH_WAKE_EVENT_INIT; + +PPH_GET_CLIENT_ID_NAME PhSetHandleClientIdFunction( + _In_ PPH_GET_CLIENT_ID_NAME GetClientIdName + ) +{ + return _InterlockedExchangePointer( + (PVOID *)&PhHandleGetClientIdName, + GetClientIdName + ); +} + +NTSTATUS PhpGetObjectBasicInformation( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _Out_ POBJECT_BASIC_INFORMATION BasicInformation + ) +{ + NTSTATUS status; + + if (KphIsConnected()) + { + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectBasicInformation, + BasicInformation, + sizeof(OBJECT_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + // The object was referenced in KProcessHacker, so we need to subtract 1 from the + // pointer count. + BasicInformation->PointerCount -= 1; + } + } + else + { + status = NtQueryObject( + Handle, + ObjectBasicInformation, + BasicInformation, + sizeof(OBJECT_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + // The object was referenced in NtQueryObject and a handle was opened to the object. We + // need to subtract 1 from the pointer count, then subtract 1 from both counts. + BasicInformation->HandleCount -= 1; + BasicInformation->PointerCount -= 2; + } + } + + return status; +} + +NTSTATUS PhpGetObjectTypeName( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ ULONG ObjectTypeNumber, + _Out_ PPH_STRING *TypeName + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PPH_STRING typeName = NULL; + + // If the cache contains the object type name, use it. Otherwise, query the type name. + + if (ObjectTypeNumber != -1 && ObjectTypeNumber < MAX_OBJECT_TYPE_NUMBER) + typeName = PhObjectTypeNames[ObjectTypeNumber]; + + if (typeName) + { + PhReferenceObject(typeName); + } + else + { + POBJECT_TYPE_INFORMATION buffer; + ULONG returnLength = 0; + PPH_STRING oldTypeName; + + // Get the needed buffer size. + if (KphIsConnected()) + { + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectTypeInformation, + NULL, + 0, + &returnLength + ); + } + else + { + status = NtQueryObject( + Handle, + ObjectTypeInformation, + NULL, + 0, + &returnLength + ); + } + + if (returnLength == 0) + return status; + + buffer = PhAllocate(returnLength); + + if (KphIsConnected()) + { + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectTypeInformation, + buffer, + returnLength, + &returnLength + ); + } + else + { + status = NtQueryObject( + Handle, + ObjectTypeInformation, + buffer, + returnLength, + &returnLength + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + // Create a copy of the type name. + typeName = PhCreateStringFromUnicodeString(&buffer->TypeName); + + if (ObjectTypeNumber != -1 && ObjectTypeNumber < MAX_OBJECT_TYPE_NUMBER) + { + // Try to store the type name in the cache. + oldTypeName = _InterlockedCompareExchangePointer( + &PhObjectTypeNames[ObjectTypeNumber], + typeName, + NULL + ); + + // Add a reference if we stored the type name + // successfully. + if (!oldTypeName) + PhReferenceObject(typeName); + } + + PhFree(buffer); + } + + // At this point typeName should contain a type name with one additional reference. + + *TypeName = typeName; + + return status; +} + +NTSTATUS PhpGetObjectName( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ BOOLEAN WithTimeout, + _Out_ PPH_STRING *ObjectName + ) +{ + NTSTATUS status; + POBJECT_NAME_INFORMATION buffer; + ULONG bufferSize; + ULONG attempts = 8; + + bufferSize = 0x200; + buffer = PhAllocate(bufferSize); + + // A loop is needed because the I/O subsystem likes to give us the wrong return lengths... + do + { + if (KphIsConnected()) + { + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectNameInformation, + buffer, + bufferSize, + &bufferSize + ); + } + else + { + if (WithTimeout) + { + status = PhCallNtQueryObjectWithTimeout( + Handle, + ObjectNameInformation, + buffer, + bufferSize, + &bufferSize + ); + } + else + { + status = NtQueryObject( + Handle, + ObjectNameInformation, + buffer, + bufferSize, + &bufferSize + ); + } + } + + if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH || + status == STATUS_BUFFER_TOO_SMALL) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } while (--attempts); + + if (NT_SUCCESS(status)) + { + *ObjectName = PhCreateStringFromUnicodeString(&buffer->Name); + } + + PhFree(buffer); + + return status; +} + +PPH_STRING PhFormatNativeKeyName( + _In_ PPH_STRING Name + ) +{ + static PH_STRINGREF hklmPrefix = PH_STRINGREF_INIT(L"\\Registry\\Machine"); + static PH_STRINGREF hkcrPrefix = PH_STRINGREF_INIT(L"\\Registry\\Machine\\Software\\Classes"); + static PH_STRINGREF hkuPrefix = PH_STRINGREF_INIT(L"\\Registry\\User"); + static PPH_STRING hkcuPrefix; + static PPH_STRING hkcucrPrefix; + + static PH_STRINGREF hklmString = PH_STRINGREF_INIT(L"HKLM"); + static PH_STRINGREF hkcrString = PH_STRINGREF_INIT(L"HKCR"); + static PH_STRINGREF hkuString = PH_STRINGREF_INIT(L"HKU"); + static PH_STRINGREF hkcuString = PH_STRINGREF_INIT(L"HKCU"); + static PH_STRINGREF hkcucrString = PH_STRINGREF_INIT(L"HKCU\\Software\\Classes"); + + static PH_INITONCE initOnce = PH_INITONCE_INIT; + + PPH_STRING newName; + PH_STRINGREF name; + + if (PhBeginInitOnce(&initOnce)) + { + HANDLE currentTokenHandle; + PTOKEN_USER tokenUser; + PPH_STRING stringSid = NULL; + + currentTokenHandle = PhGetOwnTokenAttributes().TokenHandle; + + if (currentTokenHandle && NT_SUCCESS(PhGetTokenUser( + currentTokenHandle, + &tokenUser + ))) + { + stringSid = PhSidToStringSid(tokenUser->User.Sid); + PhFree(tokenUser); + } + + if (stringSid) + { + static PH_STRINGREF registryUserPrefix = PH_STRINGREF_INIT(L"\\Registry\\User\\"); + static PH_STRINGREF classesString = PH_STRINGREF_INIT(L"_Classes"); + + hkcuPrefix = PhConcatStringRef2(®istryUserPrefix, &stringSid->sr); + hkcucrPrefix = PhConcatStringRef2(&hkcuPrefix->sr, &classesString); + + PhDereferenceObject(stringSid); + } + else + { + hkcuPrefix = PhCreateString(L"..."); // some random string that won't ever get matched + hkcucrPrefix = PhCreateString(L"..."); + } + + PhEndInitOnce(&initOnce); + } + + name = Name->sr; + + if (PhStartsWithStringRef(&name, &hkcrPrefix, TRUE)) + { + PhSkipStringRef(&name, hkcrPrefix.Length); + newName = PhConcatStringRef2(&hkcrString, &name); + } + else if (PhStartsWithStringRef(&name, &hklmPrefix, TRUE)) + { + PhSkipStringRef(&name, hklmPrefix.Length); + newName = PhConcatStringRef2(&hklmString, &name); + } + else if (PhStartsWithStringRef(&name, &hkcucrPrefix->sr, TRUE)) + { + PhSkipStringRef(&name, hkcucrPrefix->Length); + newName = PhConcatStringRef2(&hkcucrString, &name); + } + else if (PhStartsWithStringRef(&name, &hkcuPrefix->sr, TRUE)) + { + PhSkipStringRef(&name, hkcuPrefix->Length); + newName = PhConcatStringRef2(&hkcuString, &name); + } + else if (PhStartsWithStringRef(&name, &hkuPrefix, TRUE)) + { + PhSkipStringRef(&name, hkuPrefix.Length); + newName = PhConcatStringRef2(&hkuString, &name); + } + else + { + PhSetReference(&newName, Name); + } + + return newName; +} + +NTSTATUS PhGetSectionFileName( + _In_ HANDLE SectionHandle, + _Out_ PPH_STRING *FileName + ) +{ + NTSTATUS status; + SIZE_T viewSize; + PVOID viewBase; + + viewSize = 1; + viewBase = NULL; + + status = NtMapViewOfSection( + SectionHandle, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ); + + if (!NT_SUCCESS(status)) + return status; + + status = PhGetProcessMappedFileName(NtCurrentProcess(), viewBase, FileName); + NtUnmapViewOfSection(NtCurrentProcess(), viewBase); + + return status; +} + +_Callback_ PPH_STRING PhStdGetClientIdName( + _In_ PCLIENT_ID ClientId + ) +{ + static PH_QUEUED_LOCK cachedProcessesLock = PH_QUEUED_LOCK_INIT; + static PVOID processes = NULL; + static ULONG lastProcessesTickCount = 0; + + PPH_STRING name; + ULONG tickCount; + PSYSTEM_PROCESS_INFORMATION processInfo; + + // Get a new process list only if 2 seconds have passed since the last update. + + tickCount = GetTickCount(); + + if (tickCount - lastProcessesTickCount >= 2000) + { + PhAcquireQueuedLockExclusive(&cachedProcessesLock); + + // Re-check the tick count. + if (tickCount - lastProcessesTickCount >= 2000) + { + if (processes) + { + PhFree(processes); + processes = NULL; + } + + if (!NT_SUCCESS(PhEnumProcesses(&processes))) + { + PhReleaseQueuedLockExclusive(&cachedProcessesLock); + return PhCreateString(L"(Error querying processes)"); + } + + lastProcessesTickCount = tickCount; + } + + PhReleaseQueuedLockExclusive(&cachedProcessesLock); + } + + // Get a lock on the process list and get a name for the client ID. + + PhAcquireQueuedLockShared(&cachedProcessesLock); + + if (!processes) + { + PhReleaseQueuedLockShared(&cachedProcessesLock); + return NULL; + } + + processInfo = PhFindProcessInformation(processes, ClientId->UniqueProcess); + + if (ClientId->UniqueThread) + { + if (processInfo) + { + name = PhFormatString( + L"%.*s (%u): %u", + processInfo->ImageName.Length / 2, + processInfo->ImageName.Buffer, + HandleToUlong(ClientId->UniqueProcess), + HandleToUlong(ClientId->UniqueThread) + ); + } + else + { + name = PhFormatString( + L"Non-existent process (%u): %u", + HandleToUlong(ClientId->UniqueProcess), + HandleToUlong(ClientId->UniqueThread) + ); + } + } + else + { + if (processInfo) + { + name = PhFormatString( + L"%.*s (%u)", + processInfo->ImageName.Length / 2, + processInfo->ImageName.Buffer, + HandleToUlong(ClientId->UniqueProcess) + ); + } + else + { + name = PhFormatString(L"Non-existent process (%u)", HandleToUlong(ClientId->UniqueProcess)); + } + } + + PhReleaseQueuedLockShared(&cachedProcessesLock); + + return name; +} + +NTSTATUS PhpGetBestObjectName( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ PPH_STRING ObjectName, + _In_ PPH_STRING TypeName, + _Out_ PPH_STRING *BestObjectName + ) +{ + NTSTATUS status; + PPH_STRING bestObjectName = NULL; + PPH_GET_CLIENT_ID_NAME handleGetClientIdName = PhHandleGetClientIdName; + + if (PhEqualString2(TypeName, L"EtwRegistration", TRUE)) + { + if (KphIsConnected()) + { + ETWREG_BASIC_INFORMATION basicInfo; + + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectEtwRegBasicInformation, + &basicInfo, + sizeof(ETWREG_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + static PH_STRINGREF publishersKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\"); + + PPH_STRING guidString; + PPH_STRING keyName; + HANDLE keyHandle; + PPH_STRING publisherName = NULL; + + guidString = PhFormatGuid(&basicInfo.Guid); + + // We should perform a lookup on the GUID to get the publisher name. + + keyName = PhConcatStringRef2(&publishersKeyName, &guidString->sr); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName->sr, + 0 + ))) + { + publisherName = PhQueryRegistryString(keyHandle, NULL); + + if (publisherName && publisherName->Length == 0) + { + PhDereferenceObject(publisherName); + publisherName = NULL; + } + + NtClose(keyHandle); + } + + PhDereferenceObject(keyName); + + if (publisherName) + { + bestObjectName = publisherName; + PhDereferenceObject(guidString); + } + else + { + bestObjectName = guidString; + } + } + } + } + else if (PhEqualString2(TypeName, L"File", TRUE)) + { + // Convert the file name to a DOS file name. + bestObjectName = PhResolveDevicePrefix(ObjectName); + + if (!bestObjectName) + { + // The file doesn't have a DOS name. + PhSetReference(&bestObjectName, ObjectName); + } + + if (PhIsNullOrEmptyString(bestObjectName) && KphIsConnected()) + { + KPH_FILE_OBJECT_DRIVER fileObjectDriver; + PPH_STRING driverName; + + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectFileObjectDriver, + &fileObjectDriver, + sizeof(KPH_FILE_OBJECT_DRIVER), + NULL + ); + + if (NT_SUCCESS(status) && fileObjectDriver.DriverHandle) + { + if (NT_SUCCESS(PhGetDriverName(fileObjectDriver.DriverHandle, &driverName))) + { + static PH_STRINGREF prefix = PH_STRINGREF_INIT(L"Unnamed file: "); + + PhMoveReference(&bestObjectName, PhConcatStringRef2(&prefix, &driverName->sr)); + PhDereferenceObject(driverName); + } + + NtClose(fileObjectDriver.DriverHandle); + } + } + } + else if (PhEqualString2(TypeName, L"Job", TRUE)) + { + HANDLE dupHandle; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processIdList; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + JOB_OBJECT_QUERY, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + if (handleGetClientIdName && NT_SUCCESS(PhGetJobProcessIdList(dupHandle, &processIdList))) + { + PH_STRING_BUILDER sb; + ULONG i; + CLIENT_ID clientId; + PPH_STRING name; + + PhInitializeStringBuilder(&sb, 40); + clientId.UniqueThread = NULL; + + for (i = 0; i < processIdList->NumberOfProcessIdsInList; i++) + { + clientId.UniqueProcess = (HANDLE)processIdList->ProcessIdList[i]; + name = handleGetClientIdName(&clientId); + + if (name) + { + PhAppendStringBuilder(&sb, &name->sr); + PhAppendStringBuilder2(&sb, L"; "); + PhDereferenceObject(name); + } + } + + PhFree(processIdList); + + if (sb.String->Length != 0) + PhRemoveEndStringBuilder(&sb, 2); + + if (sb.String->Length == 0) + PhAppendStringBuilder2(&sb, L"(No processes)"); + + bestObjectName = PhFinalStringBuilderString(&sb); + } + + NtClose(dupHandle); + } + else if (PhEqualString2(TypeName, L"Key", TRUE)) + { + bestObjectName = PhFormatNativeKeyName(ObjectName); + } + else if (PhEqualString2(TypeName, L"Process", TRUE)) + { + CLIENT_ID clientId; + + clientId.UniqueThread = NULL; + + if (KphIsConnected()) + { + PROCESS_BASIC_INFORMATION basicInfo; + + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectProcessBasicInformation, + &basicInfo, + sizeof(PROCESS_BASIC_INFORMATION), + NULL + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + clientId.UniqueProcess = basicInfo.UniqueProcessId; + } + else + { + HANDLE dupHandle; + PROCESS_BASIC_INFORMATION basicInfo; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + ProcessQueryAccess, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetProcessBasicInformation(dupHandle, &basicInfo); + NtClose(dupHandle); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + clientId.UniqueProcess = basicInfo.UniqueProcessId; + } + + if (handleGetClientIdName) + bestObjectName = handleGetClientIdName(&clientId); + } + else if (PhEqualString2(TypeName, L"Section", TRUE)) + { + HANDLE dupHandle; + PPH_STRING fileName; + + if (!PhIsNullOrEmptyString(ObjectName)) + goto CleanupExit; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + SECTION_QUERY | SECTION_MAP_READ, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetSectionFileName(dupHandle, &fileName); + + if (NT_SUCCESS(status)) + { + bestObjectName = PhResolveDevicePrefix(fileName); + PhDereferenceObject(fileName); + } + else + { + SECTION_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(PhGetSectionBasicInformation(dupHandle, &basicInfo))) + { + PH_FORMAT format[4]; + PWSTR sectionType = L"Unknown"; + + if (basicInfo.AllocationAttributes & SEC_COMMIT) + sectionType = L"Commit"; + else if (basicInfo.AllocationAttributes & SEC_FILE) + sectionType = L"File"; + else if (basicInfo.AllocationAttributes & SEC_IMAGE) + sectionType = L"Image"; + else if (basicInfo.AllocationAttributes & SEC_RESERVE) + sectionType = L"Reserve"; + + PhInitFormatS(&format[0], sectionType); + PhInitFormatS(&format[1], L" ("); + PhInitFormatSize(&format[2], basicInfo.MaximumSize.QuadPart); + PhInitFormatC(&format[3], ')'); + bestObjectName = PhFormat(format, 4, 20); + } + } + + NtClose(dupHandle); + } + else if (PhEqualString2(TypeName, L"Thread", TRUE)) + { + CLIENT_ID clientId; + + if (KphIsConnected()) + { + THREAD_BASIC_INFORMATION basicInfo; + + status = KphQueryInformationObject( + ProcessHandle, + Handle, + KphObjectThreadBasicInformation, + &basicInfo, + sizeof(THREAD_BASIC_INFORMATION), + NULL + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + clientId = basicInfo.ClientId; + } + else + { + HANDLE dupHandle; + THREAD_BASIC_INFORMATION basicInfo; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + ThreadQueryAccess, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetThreadBasicInformation(dupHandle, &basicInfo); + NtClose(dupHandle); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + clientId = basicInfo.ClientId; + } + + if (handleGetClientIdName) + bestObjectName = handleGetClientIdName(&clientId); + } + else if (PhEqualString2(TypeName, L"TmEn", TRUE)) + { + HANDLE dupHandle; + ENLISTMENT_BASIC_INFORMATION basicInfo; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + ENLISTMENT_QUERY_INFORMATION, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetEnlistmentBasicInformation(dupHandle, &basicInfo); + NtClose(dupHandle); + + if (NT_SUCCESS(status)) + { + bestObjectName = PhFormatGuid(&basicInfo.EnlistmentId); + } + } + else if (PhEqualString2(TypeName, L"TmRm", TRUE)) + { + HANDLE dupHandle; + GUID guid; + PPH_STRING description; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + RESOURCEMANAGER_QUERY_INFORMATION, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetResourceManagerBasicInformation( + dupHandle, + &guid, + &description + ); + NtClose(dupHandle); + + if (NT_SUCCESS(status)) + { + if (!PhIsNullOrEmptyString(description)) + { + bestObjectName = description; + } + else + { + bestObjectName = PhFormatGuid(&guid); + + if (description) + PhDereferenceObject(description); + } + } + } + else if (PhEqualString2(TypeName, L"TmTm", TRUE)) + { + HANDLE dupHandle; + PPH_STRING logFileName = NULL; + TRANSACTIONMANAGER_BASIC_INFORMATION basicInfo; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + TRANSACTIONMANAGER_QUERY_INFORMATION, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetTransactionManagerLogFileName( + dupHandle, + &logFileName + ); + + if (NT_SUCCESS(status) && !PhIsNullOrEmptyString(logFileName)) + { + bestObjectName = PhGetFileName(logFileName); + PhDereferenceObject(logFileName); + } + else + { + if (logFileName) + PhDereferenceObject(logFileName); + + status = PhGetTransactionManagerBasicInformation( + dupHandle, + &basicInfo + ); + + if (NT_SUCCESS(status)) + { + bestObjectName = PhFormatGuid(&basicInfo.TmIdentity); + } + } + + NtClose(dupHandle); + } + else if (PhEqualString2(TypeName, L"TmTx", TRUE)) + { + HANDLE dupHandle; + PPH_STRING description = NULL; + TRANSACTION_BASIC_INFORMATION basicInfo; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + TRANSACTION_QUERY_INFORMATION, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetTransactionPropertiesInformation( + dupHandle, + NULL, + NULL, + &description + ); + + if (NT_SUCCESS(status) && !PhIsNullOrEmptyString(description)) + { + bestObjectName = description; + } + else + { + if (description) + PhDereferenceObject(description); + + status = PhGetTransactionBasicInformation( + dupHandle, + &basicInfo + ); + + if (NT_SUCCESS(status)) + { + bestObjectName = PhFormatGuid(&basicInfo.TransactionId); + } + } + + NtClose(dupHandle); + } + else if (PhEqualString2(TypeName, L"Token", TRUE)) + { + HANDLE dupHandle; + PTOKEN_USER tokenUser = NULL; + TOKEN_STATISTICS statistics = { 0 }; + + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + TOKEN_QUERY, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = PhGetTokenUser(dupHandle, &tokenUser); + PhGetTokenStatistics(dupHandle, &statistics); + + if (NT_SUCCESS(status)) + { + PPH_STRING fullName; + + fullName = PhGetSidFullName(tokenUser->User.Sid, TRUE, NULL); + + if (fullName) + { + PH_FORMAT format[4]; + + PhInitFormatSR(&format[0], fullName->sr); + PhInitFormatS(&format[1], L": 0x"); + PhInitFormatX(&format[2], statistics.AuthenticationId.LowPart); + PhInitFormatS(&format[3], statistics.TokenType == TokenPrimary ? L" (Primary)" : L" (Impersonation)"); + + bestObjectName = PhFormat(format, 4, fullName->Length + 8 + 16 + 16); + PhDereferenceObject(fullName); + } + + PhFree(tokenUser); + } + + NtClose(dupHandle); + } + +CleanupExit: + + if (!bestObjectName) + PhSetReference(&bestObjectName, ObjectName); + + *BestObjectName = bestObjectName; + + return STATUS_SUCCESS; +} + +/** + * Gets information for a handle. + * + * \param ProcessHandle A handle to the process in which the handle resides. + * \param Handle The handle value. + * \param ObjectTypeNumber The object type number of the handle. You can specify -1 for this + * parameter if the object type number is not known. + * \param BasicInformation A variable which receives basic information about the object. + * \param TypeName A variable which receives the object type name. + * \param ObjectName A variable which receives the object name. + * \param BestObjectName A variable which receives the formatted object name. + * + * \retval STATUS_INVALID_HANDLE The handle specified in \c ProcessHandle or \c Handle is invalid. + * \retval STATUS_INVALID_PARAMETER_3 The value specified in \c ObjectTypeNumber is invalid. + */ +NTSTATUS PhGetHandleInformation( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ ULONG ObjectTypeNumber, + _Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation, + _Out_opt_ PPH_STRING *TypeName, + _Out_opt_ PPH_STRING *ObjectName, + _Out_opt_ PPH_STRING *BestObjectName + ) +{ + NTSTATUS status; + NTSTATUS subStatus; + + status = PhGetHandleInformationEx( + ProcessHandle, + Handle, + ObjectTypeNumber, + 0, + &subStatus, + BasicInformation, + TypeName, + ObjectName, + BestObjectName, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Fail if any component failed, for compatibility reasons. + if (!NT_SUCCESS(subStatus)) + { + if (TypeName) + PhClearReference(TypeName); + if (ObjectName) + PhClearReference(ObjectName); + if (BestObjectName) + PhClearReference(BestObjectName); + + return subStatus; + } + + return status; +} + +/** + * Gets information for a handle. + * + * \param ProcessHandle A handle to the process in which the handle resides. + * \param Handle The handle value. + * \param ObjectTypeNumber The object type number of the handle. You can specify -1 for this + * parameter if the object type number is not known. + * \param Flags Reserved. + * \param SubStatus A variable which receives the NTSTATUS value of the last component that fails. + * If all operations succeed, the value will be STATUS_SUCCESS. If the function returns an error + * status, this variable is not set. + * \param BasicInformation A variable which receives basic information about the object. + * \param TypeName A variable which receives the object type name. + * \param ObjectName A variable which receives the object name. + * \param BestObjectName A variable which receives the formatted object name. + * \param ExtraInformation Reserved. + * + * \retval STATUS_INVALID_HANDLE The handle specified in \c ProcessHandle or \c Handle is invalid. + * \retval STATUS_INVALID_PARAMETER_3 The value specified in \c ObjectTypeNumber is invalid. + * + * \remarks If \a BasicInformation or \a TypeName are specified, the function will fail if either + * cannot be queried. \a ObjectName, \a BestObjectName and \a ExtraInformation will be NULL if they + * cannot be queried. + */ +NTSTATUS PhGetHandleInformationEx( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ ULONG ObjectTypeNumber, + _Reserved_ ULONG Flags, + _Out_opt_ PNTSTATUS SubStatus, + _Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation, + _Out_opt_ PPH_STRING *TypeName, + _Out_opt_ PPH_STRING *ObjectName, + _Out_opt_ PPH_STRING *BestObjectName, + _Reserved_ PVOID *ExtraInformation + ) +{ + NTSTATUS status = STATUS_SUCCESS; + NTSTATUS subStatus = STATUS_SUCCESS; + HANDLE dupHandle = NULL; + PPH_STRING typeName = NULL; + PPH_STRING objectName = NULL; + PPH_STRING bestObjectName = NULL; + + if (Handle == NULL || Handle == NtCurrentProcess() || Handle == NtCurrentThread()) + return STATUS_INVALID_HANDLE; + if (ObjectTypeNumber != -1 && ObjectTypeNumber >= MAX_OBJECT_TYPE_NUMBER) + return STATUS_INVALID_PARAMETER_3; + + // Duplicate the handle if we're not using KPH. + if (!KphIsConnected()) + { + // However, we obviously don't need to duplicate it + // if the handle is in the current process. + if (ProcessHandle != NtCurrentProcess()) + { + status = NtDuplicateObject( + ProcessHandle, + Handle, + NtCurrentProcess(), + &dupHandle, + 0, + 0, + 0 + ); + + if (!NT_SUCCESS(status)) + return status; + } + else + { + dupHandle = Handle; + } + } + + // Get basic information. + if (BasicInformation) + { + status = PhpGetObjectBasicInformation( + ProcessHandle, + KphIsConnected() ? Handle : dupHandle, + BasicInformation + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + } + + // Exit early if we don't need to get any other information. + if (!TypeName && !ObjectName && !BestObjectName) + goto CleanupExit; + + // Get the type name. + status = PhpGetObjectTypeName( + ProcessHandle, + KphIsConnected() ? Handle : dupHandle, + ObjectTypeNumber, + &typeName + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + // Exit early if we don't need to get the object name. + if (!ObjectName && !BestObjectName) + goto CleanupExit; + + // Get the object name. + // If we're dealing with a file handle we must take special precautions so we don't hang. + if (PhEqualString2(typeName, L"File", TRUE) && !KphIsConnected()) + { +#define QUERY_NORMALLY 0 +#define QUERY_WITH_TIMEOUT 1 +#define QUERY_FAIL 2 + + ULONG hackLevel = QUERY_WITH_TIMEOUT; + + // We can't use the timeout method on XP because hanging threads can't even be terminated! + if (WindowsVersion <= WINDOWS_XP) + hackLevel = QUERY_FAIL; + + if (hackLevel == QUERY_NORMALLY || hackLevel == QUERY_WITH_TIMEOUT) + { + status = PhpGetObjectName( + ProcessHandle, + KphIsConnected() ? Handle : dupHandle, + hackLevel == QUERY_WITH_TIMEOUT, + &objectName + ); + } + else + { + // Pretend the file object has no name. + objectName = PhReferenceEmptyString(); + status = STATUS_SUCCESS; + } + } + else + { + // Query the object normally. + status = PhpGetObjectName( + ProcessHandle, + KphIsConnected() ? Handle : dupHandle, + FALSE, + &objectName + ); + } + + if (!NT_SUCCESS(status)) + { + if (PhEqualString2(typeName, L"File", TRUE) && KphIsConnected()) + { + // PhpGetBestObjectName can provide us with a name. + objectName = PhReferenceEmptyString(); + status = STATUS_SUCCESS; + } + else + { + subStatus = status; + status = STATUS_SUCCESS; + goto CleanupExit; + } + } + + // Exit early if we don't need to get the best object name. + if (!BestObjectName) + goto CleanupExit; + + status = PhpGetBestObjectName( + ProcessHandle, + Handle, + objectName, + typeName, + &bestObjectName + ); + + if (!NT_SUCCESS(status)) + { + subStatus = status; + status = STATUS_SUCCESS; + goto CleanupExit; + } + +CleanupExit: + + if (NT_SUCCESS(status)) + { + if (SubStatus) + *SubStatus = subStatus; + if (TypeName) + PhSetReference(TypeName, typeName); + if (ObjectName) + PhSetReference(ObjectName, objectName); + if (BestObjectName) + PhSetReference(BestObjectName, bestObjectName); + } + + if (dupHandle && ProcessHandle != NtCurrentProcess()) + NtClose(dupHandle); + + PhClearReference(&typeName); + PhClearReference(&objectName); + PhClearReference(&bestObjectName); + + return status; +} + +NTSTATUS PhEnumObjectTypes( + _Out_ POBJECT_TYPES_INFORMATION *ObjectTypes + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + + bufferSize = 0x1000; + buffer = PhAllocate(bufferSize); + + while ((status = NtQueryObject( + NULL, + ObjectTypesInformation, + buffer, + bufferSize, + NULL + )) == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + + // Fail if we're resizing the buffer to something very large. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *ObjectTypes = (POBJECT_TYPES_INFORMATION)buffer; + + return status; +} + +ULONG PhGetObjectTypeNumber( + _In_ PUNICODE_STRING TypeName + ) +{ + POBJECT_TYPES_INFORMATION objectTypes; + POBJECT_TYPE_INFORMATION objectType; + ULONG i; + + if (NT_SUCCESS(PhEnumObjectTypes(&objectTypes))) + { + objectType = PH_FIRST_OBJECT_TYPE(objectTypes); + + for (i = 0; i < objectTypes->NumberOfTypes; i++) + { + if (RtlEqualUnicodeString(&objectType->TypeName, TypeName, TRUE)) + { + if (WindowsVersion >= WINDOWS_8_1) + return objectType->TypeIndex; + else if (WindowsVersion >= WINDOWS_7) + return i + 2; + else + return i + 1; + } + + objectType = PH_NEXT_OBJECT_TYPE(objectType); + } + + PhFree(objectTypes); + } + + return -1; +} + +PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT PhpAcquireCallWithTimeoutThread( + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + + PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext; + PSLIST_ENTRY listEntry; + PH_QUEUED_WAIT_BLOCK waitBlock; + + if (PhBeginInitOnce(&initOnce)) + { + ULONG i; + + for (i = 0; i < PH_QUERY_HACK_MAX_THREADS; i++) + { + threadContext = PhAllocate(sizeof(PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT)); + memset(threadContext, 0, sizeof(PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT)); + RtlInterlockedPushEntrySList(&PhpCallWithTimeoutThreadListHead, &threadContext->ListEntry); + } + + PhEndInitOnce(&initOnce); + } + + while (TRUE) + { + if (listEntry = RtlInterlockedPopEntrySList(&PhpCallWithTimeoutThreadListHead)) + break; + + if (!Timeout || Timeout->QuadPart != 0) + { + PhQueueWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock); + + if (listEntry = RtlInterlockedPopEntrySList(&PhpCallWithTimeoutThreadListHead)) + { + // A new entry has just become available; cancel the wait. + PhSetWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock); + break; + } + else + { + PhWaitForWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, &waitBlock, FALSE, Timeout); + } + } + else + { + return NULL; + } + } + + return CONTAINING_RECORD(listEntry, PHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT, ListEntry); +} + +VOID PhpReleaseCallWithTimeoutThread( + _Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext + ) +{ + RtlInterlockedPushEntrySList(&PhpCallWithTimeoutThreadListHead, &ThreadContext->ListEntry); + PhSetWakeEvent(&PhpCallWithTimeoutThreadReleaseEvent, NULL); +} + +NTSTATUS PhpCallWithTimeout( + _Inout_ PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT ThreadContext, + _In_ PUSER_THREAD_START_ROUTINE Routine, + _In_opt_ PVOID Context, + _In_ PLARGE_INTEGER Timeout + ) +{ + NTSTATUS status; + + // Create objects if necessary. + + if (!ThreadContext->StartEventHandle) + { + if (!NT_SUCCESS(status = NtCreateEvent(&ThreadContext->StartEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) + return status; + } + + if (!ThreadContext->CompletedEventHandle) + { + if (!NT_SUCCESS(status = NtCreateEvent(&ThreadContext->CompletedEventHandle, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE))) + return status; + } + + // Create a query thread if we don't have one. + if (!ThreadContext->ThreadHandle) + { + CLIENT_ID clientId; + + NtClearEvent(ThreadContext->StartEventHandle); + NtClearEvent(ThreadContext->CompletedEventHandle); + + if (!NT_SUCCESS(status = RtlCreateUserThread( + NtCurrentProcess(), + NULL, + FALSE, + 0, + 0, + 32 * 1024, + PhpCallWithTimeoutThreadStart, + ThreadContext, + &ThreadContext->ThreadHandle, + &clientId))) + { + return status; + } + + // Wait for the thread to initialize. + NtWaitForSingleObject(ThreadContext->CompletedEventHandle, FALSE, NULL); + } + + ThreadContext->Routine = Routine; + ThreadContext->Context = Context; + + NtSetEvent(ThreadContext->StartEventHandle, NULL); + status = NtWaitForSingleObject(ThreadContext->CompletedEventHandle, FALSE, Timeout); + + ThreadContext->Routine = NULL; + MemoryBarrier(); + ThreadContext->Context = NULL; + + if (status != STATUS_WAIT_0) + { + // The operation timed out, or there was an error. Kill the thread. On Vista and above, the + // thread stack is freed automatically. + NtTerminateThread(ThreadContext->ThreadHandle, STATUS_UNSUCCESSFUL); + status = NtWaitForSingleObject(ThreadContext->ThreadHandle, FALSE, NULL); + NtClose(ThreadContext->ThreadHandle); + ThreadContext->ThreadHandle = NULL; + + status = STATUS_UNSUCCESSFUL; + } + + return status; +} + +NTSTATUS PhpCallWithTimeoutThreadStart( + _In_ PVOID Parameter + ) +{ + PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext = Parameter; + + NtSetEvent(threadContext->CompletedEventHandle, NULL); + + while (TRUE) + { + if (NtWaitForSingleObject(threadContext->StartEventHandle, FALSE, NULL) != STATUS_WAIT_0) + continue; + + if (threadContext->Routine) + threadContext->Routine(threadContext->Context); + + NtSetEvent(threadContext->CompletedEventHandle, NULL); + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhCallWithTimeout( + _In_ PUSER_THREAD_START_ROUTINE Routine, + _In_opt_ PVOID Context, + _In_opt_ PLARGE_INTEGER AcquireTimeout, + _In_ PLARGE_INTEGER CallTimeout + ) +{ + NTSTATUS status; + PPHP_CALL_WITH_TIMEOUT_THREAD_CONTEXT threadContext; + + if (threadContext = PhpAcquireCallWithTimeoutThread(AcquireTimeout)) + { + status = PhpCallWithTimeout(threadContext, Routine, Context, CallTimeout); + PhpReleaseCallWithTimeoutThread(threadContext); + } + else + { + status = STATUS_UNSUCCESSFUL; + } + + return status; +} + +NTSTATUS PhpCommonQueryObjectRoutine( + _In_ PVOID Parameter + ) +{ + PPHP_QUERY_OBJECT_COMMON_CONTEXT context = Parameter; + + switch (context->Work) + { + case NtQueryObjectWork: + context->Status = NtQueryObject( + context->u.NtQueryObject.Handle, + context->u.NtQueryObject.ObjectInformationClass, + context->u.NtQueryObject.ObjectInformation, + context->u.NtQueryObject.ObjectInformationLength, + context->u.NtQueryObject.ReturnLength + ); + break; + case NtQuerySecurityObjectWork: + context->Status = NtQuerySecurityObject( + context->u.NtQuerySecurityObject.Handle, + context->u.NtQuerySecurityObject.SecurityInformation, + context->u.NtQuerySecurityObject.SecurityDescriptor, + context->u.NtQuerySecurityObject.Length, + context->u.NtQuerySecurityObject.LengthNeeded + ); + break; + case NtSetSecurityObjectWork: + context->Status = NtSetSecurityObject( + context->u.NtSetSecurityObject.Handle, + context->u.NtSetSecurityObject.SecurityInformation, + context->u.NtSetSecurityObject.SecurityDescriptor + ); + break; + default: + context->Status = STATUS_INVALID_PARAMETER; + break; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhpCommonQueryObjectWithTimeout( + _In_ PPHP_QUERY_OBJECT_COMMON_CONTEXT Context + ) +{ + NTSTATUS status; + LARGE_INTEGER timeout; + + timeout.QuadPart = -1 * PH_TIMEOUT_SEC; + status = PhCallWithTimeout(PhpCommonQueryObjectRoutine, Context, NULL, &timeout); + + if (NT_SUCCESS(status)) + status = Context->Status; + + PhFree(Context); + + return status; +} + +NTSTATUS PhCallNtQueryObjectWithTimeout( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ) +{ + PPHP_QUERY_OBJECT_COMMON_CONTEXT context; + + context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT)); + context->Work = NtQueryObjectWork; + context->Status = STATUS_UNSUCCESSFUL; + context->u.NtQueryObject.Handle = Handle; + context->u.NtQueryObject.ObjectInformationClass = ObjectInformationClass; + context->u.NtQueryObject.ObjectInformation = ObjectInformation; + context->u.NtQueryObject.ObjectInformationLength = ObjectInformationLength; + context->u.NtQueryObject.ReturnLength = ReturnLength; + + return PhpCommonQueryObjectWithTimeout(context); +} + +NTSTATUS PhCallNtQuerySecurityObjectWithTimeout( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_writes_bytes_opt_(Length) PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ULONG Length, + _Out_ PULONG LengthNeeded + ) +{ + PPHP_QUERY_OBJECT_COMMON_CONTEXT context; + + context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT)); + context->Work = NtQuerySecurityObjectWork; + context->Status = STATUS_UNSUCCESSFUL; + context->u.NtQuerySecurityObject.Handle = Handle; + context->u.NtQuerySecurityObject.SecurityInformation = SecurityInformation; + context->u.NtQuerySecurityObject.SecurityDescriptor = SecurityDescriptor; + context->u.NtQuerySecurityObject.Length = Length; + context->u.NtQuerySecurityObject.LengthNeeded = LengthNeeded; + + return PhpCommonQueryObjectWithTimeout(context); +} + +NTSTATUS PhCallNtSetSecurityObjectWithTimeout( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ) +{ + PPHP_QUERY_OBJECT_COMMON_CONTEXT context; + + context = PhAllocate(sizeof(PHP_QUERY_OBJECT_COMMON_CONTEXT)); + context->Work = NtSetSecurityObjectWork; + context->Status = STATUS_UNSUCCESSFUL; + context->u.NtSetSecurityObject.Handle = Handle; + context->u.NtSetSecurityObject.SecurityInformation = SecurityInformation; + context->u.NtSetSecurityObject.SecurityDescriptor = SecurityDescriptor; + + return PhpCommonQueryObjectWithTimeout(context); +} diff --git a/phlib/icotobmp.c b/phlib/icotobmp.c new file mode 100644 index 0000000..3fdf75c --- /dev/null +++ b/phlib/icotobmp.c @@ -0,0 +1,237 @@ +#include +#include +#include + +// code from http://msdn.microsoft.com/en-us/library/bb757020.aspx + +typedef HPAINTBUFFER (*_BeginBufferedPaint)( + _In_ HDC hdcTarget, + _In_ const RECT *prcTarget, + _In_ BP_BUFFERFORMAT dwFormat, + _In_ BP_PAINTPARAMS *pPaintParams, + _Out_ HDC *phdc + ); + +typedef HRESULT (*_EndBufferedPaint)( + _In_ HPAINTBUFFER hBufferedPaint, + _In_ BOOL fUpdateTarget + ); + +typedef HRESULT (*_GetBufferedPaintBits)( + _In_ HPAINTBUFFER hBufferedPaint, + _Out_ RGBQUAD **ppbBuffer, + _Out_ int *pcxRow + ); + +static BOOLEAN ImportsInitialized = FALSE; +static _BeginBufferedPaint BeginBufferedPaint_I = NULL; +static _EndBufferedPaint EndBufferedPaint_I = NULL; +static _GetBufferedPaintBits GetBufferedPaintBits_I = NULL; + +static HBITMAP PhpCreateBitmap32( + _In_ HDC hdc, + _In_ ULONG Width, + _In_ ULONG Height, + _Outptr_opt_ PVOID *Bits + ) +{ + BITMAPINFO bitmapInfo; + + memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + + bitmapInfo.bmiHeader.biWidth = Width; + bitmapInfo.bmiHeader.biHeight = Height; + bitmapInfo.bmiHeader.biBitCount = 32; + + return CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, Bits, NULL, 0); +} + +static BOOLEAN PhpHasAlpha( + _In_ PULONG Argb, + _In_ ULONG Width, + _In_ ULONG Height, + _In_ ULONG RowWidth + ) +{ + ULONG delta; + ULONG x; + ULONG y; + + delta = RowWidth - Width; + + for (y = Width; y; y--) + { + for (x = Height; x; x--) + { + if (*Argb++ & 0xff000000) + return TRUE; + } + + Argb += delta; + } + + return FALSE; +} + +static VOID PhpConvertToPArgb32( + _In_ HDC hdc, + _Inout_ PULONG Argb, + _In_ HBITMAP Bitmap, + _In_ ULONG Width, + _In_ ULONG Height, + _In_ ULONG RowWidth + ) +{ + BITMAPINFO bitmapInfo; + PVOID bits; + + memset(&bitmapInfo, 0, sizeof(BITMAPINFO)); + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + + bitmapInfo.bmiHeader.biWidth = Width; + bitmapInfo.bmiHeader.biHeight = Height; + bitmapInfo.bmiHeader.biBitCount = 32; + + bits = PhAllocate(Width * sizeof(ULONG) * Height); + + if (GetDIBits(hdc, Bitmap, 0, Height, bits, &bitmapInfo, DIB_RGB_COLORS) == Height) + { + PULONG argbMask; + ULONG delta; + ULONG x; + ULONG y; + + argbMask = (PULONG)bits; + delta = RowWidth - Width; + + for (y = Height; y; y--) + { + for (x = Width; x; x--) + { + if (*argbMask++) + { + *Argb++ = 0; // transparent + } + else + { + *Argb++ |= 0xff000000; // opaque + } + } + + Argb += delta; + } + } + + PhFree(bits); +} + +static VOID PhpConvertToPArgb32IfNeeded( + _In_ HPAINTBUFFER PaintBuffer, + _In_ HDC hdc, + _In_ HICON Icon, + _In_ ULONG Width, + _In_ ULONG Height + ) +{ + RGBQUAD *quad; + ULONG rowWidth; + + if (SUCCEEDED(GetBufferedPaintBits_I(PaintBuffer, &quad, &rowWidth))) + { + PULONG argb = (PULONG)quad; + + if (!PhpHasAlpha(argb, Width, Height, rowWidth)) + { + ICONINFO iconInfo; + + if (GetIconInfo(Icon, &iconInfo)) + { + if (iconInfo.hbmMask) + { + PhpConvertToPArgb32(hdc, argb, iconInfo.hbmMask, Width, Height, rowWidth); + } + + DeleteObject(iconInfo.hbmColor); + DeleteObject(iconInfo.hbmMask); + } + } + } +} + +HBITMAP PhIconToBitmap( + _In_ HICON Icon, + _In_ ULONG Width, + _In_ ULONG Height + ) +{ + HBITMAP bitmap; + RECT iconRectangle; + HDC screenHdc; + HDC hdc; + HBITMAP oldBitmap; + BLENDFUNCTION blendFunction = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; + BP_PAINTPARAMS paintParams = { sizeof(paintParams) }; + HDC bufferHdc; + HPAINTBUFFER paintBuffer; + + iconRectangle.left = 0; + iconRectangle.top = 0; + iconRectangle.right = Width; + iconRectangle.bottom = Height; + + if (!ImportsInitialized) + { + HMODULE uxtheme; + + uxtheme = GetModuleHandle(L"uxtheme.dll"); + BeginBufferedPaint_I = PhGetProcedureAddress(uxtheme, "BeginBufferedPaint", 0); + EndBufferedPaint_I = PhGetProcedureAddress(uxtheme, "EndBufferedPaint", 0); + GetBufferedPaintBits_I = PhGetProcedureAddress(uxtheme, "GetBufferedPaintBits", 0); + ImportsInitialized = TRUE; + } + + if (!BeginBufferedPaint_I || !EndBufferedPaint_I || !GetBufferedPaintBits_I) + { + // Probably XP. + + screenHdc = GetDC(NULL); + hdc = CreateCompatibleDC(screenHdc); + bitmap = CreateCompatibleBitmap(screenHdc, Width, Height); + ReleaseDC(NULL, screenHdc); + + oldBitmap = SelectObject(hdc, bitmap); + FillRect(hdc, &iconRectangle, (HBRUSH)(COLOR_WINDOW + 1)); + DrawIconEx(hdc, 0, 0, Icon, Width, Height, 0, NULL, DI_NORMAL); + SelectObject(hdc, oldBitmap); + + DeleteDC(hdc); + + return bitmap; + } + + screenHdc = GetDC(NULL); + hdc = CreateCompatibleDC(screenHdc); + bitmap = PhpCreateBitmap32(screenHdc, Width, Height, NULL); + ReleaseDC(NULL, screenHdc); + oldBitmap = SelectObject(hdc, bitmap); + + paintParams.dwFlags = BPPF_ERASE; + paintParams.pBlendFunction = &blendFunction; + + paintBuffer = BeginBufferedPaint_I(hdc, &iconRectangle, BPBF_DIB, &paintParams, &bufferHdc); + DrawIconEx(bufferHdc, 0, 0, Icon, Width, Height, 0, NULL, DI_NORMAL); + // If the icon did not have an alpha channel, we need to convert the buffer to PARGB. + PhpConvertToPArgb32IfNeeded(paintBuffer, hdc, Icon, Width, Height); + // This will write the buffer contents to the destination bitmap. + EndBufferedPaint_I(paintBuffer, TRUE); + + SelectObject(hdc, oldBitmap); + DeleteDC(hdc); + + return bitmap; +} diff --git a/phlib/include/apiimport.h b/phlib/include/apiimport.h new file mode 100644 index 0000000..80769e3 --- /dev/null +++ b/phlib/include/apiimport.h @@ -0,0 +1,86 @@ +#ifndef _PH_APIIMPORT_H +#define _PH_APIIMPORT_H + +// comctl32 + +typedef HRESULT (WINAPI *_TaskDialogIndirect)( + _In_ const struct _TASKDIALOGCONFIG *pTaskConfig, + _In_ int *pnButton, + _In_ int *pnRadioButton, + _In_ BOOL *pfVerificationFlagChecked + ); + +// ntdll + +typedef NTSTATUS (NTAPI *_NtQueryInformationEnlistment)( + _In_ HANDLE EnlistmentHandle, + _In_ ENLISTMENT_INFORMATION_CLASS EnlistmentInformationClass, + _Out_writes_bytes_(EnlistmentInformationLength) PVOID EnlistmentInformation, + _In_ ULONG EnlistmentInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +typedef NTSTATUS (NTAPI *_NtQueryInformationResourceManager)( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _Out_writes_bytes_(ResourceManagerInformationLength) PVOID ResourceManagerInformation, + _In_ ULONG ResourceManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +typedef NTSTATUS (NTAPI *_NtQueryInformationTransaction)( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _Out_writes_bytes_(TransactionInformationLength) PVOID TransactionInformation, + _In_ ULONG TransactionInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +typedef NTSTATUS (NTAPI *_NtQueryInformationTransactionManager)( + _In_ HANDLE TransactionManagerHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _Out_writes_bytes_(TransactionManagerInformationLength) PVOID TransactionManagerInformation, + _In_ ULONG TransactionManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// shell32 + +#if defined(_M_IX86) +#define __unaligned +#endif + +typedef HRESULT (WINAPI *_SHCreateShellItem)( + _In_opt_ const struct _ITEMIDLIST __unaligned *pidlParent, + _In_opt_ struct IShellFolder *psfParent, + _In_ const struct _ITEMIDLIST __unaligned *pidl, + _Out_ struct IShellItem **ppsi + ); + +typedef HRESULT (WINAPI *_SHOpenFolderAndSelectItems)( + _In_ const struct _ITEMIDLIST __unaligned *pidlFolder, + _In_ UINT cidl, + _In_reads_opt_(cidl) const struct _ITEMIDLIST __unaligned **apidl, + _In_ DWORD dwFlags + ); + +typedef HRESULT (WINAPI *_SHParseDisplayName)( + _In_ LPCWSTR pszName, + _In_opt_ struct IBindCtx *pbc, + _Out_ const struct _ITEMIDLIST __unaligned **ppidl, + _In_ ULONG sfgaoIn, + _Out_ ULONG *psfgaoOut + ); + +#define PH_DECLARE_IMPORT(Name) _##Name Name##_Import(VOID) + +PH_DECLARE_IMPORT(TaskDialogIndirect); +PH_DECLARE_IMPORT(NtQueryInformationEnlistment); +PH_DECLARE_IMPORT(NtQueryInformationResourceManager); +PH_DECLARE_IMPORT(NtQueryInformationTransaction); +PH_DECLARE_IMPORT(NtQueryInformationTransactionManager); +PH_DECLARE_IMPORT(SHCreateShellItem); +PH_DECLARE_IMPORT(SHOpenFolderAndSelectItems); +PH_DECLARE_IMPORT(SHParseDisplayName); + +#endif diff --git a/phlib/include/circbuf.h b/phlib/include/circbuf.h new file mode 100644 index 0000000..b3e0d76 --- /dev/null +++ b/phlib/include/circbuf.h @@ -0,0 +1,26 @@ +#ifndef _PH_CIRCBUF_H +#define _PH_CIRCBUF_H + +#define PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + +#undef T +#define T ULONG +#include "circbuf_h.h" + +#undef T +#define T ULONG64 +#include "circbuf_h.h" + +#undef T +#define T PVOID +#include "circbuf_h.h" + +#undef T +#define T SIZE_T +#include "circbuf_h.h" + +#undef T +#define T FLOAT +#include "circbuf_h.h" + +#endif diff --git a/phlib/include/circbuf_h.h b/phlib/include/circbuf_h.h new file mode 100644 index 0000000..84b9c10 --- /dev/null +++ b/phlib/include/circbuf_h.h @@ -0,0 +1,140 @@ +#ifdef T + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct T___(_PH_CIRCULAR_BUFFER, T) +{ + ULONG Size; +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + ULONG SizeMinusOne; +#endif + ULONG Count; + LONG Index; + T *Data; +} T___(PH_CIRCULAR_BUFFER, T), *T___(PPH_CIRCULAR_BUFFER, T); + +PHLIBAPI +VOID +NTAPI +T___(PhInitializeCircularBuffer, T)( + _Out_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ ULONG Size + ); + +PHLIBAPI +VOID +NTAPI +T___(PhDeleteCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer + ); + +PHLIBAPI +VOID +NTAPI +T___(PhResizeCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ ULONG NewSize + ); + +PHLIBAPI +VOID +NTAPI +T___(PhClearCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer + ); + +PHLIBAPI +VOID +NTAPI +T___(PhCopyCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _Out_writes_(Count) T *Destination, + _In_ ULONG Count + ); + +FORCEINLINE T T___(PhGetItemCircularBuffer, T)( + _In_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ LONG Index + ) +{ +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + return Buffer->Data[(Buffer->Index + Index) & Buffer->SizeMinusOne]; +#else + ULONG size; + + size = Buffer->Size; + // Modulo is dividend-based. + return Buffer->Data[(((Buffer->Index + Index) % size) + size) % size]; +#endif +} + +FORCEINLINE VOID T___(PhSetItemCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ LONG Index, + _In_ T Value + ) +{ +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + Buffer->Data[(Buffer->Index + Index) & Buffer->SizeMinusOne] = Value; +#else + ULONG size; + + size = Buffer->Size; + Buffer->Data[(((Buffer->Index + Index) % size) + size) % size] = Value; +#endif +} + +FORCEINLINE VOID T___(PhAddItemCircularBuffer, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ T Value + ) +{ +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + Buffer->Data[Buffer->Index = ((Buffer->Index - 1) & Buffer->SizeMinusOne)] = Value; +#else + ULONG size; + + size = Buffer->Size; + Buffer->Data[Buffer->Index = (((Buffer->Index - 1) % size) + size) % size] = Value; +#endif + + if (Buffer->Count < Buffer->Size) + Buffer->Count++; +} + +FORCEINLINE T T___(PhAddItemCircularBuffer2, T)( + _Inout_ T___(PPH_CIRCULAR_BUFFER, T) Buffer, + _In_ T Value + ) +{ + LONG index; + T oldValue; + +#ifdef PH_CIRCULAR_BUFFER_POWER_OF_TWO_SIZE + index = ((Buffer->Index - 1) & Buffer->SizeMinusOne); +#else + ULONG size; + + size = Buffer->Size; + index = (((Buffer->Index - 1) % size) + size) % size; +#endif + + Buffer->Index = index; + oldValue = Buffer->Data[index]; + Buffer->Data[index] = Value; + + if (Buffer->Count < Buffer->Size) + Buffer->Count++; + + return oldValue; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/colorbox.h b/phlib/include/colorbox.h new file mode 100644 index 0000000..b6ec680 --- /dev/null +++ b/phlib/include/colorbox.h @@ -0,0 +1,30 @@ +#ifndef _PH_COLORBOX_H +#define _PH_COLORBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_COLORBOX_CLASSNAME L"PhColorBox" + +PHLIBAPI +BOOLEAN +NTAPI +PhColorBoxInitialization( + VOID + ); + +#define CBCM_SETCOLOR (WM_APP + 1501) +#define CBCM_GETCOLOR (WM_APP + 1502) + +#define ColorBox_SetColor(hWnd, Color) \ + SendMessage((hWnd), CBCM_SETCOLOR, (WPARAM)(Color), 0) + +#define ColorBox_GetColor(hWnd) \ + ((COLORREF)SendMessage((hWnd), CBCM_GETCOLOR, 0, 0)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/cpysave.h b/phlib/include/cpysave.h new file mode 100644 index 0000000..5b91b60 --- /dev/null +++ b/phlib/include/cpysave.h @@ -0,0 +1,78 @@ +#ifndef _PH_CPYSAVE_H +#define _PH_CPYSAVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_EXPORT_MODE_TABS 0 +#define PH_EXPORT_MODE_SPACES 1 +#define PH_EXPORT_MODE_CSV 2 + +PHLIBAPI +VOID PhaCreateTextTable( + _Out_ PPH_STRING ***Table, + _In_ ULONG Rows, + _In_ ULONG Columns + ); + +PHLIBAPI +PPH_LIST PhaFormatTextTable( + _In_ PPH_STRING **Table, + _In_ ULONG Rows, + _In_ ULONG Columns, + _In_ ULONG Mode + ); + +PHLIBAPI +VOID PhMapDisplayIndexTreeNew( + _In_ HWND TreeNewHandle, + _Out_opt_ PULONG *DisplayToId, + _Out_opt_ PWSTR **DisplayToText, + _Out_ PULONG NumberOfColumns + ); + +PHLIBAPI +PPH_STRING PhGetTreeNewText( + _In_ HWND TreeNewHandle, + _Reserved_ ULONG Reserved + ); + +PHLIBAPI +PPH_LIST PhGetGenericTreeNewLines( + _In_ HWND TreeNewHandle, + _In_ ULONG Mode + ); + +PHLIBAPI +VOID PhaMapDisplayIndexListView( + _In_ HWND ListViewHandle, + _Out_writes_(Count) PULONG DisplayToId, + _Out_writes_opt_(Count) PPH_STRING *DisplayToText, + _In_ ULONG Count, + _Out_ PULONG NumberOfColumns + ); + +PHLIBAPI +PPH_STRING PhaGetListViewItemText( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT SubItemIndex + ); + +PHLIBAPI +PPH_STRING PhGetListViewText( + _In_ HWND ListViewHandle + ); + +PHLIBAPI +PPH_LIST PhGetListViewLines( + _In_ HWND ListViewHandle, + _In_ ULONG Mode + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/dltmgr.h b/phlib/include/dltmgr.h new file mode 100644 index 0000000..f292bd7 --- /dev/null +++ b/phlib/include/dltmgr.h @@ -0,0 +1,35 @@ +#ifndef _PH_DLTMGR_H +#define _PH_DLTMGR_H + +typedef struct _PH_SINGLE_DELTA +{ + FLOAT Value; + FLOAT Delta; +} PH_SINGLE_DELTA, *PPH_SINGLE_DELTA; + +typedef struct _PH_UINT32_DELTA +{ + ULONG Value; + ULONG Delta; +} PH_UINT32_DELTA, *PPH_UINT32_DELTA; + +typedef struct _PH_UINT64_DELTA +{ + ULONG64 Value; + ULONG64 Delta; +} PH_UINT64_DELTA, *PPH_UINT64_DELTA; + +typedef struct _PH_UINTPTR_DELTA +{ + ULONG_PTR Value; + ULONG_PTR Delta; +} PH_UINTPTR_DELTA, *PPH_UINTPTR_DELTA; + +#define PhInitializeDelta(DltMgr) \ + ((DltMgr)->Value = 0, (DltMgr)->Delta = 0) + +#define PhUpdateDelta(DltMgr, NewValue) \ + ((DltMgr)->Delta = (NewValue) - (DltMgr)->Value, \ + (DltMgr)->Value = (NewValue), (DltMgr)->Delta) + +#endif diff --git a/phlib/include/dspick.h b/phlib/include/dspick.h new file mode 100644 index 0000000..e0a945c --- /dev/null +++ b/phlib/include/dspick.h @@ -0,0 +1,48 @@ +#ifndef _PH_DSPICK_H +#define _PH_DSPICK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_DSPICK_MULTISELECT 0x1 + +typedef struct _PH_DSPICK_OBJECT +{ + PPH_STRING Name; + PSID Sid; +} PH_DSPICK_OBJECT, *PPH_DSPICK_OBJECT; + +typedef struct _PH_DSPICK_OBJECTS +{ + ULONG NumberOfObjects; + PH_DSPICK_OBJECT Objects[1]; +} PH_DSPICK_OBJECTS, *PPH_DSPICK_OBJECTS; + +PHLIBAPI +VOID PhFreeDsObjectPickerDialog( + _In_ PVOID PickerDialog + ); + +PHLIBAPI +PVOID PhCreateDsObjectPickerDialog( + _In_ ULONG Flags + ); + +PHLIBAPI +BOOLEAN PhShowDsObjectPickerDialog( + _In_ HWND hWnd, + _In_ PVOID PickerDialog, + _Out_ PPH_DSPICK_OBJECTS *Objects + ); + +PHLIBAPI +VOID PhFreeDsObjectPickerObjects( + _In_ PPH_DSPICK_OBJECTS Objects + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/emenu.h b/phlib/include/emenu.h new file mode 100644 index 0000000..95c39f3 --- /dev/null +++ b/phlib/include/emenu.h @@ -0,0 +1,219 @@ +#ifndef _PH_EMENU_H +#define _PH_EMENU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_EMENU_DISABLED 0x1 +#define PH_EMENU_CHECKED 0x2 +#define PH_EMENU_HIGHLIGHT 0x4 +#define PH_EMENU_MENUBARBREAK 0x8 +#define PH_EMENU_MENUBREAK 0x10 +#define PH_EMENU_DEFAULT 0x20 +#define PH_EMENU_MOUSESELECT 0x40 +#define PH_EMENU_RADIOCHECK 0x80 + +#define PH_EMENU_SEPARATECHECKSPACE 0x100000 +#define PH_EMENU_SEPARATOR 0x200000 + +#define PH_EMENU_TEXT_OWNED 0x80000000 +#define PH_EMENU_BITMAP_OWNED 0x40000000 + +struct _PH_EMENU_ITEM; + +typedef VOID (NTAPI *PPH_EMENU_ITEM_DELETE_FUNCTION)( + _In_ struct _PH_EMENU_ITEM *Item + ); + +typedef struct _PH_EMENU_ITEM +{ + ULONG Flags; + ULONG Id; + PWSTR Text; + HBITMAP Bitmap; + + PVOID Parameter; + PVOID Context; + PPH_EMENU_ITEM_DELETE_FUNCTION DeleteFunction; + PVOID Reserved; + + struct _PH_EMENU_ITEM *Parent; + PPH_LIST Items; +} PH_EMENU_ITEM, *PPH_EMENU_ITEM; + +typedef struct _PH_EMENU_ITEM PH_EMENU, *PPH_EMENU; + +PHLIBAPI +PPH_EMENU_ITEM PhCreateEMenuItem( + _In_ ULONG Flags, + _In_ ULONG Id, + _In_ PWSTR Text, + _In_opt_ HBITMAP Bitmap, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID PhDestroyEMenuItem( + _In_ PPH_EMENU_ITEM Item + ); + +#define PH_EMENU_FIND_DESCEND 0x1 +#define PH_EMENU_FIND_STARTSWITH 0x2 +#define PH_EMENU_FIND_LITERAL 0x4 + +PHLIBAPI +PPH_EMENU_ITEM PhFindEMenuItem( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Flags, + _In_opt_ PWSTR Text, + _In_opt_ ULONG Id + ); + +PHLIBAPI +PPH_EMENU_ITEM PhFindEMenuItemEx( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Flags, + _In_opt_ PWSTR Text, + _In_opt_ ULONG Id, + _Out_opt_ PPH_EMENU_ITEM *FoundParent, + _Out_opt_ PULONG FoundIndex + ); + +PHLIBAPI +ULONG PhIndexOfEMenuItem( + _In_ PPH_EMENU_ITEM Parent, + _In_ PPH_EMENU_ITEM Item + ); + +PHLIBAPI +VOID PhInsertEMenuItem( + _Inout_ PPH_EMENU_ITEM Parent, + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG Index + ); + +PHLIBAPI +BOOLEAN PhRemoveEMenuItem( + _Inout_opt_ PPH_EMENU_ITEM Parent, + _In_opt_ PPH_EMENU_ITEM Item, + _In_opt_ ULONG Index + ); + +PHLIBAPI +VOID PhRemoveAllEMenuItems( + _Inout_ PPH_EMENU_ITEM Parent + ); + +PHLIBAPI +PPH_EMENU PhCreateEMenu( + VOID + ); + +PHLIBAPI +VOID PhDestroyEMenu( + _In_ PPH_EMENU Menu + ); + +#define PH_EMENU_CONVERT_ID 0x1 + +typedef struct _PH_EMENU_DATA +{ + PPH_LIST IdToItem; +} PH_EMENU_DATA, *PPH_EMENU_DATA; + +PHLIBAPI +VOID PhInitializeEMenuData( + _Out_ PPH_EMENU_DATA Data + ); + +PHLIBAPI +VOID PhDeleteEMenuData( + _Inout_ PPH_EMENU_DATA Data + ); + +PHLIBAPI +HMENU PhEMenuToHMenu( + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG Flags, + _Inout_opt_ PPH_EMENU_DATA Data + ); + +PHLIBAPI +VOID PhEMenuToHMenu2( + _In_ HMENU MenuHandle, + _In_ PPH_EMENU_ITEM Menu, + _In_ ULONG Flags, + _Inout_opt_ PPH_EMENU_DATA Data + ); + +PHLIBAPI +VOID PhHMenuToEMenuItem( + _Inout_ PPH_EMENU_ITEM MenuItem, + _In_ HMENU MenuHandle + ); + +PHLIBAPI +VOID PhLoadResourceEMenuItem( + _Inout_ PPH_EMENU_ITEM MenuItem, + _In_ HINSTANCE InstanceHandle, + _In_ PWSTR Resource, + _In_ ULONG SubMenuIndex + ); + +#define PH_EMENU_SHOW_SEND_COMMAND 0x1 +#define PH_EMENU_SHOW_LEFTRIGHT 0x2 + +PHLIBAPI +PPH_EMENU_ITEM PhShowEMenu( + _In_ PPH_EMENU Menu, + _In_ HWND WindowHandle, + _In_ ULONG Flags, + _In_ ULONG Align, + _In_ ULONG X, + _In_ ULONG Y + ); + +// Convenience functions + +PHLIBAPI +BOOLEAN PhSetFlagsEMenuItem( + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG Id, + _In_ ULONG Mask, + _In_ ULONG Value + ); + +FORCEINLINE BOOLEAN PhEnableEMenuItem( + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG Id, + _In_ BOOLEAN Enable + ) +{ + return PhSetFlagsEMenuItem(Item, Id, PH_EMENU_DISABLED, Enable ? 0 : PH_EMENU_DISABLED); +} + +PHLIBAPI +VOID PhSetFlagsAllEMenuItems( + _In_ PPH_EMENU_ITEM Item, + _In_ ULONG Mask, + _In_ ULONG Value + ); + +#define PH_EMENU_MODIFY_TEXT 0x1 +#define PH_EMENU_MODIFY_BITMAP 0x2 + +PHLIBAPI +VOID PhModifyEMenuItem( + _Inout_ PPH_EMENU_ITEM Item, + _In_ ULONG ModifyFlags, + _In_ ULONG OwnedFlags, + _In_opt_ PWSTR Text, + _In_opt_ HBITMAP Bitmap + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/fastlock.h b/phlib/include/fastlock.h new file mode 100644 index 0000000..d059e61 --- /dev/null +++ b/phlib/include/fastlock.h @@ -0,0 +1,93 @@ +#ifndef _PH_FASTLOCK_H +#define _PH_FASTLOCK_H + +// FastLock is a port of FastResourceLock from PH 1.x. + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _PH_FAST_LOCK +{ + ULONG Value; + HANDLE ExclusiveWakeEvent; + HANDLE SharedWakeEvent; +} PH_FAST_LOCK, *PPH_FAST_LOCK; + +#define PH_FAST_LOCK_INIT { 0, NULL, NULL } + +PHLIBAPI +VOID +NTAPI +PhInitializeFastLock( + _Out_ PPH_FAST_LOCK FastLock + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteFastLock( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhAcquireFastLockExclusive PhfAcquireFastLockExclusive +_May_raise_ +_Acquires_exclusive_lock_(*FastLock) +PHLIBAPI +VOID +FASTCALL +PhfAcquireFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhAcquireFastLockShared PhfAcquireFastLockShared +_May_raise_ +_Acquires_shared_lock_(*FastLock) +PHLIBAPI +VOID +FASTCALL +PhfAcquireFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhReleaseFastLockExclusive PhfReleaseFastLockExclusive +_Releases_exclusive_lock_(*FastLock) +PHLIBAPI +VOID +FASTCALL +PhfReleaseFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhReleaseFastLockShared PhfReleaseFastLockShared +_Releases_shared_lock_(*FastLock) +PHLIBAPI +VOID +FASTCALL +PhfReleaseFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhTryAcquireFastLockExclusive PhfTryAcquireFastLockExclusive +_When_(return != 0, _Acquires_exclusive_lock_(*FastLock)) +PHLIBAPI +BOOLEAN +FASTCALL +PhfTryAcquireFastLockExclusive( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#define PhTryAcquireFastLockShared PhfTryAcquireFastLockShared +_When_(return != 0, _Acquires_shared_lock_(*FastLock)) +PHLIBAPI +BOOLEAN +FASTCALL +PhfTryAcquireFastLockShared( + _Inout_ PPH_FAST_LOCK FastLock + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/filepool.h b/phlib/include/filepool.h new file mode 100644 index 0000000..26fa765 --- /dev/null +++ b/phlib/include/filepool.h @@ -0,0 +1,196 @@ +#ifndef _PH_FILEPOOL_H +#define _PH_FILEPOOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +// On-disk structures + +// Each file has at least one segment. Each segment has a number of blocks, which are allocated from +// a bitmap. The segment header is always in the first block of each segment, except for the first +// segment. In the first segment, the file header is in the first few blocks, followed by the +// segment header. +// +// The segments are placed in a particular free list depending on how many blocks they have free; +// this allows allocators to simply skip the segments which don't have enough segments free, and +// allocate new segments if necessary. The free list does not however guarantee that a particular +// segment has a particular number of contiguous blocks free; low performance can still occur when +// there is fragmentation. + +/** The number of 32-bit integers used for each allocation bitmap. */ +#define PH_FP_BITMAP_SIZE 64 +/** The power-of-two index of the bitmap size. */ +#define PH_FP_BITMAP_SIZE_SHIFT 6 +/** The number of blocks that are available in each segment. */ +#define PH_FP_BLOCK_COUNT (PH_FP_BITMAP_SIZE * 32) +/** The power-of-two index of the block count. */ +#define PH_FP_BLOCK_COUNT_SHIFT (PH_FP_BITMAP_SIZE_SHIFT + 5) +/** The number of free lists for segments. */ +#define PH_FP_FREE_LIST_COUNT 8 + +// Block flags +/** The block is the beginning of a large allocation (one that spans several segments). */ +#define PH_FP_BLOCK_LARGE_ALLOCATION 0x1 + +typedef struct _PH_FP_BLOCK_HEADER +{ + ULONG Flags; // PH_FP_BLOCK_* + /** The number of blocks in the entire logical block, or the number + * of segments in a large allocation. */ + ULONG Span; + ULONGLONG Body; +} PH_FP_BLOCK_HEADER, *PPH_FP_BLOCK_HEADER; + +typedef struct _PH_FP_SEGMENT_HEADER +{ + ULONG Bitmap[PH_FP_BITMAP_SIZE]; + ULONG FreeBlocks; + ULONG FreeFlink; + ULONG FreeBlink; + ULONG Reserved[13]; +} PH_FP_SEGMENT_HEADER, *PPH_FP_SEGMENT_HEADER; + +#define PH_FP_MAGIC ('loPF') + +typedef struct _PH_FP_FILE_HEADER +{ + ULONG Magic; + ULONG SegmentShift; + ULONG SegmentCount; + ULONGLONG UserContext; + ULONG FreeLists[PH_FP_FREE_LIST_COUNT]; +} PH_FP_FILE_HEADER, *PPH_FP_FILE_HEADER; + +// Runtime + +typedef struct _PH_FILE_POOL_PARAMETERS +{ + // File options + + /** + * The base-2 logarithm of the size of each segment. This value must be between 16 and 28, + * inclusive. + */ + ULONG SegmentShift; + + // Runtime options + + /** The maximum number of inactive segments to keep mapped. */ + ULONG MaximumInactiveViews; +} PH_FILE_POOL_PARAMETERS, *PPH_FILE_POOL_PARAMETERS; + +typedef struct _PH_FILE_POOL +{ + HANDLE FileHandle; + HANDLE SectionHandle; + BOOLEAN ReadOnly; + + PH_FREE_LIST ViewFreeList; + PLIST_ENTRY *ByIndexBuckets; + ULONG ByIndexSize; + PH_AVL_TREE ByBaseSet; + + ULONG MaximumInactiveViews; + ULONG NumberOfInactiveViews; + LIST_ENTRY InactiveViewsListHead; + + PPH_FP_BLOCK_HEADER FirstBlockOfFirstSegment; + PPH_FP_FILE_HEADER Header; + ULONG SegmentShift; // The power-of-two size of each segment + ULONG SegmentSize; // The size of each segment + ULONG BlockShift; // The power-of-two size of each block in each segment + ULONG BlockSize; // The size of each block in each segment + ULONG FileHeaderBlockSpan; // The number of blocks needed to store a file header + ULONG SegmentHeaderBlockSpan; // The number of blocks needed to store a segment header +} PH_FILE_POOL, *PPH_FILE_POOL; + +PHLIBAPI +NTSTATUS PhCreateFilePool( + _Out_ PPH_FILE_POOL *Pool, + _In_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS PhCreateFilePool2( + _Out_ PPH_FILE_POOL *Pool, + _In_ PWSTR FileName, + _In_ BOOLEAN ReadOnly, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters + ); + +PHLIBAPI +VOID PhDestroyFilePool( + _In_ _Post_invalid_ PPH_FILE_POOL Pool + ); + +PHLIBAPI +PVOID PhAllocateFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Size, + _Out_opt_ PULONG Rva + ); + +PHLIBAPI +VOID PhFreeFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Block + ); + +PHLIBAPI +BOOLEAN PhFreeFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ); + +PHLIBAPI +VOID PhReferenceFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ); + +PHLIBAPI +VOID PhDereferenceFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ); + +PHLIBAPI +PVOID PhReferenceFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ); + +PHLIBAPI +BOOLEAN PhDereferenceFilePoolByRva( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Rva + ); + +PHLIBAPI +ULONG PhEncodeRvaFilePool( + _In_ PPH_FILE_POOL Pool, + _In_ PVOID Address + ); + +PHLIBAPI +VOID PhGetUserContextFilePool( + _In_ PPH_FILE_POOL Pool, + _Out_ PULONGLONG Context + ); + +PHLIBAPI +VOID PhSetUserContextFilePool( + _Inout_ PPH_FILE_POOL Pool, + _In_ PULONGLONG Context + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/filepoolp.h b/phlib/include/filepoolp.h new file mode 100644 index 0000000..f11cc0f --- /dev/null +++ b/phlib/include/filepoolp.h @@ -0,0 +1,204 @@ +#ifndef _PH_FILEPOOLP_H +#define _PH_FILEPOOLP_H + +typedef struct _PH_FILE_POOL_VIEW +{ + LIST_ENTRY ByIndexListEntry; + PH_AVL_LINKS ByBaseLinks; + LIST_ENTRY InactiveViewsListEntry; + + ULONG RefCount; + ULONG SegmentIndex; + PVOID Base; +} PH_FILE_POOL_VIEW, *PPH_FILE_POOL_VIEW; + +NTSTATUS PhpValidateFilePoolParameters( + _Inout_ PPH_FILE_POOL_PARAMETERS Parameters + ); + +VOID PhpSetDefaultFilePoolParameters( + _Out_ PPH_FILE_POOL_PARAMETERS Parameters + ); + +// Range mapping + +NTSTATUS PhFppExtendRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG NewSize + ); + +NTSTATUS PhFppMapRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG Offset, + _In_ ULONG Size, + _Out_ PVOID *Base + ); + +NTSTATUS PhFppUnmapRange( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ); + +// Segments + +VOID PhFppInitializeSegment( + _Inout_ PPH_FILE_POOL Pool, + _Out_ PPH_FP_BLOCK_HEADER BlockOfSegmentHeader, + _In_ ULONG AdditionalBlocksUsed + ); + +PPH_FP_BLOCK_HEADER PhFppAllocateSegment( + _Inout_ PPH_FILE_POOL Pool, + _Out_ PULONG NewSegmentIndex + ); + +PPH_FP_SEGMENT_HEADER PhFppGetHeaderSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock + ); + +// Views + +VOID PhFppAddViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppRemoveViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +PPH_FILE_POOL_VIEW PhFppFindViewByIndex( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ); + +LONG NTAPI PhpFilePoolViewByBaseCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +VOID PhFppAddViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppRemoveViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +PPH_FILE_POOL_VIEW PhFppFindViewByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ); + +PPH_FILE_POOL_VIEW PhFppCreateView( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ); + +VOID PhFppDestroyView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppActivateView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppDeactivateView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppReferenceView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +VOID PhFppDereferenceView( + _Inout_ PPH_FILE_POOL Pool, + _Inout_ PPH_FILE_POOL_VIEW View + ); + +PPH_FP_BLOCK_HEADER PhFppReferenceSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ); + +VOID PhFppDereferenceSegment( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex + ); + +VOID PhFppReferenceSegmentByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ); + +VOID PhFppDereferenceSegmentByBase( + _Inout_ PPH_FILE_POOL Pool, + _In_ PVOID Base + ); + +// Bitmap allocation + +PPH_FP_BLOCK_HEADER PhFppAllocateBlocks( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, + _In_ ULONG NumberOfBlocks + ); + +VOID PhFppFreeBlocks( + _Inout_ PPH_FILE_POOL Pool, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, + _In_ PPH_FP_BLOCK_HEADER BlockHeader + ); + +// Free list + +ULONG PhFppComputeFreeListIndex( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG NumberOfBlocks + ); + +BOOLEAN PhFppInsertFreeList( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG FreeListIndex, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_SEGMENT_HEADER SegmentHeader + ); + +BOOLEAN PhFppRemoveFreeList( + _Inout_ PPH_FILE_POOL Pool, + _In_ ULONG FreeListIndex, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_SEGMENT_HEADER SegmentHeader + ); + +// Misc. + +PPH_FP_BLOCK_HEADER PhFppGetHeaderBlock( + _In_ PPH_FILE_POOL Pool, + _In_ PVOID Block + ); + +ULONG PhFppEncodeRva( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG SegmentIndex, + _In_ PPH_FP_BLOCK_HEADER FirstBlock, + _In_ PVOID Address + ); + +ULONG PhFppDecodeRva( + _In_ PPH_FILE_POOL Pool, + _In_ ULONG Rva, + _Out_ PULONG SegmentIndex + ); + +#endif diff --git a/phlib/include/filestream.h b/phlib/include/filestream.h new file mode 100644 index 0000000..ec4a207 --- /dev/null +++ b/phlib/include/filestream.h @@ -0,0 +1,204 @@ +#ifndef _PH_FILESTREAM_H +#define _PH_FILESTREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Core flags (PhCreateFileStream2) +/** Indicates that the file stream object should not close the file handle upon deletion. */ +#define PH_FILE_STREAM_HANDLE_UNOWNED 0x1 +/** + * Indicates that the file stream object should not buffer I/O operations. Note that this does not + * prevent the operating system from buffering I/O. + */ +#define PH_FILE_STREAM_UNBUFFERED 0x2 +/** + * Indicates that the file handle supports asynchronous operations. The file handle must not have + * been opened with FILE_SYNCHRONOUS_IO_ALERT or FILE_SYNCHRONOUS_IO_NONALERT. + */ +#define PH_FILE_STREAM_ASYNCHRONOUS 0x4 +/** + * Indicates that the file stream object should maintain the file position and not use the file + * object's own file position. + */ +#define PH_FILE_STREAM_OWN_POSITION 0x8 + +// Higher-level flags (PhCreateFileStream) +#define PH_FILE_STREAM_APPEND 0x00010000 + +// Internal flags +/** Indicates that at least one write has been issued to the file handle. */ +#define PH_FILE_STREAM_WRITTEN 0x80000000 + +// Seek +typedef enum _PH_SEEK_ORIGIN +{ + SeekStart, + SeekCurrent, + SeekEnd +} PH_SEEK_ORIGIN; + +typedef struct _PH_FILE_STREAM +{ + HANDLE FileHandle; + ULONG Flags; + LARGE_INTEGER Position; // file object position, *not* the actual position + + PVOID Buffer; + ULONG BufferLength; + + ULONG ReadPosition; // read position in buffer + ULONG ReadLength; // how much available to read from buffer + ULONG WritePosition; // write position in buffer +} PH_FILE_STREAM, *PPH_FILE_STREAM; + +extern PPH_OBJECT_TYPE PhFileStreamType; + +BOOLEAN +NTAPI +PhFileStreamInitialization( + VOID + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateFileStream( + _Out_ PPH_FILE_STREAM *FileStream, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG ShareMode, + _In_ ULONG CreateDisposition, + _In_ ULONG Flags + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateFileStream2( + _Out_ PPH_FILE_STREAM *FileStream, + _In_ HANDLE FileHandle, + _In_ ULONG Flags, + _In_ ULONG BufferLength + ); + +PHLIBAPI +VOID +NTAPI +PhVerifyFileStream( + _In_ PPH_FILE_STREAM FileStream + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReadLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhFlushFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ BOOLEAN Full + ); + +PHLIBAPI +VOID +NTAPI +PhGetPositionFileStream( + _In_ PPH_FILE_STREAM FileStream, + _Out_ PLARGE_INTEGER Position + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSeekFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Offset, + _In_ PH_SEEK_ORIGIN Origin + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhLockFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Position, + _In_ PLARGE_INTEGER Length, + _In_ BOOLEAN Wait, + _In_ BOOLEAN Shared + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhUnlockFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Position, + _In_ PLARGE_INTEGER Length + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteStringAsUtf8FileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteStringAsUtf8FileStream2( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PWSTR String + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteStringAsUtf8FileStreamEx( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PWSTR Buffer, + _In_ SIZE_T Length + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteStringFormatAsUtf8FileStream_V( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWriteStringFormatAsUtf8FileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ _Printf_format_string_ PWSTR Format, + ... + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/filestreamp.h b/phlib/include/filestreamp.h new file mode 100644 index 0000000..597a400 --- /dev/null +++ b/phlib/include/filestreamp.h @@ -0,0 +1,40 @@ +#ifndef _PH_FILESTREAMP_H +#define _PH_FILESTREAMP_H + +VOID NTAPI PhpFileStreamDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +NTSTATUS PhpAllocateBufferFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ); + +NTSTATUS PhpReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReadLength + ); + +NTSTATUS PhpWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +NTSTATUS PhpFlushReadFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ); + +NTSTATUS PhpFlushWriteFileStream( + _Inout_ PPH_FILE_STREAM FileStream + ); + +NTSTATUS PhpSeekFileStream( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ PLARGE_INTEGER Offset, + _In_ PH_SEEK_ORIGIN Origin + ); + +#endif diff --git a/phlib/include/graph.h b/phlib/include/graph.h new file mode 100644 index 0000000..bc15a12 --- /dev/null +++ b/phlib/include/graph.h @@ -0,0 +1,255 @@ +#ifndef _PH_GRAPH_H +#define _PH_GRAPH_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Graph drawing + +extern RECT PhNormalGraphTextMargin; +extern RECT PhNormalGraphTextPadding; + +#define PH_GRAPH_USE_GRID_X 0x1 +#define PH_GRAPH_USE_GRID_Y 0x2 +#define PH_GRAPH_LOGARITHMIC_GRID_Y 0x4 +#define PH_GRAPH_USE_LINE_2 0x10 +#define PH_GRAPH_OVERLAY_LINE_2 0x20 +#define PH_GRAPH_LABEL_MAX_Y 0x1000 + +typedef PPH_STRING (NTAPI *PPH_GRAPH_LABEL_Y_FUNCTION)( + _In_ struct _PH_GRAPH_DRAW_INFO *DrawInfo, + _In_ ULONG DataIndex, + _In_ FLOAT Value, + _In_ FLOAT Parameter + ); + +typedef struct _PH_GRAPH_DRAW_INFO +{ + // Basic + ULONG Width; + ULONG Height; + ULONG Flags; + ULONG Step; + COLORREF BackColor; + + // Data/lines + ULONG LineDataCount; + PFLOAT LineData1; + PFLOAT LineData2; + COLORREF LineColor1; + COLORREF LineColor2; + COLORREF LineBackColor1; + COLORREF LineBackColor2; + + // Grid + COLORREF GridColor; + ULONG GridWidth; + FLOAT GridHeight; + ULONG GridXOffset; + ULONG GridYThreshold; + FLOAT GridBase; // Base for logarithmic grid + + // y-axis label + PPH_GRAPH_LABEL_Y_FUNCTION LabelYFunction; + FLOAT LabelYFunctionParameter; + HFONT LabelYFont; + COLORREF LabelYColor; + ULONG LabelMaxYIndexLimit; + + // Text + PH_STRINGREF Text; + RECT TextRect; + RECT TextBoxRect; + HFONT TextFont; + COLORREF TextColor; + COLORREF TextBoxColor; +} PH_GRAPH_DRAW_INFO, *PPH_GRAPH_DRAW_INFO; + +// Graph control + +#define PH_GRAPH_CLASSNAME L"PhGraph" + +PHLIBAPI +BOOLEAN PhGraphControlInitialization( + VOID + ); + +PHLIBAPI +VOID PhDrawGraphDirect( + _In_ HDC hdc, + _In_ PVOID Bits, + _In_ PPH_GRAPH_DRAW_INFO DrawInfo + ); + +PHLIBAPI +VOID PhSetGraphText( + _In_ HDC hdc, + _Inout_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ PPH_STRINGREF Text, + _In_ PRECT Margin, + _In_ PRECT Padding, + _In_ ULONG Align + ); + +// Configuration + +typedef struct _PH_GRAPH_OPTIONS +{ + COLORREF FadeOutBackColor; + ULONG FadeOutWidth; + HCURSOR DefaultCursor; +} PH_GRAPH_OPTIONS, *PPH_GRAPH_OPTIONS; + +// Styles + +#define GC_STYLE_FADEOUT 0x1 +#define GC_STYLE_DRAW_PANEL 0x2 + +// Messages + +#define GCM_GETDRAWINFO (WM_USER + 1301) +#define GCM_SETDRAWINFO (WM_USER + 1302) +#define GCM_DRAW (WM_USER + 1303) +#define GCM_MOVEGRID (WM_USER + 1304) +#define GCM_GETBUFFEREDCONTEXT (WM_USER + 1305) +#define GCM_SETTOOLTIP (WM_USER + 1306) +#define GCM_UPDATETOOLTIP (WM_USER + 1307) +#define GCM_GETOPTIONS (WM_USER + 1308) +#define GCM_SETOPTIONS (WM_USER + 1309) + +#define Graph_GetDrawInfo(hWnd, DrawInfo) \ + SendMessage((hWnd), GCM_GETDRAWINFO, 0, (LPARAM)(DrawInfo)) +#define Graph_SetDrawInfo(hWnd, DrawInfo) \ + SendMessage((hWnd), GCM_SETDRAWINFO, 0, (LPARAM)(DrawInfo)) +#define Graph_Draw(hWnd) \ + SendMessage((hWnd), GCM_DRAW, 0, 0) +#define Graph_MoveGrid(hWnd, Increment) \ + SendMessage((hWnd), GCM_MOVEGRID, (WPARAM)(Increment), 0) +#define Graph_GetBufferedContext(hWnd) \ + ((HDC)SendMessage((hWnd), GCM_GETBUFFEREDCONTEXT, 0, 0)) +#define Graph_SetTooltip(hWnd, Enable) \ + ((HDC)SendMessage((hWnd), GCM_SETTOOLTIP, (WPARAM)(Enable), 0)) +#define Graph_UpdateTooltip(hWnd) \ + ((HDC)SendMessage((hWnd), GCM_UPDATETOOLTIP, 0, 0)) +#define Graph_GetOptions(hWnd, Options) \ + SendMessage((hWnd), GCM_GETOPTIONS, 0, (LPARAM)(Options)) +#define Graph_SetOptions(hWnd, Options) \ + SendMessage((hWnd), GCM_SETOPTIONS, 0, (LPARAM)(Options)) + +// Notifications + +#define GCN_GETDRAWINFO (WM_USER + 1351) +#define GCN_GETTOOLTIPTEXT (WM_USER + 1352) +#define GCN_MOUSEEVENT (WM_USER + 1353) +#define GCN_DRAWPANEL (WM_USER + 1354) + +typedef struct _PH_GRAPH_GETDRAWINFO +{ + NMHDR Header; + PPH_GRAPH_DRAW_INFO DrawInfo; +} PH_GRAPH_GETDRAWINFO, *PPH_GRAPH_GETDRAWINFO; + +typedef struct _PH_GRAPH_GETTOOLTIPTEXT +{ + NMHDR Header; + ULONG Index; + ULONG TotalCount; + + PH_STRINGREF Text; // must be null-terminated +} PH_GRAPH_GETTOOLTIPTEXT, *PPH_GRAPH_GETTOOLTIPTEXT; + +typedef struct _PH_GRAPH_MOUSEEVENT +{ + NMHDR Header; + ULONG Index; + ULONG TotalCount; + + ULONG Message; + ULONG Keys; + POINT Point; +} PH_GRAPH_MOUSEEVENT, *PPH_GRAPH_MOUSEEVENT; + +typedef struct _PH_GRAPH_DRAWPANEL +{ + NMHDR Header; + HDC hdc; + RECT Rect; +} PH_GRAPH_DRAWPANEL, *PPH_GRAPH_DRAWPANEL; + +// Graph buffer management + +#define PH_GRAPH_DATA_COUNT(Width, Step) (((Width) + (Step) - 1) / (Step) + 1) // round up in division + +typedef struct _PH_GRAPH_BUFFERS +{ + PFLOAT Data1; // invalidate by setting Valid to FALSE + PFLOAT Data2; // invalidate by setting Valid to FALSE + ULONG AllocatedCount; + BOOLEAN Valid; // indicates the data is valid +} PH_GRAPH_BUFFERS, *PPH_GRAPH_BUFFERS; + +PHLIBAPI +VOID PhInitializeGraphBuffers( + _Out_ PPH_GRAPH_BUFFERS Buffers + ); + +PHLIBAPI +VOID PhDeleteGraphBuffers( + _Inout_ PPH_GRAPH_BUFFERS Buffers + ); + +PHLIBAPI +VOID PhGetDrawInfoGraphBuffers( + _Inout_ PPH_GRAPH_BUFFERS Buffers, + _Inout_ PPH_GRAPH_DRAW_INFO DrawInfo, + _In_ ULONG DataCount + ); + +// Graph control state + +// The basic buffer management structure was moved out of this section because +// the text management is not needed for most cases. + +typedef struct _PH_GRAPH_STATE +{ + // Union for compatibility + union + { + struct + { + PFLOAT Data1; // invalidate by setting Valid to FALSE + PFLOAT Data2; // invalidate by setting Valid to FALSE + ULONG AllocatedCount; + BOOLEAN Valid; // indicates the data is valid + }; + PH_GRAPH_BUFFERS Buffers; + }; + + PPH_STRING Text; + PPH_STRING TooltipText; // invalidate by setting TooltipIndex to -1 + ULONG TooltipIndex; // indicates the tooltip text is valid for this index +} PH_GRAPH_STATE, *PPH_GRAPH_STATE; + +PHLIBAPI +VOID PhInitializeGraphState( + _Out_ PPH_GRAPH_STATE State + ); + +PHLIBAPI +VOID PhDeleteGraphState( + _Inout_ PPH_GRAPH_STATE State + ); + +PHLIBAPI +VOID PhGraphStateGetDrawInfo( + _Inout_ PPH_GRAPH_STATE State, + _In_ PPH_GRAPH_GETDRAWINFO GetDrawInfo, + _In_ ULONG DataCount + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/guisup.h b/phlib/include/guisup.h new file mode 100644 index 0000000..b1fe736 --- /dev/null +++ b/phlib/include/guisup.h @@ -0,0 +1,723 @@ +#ifndef _PH_PHGUI_H +#define _PH_PHGUI_H + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// guisup + +typedef BOOL (WINAPI *_ChangeWindowMessageFilter)( + _In_ UINT message, + _In_ DWORD dwFlag + ); + +typedef BOOL (WINAPI *_IsImmersiveProcess)( + _In_ HANDLE hProcess + ); + +#define RFF_NOBROWSE 0x0001 +#define RFF_NODEFAULT 0x0002 +#define RFF_CALCDIRECTORY 0x0004 +#define RFF_NOLABEL 0x0008 +#define RFF_NOSEPARATEMEM 0x0020 + +#define RFN_VALIDATE (-510) + +typedef struct _NMRUNFILEDLGW +{ + NMHDR hdr; + LPCWSTR lpszFile; + LPCWSTR lpszDirectory; + UINT nShow; +} NMRUNFILEDLGW, *LPNMRUNFILEDLGW, *PNMRUNFILEDLGW; + +typedef NMRUNFILEDLGW NMRUNFILEDLG; +typedef PNMRUNFILEDLGW PNMRUNFILEDLG; +typedef LPNMRUNFILEDLGW LPNMRUNFILEDLG; + +#define RF_OK 0x0000 +#define RF_CANCEL 0x0001 +#define RF_RETRY 0x0002 + +typedef HANDLE HTHEME; + +typedef BOOL (WINAPI *_RunFileDlg)( + _In_ HWND hwndOwner, + _In_opt_ HICON hIcon, + _In_opt_ LPCWSTR lpszDirectory, + _In_opt_ LPCWSTR lpszTitle, + _In_opt_ LPCWSTR lpszDescription, + _In_ ULONG uFlags + ); + +typedef HRESULT (WINAPI *_SHAutoComplete)( + _In_ HWND hwndEdit, + _In_ DWORD dwFlags + ); + +extern _ChangeWindowMessageFilter ChangeWindowMessageFilter_I; +extern _IsImmersiveProcess IsImmersiveProcess_I; +extern _RunFileDlg RunFileDlg; +extern _SHAutoComplete SHAutoComplete_I; + +PHLIBAPI +VOID PhGuiSupportInitialization( + VOID + ); + +PHLIBAPI +VOID PhSetControlTheme( + _In_ HWND Handle, + _In_ PWSTR Theme + ); + +FORCEINLINE VOID PhSetWindowStyle( + _In_ HWND Handle, + _In_ LONG_PTR Mask, + _In_ LONG_PTR Value + ) +{ + LONG_PTR style; + + style = GetWindowLongPtr(Handle, GWL_STYLE); + style = (style & ~Mask) | (Value & Mask); + SetWindowLongPtr(Handle, GWL_STYLE, style); +} + +FORCEINLINE VOID PhSetWindowExStyle( + _In_ HWND Handle, + _In_ LONG_PTR Mask, + _In_ LONG_PTR Value + ) +{ + LONG_PTR style; + + style = GetWindowLongPtr(Handle, GWL_EXSTYLE); + style = (style & ~Mask) | (Value & Mask); + SetWindowLongPtr(Handle, GWL_EXSTYLE, style); +} + +#ifndef WM_REFLECT +#define WM_REFLECT 0x2000 +#endif + +#define REFLECT_MESSAGE(hwnd, msg, wParam, lParam) \ + { \ + LRESULT result_ = PhReflectMessage(hwnd, msg, wParam, lParam); \ + \ + if (result_) \ + return result_; \ + } + +#define REFLECT_MESSAGE_DLG(hwndDlg, hwnd, msg, wParam, lParam) \ + { \ + LRESULT result_ = PhReflectMessage(hwnd, msg, wParam, lParam); \ + \ + if (result_) \ + { \ + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, result_); \ + return TRUE; \ + } \ + } + +FORCEINLINE LRESULT PhReflectMessage( + _In_ HWND Handle, + _In_ UINT Message, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + if (Message == WM_NOTIFY) + { + LPNMHDR header = (LPNMHDR)lParam; + + if (header->hwndFrom == Handle) + return SendMessage(Handle, WM_REFLECT + Message, wParam, lParam); + } + + return 0; +} + +#define PH_DEFINE_MAKE_ATOM(AtomName) \ +do { \ + static UNICODE_STRING atomName = RTL_CONSTANT_STRING(AtomName); \ + static PH_INITONCE initOnce = PH_INITONCE_INIT; \ + static RTL_ATOM atom = 0; \ +\ + if (PhBeginInitOnce(&initOnce)) \ + { \ + NtAddAtom(atomName.Buffer, atomName.Length, &atom); \ + PhEndInitOnce(&initOnce); \ + } \ +\ + if (atom) \ + return (PWSTR)(ULONG_PTR)atom; \ + else \ + return atomName.Buffer; \ +} while (0) + +FORCEINLINE VOID PhSetListViewStyle( + _In_ HWND Handle, + _In_ BOOLEAN AllowDragDrop, + _In_ BOOLEAN ShowLabelTips + ) +{ + ULONG style; + + style = LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP; + + if (AllowDragDrop) + style |= LVS_EX_HEADERDRAGDROP; + if (ShowLabelTips) + style |= LVS_EX_LABELTIP; + + ListView_SetExtendedListViewStyleEx( + Handle, + style, + -1 + ); +} + +PHLIBAPI +INT PhAddListViewColumn( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT DisplayIndex, + _In_ INT SubItemIndex, + _In_ INT Format, + _In_ INT Width, + _In_ PWSTR Text + ); + +PHLIBAPI +INT PhAddListViewItem( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ PWSTR Text, + _In_opt_ PVOID Param + ); + +PHLIBAPI +INT PhFindListViewItemByFlags( + _In_ HWND ListViewHandle, + _In_ INT StartIndex, + _In_ ULONG Flags + ); + +PHLIBAPI +INT PhFindListViewItemByParam( + _In_ HWND ListViewHandle, + _In_ INT StartIndex, + _In_opt_ PVOID Param + ); + +PHLIBAPI +LOGICAL PhGetListViewItemImageIndex( + _In_ HWND ListViewHandle, + _In_ INT Index, + _Out_ PINT ImageIndex + ); + +PHLIBAPI +LOGICAL PhGetListViewItemParam( + _In_ HWND ListViewHandle, + _In_ INT Index, + _Out_ PVOID *Param + ); + +PHLIBAPI +VOID PhRemoveListViewItem( + _In_ HWND ListViewHandle, + _In_ INT Index + ); + +PHLIBAPI +VOID PhSetListViewItemImageIndex( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT ImageIndex + ); + +PHLIBAPI +VOID PhSetListViewSubItem( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ INT SubItemIndex, + _In_ PWSTR Text + ); + +PHLIBAPI +BOOLEAN PhLoadListViewColumnSettings( + _In_ HWND ListViewHandle, + _In_ PPH_STRING Settings + ); + +PHLIBAPI +PPH_STRING PhSaveListViewColumnSettings( + _In_ HWND ListViewHandle + ); + +PHLIBAPI +INT PhAddTabControlTab( + _In_ HWND TabControlHandle, + _In_ INT Index, + _In_ PWSTR Text + ); + +#define PhaGetDlgItemText(hwndDlg, id) \ + PH_AUTO_T(PH_STRING, PhGetWindowText(GetDlgItem(hwndDlg, id))) + +PHLIBAPI +PPH_STRING PhGetWindowText( + _In_ HWND hwnd + ); + +#define PH_GET_WINDOW_TEXT_INTERNAL 0x1 +#define PH_GET_WINDOW_TEXT_LENGTH_ONLY 0x2 + +PHLIBAPI +ULONG PhGetWindowTextEx( + _In_ HWND hwnd, + _In_ ULONG Flags, + _Out_opt_ PPH_STRING *Text + ); + +PHLIBAPI +VOID PhAddComboBoxStrings( + _In_ HWND hWnd, + _In_ PWSTR *Strings, + _In_ ULONG NumberOfStrings + ); + +PHLIBAPI +PPH_STRING PhGetComboBoxString( + _In_ HWND hwnd, + _In_ INT Index + ); + +PHLIBAPI +INT PhSelectComboBoxString( + _In_ HWND hwnd, + _In_ PWSTR String, + _In_ BOOLEAN Partial + ); + +PHLIBAPI +PPH_STRING PhGetListBoxString( + _In_ HWND hwnd, + _In_ INT Index + ); + +PHLIBAPI +VOID PhSetStateAllListViewItems( + _In_ HWND hWnd, + _In_ ULONG State, + _In_ ULONG Mask + ); + +PHLIBAPI +PVOID PhGetSelectedListViewItemParam( + _In_ HWND hWnd + ); + +PHLIBAPI +VOID PhGetSelectedListViewItemParams( + _In_ HWND hWnd, + _Out_ PVOID **Items, + _Out_ PULONG NumberOfItems + ); + +PHLIBAPI +VOID PhSetImageListBitmap( + _In_ HIMAGELIST ImageList, + _In_ INT Index, + _In_ HINSTANCE InstanceHandle, + _In_ LPCWSTR BitmapName + ); + +#define PH_LOAD_ICON_SHARED 0x1 +#define PH_LOAD_ICON_SIZE_SMALL 0x2 +#define PH_LOAD_ICON_SIZE_LARGE 0x4 +#define PH_LOAD_ICON_STRICT 0x8 + +PHLIBAPI +HICON PhLoadIcon( + _In_opt_ HINSTANCE InstanceHandle, + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ ULONG Width, + _In_opt_ ULONG Height + ); + +PHLIBAPI +VOID PhGetStockApplicationIcon( + _Out_opt_ HICON *SmallIcon, + _Out_opt_ HICON *LargeIcon + ); + +PHLIBAPI +HICON PhGetFileShellIcon( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR DefaultExtension, + _In_ BOOLEAN LargeIcon + ); + +PHLIBAPI +VOID PhSetClipboardString( + _In_ HWND hWnd, + _In_ PPH_STRINGREF String + ); + +typedef struct _DLGTEMPLATEEX +{ + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +} DLGTEMPLATEEX, *PDLGTEMPLATEEX; + +PHLIBAPI +HWND PhCreateDialogFromTemplate( + _In_ HWND Parent, + _In_ ULONG Style, + _In_ PVOID Instance, + _In_ PWSTR Template, + _In_ DLGPROC DialogProc, + _In_ PVOID Parameter + ); + +PHLIBAPI +BOOLEAN PhModalPropertySheet( + _Inout_ PROPSHEETHEADER *Header + ); + +#define PH_ANCHOR_LEFT 0x1 +#define PH_ANCHOR_TOP 0x2 +#define PH_ANCHOR_RIGHT 0x4 +#define PH_ANCHOR_BOTTOM 0x8 +#define PH_ANCHOR_ALL 0xf + +// This interface is horrible and should be rewritten, but it works for now. + +#define PH_LAYOUT_FORCE_INVALIDATE 0x1000 // invalidate the control when it is resized +#define PH_LAYOUT_TAB_CONTROL 0x2000 // this is a dummy item, a hack for the tab control +#define PH_LAYOUT_IMMEDIATE_RESIZE 0x4000 // needed for the tab control hack + +#define PH_LAYOUT_DUMMY_MASK (PH_LAYOUT_TAB_CONTROL) // items that don't have a window handle, or don't actually get their window resized + +typedef struct _PH_LAYOUT_ITEM +{ + HWND Handle; + struct _PH_LAYOUT_ITEM *ParentItem; // for rectangle calculation + struct _PH_LAYOUT_ITEM *LayoutParentItem; // for actual resizing + ULONG LayoutNumber; + ULONG NumberOfChildren; + HDWP DeferHandle; + + RECT Rect; + RECT OrigRect; + RECT Margin; + ULONG Anchor; +} PH_LAYOUT_ITEM, *PPH_LAYOUT_ITEM; + +typedef struct _PH_LAYOUT_MANAGER +{ + PPH_LIST List; + PH_LAYOUT_ITEM RootItem; + + ULONG LayoutNumber; +} PH_LAYOUT_MANAGER, *PPH_LAYOUT_MANAGER; + +PHLIBAPI +VOID PhInitializeLayoutManager( + _Out_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND RootWindowHandle + ); + +PHLIBAPI +VOID PhDeleteLayoutManager( + _Inout_ PPH_LAYOUT_MANAGER Manager + ); + +PHLIBAPI +PPH_LAYOUT_ITEM PhAddLayoutItem( + _Inout_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND Handle, + _In_opt_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor + ); + +PHLIBAPI +PPH_LAYOUT_ITEM PhAddLayoutItemEx( + _Inout_ PPH_LAYOUT_MANAGER Manager, + _In_ HWND Handle, + _In_opt_ PPH_LAYOUT_ITEM ParentItem, + _In_ ULONG Anchor, + _In_ RECT Margin + ); + +PHLIBAPI +VOID PhLayoutManagerLayout( + _Inout_ PPH_LAYOUT_MANAGER Manager + ); + +FORCEINLINE VOID PhResizingMinimumSize( + _Inout_ PRECT Rect, + _In_ WPARAM Edge, + _In_ LONG MinimumWidth, + _In_ LONG MinimumHeight + ) +{ + if (Edge == WMSZ_BOTTOMRIGHT || Edge == WMSZ_RIGHT || Edge == WMSZ_TOPRIGHT) + { + if (Rect->right - Rect->left < MinimumWidth) + Rect->right = Rect->left + MinimumWidth; + } + else if (Edge == WMSZ_BOTTOMLEFT || Edge == WMSZ_LEFT || Edge == WMSZ_TOPLEFT) + { + if (Rect->right - Rect->left < MinimumWidth) + Rect->left = Rect->right - MinimumWidth; + } + + if (Edge == WMSZ_BOTTOMRIGHT || Edge == WMSZ_BOTTOM || Edge == WMSZ_BOTTOMLEFT) + { + if (Rect->bottom - Rect->top < MinimumHeight) + Rect->bottom = Rect->top + MinimumHeight; + } + else if (Edge == WMSZ_TOPRIGHT || Edge == WMSZ_TOP || Edge == WMSZ_TOPLEFT) + { + if (Rect->bottom - Rect->top < MinimumHeight) + Rect->top = Rect->bottom - MinimumHeight; + } +} + +FORCEINLINE VOID PhCopyControlRectangle( + _In_ HWND ParentWindowHandle, + _In_ HWND FromControlHandle, + _In_ HWND ToControlHandle + ) +{ + RECT windowRect; + + GetWindowRect(FromControlHandle, &windowRect); + MapWindowPoints(NULL, ParentWindowHandle, (POINT *)&windowRect, 2); + MoveWindow(ToControlHandle, windowRect.left, windowRect.top, + windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, FALSE); +} + +// icotobmp + +PHLIBAPI +HBITMAP +NTAPI +PhIconToBitmap( + _In_ HICON Icon, + _In_ ULONG Width, + _In_ ULONG Height + ); + +// extlv + +#define PH_ALIGN_CENTER 0x0 +#define PH_ALIGN_LEFT 0x1 +#define PH_ALIGN_RIGHT 0x2 +#define PH_ALIGN_TOP 0x4 +#define PH_ALIGN_BOTTOM 0x8 + +typedef enum _PH_ITEM_STATE +{ + // The item is normal. Use the ItemColorFunction to determine the color of the item. + NormalItemState = 0, + // The item is new. On the next tick, change the state to NormalItemState. When an item is in + // this state, highlight it in NewColor. + NewItemState, + // The item is being removed. On the next tick, delete the item. When an item is in this state, + // highlight it in RemovingColor. + RemovingItemState +} PH_ITEM_STATE; + +typedef COLORREF (NTAPI *PPH_EXTLV_GET_ITEM_COLOR)( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ); + +typedef HFONT (NTAPI *PPH_EXTLV_GET_ITEM_FONT)( + _In_ INT Index, + _In_ PVOID Param, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID +NTAPI +PhSetExtendedListView( + _In_ HWND hWnd + ); + +PHLIBAPI +VOID +NTAPI +PhSetHeaderSortIcon( + _In_ HWND hwnd, + _In_ INT Index, + _In_ PH_SORT_ORDER Order + ); + +// next 1122 + +#define ELVM_ADDFALLBACKCOLUMN (WM_APP + 1106) +#define ELVM_ADDFALLBACKCOLUMNS (WM_APP + 1109) +#define ELVM_RESERVED5 (WM_APP + 1120) +#define ELVM_INIT (WM_APP + 1102) +#define ELVM_SETCOLUMNWIDTH (WM_APP + 1121) +#define ELVM_SETCOMPAREFUNCTION (WM_APP + 1104) +#define ELVM_SETCONTEXT (WM_APP + 1103) +#define ELVM_SETCURSOR (WM_APP + 1114) +#define ELVM_RESERVED4 (WM_APP + 1118) +#define ELVM_SETITEMCOLORFUNCTION (WM_APP + 1111) +#define ELVM_SETITEMFONTFUNCTION (WM_APP + 1117) +#define ELVM_RESERVED1 (WM_APP + 1112) +#define ELVM_SETREDRAW (WM_APP + 1116) +#define ELVM_RESERVED2 (WM_APP + 1113) +#define ELVM_SETSORT (WM_APP + 1108) +#define ELVM_SETSORTFAST (WM_APP + 1119) +#define ELVM_RESERVED0 (WM_APP + 1110) +#define ELVM_SETTRISTATE (WM_APP + 1107) +#define ELVM_SETTRISTATECOMPAREFUNCTION (WM_APP + 1105) +#define ELVM_SORTITEMS (WM_APP + 1101) +#define ELVM_RESERVED3 (WM_APP + 1115) + +#define ExtendedListView_AddFallbackColumn(hWnd, Column) \ + SendMessage((hWnd), ELVM_ADDFALLBACKCOLUMN, (WPARAM)(Column), 0) +#define ExtendedListView_AddFallbackColumns(hWnd, NumberOfColumns, Columns) \ + SendMessage((hWnd), ELVM_ADDFALLBACKCOLUMNS, (WPARAM)(NumberOfColumns), (LPARAM)(Columns)) +#define ExtendedListView_Init(hWnd) \ + SendMessage((hWnd), ELVM_INIT, 0, 0) +#define ExtendedListView_SetColumnWidth(hWnd, Column, Width) \ + SendMessage((hWnd), ELVM_SETCOLUMNWIDTH, (WPARAM)(Column), (LPARAM)(Width)) +#define ExtendedListView_SetCompareFunction(hWnd, Column, CompareFunction) \ + SendMessage((hWnd), ELVM_SETCOMPAREFUNCTION, (WPARAM)(Column), (LPARAM)(CompareFunction)) +#define ExtendedListView_SetContext(hWnd, Context) \ + SendMessage((hWnd), ELVM_SETCONTEXT, 0, (LPARAM)(Context)) +#define ExtendedListView_SetCursor(hWnd, Cursor) \ + SendMessage((hWnd), ELVM_SETCURSOR, 0, (LPARAM)(Cursor)) +#define ExtendedListView_SetItemColorFunction(hWnd, ItemColorFunction) \ + SendMessage((hWnd), ELVM_SETITEMCOLORFUNCTION, 0, (LPARAM)(ItemColorFunction)) +#define ExtendedListView_SetItemFontFunction(hWnd, ItemFontFunction) \ + SendMessage((hWnd), ELVM_SETITEMFONTFUNCTION, 0, (LPARAM)(ItemFontFunction)) +#define ExtendedListView_SetRedraw(hWnd, Redraw) \ + SendMessage((hWnd), ELVM_SETREDRAW, (WPARAM)(Redraw), 0) +#define ExtendedListView_SetSort(hWnd, Column, Order) \ + SendMessage((hWnd), ELVM_SETSORT, (WPARAM)(Column), (LPARAM)(Order)) +#define ExtendedListView_SetSortFast(hWnd, Fast) \ + SendMessage((hWnd), ELVM_SETSORTFAST, (WPARAM)(Fast), 0) +#define ExtendedListView_SetTriState(hWnd, TriState) \ + SendMessage((hWnd), ELVM_SETTRISTATE, (WPARAM)(TriState), 0) +#define ExtendedListView_SetTriStateCompareFunction(hWnd, CompareFunction) \ + SendMessage((hWnd), ELVM_SETTRISTATECOMPAREFUNCTION, 0, (LPARAM)(CompareFunction)) +#define ExtendedListView_SortItems(hWnd) \ + SendMessage((hWnd), ELVM_SORTITEMS, 0, 0) + +#define ELVSCW_AUTOSIZE (-1) +#define ELVSCW_AUTOSIZE_USEHEADER (-2) +#define ELVSCW_AUTOSIZE_REMAININGSPACE (-3) + +/** + * Gets the brightness of a color. + * + * \param Color The color. + * + * \return A value ranging from 0 to 255, indicating the brightness of the color. + */ +FORCEINLINE +ULONG +PhGetColorBrightness( + _In_ COLORREF Color + ) +{ + ULONG r = Color & 0xff; + ULONG g = (Color >> 8) & 0xff; + ULONG b = (Color >> 16) & 0xff; + ULONG min; + ULONG max; + + min = r; + if (g < min) min = g; + if (b < min) min = b; + + max = r; + if (g > max) max = g; + if (b > max) max = b; + + return (min + max) / 2; +} + +FORCEINLINE +COLORREF +PhHalveColorBrightness( + _In_ COLORREF Color + ) +{ + /*return RGB( + (UCHAR)Color / 2, + (UCHAR)(Color >> 8) / 2, + (UCHAR)(Color >> 16) / 2 + );*/ + // Since all targets are little-endian, we can use the following method. + *((PUCHAR)&Color) /= 2; + *((PUCHAR)&Color + 1) /= 2; + *((PUCHAR)&Color + 2) /= 2; + + return Color; +} + +FORCEINLINE +COLORREF +PhMakeColorBrighter( + _In_ COLORREF Color, + _In_ UCHAR Increment + ) +{ + UCHAR r; + UCHAR g; + UCHAR b; + + r = (UCHAR)Color; + g = (UCHAR)(Color >> 8); + b = (UCHAR)(Color >> 16); + + if (r <= 255 - Increment) + r += Increment; + else + r = 255; + + if (g <= 255 - Increment) + g += Increment; + else + g = 255; + + if (b <= 255 - Increment) + b += Increment; + else + b = 255; + + return RGB(r, g, b); +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/phlib/include/guisupp.h b/phlib/include/guisupp.h new file mode 100644 index 0000000..a2372bd --- /dev/null +++ b/phlib/include/guisupp.h @@ -0,0 +1,43 @@ +#ifndef _PH_GUISUPP_H +#define _PH_GUISUPP_H + +typedef HRESULT (WINAPI *_LoadIconMetric)( + _In_ HINSTANCE hinst, + _In_ PCWSTR pszName, + _In_ int lims, + _Out_ HICON *phico + ); + +typedef HRESULT (WINAPI *_LoadIconWithScaleDown)( + _In_ HINSTANCE hinst, + _In_ PCWSTR pszName, + _In_ int cx, + _In_ int cy, + _Out_ HICON *phico + ); + +typedef struct _PHP_ICON_ENTRY +{ + HINSTANCE InstanceHandle; + PWSTR Name; + ULONG Width; + ULONG Height; + HICON Icon; +} PHP_ICON_ENTRY, *PPHP_ICON_ENTRY; + +#define PHP_ICON_ENTRY_SIZE_SMALL (-1) +#define PHP_ICON_ENTRY_SIZE_LARGE (-2) + +FORCEINLINE ULONG PhpGetIconEntrySize( + _In_ ULONG InputSize, + _In_ ULONG Flags + ) +{ + if (Flags & PH_LOAD_ICON_SIZE_SMALL) + return PHP_ICON_ENTRY_SIZE_SMALL; + if (Flags & PH_LOAD_ICON_SIZE_LARGE) + return PHP_ICON_ENTRY_SIZE_LARGE; + return InputSize; +} + +#endif diff --git a/phlib/include/handle.h b/phlib/include/handle.h new file mode 100644 index 0000000..10a3f1f --- /dev/null +++ b/phlib/include/handle.h @@ -0,0 +1,167 @@ +#ifndef _PH_HANDLE_H +#define _PH_HANDLE_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct _PH_HANDLE_TABLE; +typedef struct _PH_HANDLE_TABLE *PPH_HANDLE_TABLE; + +typedef struct _PH_HANDLE_TABLE_ENTRY +{ + union + { + PVOID Object; + ULONG_PTR Value; + struct + { + /** The type of the entry; 1 if the entry is free, otherwise 0 if the entry is in use. */ + ULONG_PTR Type : 1; + /** + * Whether the entry is not locked; 1 if the entry is not locked, otherwise 0 if the + * entry is locked. + */ + ULONG_PTR Locked : 1; + ULONG_PTR Value : sizeof(ULONG_PTR) * 8 - 2; + } TypeAndValue; + }; + union + { + ACCESS_MASK GrantedAccess; + ULONG NextFreeValue; + ULONG_PTR Value2; + }; +} PH_HANDLE_TABLE_ENTRY, *PPH_HANDLE_TABLE_ENTRY; + +#define PH_HANDLE_TABLE_SAFE +#define PH_HANDLE_TABLE_FREE_COUNT 64 + +#define PH_HANDLE_TABLE_STRICT_FIFO 0x1 +#define PH_HANDLE_TABLE_VALID_FLAGS 0x1 + +PHLIBAPI +PPH_HANDLE_TABLE +NTAPI +PhCreateHandleTable( + VOID + ); + +PHLIBAPI +VOID +NTAPI +PhDestroyHandleTable( + _In_ _Post_invalid_ PPH_HANDLE_TABLE HandleTable + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhLockHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +PHLIBAPI +VOID +NTAPI +PhUnlockHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +PHLIBAPI +HANDLE +NTAPI +PhCreateHandle( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhDestroyHandle( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ HANDLE Handle, + _In_opt_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +PHLIBAPI +PPH_HANDLE_TABLE_ENTRY +NTAPI +PhLookupHandleTableEntry( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ HANDLE Handle + ); + +typedef BOOLEAN (NTAPI *PPH_ENUM_HANDLE_TABLE_CALLBACK)( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ HANDLE Handle, + _In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID +NTAPI +PhEnumHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID +NTAPI +PhSweepHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_ENUM_HANDLE_TABLE_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +typedef enum _PH_HANDLE_TABLE_INFORMATION_CLASS +{ + HandleTableBasicInformation, + HandleTableFlagsInformation, + MaxHandleTableInfoClass +} PH_HANDLE_TABLE_INFORMATION_CLASS; + +typedef struct _PH_HANDLE_TABLE_BASIC_INFORMATION +{ + ULONG Count; + ULONG Flags; + ULONG TableLevel; +} PH_HANDLE_TABLE_BASIC_INFORMATION, *PPH_HANDLE_TABLE_BASIC_INFORMATION; + +typedef struct _PH_HANDLE_TABLE_FLAGS_INFORMATION +{ + ULONG Flags; +} PH_HANDLE_TABLE_FLAGS_INFORMATION, *PPH_HANDLE_TABLE_FLAGS_INFORMATION; + +PHLIBAPI +NTSTATUS +NTAPI +PhQueryInformationHandleTable( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass, + _Out_writes_bytes_opt_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetInformationHandleTable( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PH_HANDLE_TABLE_INFORMATION_CLASS InformationClass, + _In_reads_bytes_(BufferLength) PVOID Buffer, + _In_ ULONG BufferLength + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/handlep.h b/phlib/include/handlep.h new file mode 100644 index 0000000..ed1b69d --- /dev/null +++ b/phlib/include/handlep.h @@ -0,0 +1,147 @@ +#ifndef _PH_HANDLEP_H +#define _PH_HANDLEP_H + +#define PH_HANDLE_TABLE_ENTRY_TYPE 0x1 +#define PH_HANDLE_TABLE_ENTRY_IN_USE 0x0 +#define PH_HANDLE_TABLE_ENTRY_FREE 0x1 + +// Locked actually means Not Locked. This means that an in use, locked handle table entry can be +// used as-is. +#define PH_HANDLE_TABLE_ENTRY_LOCKED 0x2 +#define PH_HANDLE_TABLE_ENTRY_LOCKED_SHIFT 1 + +// There is initially one handle table level, with 256 entries. When the handle table is expanded, +// the table is replaced with a level 1 table, which contains 256 pointers to level 0 tables (the +// first entry already points to the initial level 0 table). Similarly, when the handle table is +// expanded a second time, the table is replaced with a level 2 table, which contains 256 pointers +// to level 1 tables. +// +// This provides a maximum of 16,777,216 handles. + +#define PH_HANDLE_TABLE_LEVEL_ENTRIES 256 +#define PH_HANDLE_TABLE_LEVEL_MASK 0x3 + +#define PH_HANDLE_TABLE_LOCKS 8 +#define PH_HANDLE_TABLE_LOCK_INDEX(HandleValue) ((HandleValue) % PH_HANDLE_TABLE_LOCKS) + +typedef struct _PH_HANDLE_TABLE +{ + PH_QUEUED_LOCK Lock; + PH_WAKE_EVENT HandleWakeEvent; + + ULONG Count; + ULONG_PTR TableValue; + ULONG FreeValue; + ULONG NextValue; + ULONG FreeValueAlt; + + ULONG Flags; + + PH_QUEUED_LOCK Locks[PH_HANDLE_TABLE_LOCKS]; +} PH_HANDLE_TABLE, *PPH_HANDLE_TABLE; + +FORCEINLINE VOID PhpLockHandleTableShared( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG Index + ) +{ + PhAcquireQueuedLockShared(&HandleTable->Locks[Index]); +} + +FORCEINLINE VOID PhpUnlockHandleTableShared( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG Index + ) +{ + PhReleaseQueuedLockShared(&HandleTable->Locks[Index]); +} + +// Handle values work by specifying indicies into each +// level. +// +// Bits 0-7: level 0 +// Bits 8-15: level 1 +// Bits 16-23: level 2 +// Bits 24-31: reserved + +#define PH_HANDLE_VALUE_INVALID ((ULONG)-1) +#define PH_HANDLE_VALUE_SHIFT 2 +#define PH_HANDLE_VALUE_BIAS 4 + +#define PH_HANDLE_VALUE_LEVEL0(HandleValue) ((HandleValue) & 0xff) +#define PH_HANDLE_VALUE_LEVEL1_U(HandleValue) ((HandleValue) >> 8) +#define PH_HANDLE_VALUE_LEVEL1(HandleValue) (PH_HANDLE_VALUE_LEVEL1_U(HandleValue) & 0xff) +#define PH_HANDLE_VALUE_LEVEL2_U(HandleValue) ((HandleValue) >> 16) +#define PH_HANDLE_VALUE_LEVEL2(HandleValue) (PH_HANDLE_VALUE_LEVEL2_U(HandleValue) & 0xff) +#define PH_HANDLE_VALUE_IS_INVALID(HandleValue) (((HandleValue) >> 24) != 0) + +FORCEINLINE HANDLE PhpEncodeHandle( + _In_ ULONG HandleValue + ) +{ + return UlongToHandle(((HandleValue << PH_HANDLE_VALUE_SHIFT) + PH_HANDLE_VALUE_BIAS)); +} + +FORCEINLINE ULONG PhpDecodeHandle( + _In_ HANDLE Handle + ) +{ + return (HandleToUlong(Handle) - PH_HANDLE_VALUE_BIAS) >> PH_HANDLE_VALUE_SHIFT; +} + +VOID PhpBlockOnLockedHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +PPH_HANDLE_TABLE_ENTRY PhpAllocateHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _Out_ PULONG HandleValue + ); + +VOID PhpFreeHandleTableEntry( + _Inout_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG HandleValue, + _Inout_ PPH_HANDLE_TABLE_ENTRY HandleTableEntry + ); + +BOOLEAN PhpAllocateMoreHandleTableEntries( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ BOOLEAN Initialize + ); + +PPH_HANDLE_TABLE_ENTRY PhpLookupHandleTableEntry( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ ULONG HandleValue + ); + +ULONG PhpMoveFreeHandleTableEntries( + _Inout_ PPH_HANDLE_TABLE HandleTable + ); + +PPH_HANDLE_TABLE_ENTRY PhpCreateHandleTableLevel0( + _In_ PPH_HANDLE_TABLE HandleTable, + _In_ BOOLEAN Initialize + ); + +VOID PhpFreeHandleTableLevel0( + _In_ PPH_HANDLE_TABLE_ENTRY Table + ); + +PPH_HANDLE_TABLE_ENTRY *PhpCreateHandleTableLevel1( + _In_ PPH_HANDLE_TABLE HandleTable + ); + +VOID PhpFreeHandleTableLevel1( + _In_ PPH_HANDLE_TABLE_ENTRY *Table + ); + +PPH_HANDLE_TABLE_ENTRY **PhpCreateHandleTableLevel2( + _In_ PPH_HANDLE_TABLE HandleTable + ); + +VOID PhpFreeHandleTableLevel2( + _In_ PPH_HANDLE_TABLE_ENTRY **Table + ); + +#endif diff --git a/phlib/include/hexedit.h b/phlib/include/hexedit.h new file mode 100644 index 0000000..3fbd74a --- /dev/null +++ b/phlib/include/hexedit.h @@ -0,0 +1,49 @@ +#ifndef _PH_HEXEDIT_H +#define _PH_HEXEDIT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_HEXEDIT_CLASSNAME L"PhHexEdit" + +#define EDIT_NONE 0 +#define EDIT_ASCII 1 +#define EDIT_HIGH 2 +#define EDIT_LOW 3 + +PHLIBAPI +BOOLEAN PhHexEditInitialization( + VOID + ); + +#define HEM_SETBUFFER (WM_USER + 1) +#define HEM_SETDATA (WM_USER + 2) +#define HEM_GETBUFFER (WM_USER + 3) +#define HEM_SETSEL (WM_USER + 4) +#define HEM_SETEDITMODE (WM_USER + 5) +#define HEM_SETBYTESPERROW (WM_USER + 6) + +#define HexEdit_SetBuffer(hWnd, Buffer, Length) \ + SendMessage((hWnd), HEM_SETBUFFER, (WPARAM)(Length), (LPARAM)(Buffer)) + +#define HexEdit_SetData(hWnd, Buffer, Length) \ + SendMessage((hWnd), HEM_SETDATA, (WPARAM)(Length), (LPARAM)(Buffer)) + +#define HexEdit_GetBuffer(hWnd, Length) \ + ((PUCHAR)SendMessage((hWnd), HEM_GETBUFFER, (WPARAM)(Length), 0)) + +#define HexEdit_SetSel(hWnd, Start, End) \ + SendMessage((hWnd), HEM_SETSEL, (WPARAM)(Start), (LPARAM)(End)) + +#define HexEdit_SetEditMode(hWnd, Mode) \ + SendMessage((hWnd), HEM_SETEDITMODE, (WPARAM)(Mode), 0) + +#define HexEdit_SetBytesPerRow(hWnd, BytesPerRow) \ + SendMessage((hWnd), HEM_SETBYTESPERROW, (WPARAM)(BytesPerRow), 0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/hexeditp.h b/phlib/include/hexeditp.h new file mode 100644 index 0000000..53bb4f0 --- /dev/null +++ b/phlib/include/hexeditp.h @@ -0,0 +1,201 @@ +#ifndef _PH_HEXEDITP_H +#define _PH_HEXEDITP_H + +typedef struct _PHP_HEXEDIT_CONTEXT +{ + PUCHAR Data; + LONG Length; + BOOLEAN UserBuffer; + LONG TopIndex; // index of first visible byte on screen + + LONG CurrentAddress; + LONG CurrentMode; + LONG SelStart; + LONG SelEnd; + + LONG BytesPerRow; + LONG LinesPerPage; + BOOLEAN ShowAddress; + BOOLEAN ShowAscii; + BOOLEAN ShowHex; + BOOLEAN AddressIsWide; + BOOLEAN AllowLengthChange; + + BOOLEAN NoAddressChange; + BOOLEAN HalfPage; + + HFONT Font; + LONG LineHeight; + LONG NullWidth; + PWCHAR CharBuffer; + ULONG CharBufferLength; + BOOLEAN Update; + + LONG HexOffset; + LONG AsciiOffset; + LONG AddressOffset; + + BOOLEAN HasCapture; + POINT EditPosition; +} PHP_HEXEDIT_CONTEXT, *PPHP_HEXEDIT_CONTEXT; + +#define IS_PRINTABLE(Byte) ((ULONG)((Byte) - ' ') <= (ULONG)('~' - ' ')) + +#define TO_HEX(Buffer, Byte) \ +{ \ + *(Buffer)++ = PhIntegerToChar[(Byte) >> 4]; \ + *(Buffer)++ = PhIntegerToChar[(Byte) & 0xf]; \ +} + +#define REDRAW_WINDOW(hwnd) \ + RedrawWindow((hwnd), NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE) + +VOID PhpCreateHexEditContext( + _Out_ PPHP_HEXEDIT_CONTEXT *Context + ); + +VOID PhpFreeHexEditContext( + _In_ _Post_invalid_ PPHP_HEXEDIT_CONTEXT Context + ); + +LRESULT CALLBACK PhpHexEditWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID PhpHexEditUpdateMetrics( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ BOOLEAN UpdateLineHeight, + _In_opt_ HDC hdc + ); + +VOID PhpHexEditOnPaint( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PAINTSTRUCT *PaintStruct, + _In_ HDC hdc + ); + +VOID PhpHexEditUpdateScrollbars( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +FORCEINLINE BOOLEAN PhpHexEditHasSelected( + _In_ PPHP_HEXEDIT_CONTEXT Context + ) +{ + return Context->SelStart != -1; +} + +VOID PhpHexEditCreateAddressCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditCreateEditCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditRepositionCaret( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG Position + ); + +VOID PhpHexEditCalculatePosition( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG X, + _In_ LONG Y, + _Out_ POINT *Point + ); + +VOID PhpHexEditMove( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG X, + _In_ LONG Y + ); + +VOID PhpHexEditSetSel( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG E + ); + +VOID PhpHexEditScrollTo( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG Position + ); + +VOID PhpHexEditClearEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditCopyEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditCutEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditPasteEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditSelectAll( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditUndoEdit( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditNormalizeSel( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context + ); + +VOID PhpHexEditSelDelete( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG E + ); + +VOID PhpHexEditSelInsert( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ LONG S, + _In_ LONG L + ); + +VOID PhpHexEditSetBuffer( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PUCHAR Data, + _In_ ULONG Length + ); + +VOID PhpHexEditSetData( + _In_ HWND hwnd, + _In_ PPHP_HEXEDIT_CONTEXT Context, + _In_ PUCHAR Data, + _In_ ULONG Length + ); + +#endif diff --git a/phlib/include/hndlinfo.h b/phlib/include/hndlinfo.h new file mode 100644 index 0000000..c92bd4b --- /dev/null +++ b/phlib/include/hndlinfo.h @@ -0,0 +1,138 @@ +#ifndef _PH_HNDLINFO_H +#define _PH_HNDLINFO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_OBJECT_TYPE_NUMBER 257 + +typedef PPH_STRING (NTAPI *PPH_GET_CLIENT_ID_NAME)( + _In_ PCLIENT_ID ClientId + ); + +PHLIBAPI +PPH_GET_CLIENT_ID_NAME +NTAPI +PhSetHandleClientIdFunction( + _In_ PPH_GET_CLIENT_ID_NAME GetClientIdName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatNativeKeyName( + _In_ PPH_STRING Name + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetSectionFileName( + _In_ HANDLE SectionHandle, + _Out_ PPH_STRING *FileName + ); + +PHLIBAPI +_Callback_ PPH_STRING +NTAPI +PhStdGetClientIdName( + _In_ PCLIENT_ID ClientId + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetHandleInformation( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ ULONG ObjectTypeNumber, + _Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation, + _Out_opt_ PPH_STRING *TypeName, + _Out_opt_ PPH_STRING *ObjectName, + _Out_opt_ PPH_STRING *BestObjectName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetHandleInformationEx( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ ULONG ObjectTypeNumber, + _Reserved_ ULONG Flags, + _Out_opt_ PNTSTATUS SubStatus, + _Out_opt_ POBJECT_BASIC_INFORMATION BasicInformation, + _Out_opt_ PPH_STRING *TypeName, + _Out_opt_ PPH_STRING *ObjectName, + _Out_opt_ PPH_STRING *BestObjectName, + _Reserved_ PVOID *ExtraInformation + ); + +#define PH_FIRST_OBJECT_TYPE(ObjectTypes) \ + (POBJECT_TYPE_INFORMATION)((PCHAR)(ObjectTypes) + ALIGN_UP(sizeof(OBJECT_TYPES_INFORMATION), ULONG_PTR)) + +#define PH_NEXT_OBJECT_TYPE(ObjectType) \ + (POBJECT_TYPE_INFORMATION)((PCHAR)(ObjectType) + sizeof(OBJECT_TYPE_INFORMATION) + \ + ALIGN_UP(ObjectType->TypeName.MaximumLength, ULONG_PTR)) + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumObjectTypes( + _Out_ POBJECT_TYPES_INFORMATION *ObjectTypes + ); + +PHLIBAPI +ULONG +NTAPI +PhGetObjectTypeNumber( + _In_ PUNICODE_STRING TypeName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCallWithTimeout( + _In_ PUSER_THREAD_START_ROUTINE Routine, + _In_opt_ PVOID Context, + _In_opt_ PLARGE_INTEGER AcquireTimeout, + _In_ PLARGE_INTEGER CallTimeout + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCallNtQueryObjectWithTimeout( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCallNtQuerySecurityObjectWithTimeout( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_writes_bytes_opt_(Length) PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ULONG Length, + _Out_ PULONG LengthNeeded + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCallNtSetSecurityObjectWithTimeout( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/kphapi.h b/phlib/include/kphapi.h new file mode 100644 index 0000000..f878c57 --- /dev/null +++ b/phlib/include/kphapi.h @@ -0,0 +1,242 @@ +#ifndef _KPHAPI_H +#define _KPHAPI_H + +// This file contains KProcessHacker definitions shared across kernel-mode and user-mode. + +// Process information + +typedef enum _KPH_PROCESS_INFORMATION_CLASS +{ + KphProcessReserved1 = 1, + KphProcessReserved2 = 2, + KphProcessReserved3 = 3, + MaxKphProcessInfoClass +} KPH_PROCESS_INFORMATION_CLASS; + +// Thread information + +typedef enum _KPH_THREAD_INFORMATION_CLASS +{ + KphThreadReserved1 = 1, + KphThreadReserved2 = 2, + KphThreadReserved3 = 3, + MaxKphThreadInfoClass +} KPH_THREAD_INFORMATION_CLASS; + +// Process handle information + +typedef struct _KPH_PROCESS_HANDLE +{ + HANDLE Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; + USHORT ObjectTypeIndex; + USHORT Reserved1; + ULONG HandleAttributes; + ULONG Reserved2; +} KPH_PROCESS_HANDLE, *PKPH_PROCESS_HANDLE; + +typedef struct _KPH_PROCESS_HANDLE_INFORMATION +{ + ULONG HandleCount; + KPH_PROCESS_HANDLE Handles[1]; +} KPH_PROCESS_HANDLE_INFORMATION, *PKPH_PROCESS_HANDLE_INFORMATION; + +// Object information + +typedef enum _KPH_OBJECT_INFORMATION_CLASS +{ + KphObjectBasicInformation, // q: OBJECT_BASIC_INFORMATION + KphObjectNameInformation, // q: OBJECT_NAME_INFORMATION + KphObjectTypeInformation, // q: OBJECT_TYPE_INFORMATION + KphObjectHandleFlagInformation, // qs: OBJECT_HANDLE_FLAG_INFORMATION + KphObjectProcessBasicInformation, // q: PROCESS_BASIC_INFORMATION + KphObjectThreadBasicInformation, // q: THREAD_BASIC_INFORMATION + KphObjectEtwRegBasicInformation, // q: ETWREG_BASIC_INFORMATION + KphObjectFileObjectInformation, // q: KPH_FILE_OBJECT_INFORMATION + KphObjectFileObjectDriver, // q: KPH_FILE_OBJECT_DRIVER + MaxKphObjectInfoClass +} KPH_OBJECT_INFORMATION_CLASS; + +typedef struct _KPH_FILE_OBJECT_INFORMATION +{ + BOOLEAN LockOperation; + BOOLEAN DeletePending; + BOOLEAN ReadAccess; + BOOLEAN WriteAccess; + BOOLEAN DeleteAccess; + BOOLEAN SharedRead; + BOOLEAN SharedWrite; + BOOLEAN SharedDelete; + LARGE_INTEGER CurrentByteOffset; + ULONG Flags; +} KPH_FILE_OBJECT_INFORMATION, *PKPH_FILE_OBJECT_INFORMATION; + +typedef struct _KPH_FILE_OBJECT_DRIVER +{ + HANDLE DriverHandle; +} KPH_FILE_OBJECT_DRIVER, *PKPH_FILE_OBJECT_DRIVER; + +// Driver information + +typedef enum _DRIVER_INFORMATION_CLASS +{ + DriverBasicInformation, + DriverNameInformation, + DriverServiceKeyNameInformation, + MaxDriverInfoClass +} DRIVER_INFORMATION_CLASS; + +typedef struct _DRIVER_BASIC_INFORMATION +{ + ULONG Flags; + PVOID DriverStart; + ULONG DriverSize; +} DRIVER_BASIC_INFORMATION, *PDRIVER_BASIC_INFORMATION; + +typedef struct _DRIVER_NAME_INFORMATION +{ + UNICODE_STRING DriverName; +} DRIVER_NAME_INFORMATION, *PDRIVER_NAME_INFORMATION; + +typedef struct _DRIVER_SERVICE_KEY_NAME_INFORMATION +{ + UNICODE_STRING ServiceKeyName; +} DRIVER_SERVICE_KEY_NAME_INFORMATION, *PDRIVER_SERVICE_KEY_NAME_INFORMATION; + +// ETW registration object information + +typedef struct _ETWREG_BASIC_INFORMATION +{ + GUID Guid; + ULONG_PTR SessionId; +} ETWREG_BASIC_INFORMATION, *PETWREG_BASIC_INFORMATION; + +// Device + +#define KPH_DEVICE_SHORT_NAME L"KProcessHacker3" +#define KPH_DEVICE_TYPE 0x9999 +#define KPH_DEVICE_NAME (L"\\Device\\" KPH_DEVICE_SHORT_NAME) + +// Parameters + +typedef enum _KPH_SECURITY_LEVEL +{ + KphSecurityNone = 0, // all clients are allowed + KphSecurityPrivilegeCheck = 1, // require SeDebugPrivilege + KphSecuritySignatureCheck = 2, // require trusted signature + KphSecuritySignatureAndPrivilegeCheck = 3, // require trusted signature and SeDebugPrivilege + KphMaxSecurityLevel +} KPH_SECURITY_LEVEL, *PKPH_SECURITY_LEVEL; + +typedef struct _KPH_DYN_STRUCT_DATA +{ + SHORT EgeGuid; + SHORT EpObjectTable; + SHORT Reserved0; + SHORT Reserved1; + SHORT Reserved2; + SHORT EreGuidEntry; + SHORT HtHandleContentionEvent; + SHORT OtName; + SHORT OtIndex; + SHORT ObDecodeShift; + SHORT ObAttributesShift; +} KPH_DYN_STRUCT_DATA, *PKPH_DYN_STRUCT_DATA; + +typedef struct _KPH_DYN_PACKAGE +{ + USHORT MajorVersion; + USHORT MinorVersion; + USHORT ServicePackMajor; // -1 to ignore + USHORT BuildNumber; // -1 to ignore + ULONG ResultingNtVersion; // PHNT_* + KPH_DYN_STRUCT_DATA StructData; +} KPH_DYN_PACKAGE, *PKPH_DYN_PACKAGE; + +#define KPH_DYN_CONFIGURATION_VERSION 3 +#define KPH_DYN_MAXIMUM_PACKAGES 64 + +typedef struct _KPH_DYN_CONFIGURATION +{ + ULONG Version; + ULONG NumberOfPackages; + KPH_DYN_PACKAGE Packages[1]; +} KPH_DYN_CONFIGURATION, *PKPH_DYN_CONFIGURATION; + +// Verification + +#ifdef __BCRYPT_H__ +#define KPH_SIGN_ALGORITHM BCRYPT_ECDSA_P256_ALGORITHM +#define KPH_SIGN_ALGORITHM_BITS 256 +#define KPH_HASH_ALGORITHM BCRYPT_SHA256_ALGORITHM +#define KPH_BLOB_PUBLIC BCRYPT_ECCPUBLIC_BLOB +#endif + +#define KPH_SIGNATURE_MAX_SIZE (128 * 1024) // 128 kB + +typedef ULONG KPH_KEY, *PKPH_KEY; + +typedef enum _KPH_KEY_LEVEL +{ + KphKeyLevel1 = 1, + KphKeyLevel2 = 2 +} KPH_KEY_LEVEL; + +#define KPH_KEY_BACKOFF_TIME ((LONGLONG)(100 * 1000 * 10)) // 100ms + +#define KPH_PROCESS_READ_ACCESS \ + (PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ) +#define KPH_THREAD_READ_ACCESS \ + (THREAD_QUERY_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION | THREAD_GET_CONTEXT) +#define KPH_TOKEN_READ_ACCESS \ + (TOKEN_QUERY | TOKEN_QUERY_SOURCE) + +// Features + +// No features defined. + +// Control codes + +#define KPH_CTL_CODE(x) CTL_CODE(KPH_DEVICE_TYPE, 0x800 + x, METHOD_NEITHER, FILE_ANY_ACCESS) + +// General +#define KPH_GETFEATURES KPH_CTL_CODE(0) +#define KPH_VERIFYCLIENT KPH_CTL_CODE(1) +#define KPH_RETRIEVEKEY KPH_CTL_CODE(2) // User-mode only + +// Processes +#define KPH_OPENPROCESS KPH_CTL_CODE(50) // L1/L2 protected API +#define KPH_OPENPROCESSTOKEN KPH_CTL_CODE(51) // L1/L2 protected API +#define KPH_OPENPROCESSJOB KPH_CTL_CODE(52) +#define KPH_RESERVED53 KPH_CTL_CODE(53) +#define KPH_RESERVED54 KPH_CTL_CODE(54) +#define KPH_TERMINATEPROCESS KPH_CTL_CODE(55) // L2 protected API +#define KPH_RESERVED56 KPH_CTL_CODE(56) +#define KPH_RESERVED57 KPH_CTL_CODE(57) +#define KPH_READVIRTUALMEMORYUNSAFE KPH_CTL_CODE(58) // L2 protected API +#define KPH_QUERYINFORMATIONPROCESS KPH_CTL_CODE(59) +#define KPH_SETINFORMATIONPROCESS KPH_CTL_CODE(60) + +// Threads +#define KPH_OPENTHREAD KPH_CTL_CODE(100) // L1/L2 protected API +#define KPH_OPENTHREADPROCESS KPH_CTL_CODE(101) +#define KPH_RESERVED102 KPH_CTL_CODE(102) +#define KPH_RESERVED103 KPH_CTL_CODE(103) +#define KPH_RESERVED104 KPH_CTL_CODE(104) +#define KPH_RESERVED105 KPH_CTL_CODE(105) +#define KPH_CAPTURESTACKBACKTRACETHREAD KPH_CTL_CODE(106) +#define KPH_QUERYINFORMATIONTHREAD KPH_CTL_CODE(107) +#define KPH_SETINFORMATIONTHREAD KPH_CTL_CODE(108) + +// Handles +#define KPH_ENUMERATEPROCESSHANDLES KPH_CTL_CODE(150) +#define KPH_QUERYINFORMATIONOBJECT KPH_CTL_CODE(151) +#define KPH_SETINFORMATIONOBJECT KPH_CTL_CODE(152) +#define KPH_RESERVED153 KPH_CTL_CODE(153) + +// Misc. +#define KPH_OPENDRIVER KPH_CTL_CODE(200) +#define KPH_QUERYINFORMATIONDRIVER KPH_CTL_CODE(201) + +#endif \ No newline at end of file diff --git a/phlib/include/kphuser.h b/phlib/include/kphuser.h new file mode 100644 index 0000000..102534b --- /dev/null +++ b/phlib/include/kphuser.h @@ -0,0 +1,300 @@ +#ifndef _PH_KPHUSER_H +#define _PH_KPHUSER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _KPH_PARAMETERS +{ + KPH_SECURITY_LEVEL SecurityLevel; + BOOLEAN CreateDynamicConfiguration; +} KPH_PARAMETERS, *PKPH_PARAMETERS; + +PHLIBAPI +NTSTATUS +NTAPI +KphConnect( + _In_opt_ PWSTR DeviceName + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphConnect2( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphConnect2Ex( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName, + _In_opt_ PKPH_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphDisconnect( + VOID + ); + +PHLIBAPI +BOOLEAN +NTAPI +KphIsConnected( + VOID + ); + +PHLIBAPI +BOOLEAN +NTAPI +KphIsVerified( + VOID + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphSetParameters( + _In_opt_ PWSTR DeviceName, + _In_ PKPH_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphInstall( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphInstallEx( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName, + _In_opt_ PKPH_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphUninstall( + _In_opt_ PWSTR DeviceName + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphGetFeatures( + _Out_ PULONG Features + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphVerifyClient( + _In_reads_bytes_(SignatureSize) PUCHAR Signature, + _In_ ULONG SignatureSize + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PCLIENT_ID ClientId + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenProcessJob( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE JobHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphTerminateProcess( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphReadVirtualMemoryUnsafe( + _In_opt_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _Out_writes_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphSetInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + _In_reads_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PCLIENT_ID ClientId + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenThreadProcess( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE ProcessHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphCaptureStackBackTraceThread( + _In_ HANDLE ThreadHandle, + _In_ ULONG FramesToSkip, + _In_ ULONG FramesToCapture, + _Out_writes_(FramesToCapture) PVOID *BackTrace, + _Out_opt_ PULONG CapturedFrames, + _Out_opt_ PULONG BackTraceHash + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphQueryInformationThread( + _In_ HANDLE ThreadHandle, + _In_ KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + _Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphSetInformationThread( + _In_ HANDLE ThreadHandle, + _In_ KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphEnumerateProcessHandles( + _In_ HANDLE ProcessHandle, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _In_opt_ ULONG BufferLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphEnumerateProcessHandles2( + _In_ HANDLE ProcessHandle, + _Out_ PKPH_PROCESS_HANDLE_INFORMATION *Handles + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphQueryInformationObject( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphSetInformationObject( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + _In_reads_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphOpenDriver( + _Out_ PHANDLE DriverHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +PHLIBAPI +NTSTATUS +NTAPI +KphQueryInformationDriver( + _In_ HANDLE DriverHandle, + _In_ DRIVER_INFORMATION_CLASS DriverInformationClass, + _Out_writes_bytes_(DriverInformationLength) PVOID DriverInformation, + _In_ ULONG DriverInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// kphdata + +PHLIBAPI +NTSTATUS +NTAPI +KphInitializeDynamicPackage( + _Out_ PKPH_DYN_PACKAGE Package + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/kphuserp.h b/phlib/include/kphuserp.h new file mode 100644 index 0000000..5fa2fd2 --- /dev/null +++ b/phlib/include/kphuserp.h @@ -0,0 +1,127 @@ +#ifndef _PH_KPHUSERP_H +#define _PH_KPHUSERP_H + +typedef NTSTATUS (NTAPI *PKPHP_WITH_KEY_CONTINUATION)( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +NTSTATUS KphpDeviceIoControl( + _In_ ULONG KphControlCode, + _In_ PVOID InBuffer, + _In_ ULONG InBufferLength + ); + +typedef struct _KPHP_RETRIEVE_KEY_CONTEXT +{ + IO_STATUS_BLOCK Iosb; + PKPHP_WITH_KEY_CONTINUATION Continuation; + PVOID Context; + NTSTATUS Status; +} KPHP_RETRIEVE_KEY_CONTEXT, *PKPHP_RETRIEVE_KEY_CONTEXT; + +VOID KphpWithKeyApcRoutine( + _In_ PVOID ApcContext, + _In_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG Reserved + ); + +NTSTATUS KphpWithKey( + _In_ KPH_KEY_LEVEL KeyLevel, + _In_ PKPHP_WITH_KEY_CONTINUATION Continuation, + _In_ PVOID Context + ); + +// Get L1 key + +typedef struct _KPHP_GET_L1_KEY_CONTEXT +{ + PKPH_KEY Key; +} KPHP_GET_L1_KEY_CONTEXT, *PKPHP_GET_L1_KEY_CONTEXT; + +NTSTATUS KphpGetL1KeyContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +NTSTATUS KphpGetL1Key( + _Out_ PKPH_KEY Key + ); + +// Open process + +typedef struct _KPH_OPEN_PROCESS_INPUT +{ + PHANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PCLIENT_ID ClientId; + KPH_KEY Key; +} KPH_OPEN_PROCESS_INPUT, *PKPH_OPEN_PROCESS_INPUT; + +NTSTATUS KphpOpenProcessContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +// Open process token + +typedef struct _KPH_OPEN_PROCESS_TOKEN_INPUT +{ + HANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PHANDLE TokenHandle; + KPH_KEY Key; +} KPH_OPEN_PROCESS_TOKEN_INPUT, *PKPH_OPEN_PROCESS_TOKEN_INPUT; + +NTSTATUS KphpOpenProcessTokenContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +// Terminate process + +typedef struct _KPH_TERMINATE_PROCESS_INPUT +{ + HANDLE ProcessHandle; + NTSTATUS ExitStatus; + KPH_KEY Key; +} KPH_TERMINATE_PROCESS_INPUT, *PKPH_TERMINATE_PROCESS_INPUT; + +NTSTATUS KphpTerminateProcessContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +// Read virtual memory, unsafe + +typedef struct _KPH_READ_VIRTUAL_MEMORY_UNSAFE_INPUT +{ + HANDLE ProcessHandle; + PVOID BaseAddress; + PVOID Buffer; + SIZE_T BufferSize; + PSIZE_T NumberOfBytesRead; + KPH_KEY Key; +} KPH_READ_VIRTUAL_MEMORY_UNSAFE_INPUT, *PKPH_READ_VIRTUAL_MEMORY_UNSAFE_INPUT; + +NTSTATUS KphpReadVirtualMemoryUnsafeContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +// Open thread + +typedef struct _KPH_OPEN_THREAD_INPUT +{ + PHANDLE ThreadHandle; + ACCESS_MASK DesiredAccess; + PCLIENT_ID ClientId; + KPH_KEY Key; +} KPH_OPEN_THREAD_INPUT, *PKPH_OPEN_THREAD_INPUT; + +NTSTATUS KphpOpenThreadContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ); + +#endif diff --git a/phlib/include/lsasup.h b/phlib/include/lsasup.h new file mode 100644 index 0000000..3784f41 --- /dev/null +++ b/phlib/include/lsasup.h @@ -0,0 +1,88 @@ +#ifndef _PH_LSASUP_H +#define _PH_LSASUP_H + +#ifdef __cplusplus +extern "C" { +#endif + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenLsaPolicy( + _Out_ PLSA_HANDLE PolicyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PUNICODE_STRING SystemName + ); + +PHLIBAPI +LSA_HANDLE +NTAPI +PhGetLookupPolicyHandle( + VOID + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhLookupPrivilegeName( + _In_ PLUID PrivilegeValue, + _Out_ PPH_STRING *PrivilegeName + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhLookupPrivilegeDisplayName( + _In_ PPH_STRINGREF PrivilegeName, + _Out_ PPH_STRING *PrivilegeDisplayName + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhLookupPrivilegeValue( + _In_ PPH_STRINGREF PrivilegeName, + _Out_ PLUID PrivilegeValue + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhLookupSid( + _In_ PSID Sid, + _Out_opt_ PPH_STRING *Name, + _Out_opt_ PPH_STRING *DomainName, + _Out_opt_ PSID_NAME_USE NameUse + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhLookupName( + _In_ PPH_STRINGREF Name, + _Out_opt_ PSID *Sid, + _Out_opt_ PPH_STRING *DomainName, + _Out_opt_ PSID_NAME_USE NameUse + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetSidFullName( + _In_ PSID Sid, + _In_ BOOLEAN IncludeDomain, + _Out_opt_ PSID_NAME_USE NameUse + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhSidToStringSid( + _In_ PSID Sid + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/mapimg.h b/phlib/include/mapimg.h new file mode 100644 index 0000000..ccd986d --- /dev/null +++ b/phlib/include/mapimg.h @@ -0,0 +1,388 @@ +#ifndef _PH_MAPIMG_H +#define _PH_MAPIMG_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _PH_MAPPED_IMAGE +{ + PVOID ViewBase; + SIZE_T Size; + + PIMAGE_NT_HEADERS NtHeaders; + ULONG NumberOfSections; + PIMAGE_SECTION_HEADER Sections; + USHORT Magic; +} PH_MAPPED_IMAGE, *PPH_MAPPED_IMAGE; + +PHLIBAPI +NTSTATUS +NTAPI +PhInitializeMappedImage( + _Out_ PPH_MAPPED_IMAGE MappedImage, + _In_ PVOID ViewBase, + _In_ SIZE_T Size + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhLoadMappedImage( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PPH_MAPPED_IMAGE MappedImage + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhUnloadMappedImage( + _Inout_ PPH_MAPPED_IMAGE MappedImage + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhMapViewOfEntireFile( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PVOID *ViewBase, + _Out_ PSIZE_T Size + ); + +PHLIBAPI +PIMAGE_SECTION_HEADER +NTAPI +PhMappedImageRvaToSection( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Rva + ); + +PHLIBAPI +PVOID +NTAPI +PhMappedImageRvaToVa( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Rva, + _Out_opt_ PIMAGE_SECTION_HEADER *Section + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhGetMappedImageSectionName( + _In_ PIMAGE_SECTION_HEADER Section, + _Out_writes_opt_z_(Count) PSTR Buffer, + _In_ ULONG Count, + _Out_opt_ PULONG ReturnCount + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageDataEntry( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Index, + _Out_ PIMAGE_DATA_DIRECTORY *Entry + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageLoadConfig32( + _In_ PPH_MAPPED_IMAGE MappedImage, + _Out_ PIMAGE_LOAD_CONFIG_DIRECTORY32 *LoadConfig + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageLoadConfig64( + _In_ PPH_MAPPED_IMAGE MappedImage, + _Out_ PIMAGE_LOAD_CONFIG_DIRECTORY64 *LoadConfig + ); + +typedef struct _PH_REMOTE_MAPPED_IMAGE +{ + PVOID ViewBase; + + PIMAGE_NT_HEADERS NtHeaders; + ULONG NumberOfSections; + PIMAGE_SECTION_HEADER Sections; + USHORT Magic; +} PH_REMOTE_MAPPED_IMAGE, *PPH_REMOTE_MAPPED_IMAGE; + +NTSTATUS +NTAPI +PhLoadRemoteMappedImage( + _In_ HANDLE ProcessHandle, + _In_ PVOID ViewBase, + _Out_ PPH_REMOTE_MAPPED_IMAGE RemoteMappedImage + ); + +NTSTATUS +NTAPI +PhUnloadRemoteMappedImage( + _Inout_ PPH_REMOTE_MAPPED_IMAGE RemoteMappedImage + ); + +typedef struct _PH_MAPPED_IMAGE_EXPORTS +{ + PPH_MAPPED_IMAGE MappedImage; + ULONG NumberOfEntries; + + PIMAGE_DATA_DIRECTORY DataDirectory; + PIMAGE_EXPORT_DIRECTORY ExportDirectory; + PULONG AddressTable; + PULONG NamePointerTable; + PUSHORT OrdinalTable; +} PH_MAPPED_IMAGE_EXPORTS, *PPH_MAPPED_IMAGE_EXPORTS; + +typedef struct _PH_MAPPED_IMAGE_EXPORT_ENTRY +{ + USHORT Ordinal; + PSTR Name; +} PH_MAPPED_IMAGE_EXPORT_ENTRY, *PPH_MAPPED_IMAGE_EXPORT_ENTRY; + +typedef struct _PH_MAPPED_IMAGE_EXPORT_FUNCTION +{ + PVOID Function; + PSTR ForwardedName; +} PH_MAPPED_IMAGE_EXPORT_FUNCTION, *PPH_MAPPED_IMAGE_EXPORT_FUNCTION; + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageExports( + _Out_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ PPH_MAPPED_IMAGE MappedImage + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageExportEntry( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_EXPORT_ENTRY Entry + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageExportFunction( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_opt_ PSTR Name, + _In_opt_ USHORT Ordinal, + _Out_ PPH_MAPPED_IMAGE_EXPORT_FUNCTION Function + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageExportFunctionRemote( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_opt_ PSTR Name, + _In_opt_ USHORT Ordinal, + _In_ PVOID RemoteBase, + _Out_ PVOID *Function + ); + +#define PH_MAPPED_IMAGE_DELAY_IMPORTS 0x1 + +typedef struct _PH_MAPPED_IMAGE_IMPORTS +{ + PPH_MAPPED_IMAGE MappedImage; + ULONG Flags; + ULONG NumberOfDlls; + + union + { + PIMAGE_IMPORT_DESCRIPTOR DescriptorTable; + PVOID DelayDescriptorTable; + }; +} PH_MAPPED_IMAGE_IMPORTS, *PPH_MAPPED_IMAGE_IMPORTS; + +typedef struct _PH_MAPPED_IMAGE_IMPORT_DLL +{ + PPH_MAPPED_IMAGE MappedImage; + ULONG Flags; + PSTR Name; + ULONG NumberOfEntries; + + union + { + PIMAGE_IMPORT_DESCRIPTOR Descriptor; + PVOID DelayDescriptor; + }; + PVOID *LookupTable; +} PH_MAPPED_IMAGE_IMPORT_DLL, *PPH_MAPPED_IMAGE_IMPORT_DLL; + +typedef struct _PH_MAPPED_IMAGE_IMPORT_ENTRY +{ + PSTR Name; + union + { + USHORT Ordinal; + USHORT NameHint; + }; +} PH_MAPPED_IMAGE_IMPORT_ENTRY, *PPH_MAPPED_IMAGE_IMPORT_ENTRY; + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageImports( + _Out_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ PPH_MAPPED_IMAGE MappedImage + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageImportDll( + _In_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_IMPORT_DLL ImportDll + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageImportEntry( + _In_ PPH_MAPPED_IMAGE_IMPORT_DLL ImportDll, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_IMPORT_ENTRY Entry + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedImageDelayImports( + _Out_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ PPH_MAPPED_IMAGE MappedImage + ); + +USHORT +NTAPI +PhCheckSum( + _In_ ULONG Sum, + _In_reads_(Count) PUSHORT Buffer, + _In_ ULONG Count + ); + +PHLIBAPI +ULONG +NTAPI +PhCheckSumMappedImage( + _In_ PPH_MAPPED_IMAGE MappedImage + ); + +// maplib + +struct _PH_MAPPED_ARCHIVE; +typedef struct _PH_MAPPED_ARCHIVE *PPH_MAPPED_ARCHIVE; + +typedef enum _PH_MAPPED_ARCHIVE_MEMBER_TYPE +{ + NormalArchiveMemberType, + LinkerArchiveMemberType, + LongnamesArchiveMemberType +} PH_MAPPED_ARCHIVE_MEMBER_TYPE; + +typedef struct _PH_MAPPED_ARCHIVE_MEMBER +{ + PPH_MAPPED_ARCHIVE MappedArchive; + PH_MAPPED_ARCHIVE_MEMBER_TYPE Type; + PSTR Name; + ULONG Size; + PVOID Data; + + PIMAGE_ARCHIVE_MEMBER_HEADER Header; + CHAR NameBuffer[20]; +} PH_MAPPED_ARCHIVE_MEMBER, *PPH_MAPPED_ARCHIVE_MEMBER; + +typedef struct _PH_MAPPED_ARCHIVE +{ + PVOID ViewBase; + SIZE_T Size; + + PH_MAPPED_ARCHIVE_MEMBER FirstLinkerMember; + PH_MAPPED_ARCHIVE_MEMBER SecondLinkerMember; + PH_MAPPED_ARCHIVE_MEMBER LongnamesMember; + BOOLEAN HasLongnamesMember; + + PPH_MAPPED_ARCHIVE_MEMBER FirstStandardMember; + PPH_MAPPED_ARCHIVE_MEMBER LastStandardMember; +} PH_MAPPED_ARCHIVE, *PPH_MAPPED_ARCHIVE; + +typedef struct _PH_MAPPED_ARCHIVE_IMPORT_ENTRY +{ + PSTR Name; + PSTR DllName; + union + { + USHORT Ordinal; + USHORT NameHint; + }; + BYTE Type; + BYTE NameType; + USHORT Machine; +} PH_MAPPED_ARCHIVE_IMPORT_ENTRY, *PPH_MAPPED_ARCHIVE_IMPORT_ENTRY; + +PHLIBAPI +NTSTATUS +NTAPI +PhInitializeMappedArchive( + _Out_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PVOID ViewBase, + _In_ SIZE_T Size + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhLoadMappedArchive( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PPH_MAPPED_ARCHIVE MappedArchive + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhUnloadMappedArchive( + _Inout_ PPH_MAPPED_ARCHIVE MappedArchive + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetNextMappedArchiveMember( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, + _Out_ PPH_MAPPED_ARCHIVE_MEMBER NextMember + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhIsMappedArchiveMemberShortFormat( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetMappedArchiveImportEntry( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, + _Out_ PPH_MAPPED_ARCHIVE_IMPORT_ENTRY Entry + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/ph.h b/phlib/include/ph.h new file mode 100644 index 0000000..632ee40 --- /dev/null +++ b/phlib/include/ph.h @@ -0,0 +1,11 @@ +#ifndef _PH_PH_H +#define _PH_PH_H + +#pragma once + +#include +#include +#include +#include + +#endif diff --git a/phlib/include/phbase.h b/phlib/include/phbase.h new file mode 100644 index 0000000..6fdd4d9 --- /dev/null +++ b/phlib/include/phbase.h @@ -0,0 +1,45 @@ +#ifndef _PH_PHBASE_H +#define _PH_PHBASE_H + +#pragma once + +#ifndef PHLIB_NO_DEFAULT_LIB +#pragma comment(lib, "ntdll.lib") + +#pragma comment(lib, "comctl32.lib") +#pragma comment(lib, "version.lib") +#endif + +// nonstandard extension used : nameless struct/union +#pragma warning(disable: 4201) +// nonstandard extension used : bit field types other than int +#pragma warning(disable: 4214) +// 'function': attributes not present on previous declaration +#pragma warning(disable: 4985) + +#ifndef UNICODE +#define UNICODE +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#if !defined(_PHLIB_) +#define PHLIBAPI __declspec(dllimport) +#else +#define PHLIBAPI +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#endif \ No newline at end of file diff --git a/phlib/include/phbasesup.h b/phlib/include/phbasesup.h new file mode 100644 index 0000000..f59e12f --- /dev/null +++ b/phlib/include/phbasesup.h @@ -0,0 +1,3740 @@ +#ifndef _PH_PHBASESUP_H +#define _PH_PHBASESUP_H + +#ifdef __cplusplus +extern "C" { +#endif + +BOOLEAN +PhBaseInitialization( + VOID + ); + +// Threads + +#ifdef DEBUG +struct _PH_AUTO_POOL; +typedef struct _PH_AUTO_POOL *PPH_AUTO_POOL; + +typedef struct _PHP_BASE_THREAD_DBG +{ + CLIENT_ID ClientId; + LIST_ENTRY ListEntry; + PVOID StartAddress; + PVOID Parameter; + + PPH_AUTO_POOL CurrentAutoPool; +} PHP_BASE_THREAD_DBG, *PPHP_BASE_THREAD_DBG; + +extern ULONG PhDbgThreadDbgTlsIndex; +extern LIST_ENTRY PhDbgThreadListHead; +extern PH_QUEUED_LOCK PhDbgThreadListLock; +#endif + +PHLIBAPI +HANDLE +NTAPI +PhCreateThread( + _In_opt_ SIZE_T StackSize, + _In_ PUSER_THREAD_START_ROUTINE StartAddress, + _In_opt_ PVOID Parameter + ); + +// DLLs + +FORCEINLINE +PVOID +PhGetDllHandle( + _In_ PWSTR DllName + ) +{ + UNICODE_STRING dllName; + PVOID dllHandle; + + RtlInitUnicodeString(&dllName, DllName); + + if (NT_SUCCESS(LdrGetDllHandle(NULL, NULL, &dllName, &dllHandle))) + return dllHandle; + else + return NULL; +} + +FORCEINLINE +PVOID +PhGetProcedureAddress( + _In_ PVOID DllHandle, + _In_opt_ PSTR ProcedureName, + _In_opt_ ULONG ProcedureNumber + ) +{ + NTSTATUS status; + ANSI_STRING procedureName; + PVOID procedureAddress; + + if (ProcedureName) + { + RtlInitAnsiString(&procedureName, ProcedureName); + status = LdrGetProcedureAddress( + DllHandle, + &procedureName, + 0, + &procedureAddress + ); + } + else + { + status = LdrGetProcedureAddress( + DllHandle, + NULL, + ProcedureNumber, + &procedureAddress + ); + } + + if (!NT_SUCCESS(status)) + return NULL; + + return procedureAddress; +} + +// Misc. system + +PHLIBAPI +VOID +NTAPI +PhQuerySystemTime( + _Out_ PLARGE_INTEGER SystemTime + ); + +PHLIBAPI +VOID +NTAPI +PhQueryTimeZoneBias( + _Out_ PLARGE_INTEGER TimeZoneBias + ); + +PHLIBAPI +VOID +NTAPI +PhSystemTimeToLocalTime( + _In_ PLARGE_INTEGER SystemTime, + _Out_ PLARGE_INTEGER LocalTime + ); + +PHLIBAPI +VOID +NTAPI +PhLocalTimeToSystemTime( + _In_ PLARGE_INTEGER LocalTime, + _Out_ PLARGE_INTEGER SystemTime + ); + +// Heap + +_May_raise_ +_Check_return_ +_Ret_notnull_ +_Post_writable_byte_size_(Size) +PHLIBAPI +PVOID +NTAPI +PhAllocate( + _In_ SIZE_T Size + ); + +PHLIBAPI +PVOID +NTAPI +PhAllocateSafe( + _In_ SIZE_T Size + ); + +PHLIBAPI +PVOID +NTAPI +PhAllocateExSafe( + _In_ SIZE_T Size, + _In_ ULONG Flags + ); + +PHLIBAPI +VOID +NTAPI +PhFree( + _Frees_ptr_opt_ PVOID Memory + ); + +_May_raise_ +_Post_writable_byte_size_(Size) +PHLIBAPI +PVOID +NTAPI +PhReAllocate( + _Frees_ptr_ PVOID Memory, + _In_ SIZE_T Size + ); + +PHLIBAPI +PVOID +NTAPI +PhReAllocateSafe( + _In_ PVOID Memory, + _In_ SIZE_T Size + ); + +_Check_return_ +_Ret_maybenull_ +PHLIBAPI +PVOID +NTAPI +PhAllocatePage( + _In_ SIZE_T Size, + _Out_opt_ PSIZE_T NewSize + ); + +PHLIBAPI +VOID +NTAPI +PhFreePage( + _Frees_ptr_opt_ PVOID Memory + ); + +FORCEINLINE +PVOID +PhAllocateCopy( + _In_ PVOID Data, + _In_ SIZE_T Size + ) +{ + PVOID copy; + + copy = PhAllocate(Size); + memcpy(copy, Data, Size); + + return copy; +} + +// Event + +#define PH_EVENT_SET 0x1 +#define PH_EVENT_SET_SHIFT 0 +#define PH_EVENT_REFCOUNT_SHIFT 1 +#define PH_EVENT_REFCOUNT_INC 0x2 +#define PH_EVENT_REFCOUNT_MASK (((ULONG_PTR)1 << 15) - 1) + +/** + * A fast event object. + * + * \remarks This event object does not use a kernel event object until necessary, and frees the + * object automatically when it is no longer needed. + */ +typedef struct _PH_EVENT +{ + union + { + ULONG_PTR Value; + struct + { + USHORT Set : 1; + USHORT RefCount : 15; + UCHAR Reserved; + UCHAR AvailableForUse; +#ifdef _WIN64 + ULONG Spare; +#endif + }; + }; + HANDLE EventHandle; +} PH_EVENT, *PPH_EVENT; + +C_ASSERT(sizeof(PH_EVENT) == sizeof(ULONG_PTR) + sizeof(HANDLE)); + +#define PH_EVENT_INIT { { PH_EVENT_REFCOUNT_INC }, NULL } + +PHLIBAPI +VOID +FASTCALL +PhfInitializeEvent( + _Out_ PPH_EVENT Event + ); + +#define PhSetEvent PhfSetEvent +PHLIBAPI +VOID +FASTCALL +PhfSetEvent( + _Inout_ PPH_EVENT Event + ); + +PHLIBAPI +BOOLEAN +FASTCALL +PhfWaitForEvent( + _Inout_ PPH_EVENT Event, + _In_opt_ PLARGE_INTEGER Timeout + ); + +FORCEINLINE +BOOLEAN +PhWaitForEvent( + _Inout_ PPH_EVENT Event, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + if (Event->Set) + return TRUE; + + return PhfWaitForEvent(Event, Timeout); +} + +#define PhResetEvent PhfResetEvent +PHLIBAPI +VOID +FASTCALL +PhfResetEvent( + _Inout_ PPH_EVENT Event + ); + +FORCEINLINE +VOID +PhInitializeEvent( + _Out_ PPH_EVENT Event + ) +{ + Event->Value = PH_EVENT_REFCOUNT_INC; + Event->EventHandle = NULL; +} + +/** + * Determines whether an event object is set. + * + * \param Event A pointer to an event object. + * + * \return TRUE if the event object is set, otherwise FALSE. + */ +FORCEINLINE +BOOLEAN +PhTestEvent( + _In_ PPH_EVENT Event + ) +{ + return (BOOLEAN)Event->Set; +} + +// Barrier + +#define PH_BARRIER_COUNT_SHIFT 0 +#define PH_BARRIER_COUNT_MASK (((LONG_PTR)1 << (sizeof(ULONG_PTR) * 8 / 2 - 1)) - 1) +#define PH_BARRIER_COUNT_INC ((LONG_PTR)1 << PH_BARRIER_COUNT_SHIFT) +#define PH_BARRIER_TARGET_SHIFT (sizeof(ULONG_PTR) * 8 / 2) +#define PH_BARRIER_TARGET_MASK (((LONG_PTR)1 << (sizeof(ULONG_PTR) * 8 / 2 - 1)) - 1) +#define PH_BARRIER_TARGET_INC ((LONG_PTR)1 << PH_BARRIER_TARGET_SHIFT) +#define PH_BARRIER_WAKING ((LONG_PTR)1 << (sizeof(ULONG_PTR) * 8 - 1)) + +#define PH_BARRIER_MASTER 1 +#define PH_BARRIER_SLAVE 2 +#define PH_BARRIER_OBSERVER 3 + +typedef struct _PH_BARRIER +{ + ULONG_PTR Value; + PH_WAKE_EVENT WakeEvent; +} PH_BARRIER, *PPH_BARRIER; + +#define PH_BARRIER_INIT(Target) { (ULONG_PTR)(Target) << PH_BARRIER_TARGET_SHIFT, PH_WAKE_EVENT_INIT } + +PHLIBAPI +VOID +FASTCALL +PhfInitializeBarrier( + _Out_ PPH_BARRIER Barrier, + _In_ ULONG_PTR Target + ); + +#define PhWaitForBarrier PhfWaitForBarrier +PHLIBAPI +BOOLEAN +FASTCALL +PhfWaitForBarrier( + _Inout_ PPH_BARRIER Barrier, + _In_ BOOLEAN Spin + ); + +FORCEINLINE +VOID +PhInitializeBarrier( + _Out_ PPH_BARRIER Barrier, + _In_ ULONG_PTR Target + ) +{ + Barrier->Value = Target << PH_BARRIER_TARGET_SHIFT; + PhInitializeQueuedLock(&Barrier->WakeEvent); +} + +// Rundown protection + +#define PH_RUNDOWN_ACTIVE 0x1 +#define PH_RUNDOWN_REF_SHIFT 1 +#define PH_RUNDOWN_REF_INC 0x2 + +typedef struct _PH_RUNDOWN_PROTECT +{ + ULONG_PTR Value; +} PH_RUNDOWN_PROTECT, *PPH_RUNDOWN_PROTECT; + +#define PH_RUNDOWN_PROTECT_INIT { 0 } + +typedef struct _PH_RUNDOWN_WAIT_BLOCK +{ + ULONG_PTR Count; + PH_EVENT WakeEvent; +} PH_RUNDOWN_WAIT_BLOCK, *PPH_RUNDOWN_WAIT_BLOCK; + +PHLIBAPI +VOID +FASTCALL +PhfInitializeRundownProtection( + _Out_ PPH_RUNDOWN_PROTECT Protection + ); + +PHLIBAPI +BOOLEAN +FASTCALL +PhfAcquireRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ); + +PHLIBAPI +VOID +FASTCALL +PhfReleaseRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ); + +PHLIBAPI +VOID +FASTCALL +PhfWaitForRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ); + +FORCEINLINE +VOID +PhInitializeRundownProtection( + _Out_ PPH_RUNDOWN_PROTECT Protection + ) +{ + Protection->Value = 0; +} + +FORCEINLINE +BOOLEAN +PhAcquireRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + + value = Protection->Value & ~PH_RUNDOWN_ACTIVE; // fail fast path when rundown is active + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)(value + PH_RUNDOWN_REF_INC), + (PVOID)value + ) == value) + { + return TRUE; + } + else + { + return PhfAcquireRundownProtection(Protection); + } +} + +FORCEINLINE +VOID +PhReleaseRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + + value = Protection->Value & ~PH_RUNDOWN_ACTIVE; // Fail fast path when rundown is active + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)(value - PH_RUNDOWN_REF_INC), + (PVOID)value + ) != value) + { + PhfReleaseRundownProtection(Protection); + } +} + +FORCEINLINE +VOID +PhWaitForRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + + value = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)PH_RUNDOWN_ACTIVE, + (PVOID)0 + ); + + if (value != 0 && value != PH_RUNDOWN_ACTIVE) + PhfWaitForRundownProtection(Protection); +} + +// One-time initialization + +#define PH_INITONCE_SHIFT 31 +#define PH_INITONCE_INITIALIZING (0x1 << PH_INITONCE_SHIFT) +#define PH_INITONCE_INITIALIZING_SHIFT PH_INITONCE_SHIFT + +typedef struct _PH_INITONCE +{ + PH_EVENT Event; +} PH_INITONCE, *PPH_INITONCE; + +C_ASSERT(PH_INITONCE_SHIFT >= FIELD_OFFSET(PH_EVENT, AvailableForUse) * 8); + +#define PH_INITONCE_INIT { PH_EVENT_INIT } + +#define PhInitializeInitOnce PhfInitializeInitOnce +PHLIBAPI +VOID +FASTCALL +PhfInitializeInitOnce( + _Out_ PPH_INITONCE InitOnce + ); + +PHLIBAPI +BOOLEAN +FASTCALL +PhfBeginInitOnce( + _Inout_ PPH_INITONCE InitOnce + ); + +#define PhEndInitOnce PhfEndInitOnce +PHLIBAPI +VOID +FASTCALL +PhfEndInitOnce( + _Inout_ PPH_INITONCE InitOnce + ); + +FORCEINLINE +BOOLEAN +PhBeginInitOnce( + _Inout_ PPH_INITONCE InitOnce + ) +{ + if (InitOnce->Event.Set) + return FALSE; + else + return PhfBeginInitOnce(InitOnce); +} + +FORCEINLINE +BOOLEAN +PhTestInitOnce( + _In_ PPH_INITONCE InitOnce + ) +{ + return (BOOLEAN)InitOnce->Event.Set; +} + +// String + +PHLIBAPI +SIZE_T +NTAPI +PhCountStringZ( + _In_ PWSTR String + ); + +PHLIBAPI +PSTR +NTAPI +PhDuplicateBytesZ( + _In_ PSTR String + ); + +PHLIBAPI +PSTR +NTAPI +PhDuplicateBytesZSafe( + _In_ PSTR String + ); + +PHLIBAPI +PWSTR +NTAPI +PhDuplicateStringZ( + _In_ PWSTR String + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhCopyBytesZ( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhCopyStringZ( + _In_ PWSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhCopyStringZFromBytes( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhCopyStringZFromMultiByte( + _In_ PSTR InputBuffer, + _In_ SIZE_T InputCount, + _Out_writes_opt_z_(OutputCount) PWSTR OutputBuffer, + _In_ SIZE_T OutputCount, + _Out_opt_ PSIZE_T ReturnCount + ); + +PHLIBAPI +LONG +NTAPI +PhCompareStringZNatural( + _In_ PWSTR A, + _In_ PWSTR B, + _In_ BOOLEAN IgnoreCase + ); + +FORCEINLINE +BOOLEAN +PhAreCharactersDifferent( + _In_ WCHAR Char1, + _In_ WCHAR Char2 + ) +{ + WCHAR d; + + d = Char1 ^ Char2; + + // We ignore bits beyond bit 5 because bit 6 is the case bit, and also we + // don't support localization here. + if (d & 0x1f) + return TRUE; + + return FALSE; +} + +FORCEINLINE +BOOLEAN +PhIsDigitCharacter( + _In_ WCHAR Char + ) +{ + return (USHORT)(Char - '0') < 10; +} + +FORCEINLINE +LONG +PhCompareBytesZ( + _In_ PSTR String1, + _In_ PSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return strcmp(String1, String2); + else + return _stricmp(String1, String2); +} + +FORCEINLINE +BOOLEAN +PhEqualBytesZ( + _In_ PSTR String1, + _In_ PSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return strcmp(String1, String2) == 0; + else + return _stricmp(String1, String2) == 0; +} + +FORCEINLINE +LONG +PhCompareStringZ( + _In_ PWSTR String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return wcscmp(String1, String2); + else + return _wcsicmp(String1, String2); +} + +FORCEINLINE +BOOLEAN +PhEqualStringZ( + _In_ PWSTR String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + { + return wcscmp(String1, String2) == 0; + } + else + { + // wcsicmp is very expensive, so we do a quick check for negatives first. + if (PhAreCharactersDifferent(String1[0], String2[0])) + return FALSE; + + return _wcsicmp(String1, String2) == 0; + } +} + +typedef struct _PH_STRINGREF +{ + /** The length, in bytes, of the string. */ + SIZE_T Length; + /** The buffer containing the contents of the string. */ + PWCH Buffer; +} PH_STRINGREF, *PPH_STRINGREF; + +typedef struct _PH_BYTESREF +{ + /** The length, in bytes, of the string. */ + SIZE_T Length; + /** The buffer containing the contents of the string. */ + PCH Buffer; +} PH_BYTESREF, *PPH_BYTESREF; + +typedef struct _PH_RELATIVE_BYTESREF +{ + /** The length, in bytes, of the string. */ + ULONG Length; + /** A user-defined offset. */ + ULONG Offset; +} PH_RELATIVE_BYTESREF, *PPH_RELATIVE_BYTESREF, PH_RELATIVE_STRINGREF, *PPH_RELATIVE_STRINGREF; + +#define PH_STRINGREF_INIT(String) { sizeof(String) - sizeof(WCHAR), (String) } +#define PH_BYTESREF_INIT(String) { sizeof(String) - sizeof(CHAR), (String) } + +FORCEINLINE +VOID +PhInitializeStringRef( + _Out_ PPH_STRINGREF String, + _In_ PWSTR Buffer + ) +{ + String->Length = wcslen(Buffer) * sizeof(WCHAR); + String->Buffer = Buffer; +} + +FORCEINLINE +VOID +PhInitializeStringRefLongHint( + _Out_ PPH_STRINGREF String, + _In_ PWSTR Buffer + ) +{ + String->Length = PhCountStringZ(Buffer) * sizeof(WCHAR); + String->Buffer = Buffer; +} + +FORCEINLINE +VOID +PhInitializeBytesRef( + _Out_ PPH_BYTESREF Bytes, + _In_ PSTR Buffer + ) +{ + Bytes->Length = strlen(Buffer) * sizeof(CHAR); + Bytes->Buffer = Buffer; +} + +FORCEINLINE +VOID +PhInitializeEmptyStringRef( + _Out_ PPH_STRINGREF String + ) +{ + String->Length = 0; + String->Buffer = NULL; +} + +FORCEINLINE +BOOLEAN +PhStringRefToUnicodeString( + _In_ PPH_STRINGREF String, + _Out_ PUNICODE_STRING UnicodeString + ) +{ + UnicodeString->Length = (USHORT)String->Length; + UnicodeString->MaximumLength = (USHORT)String->Length; + UnicodeString->Buffer = String->Buffer; + + return String->Length <= UNICODE_STRING_MAX_BYTES; +} + +FORCEINLINE +VOID +PhUnicodeStringToStringRef( + _In_ PUNICODE_STRING UnicodeString, + _Out_ PPH_STRINGREF String + ) +{ + String->Length = UnicodeString->Length; + String->Buffer = UnicodeString->Buffer; +} + +PHLIBAPI +LONG +NTAPI +PhCompareStringRef( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhEqualStringRef( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +ULONG_PTR +NTAPI +PhFindCharInStringRef( + _In_ PPH_STRINGREF String, + _In_ WCHAR Character, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +ULONG_PTR +NTAPI +PhFindLastCharInStringRef( + _In_ PPH_STRINGREF String, + _In_ WCHAR Character, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +ULONG_PTR +NTAPI +PhFindStringInStringRef( + _In_ PPH_STRINGREF String, + _In_ PPH_STRINGREF SubString, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSplitStringRefAtChar( + _In_ PPH_STRINGREF Input, + _In_ WCHAR Separator, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSplitStringRefAtLastChar( + _In_ PPH_STRINGREF Input, + _In_ WCHAR Separator, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSplitStringRefAtString( + _In_ PPH_STRINGREF Input, + _In_ PPH_STRINGREF Separator, + _In_ BOOLEAN IgnoreCase, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart + ); + +#define PH_SPLIT_AT_CHAR_SET 0x0 // default +#define PH_SPLIT_AT_STRING 0x1 +#define PH_SPLIT_AT_RANGE 0x2 +#define PH_SPLIT_CASE_INSENSITIVE 0x1000 +#define PH_SPLIT_COMPLEMENT_CHAR_SET 0x2000 +#define PH_SPLIT_START_AT_END 0x4000 +#define PH_SPLIT_CHAR_SET_IS_UPPERCASE 0x8000 + +PHLIBAPI +BOOLEAN +NTAPI +PhSplitStringRefEx( + _In_ PPH_STRINGREF Input, + _In_ PPH_STRINGREF Separator, + _In_ ULONG Flags, + _Out_ PPH_STRINGREF FirstPart, + _Out_ PPH_STRINGREF SecondPart, + _Out_opt_ PPH_STRINGREF SeparatorPart + ); + +#define PH_TRIM_START_ONLY 0x1 +#define PH_TRIM_END_ONLY 0x2 + +PHLIBAPI +VOID +NTAPI +PhTrimStringRef( + _Inout_ PPH_STRINGREF String, + _In_ PPH_STRINGREF CharSet, + _In_ ULONG Flags + ); + +FORCEINLINE +LONG +PhCompareStringRef2( + _In_ PPH_STRINGREF String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF sr2; + + PhInitializeStringRef(&sr2, String2); + + return PhCompareStringRef(String1, &sr2, IgnoreCase); +} + +FORCEINLINE +BOOLEAN +PhEqualStringRef2( + _In_ PPH_STRINGREF String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF sr2; + + PhInitializeStringRef(&sr2, String2); + + return PhEqualStringRef(String1, &sr2, IgnoreCase); +} + +FORCEINLINE +BOOLEAN +PhStartsWithStringRef( + _In_ PPH_STRINGREF String, + _In_ PPH_STRINGREF Prefix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF sr; + + sr.Buffer = String->Buffer; + sr.Length = Prefix->Length; + + if (String->Length < sr.Length) + return FALSE; + + return PhEqualStringRef(&sr, Prefix, IgnoreCase); +} + +FORCEINLINE +BOOLEAN +PhStartsWithStringRef2( + _In_ PPH_STRINGREF String, + _In_ PWSTR Prefix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF prefix; + + PhInitializeStringRef(&prefix, Prefix); + + return PhStartsWithStringRef(String, &prefix, IgnoreCase); +} + +FORCEINLINE +BOOLEAN +PhEndsWithStringRef( + _In_ PPH_STRINGREF String, + _In_ PPH_STRINGREF Suffix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF sr; + + if (Suffix->Length > String->Length) + return FALSE; + + sr.Buffer = (PWCHAR)((PCHAR)String->Buffer + String->Length - Suffix->Length); + sr.Length = Suffix->Length; + + return PhEqualStringRef(&sr, Suffix, IgnoreCase); +} + +FORCEINLINE +BOOLEAN +PhEndsWithStringRef2( + _In_ PPH_STRINGREF String, + _In_ PWSTR Suffix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF suffix; + + PhInitializeStringRef(&suffix, Suffix); + + return PhEndsWithStringRef(String, &suffix, IgnoreCase); +} + +FORCEINLINE +VOID +PhSkipStringRef( + _Inout_ PPH_STRINGREF String, + _In_ LONG_PTR Length + ) +{ + String->Buffer = (PWCH)((PCHAR)String->Buffer + Length); + String->Length -= Length; +} + +FORCEINLINE +VOID +PhReverseStringRef( + _In_ PPH_STRINGREF String + ) +{ + SIZE_T i; + SIZE_T j; + WCHAR t; + + for (i = 0, j = String->Length / sizeof(WCHAR) - 1; i <= j; i++, j--) + { + t = String->Buffer[i]; + String->Buffer[i] = String->Buffer[j]; + String->Buffer[j] = t; + } +} + +extern PPH_OBJECT_TYPE PhStringType; + +/** + * A 16-bit string object, which supports UTF-16. + * + * \remarks The \a Length never includes the null terminator. Every string must have a null + * terminator at the end, for compatibility reasons. The invariant is: + * \code Buffer[Length / sizeof(WCHAR)] = 0 \endcode + */ +typedef struct _PH_STRING +{ + // Header + union + { + PH_STRINGREF sr; + struct + { + /** The length, in bytes, of the string. */ + SIZE_T Length; + /** The buffer containing the contents of the string. */ + PWCH Buffer; + }; + }; + + // Data + union + { + WCHAR Data[1]; + struct + { + /** Reserved. */ + ULONG AllocationFlags; + /** Reserved. */ + PVOID Allocation; + }; + }; +} PH_STRING, *PPH_STRING; + +PHLIBAPI +PPH_STRING +NTAPI +PhCreateString( + _In_ PWSTR Buffer + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhCreateStringEx( + _In_opt_ PWCHAR Buffer, + _In_ SIZE_T Length + ); + +FORCEINLINE +PPH_STRING +PhCreateString2( + _In_ PPH_STRINGREF String + ) +{ + return PhCreateStringEx(String->Buffer, String->Length); +} + +FORCEINLINE +PPH_STRING +PhCreateStringFromUnicodeString( + _In_ PUNICODE_STRING UnicodeString + ) +{ + return PhCreateStringEx(UnicodeString->Buffer, UnicodeString->Length); +} + +PHLIBAPI +PPH_STRING +NTAPI +PhReferenceEmptyString( + VOID + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConcatStrings( + _In_ ULONG Count, + ... + ); + +#define PH_CONCAT_STRINGS_LENGTH_CACHE_SIZE 16 + +PHLIBAPI +PPH_STRING +NTAPI +PhConcatStrings_V( + _In_ ULONG Count, + _In_ va_list ArgPtr + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConcatStrings2( + _In_ PWSTR String1, + _In_ PWSTR String2 + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConcatStringRef2( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2 + ); + +PPH_STRING +NTAPI +PhConcatStringRef3( + _In_ PPH_STRINGREF String1, + _In_ PPH_STRINGREF String2, + _In_ PPH_STRINGREF String3 + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatString( + _In_ _Printf_format_string_ PWSTR Format, + ... + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatString_V( + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ); + +/** + * Retrieves a pointer to a string object's buffer or returns NULL. + * + * \param String A pointer to a string object. + * + * \return A pointer to the string object's buffer if the supplied pointer is non-NULL, otherwise + * NULL. + */ +FORCEINLINE +PWSTR +PhGetString( + _In_opt_ PPH_STRING String + ) +{ + if (String) + return String->Buffer; + else + return NULL; +} + +FORCEINLINE +PH_STRINGREF +PhGetStringRef( + _In_opt_ PPH_STRING String + ) +{ + PH_STRINGREF sr; + + if (String) + sr = String->sr; + else + PhInitializeEmptyStringRef(&sr); + + return sr; +} + +/** + * Retrieves a pointer to a string object's buffer or returns an empty string. + * + * \param String A pointer to a string object. + * + * \return A pointer to the string object's buffer if the supplied pointer is non-NULL, otherwise an + * empty string. + */ +FORCEINLINE +PWSTR +PhGetStringOrEmpty( + _In_opt_ PPH_STRING String + ) +{ + if (String) + return String->Buffer; + else + return L""; +} + +/** + * Retrieves a pointer to a string object's buffer or returns the specified alternative string. + * + * \param String A pointer to a string object. + * \param DefaultString The alternative string. + * + * \return A pointer to the string object's buffer if the supplied pointer is non-NULL, otherwise + * the specified alternative string. + */ +FORCEINLINE +PWSTR +PhGetStringOrDefault( + _In_opt_ PPH_STRING String, + _In_ PWSTR DefaultString + ) +{ + if (String) + return String->Buffer; + else + return DefaultString; +} + +/** + * Determines whether a string is null or empty. + * + * \param String A pointer to a string object. + */ +FORCEINLINE +BOOLEAN +PhIsNullOrEmptyString( + _In_opt_ PPH_STRING String + ) +{ + return !String || String->Length == 0; +} + +/** + * Duplicates a string. + * + * \param String A string to duplicate. + */ +FORCEINLINE +PPH_STRING +PhDuplicateString( + _In_ PPH_STRING String + ) +{ + return PhCreateStringEx(String->Buffer, String->Length); +} + +/** + * Compares two strings. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +FORCEINLINE +LONG +PhCompareString( + _In_ PPH_STRING String1, + _In_ PPH_STRING String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return wcscmp(String1->Buffer, String2->Buffer); + else + return PhCompareStringRef(&String1->sr, &String2->sr, IgnoreCase); // faster than wcsicmp +} + +/** + * Compares two strings. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +FORCEINLINE +LONG +PhCompareString2( + _In_ PPH_STRING String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + { + return wcscmp(String1->Buffer, String2); + } + else + { + return PhCompareStringRef2(&String1->sr, String2, IgnoreCase); + } +} + +/** + * Compares two strings, handling NULL strings. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +FORCEINLINE +LONG +PhCompareStringWithNull( + _In_opt_ PPH_STRING String1, + _In_opt_ PPH_STRING String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (String1 && String2) + { + return PhCompareString(String1, String2, IgnoreCase); + } + else if (!String1) + { + return !String2 ? 0 : -1; + } + else + { + return 1; + } +} + +/** + * Determines whether two strings are equal. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +FORCEINLINE +BOOLEAN +PhEqualString( + _In_ PPH_STRING String1, + _In_ PPH_STRING String2, + _In_ BOOLEAN IgnoreCase + ) +{ + return PhEqualStringRef(&String1->sr, &String2->sr, IgnoreCase); +} + +/** + * Determines whether two strings are equal. + * + * \param String1 The first string. + * \param String2 The second string. + * \param IgnoreCase Whether to ignore character cases. + */ +FORCEINLINE +BOOLEAN +PhEqualString2( + _In_ PPH_STRING String1, + _In_ PWSTR String2, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + { + return wcscmp(String1->Buffer, String2) == 0; + } + else + { + return PhEqualStringRef2(&String1->sr, String2, IgnoreCase); + } +} + +/** + * Determines whether a string starts with another. + * + * \param String The first string. + * \param Prefix The second string. + * \param IgnoreCase Whether to ignore character cases. + * + * \return TRUE if \a String starts with \a Prefix, otherwise FALSE. + */ +FORCEINLINE +BOOLEAN +PhStartsWithString( + _In_ PPH_STRING String, + _In_ PPH_STRING Prefix, + _In_ BOOLEAN IgnoreCase + ) +{ + return PhStartsWithStringRef(&String->sr, &Prefix->sr, IgnoreCase); +} + +/** + * Determines whether a string starts with another. + * + * \param String The first string. + * \param Prefix The second string. + * \param IgnoreCase Whether to ignore character cases. + * + * \return TRUE if \a String starts with \a Prefix, otherwise FALSE. + */ +FORCEINLINE +BOOLEAN +PhStartsWithString2( + _In_ PPH_STRING String, + _In_ PWSTR Prefix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF prefix; + + PhInitializeStringRef(&prefix, Prefix); + + return PhStartsWithStringRef(&String->sr, &prefix, IgnoreCase); +} + +/** + * Determines whether a string ends with another. + * + * \param String The first string. + * \param Suffix The second string. + * \param IgnoreCase Whether to ignore character cases. + * + * \return TRUE if \a String ends with \a Suffix, otherwise FALSE. + */ +FORCEINLINE +BOOLEAN +PhEndsWithString( + _In_ PPH_STRING String, + _In_ PPH_STRING Suffix, + _In_ BOOLEAN IgnoreCase + ) +{ + return PhEndsWithStringRef(&String->sr, &Suffix->sr, IgnoreCase); +} + +/** + * Determines whether a string ends with another. + * + * \param String The first string. + * \param Suffix The second string. + * \param IgnoreCase Whether to ignore character cases. + * + * \return TRUE if \a String ends with \a Suffix, otherwise FALSE. + */ +FORCEINLINE +BOOLEAN +PhEndsWithString2( + _In_ PPH_STRING String, + _In_ PWSTR Suffix, + _In_ BOOLEAN IgnoreCase + ) +{ + PH_STRINGREF suffix; + + PhInitializeStringRef(&suffix, Suffix); + + return PhEndsWithStringRef(&String->sr, &suffix, IgnoreCase); +} + +/** + * Locates a character in a string. + * + * \param String The string to search. + * \param StartIndex The index, in characters, to start searching at. + * \param Char The character to search for. + * + * \return The index, in characters, of the first occurrence of \a Char in \a String after + * \a StartIndex. If \a Char was not found, -1 is returned. + */ +FORCEINLINE +ULONG_PTR +PhFindCharInString( + _In_ PPH_STRING String, + _In_ SIZE_T StartIndex, + _In_ WCHAR Char + ) +{ + if (StartIndex != 0) + { + ULONG_PTR r; + PH_STRINGREF sr; + + sr = String->sr; + PhSkipStringRef(&sr, StartIndex * sizeof(WCHAR)); + r = PhFindCharInStringRef(&sr, Char, FALSE); + + if (r != -1) + return r + StartIndex; + else + return -1; + } + else + { + return PhFindCharInStringRef(&String->sr, Char, FALSE); + } +} + +/** + * Locates a character in a string, backwards. + * + * \param String The string to search. + * \param StartIndex The index, in characters, to start searching at. + * \param Char The character to search for. + * + * \return The index, in characters, of the last occurrence of \a Char in \a String after + * \a StartIndex. If \a Char was not found, -1 is returned. + */ +FORCEINLINE +ULONG_PTR +PhFindLastCharInString( + _In_ PPH_STRING String, + _In_ SIZE_T StartIndex, + _In_ WCHAR Char + ) +{ + if (StartIndex != 0) + { + ULONG_PTR r; + PH_STRINGREF sr; + + sr = String->sr; + PhSkipStringRef(&sr, StartIndex * sizeof(WCHAR)); + r = PhFindLastCharInStringRef(&sr, Char, FALSE); + + if (r != -1) + return r + StartIndex; + else + return -1; + } + else + { + return PhFindLastCharInStringRef(&String->sr, Char, FALSE); + } +} + +/** + * Locates a string in a string. + * + * \param String The string to search. + * \param StartIndex The index, in characters, to start searching at. + * \param SubString The string to search for. + * + * \return The index, in characters, of the first occurrence of \a SubString in \a String after + * \a StartIndex. If \a SubString was not found, -1 is returned. + */ +FORCEINLINE +ULONG_PTR +PhFindStringInString( + _In_ PPH_STRING String, + _In_ SIZE_T StartIndex, + _In_ PWSTR SubString + ) +{ + PH_STRINGREF sr2; + + PhInitializeStringRef(&sr2, SubString); + + if (StartIndex != 0) + { + ULONG_PTR r; + PH_STRINGREF sr1; + + sr1 = String->sr; + PhSkipStringRef(&sr1, StartIndex * sizeof(WCHAR)); + r = PhFindStringInStringRef(&sr1, &sr2, FALSE); + + if (r != -1) + return r + StartIndex; + else + return -1; + } + else + { + return PhFindStringInStringRef(&String->sr, &sr2, FALSE); + } +} + +/** + * Creates a substring of a string. + * + * \param String The original string. + * \param StartIndex The start index, in characters. + * \param Count The number of characters to use. + */ +FORCEINLINE +PPH_STRING +PhSubstring( + _In_ PPH_STRING String, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ) +{ + return PhCreateStringEx(&String->Buffer[StartIndex], Count * sizeof(WCHAR)); +} + +/** + * Updates a string object's length with its true length as determined by an embedded null + * terminator. + * + * \param String The string to modify. + * + * \remarks Use this function after modifying a string object's buffer manually. + */ +FORCEINLINE +VOID +PhTrimToNullTerminatorString( + _Inout_ PPH_STRING String + ) +{ + String->Length = PhCountStringZ(String->Buffer) * sizeof(WCHAR); +} + +// Byte string + +extern PPH_OBJECT_TYPE PhBytesType; + +/** + * An 8-bit string object, which supports ASCII, UTF-8 and Windows multi-byte encodings, as well as + * binary data. + */ +typedef struct _PH_BYTES +{ + // Header + union + { + PH_BYTESREF br; + struct + { + /** The length, in bytes, of the string. */ + SIZE_T Length; + /** The buffer containing the contents of the string. */ + PCH Buffer; + }; + }; + + // Data + union + { + CHAR Data[1]; + struct + { + /** Reserved. */ + ULONG AllocationFlags; + /** Reserved. */ + PVOID Allocation; + }; + }; +} PH_BYTES, *PPH_BYTES; + +PHLIBAPI +PPH_BYTES +NTAPI +PhCreateBytes( + _In_ PSTR Buffer + ); + +PHLIBAPI +PPH_BYTES +NTAPI +PhCreateBytesEx( + _In_opt_ PCHAR Buffer, + _In_ SIZE_T Length + ); + +FORCEINLINE +PPH_BYTES +PhCreateBytes2( + _In_ PPH_BYTESREF Bytes + ) +{ + return PhCreateBytesEx(Bytes->Buffer, Bytes->Length); +} + +// Unicode + +#define PH_UNICODE_BYTE_ORDER_MARK 0xfeff +#define PH_UNICODE_MAX_CODE_POINT 0x10ffff + +#define PH_UNICODE_UTF16_TO_HIGH_SURROGATE(CodePoint) ((USHORT)((CodePoint) >> 10) + 0xd7c0) +#define PH_UNICODE_UTF16_TO_LOW_SURROGATE(CodePoint) ((USHORT)((CodePoint) & 0x3ff) + 0xdc00) +#define PH_UNICODE_UTF16_IS_HIGH_SURROGATE(CodeUnit) ((CodeUnit) >= 0xd800 && (CodeUnit) <= 0xdbff) +#define PH_UNICODE_UTF16_IS_LOW_SURROGATE(CodeUnit) ((CodeUnit) >= 0xdc00 && (CodeUnit) <= 0xdfff) +#define PH_UNICODE_UTF16_TO_CODE_POINT(HighSurrogate, LowSurrogate) (((ULONG)(HighSurrogate) << 10) + (ULONG)(LowSurrogate) - 0x35fdc00) + +#define PH_UNICODE_UTF8 0 +#define PH_UNICODE_UTF16 1 +#define PH_UNICODE_UTF32 2 + +typedef struct _PH_UNICODE_DECODER +{ + UCHAR Encoding; // PH_UNICODE_* + UCHAR State; + UCHAR InputCount; + UCHAR Reserved; + union + { + UCHAR Utf8[4]; + USHORT Utf16[2]; + ULONG Utf32; + } Input; + union + { + struct + { + UCHAR Input[4]; + UCHAR CodeUnit1; + UCHAR CodeUnit2; + UCHAR CodeUnit3; + UCHAR CodeUnit4; + } Utf8; + struct + { + USHORT Input[2]; + USHORT CodeUnit; + } Utf16; + struct + { + ULONG Input; + } Utf32; + } u; +} PH_UNICODE_DECODER, *PPH_UNICODE_DECODER; + +FORCEINLINE +VOID +PhInitializeUnicodeDecoder( + _Out_ PPH_UNICODE_DECODER Decoder, + _In_ UCHAR Encoding + ) +{ + memset(Decoder, 0, sizeof(PH_UNICODE_DECODER)); + Decoder->Encoding = Encoding; +} + +PHLIBAPI +BOOLEAN +NTAPI +PhWriteUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _In_ ULONG CodeUnit + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhDecodeUnicodeDecoder( + _Inout_ PPH_UNICODE_DECODER Decoder, + _Out_ PULONG CodePoint + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhEncodeUnicode( + _In_ UCHAR Encoding, + _In_ ULONG CodePoint, + _Out_opt_ PVOID CodeUnits, + _Out_ PULONG NumberOfCodeUnits + ); + +// 8-bit to UTF-16 + +PHLIBAPI +VOID +NTAPI +PhZeroExtendToUtf16Buffer( + _In_reads_bytes_(InputLength) PCH Input, + _In_ SIZE_T InputLength, + _Out_writes_bytes_(InputLength * sizeof(WCHAR)) PWCH Output + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhZeroExtendToUtf16Ex( + _In_reads_bytes_(InputLength) PCH Input, + _In_ SIZE_T InputLength + ); + +FORCEINLINE +PPH_STRING +PhZeroExtendToUtf16( + _In_ PSTR Input + ) +{ + return PhZeroExtendToUtf16Ex(Input, strlen(Input)); +} + +// UTF-16 to ASCII + +PHLIBAPI +PPH_BYTES +NTAPI +PhConvertUtf16ToAsciiEx( + _In_ PWCH Buffer, + _In_ SIZE_T Length, + _In_opt_ CHAR Replacement + ); + +FORCEINLINE +PPH_BYTES +PhConvertUtf16ToAscii( + _In_ PWSTR Buffer, + _In_opt_ CHAR Replacement + ) +{ + return PhConvertUtf16ToAsciiEx(Buffer, PhCountStringZ(Buffer) * sizeof(WCHAR), Replacement); +} + +// Multi-byte to UTF-16 +// In-place: RtlMultiByteToUnicodeN, RtlMultiByteToUnicodeSize + +PHLIBAPI +PPH_STRING +NTAPI +PhConvertMultiByteToUtf16( + _In_ PSTR Buffer + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConvertMultiByteToUtf16Ex( + _In_ PCHAR Buffer, + _In_ SIZE_T Length + ); + +// UTF-16 to multi-byte +// In-place: RtlUnicodeToMultiByteN, RtlUnicodeToMultiByteSize + +PHLIBAPI +PPH_BYTES +NTAPI +PhConvertUtf16ToMultiByte( + _In_ PWSTR Buffer + ); + +PHLIBAPI +PPH_BYTES +NTAPI +PhConvertUtf16ToMultiByteEx( + _In_ PWCHAR Buffer, + _In_ SIZE_T Length + ); + +// UTF-8 to UTF-16 + +PHLIBAPI +BOOLEAN +NTAPI +PhConvertUtf8ToUtf16Size( + _Out_ PSIZE_T BytesInUtf16String, + _In_reads_bytes_(BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T BytesInUtf8String + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhConvertUtf8ToUtf16Buffer( + _Out_writes_bytes_to_(MaxBytesInUtf16String, *BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T MaxBytesInUtf16String, + _Out_opt_ PSIZE_T BytesInUtf16String, + _In_reads_bytes_(BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T BytesInUtf8String + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConvertUtf8ToUtf16( + _In_ PSTR Buffer + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhConvertUtf8ToUtf16Ex( + _In_ PCHAR Buffer, + _In_ SIZE_T Length + ); + +// UTF-16 to UTF-8 + +PHLIBAPI +BOOLEAN +NTAPI +PhConvertUtf16ToUtf8Size( + _Out_ PSIZE_T BytesInUtf8String, + _In_reads_bytes_(BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T BytesInUtf16String + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhConvertUtf16ToUtf8Buffer( + _Out_writes_bytes_to_(MaxBytesInUtf8String, *BytesInUtf8String) PCH Utf8String, + _In_ SIZE_T MaxBytesInUtf8String, + _Out_opt_ PSIZE_T BytesInUtf8String, + _In_reads_bytes_(BytesInUtf16String) PWCH Utf16String, + _In_ SIZE_T BytesInUtf16String + ); + +PHLIBAPI +PPH_BYTES +NTAPI +PhConvertUtf16ToUtf8( + _In_ PWSTR Buffer + ); + +PHLIBAPI +PPH_BYTES +NTAPI +PhConvertUtf16ToUtf8Ex( + _In_ PWCHAR Buffer, + _In_ SIZE_T Length + ); + +// String builder + +/** + * A string builder structure. + * The string builder object allows you to easily construct complex strings without allocating + * a great number of strings in the process. + */ +typedef struct _PH_STRING_BUILDER +{ + /** Allocated length of the string, not including the null terminator. */ + SIZE_T AllocatedLength; + /** + * The constructed string. + * \a String will be allocated for \a AllocatedLength, we will modify the \a Length field to be + * the correct length. + */ + PPH_STRING String; +} PH_STRING_BUILDER, *PPH_STRING_BUILDER; + +PHLIBAPI +VOID +NTAPI +PhInitializeStringBuilder( + _Out_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T InitialCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFinalStringBuilderString( + _Inout_ PPH_STRING_BUILDER StringBuilder + ); + +PHLIBAPI +VOID +NTAPI +PhAppendStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +VOID +NTAPI +PhAppendStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ PWSTR String + ); + +PHLIBAPI +VOID +NTAPI +PhAppendStringBuilderEx( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_opt_ PWCHAR String, + _In_ SIZE_T Length + ); + +PHLIBAPI +VOID +NTAPI +PhAppendCharStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ WCHAR Character + ); + +PHLIBAPI +VOID +NTAPI +PhAppendCharStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ WCHAR Character, + _In_ SIZE_T Count + ); + +PHLIBAPI +VOID +NTAPI +PhAppendFormatStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ _Printf_format_string_ PWSTR Format, + ... + ); + +VOID +NTAPI +PhAppendFormatStringBuilder_V( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ _Printf_format_string_ PWSTR Format, + _In_ va_list ArgPtr + ); + +PHLIBAPI +VOID +NTAPI +PhInsertStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +VOID +NTAPI +PhInsertStringBuilder2( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_ PWSTR String + ); + +PHLIBAPI +VOID +NTAPI +PhInsertStringBuilderEx( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Index, + _In_opt_ PWCHAR String, + _In_ SIZE_T Length + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ); + +FORCEINLINE +VOID +PhRemoveEndStringBuilder( + _Inout_ PPH_STRING_BUILDER StringBuilder, + _In_ SIZE_T Count + ) +{ + PhRemoveStringBuilder( + StringBuilder, + StringBuilder->String->Length / sizeof(WCHAR) - Count, + Count + ); +} + +// Byte string builder + +/** + * A byte string builder structure. + * This is similar to string builder, but is based on PH_BYTES and is suitable for general binary + * data. + */ +typedef struct _PH_BYTES_BUILDER +{ + /** Allocated length of the byte string, not including the null terminator. */ + SIZE_T AllocatedLength; + /** + * The constructed byte string. + * \a Bytes will be allocated for \a AllocatedLength, we will modify the \a Length field to be + * the correct length. + */ + PPH_BYTES Bytes; +} PH_BYTES_BUILDER, *PPH_BYTES_BUILDER; + +PHLIBAPI +VOID +NTAPI +PhInitializeBytesBuilder( + _Out_ PPH_BYTES_BUILDER BytesBuilder, + _In_ SIZE_T InitialCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteBytesBuilder( + _Inout_ PPH_BYTES_BUILDER BytesBuilder + ); + +PHLIBAPI +PPH_BYTES +NTAPI +PhFinalBytesBuilderBytes( + _Inout_ PPH_BYTES_BUILDER BytesBuilder + ); + +FORCEINLINE +PVOID +PhOffsetBytesBuilder( + _In_ PPH_BYTES_BUILDER BytesBuilder, + _In_ SIZE_T Offset + ) +{ + return BytesBuilder->Bytes->Buffer + Offset; +} + +PHLIBAPI +VOID +NTAPI +PhAppendBytesBuilder( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_ PPH_BYTESREF Bytes + ); + +PHLIBAPI +VOID +NTAPI +PhAppendBytesBuilder2( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_ PCHAR Bytes + ); + +PHLIBAPI +PVOID +NTAPI +PhAppendBytesBuilderEx( + _Inout_ PPH_BYTES_BUILDER BytesBuilder, + _In_opt_ PVOID Buffer, + _In_ SIZE_T Length, + _In_opt_ SIZE_T Alignment, + _Out_opt_ PSIZE_T Offset + ); + +// Array + +/** An array structure. Storage is automatically allocated for new elements. */ +typedef struct _PH_ARRAY +{ + /** The number of items in the list. */ + SIZE_T Count; + /** The number of items for which storage is allocated. */ + SIZE_T AllocatedCount; + /** The size of each item, in bytes. */ + SIZE_T ItemSize; + /** The base address of the array. */ + PVOID Items; +} PH_ARRAY, *PPH_ARRAY; + +FORCEINLINE +PVOID +PhItemArray( + _In_ PPH_ARRAY Array, + _In_ SIZE_T Index + ) +{ + return (PCHAR)Array->Items + Index * Array->ItemSize; +} + +PHLIBAPI +VOID +NTAPI +PhInitializeArray( + _Out_ PPH_ARRAY Array, + _In_ SIZE_T ItemSize, + _In_ SIZE_T InitialCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteArray( + _Inout_ PPH_ARRAY Array + ); + +PHLIBAPI +PVOID +NTAPI +PhFinalArrayItems( + _Inout_ PPH_ARRAY Array + ); + +PHLIBAPI +VOID +NTAPI +PhResizeArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T NewCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhAddItemArray( + _Inout_ PPH_ARRAY Array, + _In_ PVOID Item + ); + +PHLIBAPI +VOID +NTAPI +PhAddItemsArray( + _Inout_ PPH_ARRAY Array, + _In_ PVOID Items, + _In_ SIZE_T Count + ); + +PHLIBAPI +VOID +NTAPI +PhClearArray( + _Inout_ PPH_ARRAY Array + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveItemArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T Index + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveItemsArray( + _Inout_ PPH_ARRAY Array, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ); + +// List + +extern PPH_OBJECT_TYPE PhListType; + +/** A list structure. Storage is automatically allocated for new elements. */ +typedef struct _PH_LIST +{ + /** The number of items in the list. */ + ULONG Count; + /** The number of items for which storage is allocated. */ + ULONG AllocatedCount; + /** The array of list items. */ + PVOID *Items; +} PH_LIST, *PPH_LIST; + +PHLIBAPI +PPH_LIST +NTAPI +PhCreateList( + _In_ ULONG InitialCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhResizeList( + _Inout_ PPH_LIST List, + _In_ ULONG NewCapacity + ); + +PHLIBAPI +VOID +NTAPI +PhAddItemList( + _Inout_ PPH_LIST List, + _In_ PVOID Item + ); + +PHLIBAPI +VOID +NTAPI +PhAddItemsList( + _Inout_ PPH_LIST List, + _In_ PVOID *Items, + _In_ ULONG Count + ); + +PHLIBAPI +VOID +NTAPI +PhClearList( + _Inout_ PPH_LIST List + ); + +_Success_(return != -1) +PHLIBAPI +ULONG +NTAPI +PhFindItemList( + _In_ PPH_LIST List, + _In_ PVOID Item + ); + +PHLIBAPI +VOID +NTAPI +PhInsertItemList( + _Inout_ PPH_LIST List, + _In_ ULONG Index, + _In_ PVOID Item + ); + +PHLIBAPI +VOID +NTAPI +PhInsertItemsList( + _Inout_ PPH_LIST List, + _In_ ULONG Index, + _In_ PVOID *Items, + _In_ ULONG Count + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveItemList( + _Inout_ PPH_LIST List, + _In_ ULONG Index + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveItemsList( + _Inout_ PPH_LIST List, + _In_ ULONG StartIndex, + _In_ ULONG Count + ); + +/** + * A comparison function. + * + * \param Item1 The first item. + * \param Item2 The second item. + * \param Context A user-defined value. + * + * \return + * \li A positive value if \a Item1 > \a Item2, + * \li A negative value if \a Item1 < \a Item2, and + * \li 0 if \a Item1 = \a Item2. + */ +typedef LONG (NTAPI *PPH_COMPARE_FUNCTION)( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ); + +// Pointer list + +extern PPH_OBJECT_TYPE PhPointerListType; + +/** + * A pointer list structure. The pointer list is similar to the normal list structure, but both + * insertions and deletions occur in constant time. The list is not ordered. + */ +typedef struct _PH_POINTER_LIST +{ + /** The number of pointers in the list. */ + ULONG Count; + /** The number of pointers for which storage is allocated. */ + ULONG AllocatedCount; + /** Index into pointer array for free list. */ + ULONG FreeEntry; + /** Index of next usable index into pointer array. */ + ULONG NextEntry; + /** The array of pointers. */ + PVOID *Items; +} PH_POINTER_LIST, *PPH_POINTER_LIST; + +#define PH_IS_LIST_POINTER_VALID(Pointer) (!((ULONG_PTR)(Pointer) & 0x1)) + +PHLIBAPI +PPH_POINTER_LIST +NTAPI +PhCreatePointerList( + _In_ ULONG InitialCapacity + ); + +PHLIBAPI +HANDLE +NTAPI +PhAddItemPointerList( + _Inout_ PPH_POINTER_LIST PointerList, + _In_ PVOID Pointer + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhEnumPointerListEx( + _In_ PPH_POINTER_LIST PointerList, + _Inout_ PULONG EnumerationKey, + _Out_ PVOID *Pointer, + _Out_ PHANDLE PointerHandle + ); + +PHLIBAPI +HANDLE +NTAPI +PhFindItemPointerList( + _In_ PPH_POINTER_LIST PointerList, + _In_ PVOID Pointer + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveItemPointerList( + _Inout_ PPH_POINTER_LIST PointerList, + _In_ HANDLE PointerHandle + ); + +FORCEINLINE +BOOLEAN +PhEnumPointerList( + _In_ PPH_POINTER_LIST PointerList, + _Inout_ PULONG EnumerationKey, + _Out_ PVOID *Pointer + ) +{ + while (*EnumerationKey < PointerList->NextEntry) + { + PVOID pointer = PointerList->Items[*EnumerationKey]; + + (*EnumerationKey)++; + + if (PH_IS_LIST_POINTER_VALID(pointer)) + { + *Pointer = pointer; + return TRUE; + } + } + + return FALSE; +} + +// Hash + +typedef struct _PH_HASH_ENTRY +{ + struct _PH_HASH_ENTRY *Next; + ULONG Hash; +} PH_HASH_ENTRY, *PPH_HASH_ENTRY; + +#define PH_HASH_SET_INIT { 0 } +#define PH_HASH_SET_SIZE(Buckets) (sizeof(Buckets) / sizeof(PPH_HASH_ENTRY)) + +/** + * Initializes a hash set. + * + * \param Buckets The bucket array. + * \param NumberOfBuckets The number of buckets. + */ +FORCEINLINE +VOID +PhInitializeHashSet( + _Out_ PPH_HASH_ENTRY *Buckets, + _In_ ULONG NumberOfBuckets + ) +{ + memset(Buckets, 0, sizeof(PPH_HASH_ENTRY) * NumberOfBuckets); +} + +/** + * Allocates and initializes a hash set. + * + * \param NumberOfBuckets The number of buckets. + * + * \return The allocated hash set. You must free it with PhFree() when you no longer need it. + */ +FORCEINLINE +PPH_HASH_ENTRY * +PhCreateHashSet( + _In_ ULONG NumberOfBuckets + ) +{ + PPH_HASH_ENTRY *buckets; + + buckets = (PPH_HASH_ENTRY *)PhAllocate(sizeof(PPH_HASH_ENTRY) * NumberOfBuckets); + PhInitializeHashSet(buckets, NumberOfBuckets); + + return buckets; +} + +/** + * Determines the number of entries in a hash set. + * + * \param Buckets The bucket array. + * \param NumberOfBuckets The number of buckets. + * + * \return The number of entries in the hash set. + */ +FORCEINLINE +ULONG +PhCountHashSet( + _In_ PPH_HASH_ENTRY *Buckets, + _In_ ULONG NumberOfBuckets + ) +{ + ULONG i; + PPH_HASH_ENTRY entry; + ULONG count; + + count = 0; + + for (i = 0; i < NumberOfBuckets; i++) + { + for (entry = Buckets[i]; entry; entry = entry->Next) + count++; + } + + return count; +} + +/** + * Moves entries from one hash set to another. + * + * \param NewBuckets The new bucket array. + * \param NumberOfNewBuckets The number of buckets in \a NewBuckets. + * \param OldBuckets The old bucket array. + * \param NumberOfOldBuckets The number of buckets in \a OldBuckets. + * + * \remarks \a NewBuckets and \a OldBuckets must be different. + */ +FORCEINLINE +VOID +PhDistributeHashSet( + _Inout_ PPH_HASH_ENTRY *NewBuckets, + _In_ ULONG NumberOfNewBuckets, + _In_ PPH_HASH_ENTRY *OldBuckets, + _In_ ULONG NumberOfOldBuckets + ) +{ + ULONG i; + PPH_HASH_ENTRY entry; + PPH_HASH_ENTRY nextEntry; + ULONG index; + + for (i = 0; i < NumberOfOldBuckets; i++) + { + entry = OldBuckets[i]; + + while (entry) + { + nextEntry = entry->Next; + + index = entry->Hash & (NumberOfNewBuckets - 1); + entry->Next = NewBuckets[index]; + NewBuckets[index] = entry; + + entry = nextEntry; + } + } +} + +/** + * Adds an entry to a hash set. + * + * \param Buckets The bucket array. + * \param NumberOfBuckets The number of buckets. + * \param Entry The entry. + * \param Hash The hash for the entry. + * + * \remarks This function does not check for duplicates. + */ +FORCEINLINE +VOID +PhAddEntryHashSet( + _Inout_ PPH_HASH_ENTRY *Buckets, + _In_ ULONG NumberOfBuckets, + _Out_ PPH_HASH_ENTRY Entry, + _In_ ULONG Hash + ) +{ + ULONG index; + + index = Hash & (NumberOfBuckets - 1); + + Entry->Hash = Hash; + Entry->Next = Buckets[index]; + Buckets[index] = Entry; +} + +/** + * Begins the process of finding an entry in a hash set. + * + * \param Buckets The bucket array. + * \param NumberOfBuckets The number of buckets. + * \param Hash The hash for the entry. + * + * \return The first entry in the chain. + * + * \remarks If the function returns NULL, the entry does not exist in the hash set. + */ +FORCEINLINE +PPH_HASH_ENTRY +PhFindEntryHashSet( + _In_ PPH_HASH_ENTRY *Buckets, + _In_ ULONG NumberOfBuckets, + _In_ ULONG Hash + ) +{ + return Buckets[Hash & (NumberOfBuckets - 1)]; +} + +/** + * Removes an entry from a hash set. + * + * \param Buckets The bucket array. + * \param NumberOfBuckets The number of buckets. + * \param Entry An entry present in the hash set. + */ +FORCEINLINE +VOID +PhRemoveEntryHashSet( + _Inout_ PPH_HASH_ENTRY *Buckets, + _In_ ULONG NumberOfBuckets, + _Inout_ PPH_HASH_ENTRY Entry + ) +{ + ULONG index; + PPH_HASH_ENTRY entry; + PPH_HASH_ENTRY previousEntry; + + index = Entry->Hash & (NumberOfBuckets - 1); + previousEntry = NULL; + + entry = Buckets[index]; + + do + { + if (entry == Entry) + { + if (!previousEntry) + Buckets[index] = entry->Next; + else + previousEntry->Next = entry->Next; + + return; + } + + previousEntry = entry; + entry = entry->Next; + } while (entry); + + // Entry doesn't actually exist in the set. This is a fatal logic error. + PhRaiseStatus(STATUS_INTERNAL_ERROR); +} + +/** + * Resizes a hash set. + * + * \param Buckets A pointer to the bucket array. On return the new bucket array is stored in this + * variable. + * \param NumberOfBuckets A pointer to the number of buckets. On return the new number of buckets is + * stored in this variable. + * \param NewNumberOfBuckets The new number of buckets. + */ +FORCEINLINE +VOID +PhResizeHashSet( + _Inout_ PPH_HASH_ENTRY **Buckets, + _Inout_ PULONG NumberOfBuckets, + _In_ ULONG NewNumberOfBuckets + ) +{ + PPH_HASH_ENTRY *newBuckets; + + newBuckets = PhCreateHashSet(NewNumberOfBuckets); + PhDistributeHashSet(newBuckets, NewNumberOfBuckets, *Buckets, *NumberOfBuckets); + + PhFree(*Buckets); + *Buckets = newBuckets; + *NumberOfBuckets = NewNumberOfBuckets; +} + +// Hashtable + +extern PPH_OBJECT_TYPE PhHashtableType; + +typedef struct _PH_HASHTABLE_ENTRY +{ + /** Hash code of the entry. -1 if entry is unused. */ + ULONG HashCode; + /** + * Either the index of the next entry in the bucket, the index of the next free entry, or -1 for + * invalid. + */ + ULONG Next; + /** The beginning of user data. */ + QUAD Body; +} PH_HASHTABLE_ENTRY, *PPH_HASHTABLE_ENTRY; + +/** + * A comparison function used by a hashtable. + * + * \param Entry1 The first entry. + * \param Entry2 The second entry. + * + * \return TRUE if the entries are equal, otherwise FALSE. + */ +typedef BOOLEAN (NTAPI *PPH_HASHTABLE_EQUAL_FUNCTION)( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +/** + * A hash function used by a hashtable. + * + * \param Entry The entry. + * + * \return A hash code for the entry. + * + * \remarks + * \li Two entries which are considered to be equal by the comparison function must be given the + * same hash code. + * \li Two different entries do not have to be given different hash codes. + */ +typedef ULONG (NTAPI *PPH_HASHTABLE_HASH_FUNCTION)( + _In_ PVOID Entry + ); + +// Use power-of-two sizes instead of primes +#define PH_HASHTABLE_POWER_OF_TWO_SIZE + +// Enables 2^32-1 possible hash codes instead of only 2^31 +//#define PH_HASHTABLE_FULL_HASH + +/** + * A hashtable structure. + */ +typedef struct _PH_HASHTABLE +{ + /** Size of user data in each entry. */ + ULONG EntrySize; + /** The comparison function. */ + PPH_HASHTABLE_EQUAL_FUNCTION EqualFunction; + /** The hash function. */ + PPH_HASHTABLE_HASH_FUNCTION HashFunction; + + /** The number of allocated buckets. */ + ULONG AllocatedBuckets; + /** The bucket array. */ + PULONG Buckets; + /** The number of allocated entries. */ + ULONG AllocatedEntries; + /** The entry array. */ + PVOID Entries; + + /** Number of entries in the hashtable. */ + ULONG Count; + /** Index into entry array for free list. */ + ULONG FreeEntry; + /** + * Index of next usable index into entry array, a.k.a. the count of entries that were ever + * allocated. + */ + ULONG NextEntry; +} PH_HASHTABLE, *PPH_HASHTABLE; + +#define PH_HASHTABLE_ENTRY_SIZE(InnerSize) (FIELD_OFFSET(PH_HASHTABLE_ENTRY, Body) + (InnerSize)) +#define PH_HASHTABLE_GET_ENTRY(Hashtable, Index) \ + ((PPH_HASHTABLE_ENTRY)PTR_ADD_OFFSET((Hashtable)->Entries, \ + PH_HASHTABLE_ENTRY_SIZE((Hashtable)->EntrySize) * (Index))) +#define PH_HASHTABLE_GET_ENTRY_INDEX(Hashtable, Entry) \ + ((ULONG)(PTR_ADD_OFFSET(Entry, -(Hashtable)->Entries) / \ + PH_HASHTABLE_ENTRY_SIZE((Hashtable)->EntrySize))) + +PHLIBAPI +PPH_HASHTABLE +NTAPI +PhCreateHashtable( + _In_ ULONG EntrySize, + _In_ PPH_HASHTABLE_EQUAL_FUNCTION EqualFunction, + _In_ PPH_HASHTABLE_HASH_FUNCTION HashFunction, + _In_ ULONG InitialCapacity + ); + +PHLIBAPI +PVOID +NTAPI +PhAddEntryHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ); + +PHLIBAPI +PVOID +NTAPI +PhAddEntryHashtableEx( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry, + _Out_opt_ PBOOLEAN Added + ); + +PHLIBAPI +VOID +NTAPI +PhClearHashtable( + _Inout_ PPH_HASHTABLE Hashtable + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhEnumHashtable( + _In_ PPH_HASHTABLE Hashtable, + _Out_ PVOID *Entry, + _Inout_ PULONG EnumerationKey + ); + +PHLIBAPI +PVOID +NTAPI +PhFindEntryHashtable( + _In_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhRemoveEntryHashtable( + _Inout_ PPH_HASHTABLE Hashtable, + _In_ PVOID Entry + ); + +// New faster enumeration method + +typedef struct _PH_HASHTABLE_ENUM_CONTEXT +{ + ULONG_PTR Current; + ULONG_PTR End; + ULONG_PTR Step; +} PH_HASHTABLE_ENUM_CONTEXT, *PPH_HASHTABLE_ENUM_CONTEXT; + +FORCEINLINE +VOID +PhBeginEnumHashtable( + _In_ PPH_HASHTABLE Hashtable, + _Out_ PPH_HASHTABLE_ENUM_CONTEXT Context + ) +{ + Context->Current = (ULONG_PTR)Hashtable->Entries; + Context->Step = PH_HASHTABLE_ENTRY_SIZE(Hashtable->EntrySize); + Context->End = Context->Current + (ULONG_PTR)Hashtable->NextEntry * Context->Step; +} + +FORCEINLINE +PVOID +PhNextEnumHashtable( + _Inout_ PPH_HASHTABLE_ENUM_CONTEXT Context + ) +{ + PPH_HASHTABLE_ENTRY entry; + + while (Context->Current != Context->End) + { + entry = (PPH_HASHTABLE_ENTRY)Context->Current; + Context->Current += Context->Step; + + if (entry->HashCode != -1) + return &entry->Body; + } + + return NULL; +} + +PHLIBAPI +ULONG +NTAPI +PhHashBytes( + _In_reads_(Length) PUCHAR Bytes, + _In_ SIZE_T Length + ); + +PHLIBAPI +ULONG +NTAPI +PhHashStringRef( + _In_ PPH_STRINGREF String, + _In_ BOOLEAN IgnoreCase + ); + +FORCEINLINE +ULONG +PhHashInt32( + _In_ ULONG Value + ) +{ + // Java style. + Value ^= (Value >> 20) ^ (Value >> 12); + return Value ^ (Value >> 7) ^ (Value >> 4); +} + +FORCEINLINE +ULONG +PhHashInt64( + _In_ ULONG64 Value + ) +{ + // http://www.concentric.net/~Ttwang/tech/inthash.htm + + Value = ~Value + (Value << 18); + Value ^= Value >> 31; + Value *= 21; + Value ^= Value >> 11; + Value += Value << 6; + Value ^= Value >> 22; + + return (ULONG)Value; +} + +FORCEINLINE +ULONG +PhHashIntPtr( + _In_ ULONG_PTR Value + ) +{ +#ifdef _WIN64 + return PhHashInt64(Value); +#else + return PhHashInt32(Value); +#endif +} + +// Simple hashtable + +typedef struct _PH_KEY_VALUE_PAIR +{ + PVOID Key; + PVOID Value; +} PH_KEY_VALUE_PAIR, *PPH_KEY_VALUE_PAIR; + +PHLIBAPI +PPH_HASHTABLE +NTAPI +PhCreateSimpleHashtable( + _In_ ULONG InitialCapacity + ); + +PHLIBAPI +PVOID +NTAPI +PhAddItemSimpleHashtable( + _Inout_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key, + _In_opt_ PVOID Value + ); + +PHLIBAPI +PVOID * +NTAPI +PhFindItemSimpleHashtable( + _In_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key + ); + +FORCEINLINE +PVOID +NTAPI +PhFindItemSimpleHashtable2( + _In_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key + ) +{ + PVOID *item; + + item = PhFindItemSimpleHashtable(SimpleHashtable, Key); + + if (item) + return *item; + else + return NULL; +} + +PHLIBAPI +BOOLEAN +NTAPI +PhRemoveItemSimpleHashtable( + _Inout_ PPH_HASHTABLE SimpleHashtable, + _In_opt_ PVOID Key + ); + +// Free list + +typedef struct _PH_FREE_LIST +{ + SLIST_HEADER ListHead; + + ULONG Count; + ULONG MaximumCount; + SIZE_T Size; +} PH_FREE_LIST, *PPH_FREE_LIST; + +typedef struct _PH_FREE_LIST_ENTRY +{ + SLIST_ENTRY ListEntry; + QUAD_PTR Body; +} PH_FREE_LIST_ENTRY, *PPH_FREE_LIST_ENTRY; + +#ifdef _WIN64 +C_ASSERT(FIELD_OFFSET(PH_FREE_LIST_ENTRY, ListEntry) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_FREE_LIST_ENTRY, Body) == 0x10); +#else +C_ASSERT(FIELD_OFFSET(PH_FREE_LIST_ENTRY, ListEntry) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_FREE_LIST_ENTRY, Body) == 0x8); +#endif + +PHLIBAPI +VOID +NTAPI +PhInitializeFreeList( + _Out_ PPH_FREE_LIST FreeList, + _In_ SIZE_T Size, + _In_ ULONG MaximumCount + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteFreeList( + _Inout_ PPH_FREE_LIST FreeList + ); + +PHLIBAPI +PVOID +NTAPI +PhAllocateFromFreeList( + _Inout_ PPH_FREE_LIST FreeList + ); + +PHLIBAPI +VOID +NTAPI +PhFreeToFreeList( + _Inout_ PPH_FREE_LIST FreeList, + _In_ PVOID Memory + ); + +// Callback + +/** + * A callback function. + * + * \param Parameter A value given to all callback functions being notified. + * \param Context A user-defined value passed to PhRegisterCallback(). + */ +typedef VOID (NTAPI *PPH_CALLBACK_FUNCTION)( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +/** A callback registration structure. */ +typedef struct _PH_CALLBACK_REGISTRATION +{ + /** The list entry in the callbacks list. */ + LIST_ENTRY ListEntry; + /** The callback function. */ + PPH_CALLBACK_FUNCTION Function; + /** A user-defined value to be passed to the callback function. */ + PVOID Context; + /** A value indicating whether the registration structure is being used. */ + LONG Busy; + /** Whether the registration structure is being removed. */ + BOOLEAN Unregistering; + BOOLEAN Reserved; + /** Flags controlling the callback. */ + USHORT Flags; +} PH_CALLBACK_REGISTRATION, *PPH_CALLBACK_REGISTRATION; + +/** + * A callback structure. The callback object allows multiple callback functions to be registered and + * notified in a thread-safe way. + */ +typedef struct _PH_CALLBACK +{ + /** The list of registered callbacks. */ + LIST_ENTRY ListHead; + /** A lock protecting the callbacks list. */ + PH_QUEUED_LOCK ListLock; + /** A condition variable pulsed when the callback becomes free. */ + PH_CONDITION BusyCondition; +} PH_CALLBACK, *PPH_CALLBACK; + +#define PH_CALLBACK_DECLARE(Name) PH_CALLBACK Name = { &Name.ListHead, &Name.ListHead, PH_QUEUED_LOCK_INIT, PH_CONDITION_INIT } + +PHLIBAPI +VOID +NTAPI +PhInitializeCallback( + _Out_ PPH_CALLBACK Callback + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteCallback( + _Inout_ PPH_CALLBACK Callback + ); + +PHLIBAPI +VOID +NTAPI +PhRegisterCallback( + _Inout_ PPH_CALLBACK Callback, + _In_ PPH_CALLBACK_FUNCTION Function, + _In_opt_ PVOID Context, + _Out_ PPH_CALLBACK_REGISTRATION Registration + ); + +PHLIBAPI +VOID +NTAPI +PhRegisterCallbackEx( + _Inout_ PPH_CALLBACK Callback, + _In_ PPH_CALLBACK_FUNCTION Function, + _In_opt_ PVOID Context, + _In_ USHORT Flags, + _Out_ PPH_CALLBACK_REGISTRATION Registration + ); + +PHLIBAPI +VOID +NTAPI +PhUnregisterCallback( + _Inout_ PPH_CALLBACK Callback, + _Inout_ PPH_CALLBACK_REGISTRATION Registration + ); + +PHLIBAPI +VOID +NTAPI +PhInvokeCallback( + _In_ PPH_CALLBACK Callback, + _In_opt_ PVOID Parameter + ); + +// General + +PHLIBAPI +ULONG +NTAPI +PhGetPrimeNumber( + _In_ ULONG Minimum + ); + +PHLIBAPI +ULONG +NTAPI +PhRoundUpToPowerOfTwo( + _In_ ULONG Number + ); + +PHLIBAPI +ULONG +NTAPI +PhExponentiate( + _In_ ULONG Base, + _In_ ULONG Exponent + ); + +PHLIBAPI +ULONG64 +NTAPI +PhExponentiate64( + _In_ ULONG64 Base, + _In_ ULONG Exponent + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhHexStringToBuffer( + _In_ PPH_STRINGREF String, + _Out_writes_bytes_(String->Length / sizeof(WCHAR) / 2) PUCHAR Buffer + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhBufferToHexString( + _In_reads_bytes_(Length) PUCHAR Buffer, + _In_ ULONG Length + ); + +PPH_STRING +NTAPI +PhBufferToHexStringEx( + _In_reads_bytes_(Length) PUCHAR Buffer, + _In_ ULONG Length, + _In_ BOOLEAN UpperCase + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhStringToInteger64( + _In_ PPH_STRINGREF String, + _In_opt_ ULONG Base, + _Out_opt_ PLONG64 Integer + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhStringToDouble( + _In_ PPH_STRINGREF String, + _Reserved_ ULONG Base, + _Out_opt_ DOUBLE *Double + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhIntegerToString64( + _In_ LONG64 Integer, + _In_opt_ ULONG Base, + _In_ BOOLEAN Signed + ); + +#define PH_TIMESPAN_STR_LEN 30 +#define PH_TIMESPAN_STR_LEN_1 (PH_TIMESPAN_STR_LEN + 1) + +#define PH_TIMESPAN_HMS 0 +#define PH_TIMESPAN_HMSM 1 +#define PH_TIMESPAN_DHMS 2 + +PHLIBAPI +VOID +NTAPI +PhPrintTimeSpan( + _Out_writes_(PH_TIMESPAN_STR_LEN_1) PWSTR Destination, + _In_ ULONG64 Ticks, + _In_opt_ ULONG Mode + ); + +PHLIBAPI +VOID +NTAPI +PhFillMemoryUlong( + _Inout_updates_(Count) _Needs_align_(4) PULONG Memory, + _In_ ULONG Value, + _In_ SIZE_T Count + ); + +PHLIBAPI +VOID +NTAPI +PhDivideSinglesBySingle( + _Inout_updates_(Count) PFLOAT A, + _In_ FLOAT B, + _In_ SIZE_T Count + ); + +// Auto-dereference convenience functions + +FORCEINLINE +PPH_STRING +PhaCreateString( + _In_ PWSTR Buffer + ) +{ + return PH_AUTO_T(PH_STRING, PhCreateString(Buffer)); +} + +FORCEINLINE +PPH_STRING +PhaCreateStringEx( + _In_opt_ PWSTR Buffer, + _In_ SIZE_T Length + ) +{ + return PH_AUTO_T(PH_STRING, PhCreateStringEx(Buffer, Length)); +} + +FORCEINLINE +PPH_STRING +PhaDuplicateString( + _In_ PPH_STRING String + ) +{ + return PH_AUTO_T(PH_STRING, PhDuplicateString(String)); +} + +FORCEINLINE +PPH_STRING +PhaConcatStrings( + _In_ ULONG Count, + ... + ) +{ + va_list argptr; + + va_start(argptr, Count); + + return PH_AUTO_T(PH_STRING, PhConcatStrings_V(Count, argptr)); +} + +FORCEINLINE +PPH_STRING +PhaConcatStrings2( + _In_ PWSTR String1, + _In_ PWSTR String2 + ) +{ + return PH_AUTO_T(PH_STRING, PhConcatStrings2(String1, String2)); +} + +FORCEINLINE +PPH_STRING +PhaFormatString( + _In_ _Printf_format_string_ PWSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + + return PH_AUTO_T(PH_STRING, PhFormatString_V(Format, argptr)); +} + +FORCEINLINE +PPH_STRING +PhaLowerString( + _In_ PPH_STRING String + ) +{ + PPH_STRING newString; + + newString = PhaDuplicateString(String); + _wcslwr(newString->Buffer); + + return newString; +} + +FORCEINLINE +PPH_STRING +PhaUpperString( + _In_ PPH_STRING String + ) +{ + PPH_STRING newString; + + newString = PhaDuplicateString(String); + _wcsupr(newString->Buffer); + + return newString; +} + +FORCEINLINE +PPH_STRING +PhaSubstring( + _In_ PPH_STRING String, + _In_ SIZE_T StartIndex, + _In_ SIZE_T Count + ) +{ + return PH_AUTO_T(PH_STRING, PhSubstring(String, StartIndex, Count)); +} + +// Format + +typedef enum _PH_FORMAT_TYPE +{ + CharFormatType, + StringFormatType, + StringZFormatType, + MultiByteStringFormatType, + MultiByteStringZFormatType, + Int32FormatType, + Int64FormatType, + IntPtrFormatType, + UInt32FormatType, + UInt64FormatType, + UIntPtrFormatType, + DoubleFormatType, + SizeFormatType, + FormatTypeMask = 0x3f, + + /** If not specified, for floating-point 6 is assumed **/ + FormatUsePrecision = 0x40, + /** If not specified, ' ' is assumed */ + FormatUsePad = 0x80, + /** If not specified, 10 is assumed */ + FormatUseRadix = 0x100, + /** If not specified, the default value is assumed */ + FormatUseParameter = 0x200, + + // Floating-point flags + /** Use standard form instead of normal form */ + FormatStandardForm = 0x1000, + /** Use hexadecimal form instead of normal form */ + FormatHexadecimalForm = 0x2000, + /** Reserved */ + FormatForceDecimalPoint = 0x4000, + /** Trailing zeros and possibly the decimal point are trimmed */ + FormatCropZeros = 0x8000, + + // Floating-point and integer flags + /** Group digits (with floating-point, only works when in normal form) */ + FormatGroupDigits = 0x10000, + /** Always insert a prefix, '+' for positive and '-' for negative */ + FormatPrefixSign = 0x20000, + /** + * Pad left with zeros, taking into consideration the sign. Width must be specified. + * Format*Align cannot be used in conjunction with this flag. If FormatGroupDigits is specified, + * this flag is ignored. + */ + FormatPadZeros = 0x40000, + + // General flags + /** Applies left alignment. Width must be specified. */ + FormatLeftAlign = 0x80000000, + /** Applies right alignment. Width must be specified. */ + FormatRightAlign = 0x40000000, + /** Make characters uppercase (only available for some types) */ + FormatUpperCase = 0x20000000 +} PH_FORMAT_TYPE; + +/** Describes an element to be formatted to a string. */ +typedef struct _PH_FORMAT +{ + /** Specifies the type of the element and optional flags. */ + PH_FORMAT_TYPE Type; + /** + * The precision of the element. The meaning of this field depends on the element type. For + * \a Double and \a Size, this field specifies the number of decimal points to include. + */ + USHORT Precision; + /** + * The width of the element. This field specifies the minimum number of characters to output. + * The remaining space is padded with either spaces, zeros, or a custom character. + */ + USHORT Width; + /** The pad character. */ + WCHAR Pad; + /** + * The meaning of this field depends on the element type. For integer types, this field + * specifies the base to convert the number into. For \a Size, this field specifies the maximum + * size unit. + */ + UCHAR Radix; + /** + * The meaning of this field depends on the element type. For \a Size, this field specifies the + * minimum size unit. + */ + UCHAR Parameter; + union + { + WCHAR Char; + PH_STRINGREF String; + PWSTR StringZ; + PH_BYTESREF MultiByteString; + PSTR MultiByteStringZ; + LONG Int32; + LONG64 Int64; + LONG_PTR IntPtr; + ULONG UInt32; + ULONG64 UInt64; + ULONG_PTR UIntPtr; + DOUBLE Double; + + ULONG64 Size; + } u; +} PH_FORMAT, *PPH_FORMAT; + +// Convenience macros +#define PhInitFormatC(f, v) do { (f)->Type = CharFormatType; (f)->u.Char = (v); } while (0) +#define PhInitFormatS(f, v) do { (f)->Type = StringFormatType; PhInitializeStringRef(&(f)->u.String, (v)); } while (0) +#define PhInitFormatSR(f, v) do { (f)->Type = StringFormatType; (f)->u.String = (v); } while (0) +#define PhInitFormatMultiByteS(f, v) do { (f)->Type = MultiByteStringFormatType; PhInitializeBytesRef(&(f)->u.MultiByteString, (v)); } while (0) +#define PhInitFormatD(f, v) do { (f)->Type = Int32FormatType; (f)->u.Int32 = (v); } while (0) +#define PhInitFormatU(f, v) do { (f)->Type = UInt32FormatType; (f)->u.UInt32 = (v); } while (0) +#define PhInitFormatX(f, v) do { (f)->Type = UInt32FormatType | FormatUseRadix; (f)->u.UInt32 = (v); (f)->Radix = 16; } while (0) +#define PhInitFormatI64D(f, v) do { (f)->Type = Int64FormatType; (f)->u.Int64 = (v); } while (0) +#define PhInitFormatI64U(f, v) do { (f)->Type = UInt64FormatType; (f)->u.UInt64 = (v); } while (0) +#define PhInitFormatI64X(f, v) do { (f)->Type = UInt64FormatType | FormatUseRadix; (f)->u.UInt64 = (v); (f)->Radix = 16; } while (0) +#define PhInitFormatIU(f, v) do { (f)->Type = UIntPtrFormatType; (f)->u.UIntPtr = (v); } while (0) +#define PhInitFormatIX(f, v) do { (f)->Type = UIntPtrFormatType | FormatUseRadix; (f)->u.UIntPtr = (v); (f)->Radix = 16; } while (0) +#define PhInitFormatF(f, v, p) do { (f)->Type = DoubleFormatType | FormatUsePrecision; (f)->u.Double = (v); (f)->Precision = (p); } while (0) +#define PhInitFormatE(f, v, p) do { (f)->Type = DoubleFormatType | FormatStandardForm | FormatUsePrecision; (f)->u.Double = (v); (f)->Precision = (p); } while (0) +#define PhInitFormatA(f, v, p) do { (f)->Type = DoubleFormatType | FormatHexadecimalForm | FormatUsePrecision; (f)->u.Double = (v); (f)->Precision = (p); } while (0) +#define PhInitFormatSize(f, v) do { (f)->Type = SizeFormatType; (f)->u.Size = (v); } while (0) + +PHLIBAPI +PPH_STRING +NTAPI +PhFormat( + _In_reads_(Count) PPH_FORMAT Format, + _In_ ULONG Count, + _In_opt_ SIZE_T InitialCapacity + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhFormatToBuffer( + _In_reads_(Count) PPH_FORMAT Format, + _In_ ULONG Count, + _Out_writes_bytes_opt_(BufferLength) PWSTR Buffer, + _In_opt_ SIZE_T BufferLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +// error + +PHLIBAPI +ULONG +NTAPI +PhNtStatusToDosError( + _In_ NTSTATUS Status + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhDosErrorToNtStatus( + _In_ ULONG DosError + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhNtStatusFileNotFound( + _In_ NTSTATUS Status + ); + +// Generic tree definitions + +typedef enum _PH_TREE_ENUMERATION_ORDER +{ + TreeEnumerateInOrder, + TreeEnumerateInReverseOrder +} PH_TREE_ENUMERATION_ORDER; + +#define PhIsLeftChildElement(Links) ((Links)->Parent->Left == (Links)) +#define PhIsRightChildElement(Links) ((Links)->Parent->Right == (Links)) + +// avltree + +typedef struct _PH_AVL_LINKS +{ + struct _PH_AVL_LINKS *Parent; + struct _PH_AVL_LINKS *Left; + struct _PH_AVL_LINKS *Right; + LONG Balance; +} PH_AVL_LINKS, *PPH_AVL_LINKS; + +struct _PH_AVL_TREE; + +typedef LONG (NTAPI *PPH_AVL_TREE_COMPARE_FUNCTION)( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +typedef struct _PH_AVL_TREE +{ + PH_AVL_LINKS Root; // Right contains real root + ULONG Count; + + PPH_AVL_TREE_COMPARE_FUNCTION CompareFunction; +} PH_AVL_TREE, *PPH_AVL_TREE; + +#define PH_AVL_TREE_INIT(CompareFunction) { { NULL, NULL, NULL, 0 }, 0, CompareFunction } + +#define PhRootElementAvlTree(Tree) ((Tree)->Root.Right) + +PHLIBAPI +VOID +NTAPI +PhInitializeAvlTree( + _Out_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_TREE_COMPARE_FUNCTION CompareFunction + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhAddElementAvlTree( + _Inout_ PPH_AVL_TREE Tree, + _Out_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +VOID +NTAPI +PhRemoveElementAvlTree( + _Inout_ PPH_AVL_TREE Tree, + _Inout_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhFindElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhLowerBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhUpperBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhLowerDualBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhUpperDualBoundElementAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhMinimumElementAvlTree( + _In_ PPH_AVL_TREE Tree + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhMaximumElementAvlTree( + _In_ PPH_AVL_TREE Tree + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhSuccessorElementAvlTree( + _In_ PPH_AVL_LINKS Element + ); + +PHLIBAPI +PPH_AVL_LINKS +NTAPI +PhPredecessorElementAvlTree( + _In_ PPH_AVL_LINKS Element + ); + +typedef BOOLEAN (NTAPI *PPH_ENUM_AVL_TREE_CALLBACK)( + _In_ PPH_AVL_TREE Tree, + _In_ PPH_AVL_LINKS Element, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID +NTAPI +PhEnumAvlTree( + _In_ PPH_AVL_TREE Tree, + _In_ PH_TREE_ENUMERATION_ORDER Order, + _In_ PPH_ENUM_AVL_TREE_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/phconfig.h b/phlib/include/phconfig.h new file mode 100644 index 0000000..75892b8 --- /dev/null +++ b/phlib/include/phconfig.h @@ -0,0 +1,109 @@ +#ifndef _PH_PHCONFIG_H +#define _PH_PHCONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define _User_set_ + +PHLIBAPI extern _User_set_ PVOID PhLibImageBase; + +PHLIBAPI extern _User_set_ PWSTR PhApplicationName; +PHLIBAPI extern _User_set_ ULONG PhGlobalDpi; +PHLIBAPI extern PVOID PhHeapHandle; +PHLIBAPI extern RTL_OSVERSIONINFOEXW PhOsVersion; +PHLIBAPI extern SYSTEM_BASIC_INFORMATION PhSystemBasicInformation; +PHLIBAPI extern ULONG WindowsVersion; + +PHLIBAPI extern ACCESS_MASK ProcessQueryAccess; +PHLIBAPI extern ACCESS_MASK ProcessAllAccess; +PHLIBAPI extern ACCESS_MASK ThreadQueryAccess; +PHLIBAPI extern ACCESS_MASK ThreadSetAccess; +PHLIBAPI extern ACCESS_MASK ThreadAllAccess; + +#define WINDOWS_ANCIENT 0 +#define WINDOWS_XP 51 +#define WINDOWS_SERVER_2003 52 +#define WINDOWS_VISTA 60 +#define WINDOWS_7 61 +#define WINDOWS_8 62 +#define WINDOWS_8_1 63 +#define WINDOWS_10 100 +#define WINDOWS_NEW MAXLONG + +#define WINDOWS_HAS_CONSOLE_HOST (WindowsVersion >= WINDOWS_7) +#define WINDOWS_HAS_CYCLE_TIME (WindowsVersion >= WINDOWS_VISTA) +#define WINDOWS_HAS_IFILEDIALOG (WindowsVersion >= WINDOWS_VISTA) +#define WINDOWS_HAS_IMAGE_FILE_NAME_BY_PROCESS_ID (WindowsVersion >= WINDOWS_VISTA) +#define WINDOWS_HAS_IMMERSIVE (WindowsVersion >= WINDOWS_8) +#define WINDOWS_HAS_LIMITED_ACCESS (WindowsVersion >= WINDOWS_VISTA) +#define WINDOWS_HAS_SERVICE_TAGS (WindowsVersion >= WINDOWS_VISTA) +#define WINDOWS_HAS_UAC (WindowsVersion >= WINDOWS_VISTA) + +// Debugging + +#ifdef DEBUG +#define dprintf(format, ...) DbgPrint(format, __VA_ARGS__) +#else +#define dprintf(format, ...) +#endif + +// global + +// Initialization flags + +// Features + +// Imports + +#define PHLIB_INIT_MODULE_RESERVED1 0x1 +#define PHLIB_INIT_MODULE_RESERVED2 0x2 +/** Needed to use work queues. */ +#define PHLIB_INIT_MODULE_RESERVED3 0x4 +#define PHLIB_INIT_MODULE_RESERVED4 0x8 +/** Needed to use file streams. */ +#define PHLIB_INIT_MODULE_FILE_STREAM 0x10 +/** Needed to use symbol providers. */ +#define PHLIB_INIT_MODULE_SYMBOL_PROVIDER 0x20 +#define PHLIB_INIT_MODULE_RESERVED5 0x40 + +PHLIBAPI +NTSTATUS +NTAPI +PhInitializePhLib( + VOID + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhInitializePhLibEx( + _In_ ULONG Flags, + _In_opt_ SIZE_T HeapReserveSize, + _In_opt_ SIZE_T HeapCommitSize + ); + +#ifdef _WIN64 +FORCEINLINE +BOOLEAN +PhIsExecutingInWow64( + VOID + ) +{ + return FALSE; +} +#else +PHLIBAPI +BOOLEAN +NTAPI +PhIsExecutingInWow64( + VOID + ); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/phdata.h b/phlib/include/phdata.h new file mode 100644 index 0000000..f3b48d7 --- /dev/null +++ b/phlib/include/phdata.h @@ -0,0 +1,60 @@ +#ifndef _PH_PHDATA_H +#define _PH_PHDATA_H + +#ifdef __cplusplus +extern "C" { +#endif + +// SIDs + +extern SID PhSeNobodySid; + +extern SID PhSeEveryoneSid; + +extern SID PhSeLocalSid; + +extern SID PhSeCreatorOwnerSid; +extern SID PhSeCreatorGroupSid; + +extern SID PhSeDialupSid; +extern SID PhSeNetworkSid; +extern SID PhSeBatchSid; +extern SID PhSeInteractiveSid; +extern SID PhSeServiceSid; +extern SID PhSeAnonymousLogonSid; +extern SID PhSeProxySid; +extern SID PhSeAuthenticatedUserSid; +extern SID PhSeRestrictedCodeSid; +extern SID PhSeTerminalServerUserSid; +extern SID PhSeRemoteInteractiveLogonSid; +extern SID PhSeLocalSystemSid; +extern SID PhSeLocalServiceSid; +extern SID PhSeNetworkServiceSid; + +// Unicode + +extern PH_STRINGREF PhUnicodeByteOrderMark; + +// Characters + +extern BOOLEAN PhCharIsPrintable[256]; +extern ULONG PhCharToInteger[256]; +extern CHAR PhIntegerToChar[69]; +extern CHAR PhIntegerToCharUpper[69]; + +// CRC32 + +extern ULONG PhCrc32Table[256]; + +// Enums + +extern WCHAR *PhIoPriorityHintNames[MaxIoPriorityTypes]; +extern WCHAR *PhPagePriorityNames[MEMORY_PRIORITY_NORMAL + 1]; +extern WCHAR *PhKThreadStateNames[MaximumThreadState]; +extern WCHAR *PhKWaitReasonNames[MaximumWaitReason]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/phintrnl.h b/phlib/include/phintrnl.h new file mode 100644 index 0000000..2fe39cf --- /dev/null +++ b/phlib/include/phintrnl.h @@ -0,0 +1,49 @@ +#ifndef _PH_PHINTRNL_H +#define _PH_PHINTRNL_H + +typedef struct _PHLIB_STATISTICS_BLOCK +{ + // basesup + ULONG BaseThreadsCreated; + ULONG BaseThreadsCreateFailed; + ULONG BaseStringBuildersCreated; + ULONG BaseStringBuildersResized; + + // ref + ULONG RefObjectsCreated; + ULONG RefObjectsDestroyed; + ULONG RefObjectsAllocated; + ULONG RefObjectsFreed; + ULONG RefObjectsAllocatedFromSmallFreeList; + ULONG RefObjectsFreedToSmallFreeList; + ULONG RefObjectsAllocatedFromTypeFreeList; + ULONG RefObjectsFreedToTypeFreeList; + ULONG RefObjectsDeleteDeferred; + ULONG RefAutoPoolsCreated; + ULONG RefAutoPoolsDestroyed; + ULONG RefAutoPoolsDynamicAllocated; + ULONG RefAutoPoolsDynamicResized; + + // queuedlock + ULONG QlBlockSpins; + ULONG QlBlockWaits; + ULONG QlAcquireExclusiveBlocks; + ULONG QlAcquireSharedBlocks; + + // workqueue + ULONG WqWorkQueueThreadsCreated; + ULONG WqWorkQueueThreadsCreateFailed; + ULONG WqWorkItemsQueued; +} PHLIB_STATISTICS_BLOCK; + +#ifdef DEBUG +extern PHLIB_STATISTICS_BLOCK PhLibStatisticsBlock; +#endif + +#ifdef DEBUG +#define PHLIB_INC_STATISTIC(Name) (_InterlockedIncrement(&PhLibStatisticsBlock.Name)) +#else +#define PHLIB_INC_STATISTIC(Name) +#endif + +#endif diff --git a/phlib/include/phnative.h b/phlib/include/phnative.h new file mode 100644 index 0000000..6d1ff9b --- /dev/null +++ b/phlib/include/phnative.h @@ -0,0 +1,1042 @@ +#ifndef _PH_PHNATIVE_H +#define _PH_PHNATIVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** The PID of the idle process. */ +#define SYSTEM_IDLE_PROCESS_ID ((HANDLE)0) +/** The PID of the system process. */ +#define SYSTEM_PROCESS_ID ((HANDLE)4) + +#define SYSTEM_IDLE_PROCESS_NAME (L"System Idle Process") + +// General object-related function types + +typedef NTSTATUS (NTAPI *PPH_OPEN_OBJECT)( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PVOID Context + ); + +typedef NTSTATUS (NTAPI *PPH_GET_OBJECT_SECURITY)( + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ); + +typedef NTSTATUS (NTAPI *PPH_SET_OBJECT_SECURITY)( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ); + +typedef struct _PH_TOKEN_ATTRIBUTES +{ + HANDLE TokenHandle; + struct + { + ULONG Elevated : 1; + ULONG ElevationType : 2; + ULONG ReservedBits : 29; + }; + ULONG Reserved; +} PH_TOKEN_ATTRIBUTES, *PPH_TOKEN_ATTRIBUTES; + +PHLIBAPI +PH_TOKEN_ATTRIBUTES +NTAPI +PhGetOwnTokenAttributes( + VOID + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenProcessPublic( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ThreadId + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenThreadPublic( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ThreadId + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenThreadProcess( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE ProcessHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetObjectSecurity( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetObjectSecurity( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhTerminateProcess( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhTerminateProcessPublic( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessImageFileName( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessImageFileNameWin32( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *FileName + ); + +/** Specifies a PEB string. */ +typedef enum _PH_PEB_OFFSET +{ + PhpoCurrentDirectory, + PhpoDllPath, + PhpoImagePathName, + PhpoCommandLine, + PhpoWindowTitle, + PhpoDesktopInfo, + PhpoShellInfo, + PhpoRuntimeData, + PhpoTypeMask = 0xffff, + + PhpoWow64 = 0x10000 +} PH_PEB_OFFSET; + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessPebString( + _In_ HANDLE ProcessHandle, + _In_ PH_PEB_OFFSET Offset, + _Out_ PPH_STRING *String + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessCommandLine( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *CommandLine + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessWindowTitle( + _In_ HANDLE ProcessHandle, + _Out_ PULONG WindowFlags, + _Out_ PPH_STRING *WindowTitle + ); + +#define PH_PROCESS_DEP_ENABLED 0x1 +#define PH_PROCESS_DEP_ATL_THUNK_EMULATION_DISABLED 0x2 +#define PH_PROCESS_DEP_PERMANENT 0x4 + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessDepStatus( + _In_ HANDLE ProcessHandle, + _Out_ PULONG DepStatus + ); + +#define PH_GET_PROCESS_ENVIRONMENT_WOW64 0x1 // retrieve the WOW64 environment + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessEnvironment( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _Out_ PVOID *Environment, + _Out_ PULONG EnvironmentLength + ); + +typedef struct _PH_ENVIRONMENT_VARIABLE +{ + PH_STRINGREF Name; + PH_STRINGREF Value; +} PH_ENVIRONMENT_VARIABLE, *PPH_ENVIRONMENT_VARIABLE; + +PHLIBAPI +BOOLEAN +NTAPI +PhEnumProcessEnvironmentVariables( + _In_ PVOID Environment, + _In_ ULONG EnvironmentLength, + _Inout_ PULONG EnumerationKey, + _Out_ PPH_ENVIRONMENT_VARIABLE Variable + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessMappedFileName( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _Out_ PPH_STRING *FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessWorkingSetInformation( + _In_ HANDLE ProcessHandle, + _Out_ PMEMORY_WORKING_SET_INFORMATION *WorkingSetInformation + ); + +typedef struct _PH_PROCESS_WS_COUNTERS +{ + SIZE_T NumberOfPages; + SIZE_T NumberOfPrivatePages; + SIZE_T NumberOfSharedPages; + SIZE_T NumberOfShareablePages; +} PH_PROCESS_WS_COUNTERS, *PPH_PROCESS_WS_COUNTERS; + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessWsCounters( + _In_ HANDLE ProcessHandle, + _Out_ PPH_PROCESS_WS_COUNTERS WsCounters + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhInjectDllProcess( + _In_ HANDLE ProcessHandle, + _In_ PWSTR FileName, + _In_opt_ PLARGE_INTEGER Timeout + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhUnloadDllProcess( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_opt_ PLARGE_INTEGER Timeout + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetEnvironmentVariableRemote( + _In_ HANDLE ProcessHandle, + _In_ PPH_STRINGREF Name, + _In_opt_ PPH_STRINGREF Value, + _In_opt_ PLARGE_INTEGER Timeout + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetJobProcessIdList( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_BASIC_PROCESS_ID_LIST *ProcessIdList + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhQueryTokenVariableSize( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _Out_ PVOID *Buffer + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenUser( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_USER *User + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenOwner( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_OWNER *Owner + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenPrimaryGroup( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_PRIMARY_GROUP *PrimaryGroup + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenGroups( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_GROUPS *Groups + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenPrivileges( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_PRIVILEGES *Privileges + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetTokenSessionId( + _In_ HANDLE TokenHandle, + _In_ ULONG SessionId + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSetTokenPrivilege( + _In_ HANDLE TokenHandle, + _In_opt_ PWSTR PrivilegeName, + _In_opt_ PLUID PrivilegeLuid, + _In_ ULONG Attributes + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSetTokenPrivilege2( + _In_ HANDLE TokenHandle, + _In_ LONG Privilege, + _In_ ULONG Attributes + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetTokenIsVirtualizationEnabled( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN IsVirtualizationEnabled + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTokenIntegrityLevel( + _In_ HANDLE TokenHandle, + _Out_opt_ PMANDATORY_LEVEL IntegrityLevel, + _Out_opt_ PWSTR *IntegrityString + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetFileSize( + _In_ HANDLE FileHandle, + _Out_ PLARGE_INTEGER Size + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetFileSize( + _In_ HANDLE FileHandle, + _In_ PLARGE_INTEGER Size + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTransactionManagerBasicInformation( + _In_ HANDLE TransactionManagerHandle, + _Out_ PTRANSACTIONMANAGER_BASIC_INFORMATION BasicInformation + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTransactionManagerLogFileName( + _In_ HANDLE TransactionManagerHandle, + _Out_ PPH_STRING *LogFileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTransactionBasicInformation( + _In_ HANDLE TransactionHandle, + _Out_ PTRANSACTION_BASIC_INFORMATION BasicInformation + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetTransactionPropertiesInformation( + _In_ HANDLE TransactionHandle, + _Out_opt_ PLARGE_INTEGER Timeout, + _Out_opt_ TRANSACTION_OUTCOME *Outcome, + _Out_opt_ PPH_STRING *Description + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetResourceManagerBasicInformation( + _In_ HANDLE ResourceManagerHandle, + _Out_opt_ PGUID Guid, + _Out_opt_ PPH_STRING *Description + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetEnlistmentBasicInformation( + _In_ HANDLE EnlistmentHandle, + _Out_ PENLISTMENT_BASIC_INFORMATION BasicInformation + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenDriverByBaseAddress( + _Out_ PHANDLE DriverHandle, + _In_ PVOID BaseAddress + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetDriverName( + _In_ HANDLE DriverHandle, + _Out_ PPH_STRING *Name + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetDriverServiceKeyName( + _In_ HANDLE DriverHandle, + _Out_ PPH_STRING *ServiceKeyName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhUnloadDriver( + _In_opt_ PVOID BaseAddress, + _In_opt_ PWSTR Name + ); + +#define PH_ENUM_PROCESS_MODULES_LIMIT 0x800 + +/** + * A callback function passed to PhEnumProcessModules() and called for each process module. + * + * \param Module A structure providing information about the module. + * \param Context A user-defined value passed to PhEnumProcessModules(). + * + * \return TRUE to continue the enumeration, FALSE to stop. + */ +typedef BOOLEAN (NTAPI *PPH_ENUM_PROCESS_MODULES_CALLBACK)( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ); + +#define PH_ENUM_PROCESS_MODULES_DONT_RESOLVE_WOW64_FS 0x1 +#define PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME 0x2 + +typedef struct _PH_ENUM_PROCESS_MODULES_PARAMETERS +{ + PPH_ENUM_PROCESS_MODULES_CALLBACK Callback; + PVOID Context; + ULONG Flags; +} PH_ENUM_PROCESS_MODULES_PARAMETERS, *PPH_ENUM_PROCESS_MODULES_PARAMETERS; + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessModules( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessModulesEx( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetProcessModuleLoadCount( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ ULONG LoadCount + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessModules32( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessModules32Ex( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetProcessModuleLoadCount32( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ ULONG LoadCount + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcedureAddressRemote( + _In_ HANDLE ProcessHandle, + _In_ PWSTR FileName, + _In_opt_ PSTR ProcedureName, + _In_opt_ ULONG ProcedureNumber, + _Out_ PVOID *ProcedureAddress, + _Out_opt_ PVOID *DllBase + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumKernelModules( + _Out_ PRTL_PROCESS_MODULES *Modules + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumKernelModulesEx( + _Out_ PRTL_PROCESS_MODULE_INFORMATION_EX *Modules + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetKernelFileName( + VOID + ); + +/** + * Gets a pointer to the first process information structure in a buffer returned by + * PhEnumProcesses(). + * + * \param Processes A pointer to a buffer returned by PhEnumProcesses(). + */ +#define PH_FIRST_PROCESS(Processes) ((PSYSTEM_PROCESS_INFORMATION)(Processes)) + +/** + * Gets a pointer to the process information structure after a given structure. + * + * \param Process A pointer to a process information structure. + * + * \return A pointer to the next process information structure, or NULL if there are no more. + */ +#define PH_NEXT_PROCESS(Process) ( \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ + (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : \ + NULL \ + ) + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcesses( + _Out_ PVOID *Processes + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessesEx( + _Out_ PVOID *Processes, + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumProcessesForSession( + _Out_ PVOID *Processes, + _In_ ULONG SessionId + ); + +PHLIBAPI +PSYSTEM_PROCESS_INFORMATION +NTAPI +PhFindProcessInformation( + _In_ PVOID Processes, + _In_ HANDLE ProcessId + ); + +PHLIBAPI +PSYSTEM_PROCESS_INFORMATION +NTAPI +PhFindProcessInformationByImageName( + _In_ PVOID Processes, + _In_ PPH_STRINGREF ImageName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumHandles( + _Out_ PSYSTEM_HANDLE_INFORMATION *Handles + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumHandlesEx( + _Out_ PSYSTEM_HANDLE_INFORMATION_EX *Handles + ); + +#define PH_FIRST_PAGEFILE(Pagefiles) ( \ + /* The size of a pagefile can never be 0. A TotalSize of 0 + * is used to indicate that there are no pagefiles. + */ ((PSYSTEM_PAGEFILE_INFORMATION)(Pagefiles))->TotalSize ? \ + (PSYSTEM_PAGEFILE_INFORMATION)(Pagefiles) : \ + NULL \ + ) +#define PH_NEXT_PAGEFILE(Pagefile) ( \ + ((PSYSTEM_PAGEFILE_INFORMATION)(Pagefile))->NextEntryOffset ? \ + (PSYSTEM_PAGEFILE_INFORMATION)((PCHAR)(Pagefile) + \ + ((PSYSTEM_PAGEFILE_INFORMATION)(Pagefile))->NextEntryOffset) : \ + NULL \ + ) + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumPagefiles( + _Out_ PVOID *Pagefiles + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessImageFileNameByProcessId( + _In_ HANDLE ProcessId, + _Out_ PPH_STRING *FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessIsDotNet( + _In_ HANDLE ProcessId, + _Out_ PBOOLEAN IsDotNet + ); + +#define PH_CLR_USE_SECTION_CHECK 0x1 +#define PH_CLR_NO_WOW64_CHECK 0x2 +#define PH_CLR_KNOWN_IS_WOW64 0x4 + +#define PH_CLR_VERSION_1_0 0x1 +#define PH_CLR_VERSION_1_1 0x2 +#define PH_CLR_VERSION_2_0 0x4 +#define PH_CLR_VERSION_4_ABOVE 0x8 +#define PH_CLR_VERSION_MASK 0xf +#define PH_CLR_MSCORLIB_PRESENT 0x10000 +#define PH_CLR_JIT_PRESENT 0x20000 +#define PH_CLR_PROCESS_IS_WOW64 0x100000 + +PHLIBAPI +NTSTATUS +NTAPI +PhGetProcessIsDotNetEx( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle, + _In_ ULONG InFlags, + _Out_opt_ PBOOLEAN IsDotNet, + _Out_opt_ PULONG Flags + ); + +/** + * A callback function passed to PhEnumDirectoryObjects() and called for each directory object. + * + * \param Name The name of the object. + * \param TypeName The name of the object's type. + * \param Context A user-defined value passed to PhEnumDirectoryObjects(). + * + * \return TRUE to continue the enumeration, FALSE to stop. + */ +typedef BOOLEAN (NTAPI *PPH_ENUM_DIRECTORY_OBJECTS)( + _In_ PPH_STRINGREF Name, + _In_ PPH_STRINGREF TypeName, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumDirectoryObjects( + _In_ HANDLE DirectoryHandle, + _In_ PPH_ENUM_DIRECTORY_OBJECTS Callback, + _In_opt_ PVOID Context + ); + +typedef BOOLEAN (NTAPI *PPH_ENUM_DIRECTORY_FILE)( + _In_ PFILE_DIRECTORY_INFORMATION Information, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ PUNICODE_STRING SearchPattern, + _In_ PPH_ENUM_DIRECTORY_FILE Callback, + _In_opt_ PVOID Context + ); + +#define PH_FIRST_STREAM(Streams) ((PFILE_STREAM_INFORMATION)(Streams)) +#define PH_NEXT_STREAM(Stream) ( \ + ((PFILE_STREAM_INFORMATION)(Stream))->NextEntryOffset ? \ + (PFILE_STREAM_INFORMATION)((PCHAR)(Stream) + \ + ((PFILE_STREAM_INFORMATION)(Stream))->NextEntryOffset) : \ + NULL \ + ) + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumFileStreams( + _In_ HANDLE FileHandle, + _Out_ PVOID *Streams + ); + +PHLIBAPI +VOID +NTAPI +PhUpdateMupDevicePrefixes( + VOID + ); + +PHLIBAPI +VOID +NTAPI +PhUpdateDosDevicePrefixes( + VOID + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhResolveDevicePrefix( + _In_ PPH_STRING Name + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetFileName( + _In_ PPH_STRING FileName + ); + +#define PH_MODULE_TYPE_MODULE 1 +#define PH_MODULE_TYPE_MAPPED_FILE 2 +#define PH_MODULE_TYPE_WOW64_MODULE 3 +#define PH_MODULE_TYPE_KERNEL_MODULE 4 +#define PH_MODULE_TYPE_MAPPED_IMAGE 5 + +typedef struct _PH_MODULE_INFO +{ + ULONG Type; + PVOID BaseAddress; + ULONG Size; + PVOID EntryPoint; + ULONG Flags; + PPH_STRING Name; + PPH_STRING FileName; + + USHORT LoadOrderIndex; // -1 if N/A + USHORT LoadCount; // -1 if N/A + USHORT LoadReason; // -1 if N/A + USHORT Reserved; + LARGE_INTEGER LoadTime; // 0 if N/A +} PH_MODULE_INFO, *PPH_MODULE_INFO; + +/** + * A callback function passed to PhEnumGenericModules() and called for each process module. + * + * \param Module A structure providing information about the module. + * \param Context A user-defined value passed to PhEnumGenericModules(). + * + * \return TRUE to continue the enumeration, FALSE to stop. + */ +typedef BOOLEAN (NTAPI *PPH_ENUM_GENERIC_MODULES_CALLBACK)( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ); + +#define PH_ENUM_GENERIC_MAPPED_FILES 0x1 +#define PH_ENUM_GENERIC_MAPPED_IMAGES 0x2 + +PHLIBAPI +NTSTATUS +NTAPI +PhEnumGenericModules( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +#define PH_KEY_PREDEFINE(Number) ((HANDLE)(LONG_PTR)(-3 - (Number) * 2)) +#define PH_KEY_IS_PREDEFINED(Predefine) (((LONG_PTR)(Predefine) < 0) && ((LONG_PTR)(Predefine) & 0x1)) +#define PH_KEY_PREDEFINE_TO_NUMBER(Predefine) (ULONG)(((-(LONG_PTR)(Predefine) - 3) >> 1)) + +#define PH_KEY_LOCAL_MACHINE PH_KEY_PREDEFINE(0) // \Registry\Machine +#define PH_KEY_USERS PH_KEY_PREDEFINE(1) // \Registry\User +#define PH_KEY_CLASSES_ROOT PH_KEY_PREDEFINE(2) // \Registry\Machine\Software\Classes +#define PH_KEY_CURRENT_USER PH_KEY_PREDEFINE(3) // \Registry\User\ +#define PH_KEY_CURRENT_USER_NUMBER 3 +#define PH_KEY_MAXIMUM_PREDEFINE 4 + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ HANDLE RootDirectory, + _In_ PPH_STRINGREF ObjectName, + _In_ ULONG Attributes, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG Disposition + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhOpenKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ HANDLE RootDirectory, + _In_ PPH_STRINGREF ObjectName, + _In_ ULONG Attributes + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhQueryKey( + _In_ HANDLE KeyHandle, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_ PVOID *Buffer + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhQueryValueKey( + _In_ HANDLE KeyHandle, + _In_opt_ PPH_STRINGREF ValueName, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_ PVOID *Buffer + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateFileWin32( + _Out_ PHANDLE FileHandle, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateFileWin32Ex( + _Out_ PHANDLE FileHandle, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG CreateStatus + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhQueryFullAttributesFileWin32( + _In_ PWSTR FileName, + _Out_ PFILE_NETWORK_OPEN_INFORMATION FileInformation + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhDeleteFileWin32( + _In_ PWSTR FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhListenNamedPipe( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhDisconnectNamedPipe( + _In_ HANDLE FileHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhPeekNamedPipe( + _In_ HANDLE FileHandle, + _Out_writes_bytes_opt_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG NumberOfBytesRead, + _Out_opt_ PULONG NumberOfBytesAvailable, + _Out_opt_ PULONG NumberOfBytesLeftInMessage + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhTransceiveNamedPipe( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWaitForNamedPipe( + _In_opt_ PUNICODE_STRING FileSystemName, + _In_ PUNICODE_STRING Name, + _In_opt_ PLARGE_INTEGER Timeout, + _In_ BOOLEAN UseDefaultTimeout + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhImpersonateClientOfNamedPipe( + _In_ HANDLE FileHandle + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/phnativeinl.h b/phlib/include/phnativeinl.h new file mode 100644 index 0000000..d2954d8 --- /dev/null +++ b/phlib/include/phnativeinl.h @@ -0,0 +1,968 @@ +#ifndef _PH_PHNATINL_H +#define _PH_PHNATINL_H + +#pragma once + +// This file contains inlined native API wrapper functions. These functions were previously +// exported, but are now inlined because they are extremely simple wrappers around equivalent native +// API functions. + +/** + * Gets basic information for a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param BasicInformation A variable which receives the information. + */ +FORCEINLINE +NTSTATUS +PhGetProcessBasicInformation( + _In_ HANDLE ProcessHandle, + _Out_ PPROCESS_BASIC_INFORMATION BasicInformation + ) +{ + return NtQueryInformationProcess( + ProcessHandle, + ProcessBasicInformation, + BasicInformation, + sizeof(PROCESS_BASIC_INFORMATION), + NULL + ); +} + +/** + * Gets extended basic information for a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param ExtendedBasicInformation A variable which receives the information. + */ +FORCEINLINE +NTSTATUS +PhGetProcessExtendedBasicInformation( + _In_ HANDLE ProcessHandle, + _Out_ PPROCESS_EXTENDED_BASIC_INFORMATION ExtendedBasicInformation + ) +{ + ExtendedBasicInformation->Size = sizeof(PROCESS_EXTENDED_BASIC_INFORMATION); + + return NtQueryInformationProcess( + ProcessHandle, + ProcessBasicInformation, + ExtendedBasicInformation, + sizeof(PROCESS_EXTENDED_BASIC_INFORMATION), + NULL + ); +} + +/** + * Gets time information for a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param Times A variable which receives the information. + */ +FORCEINLINE +NTSTATUS +PhGetProcessTimes( + _In_ HANDLE ProcessHandle, + _Out_ PKERNEL_USER_TIMES Times + ) +{ + return NtQueryInformationProcess( + ProcessHandle, + ProcessTimes, + Times, + sizeof(KERNEL_USER_TIMES), + NULL + ); +} + +/** + * Gets a process' session ID. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param SessionId A variable which receives the process' session ID. + */ +FORCEINLINE +NTSTATUS +PhGetProcessSessionId( + _In_ HANDLE ProcessHandle, + _Out_ PULONG SessionId + ) +{ + NTSTATUS status; + PROCESS_SESSION_INFORMATION sessionInfo; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessSessionInformation, + &sessionInfo, + sizeof(PROCESS_SESSION_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + *SessionId = sessionInfo.SessionId; + } + + return status; +} + +/** + * Gets whether a process is running under 32-bit emulation. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param IsWow64 A variable which receives a boolean indicating whether the process is 32-bit. + */ +FORCEINLINE +NTSTATUS +PhGetProcessIsWow64( + _In_ HANDLE ProcessHandle, + _Out_ PBOOLEAN IsWow64 + ) +{ + NTSTATUS status; + ULONG_PTR wow64; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessWow64Information, + &wow64, + sizeof(ULONG_PTR), + NULL + ); + + if (NT_SUCCESS(status)) + { + *IsWow64 = !!wow64; + } + + return status; +} + +/** + * Gets a process' WOW64 PEB address. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param Peb32 A variable which receives the base address of the process' WOW64 PEB. If the process + * is 64-bit, the variable receives NULL. + */ +FORCEINLINE +NTSTATUS +PhGetProcessPeb32( + _In_ HANDLE ProcessHandle, + _Out_ PVOID *Peb32 + ) +{ + NTSTATUS status; + ULONG_PTR wow64; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessWow64Information, + &wow64, + sizeof(ULONG_PTR), + NULL + ); + + if (NT_SUCCESS(status)) + { + *Peb32 = (PVOID)wow64; + } + + return status; +} + +/** + * Gets whether a process is being debugged. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param IsBeingDebugged A variable which receives a boolean indicating whether the process is + * being debugged. + */ +FORCEINLINE +NTSTATUS +PhGetProcessIsBeingDebugged( + _In_ HANDLE ProcessHandle, + _Out_ PBOOLEAN IsBeingDebugged + ) +{ + NTSTATUS status; + PVOID debugPort; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessDebugPort, + &debugPort, + sizeof(PVOID), + NULL + ); + + if (NT_SUCCESS(status)) + { + *IsBeingDebugged = !!debugPort; + } + + return status; +} + +/** + * Gets a handle to a process' debug object. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param DebugObjectHandle A variable which receives a handle to the debug object associated with + * the process. You must close the handle when you no longer need it. + * + * \retval STATUS_PORT_NOT_SET The process is not being debugged and has no associated debug object. + */ +FORCEINLINE +NTSTATUS +PhGetProcessDebugObject( + _In_ HANDLE ProcessHandle, + _Out_ PHANDLE DebugObjectHandle + ) +{ + return NtQueryInformationProcess( + ProcessHandle, + ProcessDebugObjectHandle, + DebugObjectHandle, + sizeof(HANDLE), + NULL + ); +} + +/** + * Gets a process' no-execute status. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param ExecuteFlags A variable which receives the no-execute flags. + */ +FORCEINLINE +NTSTATUS +PhGetProcessExecuteFlags( + _In_ HANDLE ProcessHandle, + _Out_ PULONG ExecuteFlags + ) +{ + return NtQueryInformationProcess( + ProcessHandle, + ProcessExecuteFlags, + ExecuteFlags, + sizeof(ULONG), + NULL + ); +} + +/** + * Gets a process' I/O priority. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param IoPriority A variable which receives the I/O priority of the process. + */ +FORCEINLINE +NTSTATUS +PhGetProcessIoPriority( + _In_ HANDLE ProcessHandle, + _Out_ IO_PRIORITY_HINT *IoPriority + ) +{ + return NtQueryInformationProcess( + ProcessHandle, + ProcessIoPriority, + IoPriority, + sizeof(IO_PRIORITY_HINT), + NULL + ); +} + +/** + * Gets a process' page priority. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param PagePriority A variable which receives the page priority of the process. + */ +FORCEINLINE +NTSTATUS +PhGetProcessPagePriority( + _In_ HANDLE ProcessHandle, + _Out_ PULONG PagePriority + ) +{ + NTSTATUS status; + PAGE_PRIORITY_INFORMATION pagePriorityInfo; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessPagePriority, + &pagePriorityInfo, + sizeof(PAGE_PRIORITY_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + *PagePriority = pagePriorityInfo.PagePriority; + } + + return status; +} + +/** + * Gets a process' cycle count. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param CycleTime A variable which receives the 64-bit cycle time. + */ +FORCEINLINE +NTSTATUS +PhGetProcessCycleTime( + _In_ HANDLE ProcessHandle, + _Out_ PULONG64 CycleTime + ) +{ + NTSTATUS status; + PROCESS_CYCLE_TIME_INFORMATION cycleTimeInfo; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessCycleTime, + &cycleTimeInfo, + sizeof(PROCESS_CYCLE_TIME_INFORMATION), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + *CycleTime = cycleTimeInfo.AccumulatedCycles; + + return status; +} + +FORCEINLINE +NTSTATUS +PhGetProcessConsoleHostProcessId( + _In_ HANDLE ProcessHandle, + _Out_ PHANDLE ConsoleHostProcessId + ) +{ + NTSTATUS status; + ULONG_PTR consoleHostProcess; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessConsoleHostProcess, + &consoleHostProcess, + sizeof(ULONG_PTR), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + *ConsoleHostProcessId = (HANDLE)consoleHostProcess; + + return status; +} + +/** + * Sets a process' affinity mask. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_SET_INFORMATION access. + * \param AffinityMask The new affinity mask. + */ +FORCEINLINE +NTSTATUS +PhSetProcessAffinityMask( + _In_ HANDLE ProcessHandle, + _In_ ULONG_PTR AffinityMask + ) +{ + return NtSetInformationProcess( + ProcessHandle, + ProcessAffinityMask, + &AffinityMask, + sizeof(ULONG_PTR) + ); +} + +/** + * Sets a process' I/O priority. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_SET_INFORMATION access. + * \param IoPriority The new I/O priority. + */ +FORCEINLINE +NTSTATUS +PhSetProcessIoPriority( + _In_ HANDLE ProcessHandle, + _In_ IO_PRIORITY_HINT IoPriority + ) +{ + return NtSetInformationProcess( + ProcessHandle, + ProcessIoPriority, + &IoPriority, + sizeof(IO_PRIORITY_HINT) + ); +} + +/** + * Gets basic information for a thread. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION + * access. + * \param BasicInformation A variable which receives the information. + */ +FORCEINLINE +NTSTATUS +PhGetThreadBasicInformation( + _In_ HANDLE ThreadHandle, + _Out_ PTHREAD_BASIC_INFORMATION BasicInformation + ) +{ + return NtQueryInformationThread( + ThreadHandle, + ThreadBasicInformation, + BasicInformation, + sizeof(THREAD_BASIC_INFORMATION), + NULL + ); +} + +/** + * Gets a thread's I/O priority. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION + * access. + * \param IoPriority A variable which receives the I/O priority of the thread. + */ +FORCEINLINE +NTSTATUS +PhGetThreadIoPriority( + _In_ HANDLE ThreadHandle, + _Out_ IO_PRIORITY_HINT *IoPriority + ) +{ + return NtQueryInformationThread( + ThreadHandle, + ThreadIoPriority, + IoPriority, + sizeof(IO_PRIORITY_HINT), + NULL + ); +} + +/** + * Gets a thread's page priority. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION + * access. + * \param PagePriority A variable which receives the page priority of the thread. + */ +FORCEINLINE +NTSTATUS +PhGetThreadPagePriority( + _In_ HANDLE ThreadHandle, + _Out_ PULONG PagePriority + ) +{ + NTSTATUS status; + PAGE_PRIORITY_INFORMATION pagePriorityInfo; + + status = NtQueryInformationThread( + ThreadHandle, + ThreadPagePriority, + &pagePriorityInfo, + sizeof(PAGE_PRIORITY_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + *PagePriority = pagePriorityInfo.PagePriority; + } + + return status; +} + +/** + * Gets a thread's cycle count. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION + * access. + * \param CycleTime A variable which receives the 64-bit cycle time. + */ +FORCEINLINE +NTSTATUS +PhGetThreadCycleTime( + _In_ HANDLE ThreadHandle, + _Out_ PULONG64 CycleTime + ) +{ + NTSTATUS status; + THREAD_CYCLE_TIME_INFORMATION cycleTimeInfo; + + status = NtQueryInformationThread( + ThreadHandle, + ThreadCycleTime, + &cycleTimeInfo, + sizeof(THREAD_CYCLE_TIME_INFORMATION), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + *CycleTime = cycleTimeInfo.AccumulatedCycles; + + return status; +} + +/** + * Sets a thread's affinity mask. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_SET_LIMITED_INFORMATION + * access. + * \param AffinityMask The new affinity mask. + */ +FORCEINLINE +NTSTATUS +PhSetThreadAffinityMask( + _In_ HANDLE ThreadHandle, + _In_ ULONG_PTR AffinityMask + ) +{ + return NtSetInformationThread( + ThreadHandle, + ThreadAffinityMask, + &AffinityMask, + sizeof(ULONG_PTR) + ); +} + +/** + * Sets a thread's I/O priority. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_SET_LIMITED_INFORMATION + * access. + * \param IoPriority The new I/O priority. + */ +FORCEINLINE +NTSTATUS +PhSetThreadIoPriority( + _In_ HANDLE ThreadHandle, + _In_ IO_PRIORITY_HINT IoPriority + ) +{ + return NtSetInformationThread( + ThreadHandle, + ThreadIoPriority, + &IoPriority, + sizeof(IO_PRIORITY_HINT) + ); +} + +FORCEINLINE +NTSTATUS +PhGetJobBasicAndIoAccounting( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION BasicAndIoAccounting + ) +{ + return NtQueryInformationJobObject( + JobHandle, + JobObjectBasicAndIoAccountingInformation, + BasicAndIoAccounting, + sizeof(JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetJobBasicLimits( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimits + ) +{ + return NtQueryInformationJobObject( + JobHandle, + JobObjectBasicLimitInformation, + BasicLimits, + sizeof(JOBOBJECT_BASIC_LIMIT_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetJobExtendedLimits( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_EXTENDED_LIMIT_INFORMATION ExtendedLimits + ) +{ + return NtQueryInformationJobObject( + JobHandle, + JobObjectExtendedLimitInformation, + ExtendedLimits, + sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetJobBasicUiRestrictions( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_BASIC_UI_RESTRICTIONS BasicUiRestrictions + ) +{ + return NtQueryInformationJobObject( + JobHandle, + JobObjectBasicUIRestrictions, + BasicUiRestrictions, + sizeof(JOBOBJECT_BASIC_UI_RESTRICTIONS), + NULL + ); +} + +/** + * Gets a token's session ID. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param SessionId A variable which receives the session ID. + */ +FORCEINLINE +NTSTATUS +PhGetTokenSessionId( + _In_ HANDLE TokenHandle, + _Out_ PULONG SessionId + ) +{ + ULONG returnLength; + + return NtQueryInformationToken( + TokenHandle, + TokenSessionId, + SessionId, + sizeof(ULONG), + &returnLength + ); +} + +/** + * Gets a token's elevation type. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param ElevationType A variable which receives the elevation type. + */ +FORCEINLINE +NTSTATUS +PhGetTokenElevationType( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_ELEVATION_TYPE ElevationType + ) +{ + ULONG returnLength; + + return NtQueryInformationToken( + TokenHandle, + TokenElevationType, + ElevationType, + sizeof(TOKEN_ELEVATION_TYPE), + &returnLength + ); +} + +/** + * Gets whether a token is elevated. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param Elevated A variable which receives a boolean indicating whether the token is elevated. + */ +FORCEINLINE +NTSTATUS +PhGetTokenIsElevated( + _In_ HANDLE TokenHandle, + _Out_ PBOOLEAN Elevated + ) +{ + NTSTATUS status; + TOKEN_ELEVATION elevation; + ULONG returnLength; + + status = NtQueryInformationToken( + TokenHandle, + TokenElevation, + &elevation, + sizeof(TOKEN_ELEVATION), + &returnLength + ); + + if (NT_SUCCESS(status)) + { + *Elevated = !!elevation.TokenIsElevated; + } + + return status; +} + +/** + * Gets a token's statistics. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param Statistics A variable which receives the token's statistics. + */ +FORCEINLINE +NTSTATUS +PhGetTokenStatistics( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_STATISTICS Statistics + ) +{ + ULONG returnLength; + + return NtQueryInformationToken( + TokenHandle, + TokenStatistics, + Statistics, + sizeof(TOKEN_STATISTICS), + &returnLength + ); +} + +/** + * Gets a token's source. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY_SOURCE access. + * \param Source A variable which receives the token's source. + */ +FORCEINLINE +NTSTATUS +PhGetTokenSource( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_SOURCE Source + ) +{ + ULONG returnLength; + + return NtQueryInformationToken( + TokenHandle, + TokenSource, + Source, + sizeof(TOKEN_SOURCE), + &returnLength + ); +} + +/** + * Gets a handle to a token's linked token. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param LinkedTokenHandle A variable which receives a handle to the linked token. You must close + * the handle using NtClose() when you no longer need it. + */ +FORCEINLINE +NTSTATUS +PhGetTokenLinkedToken( + _In_ HANDLE TokenHandle, + _Out_ PHANDLE LinkedTokenHandle + ) +{ + NTSTATUS status; + ULONG returnLength; + TOKEN_LINKED_TOKEN linkedToken; + + status = NtQueryInformationToken( + TokenHandle, + TokenLinkedToken, + &linkedToken, + sizeof(TOKEN_LINKED_TOKEN), + &returnLength + ); + + if (!NT_SUCCESS(status)) + return status; + + *LinkedTokenHandle = linkedToken.LinkedToken; + + return status; +} + +/** + * Gets whether virtualization is allowed for a token. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param IsVirtualizationAllowed A variable which receives a boolean indicating whether + * virtualization is allowed for the token. + */ +FORCEINLINE +NTSTATUS +PhGetTokenIsVirtualizationAllowed( + _In_ HANDLE TokenHandle, + _Out_ PBOOLEAN IsVirtualizationAllowed + ) +{ + NTSTATUS status; + ULONG returnLength; + ULONG virtualizationAllowed; + + status = NtQueryInformationToken( + TokenHandle, + TokenVirtualizationAllowed, + &virtualizationAllowed, + sizeof(ULONG), + &returnLength + ); + + if (!NT_SUCCESS(status)) + return status; + + *IsVirtualizationAllowed = !!virtualizationAllowed; + + return status; +} + +/** + * Gets whether virtualization is enabled for a token. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param IsVirtualizationEnabled A variable which receives a boolean indicating whether + * virtualization is enabled for the token. + */ +FORCEINLINE +NTSTATUS +PhGetTokenIsVirtualizationEnabled( + _In_ HANDLE TokenHandle, + _Out_ PBOOLEAN IsVirtualizationEnabled + ) +{ + NTSTATUS status; + ULONG returnLength; + ULONG virtualizationEnabled; + + status = NtQueryInformationToken( + TokenHandle, + TokenVirtualizationEnabled, + &virtualizationEnabled, + sizeof(ULONG), + &returnLength + ); + + if (!NT_SUCCESS(status)) + return status; + + *IsVirtualizationEnabled = !!virtualizationEnabled; + + return status; +} + +FORCEINLINE +NTSTATUS +PhGetEventBasicInformation( + _In_ HANDLE EventHandle, + _Out_ PEVENT_BASIC_INFORMATION BasicInformation + ) +{ + return NtQueryEvent( + EventHandle, + EventBasicInformation, + BasicInformation, + sizeof(EVENT_BASIC_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetMutantBasicInformation( + _In_ HANDLE MutantHandle, + _Out_ PMUTANT_BASIC_INFORMATION BasicInformation + ) +{ + return NtQueryMutant( + MutantHandle, + MutantBasicInformation, + BasicInformation, + sizeof(MUTANT_BASIC_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetMutantOwnerInformation( + _In_ HANDLE MutantHandle, + _Out_ PMUTANT_OWNER_INFORMATION OwnerInformation + ) +{ + return NtQueryMutant( + MutantHandle, + MutantOwnerInformation, + OwnerInformation, + sizeof(MUTANT_OWNER_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetSectionBasicInformation( + _In_ HANDLE SectionHandle, + _Out_ PSECTION_BASIC_INFORMATION BasicInformation + ) +{ + return NtQuerySection( + SectionHandle, + SectionBasicInformation, + BasicInformation, + sizeof(SECTION_BASIC_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetSemaphoreBasicInformation( + _In_ HANDLE SemaphoreHandle, + _Out_ PSEMAPHORE_BASIC_INFORMATION BasicInformation + ) +{ + return NtQuerySemaphore( + SemaphoreHandle, + SemaphoreBasicInformation, + BasicInformation, + sizeof(SEMAPHORE_BASIC_INFORMATION), + NULL + ); +} + +FORCEINLINE +NTSTATUS +PhGetTimerBasicInformation( + _In_ HANDLE TimerHandle, + _Out_ PTIMER_BASIC_INFORMATION BasicInformation + ) +{ + return NtQueryTimer( + TimerHandle, + TimerBasicInformation, + BasicInformation, + sizeof(TIMER_BASIC_INFORMATION), + NULL + ); +} + +#endif diff --git a/phlib/include/phnet.h b/phlib/include/phnet.h new file mode 100644 index 0000000..50394df --- /dev/null +++ b/phlib/include/phnet.h @@ -0,0 +1,139 @@ +#ifndef _PH_PHNET_H +#define _PH_PHNET_H + +#include +#include + +#define PH_IPV4_NETWORK_TYPE 0x1 +#define PH_IPV6_NETWORK_TYPE 0x2 +#define PH_NETWORK_TYPE_MASK 0x3 + +#define PH_TCP_PROTOCOL_TYPE 0x10 +#define PH_UDP_PROTOCOL_TYPE 0x20 +#define PH_PROTOCOL_TYPE_MASK 0x30 + +#define PH_NO_NETWORK_PROTOCOL 0x0 +#define PH_TCP4_NETWORK_PROTOCOL (PH_IPV4_NETWORK_TYPE | PH_TCP_PROTOCOL_TYPE) +#define PH_TCP6_NETWORK_PROTOCOL (PH_IPV6_NETWORK_TYPE | PH_TCP_PROTOCOL_TYPE) +#define PH_UDP4_NETWORK_PROTOCOL (PH_IPV4_NETWORK_TYPE | PH_UDP_PROTOCOL_TYPE) +#define PH_UDP6_NETWORK_PROTOCOL (PH_IPV6_NETWORK_TYPE | PH_UDP_PROTOCOL_TYPE) + +typedef struct _PH_IP_ADDRESS +{ + ULONG Type; + union + { + ULONG Ipv4; + struct in_addr InAddr; + UCHAR Ipv6[16]; + struct in6_addr In6Addr; + }; +} PH_IP_ADDRESS, *PPH_IP_ADDRESS; + +FORCEINLINE BOOLEAN PhEqualIpAddress( + _In_ PPH_IP_ADDRESS Address1, + _In_ PPH_IP_ADDRESS Address2 + ) +{ + if ((Address1->Type | Address2->Type) == 0) // don't check addresses if both are invalid + return TRUE; + if (Address1->Type != Address2->Type) + return FALSE; + + if (Address1->Type == PH_IPV4_NETWORK_TYPE) + { + return Address1->Ipv4 == Address2->Ipv4; + } + else + { +#ifdef _WIN64 + return + *(PULONG64)(Address1->Ipv6) == *(PULONG64)(Address2->Ipv6) && + *(PULONG64)(Address1->Ipv6 + 8) == *(PULONG64)(Address2->Ipv6 + 8); +#else + return + *(PULONG)(Address1->Ipv6) == *(PULONG)(Address2->Ipv6) && + *(PULONG)(Address1->Ipv6 + 4) == *(PULONG)(Address2->Ipv6 + 4) && + *(PULONG)(Address1->Ipv6 + 8) == *(PULONG)(Address2->Ipv6 + 8) && + *(PULONG)(Address1->Ipv6 + 12) == *(PULONG)(Address2->Ipv6 + 12); +#endif + } +} + +FORCEINLINE ULONG PhHashIpAddress( + _In_ PPH_IP_ADDRESS Address + ) +{ + ULONG hash = 0; + + if (Address->Type == 0) + return 0; + + hash = Address->Type | (Address->Type << 16); + + if (Address->Type == PH_IPV4_NETWORK_TYPE) + { + hash ^= Address->Ipv4; + } + else + { + hash += *(PULONG)(Address->Ipv6); + hash ^= *(PULONG)(Address->Ipv6 + 4); + hash += *(PULONG)(Address->Ipv6 + 8); + hash ^= *(PULONG)(Address->Ipv6 + 12); + } + + return hash; +} + +FORCEINLINE BOOLEAN PhIsNullIpAddress( + _In_ PPH_IP_ADDRESS Address + ) +{ + if (Address->Type == 0) + { + return TRUE; + } + else if (Address->Type == PH_IPV4_NETWORK_TYPE) + { + return Address->Ipv4 == 0; + } + else if (Address->Type == PH_IPV6_NETWORK_TYPE) + { +#ifdef _WIN64 + return (*(PULONG64)(Address->Ipv6) | *(PULONG64)(Address->Ipv6 + 8)) == 0; +#else + return (*(PULONG)(Address->Ipv6) | *(PULONG)(Address->Ipv6 + 4) | + *(PULONG)(Address->Ipv6 + 8) | *(PULONG)(Address->Ipv6 + 12)) == 0; +#endif + } + else + { + return TRUE; + } +} + +typedef struct _PH_IP_ENDPOINT +{ + PH_IP_ADDRESS Address; + ULONG Port; +} PH_IP_ENDPOINT, *PPH_IP_ENDPOINT; + +FORCEINLINE BOOLEAN PhEqualIpEndpoint( + _In_ PPH_IP_ENDPOINT Endpoint1, + _In_ PPH_IP_ENDPOINT Endpoint2 + ) +{ + return + PhEqualIpAddress(&Endpoint1->Address, &Endpoint2->Address) && + Endpoint1->Port == Endpoint2->Port; +} + +FORCEINLINE ULONG PhHashIpEndpoint( + _In_ PPH_IP_ENDPOINT Endpoint + ) +{ + return PhHashIpAddress(&Endpoint->Address) ^ Endpoint->Port; +} + +#endif diff --git a/phlib/include/phsup.h b/phlib/include/phsup.h new file mode 100644 index 0000000..55e36cf --- /dev/null +++ b/phlib/include/phsup.h @@ -0,0 +1,546 @@ +#ifndef _PH_PHSUP_H +#define _PH_PHSUP_H + +// This header file provides some useful definitions specific to phlib. + +#include +#include +#include +#include + +// Memory + +#define PTR_ADD_OFFSET(Pointer, Offset) ((PVOID)((ULONG_PTR)(Pointer) + (ULONG_PTR)(Offset))) +#define PTR_SUB_OFFSET(Pointer, Offset) ((PVOID)((ULONG_PTR)(Pointer) - (ULONG_PTR)(Offset))) +#define ALIGN_UP_BY(Address, Align) (((ULONG_PTR)(Address) + (Align) - 1) & ~((Align) - 1)) +#define ALIGN_UP_POINTER_BY(Pointer, Align) ((PVOID)ALIGN_UP_BY(Pointer, Align)) +#define ALIGN_UP(Address, Type) ALIGN_UP_BY(Address, sizeof(Type)) +#define ALIGN_UP_POINTER(Pointer, Type) ((PVOID)ALIGN_UP(Pointer, Type)) + +#define PAGE_SIZE 0x1000 + +#define PH_LARGE_BUFFER_SIZE (256 * 1024 * 1024) + +// Exceptions + +#define PhRaiseStatus(Status) RtlRaiseStatus(Status) + +#define SIMPLE_EXCEPTION_FILTER(Condition) \ + ((Condition) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) + +// Compiler + +#ifdef DEBUG +#define ASSUME_ASSERT(Expression) assert(Expression) +#define ASSUME_NO_DEFAULT assert(FALSE) +#else +#define ASSUME_ASSERT(Expression) __assume(Expression) +#define ASSUME_NO_DEFAULT __assume(FALSE) +#endif + +// Time + +#define PH_TICKS_PER_NS ((LONG64)1 * 10) +#define PH_TICKS_PER_MS (PH_TICKS_PER_NS * 1000) +#define PH_TICKS_PER_SEC (PH_TICKS_PER_MS * 1000) +#define PH_TICKS_PER_MIN (PH_TICKS_PER_SEC * 60) +#define PH_TICKS_PER_HOUR (PH_TICKS_PER_MIN * 60) +#define PH_TICKS_PER_DAY (PH_TICKS_PER_HOUR * 24) + +#define PH_TICKS_PARTIAL_MS(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_MS) % 1000) +#define PH_TICKS_PARTIAL_SEC(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_SEC) % 60) +#define PH_TICKS_PARTIAL_MIN(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_MIN) % 60) +#define PH_TICKS_PARTIAL_HOURS(Ticks) (((ULONG64)(Ticks) / PH_TICKS_PER_HOUR) % 24) +#define PH_TICKS_PARTIAL_DAYS(Ticks) ((ULONG64)(Ticks) / PH_TICKS_PER_DAY) + +#define PH_TIMEOUT_MS PH_TICKS_PER_MS +#define PH_TIMEOUT_SEC PH_TICKS_PER_SEC + +// Annotations + +/** + * Indicates that a function assumes the specified number of references are available for the + * object. + * + * \remarks Usually functions reference objects if they store them for later usage; this annotation + * specifies that the caller must supply these extra references itself. In effect these references + * are "transferred" to the function and must not be used. E.g. if you create an object and + * immediately call a function with _Assume_refs_(1), you may no longer use the object since that + * one reference you held is no longer yours. + */ +#define _Assume_refs_(count) + +#define _Callback_ + +/** + * Indicates that a function may raise a software exception. + * + * \remarks Do not use this annotation for temporary usages of exceptions, e.g. unimplemented + * functions. + */ +#define _May_raise_ + +/** + * Indicates that a function requires the specified value to be aligned at the specified number of + * bytes. + */ +#define _Needs_align_(align) + +// Casts + +// Zero extension and sign extension macros +#define C_1uTo2(x) ((unsigned short)(unsigned char)(x)) +#define C_1sTo2(x) ((unsigned short)(signed char)(x)) +#define C_1uTo4(x) ((unsigned int)(unsigned char)(x)) +#define C_1sTo4(x) ((unsigned int)(signed char)(x)) +#define C_2uTo4(x) ((unsigned int)(unsigned short)(x)) +#define C_2sTo4(x) ((unsigned int)(signed short)(x)) +#define C_4uTo8(x) ((unsigned __int64)(unsigned int)(x)) +#define C_4sTo8(x) ((unsigned __int64)(signed int)(x)) + +// Sorting + +typedef enum _PH_SORT_ORDER +{ + NoSortOrder = 0, + AscendingSortOrder, + DescendingSortOrder +} PH_SORT_ORDER, *PPH_SORT_ORDER; + +FORCEINLINE LONG PhModifySort( + _In_ LONG Result, + _In_ PH_SORT_ORDER Order + ) +{ + if (Order == AscendingSortOrder) + return Result; + else if (Order == DescendingSortOrder) + return -Result; + else + return Result; +} + +#define PH_BUILTIN_COMPARE(value1, value2) \ + if (value1 > value2) \ + return 1; \ + else if (value1 < value2) \ + return -1; \ + \ + return 0 + +FORCEINLINE int charcmp( + _In_ signed char value1, + _In_ signed char value2 + ) +{ + return C_1sTo4(value1 - value2); +} + +FORCEINLINE int ucharcmp( + _In_ unsigned char value1, + _In_ unsigned char value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int shortcmp( + _In_ signed short value1, + _In_ signed short value2 + ) +{ + return C_2sTo4(value1 - value2); +} + +FORCEINLINE int ushortcmp( + _In_ unsigned short value1, + _In_ unsigned short value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int intcmp( + _In_ int value1, + _In_ int value2 + ) +{ + return value1 - value2; +} + +FORCEINLINE int uintcmp( + _In_ unsigned int value1, + _In_ unsigned int value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int int64cmp( + _In_ __int64 value1, + _In_ __int64 value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int uint64cmp( + _In_ unsigned __int64 value1, + _In_ unsigned __int64 value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int intptrcmp( + _In_ LONG_PTR value1, + _In_ LONG_PTR value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int uintptrcmp( + _In_ ULONG_PTR value1, + _In_ ULONG_PTR value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int singlecmp( + _In_ float value1, + _In_ float value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int doublecmp( + _In_ double value1, + _In_ double value2 + ) +{ + PH_BUILTIN_COMPARE(value1, value2); +} + +FORCEINLINE int wcsicmp2( + _In_opt_ PWSTR Value1, + _In_opt_ PWSTR Value2 + ) +{ + if (Value1 && Value2) + return _wcsicmp(Value1, Value2); + else if (!Value1) + return !Value2 ? 0 : -1; + else + return 1; +} + +typedef int (__cdecl *PC_COMPARE_FUNCTION)(void *, const void *, const void *); + +// Synchronization + +#ifndef _WIN64 + +#ifndef _InterlockedCompareExchangePointer +void *_InterlockedCompareExchangePointer( + void *volatile *Destination, + void *Exchange, + void *Comparand + ); +#endif + +#if (_MSC_VER < 1900) +#ifndef _InterlockedExchangePointer +FORCEINLINE void *_InterlockedExchangePointer( + void *volatile *Destination, + void *Exchange + ) +{ + return (PVOID)_InterlockedExchange( + (PLONG_PTR)Destination, + (LONG_PTR)Exchange + ); +} +#endif +#endif + +#endif + +FORCEINLINE LONG_PTR _InterlockedExchangeAddPointer( + _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend, + _In_ LONG_PTR Value + ) +{ +#ifdef _WIN64 + return (LONG_PTR)_InterlockedExchangeAdd64((PLONG64)Addend, (LONG64)Value); +#else + return (LONG_PTR)_InterlockedExchangeAdd((PLONG)Addend, (LONG)Value); +#endif +} + +FORCEINLINE LONG_PTR _InterlockedIncrementPointer( + _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend + ) +{ +#ifdef _WIN64 + return (LONG_PTR)_InterlockedIncrement64((PLONG64)Addend); +#else + return (LONG_PTR)_InterlockedIncrement((PLONG)Addend); +#endif +} + +FORCEINLINE LONG_PTR _InterlockedDecrementPointer( + _Inout_ _Interlocked_operand_ LONG_PTR volatile *Addend + ) +{ +#ifdef _WIN64 + return (LONG_PTR)_InterlockedDecrement64((PLONG64)Addend); +#else + return (LONG_PTR)_InterlockedDecrement((PLONG)Addend); +#endif +} + +FORCEINLINE BOOLEAN _InterlockedBitTestAndResetPointer( + _Inout_ _Interlocked_operand_ LONG_PTR volatile *Base, + _In_ LONG_PTR Bit + ) +{ +#ifdef _WIN64 + return _interlockedbittestandreset64((PLONG64)Base, (LONG64)Bit); +#else + return _interlockedbittestandreset((PLONG)Base, (LONG)Bit); +#endif +} + +FORCEINLINE BOOLEAN _InterlockedBitTestAndSetPointer( + _Inout_ _Interlocked_operand_ LONG_PTR volatile *Base, + _In_ LONG_PTR Bit + ) +{ +#ifdef _WIN64 + return _interlockedbittestandset64((PLONG64)Base, (LONG64)Bit); +#else + return _interlockedbittestandset((PLONG)Base, (LONG)Bit); +#endif +} + +FORCEINLINE BOOLEAN _InterlockedIncrementNoZero( + _Inout_ _Interlocked_operand_ LONG volatile *Addend + ) +{ + LONG value; + LONG newValue; + + value = *Addend; + + while (TRUE) + { + if (value == 0) + return FALSE; + + if ((newValue = _InterlockedCompareExchange( + Addend, + value + 1, + value + )) == value) + { + return TRUE; + } + + value = newValue; + } +} + +FORCEINLINE BOOLEAN _InterlockedIncrementPositive( + _Inout_ _Interlocked_operand_ LONG volatile *Addend + ) +{ + LONG value; + LONG newValue; + + value = *Addend; + + while (TRUE) + { + if (value <= 0) + return FALSE; + + if ((newValue = _InterlockedCompareExchange( + Addend, + value + 1, + value + )) == value) + { + return TRUE; + } + + value = newValue; + } +} + +// Strings + +#define PH_INT32_STR_LEN 12 +#define PH_INT32_STR_LEN_1 (PH_INT32_STR_LEN + 1) + +#define PH_INT64_STR_LEN 50 +#define PH_INT64_STR_LEN_1 (PH_INT64_STR_LEN + 1) + +#define PH_PTR_STR_LEN 24 +#define PH_PTR_STR_LEN_1 (PH_PTR_STR_LEN + 1) + +FORCEINLINE VOID PhPrintInt32( + _Out_writes_(PH_INT32_STR_LEN_1) PWSTR Destination, + _In_ LONG Int32 + ) +{ + _ltow(Int32, Destination, 10); +} + +FORCEINLINE VOID PhPrintUInt32( + _Out_writes_(PH_INT32_STR_LEN_1) PWSTR Destination, + _In_ ULONG UInt32 + ) +{ + _ultow(UInt32, Destination, 10); +} + +FORCEINLINE VOID PhPrintInt64( + _Out_writes_(PH_INT64_STR_LEN_1) PWSTR Destination, + _In_ LONG64 Int64 + ) +{ + _i64tow(Int64, Destination, 10); +} + +FORCEINLINE VOID PhPrintUInt64( + _Out_writes_(PH_INT64_STR_LEN_1) PWSTR Destination, + _In_ ULONG64 UInt64 + ) +{ + _ui64tow(UInt64, Destination, 10); +} + +FORCEINLINE VOID PhPrintPointer( + _Out_writes_(PH_PTR_STR_LEN_1) PWSTR Destination, + _In_ PVOID Pointer + ) +{ + Destination[0] = '0'; + Destination[1] = 'x'; +#ifdef _WIN64 + _ui64tow((ULONG64)Pointer, &Destination[2], 16); +#else + _ultow((ULONG)Pointer, &Destination[2], 16); +#endif +} + +// Misc. + +FORCEINLINE ULONG PhCountBits( + _In_ ULONG Value + ) +{ + ULONG count = 0; + + while (Value) + { + count++; + Value &= Value - 1; + } + + return count; +} + +FORCEINLINE ULONG PhRoundNumber( + _In_ ULONG Value, + _In_ ULONG Granularity + ) +{ + return (Value + Granularity / 2) / Granularity * Granularity; +} + +FORCEINLINE ULONG PhMultiplyDivide( + _In_ ULONG Number, + _In_ ULONG Numerator, + _In_ ULONG Denominator + ) +{ + return (ULONG)(((ULONG64)Number * (ULONG64)Numerator + Denominator / 2) / (ULONG64)Denominator); +} + +FORCEINLINE LONG PhMultiplyDivideSigned( + _In_ LONG Number, + _In_ ULONG Numerator, + _In_ ULONG Denominator + ) +{ + if (Number >= 0) + return PhMultiplyDivide(Number, Numerator, Denominator); + else + return -(LONG)PhMultiplyDivide(-Number, Numerator, Denominator); +} + +FORCEINLINE VOID PhProbeAddress( + _In_ PVOID UserAddress, + _In_ SIZE_T UserLength, + _In_ PVOID BufferAddress, + _In_ SIZE_T BufferLength, + _In_ ULONG Alignment + ) +{ + if (UserLength != 0) + { + if (((ULONG_PTR)UserAddress & (Alignment - 1)) != 0) + PhRaiseStatus(STATUS_DATATYPE_MISALIGNMENT); + + if ( + ((ULONG_PTR)UserAddress + UserLength < (ULONG_PTR)UserAddress) || + ((ULONG_PTR)UserAddress < (ULONG_PTR)BufferAddress) || + ((ULONG_PTR)UserAddress + UserLength > (ULONG_PTR)BufferAddress + BufferLength) + ) + PhRaiseStatus(STATUS_ACCESS_VIOLATION); + } +} + +FORCEINLINE PLARGE_INTEGER PhTimeoutFromMilliseconds( + _Out_ PLARGE_INTEGER Timeout, + _In_ ULONG Milliseconds + ) +{ + if (Milliseconds == INFINITE) + return NULL; + + Timeout->QuadPart = -(LONGLONG)UInt32x32To64(Milliseconds, PH_TIMEOUT_MS); + + return Timeout; +} + +FORCEINLINE NTSTATUS PhGetLastWin32ErrorAsNtStatus() +{ + ULONG win32Result; + + // This is needed because NTSTATUS_FROM_WIN32 uses the argument multiple times. + win32Result = GetLastError(); + + return NTSTATUS_FROM_WIN32(win32Result); +} + +FORCEINLINE PVOID PhGetModuleProcAddress( + _In_ PWSTR ModuleName, + _In_ PSTR ProcName + ) +{ + HMODULE module; + + module = GetModuleHandle(ModuleName); + + if (module) + return GetProcAddress(module, ProcName); + else + return NULL; +} + +#endif diff --git a/phlib/include/phutil.h b/phlib/include/phutil.h new file mode 100644 index 0000000..80991fb --- /dev/null +++ b/phlib/include/phutil.h @@ -0,0 +1,1049 @@ +#ifndef _PH_PHUTIL_H +#define _PH_PHUTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern WCHAR *PhSizeUnitNames[7]; +extern ULONG PhMaxSizeUnit; + +typedef struct _PH_INTEGER_PAIR +{ + LONG X; + LONG Y; +} PH_INTEGER_PAIR, *PPH_INTEGER_PAIR; + +typedef struct _PH_SCALABLE_INTEGER_PAIR +{ + union + { + PH_INTEGER_PAIR Pair; + struct + { + LONG X; + LONG Y; + }; + }; + ULONG Scale; +} PH_SCALABLE_INTEGER_PAIR, *PPH_SCALABLE_INTEGER_PAIR; + +typedef struct _PH_RECTANGLE +{ + union + { + PH_INTEGER_PAIR Position; + struct + { + LONG Left; + LONG Top; + }; + }; + union + { + PH_INTEGER_PAIR Size; + struct + { + LONG Width; + LONG Height; + }; + }; +} PH_RECTANGLE, *PPH_RECTANGLE; + +FORCEINLINE +PH_RECTANGLE +PhRectToRectangle( + _In_ RECT Rect + ) +{ + PH_RECTANGLE rectangle; + + rectangle.Left = Rect.left; + rectangle.Top = Rect.top; + rectangle.Width = Rect.right - Rect.left; + rectangle.Height = Rect.bottom - Rect.top; + + return rectangle; +} + +FORCEINLINE +RECT +PhRectangleToRect( + _In_ PH_RECTANGLE Rectangle + ) +{ + RECT rect; + + rect.left = Rectangle.Left; + rect.top = Rectangle.Top; + rect.right = Rectangle.Left + Rectangle.Width; + rect.bottom = Rectangle.Top + Rectangle.Height; + + return rect; +} + +FORCEINLINE +VOID +PhConvertRect( + _Inout_ PRECT Rect, + _In_ PRECT ParentRect + ) +{ + Rect->right = ParentRect->right - ParentRect->left - Rect->right; + Rect->bottom = ParentRect->bottom - ParentRect->top - Rect->bottom; +} + +FORCEINLINE +RECT +PhMapRect( + _In_ RECT InnerRect, + _In_ RECT OuterRect + ) +{ + RECT rect; + + rect.left = InnerRect.left - OuterRect.left; + rect.top = InnerRect.top - OuterRect.top; + rect.right = InnerRect.right - OuterRect.left; + rect.bottom = InnerRect.bottom - OuterRect.top; + + return rect; +} + +PHLIBAPI +VOID +NTAPI +PhAdjustRectangleToBounds( + _Inout_ PPH_RECTANGLE Rectangle, + _In_ PPH_RECTANGLE Bounds + ); + +PHLIBAPI +VOID +NTAPI +PhCenterRectangle( + _Inout_ PPH_RECTANGLE Rectangle, + _In_ PPH_RECTANGLE Bounds + ); + +PHLIBAPI +VOID +NTAPI +PhAdjustRectangleToWorkingArea( + _In_opt_ HWND hWnd, + _Inout_ PPH_RECTANGLE Rectangle + ); + +PHLIBAPI +VOID +NTAPI +PhCenterWindow( + _In_ HWND WindowHandle, + _In_opt_ HWND ParentWindowHandle + ); + +FORCEINLINE +VOID +PhLargeIntegerToSystemTime( + _Out_ PSYSTEMTIME SystemTime, + _In_ PLARGE_INTEGER LargeInteger + ) +{ + FILETIME fileTime; + + fileTime.dwLowDateTime = LargeInteger->LowPart; + fileTime.dwHighDateTime = LargeInteger->HighPart; + FileTimeToSystemTime(&fileTime, SystemTime); +} + +FORCEINLINE +VOID +PhLargeIntegerToLocalSystemTime( + _Out_ PSYSTEMTIME SystemTime, + _In_ PLARGE_INTEGER LargeInteger + ) +{ + FILETIME fileTime; + FILETIME newFileTime; + + fileTime.dwLowDateTime = LargeInteger->LowPart; + fileTime.dwHighDateTime = LargeInteger->HighPart; + FileTimeToLocalFileTime(&fileTime, &newFileTime); + FileTimeToSystemTime(&newFileTime, SystemTime); +} + +PHLIBAPI +VOID +NTAPI +PhReferenceObjects( + _In_reads_(NumberOfObjects) PVOID *Objects, + _In_ ULONG NumberOfObjects + ); + +PHLIBAPI +VOID +NTAPI +PhDereferenceObjects( + _In_reads_(NumberOfObjects) PVOID *Objects, + _In_ ULONG NumberOfObjects + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetMessage( + _In_ PVOID DllHandle, + _In_ ULONG MessageTableId, + _In_ ULONG MessageLanguageId, + _In_ ULONG MessageId + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetNtMessage( + _In_ NTSTATUS Status + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetWin32Message( + _In_ ULONG Result + ); + +PHLIBAPI +INT +NTAPI +PhShowMessage( + _In_ HWND hWnd, + _In_ ULONG Type, + _In_ PWSTR Format, + ... + ); + +PHLIBAPI +INT +NTAPI +PhShowMessage_V( + _In_ HWND hWnd, + _In_ ULONG Type, + _In_ PWSTR Format, + _In_ va_list ArgPtr + ); + +#define PhShowError(hWnd, Format, ...) PhShowMessage(hWnd, MB_OK | MB_ICONERROR, Format, __VA_ARGS__) +#define PhShowWarning(hWnd, Format, ...) PhShowMessage(hWnd, MB_OK | MB_ICONWARNING, Format, __VA_ARGS__) +#define PhShowInformation(hWnd, Format, ...) PhShowMessage(hWnd, MB_OK | MB_ICONINFORMATION, Format, __VA_ARGS__) + +PHLIBAPI +PPH_STRING +NTAPI +PhGetStatusMessage( + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ); + +PHLIBAPI +VOID +NTAPI +PhShowStatus( + _In_ HWND hWnd, + _In_opt_ PWSTR Message, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhShowContinueStatus( + _In_ HWND hWnd, + _In_opt_ PWSTR Message, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhShowConfirmMessage( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PWSTR Object, + _In_opt_ PWSTR Message, + _In_ BOOLEAN Warning + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhFindIntegerSiKeyValuePairs( + _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, + _In_ ULONG SizeOfKeyValuePairs, + _In_ PWSTR String, + _Out_ PULONG Integer + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhFindStringSiKeyValuePairs( + _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, + _In_ ULONG SizeOfKeyValuePairs, + _In_ ULONG Integer, + _Out_ PWSTR *String + ); + +#define GUID_VERSION_MAC 1 +#define GUID_VERSION_DCE 2 +#define GUID_VERSION_MD5 3 +#define GUID_VERSION_RANDOM 4 +#define GUID_VERSION_SHA1 5 + +#define GUID_VARIANT_NCS_MASK 0x80 +#define GUID_VARIANT_NCS 0x00 +#define GUID_VARIANT_STANDARD_MASK 0xc0 +#define GUID_VARIANT_STANDARD 0x80 +#define GUID_VARIANT_MICROSOFT_MASK 0xe0 +#define GUID_VARIANT_MICROSOFT 0xc0 +#define GUID_VARIANT_RESERVED_MASK 0xe0 +#define GUID_VARIANT_RESERVED 0xe0 + +typedef union _GUID_EX +{ + GUID Guid; + UCHAR Data[16]; + struct + { + ULONG TimeLowPart; + USHORT TimeMidPart; + USHORT TimeHighPart; + UCHAR ClockSequenceHigh; + UCHAR ClockSequenceLow; + UCHAR Node[6]; + } s; + struct + { + ULONG Part0; + USHORT Part32; + UCHAR Part48; + UCHAR Part56 : 4; + UCHAR Version : 4; + UCHAR Variant; + UCHAR Part72; + USHORT Part80; + ULONG Part96; + } s2; +} GUID_EX, *PGUID_EX; + +PHLIBAPI +VOID +NTAPI +PhGenerateGuid( + _Out_ PGUID Guid + ); + +PHLIBAPI +VOID +NTAPI +PhGenerateGuidFromName( + _Out_ PGUID Guid, + _In_ PGUID Namespace, + _In_ PCHAR Name, + _In_ ULONG NameLength, + _In_ UCHAR Version + ); + +PHLIBAPI +VOID +NTAPI +PhGenerateRandomAlphaString( + _Out_writes_z_(Count) PWSTR Buffer, + _In_ ULONG Count + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhEllipsisString( + _In_ PPH_STRING String, + _In_ ULONG DesiredCount + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhEllipsisStringPath( + _In_ PPH_STRING String, + _In_ ULONG DesiredCount + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhMatchWildcards( + _In_ PWSTR Pattern, + _In_ PWSTR String, + _In_ BOOLEAN IgnoreCase + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhEscapeStringForMenuPrefix( + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +LONG +NTAPI +PhCompareUnicodeStringZIgnoreMenuPrefix( + _In_ PWSTR A, + _In_ PWSTR B, + _In_ BOOLEAN IgnoreCase, + _In_ BOOLEAN MatchIfPrefix + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatDate( + _In_opt_ PSYSTEMTIME Date, + _In_opt_ PWSTR Format + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatTime( + _In_opt_ PSYSTEMTIME Time, + _In_opt_ PWSTR Format + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatDateTime( + _In_opt_ PSYSTEMTIME DateTime + ); + +#define PhaFormatDateTime(DateTime) PH_AUTO_T(PH_STRING, PhFormatDateTime(DateTime)) + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatTimeSpan( + _In_ ULONG64 Ticks, + _In_opt_ ULONG Mode + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatTimeSpanRelative( + _In_ ULONG64 TimeSpan + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatUInt64( + _In_ ULONG64 Value, + _In_ BOOLEAN GroupDigits + ); + +#define PhaFormatUInt64(Value, GroupDigits) PH_AUTO_T(PH_STRING, PhFormatUInt64((Value), (GroupDigits))) + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatDecimal( + _In_ PWSTR Value, + _In_ ULONG FractionalDigits, + _In_ BOOLEAN GroupDigits + ); + +#define PhaFormatDecimal(Value, FractionalDigits, GroupDigits) \ + PH_AUTO_T(PH_STRING, PhFormatDecimal((Value), (FractionalDigits), (GroupDigits))) + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatSize( + _In_ ULONG64 Size, + _In_ ULONG MaxSizeUnit + ); + +#define PhaFormatSize(Size, MaxSizeUnit) PH_AUTO_T(PH_STRING, PhFormatSize((Size), (MaxSizeUnit))) + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatGuid( + _In_ PGUID Guid + ); + +PHLIBAPI +PVOID +NTAPI +PhGetFileVersionInfo( + _In_ PWSTR FileName + ); + +PHLIBAPI +ULONG +NTAPI +PhGetFileVersionInfoLangCodePage( + _In_ PVOID VersionInfo + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetFileVersionInfoString( + _In_ PVOID VersionInfo, + _In_ PWSTR SubBlock + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetFileVersionInfoString2( + _In_ PVOID VersionInfo, + _In_ ULONG LangCodePage, + _In_ PWSTR StringName + ); + +typedef struct _PH_IMAGE_VERSION_INFO +{ + PPH_STRING CompanyName; + PPH_STRING FileDescription; + PPH_STRING FileVersion; + PPH_STRING ProductName; +} PH_IMAGE_VERSION_INFO, *PPH_IMAGE_VERSION_INFO; + +PHLIBAPI +BOOLEAN +NTAPI +PhInitializeImageVersionInfo( + _Out_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, + _In_ PWSTR FileName + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteImageVersionInfo( + _Inout_ PPH_IMAGE_VERSION_INFO ImageVersionInfo + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhFormatImageVersionInfo( + _In_opt_ PPH_STRING FileName, + _In_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, + _In_opt_ PPH_STRINGREF Indent, + _In_opt_ ULONG LineLimit + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetFullPath( + _In_ PWSTR FileName, + _Out_opt_ PULONG IndexOfFileName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhExpandEnvironmentStrings( + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetBaseName( + _In_ PPH_STRING FileName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetSystemDirectory( + VOID + ); + +PHLIBAPI +VOID +NTAPI +PhGetSystemRoot( + _Out_ PPH_STRINGREF SystemRoot + ); + +PHLIBAPI +PLDR_DATA_TABLE_ENTRY +NTAPI +PhFindLoaderEntry( + _In_opt_ PVOID DllBase, + _In_opt_ PPH_STRINGREF FullDllName, + _In_opt_ PPH_STRINGREF BaseDllName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetDllFileName( + _In_ PVOID DllHandle, + _Out_opt_ PULONG IndexOfFileName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetApplicationFileName( + VOID + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetApplicationDirectory( + VOID + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetKnownLocation( + _In_ ULONG Folder, + _In_opt_ PWSTR AppendPath + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWaitForMultipleObjectsAndPump( + _In_opt_ HWND hWnd, + _In_ ULONG NumberOfHandles, + _In_ PHANDLE Handles, + _In_ ULONG Timeout + ); + +typedef struct _PH_CREATE_PROCESS_INFO +{ + PUNICODE_STRING DllPath; + PUNICODE_STRING WindowTitle; + PUNICODE_STRING DesktopInfo; + PUNICODE_STRING ShellInfo; + PUNICODE_STRING RuntimeData; +} PH_CREATE_PROCESS_INFO, *PPH_CREATE_PROCESS_INFO; + +#define PH_CREATE_PROCESS_INHERIT_HANDLES 0x1 +#define PH_CREATE_PROCESS_UNICODE_ENVIRONMENT 0x2 +#define PH_CREATE_PROCESS_SUSPENDED 0x4 +#define PH_CREATE_PROCESS_BREAKAWAY_FROM_JOB 0x8 +#define PH_CREATE_PROCESS_NEW_CONSOLE 0x10 + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateProcess( + _In_ PWSTR FileName, + _In_opt_ PPH_STRINGREF CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PPH_STRINGREF CurrentDirectory, + _In_opt_ PPH_CREATE_PROCESS_INFO Information, + _In_ ULONG Flags, + _In_opt_ HANDLE ParentProcessHandle, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateProcessWin32( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PWSTR CurrentDirectory, + _In_ ULONG Flags, + _In_opt_ HANDLE TokenHandle, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateProcessWin32Ex( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PWSTR CurrentDirectory, + _In_opt_ STARTUPINFO *StartupInfo, + _In_ ULONG Flags, + _In_opt_ HANDLE TokenHandle, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ); + +typedef struct _PH_CREATE_PROCESS_AS_USER_INFO +{ + _In_opt_ PWSTR ApplicationName; + _In_opt_ PWSTR CommandLine; + _In_opt_ PWSTR CurrentDirectory; + _In_opt_ PVOID Environment; + _In_opt_ PWSTR DesktopName; + _In_opt_ ULONG SessionId; // use PH_CREATE_PROCESS_SET_SESSION_ID + union + { + struct + { + _In_ PWSTR DomainName; + _In_ PWSTR UserName; + _In_ PWSTR Password; + _In_opt_ ULONG LogonType; + }; + _In_ HANDLE ProcessIdWithToken; // use PH_CREATE_PROCESS_USE_PROCESS_TOKEN + _In_ ULONG SessionIdWithToken; // use PH_CREATE_PROCESS_USE_SESSION_TOKEN + }; +} PH_CREATE_PROCESS_AS_USER_INFO, *PPH_CREATE_PROCESS_AS_USER_INFO; + +#define PH_CREATE_PROCESS_USE_PROCESS_TOKEN 0x1000 +#define PH_CREATE_PROCESS_USE_SESSION_TOKEN 0x2000 +#define PH_CREATE_PROCESS_USE_LINKED_TOKEN 0x10000 +#define PH_CREATE_PROCESS_SET_SESSION_ID 0x20000 +#define PH_CREATE_PROCESS_WITH_PROFILE 0x40000 + +PHLIBAPI +NTSTATUS +NTAPI +PhCreateProcessAsUser( + _In_ PPH_CREATE_PROCESS_AS_USER_INFO Information, + _In_ ULONG Flags, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhFilterTokenForLimitedUser( + _In_ HANDLE TokenHandle, + _Out_ PHANDLE NewTokenHandle + ); + +PHLIBAPI +VOID +NTAPI +PhShellExecute( + _In_ HWND hWnd, + _In_ PWSTR FileName, + _In_opt_ PWSTR Parameters + ); + +#define PH_SHELL_EXECUTE_ADMIN 0x1 +#define PH_SHELL_EXECUTE_PUMP_MESSAGES 0x2 + +PHLIBAPI +BOOLEAN +NTAPI +PhShellExecuteEx( + _In_opt_ HWND hWnd, + _In_ PWSTR FileName, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ); + +PHLIBAPI +VOID +NTAPI +PhShellExploreFile( + _In_ HWND hWnd, + _In_ PWSTR FileName + ); + +PHLIBAPI +VOID +NTAPI +PhShellProperties( + _In_ HWND hWnd, + _In_ PWSTR FileName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhExpandKeyName( + _In_ PPH_STRING KeyName, + _In_ BOOLEAN Computer + ); + +PHLIBAPI +VOID +NTAPI +PhShellOpenKey( + _In_ HWND hWnd, + _In_ PPH_STRING KeyName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhQueryRegistryString( + _In_ HANDLE KeyHandle, + _In_opt_ PWSTR ValueName + ); + +typedef struct _PH_FLAG_MAPPING +{ + ULONG Flag1; + ULONG Flag2; +} PH_FLAG_MAPPING, *PPH_FLAG_MAPPING; + +PHLIBAPI +VOID +NTAPI +PhMapFlags1( + _Inout_ PULONG Value2, + _In_ ULONG Value1, + _In_ const PH_FLAG_MAPPING *Mappings, + _In_ ULONG NumberOfMappings + ); + +PHLIBAPI +VOID +NTAPI +PhMapFlags2( + _Inout_ PULONG Value1, + _In_ ULONG Value2, + _In_ const PH_FLAG_MAPPING *Mappings, + _In_ ULONG NumberOfMappings + ); + +PHLIBAPI +PVOID +NTAPI +PhCreateOpenFileDialog( + VOID + ); + +PHLIBAPI +PVOID +NTAPI +PhCreateSaveFileDialog( + VOID + ); + +PHLIBAPI +VOID +NTAPI +PhFreeFileDialog( + _In_ PVOID FileDialog + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhShowFileDialog( + _In_ HWND hWnd, + _In_ PVOID FileDialog + ); + +#define PH_FILEDIALOG_CREATEPROMPT 0x1 +#define PH_FILEDIALOG_PATHMUSTEXIST 0x2 // default both +#define PH_FILEDIALOG_FILEMUSTEXIST 0x4 // default open +#define PH_FILEDIALOG_SHOWHIDDEN 0x8 +#define PH_FILEDIALOG_NODEREFERENCELINKS 0x10 +#define PH_FILEDIALOG_OVERWRITEPROMPT 0x20 // default save +#define PH_FILEDIALOG_DEFAULTEXPANDED 0x40 +#define PH_FILEDIALOG_STRICTFILETYPES 0x80 +#define PH_FILEDIALOG_PICKFOLDERS 0x100 + +PHLIBAPI +ULONG +NTAPI +PhGetFileDialogOptions( + _In_ PVOID FileDialog + ); + +PHLIBAPI +VOID +NTAPI +PhSetFileDialogOptions( + _In_ PVOID FileDialog, + _In_ ULONG Options + ); + +PHLIBAPI +ULONG +NTAPI +PhGetFileDialogFilterIndex( + _In_ PVOID FileDialog + ); + +typedef struct _PH_FILETYPE_FILTER +{ + PWSTR Name; + PWSTR Filter; +} PH_FILETYPE_FILTER, *PPH_FILETYPE_FILTER; + +PHLIBAPI +VOID +NTAPI +PhSetFileDialogFilter( + _In_ PVOID FileDialog, + _In_ PPH_FILETYPE_FILTER Filters, + _In_ ULONG NumberOfFilters + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetFileDialogFileName( + _In_ PVOID FileDialog + ); + +PHLIBAPI +VOID +NTAPI +PhSetFileDialogFileName( + _In_ PVOID FileDialog, + _In_ PWSTR FileName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhIsExecutablePacked( + _In_ PWSTR FileName, + _Out_ PBOOLEAN IsPacked, + _Out_opt_ PULONG NumberOfModules, + _Out_opt_ PULONG NumberOfFunctions + ); + +PHLIBAPI +ULONG +NTAPI +PhCrc32( + _In_ ULONG Crc, + _In_reads_(Length) PCHAR Buffer, + _In_ SIZE_T Length + ); + +typedef enum _PH_HASH_ALGORITHM +{ + Md5HashAlgorithm, + Sha1HashAlgorithm, + Crc32HashAlgorithm +} PH_HASH_ALGORITHM; + +typedef struct _PH_HASH_CONTEXT +{ + PH_HASH_ALGORITHM Algorithm; + ULONG Context[64]; +} PH_HASH_CONTEXT, *PPH_HASH_CONTEXT; + +PHLIBAPI +VOID +NTAPI +PhInitializeHash( + _Out_ PPH_HASH_CONTEXT Context, + _In_ PH_HASH_ALGORITHM Algorithm + ); + +PHLIBAPI +VOID +NTAPI +PhUpdateHash( + _Inout_ PPH_HASH_CONTEXT Context, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhFinalHash( + _Inout_ PPH_HASH_CONTEXT Context, + _Out_writes_bytes_(HashLength) PVOID Hash, + _In_ ULONG HashLength, + _Out_opt_ PULONG ReturnLength + ); + +typedef enum _PH_COMMAND_LINE_OPTION_TYPE +{ + NoArgumentType, + MandatoryArgumentType, + OptionalArgumentType +} PH_COMMAND_LINE_OPTION_TYPE, *PPH_COMMAND_LINE_OPTION_TYPE; + +typedef struct _PH_COMMAND_LINE_OPTION +{ + ULONG Id; + PWSTR Name; + PH_COMMAND_LINE_OPTION_TYPE Type; +} PH_COMMAND_LINE_OPTION, *PPH_COMMAND_LINE_OPTION; + +typedef BOOLEAN (NTAPI *PPH_COMMAND_LINE_CALLBACK)( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ); + +#define PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS 0x1 +#define PH_COMMAND_LINE_IGNORE_FIRST_PART 0x2 + +PHLIBAPI +PPH_STRING +NTAPI +PhParseCommandLinePart( + _In_ PPH_STRINGREF CommandLine, + _Inout_ PULONG_PTR Index + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhParseCommandLine( + _In_ PPH_STRINGREF CommandLine, + _In_opt_ PPH_COMMAND_LINE_OPTION Options, + _In_ ULONG NumberOfOptions, + _In_ ULONG Flags, + _In_ PPH_COMMAND_LINE_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhEscapeCommandLinePart( + _In_ PPH_STRINGREF String + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhParseCommandLineFuzzy( + _In_ PPH_STRINGREF CommandLine, + _Out_ PPH_STRINGREF FileName, + _Out_ PPH_STRINGREF Arguments, + _Out_opt_ PPH_STRING *FullFileName + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/provider.h b/phlib/include/provider.h new file mode 100644 index 0000000..7d33a84 --- /dev/null +++ b/phlib/include/provider.h @@ -0,0 +1,139 @@ +#ifndef _PH_PROVIDER_H +#define _PH_PROVIDER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(DEBUG) +extern PPH_LIST PhDbgProviderList; +extern PH_QUEUED_LOCK PhDbgProviderListLock; +#endif + +typedef enum _PH_PROVIDER_THREAD_STATE +{ + ProviderThreadRunning, + ProviderThreadStopped, + ProviderThreadStopping +} PH_PROVIDER_THREAD_STATE; + +typedef VOID (NTAPI *PPH_PROVIDER_FUNCTION)( + _In_ PVOID Object + ); + +struct _PH_PROVIDER_THREAD; +typedef struct _PH_PROVIDER_THREAD *PPH_PROVIDER_THREAD; + +typedef struct _PH_PROVIDER_REGISTRATION +{ + LIST_ENTRY ListEntry; + PPH_PROVIDER_THREAD ProviderThread; + PPH_PROVIDER_FUNCTION Function; + PVOID Object; + ULONG RunId; + BOOLEAN Enabled; + BOOLEAN Unregistering; + BOOLEAN Boosting; +} PH_PROVIDER_REGISTRATION, *PPH_PROVIDER_REGISTRATION; + +typedef struct _PH_PROVIDER_THREAD +{ + HANDLE ThreadHandle; + HANDLE TimerHandle; + ULONG Interval; + PH_PROVIDER_THREAD_STATE State; + + PH_QUEUED_LOCK Lock; + LIST_ENTRY ListHead; + ULONG BoostCount; +} PH_PROVIDER_THREAD, *PPH_PROVIDER_THREAD; + +PHLIBAPI +VOID +NTAPI +PhInitializeProviderThread( + _Out_ PPH_PROVIDER_THREAD ProviderThread, + _In_ ULONG Interval + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ); + +PHLIBAPI +VOID +NTAPI +PhStartProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ); + +PHLIBAPI +VOID +NTAPI +PhStopProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ); + +PHLIBAPI +VOID +NTAPI +PhSetIntervalProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread, + _In_ ULONG Interval + ); + +PHLIBAPI +VOID +NTAPI +PhRegisterProvider( + _Inout_ PPH_PROVIDER_THREAD ProviderThread, + _In_ PPH_PROVIDER_FUNCTION Function, + _In_opt_ PVOID Object, + _Out_ PPH_PROVIDER_REGISTRATION Registration + ); + +PHLIBAPI +VOID +NTAPI +PhUnregisterProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhBoostProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration, + _Out_opt_ PULONG FutureRunId + ); + +PHLIBAPI +ULONG +NTAPI +PhGetRunIdProvider( + _In_ PPH_PROVIDER_REGISTRATION Registration + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhGetEnabledProvider( + _In_ PPH_PROVIDER_REGISTRATION Registration + ); + +PHLIBAPI +VOID +NTAPI +PhSetEnabledProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration, + _In_ BOOLEAN Enabled + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/queuedlock.h b/phlib/include/queuedlock.h new file mode 100644 index 0000000..dd96ea0 --- /dev/null +++ b/phlib/include/queuedlock.h @@ -0,0 +1,349 @@ +#ifndef _PH_QUEUEDLOCK_H +#define _PH_QUEUEDLOCK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_QUEUED_LOCK_OWNED ((ULONG_PTR)0x1) +#define PH_QUEUED_LOCK_OWNED_SHIFT 0 +#define PH_QUEUED_LOCK_WAITERS ((ULONG_PTR)0x2) + +// Valid only if Waiters = 0 +#define PH_QUEUED_LOCK_SHARED_INC ((ULONG_PTR)0x4) +#define PH_QUEUED_LOCK_SHARED_SHIFT 2 + +// Valid only if Waiters = 1 +#define PH_QUEUED_LOCK_TRAVERSING ((ULONG_PTR)0x4) +#define PH_QUEUED_LOCK_MULTIPLE_SHARED ((ULONG_PTR)0x8) + +#define PH_QUEUED_LOCK_FLAGS ((ULONG_PTR)0xf) + +#define PhGetQueuedLockSharedOwners(Value) \ + ((ULONG_PTR)(Value) >> PH_QUEUED_LOCK_SHARED_SHIFT) +#define PhGetQueuedLockWaitBlock(Value) \ + ((PPH_QUEUED_WAIT_BLOCK)((ULONG_PTR)(Value) & ~PH_QUEUED_LOCK_FLAGS)) + +typedef struct _PH_QUEUED_LOCK +{ + ULONG_PTR Value; +} PH_QUEUED_LOCK, *PPH_QUEUED_LOCK; + +#define PH_QUEUED_LOCK_INIT { 0 } + +#define PH_QUEUED_WAITER_EXCLUSIVE 0x1 +#define PH_QUEUED_WAITER_SPINNING 0x2 +#define PH_QUEUED_WAITER_SPINNING_SHIFT 1 + +typedef struct DECLSPEC_ALIGN(16) _PH_QUEUED_WAIT_BLOCK +{ + /** A pointer to the next wait block, i.e. the wait block pushed onto the list before this one. */ + struct _PH_QUEUED_WAIT_BLOCK *Next; + /** + * A pointer to the previous wait block, i.e. the wait block pushed onto the list after this + * one. + */ + struct _PH_QUEUED_WAIT_BLOCK *Previous; + /** A pointer to the last wait block, i.e. the first waiter pushed onto the list. */ + struct _PH_QUEUED_WAIT_BLOCK *Last; + + ULONG SharedOwners; + ULONG Flags; +} PH_QUEUED_WAIT_BLOCK, *PPH_QUEUED_WAIT_BLOCK; + +BOOLEAN PhQueuedLockInitialization( + VOID + ); + +// Queued lock + +FORCEINLINE +VOID +PhInitializeQueuedLock( + _Out_ PPH_QUEUED_LOCK QueuedLock + ) +{ + QueuedLock->Value = 0; +} + +PHLIBAPI +VOID +FASTCALL +PhfAcquireQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ); + +_Acquires_exclusive_lock_(*QueuedLock) +FORCEINLINE +VOID +PhAcquireQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + if (_InterlockedBitTestAndSetPointer((PLONG_PTR)&QueuedLock->Value, PH_QUEUED_LOCK_OWNED_SHIFT)) + { + // Owned bit was already set. Slow path. + PhfAcquireQueuedLockExclusive(QueuedLock); + } +} + +PHLIBAPI +VOID +FASTCALL +PhfAcquireQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ); + +_Acquires_shared_lock_(*QueuedLock) +FORCEINLINE +VOID +PhAcquireQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)(PH_QUEUED_LOCK_OWNED | PH_QUEUED_LOCK_SHARED_INC), + (PVOID)0 + ) != 0) + { + PhfAcquireQueuedLockShared(QueuedLock); + } +} + +_When_(return != 0, _Acquires_exclusive_lock_(*QueuedLock)) +FORCEINLINE +BOOLEAN +PhTryAcquireQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + if (!_InterlockedBitTestAndSetPointer((PLONG_PTR)&QueuedLock->Value, PH_QUEUED_LOCK_OWNED_SHIFT)) + { + return TRUE; + } + else + { + return FALSE; + } +} + +PHLIBAPI +VOID +FASTCALL +PhfReleaseQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ); + +PHLIBAPI +VOID +FASTCALL +PhfWakeForReleaseQueuedLock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ); + +_Releases_exclusive_lock_(*QueuedLock) +FORCEINLINE +VOID +PhReleaseQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + + value = (ULONG_PTR)_InterlockedExchangeAddPointer((PLONG_PTR)&QueuedLock->Value, -(LONG_PTR)PH_QUEUED_LOCK_OWNED); + + if ((value & (PH_QUEUED_LOCK_WAITERS | PH_QUEUED_LOCK_TRAVERSING)) == PH_QUEUED_LOCK_WAITERS) + { + PhfWakeForReleaseQueuedLock(QueuedLock, value - PH_QUEUED_LOCK_OWNED); + } +} + +PHLIBAPI +VOID +FASTCALL +PhfReleaseQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ); + +_Releases_shared_lock_(*QueuedLock) +FORCEINLINE +VOID +PhReleaseQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + + value = PH_QUEUED_LOCK_OWNED | PH_QUEUED_LOCK_SHARED_INC; + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)0, + (PVOID)value + ) != value) + { + PhfReleaseQueuedLockShared(QueuedLock); + } +} + +FORCEINLINE +VOID +PhAcquireReleaseQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + BOOLEAN owned; + + MemoryBarrier(); + owned = !!(QueuedLock->Value & PH_QUEUED_LOCK_OWNED); + MemoryBarrier(); + + if (owned) + { + PhAcquireQueuedLockExclusive(QueuedLock); + PhReleaseQueuedLockExclusive(QueuedLock); + } +} + +FORCEINLINE +BOOLEAN +PhTryAcquireReleaseQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + BOOLEAN owned; + + // Need two memory barriers because we don't want the compiler re-ordering the following check + // in either direction. + MemoryBarrier(); + owned = !(QueuedLock->Value & PH_QUEUED_LOCK_OWNED); + MemoryBarrier(); + + return owned; +} + +// Condition variable + +typedef struct _PH_QUEUED_LOCK PH_CONDITION, *PPH_CONDITION; + +#define PH_CONDITION_INIT PH_QUEUED_LOCK_INIT + +FORCEINLINE +VOID +PhInitializeCondition( + _Out_ PPH_CONDITION Condition + ) +{ + PhInitializeQueuedLock(Condition); +} + +#define PhPulseCondition PhfPulseCondition +PHLIBAPI +VOID +FASTCALL +PhfPulseCondition( + _Inout_ PPH_CONDITION Condition + ); + +#define PhPulseAllCondition PhfPulseAllCondition +PHLIBAPI +VOID +FASTCALL +PhfPulseAllCondition( + _Inout_ PPH_CONDITION Condition + ); + +#define PhWaitForCondition PhfWaitForCondition +PHLIBAPI +VOID +FASTCALL +PhfWaitForCondition( + _Inout_ PPH_CONDITION Condition, + _Inout_ PPH_QUEUED_LOCK Lock, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#define PH_CONDITION_WAIT_QUEUED_LOCK 0x1 +#define PH_CONDITION_WAIT_CRITICAL_SECTION 0x2 +#define PH_CONDITION_WAIT_FAST_LOCK 0x4 +#define PH_CONDITION_WAIT_LOCK_TYPE_MASK 0xfff + +#define PH_CONDITION_WAIT_SHARED 0x1000 +#define PH_CONDITION_WAIT_SPIN 0x2000 + +#define PhWaitForConditionEx PhfWaitForConditionEx +PHLIBAPI +VOID +FASTCALL +PhfWaitForConditionEx( + _Inout_ PPH_CONDITION Condition, + _Inout_ PVOID Lock, + _In_ ULONG Flags, + _In_opt_ PLARGE_INTEGER Timeout + ); + +// Wake event + +typedef struct _PH_QUEUED_LOCK PH_WAKE_EVENT, *PPH_WAKE_EVENT; + +#define PH_WAKE_EVENT_INIT PH_QUEUED_LOCK_INIT + +FORCEINLINE +VOID +PhInitializeWakeEvent( + _Out_ PPH_WAKE_EVENT WakeEvent + ) +{ + PhInitializeQueuedLock(WakeEvent); +} + +#define PhQueueWakeEvent PhfQueueWakeEvent +PHLIBAPI +VOID +FASTCALL +PhfQueueWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Out_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ); + +PHLIBAPI +VOID +FASTCALL +PhfSetWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Inout_opt_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ); + +FORCEINLINE +VOID +PhSetWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Inout_opt_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ) +{ + // The wake event is similar to a synchronization event in that it does not have thread-safe + // pulsing; we can simply skip the function call if there's nothing to wake. However, if we're + // cancelling a wait (WaitBlock != NULL) we need to make the call. + + if (WakeEvent->Value || WaitBlock) + PhfSetWakeEvent(WakeEvent, WaitBlock); +} + +#define PhWaitForWakeEvent PhfWaitForWakeEvent +PHLIBAPI +NTSTATUS +FASTCALL +PhfWaitForWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock, + _In_ BOOLEAN Spin, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/ref.h b/phlib/include/ref.h new file mode 100644 index 0000000..4f3bb13 --- /dev/null +++ b/phlib/include/ref.h @@ -0,0 +1,309 @@ +/* + * Process Hacker - + * internal object manager + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef _PH_REF_H +#define _PH_REF_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Configuration + +#define PH_OBJECT_SMALL_OBJECT_SIZE 48 +#define PH_OBJECT_SMALL_OBJECT_COUNT 512 + +// Object type flags +#define PH_OBJECT_TYPE_USE_FREE_LIST 0x00000001 +#define PH_OBJECT_TYPE_VALID_FLAGS 0x00000001 + +// Object type callbacks + +/** + * The delete procedure for an object type, called when an object of the type is being freed. + * + * \param Object A pointer to the object being freed. + * \param Flags Reserved. + */ +typedef VOID (NTAPI *PPH_TYPE_DELETE_PROCEDURE)( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +struct _PH_OBJECT_TYPE; +typedef struct _PH_OBJECT_TYPE *PPH_OBJECT_TYPE; + +struct _PH_QUEUED_LOCK; +typedef struct _PH_QUEUED_LOCK PH_QUEUED_LOCK, *PPH_QUEUED_LOCK; + +#ifdef DEBUG +typedef VOID (NTAPI *PPH_CREATE_OBJECT_HOOK)( + _In_ PVOID Object, + _In_ SIZE_T Size, + _In_ ULONG Flags, + _In_ PPH_OBJECT_TYPE ObjectType + ); +#endif + +typedef struct _PH_OBJECT_TYPE_PARAMETERS +{ + SIZE_T FreeListSize; + ULONG FreeListCount; +} PH_OBJECT_TYPE_PARAMETERS, *PPH_OBJECT_TYPE_PARAMETERS; + +typedef struct _PH_OBJECT_TYPE_INFORMATION +{ + PWSTR Name; + ULONG NumberOfObjects; + USHORT Flags; + UCHAR TypeIndex; + UCHAR Reserved; +} PH_OBJECT_TYPE_INFORMATION, *PPH_OBJECT_TYPE_INFORMATION; + +extern PPH_OBJECT_TYPE PhObjectTypeObject; +extern PPH_OBJECT_TYPE PhAllocType; + +#ifdef DEBUG +extern LIST_ENTRY PhDbgObjectListHead; +extern PH_QUEUED_LOCK PhDbgObjectListLock; +extern PPH_CREATE_OBJECT_HOOK PhDbgCreateObjectHook; +#endif + +NTSTATUS PhRefInitialization( + VOID + ); + +_May_raise_ +PHLIBAPI +PVOID +NTAPI +PhCreateObject( + _In_ SIZE_T ObjectSize, + _In_ PPH_OBJECT_TYPE ObjectType + ); + +PHLIBAPI +PVOID +NTAPI +PhReferenceObject( + _In_ PVOID Object + ); + +_May_raise_ +PHLIBAPI +PVOID +NTAPI +PhReferenceObjectEx( + _In_ PVOID Object, + _In_ LONG RefCount + ); + +PHLIBAPI +PVOID +NTAPI +PhReferenceObjectSafe( + _In_ PVOID Object + ); + +PHLIBAPI +VOID +NTAPI +PhDereferenceObject( + _In_ PVOID Object + ); + +PHLIBAPI +VOID +NTAPI +PhDereferenceObjectDeferDelete( + _In_ PVOID Object + ); + +_May_raise_ +PHLIBAPI +VOID +NTAPI +PhDereferenceObjectEx( + _In_ PVOID Object, + _In_ LONG RefCount, + _In_ BOOLEAN DeferDelete + ); + +PHLIBAPI +PPH_OBJECT_TYPE +NTAPI +PhGetObjectType( + _In_ PVOID Object + ); + +PHLIBAPI +PPH_OBJECT_TYPE +NTAPI +PhCreateObjectType( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ PPH_TYPE_DELETE_PROCEDURE DeleteProcedure + ); + +PHLIBAPI +PPH_OBJECT_TYPE +NTAPI +PhCreateObjectTypeEx( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ PPH_TYPE_DELETE_PROCEDURE DeleteProcedure, + _In_opt_ PPH_OBJECT_TYPE_PARAMETERS Parameters + ); + +PHLIBAPI +VOID +NTAPI +PhGetObjectTypeInformation( + _In_ PPH_OBJECT_TYPE ObjectType, + _Out_ PPH_OBJECT_TYPE_INFORMATION Information + ); + +PHLIBAPI +PVOID +NTAPI +PhCreateAlloc( + _In_ SIZE_T Size + ); + +// Object reference functions + +FORCEINLINE +VOID +PhSwapReference( + _Inout_ PVOID *ObjectReference, + _In_opt_ PVOID NewObject + ) +{ + PVOID oldObject; + + oldObject = *ObjectReference; + *ObjectReference = NewObject; + + if (NewObject) PhReferenceObject(NewObject); + if (oldObject) PhDereferenceObject(oldObject); +} + +FORCEINLINE +VOID +PhMoveReference( + _Inout_ PVOID *ObjectReference, + _In_opt_ _Assume_refs_(1) PVOID NewObject + ) +{ + PVOID oldObject; + + oldObject = *ObjectReference; + *ObjectReference = NewObject; + + if (oldObject) PhDereferenceObject(oldObject); +} + +FORCEINLINE +VOID +PhSetReference( + _Out_ PVOID *ObjectReference, + _In_opt_ PVOID NewObject + ) +{ + *ObjectReference = NewObject; + + if (NewObject) PhReferenceObject(NewObject); +} + +FORCEINLINE +VOID +PhClearReference( + _Inout_ PVOID *ObjectReference + ) +{ + PhMoveReference(ObjectReference, NULL); +} + +// Auto-dereference pool + +/** The size of the static array in an auto-release pool. */ +#define PH_AUTO_POOL_STATIC_SIZE 64 +/** The maximum size of the dynamic array for it to be kept after the auto-release pool is drained. */ +#define PH_AUTO_POOL_DYNAMIC_BIG_SIZE 256 + +/** + * An auto-dereference pool can be used for semi-automatic reference counting. Batches of objects + * are dereferenced at a certain time. + * + * This object is not thread-safe and cannot be used across thread boundaries. Always store them as + * local variables. + */ +typedef struct _PH_AUTO_POOL +{ + ULONG StaticCount; + PVOID StaticObjects[PH_AUTO_POOL_STATIC_SIZE]; + + ULONG DynamicCount; + ULONG DynamicAllocated; + PVOID *DynamicObjects; + + struct _PH_AUTO_POOL *NextPool; +} PH_AUTO_POOL, *PPH_AUTO_POOL; + +PHLIBAPI +VOID +NTAPI +PhInitializeAutoPool( + _Out_ PPH_AUTO_POOL AutoPool + ); + +_May_raise_ +PHLIBAPI +VOID +NTAPI +PhDeleteAutoPool( + _Inout_ PPH_AUTO_POOL AutoPool + ); + +PHLIBAPI +VOID +NTAPI +PhDrainAutoPool( + _In_ PPH_AUTO_POOL AutoPool + ); + +_May_raise_ +PHLIBAPI +PVOID +NTAPI +PhAutoDereferenceObject( + _In_opt_ PVOID Object + ); + +#define PH_AUTO PhAutoDereferenceObject +#define PH_AUTO_T(Type, Object) ((Type *)PH_AUTO(Object)) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/refp.h b/phlib/include/refp.h new file mode 100644 index 0000000..dfa9478 --- /dev/null +++ b/phlib/include/refp.h @@ -0,0 +1,175 @@ +/* + * Process Hacker - + * internal object manager + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef _PH_REFP_H +#define _PH_REFP_H + +#define PH_OBJECT_TYPE_TABLE_SIZE 256 + +/** The object was allocated from the small free list. */ +#define PH_OBJECT_FROM_SMALL_FREE_LIST 0x1 +/** The object was allocated from the type free list. */ +#define PH_OBJECT_FROM_TYPE_FREE_LIST 0x2 + +/** + * The object header contains object manager information including the reference count of an object + * and its type. + */ +typedef struct _PH_OBJECT_HEADER +{ + union + { + struct + { + USHORT TypeIndex; + UCHAR Flags; + UCHAR Reserved1; +#ifdef _WIN64 + ULONG Reserved2; +#endif + union + { + LONG RefCount; + struct + { + LONG SavedTypeIndex : 16; + LONG SavedFlags : 8; + LONG Reserved : 7; + LONG DeferDelete : 1; // MUST be the high bit, so that RefCount < 0 when deferring delete + }; + }; +#ifdef _WIN64 + ULONG Reserved3; +#endif + }; + SLIST_ENTRY DeferDeleteListEntry; + }; + +#ifdef DEBUG + PVOID StackBackTrace[16]; + LIST_ENTRY ObjectListEntry; +#endif + + /** + * The body of the object. For use by the \ref PhObjectToObjectHeader and + * \ref PhObjectHeaderToObject macros. + */ + QUAD_PTR Body; +} PH_OBJECT_HEADER, *PPH_OBJECT_HEADER; + +#ifndef DEBUG +#ifdef _WIN64 +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, TypeIndex) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Flags) == 0x2); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Reserved1) == 0x3); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Reserved2) == 0x4); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, RefCount) == 0x8); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Reserved3) == 0xc); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, DeferDeleteListEntry) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Body) == 0x10); +#else +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, TypeIndex) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Flags) == 0x2); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Reserved1) == 0x3); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, RefCount) == 0x4); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, DeferDeleteListEntry) == 0x0); +C_ASSERT(FIELD_OFFSET(PH_OBJECT_HEADER, Body) == 0x8); +#endif +#endif + +/** + * Gets a pointer to the object header for an object. + * + * \param Object A pointer to an object. + * + * \return A pointer to the object header of the object. + */ +#define PhObjectToObjectHeader(Object) ((PPH_OBJECT_HEADER)CONTAINING_RECORD((PCHAR)(Object), PH_OBJECT_HEADER, Body)) + +/** + * Gets a pointer to an object from an object header. + * + * \param ObjectHeader A pointer to an object header. + * + * \return A pointer to an object. + */ +#define PhObjectHeaderToObject(ObjectHeader) ((PVOID)&((PPH_OBJECT_HEADER)(ObjectHeader))->Body) + +/** + * Calculates the total size to allocate for an object. + * + * \param Size The size of the object to allocate. + * + * \return The new size, including space for the object header. + */ +#define PhAddObjectHeaderSize(Size) ((Size) + FIELD_OFFSET(PH_OBJECT_HEADER, Body)) + +/** An object type specifies a kind of object and its delete procedure. */ +typedef struct _PH_OBJECT_TYPE +{ + /** The flags that were used to create the object type. */ + USHORT Flags; + UCHAR TypeIndex; + UCHAR Reserved; + /** The total number of objects of this type that are alive. */ + ULONG NumberOfObjects; + /** An optional procedure called when objects of this type are freed. */ + PPH_TYPE_DELETE_PROCEDURE DeleteProcedure; + /** The name of the type. */ + PWSTR Name; + /** A free list to use when allocating for this type. */ + PH_FREE_LIST FreeList; +} PH_OBJECT_TYPE, *PPH_OBJECT_TYPE; + +/** + * Increments a reference count, but will never increment from a nonpositive value to 1. + * + * \param RefCount A pointer to a reference count. + */ +FORCEINLINE +BOOLEAN +PhpInterlockedIncrementSafe( + _Inout_ PLONG RefCount + ) +{ + /* Here we will attempt to increment the reference count, making sure that it is positive. */ + return _InterlockedIncrementPositive(RefCount); +} + +PPH_OBJECT_HEADER PhpAllocateObject( + _In_ PPH_OBJECT_TYPE ObjectType, + _In_ SIZE_T ObjectSize + ); + +VOID PhpFreeObject( + _In_ PPH_OBJECT_HEADER ObjectHeader + ); + +VOID PhpDeferDeleteObject( + _In_ PPH_OBJECT_HEADER ObjectHeader + ); + +NTSTATUS PhpDeferDeleteObjectRoutine( + _In_ PVOID Parameter + ); + +#endif diff --git a/phlib/include/secedit.h b/phlib/include/secedit.h new file mode 100644 index 0000000..eae2580 --- /dev/null +++ b/phlib/include/secedit.h @@ -0,0 +1,163 @@ +#ifndef _PH_SECEDIT_H +#define _PH_SECEDIT_H + +#ifdef __cplusplus +extern "C" { +#endif + +// secedit + +typedef struct _PH_ACCESS_ENTRY +{ + PWSTR Name; + ACCESS_MASK Access; + BOOLEAN General; + BOOLEAN Specific; + PWSTR ShortName; +} PH_ACCESS_ENTRY, *PPH_ACCESS_ENTRY; + +PHLIBAPI +HPROPSHEETPAGE +NTAPI +PhCreateSecurityPage( + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ); + +PHLIBAPI +VOID +NTAPI +PhEditSecurity( + _In_ HWND hWnd, + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ); + +typedef struct _PH_STD_OBJECT_SECURITY +{ + PPH_OPEN_OBJECT OpenObject; + PWSTR ObjectType; + PVOID Context; +} PH_STD_OBJECT_SECURITY, *PPH_STD_OBJECT_SECURITY; + +FORCEINLINE ACCESS_MASK PhGetAccessForGetSecurity( + _In_ SECURITY_INFORMATION SecurityInformation + ) +{ + ACCESS_MASK access = 0; + + if ( + (SecurityInformation & OWNER_SECURITY_INFORMATION) || + (SecurityInformation & GROUP_SECURITY_INFORMATION) || + (SecurityInformation & DACL_SECURITY_INFORMATION) + ) + { + access |= READ_CONTROL; + } + + if (SecurityInformation & SACL_SECURITY_INFORMATION) + { + access |= ACCESS_SYSTEM_SECURITY; + } + + return access; +} + +FORCEINLINE ACCESS_MASK PhGetAccessForSetSecurity( + _In_ SECURITY_INFORMATION SecurityInformation + ) +{ + ACCESS_MASK access = 0; + + if ( + (SecurityInformation & OWNER_SECURITY_INFORMATION) || + (SecurityInformation & GROUP_SECURITY_INFORMATION) + ) + { + access |= WRITE_OWNER; + } + + if (SecurityInformation & DACL_SECURITY_INFORMATION) + { + access |= WRITE_DAC; + } + + if (SecurityInformation & SACL_SECURITY_INFORMATION) + { + access |= ACCESS_SYSTEM_SECURITY; + } + + return access; +} + +PHLIBAPI +_Callback_ NTSTATUS +NTAPI +PhStdGetObjectSecurity( + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ); + +PHLIBAPI +_Callback_ NTSTATUS +NTAPI +PhStdSetObjectSecurity( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetSeObjectSecurity( + _In_ HANDLE Handle, + _In_ ULONG ObjectType, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhSetSeObjectSecurity( + _In_ HANDLE Handle, + _In_ ULONG ObjectType, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +// secdata + +PHLIBAPI +BOOLEAN +NTAPI +PhGetAccessEntries( + _In_ PWSTR Type, + _Out_ PPH_ACCESS_ENTRY *AccessEntries, + _Out_ PULONG NumberOfAccessEntries + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetAccessString( + _In_ ACCESS_MASK Access, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/phlib/include/seceditp.h b/phlib/include/seceditp.h new file mode 100644 index 0000000..57b4f54 --- /dev/null +++ b/phlib/include/seceditp.h @@ -0,0 +1,100 @@ +#ifndef _PH_SECEDITP_H +#define _PH_SECEDITP_H + +#include +#include + +typedef struct +{ + ISecurityInformationVtbl *VTable; + + ULONG RefCount; + + PPH_STRING ObjectName; + PPH_GET_OBJECT_SECURITY GetObjectSecurity; + PPH_SET_OBJECT_SECURITY SetObjectSecurity; + PVOID Context; + PSI_ACCESS AccessEntries; + ULONG NumberOfAccessEntries; +} PhSecurityInformation; + +ISecurityInformation *PhSecurityInformation_Create( + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_QueryInterface( + _In_ ISecurityInformation *This, + _In_ REFIID Riid, + _Out_ PVOID *Object + ); + +ULONG STDMETHODCALLTYPE PhSecurityInformation_AddRef( + _In_ ISecurityInformation *This + ); + +ULONG STDMETHODCALLTYPE PhSecurityInformation_Release( + _In_ ISecurityInformation *This + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetObjectInformation( + _In_ ISecurityInformation *This, + _Out_ PSI_OBJECT_INFO ObjectInfo + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetSecurity( + _In_ ISecurityInformation *This, + _In_ SECURITY_INFORMATION RequestedInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, + _In_ BOOL Default + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_SetSecurity( + _In_ ISecurityInformation *This, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetAccessRights( + _In_ ISecurityInformation *This, + _In_ const GUID *ObjectType, + _In_ ULONG Flags, + _Out_ PSI_ACCESS *Access, + _Out_ PULONG Accesses, + _Out_ PULONG DefaultAccess + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_MapGeneric( + _In_ ISecurityInformation *This, + _In_ const GUID *ObjectType, + _In_ PUCHAR AceFlags, + _Inout_ PACCESS_MASK Mask + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetInheritTypes( + _In_ ISecurityInformation *This, + _Out_ PSI_INHERIT_TYPE *InheritTypes, + _Out_ PULONG InheritTypesCount + ); + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_PropertySheetPageCallback( + _In_ ISecurityInformation *This, + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ SI_PAGE_TYPE uPage + ); + +typedef HPROPSHEETPAGE (WINAPI *_CreateSecurityPage)( + _In_ LPSECURITYINFO psi + ); + +typedef BOOL (WINAPI *_EditSecurity)( + _In_ HWND hwndOwner, + _In_ LPSECURITYINFO psi + ); + +#endif diff --git a/phlib/include/svcsup.h b/phlib/include/svcsup.h new file mode 100644 index 0000000..ab3b943 --- /dev/null +++ b/phlib/include/svcsup.h @@ -0,0 +1,146 @@ +#ifndef _PH_SVCSUP_H +#define _PH_SVCSUP_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern WCHAR *PhServiceTypeStrings[10]; +extern WCHAR *PhServiceStartTypeStrings[5]; +extern WCHAR *PhServiceErrorControlStrings[4]; + +PHLIBAPI +PVOID +NTAPI +PhEnumServices( + _In_ SC_HANDLE ScManagerHandle, + _In_opt_ ULONG Type, + _In_opt_ ULONG State, + _Out_ PULONG Count + ); + +PHLIBAPI +SC_HANDLE +NTAPI +PhOpenService( + _In_ PWSTR ServiceName, + _In_ ACCESS_MASK DesiredAccess + ); + +PHLIBAPI +PVOID +NTAPI +PhGetServiceConfig( + _In_ SC_HANDLE ServiceHandle + ); + +PHLIBAPI +PVOID +NTAPI +PhQueryServiceVariableSize( + _In_ SC_HANDLE ServiceHandle, + _In_ ULONG InfoLevel + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetServiceDescription( + _In_ SC_HANDLE ServiceHandle + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhGetServiceDelayedAutoStart( + _In_ SC_HANDLE ServiceHandle, + _Out_ PBOOLEAN DelayedAutoStart + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhSetServiceDelayedAutoStart( + _In_ SC_HANDLE ServiceHandle, + _In_ BOOLEAN DelayedAutoStart + ); + +PHLIBAPI +PWSTR +NTAPI +PhGetServiceStateString( + _In_ ULONG ServiceState + ); + +PHLIBAPI +PWSTR +NTAPI +PhGetServiceTypeString( + _In_ ULONG ServiceType + ); + +PHLIBAPI +ULONG +NTAPI +PhGetServiceTypeInteger( + _In_ PWSTR ServiceType + ); + +PHLIBAPI +PWSTR +NTAPI +PhGetServiceStartTypeString( + _In_ ULONG ServiceStartType + ); + +PHLIBAPI +ULONG +NTAPI +PhGetServiceStartTypeInteger( + _In_ PWSTR ServiceStartType + ); + +PHLIBAPI +PWSTR +NTAPI +PhGetServiceErrorControlString( + _In_ ULONG ServiceErrorControl + ); + +PHLIBAPI +ULONG +NTAPI +PhGetServiceErrorControlInteger( + _In_ PWSTR ServiceErrorControl + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetServiceNameFromTag( + _In_ HANDLE ProcessId, + _In_ PVOID ServiceTag + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetThreadServiceTag( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE ProcessHandle, + _Out_ PVOID *ServiceTag + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhGetServiceDllParameter( + _In_ PPH_STRINGREF ServiceName, + _Out_ PPH_STRING *ServiceDll + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/symprv.h b/phlib/include/symprv.h new file mode 100644 index 0000000..4d02924 --- /dev/null +++ b/phlib/include/symprv.h @@ -0,0 +1,303 @@ +#ifndef _PH_SYMPRV_H +#define _PH_SYMPRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern PPH_OBJECT_TYPE PhSymbolProviderType; +extern PH_CALLBACK PhSymInitCallback; + +#define PH_MAX_SYMBOL_NAME_LEN 128 + +typedef struct _PH_SYMBOL_PROVIDER +{ + LIST_ENTRY ModulesListHead; + PH_QUEUED_LOCK ModulesListLock; + HANDLE ProcessHandle; + BOOLEAN IsRealHandle; + BOOLEAN IsRegistered; + + PH_INITONCE InitOnce; + PH_AVL_TREE ModulesSet; + PH_CALLBACK EventCallback; +} PH_SYMBOL_PROVIDER, *PPH_SYMBOL_PROVIDER; + +typedef enum _PH_SYMBOL_RESOLVE_LEVEL +{ + PhsrlFunction, + PhsrlModule, + PhsrlAddress, + PhsrlInvalid +} PH_SYMBOL_RESOLVE_LEVEL, *PPH_SYMBOL_RESOLVE_LEVEL; + +typedef struct _PH_SYMBOL_INFORMATION +{ + ULONG64 Address; + ULONG64 ModuleBase; + ULONG Index; + ULONG Size; +} PH_SYMBOL_INFORMATION, *PPH_SYMBOL_INFORMATION; + +typedef struct _PH_SYMBOL_LINE_INFORMATION +{ + ULONG LineNumber; + ULONG64 Address; +} PH_SYMBOL_LINE_INFORMATION, *PPH_SYMBOL_LINE_INFORMATION; + +typedef enum _PH_SYMBOL_EVENT_TYPE +{ + SymbolDeferredSymbolLoadStart = 1, + SymbolDeferredSymbolLoadComplete = 2, + SymbolDeferredSymbolLoadFailure = 3, + SymbolSymbolsUnloaded = 4, + SymbolDeferredSymbolLoadCancel = 7 +} PH_SYMBOL_EVENT_TYPE; + +typedef struct _PH_SYMBOL_EVENT_DATA +{ + PPH_SYMBOL_PROVIDER SymbolProvider; + PH_SYMBOL_EVENT_TYPE Type; + + ULONG64 BaseAddress; + ULONG CheckSum; + ULONG TimeStamp; + PPH_STRING FileName; +} PH_SYMBOL_EVENT_DATA, *PPH_SYMBOL_EVENT_DATA; + +PHLIBAPI +BOOLEAN +NTAPI +PhSymbolProviderInitialization( + VOID + ); + +PHLIBAPI +VOID +NTAPI +PhSymbolProviderCompleteInitialization( + _In_opt_ PVOID DbgHelpBase + ); + +PHLIBAPI +PPH_SYMBOL_PROVIDER +NTAPI +PhCreateSymbolProvider( + _In_opt_ HANDLE ProcessId + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhGetLineFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_ PPH_STRING *FileName, + _Out_opt_ PULONG Displacement, + _Out_opt_ PPH_SYMBOL_LINE_INFORMATION Information + ); + +PHLIBAPI +ULONG64 +NTAPI +PhGetModuleFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_opt_ PPH_STRING *FileName + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetSymbolFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_opt_ PPH_SYMBOL_RESOLVE_LEVEL ResolveLevel, + _Out_opt_ PPH_STRING *FileName, + _Out_opt_ PPH_STRING *SymbolName, + _Out_opt_ PULONG64 Displacement + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhGetSymbolFromName( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR Name, + _Out_ PPH_SYMBOL_INFORMATION Information + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhLoadModuleSymbolProvider( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR FileName, + _In_ ULONG64 BaseAddress, + _In_ ULONG Size + ); + +PHLIBAPI +VOID +NTAPI +PhSetOptionsSymbolProvider( + _In_ ULONG Mask, + _In_ ULONG Value + ); + +PHLIBAPI +VOID +NTAPI +PhSetSearchPathSymbolProvider( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR Path + ); + +#ifdef _WIN64 +PHLIBAPI +NTSTATUS +NTAPI +PhAccessOutOfProcessFunctionEntry( + _In_ HANDLE ProcessHandle, + _In_ ULONG64 ControlPc, + _Out_ PRUNTIME_FUNCTION Function + ); +#endif + +PHLIBAPI +ULONG64 +__stdcall +PhGetModuleBase64( + _In_ HANDLE hProcess, + _In_ DWORD64 dwAddr + ); + +PHLIBAPI +PVOID +__stdcall +PhFunctionTableAccess64( + _In_ HANDLE hProcess, + _In_ DWORD64 AddrBase + ); + +#ifndef _DBGHELP_ + +// Some of the types used below are defined in dbghelp.h. + +typedef struct _tagSTACKFRAME64 *LPSTACKFRAME64; +typedef struct _tagADDRESS64 *LPADDRESS64; + +typedef BOOL (__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)( + _In_ HANDLE hProcess, + _In_ DWORD64 qwBaseAddress, + _Out_writes_bytes_(nSize) PVOID lpBuffer, + _In_ DWORD nSize, + _Out_ LPDWORD lpNumberOfBytesRead + ); + +typedef PVOID (__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)( + _In_ HANDLE ahProcess, + _In_ DWORD64 AddrBase + ); + +typedef DWORD64 (__stdcall *PGET_MODULE_BASE_ROUTINE64)( + _In_ HANDLE hProcess, + _In_ DWORD64 Address + ); + +typedef DWORD64 (__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)( + _In_ HANDLE hProcess, + _In_ HANDLE hThread, + _In_ LPADDRESS64 lpaddr + ); + +typedef enum _MINIDUMP_TYPE MINIDUMP_TYPE; +typedef struct _MINIDUMP_EXCEPTION_INFORMATION *PMINIDUMP_EXCEPTION_INFORMATION; +typedef struct _MINIDUMP_USER_STREAM_INFORMATION *PMINIDUMP_USER_STREAM_INFORMATION; +typedef struct _MINIDUMP_CALLBACK_INFORMATION *PMINIDUMP_CALLBACK_INFORMATION; + +#endif + +PHLIBAPI +BOOLEAN +NTAPI +PhStackWalk( + _In_ ULONG MachineType, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ThreadHandle, + _Inout_ LPSTACKFRAME64 StackFrame, + _Inout_ PVOID ContextRecord, + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ); + +PHLIBAPI +BOOLEAN +NTAPI +PhWriteMiniDumpProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE FileHandle, + _In_ MINIDUMP_TYPE DumpType, + _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +// High-level stack walking + +#define PH_THREAD_STACK_FRAME_I386 0x1 +#define PH_THREAD_STACK_FRAME_AMD64 0x2 +#define PH_THREAD_STACK_FRAME_KERNEL 0x4 +#define PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT 0x100 + +/** Contains information about a thread stack frame. */ +typedef struct _PH_THREAD_STACK_FRAME +{ + PVOID PcAddress; + PVOID ReturnAddress; + PVOID FrameAddress; + PVOID StackAddress; + PVOID BStoreAddress; + PVOID Params[4]; + ULONG Flags; +} PH_THREAD_STACK_FRAME, *PPH_THREAD_STACK_FRAME; + +#define PH_WALK_I386_STACK 0x1 +#define PH_WALK_AMD64_STACK 0x2 +#define PH_WALK_KERNEL_STACK 0x10 + +/** + * A callback function passed to PhWalkThreadStack() and called for each stack frame. + * + * \param StackFrame A structure providing information about the stack frame. + * \param Context A user-defined value passed to PhWalkThreadStack(). + * + * \return TRUE to continue the stack walk, FALSE to stop. + */ +typedef BOOLEAN (NTAPI *PPH_WALK_THREAD_STACK_CALLBACK)( + _In_ PPH_THREAD_STACK_FRAME StackFrame, + _In_opt_ PVOID Context + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhWalkThreadStack( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE ProcessHandle, + _In_opt_ PCLIENT_ID ClientId, + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG Flags, + _In_ PPH_WALK_THREAD_STACK_CALLBACK Callback, + _In_opt_ PVOID Context + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/symprvp.h b/phlib/include/symprvp.h new file mode 100644 index 0000000..e4435bf --- /dev/null +++ b/phlib/include/symprvp.h @@ -0,0 +1,170 @@ +#ifndef _PH_SYMPRVP_H +#define _PH_SYMPRVP_H + +typedef BOOL (WINAPI *_SymInitialize)( + _In_ HANDLE hProcess, + _In_opt_ PCSTR UserSearchPath, + _In_ BOOL fInvadeProcess + ); + +typedef BOOL (WINAPI *_SymCleanup)( + _In_ HANDLE hProcess + ); + +typedef BOOL (WINAPI *_SymEnumSymbols)( + _In_ HANDLE hProcess, + _In_ ULONG64 BaseOfDll, + _In_opt_ PCSTR Mask, + _In_ PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + _In_opt_ const PVOID UserContext + ); + +typedef BOOL (WINAPI *_SymEnumSymbolsW)( + _In_ HANDLE hProcess, + _In_ ULONG64 BaseOfDll, + _In_opt_ PCWSTR Mask, + _In_ PSYM_ENUMERATESYMBOLS_CALLBACKW EnumSymbolsCallback, + _In_opt_ const PVOID UserContext + ); + +typedef BOOL (WINAPI *_SymFromAddr)( + _In_ HANDLE hProcess, + _In_ DWORD64 Address, + _Out_opt_ PDWORD64 Displacement, + _Inout_ PSYMBOL_INFO Symbol + ); + +typedef BOOL (WINAPI *_SymFromAddrW)( + _In_ HANDLE hProcess, + _In_ DWORD64 Address, + _Out_opt_ PDWORD64 Displacement, + _Inout_ PSYMBOL_INFOW Symbol + ); + +typedef BOOL (WINAPI *_SymFromName)( + _In_ HANDLE hProcess, + _In_ PCSTR Name, + _Inout_ PSYMBOL_INFO Symbol + ); + +typedef BOOL (WINAPI *_SymFromNameW)( + _In_ HANDLE hProcess, + _In_ PCWSTR Name, + _Inout_ PSYMBOL_INFOW Symbol + ); + +typedef BOOL (WINAPI *_SymGetLineFromAddr64)( + _In_ HANDLE hProcess, + _In_ DWORD64 dwAddr, + _Out_ PDWORD pdwDisplacement, + _Out_ PIMAGEHLP_LINE64 Line + ); + +typedef BOOL (WINAPI *_SymGetLineFromAddrW64)( + _In_ HANDLE hProcess, + _In_ DWORD64 dwAddr, + _Out_ PDWORD pdwDisplacement, + _Out_ PIMAGEHLP_LINEW64 Line + ); + +typedef DWORD64 (WINAPI *_SymLoadModule64)( + _In_ HANDLE hProcess, + _In_opt_ HANDLE hFile, + _In_opt_ PCSTR ImageName, + _In_opt_ PCSTR ModuleName, + _In_ DWORD64 BaseOfDll, + _In_ DWORD SizeOfDll + ); + +typedef DWORD64 (WINAPI *_SymLoadModuleExW)( + _In_ HANDLE hProcess, + _In_ HANDLE hFile, + _In_ PCWSTR ImageName, + _In_ PCWSTR ModuleName, + _In_ DWORD64 BaseOfDll, + _In_ DWORD DllSize, + _In_ PMODLOAD_DATA Data, + _In_ DWORD Flags + ); + +typedef DWORD (WINAPI *_SymGetOptions)(); + +typedef DWORD (WINAPI *_SymSetOptions)( + _In_ DWORD SymOptions + ); + +typedef BOOL (WINAPI *_SymGetSearchPath)( + _In_ HANDLE hProcess, + _Out_ PSTR SearchPath, + _In_ DWORD SearchPathLength + ); + +typedef BOOL (WINAPI *_SymGetSearchPathW)( + _In_ HANDLE hProcess, + _Out_ PWSTR SearchPath, + _In_ DWORD SearchPathLength + ); + +typedef BOOL (WINAPI *_SymSetSearchPath)( + _In_ HANDLE hProcess, + _In_opt_ PCSTR SearchPath + ); + +typedef BOOL (WINAPI *_SymSetSearchPathW)( + _In_ HANDLE hProcess, + _In_opt_ PCWSTR SearchPath + ); + +typedef BOOL (WINAPI *_SymUnloadModule64)( + _In_ HANDLE hProcess, + _In_ DWORD64 BaseOfDll + ); + +typedef PVOID (WINAPI *_SymFunctionTableAccess64)( + _In_ HANDLE hProcess, + _In_ DWORD64 AddrBase + ); + +typedef DWORD64 (WINAPI *_SymGetModuleBase64)( + _In_ HANDLE hProcess, + _In_ DWORD64 dwAddr + ); + +typedef BOOL (WINAPI *_SymRegisterCallbackW64)( + _In_ HANDLE hProcess, + _In_ PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction, + _In_ ULONG64 UserContext + ); + +typedef BOOL (WINAPI *_StackWalk64)( + _In_ DWORD MachineType, + _In_ HANDLE hProcess, + _In_ HANDLE hThread, + _Inout_ LPSTACKFRAME64 StackFrame, + _Inout_ PVOID ContextRecord, + _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ); + +typedef BOOL (WINAPI *_MiniDumpWriteDump)( + _In_ HANDLE hProcess, + _In_ DWORD ProcessId, + _In_ HANDLE hFile, + _In_ MINIDUMP_TYPE DumpType, + _In_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + _In_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + _In_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +typedef UINT_PTR (CALLBACK *_SymbolServerGetOptions)( + VOID + ); + +typedef BOOL (CALLBACK *_SymbolServerSetOptions)( + _In_ UINT_PTR options, + _In_ ULONG64 data + ); + +#endif \ No newline at end of file diff --git a/phlib/include/templ.h b/phlib/include/templ.h new file mode 100644 index 0000000..7cff6a1 --- /dev/null +++ b/phlib/include/templ.h @@ -0,0 +1,7 @@ +#ifndef _PH_TEMPL_H +#define _PH_TEMPL_H + +#define TEMPLATE_(f,T) f##_##T +#define T___(f,T) TEMPLATE_(f,T) + +#endif diff --git a/phlib/include/treenew.h b/phlib/include/treenew.h new file mode 100644 index 0000000..51e2425 --- /dev/null +++ b/phlib/include/treenew.h @@ -0,0 +1,672 @@ +#ifndef _PH_TREENEW_H +#define _PH_TREENEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_TREENEW_CLASSNAME L"PhTreeNew" + +#define PH_TREENEW_SEARCH_TIMEOUT 1000 +#define PH_TREENEW_SEARCH_MAXIMUM_LENGTH 1023 + +typedef struct _PH_TREENEW_COLUMN +{ + union + { + ULONG Flags; + struct + { + ULONG Visible : 1; + ULONG CustomDraw : 1; + ULONG Fixed : 1; // Whether this is the fixed column + ULONG SortDescending : 1; // Sort descending on initial click rather than ascending + ULONG DpiScaleOnAdd : 1; // Whether to DPI scale the width (only when adding) + ULONG SpareFlags : 27; + }; + }; + ULONG Id; + PVOID Context; + PWSTR Text; + LONG Width; + ULONG Alignment; + ULONG DisplayIndex; // -1 for fixed column or invalid + + ULONG TextFlags; + + struct + { + LONG ViewIndex; // Actual index in header control + LONG ViewX; // 0 for the fixed column, and an offset from the divider for normal columns + } s; +} PH_TREENEW_COLUMN, *PPH_TREENEW_COLUMN; + +typedef struct _PH_TREENEW_NODE +{ + union + { + ULONG Flags; + struct + { + ULONG Visible : 1; + ULONG Selected : 1; + ULONG Expanded : 1; + ULONG UseAutoForeColor : 1; + ULONG UseTempBackColor : 1; + ULONG Unselectable : 1; + ULONG SpareFlags : 26; + }; + }; + + COLORREF BackColor; + COLORREF ForeColor; + COLORREF TempBackColor; + HFONT Font; + HICON Icon; + + PPH_STRINGREF TextCache; + ULONG TextCacheSize; + + ULONG Index; // Index within the flat list + ULONG Level; // 0 for root, 1, 2, ... + + struct + { + union + { + ULONG Flags2; + struct + { + ULONG IsLeaf : 1; + ULONG CachedColorValid : 1; + ULONG CachedFontValid : 1; + ULONG CachedIconValid : 1; + ULONG PlusMinusHot : 1; + ULONG SpareFlags2 : 27; + }; + }; + + // Temp. drawing data + COLORREF DrawBackColor; + COLORREF DrawForeColor; + } s; +} PH_TREENEW_NODE, *PPH_TREENEW_NODE; + +// Styles +#define TN_STYLE_ICONS 0x1 +#define TN_STYLE_DOUBLE_BUFFERED 0x2 +#define TN_STYLE_NO_DIVIDER 0x4 +#define TN_STYLE_ANIMATE_DIVIDER 0x8 +#define TN_STYLE_NO_COLUMN_SORT 0x10 +#define TN_STYLE_NO_COLUMN_REORDER 0x20 +#define TN_STYLE_THIN_ROWS 0x40 +#define TN_STYLE_NO_COLUMN_HEADER 0x80 + +// Extended flags +#define TN_FLAG_ITEM_DRAG_SELECT 0x1 +#define TN_FLAG_NO_UNFOLDING_TOOLTIPS 0x2 + +// Callback flags +#define TN_CACHE 0x1 +#define TN_AUTO_FORECOLOR 0x1000 + +// Column change flags +#define TN_COLUMN_CONTEXT 0x1 +#define TN_COLUMN_TEXT 0x2 +#define TN_COLUMN_WIDTH 0x4 +#define TN_COLUMN_ALIGNMENT 0x8 +#define TN_COLUMN_DISPLAYINDEX 0x10 +#define TN_COLUMN_TEXTFLAGS 0x20 +#define TN_COLUMN_FLAG_VISIBLE 0x100000 +#define TN_COLUMN_FLAG_CUSTOMDRAW 0x200000 +#define TN_COLUMN_FLAG_FIXED 0x400000 +#define TN_COLUMN_FLAG_SORTDESCENDING 0x800000 +#define TN_COLUMN_FLAG_NODPISCALEONADD 0x1000000 +#define TN_COLUMN_FLAGS 0xfff00000 + +// Cache flags +#define TN_CACHE_COLOR 0x1 +#define TN_CACHE_FONT 0x2 +#define TN_CACHE_ICON 0x4 + +// Cell part input flags +#define TN_MEASURE_TEXT 0x1 + +// Cell part flags +#define TN_PART_CELL 0x1 +#define TN_PART_PLUSMINUS 0x2 +#define TN_PART_ICON 0x4 +#define TN_PART_CONTENT 0x8 +#define TN_PART_TEXT 0x10 + +// Hit test input flags +#define TN_TEST_COLUMN 0x1 +#define TN_TEST_SUBITEM 0x2 // requires TN_TEST_COLUMN + +// Hit test flags +#define TN_HIT_LEFT 0x1 +#define TN_HIT_RIGHT 0x2 +#define TN_HIT_ABOVE 0x4 +#define TN_HIT_BELOW 0x8 +#define TN_HIT_ITEM 0x10 +#define TN_HIT_ITEM_PLUSMINUS 0x20 // requires TN_TEST_SUBITEM +#define TN_HIT_ITEM_ICON 0x40 // requires TN_TEST_SUBITEM +#define TN_HIT_ITEM_CONTENT 0x80 // requires TN_TEST_SUBITEM +#define TN_HIT_DIVIDER 0x100 + +// Selection flags +#define TN_SELECT_DESELECT 0x1 +#define TN_SELECT_TOGGLE 0x2 +#define TN_SELECT_RESET 0x4 + +// Auto-size flags +#define TN_AUTOSIZE_REMAINING_SPACE 0x1 + +typedef struct _PH_TREENEW_CELL_PARTS +{ + ULONG Flags; + RECT RowRect; + RECT CellRect; // TN_PART_CELL + RECT PlusMinusRect; // TN_PART_PLUSMINUS + RECT IconRect; // TN_PART_ICON + RECT ContentRect; // TN_PART_CONTENT + RECT TextRect; // TN_PART_TEXT + PH_STRINGREF Text; // TN_PART_TEXT + HFONT Font; // TN_PART_TEXT +} PH_TREENEW_CELL_PARTS, *PPH_TREENEW_CELL_PARTS; + +typedef struct _PH_TREENEW_HIT_TEST +{ + POINT Point; + ULONG InFlags; + + ULONG Flags; + PPH_TREENEW_NODE Node; + PPH_TREENEW_COLUMN Column; // requires TN_TEST_COLUMN +} PH_TREENEW_HIT_TEST, *PPH_TREENEW_HIT_TEST; + +typedef enum _PH_TREENEW_MESSAGE +{ + TreeNewGetChildren, // PPH_TREENEW_GET_CHILDREN Parameter1 + TreeNewIsLeaf, // PPH_TREENEW_IS_LEAF Parameter1 + TreeNewGetCellText, // PPH_TREENEW_GET_CELL_TEXT Parameter1 + TreeNewGetNodeColor, // PPH_TREENEW_GET_NODE_COLOR Parameter1 + TreeNewGetNodeFont, // PPH_TREENEW_GET_NODE_FONT Parameter1 + TreeNewGetNodeIcon, // PPH_TREENEW_GET_NODE_ICON Parameter1 + TreeNewGetCellTooltip, // PPH_TREENEW_GET_CELL_TOOLTIP Parameter1 + TreeNewCustomDraw, // PPH_TREENEW_CUSTOM_DRAW Parameter1 + + // Notifications + TreeNewNodeExpanding, // PPH_TREENEW_NODE Parameter1, PPH_TREENEW_NODE_EVENT Parameter2 + TreeNewNodeSelecting, // PPH_TREENEW_NODE Parameter1 + + TreeNewSortChanged, + TreeNewSelectionChanged, + + TreeNewKeyDown, // PPH_TREENEW_KEY_EVENT Parameter1 + TreeNewLeftClick, // PPH_TREENEW_MOUSE_EVENT Parameter1 + TreeNewRightClick, // PPH_TREENEW_MOUSE_EVENT Parameter1 + TreeNewLeftDoubleClick, // PPH_TREENEW_MOUSE_EVENT Parameter1 + TreeNewRightDoubleClick, // PPH_TREENEW_MOUSE_EVENT Parameter1 + TreeNewContextMenu, // PPH_TREENEW_CONTEXT_MENU Parameter1 + + TreeNewHeaderRightClick, // PPH_TREENEW_HEADER_MOUSE_EVENT Parameter1 + TreeNewIncrementalSearch, // PPH_TREENEW_SEARCH_EVENT Parameter1 + + TreeNewColumnResized, // PPH_TREENEW_COLUMN Parameter1 + TreeNewColumnReordered, + + TreeNewDestroying, + TreeNewGetDialogCode, // ULONG Parameter1, PULONG Parameter2 + + MaxTreeNewMessage +} PH_TREENEW_MESSAGE; + +typedef BOOLEAN (NTAPI *PPH_TREENEW_CALLBACK)( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +typedef struct _PH_TREENEW_GET_CHILDREN +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + + ULONG NumberOfChildren; + PPH_TREENEW_NODE *Children; // can be NULL if no children +} PH_TREENEW_GET_CHILDREN, *PPH_TREENEW_GET_CHILDREN; + +typedef struct _PH_TREENEW_IS_LEAF +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + + BOOLEAN IsLeaf; +} PH_TREENEW_IS_LEAF, *PPH_TREENEW_IS_LEAF; + +typedef struct _PH_TREENEW_GET_CELL_TEXT +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + ULONG Id; + + PH_STRINGREF Text; +} PH_TREENEW_GET_CELL_TEXT, *PPH_TREENEW_GET_CELL_TEXT; + +typedef struct _PH_TREENEW_GET_NODE_COLOR +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + + COLORREF BackColor; + COLORREF ForeColor; +} PH_TREENEW_GET_NODE_COLOR, *PPH_TREENEW_GET_NODE_COLOR; + +typedef struct _PH_TREENEW_GET_NODE_FONT +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + + HFONT Font; +} PH_TREENEW_GET_NODE_FONT, *PPH_TREENEW_GET_NODE_FONT; + +typedef struct _PH_TREENEW_GET_NODE_ICON +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + + HICON Icon; +} PH_TREENEW_GET_NODE_ICON, *PPH_TREENEW_GET_NODE_ICON; + +typedef struct _PH_TREENEW_GET_CELL_TOOLTIP +{ + ULONG Flags; + PPH_TREENEW_NODE Node; + PPH_TREENEW_COLUMN Column; + + BOOLEAN Unfolding; + PH_STRINGREF Text; + HFONT Font; + ULONG MaximumWidth; +} PH_TREENEW_GET_CELL_TOOLTIP, *PPH_TREENEW_GET_CELL_TOOLTIP; + +typedef struct _PH_TREENEW_CUSTOM_DRAW +{ + PPH_TREENEW_NODE Node; + PPH_TREENEW_COLUMN Column; + + HDC Dc; + RECT CellRect; + RECT TextRect; +} PH_TREENEW_CUSTOM_DRAW, *PPH_TREENEW_CUSTOM_DRAW; + +typedef struct _PH_TREENEW_MOUSE_EVENT +{ + POINT Location; + PPH_TREENEW_NODE Node; + PPH_TREENEW_COLUMN Column; + ULONG KeyFlags; +} PH_TREENEW_MOUSE_EVENT, *PPH_TREENEW_MOUSE_EVENT; + +typedef struct _PH_TREENEW_KEY_EVENT +{ + BOOLEAN Handled; + ULONG VirtualKey; + ULONG Data; +} PH_TREENEW_KEY_EVENT, *PPH_TREENEW_KEY_EVENT; + +typedef struct _PH_TREENEW_NODE_EVENT +{ + BOOLEAN Handled; + ULONG Flags; + PVOID Reserved1; + PVOID Reserved2; +} PH_TREENEW_NODE_EVENT, *PPH_TREENEW_NODE_EVENT; + +typedef struct _PH_TREENEW_CONTEXT_MENU +{ + POINT Location; + POINT ClientLocation; + PPH_TREENEW_NODE Node; + PPH_TREENEW_COLUMN Column; + BOOLEAN KeyboardInvoked; +} PH_TREENEW_CONTEXT_MENU, *PPH_TREENEW_CONTEXT_MENU; + +typedef struct _PH_TREENEW_HEADER_MOUSE_EVENT +{ + POINT ScreenLocation; + POINT Location; + POINT HeaderLocation; + PPH_TREENEW_COLUMN Column; +} PH_TREENEW_HEADER_MOUSE_EVENT, *PPH_TREENEW_HEADER_MOUSE_EVENT; + +typedef struct _PH_TREENEW_SEARCH_EVENT +{ + LONG FoundIndex; + LONG StartIndex; + PH_STRINGREF String; +} PH_TREENEW_SEARCH_EVENT, *PPH_TREENEW_SEARCH_EVENT; + +#define TNM_FIRST (WM_USER + 1) +#define TNM_SETCALLBACK (WM_USER + 1) +#define TNM_NODESADDED (WM_USER + 2) // unimplemented +#define TNM_NODESREMOVED (WM_USER + 3) // unimplemented +#define TNM_NODESSTRUCTURED (WM_USER + 4) +#define TNM_ADDCOLUMN (WM_USER + 5) +#define TNM_REMOVECOLUMN (WM_USER + 6) +#define TNM_GETCOLUMN (WM_USER + 7) +#define TNM_SETCOLUMN (WM_USER + 8) +#define TNM_GETCOLUMNORDERARRAY (WM_USER + 9) +#define TNM_SETCOLUMNORDERARRAY (WM_USER + 10) +#define TNM_SETCURSOR (WM_USER + 11) +#define TNM_GETSORT (WM_USER + 12) +#define TNM_SETSORT (WM_USER + 13) +#define TNM_SETTRISTATE (WM_USER + 14) +#define TNM_ENSUREVISIBLE (WM_USER + 15) +#define TNM_SCROLL (WM_USER + 16) +#define TNM_GETFLATNODECOUNT (WM_USER + 17) +#define TNM_GETFLATNODE (WM_USER + 18) +#define TNM_GETCELLTEXT (WM_USER + 19) +#define TNM_SETNODEEXPANDED (WM_USER + 20) +#define TNM_GETMAXID (WM_USER + 21) +#define TNM_SETMAXID (WM_USER + 22) +#define TNM_INVALIDATENODE (WM_USER + 23) +#define TNM_INVALIDATENODES (WM_USER + 24) +#define TNM_GETFIXEDHEADER (WM_USER + 25) +#define TNM_GETHEADER (WM_USER + 26) +#define TNM_GETTOOLTIPS (WM_USER + 27) +#define TNM_SELECTRANGE (WM_USER + 28) +#define TNM_DESELECTRANGE (WM_USER + 29) +#define TNM_GETCOLUMNCOUNT (WM_USER + 30) +#define TNM_SETREDRAW (WM_USER + 31) +#define TNM_GETVIEWPARTS (WM_USER + 32) +#define TNM_GETFIXEDCOLUMN (WM_USER + 33) +#define TNM_GETFIRSTCOLUMN (WM_USER + 34) +#define TNM_SETFOCUSNODE (WM_USER + 35) +#define TNM_SETMARKNODE (WM_USER + 36) +#define TNM_SETHOTNODE (WM_USER + 37) +#define TNM_SETEXTENDEDFLAGS (WM_USER + 38) +#define TNM_GETCALLBACK (WM_USER + 39) +#define TNM_HITTEST (WM_USER + 40) +#define TNM_GETVISIBLECOLUMNCOUNT (WM_USER + 41) +#define TNM_AUTOSIZECOLUMN (WM_USER + 42) +#define TNM_SETEMPTYTEXT (WM_USER + 43) +#define TNM_SETROWHEIGHT (WM_USER + 44) +#define TNM_ISFLATNODEVALID (WM_USER + 45) +#define TNM_LAST (WM_USER + 45) + +#define TreeNew_SetCallback(hWnd, Callback, Context) \ + SendMessage((hWnd), TNM_SETCALLBACK, (WPARAM)(Context), (LPARAM)(Callback)) + +#define TreeNew_NodesStructured(hWnd) \ + SendMessage((hWnd), TNM_NODESSTRUCTURED, 0, 0) + +#define TreeNew_AddColumn(hWnd, Column) \ + SendMessage((hWnd), TNM_ADDCOLUMN, 0, (LPARAM)(Column)) + +#define TreeNew_RemoveColumn(hWnd, Id) \ + SendMessage((hWnd), TNM_REMOVECOLUMN, (WPARAM)(Id), 0) + +#define TreeNew_GetColumn(hWnd, Id, Column) \ + SendMessage((hWnd), TNM_GETCOLUMN, (WPARAM)(Id), (LPARAM)(Column)) + +#define TreeNew_SetColumn(hWnd, Mask, Column) \ + SendMessage((hWnd), TNM_SETCOLUMN, (WPARAM)(Mask), (LPARAM)(Column)) + +#define TreeNew_GetColumnOrderArray(hWnd, Count, Array) \ + SendMessage((hWnd), TNM_GETCOLUMNORDERARRAY, (WPARAM)(Count), (LPARAM)(Array)) + +#define TreeNew_SetColumnOrderArray(hWnd, Count, Array) \ + SendMessage((hWnd), TNM_SETCOLUMNORDERARRAY, (WPARAM)(Count), (LPARAM)(Array)) + +#define TreeNew_SetCursor(hWnd, Cursor) \ + SendMessage((hWnd), TNM_SETCURSOR, 0, (LPARAM)(Cursor)) + +#define TreeNew_GetSort(hWnd, Column, Order) \ + SendMessage((hWnd), TNM_GETSORT, (WPARAM)(Column), (LPARAM)(Order)) + +#define TreeNew_SetSort(hWnd, Column, Order) \ + SendMessage((hWnd), TNM_SETSORT, (WPARAM)(Column), (LPARAM)(Order)) + +#define TreeNew_SetTriState(hWnd, TriState) \ + SendMessage((hWnd), TNM_SETTRISTATE, (WPARAM)(TriState), 0) + +#define TreeNew_EnsureVisible(hWnd, Node) \ + SendMessage((hWnd), TNM_ENSUREVISIBLE, 0, (LPARAM)(Node)) + +#define TreeNew_Scroll(hWnd, DeltaRows, DeltaX) \ + SendMessage((hWnd), TNM_SCROLL, (WPARAM)(DeltaRows), (LPARAM)(DeltaX)) + +#define TreeNew_GetFlatNodeCount(hWnd) \ + ((ULONG)SendMessage((hWnd), TNM_GETFLATNODECOUNT, 0, 0)) + +#define TreeNew_GetFlatNode(hWnd, Index) \ + ((PPH_TREENEW_NODE)SendMessage((hWnd), TNM_GETFLATNODE, (WPARAM)(Index), 0)) + +#define TreeNew_GetCellText(hWnd, GetCellText) \ + SendMessage((hWnd), TNM_GETCELLTEXT, 0, (LPARAM)(GetCellText)) + +#define TreeNew_SetNodeExpanded(hWnd, Node, Expanded) \ + SendMessage((hWnd), TNM_SETNODEEXPANDED, (WPARAM)(Expanded), (LPARAM)(Node)) + +#define TreeNew_GetMaxId(hWnd) \ + ((ULONG)SendMessage((hWnd), TNM_GETMAXID, 0, 0)) + +#define TreeNew_SetMaxId(hWnd, MaxId) \ + SendMessage((hWnd), TNM_SETMAXID, (WPARAM)(MaxId), 0) + +#define TreeNew_InvalidateNode(hWnd, Node) \ + SendMessage((hWnd), TNM_INVALIDATENODE, 0, (LPARAM)(Node)) + +#define TreeNew_InvalidateNodes(hWnd, Start, End) \ + SendMessage((hWnd), TNM_INVALIDATENODES, (WPARAM)(Start), (LPARAM)(End)) + +#define TreeNew_GetFixedHeader(hWnd) \ + ((HWND)SendMessage((hWnd), TNM_GETFIXEDHEADER, 0, 0)) + +#define TreeNew_GetHeader(hWnd) \ + ((HWND)SendMessage((hWnd), TNM_GETHEADER, 0, 0)) + +#define TreeNew_GetTooltips(hWnd) \ + ((HWND)SendMessage((hWnd), TNM_GETTOOLTIPS, 0, 0)) + +#define TreeNew_SelectRange(hWnd, Start, End) \ + SendMessage((hWnd), TNM_SELECTRANGE, (WPARAM)(Start), (LPARAM)(End)) + +#define TreeNew_DeselectRange(hWnd, Start, End) \ + SendMessage((hWnd), TNM_DESELECTRANGE, (WPARAM)(Start), (LPARAM)(End)) + +#define TreeNew_GetColumnCount(hWnd) \ + ((ULONG)SendMessage((hWnd), TNM_GETCOLUMNCOUNT, 0, 0)) + +#define TreeNew_SetRedraw(hWnd, Redraw) \ + ((LONG)SendMessage((hWnd), TNM_SETREDRAW, (WPARAM)(Redraw), 0)) + +#define TreeNew_GetViewParts(hWnd, Parts) \ + SendMessage((hWnd), TNM_GETVIEWPARTS, 0, (LPARAM)(Parts)) + +#define TreeNew_GetFixedColumn(hWnd) \ + ((PPH_TREENEW_COLUMN)SendMessage((hWnd), TNM_GETFIXEDCOLUMN, 0, 0)) + +#define TreeNew_GetFirstColumn(hWnd) \ + ((PPH_TREENEW_COLUMN)SendMessage((hWnd), TNM_GETFIRSTCOLUMN, 0, 0)) + +#define TreeNew_SetFocusNode(hWnd, Node) \ + SendMessage((hWnd), TNM_SETFOCUSNODE, 0, (LPARAM)(Node)) + +#define TreeNew_SetMarkNode(hWnd, Node) \ + SendMessage((hWnd), TNM_SETMARKNODE, 0, (LPARAM)(Node)) + +#define TreeNew_SetHotNode(hWnd, Node) \ + SendMessage((hWnd), TNM_SETHOTNODE, 0, (LPARAM)(Node)) + +#define TreeNew_SetExtendedFlags(hWnd, Mask, Value) \ + SendMessage((hWnd), TNM_SETEXTENDEDFLAGS, (WPARAM)(Mask), (LPARAM)(Value)) + +#define TreeNew_GetCallback(hWnd, Callback, Context) \ + SendMessage((hWnd), TNM_GETCALLBACK, (WPARAM)(Context), (LPARAM)(Callback)) + +#define TreeNew_HitTest(hWnd, HitTest) \ + SendMessage((hWnd), TNM_HITTEST, 0, (LPARAM)(HitTest)) + +#define TreeNew_GetVisibleColumnCount(hWnd) \ + ((ULONG)SendMessage((hWnd), TNM_GETVISIBLECOLUMNCOUNT, 0, 0)) + +#define TreeNew_AutoSizeColumn(hWnd, Id, Flags) \ + SendMessage((hWnd), TNM_AUTOSIZECOLUMN, (WPARAM)(Id), (LPARAM)(Flags)) + +#define TreeNew_SetEmptyText(hWnd, Text, Flags) \ + SendMessage((hWnd), TNM_SETEMPTYTEXT, (WPARAM)(Flags), (LPARAM)(Text)) + +#define TreeNew_SetRowHeight(hWnd, RowHeight) \ + SendMessage((hWnd), TNM_SETROWHEIGHT, (WPARAM)(RowHeight), 0) + +#define TreeNew_IsFlatNodeValid(hWnd) \ + ((BOOLEAN)SendMessage((hWnd), TNM_ISFLATNODEVALID, 0, 0)) + +typedef struct _PH_TREENEW_VIEW_PARTS +{ + RECT ClientRect; + LONG HeaderHeight; + LONG RowHeight; + ULONG VScrollWidth; + ULONG HScrollHeight; + LONG VScrollPosition; + LONG HScrollPosition; + LONG FixedWidth; + LONG NormalLeft; + LONG NormalWidth; +} PH_TREENEW_VIEW_PARTS, *PPH_TREENEW_VIEW_PARTS; + +PHLIBAPI +BOOLEAN PhTreeNewInitialization( + VOID + ); + +FORCEINLINE VOID PhInitializeTreeNewNode( + _In_ PPH_TREENEW_NODE Node + ) +{ + memset(Node, 0, sizeof(PH_TREENEW_NODE)); + + Node->Visible = TRUE; + Node->Expanded = TRUE; +} + +FORCEINLINE VOID PhInvalidateTreeNewNode( + _Inout_ PPH_TREENEW_NODE Node, + _In_ ULONG Flags + ) +{ + if (Flags & TN_CACHE_COLOR) + Node->s.CachedColorValid = FALSE; + if (Flags & TN_CACHE_FONT) + Node->s.CachedFontValid = FALSE; + if (Flags & TN_CACHE_ICON) + Node->s.CachedIconValid = FALSE; +} + +FORCEINLINE BOOLEAN PhAddTreeNewColumn( + _In_ HWND hwnd, + _In_ ULONG Id, + _In_ BOOLEAN Visible, + _In_ PWSTR Text, + _In_ ULONG Width, + _In_ ULONG Alignment, + _In_ ULONG DisplayIndex, + _In_ ULONG TextFlags + ) +{ + PH_TREENEW_COLUMN column; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Id = Id; + column.Visible = Visible; + column.Text = Text; + column.Width = Width; + column.Alignment = Alignment; + column.DisplayIndex = DisplayIndex; + column.TextFlags = TextFlags; + column.DpiScaleOnAdd = TRUE; + + if (DisplayIndex == -2) + column.Fixed = TRUE; + + return !!TreeNew_AddColumn(hwnd, &column); +} + +FORCEINLINE BOOLEAN PhAddTreeNewColumnEx( + _In_ HWND hwnd, + _In_ ULONG Id, + _In_ BOOLEAN Visible, + _In_ PWSTR Text, + _In_ ULONG Width, + _In_ ULONG Alignment, + _In_ ULONG DisplayIndex, + _In_ ULONG TextFlags, + _In_ BOOLEAN SortDescending + ) +{ + PH_TREENEW_COLUMN column; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Id = Id; + column.Visible = Visible; + column.Text = Text; + column.Width = Width; + column.Alignment = Alignment; + column.DisplayIndex = DisplayIndex; + column.TextFlags = TextFlags; + column.DpiScaleOnAdd = TRUE; + + if (DisplayIndex == -2) + column.Fixed = TRUE; + if (SortDescending) + column.SortDescending = TRUE; + + return !!TreeNew_AddColumn(hwnd, &column); +} + +FORCEINLINE BOOLEAN PhAddTreeNewColumnEx2( + _In_ HWND hwnd, + _In_ ULONG Id, + _In_ BOOLEAN Visible, + _In_ PWSTR Text, + _In_ ULONG Width, + _In_ ULONG Alignment, + _In_ ULONG DisplayIndex, + _In_ ULONG TextFlags, + _In_ ULONG ExtraFlags + ) +{ + PH_TREENEW_COLUMN column; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Id = Id; + column.Visible = Visible; + column.Text = Text; + column.Width = Width; + column.Alignment = Alignment; + column.DisplayIndex = DisplayIndex; + column.TextFlags = TextFlags; + + if (DisplayIndex == -2) + column.Fixed = TRUE; + if (ExtraFlags & TN_COLUMN_FLAG_CUSTOMDRAW) + column.CustomDraw = TRUE; + if (ExtraFlags & TN_COLUMN_FLAG_SORTDESCENDING) + column.SortDescending = TRUE; + if (!(ExtraFlags & TN_COLUMN_FLAG_NODPISCALEONADD)) + column.DpiScaleOnAdd = TRUE; + + return !!TreeNew_AddColumn(hwnd, &column); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/treenewp.h b/phlib/include/treenewp.h new file mode 100644 index 0000000..fed1d15 --- /dev/null +++ b/phlib/include/treenewp.h @@ -0,0 +1,802 @@ +#ifndef _PH_TREENEWP_H +#define _PH_TREENEWP_H + +// Important notes about pointers: +// +// All memory allocation for nodes and strings is handled by the user. This usually means there is a +// very limited time during which they can be safely accessed. +// +// Node pointers are valid through the duration of message processing, and also up to the next +// restructure operation, either user- or control- initiated. This means that state such as the +// focused node, hot node and mark node must be carefully preserved through restructuring. If +// restructuring is suspended by a set-redraw call, all nodes must be considered invalid and no user +// input can be handled. +// +// Strings are valid only through the duration of message processing. + +typedef struct _PH_TREENEW_CONTEXT +{ + HWND Handle; + PVOID InstanceHandle; + HWND FixedHeaderHandle; + HWND HeaderHandle; + HWND VScrollHandle; + HWND HScrollHandle; + HWND FillerBoxHandle; + HWND TooltipsHandle; + + union + { + struct + { + ULONG FontOwned : 1; + ULONG Tracking : 1; // tracking for fixed divider + ULONG VScrollVisible : 1; + ULONG HScrollVisible : 1; + ULONG FixedColumnVisible : 1; + ULONG FixedDividerVisible : 1; + ULONG AnimateDivider : 1; + ULONG AnimateDividerFadingIn : 1; + ULONG AnimateDividerFadingOut : 1; + ULONG CanAnyExpand : 1; + ULONG TriState : 1; + ULONG HasFocus : 1; + ULONG ThemeInitialized : 1; // delay load theme data + ULONG ThemeActive : 1; + ULONG ThemeHasItemBackground : 1; + ULONG ThemeHasGlyph : 1; + ULONG ThemeHasHotGlyph : 1; + ULONG FocusNodeFound : 1; // used to preserve the focused node across restructuring + ULONG SearchFailed : 1; // used to prevent multiple beeps + ULONG SearchSingleCharMode : 1; // LV style single-character search + ULONG TooltipUnfolding : 1; // whether the current tooltip is unfolding + ULONG DoubleBuffered : 1; + ULONG SuspendUpdateStructure : 1; + ULONG SuspendUpdateLayout : 1; + ULONG SuspendUpdateMoveMouse : 1; + ULONG DragSelectionActive : 1; + ULONG SelectionRectangleAlpha : 1; // use alpha blending for the selection rectangle + ULONG CustomRowHeight : 1; + ULONG Spare : 4; + }; + ULONG Flags; + }; + ULONG Style; + ULONG ExtendedStyle; + ULONG ExtendedFlags; + + HFONT Font; + HCURSOR Cursor; + HCURSOR DividerCursor; + + RECT ClientRect; + LONG HeaderHeight; + LONG RowHeight; + ULONG VScrollWidth; + ULONG HScrollHeight; + LONG VScrollPosition; + LONG HScrollPosition; + LONG FixedWidth; // width of the fixed part of the tree list + LONG FixedWidthMinimum; + LONG NormalLeft; // FixedWidth + 1 if there is a fixed column, otherwise 0 + + PPH_TREENEW_NODE FocusNode; + ULONG HotNodeIndex; + ULONG MarkNodeIndex; // selection mark + + ULONG MouseDownLast; + POINT MouseDownLocation; + + PPH_TREENEW_CALLBACK Callback; + PVOID CallbackContext; + + PPH_TREENEW_COLUMN *Columns; // columns, indexed by ID + ULONG NextId; + ULONG AllocatedColumns; + ULONG NumberOfColumns; // just a statistic; do not use for actual logic + + PPH_TREENEW_COLUMN *ColumnsByDisplay; // columns, indexed by display order (excluding the fixed column) + ULONG AllocatedColumnsByDisplay; + ULONG NumberOfColumnsByDisplay; // the number of visible columns (excluding the fixed column) + LONG TotalViewX; // total width of normal columns + PPH_TREENEW_COLUMN FixedColumn; + PPH_TREENEW_COLUMN FirstColumn; // first column, by display order (including the fixed column) + PPH_TREENEW_COLUMN LastColumn; // last column, by display order (including the fixed column) + + PPH_TREENEW_COLUMN ResizingColumn; + LONG OldColumnWidth; + LONG TrackStartX; + LONG TrackOldFixedWidth; + ULONG DividerHot; // 0 for un-hot, 100 for completely hot + + PPH_LIST FlatList; + + ULONG SortColumn; // ID of the column to sort by + PH_SORT_ORDER SortOrder; + + FLOAT VScrollRemainder; + FLOAT HScrollRemainder; + + LONG SearchMessageTime; + PWSTR SearchString; + ULONG SearchStringCount; + ULONG AllocatedSearchString; + + ULONG TooltipIndex; + ULONG TooltipId; + PPH_STRING TooltipText; + RECT TooltipRect; // text rectangle of an unfolding tooltip + HFONT TooltipFont; + HFONT NewTooltipFont; + ULONG TooltipColumnId; + WNDPROC FixedHeaderOldWndProc; + WNDPROC HeaderOldWndProc; + + TEXTMETRIC TextMetrics; + HTHEME ThemeData; + LONG SystemBorderX; + LONG SystemBorderY; + LONG SystemEdgeX; + LONG SystemEdgeY; + + HDC BufferedContext; + HBITMAP BufferedOldBitmap; + HBITMAP BufferedBitmap; + RECT BufferedContextRect; + + LONG SystemDragX; + LONG SystemDragY; + RECT DragRect; + + LONG EnableRedraw; + HRGN SuspendUpdateRegion; + + PH_STRINGREF EmptyText; +} PH_TREENEW_CONTEXT, *PPH_TREENEW_CONTEXT; + +LRESULT CALLBACK PhTnpWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +BOOLEAN NTAPI PhTnpNullCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID PhTnpCreateTreeNewContext( + _Out_ PPH_TREENEW_CONTEXT *Context + ); + +VOID PhTnpDestroyTreeNewContext( + _In_ PPH_TREENEW_CONTEXT Context + ); + +// Event handlers + +BOOLEAN PhTnpOnCreate( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ CREATESTRUCT *CreateStruct + ); + +VOID PhTnpOnSize( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpOnSetFont( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HFONT Font, + _In_ LOGICAL Redraw + ); + +VOID PhTnpOnStyleChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Type, + _In_ STYLESTRUCT *StyleStruct + ); + +VOID PhTnpOnSettingChange( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpOnThemeChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +ULONG PhTnpOnGetDlgCode( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey, + _In_opt_ PMSG Message + ); + +VOID PhTnpOnPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpOnPrintClient( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ ULONG Flags + ); + +BOOLEAN PhTnpOnNcPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HRGN UpdateRegion + ); + +BOOLEAN PhTnpOnSetCursor( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HWND CursorWindowHandle + ); + +VOID PhTnpOnTimer( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ); + +VOID PhTnpOnMouseMove( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpOnMouseLeave( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpOnXxxButtonXxx( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Message, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpOnCaptureChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpOnKeyDown( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey, + _In_ ULONG Data + ); + +VOID PhTnpOnChar( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Character, + _In_ ULONG Data + ); + +VOID PhTnpOnMouseWheel( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpOnMouseHWheel( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpOnContextMenu( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ); + +VOID PhTnpOnVScroll( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Request, + _In_ USHORT Position + ); + +VOID PhTnpOnHScroll( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Request, + _In_ USHORT Position + ); + +BOOLEAN PhTnpOnNotify( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ); + +ULONG_PTR PhTnpOnUserMessage( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ); + +// Misc. + +VOID PhTnpSetFont( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HFONT Font, + _In_ BOOLEAN Redraw + ); + +VOID PhTnpUpdateSystemMetrics( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpUpdateTextMetrics( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpUpdateThemeData( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpInitializeThemeData( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpCancelTrack( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpLayout( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpLayoutHeader( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpSetFixedWidth( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG FixedWidth + ); + +VOID PhTnpSetRedraw( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Redraw + ); + +VOID PhTnpSendMouseEvent( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PH_TREENEW_MESSAGE Message, + _In_ LONG CursorX, + _In_ LONG CursorY, + _In_ PPH_TREENEW_NODE Node, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG VirtualKeys + ); + +// Columns + +PPH_TREENEW_COLUMN PhTnpLookupColumnById( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ); + +BOOLEAN PhTnpAddColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column + ); + +BOOLEAN PhTnpRemoveColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ); + +BOOLEAN PhTnpCopyColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id, + _Out_ PPH_TREENEW_COLUMN Column + ); + +BOOLEAN PhTnpChangeColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Mask, + _In_ ULONG Id, + _In_ PPH_TREENEW_COLUMN Column + ); + +VOID PhTnpExpandAllocatedColumns( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpUpdateColumnMaps( + _In_ PPH_TREENEW_CONTEXT Context + ); + +// Columns (header control) + +LONG PhTnpInsertColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column + ); + +VOID PhTnpChangeColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Mask, + _In_ PPH_TREENEW_COLUMN Column + ); + +VOID PhTnpDeleteColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_COLUMN Column + ); + +VOID PhTnpUpdateColumnHeaders( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpProcessResizeColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column, + _In_ LONG Delta + ); + +VOID PhTnpProcessSortColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN NewColumn + ); + +BOOLEAN PhTnpSetColumnHeaderSortIcon( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_COLUMN SortColumnPointer + ); + +VOID PhTnpAutoSizeColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HWND HeaderHandle, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG Flags + ); + +// Nodes + +BOOLEAN PhTnpGetNodeChildren( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_NODE Node, + _Out_ PPH_TREENEW_NODE **Children, + _Out_ PULONG NumberOfChildren + ); + +BOOLEAN PhTnpIsNodeLeaf( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node + ); + +BOOLEAN PhTnpGetCellText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ ULONG Id, + _Out_ PPH_STRINGREF Text + ); + +VOID PhTnpRestructureNodes( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpInsertNodeChildren( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ ULONG Level + ); + +VOID PhTnpSetExpandedNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ BOOLEAN Expanded + ); + +BOOLEAN PhTnpGetCellParts( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Index, + _In_opt_ PPH_TREENEW_COLUMN Column, + _In_ ULONG Flags, + _Out_ PPH_TREENEW_CELL_PARTS Parts + ); + +BOOLEAN PhTnpGetRowRects( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Start, + _In_ ULONG End, + _In_ BOOLEAN Clip, + _Out_ PRECT Rect + ); + +VOID PhTnpHitTest( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_HIT_TEST HitTest + ); + +VOID PhTnpSelectRange( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Start, + _In_ ULONG End, + _In_ ULONG Flags, + _Out_opt_ PULONG ChangedStart, + _Out_opt_ PULONG ChangedEnd + ); + +VOID PhTnpSetHotNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_NODE NewHotNode, + _In_ BOOLEAN NewPlusMinusHot + ); + +VOID PhTnpProcessSelectNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ LOGICAL ControlKey, + _In_ LOGICAL ShiftKey, + _In_ LOGICAL RightButton + ); + +BOOLEAN PhTnpEnsureVisibleNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Index + ); + +// Mouse + +VOID PhTnpProcessMoveMouse( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpProcessMouseVWheel( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance + ); + +VOID PhTnpProcessMouseHWheel( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance + ); + +// Keyboard + +BOOLEAN PhTnpProcessFocusKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey + ); + +BOOLEAN PhTnpProcessNodeKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey + ); + +VOID PhTnpProcessSearchKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Character + ); + +BOOLEAN PhTnpDefaultIncrementalSearch( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_SEARCH_EVENT SearchEvent, + _In_ BOOLEAN Partial, + _In_ BOOLEAN Wrap + ); + +// Scrolling + +VOID PhTnpUpdateScrollBars( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG DeltaRows, + _In_ LONG DeltaX + ); + +VOID PhTnpProcessScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG DeltaRows, + _In_ LONG DeltaX + ); + +BOOLEAN PhTnpCanScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Horizontal, + _In_ BOOLEAN Positive + ); + +// Drawing + +VOID PhTnpPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT PaintRect + ); + +VOID PhTnpPrepareRowForDraw( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _Inout_ PPH_TREENEW_NODE Node + ); + +VOID PhTnpDrawCell( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT CellRect, + _In_ PPH_TREENEW_NODE Node, + _In_ PPH_TREENEW_COLUMN Column, + _In_ LONG RowIndex, + _In_ LONG ColumnIndex + ); + +VOID PhTnpDrawDivider( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc + ); + +VOID PhTnpDrawPlusMinusGlyph( + _In_ HDC hdc, + _In_ PRECT Rect, + _In_ BOOLEAN Plus + ); + +VOID PhTnpDrawSelectionRectangle( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT Rect + ); + +VOID PhTnpDrawThemedBorder( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc + ); + +// Tooltips + +VOID PhTnpInitializeTooltips( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpGetTooltipText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPOINT Point, + _Out_ PWSTR *Text + ); + +BOOLEAN PhTnpPrepareTooltipShow( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpPrepareTooltipPop( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpPopTooltip( + _In_ PPH_TREENEW_CONTEXT Context + ); + +PPH_TREENEW_COLUMN PhTnpHitTestHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Fixed, + _In_ PPOINT Point, + _Out_opt_ PRECT ItemRect + ); + +VOID PhTnpGetHeaderTooltipText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Fixed, + _In_ PPOINT Point, + _Out_ PWSTR *Text + ); + +PWSTR PhTnpMakeContextAtom( + VOID + ); + +LRESULT CALLBACK PhTnpHeaderHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Drag selection + +BOOLEAN PhTnpDetectDrag( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY, + _In_ BOOLEAN DispatchMessages, + _Out_opt_ PULONG CancelledByMessage + ); + +VOID PhTnpDragSelect( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY + ); + +VOID PhTnpProcessDragSelect( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKeys, + _In_ PRECT OldRect, + _In_ PRECT NewRect, + _In_ PRECT TotalRect + ); + +// Double buffering + +VOID PhTnpCreateBufferedContext( + _In_ PPH_TREENEW_CONTEXT Context + ); + +VOID PhTnpDestroyBufferedContext( + _In_ PPH_TREENEW_CONTEXT Context + ); + +// Support functions + +VOID PhTnpGetMessagePos( + _In_ HWND hwnd, + _Out_ PPOINT ClientPoint + ); + +// Macros + +#define HRGN_FULL ((HRGN)1) // passed by WM_NCPAINT even though it's completely undocumented + +#define TNP_CELL_LEFT_MARGIN 6 +#define TNP_CELL_RIGHT_MARGIN 6 +#define TNP_ICON_RIGHT_PADDING 4 + +#define TNP_TIMER_NULL 1 +#define TNP_TIMER_ANIMATE_DIVIDER 2 + +#define TNP_TOOLTIPS_ITEM 0 +#define TNP_TOOLTIPS_FIXED_HEADER 1 +#define TNP_TOOLTIPS_HEADER 2 +#define TNP_TOOLTIPS_DEFAULT_MAXIMUM_WIDTH 550 + +#define TNP_ANIMATE_DIVIDER_INTERVAL 10 +#define TNP_ANIMATE_DIVIDER_INCREMENT 17 +#define TNP_ANIMATE_DIVIDER_DECREMENT 2 + +#define TNP_HIT_TEST_FIXED_DIVIDER(X, Context) \ + ((Context)->FixedDividerVisible && (X) >= (Context)->FixedWidth - 8 && (X) < (Context)->FixedWidth + 8) +#define TNP_HIT_TEST_PLUS_MINUS_GLYPH(X, NodeLevel) \ + (((X) >= TNP_CELL_LEFT_MARGIN + ((LONG)(NodeLevel) * SmallIconWidth)) && ((X) < TNP_CELL_LEFT_MARGIN + ((LONG)(NodeLevel) * SmallIconWidth) + SmallIconWidth)) + +#endif diff --git a/phlib/include/verify.h b/phlib/include/verify.h new file mode 100644 index 0000000..f18a204 --- /dev/null +++ b/phlib/include/verify.h @@ -0,0 +1,77 @@ +#ifndef _PH_VERIFY_H +#define _PH_VERIFY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PH_VERIFY_DEFAULT_SIZE_LIMIT (32 * 1024 * 1024) + +typedef enum _VERIFY_RESULT +{ + VrUnknown = 0, + VrNoSignature, + VrTrusted, + VrExpired, + VrRevoked, + VrDistrust, + VrSecuritySettings, + VrBadSignature +} VERIFY_RESULT, *PVERIFY_RESULT; + +#define PH_VERIFY_PREVENT_NETWORK_ACCESS 0x1 +#define PH_VERIFY_VIEW_PROPERTIES 0x2 + +typedef struct _PH_VERIFY_FILE_INFO +{ + PWSTR FileName; + ULONG Flags; // PH_VERIFY_* + + ULONG FileSizeLimitForHash; // 0 for PH_VERIFY_DEFAULT_SIZE_LIMIT, -1 for unlimited + ULONG NumberOfCatalogFileNames; + PWSTR *CatalogFileNames; + + HWND hWnd; // for PH_VERIFY_VIEW_PROPERTIES +} PH_VERIFY_FILE_INFO, *PPH_VERIFY_FILE_INFO; + +PHLIBAPI +VERIFY_RESULT +NTAPI +PhVerifyFile( + _In_ PWSTR FileName, + _Out_opt_ PPH_STRING *SignerName + ); + +PHLIBAPI +NTSTATUS +NTAPI +PhVerifyFileEx( + _In_ PPH_VERIFY_FILE_INFO Information, + _Out_ VERIFY_RESULT *VerifyResult, + _Out_opt_ PCERT_CONTEXT **Signatures, + _Out_opt_ PULONG NumberOfSignatures + ); + +PHLIBAPI +VOID +NTAPI +PhFreeVerifySignatures( + _In_ PCERT_CONTEXT *Signatures, + _In_ ULONG NumberOfSignatures + ); + +PHLIBAPI +PPH_STRING +NTAPI +PhGetSignerNameFromCertificate( + _In_ PCERT_CONTEXT Certificate + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/verifyp.h b/phlib/include/verifyp.h new file mode 100644 index 0000000..8a9dbdc --- /dev/null +++ b/phlib/include/verifyp.h @@ -0,0 +1,118 @@ +#ifndef _PH_VERIFYP_H +#define _PH_VERIFYP_H + +#include + +typedef struct _CATALOG_INFO +{ + DWORD cbStruct; + WCHAR wszCatalogFile[MAX_PATH]; +} CATALOG_INFO, *PCATALOG_INFO; + +typedef struct tagCRYPTUI_VIEWSIGNERINFO_STRUCT { + DWORD dwSize; + HWND hwndParent; + DWORD dwFlags; + LPCTSTR szTitle; + CMSG_SIGNER_INFO *pSignerInfo; + HCRYPTMSG hMsg; + LPCSTR pszOID; + DWORD_PTR dwReserved; + DWORD cStores; + HCERTSTORE *rghStores; + DWORD cPropSheetPages; + LPCPROPSHEETPAGE rgPropSheetPages; +} CRYPTUI_VIEWSIGNERINFO_STRUCT, *PCRYPTUI_VIEWSIGNERINFO_STRUCT; + +typedef BOOL (WINAPI *_CryptCATAdminCalcHashFromFileHandle)( + HANDLE hFile, + DWORD *pcbHash, + BYTE *pbHash, + DWORD dwFlags + ); + +typedef BOOL (WINAPI *_CryptCATAdminCalcHashFromFileHandle2)( + HCATADMIN hCatAdmin, + HANDLE hFile, + DWORD *pcbHash, + BYTE *pbHash, + DWORD dwFlags + ); + +typedef BOOL (WINAPI *_CryptCATAdminAcquireContext)( + HANDLE *phCatAdmin, + GUID *pgSubsystem, + DWORD dwFlags + ); + +typedef BOOL (WINAPI *_CryptCATAdminAcquireContext2)( + HCATADMIN *phCatAdmin, + const GUID *pgSubsystem, + PCWSTR pwszHashAlgorithm, + PCCERT_STRONG_SIGN_PARA pStrongHashPolicy, + DWORD dwFlags + ); + +typedef HANDLE (WINAPI *_CryptCATAdminEnumCatalogFromHash)( + HANDLE hCatAdmin, + BYTE *pbHash, + DWORD cbHash, + DWORD dwFlags, + HANDLE *phPrevCatInfo + ); + +typedef BOOL (WINAPI *_CryptCATCatalogInfoFromContext)( + HANDLE hCatInfo, + CATALOG_INFO *psCatInfo, + DWORD dwFlags + ); + +typedef BOOL (WINAPI *_CryptCATAdminReleaseCatalogContext)( + HANDLE hCatAdmin, + HANDLE hCatInfo, + DWORD dwFlags + ); + +typedef BOOL (WINAPI *_CryptCATAdminReleaseContext)( + HANDLE hCatAdmin, + DWORD dwFlags + ); + +typedef PCRYPT_PROVIDER_DATA (WINAPI *_WTHelperProvDataFromStateData)( + HANDLE hStateData + ); + +typedef PCRYPT_PROVIDER_SGNR (WINAPI *_WTHelperGetProvSignerFromChain)( + CRYPT_PROVIDER_DATA *pProvData, + DWORD idxSigner, + BOOL fCounterSigner, + DWORD idxCounterSigner + ); + +typedef LONG (WINAPI *_WinVerifyTrust)( + HWND hWnd, + GUID *pgActionID, + LPVOID pWVTData + ); + +typedef DWORD (WINAPI *_CertNameToStr)( + DWORD dwCertEncodingType, + PCERT_NAME_BLOB pName, + DWORD dwStrType, + LPTSTR psz, + DWORD csz + ); + +typedef PCCERT_CONTEXT (WINAPI *_CertDuplicateCertificateContext)( + _In_ PCCERT_CONTEXT pCertContext + ); + +typedef BOOL (WINAPI *_CertFreeCertificateContext)( + _In_ PCCERT_CONTEXT pCertContext + ); + +typedef BOOL (WINAPI *_CryptUIDlgViewSignerInfo)( + _In_ CRYPTUI_VIEWSIGNERINFO_STRUCT *pcvsi + ); + +#endif diff --git a/phlib/include/workqueue.h b/phlib/include/workqueue.h new file mode 100644 index 0000000..1cbe4a3 --- /dev/null +++ b/phlib/include/workqueue.h @@ -0,0 +1,108 @@ +#ifndef _PH_WORKQUEUE_H +#define _PH_WORKQUEUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(DEBUG) +extern PPH_LIST PhDbgWorkQueueList; +extern PH_QUEUED_LOCK PhDbgWorkQueueListLock; +#endif + +typedef struct _PH_WORK_QUEUE +{ + PH_RUNDOWN_PROTECT RundownProtect; + BOOLEAN Terminating; + + LIST_ENTRY QueueListHead; + PH_QUEUED_LOCK QueueLock; + PH_CONDITION QueueEmptyCondition; + + ULONG MaximumThreads; + ULONG MinimumThreads; + ULONG NoWorkTimeout; + + PH_QUEUED_LOCK StateLock; + HANDLE SemaphoreHandle; + ULONG CurrentThreads; + ULONG BusyCount; +} PH_WORK_QUEUE, *PPH_WORK_QUEUE; + +typedef VOID (NTAPI *PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION)( + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_ PVOID Context + ); + +typedef struct _PH_WORK_QUEUE_ENVIRONMENT +{ + LONG BasePriority : 6; // Base priority increment + ULONG IoPriority : 3; // I/O priority hint + ULONG PagePriority : 3; // Page/memory priority + ULONG ForceUpdate : 1; // Always set priorities regardless of cached values + ULONG SpareBits : 19; +} PH_WORK_QUEUE_ENVIRONMENT, *PPH_WORK_QUEUE_ENVIRONMENT; + +PHLIBAPI +VOID +NTAPI +PhInitializeWorkQueue( + _Out_ PPH_WORK_QUEUE WorkQueue, + _In_ ULONG MinimumThreads, + _In_ ULONG MaximumThreads, + _In_ ULONG NoWorkTimeout + ); + +PHLIBAPI +VOID +NTAPI +PhDeleteWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ); + +PHLIBAPI +VOID +NTAPI +PhWaitForWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ); + +PHLIBAPI +VOID +NTAPI +PhQueueItemWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue, + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context + ); + +PHLIBAPI +VOID +NTAPI +PhQueueItemWorkQueueEx( + _Inout_ PPH_WORK_QUEUE WorkQueue, + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context, + _In_opt_ PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION DeleteFunction, + _In_opt_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ); + +PHLIBAPI +VOID +NTAPI +PhInitializeWorkQueueEnvironment( + _Out_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ); + +PHLIBAPI +PPH_WORK_QUEUE +NTAPI +PhGetGlobalWorkQueue( + VOID + ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phlib/include/workqueuep.h b/phlib/include/workqueuep.h new file mode 100644 index 0000000..6fb9b59 --- /dev/null +++ b/phlib/include/workqueuep.h @@ -0,0 +1,49 @@ +#ifndef _PH_WORKQUEUEP_H +#define _PH_WORKQUEUEP_H + +typedef struct _PH_WORK_QUEUE_ITEM +{ + LIST_ENTRY ListEntry; + PUSER_THREAD_START_ROUTINE Function; + PVOID Context; + PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION DeleteFunction; + PH_WORK_QUEUE_ENVIRONMENT Environment; +} PH_WORK_QUEUE_ITEM, *PPH_WORK_QUEUE_ITEM; + +VOID PhpGetDefaultWorkQueueEnvironment( + _Out_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ); + +VOID PhpUpdateWorkQueueEnvironment( + _Inout_ PPH_WORK_QUEUE_ENVIRONMENT CurrentEnvironment, + _In_ PPH_WORK_QUEUE_ENVIRONMENT NewEnvironment + ); + +PPH_WORK_QUEUE_ITEM PhpCreateWorkQueueItem( + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context, + _In_opt_ PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION DeleteFunction, + _In_opt_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ); + +VOID PhpDestroyWorkQueueItem( + _In_ PPH_WORK_QUEUE_ITEM WorkQueueItem + ); + +VOID PhpExecuteWorkQueueItem( + _Inout_ PPH_WORK_QUEUE_ITEM WorkQueueItem + ); + +HANDLE PhpGetSemaphoreWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ); + +BOOLEAN PhpCreateWorkQueueThread( + _Inout_ PPH_WORK_QUEUE WorkQueue + ); + +NTSTATUS PhpWorkQueueThreadStart( + _In_ PVOID Parameter + ); + +#endif diff --git a/phlib/kph.c b/phlib/kph.c new file mode 100644 index 0000000..0acd7d0 --- /dev/null +++ b/phlib/kph.c @@ -0,0 +1,1102 @@ +/* + * Process Hacker - + * KProcessHacker API + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +HANDLE PhKphHandle = NULL; +BOOLEAN PhKphVerified; +KPH_KEY PhKphL1Key; + +NTSTATUS KphConnect( + _In_opt_ PWSTR DeviceName + ) +{ + NTSTATUS status; + HANDLE kphHandle; + UNICODE_STRING objectName; + OBJECT_ATTRIBUTES objectAttributes; + IO_STATUS_BLOCK isb; + OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo; + + if (PhKphHandle) + return STATUS_ADDRESS_ALREADY_EXISTS; + + if (DeviceName) + RtlInitUnicodeString(&objectName, DeviceName); + else + RtlInitUnicodeString(&objectName, KPH_DEVICE_NAME); + + InitializeObjectAttributes( + &objectAttributes, + &objectName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtOpenFile( + &kphHandle, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, + &objectAttributes, + &isb, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_NON_DIRECTORY_FILE + ); + + if (NT_SUCCESS(status)) + { + // Protect the handle from being closed. + + handleFlagInfo.Inherit = FALSE; + handleFlagInfo.ProtectFromClose = TRUE; + + NtSetInformationObject( + kphHandle, + ObjectHandleFlagInformation, + &handleFlagInfo, + sizeof(OBJECT_HANDLE_FLAG_INFORMATION) + ); + + PhKphHandle = kphHandle; + PhKphVerified = FALSE; + PhKphL1Key = 0; + } + + return status; +} + +NTSTATUS KphConnect2( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName + ) +{ + return KphConnect2Ex(DeviceName, FileName, NULL); +} + +NTSTATUS KphConnect2Ex( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName, + _In_opt_ PKPH_PARAMETERS Parameters + ) +{ + NTSTATUS status; + WCHAR fullDeviceName[256]; + PH_FORMAT format[2]; + SC_HANDLE scmHandle; + SC_HANDLE serviceHandle; + BOOLEAN started = FALSE; + BOOLEAN created = FALSE; + + if (!DeviceName) + DeviceName = KPH_DEVICE_SHORT_NAME; + + PhInitFormatS(&format[0], L"\\Device\\"); + PhInitFormatS(&format[1], DeviceName); + + if (!PhFormatToBuffer(format, 2, fullDeviceName, sizeof(fullDeviceName), NULL)) + return STATUS_NAME_TOO_LONG; + + // Try to open the device. + status = KphConnect(fullDeviceName); + + if (NT_SUCCESS(status) || status == STATUS_ADDRESS_ALREADY_EXISTS) + return status; + + if ( + status != STATUS_NO_SUCH_DEVICE && + status != STATUS_NO_SUCH_FILE && + status != STATUS_OBJECT_NAME_NOT_FOUND && + status != STATUS_OBJECT_PATH_NOT_FOUND + ) + return status; + + // Load the driver, and try again. + + // Try to start the service, if it exists. + + scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + + if (scmHandle) + { + serviceHandle = OpenService(scmHandle, DeviceName, SERVICE_START); + + if (serviceHandle) + { + if (StartService(serviceHandle, 0, NULL)) + started = TRUE; + + CloseServiceHandle(serviceHandle); + } + + CloseServiceHandle(scmHandle); + } + + if (!started && RtlDoesFileExists_U(FileName)) + { + // Try to create the service. + + scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + + if (scmHandle) + { + serviceHandle = CreateService( + scmHandle, + DeviceName, + DeviceName, + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_IGNORE, + FileName, + NULL, + NULL, + NULL, + NULL, + L"" + ); + + if (serviceHandle) + { + created = TRUE; + + // Set parameters if the caller supplied them. Note that we fail the entire function + // if this fails, because failing to set parameters like SecurityLevel may result in + // security vulnerabilities. + if (Parameters) + { + status = KphSetParameters(DeviceName, Parameters); + + if (!NT_SUCCESS(status)) + { + // Delete the service and fail. + goto CreateAndConnectEnd; + } + } + + if (StartService(serviceHandle, 0, NULL)) + started = TRUE; + } + + CloseServiceHandle(scmHandle); + } + } + + if (started) + { + // Try to open the device again. + status = KphConnect(fullDeviceName); + } + +CreateAndConnectEnd: + if (created) + { + // "Delete" the service. Since we (may) have a handle to the device, the SCM will delete the + // service automatically when it is stopped (upon reboot). If we don't have a handle to the + // device, the service will get deleted immediately, which is a good thing anyway. + DeleteService(serviceHandle); + CloseServiceHandle(serviceHandle); + } + + return status; +} + +NTSTATUS KphDisconnect( + VOID + ) +{ + NTSTATUS status; + OBJECT_HANDLE_FLAG_INFORMATION handleFlagInfo; + + if (!PhKphHandle) + return STATUS_ALREADY_DISCONNECTED; + + // Unprotect the handle. + + handleFlagInfo.Inherit = FALSE; + handleFlagInfo.ProtectFromClose = FALSE; + + NtSetInformationObject( + PhKphHandle, + ObjectHandleFlagInformation, + &handleFlagInfo, + sizeof(OBJECT_HANDLE_FLAG_INFORMATION) + ); + + status = NtClose(PhKphHandle); + PhKphHandle = NULL; + PhKphVerified = FALSE; + PhKphL1Key = 0; + + return status; +} + +BOOLEAN KphIsConnected( + VOID + ) +{ + return PhKphHandle != NULL; +} + +BOOLEAN KphIsVerified( + VOID + ) +{ + return PhKphVerified; +} + +NTSTATUS KphSetParameters( + _In_opt_ PWSTR DeviceName, + _In_ PKPH_PARAMETERS Parameters + ) +{ + NTSTATUS status; + HANDLE parametersKeyHandle = NULL; + PPH_STRING parametersKeyName; + ULONG disposition; + UNICODE_STRING valueName; + + if (!DeviceName) + DeviceName = KPH_DEVICE_SHORT_NAME; + + parametersKeyName = PhConcatStrings( + 3, + L"System\\CurrentControlSet\\Services\\", + DeviceName, + L"\\Parameters" + ); + status = PhCreateKey( + ¶metersKeyHandle, + KEY_WRITE | DELETE, + PH_KEY_LOCAL_MACHINE, + ¶metersKeyName->sr, + 0, + 0, + &disposition + ); + PhDereferenceObject(parametersKeyName); + + if (!NT_SUCCESS(status)) + return status; + + RtlInitUnicodeString(&valueName, L"SecurityLevel"); + status = NtSetValueKey(parametersKeyHandle, &valueName, 0, REG_DWORD, &Parameters->SecurityLevel, sizeof(ULONG)); + + if (!NT_SUCCESS(status)) + goto SetValuesEnd; + + if (Parameters->CreateDynamicConfiguration) + { + KPH_DYN_CONFIGURATION configuration; + + RtlInitUnicodeString(&valueName, L"DynamicConfiguration"); + + configuration.Version = KPH_DYN_CONFIGURATION_VERSION; + configuration.NumberOfPackages = 1; + + if (NT_SUCCESS(KphInitializeDynamicPackage(&configuration.Packages[0]))) + { + status = NtSetValueKey(parametersKeyHandle, &valueName, 0, REG_BINARY, &configuration, sizeof(KPH_DYN_CONFIGURATION)); + + if (!NT_SUCCESS(status)) + goto SetValuesEnd; + } + } + + // Put more parameters here... + +SetValuesEnd: + if (!NT_SUCCESS(status)) + { + // Delete the key if we created it. + if (disposition == REG_CREATED_NEW_KEY) + NtDeleteKey(parametersKeyHandle); + } + + NtClose(parametersKeyHandle); + + return status; +} + +NTSTATUS KphInstall( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName + ) +{ + return KphInstallEx(DeviceName, FileName, NULL); +} + +NTSTATUS KphInstallEx( + _In_opt_ PWSTR DeviceName, + _In_ PWSTR FileName, + _In_opt_ PKPH_PARAMETERS Parameters + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SC_HANDLE scmHandle; + SC_HANDLE serviceHandle; + + if (!DeviceName) + DeviceName = KPH_DEVICE_SHORT_NAME; + + scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + + if (!scmHandle) + return PhGetLastWin32ErrorAsNtStatus(); + + serviceHandle = CreateService( + scmHandle, + DeviceName, + DeviceName, + SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, + SERVICE_SYSTEM_START, + SERVICE_ERROR_IGNORE, + FileName, + NULL, + NULL, + NULL, + NULL, + L"" + ); + + if (serviceHandle) + { + // See KphConnect2Ex for more details. + if (Parameters) + { + status = KphSetParameters(DeviceName, Parameters); + + if (!NT_SUCCESS(status)) + { + DeleteService(serviceHandle); + goto CreateEnd; + } + } + + if (!StartService(serviceHandle, 0, NULL)) + status = PhGetLastWin32ErrorAsNtStatus(); + +CreateEnd: + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + + CloseServiceHandle(scmHandle); + + return status; +} + +NTSTATUS KphUninstall( + _In_opt_ PWSTR DeviceName + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SC_HANDLE scmHandle; + SC_HANDLE serviceHandle; + + if (!DeviceName) + DeviceName = KPH_DEVICE_SHORT_NAME; + + scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + + if (!scmHandle) + return PhGetLastWin32ErrorAsNtStatus(); + + serviceHandle = OpenService(scmHandle, DeviceName, SERVICE_STOP | DELETE); + + if (serviceHandle) + { + SERVICE_STATUS serviceStatus; + + ControlService(serviceHandle, SERVICE_CONTROL_STOP, &serviceStatus); + + if (!DeleteService(serviceHandle)) + status = PhGetLastWin32ErrorAsNtStatus(); + + CloseServiceHandle(serviceHandle); + } + else + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + + CloseServiceHandle(scmHandle); + + return status; +} + +NTSTATUS KphGetFeatures( + _Out_ PULONG Features + ) +{ + struct + { + PULONG Features; + } input = { Features }; + + return KphpDeviceIoControl( + KPH_GETFEATURES, + &input, + sizeof(input) + ); +} + +NTSTATUS KphVerifyClient( + _In_reads_bytes_(SignatureSize) PUCHAR Signature, + _In_ ULONG SignatureSize + ) +{ + NTSTATUS status; + struct + { + PVOID CodeAddress; + PUCHAR Signature; + ULONG SignatureSize; + } input = { KphpWithKeyApcRoutine, Signature, SignatureSize }; + + status = KphpDeviceIoControl( + KPH_VERIFYCLIENT, + &input, + sizeof(input) + ); + + if (NT_SUCCESS(status)) + PhKphVerified = TRUE; + + return status; +} + +NTSTATUS KphOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PCLIENT_ID ClientId + ) +{ + KPH_OPEN_PROCESS_INPUT input = { ProcessHandle, DesiredAccess, ClientId, 0 }; + + if ((DesiredAccess & KPH_PROCESS_READ_ACCESS) == DesiredAccess) + { + KphpGetL1Key(&input.Key); + return KphpDeviceIoControl( + KPH_OPENPROCESS, + &input, + sizeof(input) + ); + } + else + { + return KphpWithKey(KphKeyLevel2, KphpOpenProcessContinuation, &input); + } +} + +NTSTATUS KphOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ) +{ + KPH_OPEN_PROCESS_TOKEN_INPUT input = { ProcessHandle, DesiredAccess, TokenHandle, 0 }; + + if ((DesiredAccess & KPH_TOKEN_READ_ACCESS) == DesiredAccess) + { + KphpGetL1Key(&input.Key); + return KphpDeviceIoControl( + KPH_OPENPROCESSTOKEN, + &input, + sizeof(input) + ); + } + else + { + return KphpWithKey(KphKeyLevel2, KphpOpenProcessTokenContinuation, &input); + } +} + +NTSTATUS KphOpenProcessJob( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE JobHandle + ) +{ + struct + { + HANDLE ProcessHandle; + ACCESS_MASK DesiredAccess; + PHANDLE JobHandle; + } input = { ProcessHandle, DesiredAccess, JobHandle }; + + return KphpDeviceIoControl( + KPH_OPENPROCESSJOB, + &input, + sizeof(input) + ); +} + +NTSTATUS KphTerminateProcess( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ) +{ + NTSTATUS status; + KPH_TERMINATE_PROCESS_INPUT input = { ProcessHandle, ExitStatus, 0 }; + + status = KphpWithKey(KphKeyLevel2, KphpTerminateProcessContinuation, &input); + + // Check if we're trying to terminate the current process, because kernel-mode can't do it. + if (status == STATUS_CANT_TERMINATE_SELF) + { + RtlExitUserProcess(ExitStatus); + } + + return status; +} + +NTSTATUS KphReadVirtualMemoryUnsafe( + _In_opt_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _Out_writes_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ) +{ + KPH_READ_VIRTUAL_MEMORY_UNSAFE_INPUT input = { ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesRead, 0 }; + + return KphpWithKey(KphKeyLevel2, KphpReadVirtualMemoryUnsafeContinuation, &input); +} + +NTSTATUS KphQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ) +{ + struct + { + HANDLE ProcessHandle; + KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass; + PVOID ProcessInformation; + ULONG ProcessInformationLength; + PULONG ReturnLength; + } input = { ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength }; + + return KphpDeviceIoControl( + KPH_QUERYINFORMATIONPROCESS, + &input, + sizeof(input) + ); +} + +NTSTATUS KphSetInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass, + _In_reads_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength + ) +{ + struct + { + HANDLE ProcessHandle; + KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass; + PVOID ProcessInformation; + ULONG ProcessInformationLength; + } input = { ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength }; + + return KphpDeviceIoControl( + KPH_SETINFORMATIONPROCESS, + &input, + sizeof(input) + ); +} + +NTSTATUS KphOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PCLIENT_ID ClientId + ) +{ + KPH_OPEN_THREAD_INPUT input = { ThreadHandle, DesiredAccess, ClientId, 0 }; + + if ((DesiredAccess & KPH_THREAD_READ_ACCESS) == DesiredAccess) + { + KphpGetL1Key(&input.Key); + return KphpDeviceIoControl( + KPH_OPENTHREAD, + &input, + sizeof(input) + ); + } + else + { + return KphpWithKey(KphKeyLevel2, KphpOpenThreadContinuation, &input); + } +} + +NTSTATUS KphOpenThreadProcess( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE ProcessHandle + ) +{ + struct + { + HANDLE ThreadHandle; + ACCESS_MASK DesiredAccess; + PHANDLE ProcessHandle; + } input = { ThreadHandle, DesiredAccess, ProcessHandle }; + + return KphpDeviceIoControl( + KPH_OPENTHREADPROCESS, + &input, + sizeof(input) + ); +} + +NTSTATUS KphCaptureStackBackTraceThread( + _In_ HANDLE ThreadHandle, + _In_ ULONG FramesToSkip, + _In_ ULONG FramesToCapture, + _Out_writes_(FramesToCapture) PVOID *BackTrace, + _Out_opt_ PULONG CapturedFrames, + _Out_opt_ PULONG BackTraceHash + ) +{ + struct + { + HANDLE ThreadHandle; + ULONG FramesToSkip; + ULONG FramesToCapture; + PVOID *BackTrace; + PULONG CapturedFrames; + PULONG BackTraceHash; + } input = { ThreadHandle, FramesToSkip, FramesToCapture, BackTrace, CapturedFrames, BackTraceHash }; + + return KphpDeviceIoControl( + KPH_CAPTURESTACKBACKTRACETHREAD, + &input, + sizeof(input) + ); +} + +NTSTATUS KphQueryInformationThread( + _In_ HANDLE ThreadHandle, + _In_ KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + _Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength, + _Out_opt_ PULONG ReturnLength + ) +{ + struct + { + HANDLE ThreadHandle; + KPH_THREAD_INFORMATION_CLASS ThreadInformationClass; + PVOID ThreadInformation; + ULONG ThreadInformationLength; + PULONG ReturnLength; + } input = { ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength, ReturnLength }; + + return KphpDeviceIoControl( + KPH_QUERYINFORMATIONTHREAD, + &input, + sizeof(input) + ); +} + +NTSTATUS KphSetInformationThread( + _In_ HANDLE ThreadHandle, + _In_ KPH_THREAD_INFORMATION_CLASS ThreadInformationClass, + _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength + ) +{ + struct + { + HANDLE ThreadHandle; + KPH_THREAD_INFORMATION_CLASS ThreadInformationClass; + PVOID ThreadInformation; + ULONG ThreadInformationLength; + } input = { ThreadHandle, ThreadInformationClass, ThreadInformation, ThreadInformationLength }; + + return KphpDeviceIoControl( + KPH_SETINFORMATIONTHREAD, + &input, + sizeof(input) + ); +} + +NTSTATUS KphEnumerateProcessHandles( + _In_ HANDLE ProcessHandle, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _In_opt_ ULONG BufferLength, + _Out_opt_ PULONG ReturnLength + ) +{ + struct + { + HANDLE ProcessHandle; + PVOID Buffer; + ULONG BufferLength; + PULONG ReturnLength; + } input = { ProcessHandle, Buffer, BufferLength, ReturnLength }; + + return KphpDeviceIoControl( + KPH_ENUMERATEPROCESSHANDLES, + &input, + sizeof(input) + ); +} + +NTSTATUS KphEnumerateProcessHandles2( + _In_ HANDLE ProcessHandle, + _Out_ PKPH_PROCESS_HANDLE_INFORMATION *Handles + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 2048; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = KphEnumerateProcessHandles( + ProcessHandle, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_BUFFER_TOO_SMALL) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *Handles = buffer; + + return status; +} + +NTSTATUS KphQueryInformationObject( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ) +{ + struct + { + HANDLE ProcessHandle; + HANDLE Handle; + KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass; + PVOID ObjectInformation; + ULONG ObjectInformationLength; + PULONG ReturnLength; + } input = { ProcessHandle, Handle, ObjectInformationClass, ObjectInformation, ObjectInformationLength, ReturnLength }; + + return KphpDeviceIoControl( + KPH_QUERYINFORMATIONOBJECT, + &input, + sizeof(input) + ); +} + +NTSTATUS KphSetInformationObject( + _In_ HANDLE ProcessHandle, + _In_ HANDLE Handle, + _In_ KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass, + _In_reads_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength + ) +{ + struct + { + HANDLE ProcessHandle; + HANDLE Handle; + KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass; + PVOID ObjectInformation; + ULONG ObjectInformationLength; + } input = { ProcessHandle, Handle, ObjectInformationClass, ObjectInformation, ObjectInformationLength }; + + return KphpDeviceIoControl( + KPH_SETINFORMATIONOBJECT, + &input, + sizeof(input) + ); +} + +NTSTATUS KphOpenDriver( + _Out_ PHANDLE DriverHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ) +{ + struct + { + PHANDLE DriverHandle; + ACCESS_MASK DesiredAccess; + POBJECT_ATTRIBUTES ObjectAttributes; + } input = { DriverHandle, DesiredAccess, ObjectAttributes }; + + return KphpDeviceIoControl( + KPH_OPENDRIVER, + &input, + sizeof(input) + ); +} + +NTSTATUS KphQueryInformationDriver( + _In_ HANDLE DriverHandle, + _In_ DRIVER_INFORMATION_CLASS DriverInformationClass, + _Out_writes_bytes_(DriverInformationLength) PVOID DriverInformation, + _In_ ULONG DriverInformationLength, + _Out_opt_ PULONG ReturnLength + ) +{ + struct + { + HANDLE DriverHandle; + DRIVER_INFORMATION_CLASS DriverInformationClass; + PVOID DriverInformation; + ULONG DriverInformationLength; + PULONG ReturnLength; + } input = { DriverHandle, DriverInformationClass, DriverInformation, DriverInformationLength, ReturnLength }; + + return KphpDeviceIoControl( + KPH_QUERYINFORMATIONDRIVER, + &input, + sizeof(input) + ); +} + +NTSTATUS KphpDeviceIoControl( + _In_ ULONG KphControlCode, + _In_ PVOID InBuffer, + _In_ ULONG InBufferLength + ) +{ + IO_STATUS_BLOCK iosb; + + return NtDeviceIoControlFile( + PhKphHandle, + NULL, + NULL, + NULL, + &iosb, + KphControlCode, + InBuffer, + InBufferLength, + NULL, + 0 + ); +} + +VOID KphpWithKeyApcRoutine( + _In_ PVOID ApcContext, + _In_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG Reserved + ) +{ + PKPHP_RETRIEVE_KEY_CONTEXT context = CONTAINING_RECORD(IoStatusBlock, KPHP_RETRIEVE_KEY_CONTEXT, Iosb); + KPH_KEY key = PtrToUlong(ApcContext); + + if (context->Continuation != KphpGetL1KeyContinuation && + context->Continuation != KphpOpenProcessContinuation && + context->Continuation != KphpOpenProcessTokenContinuation && + context->Continuation != KphpTerminateProcessContinuation && + context->Continuation != KphpReadVirtualMemoryUnsafeContinuation && + context->Continuation != KphpOpenThreadContinuation) + { + PhRaiseStatus(STATUS_ACCESS_DENIED); + context->Status = STATUS_ACCESS_DENIED; + return; + } + + context->Status = context->Continuation(key, context->Context); +} + +NTSTATUS KphpWithKey( + _In_ KPH_KEY_LEVEL KeyLevel, + _In_ PKPHP_WITH_KEY_CONTINUATION Continuation, + _In_ PVOID Context + ) +{ + NTSTATUS status; + struct + { + KPH_KEY_LEVEL KeyLevel; + } input = { KeyLevel }; + KPHP_RETRIEVE_KEY_CONTEXT context; + + context.Continuation = Continuation; + context.Context = Context; + context.Status = STATUS_UNSUCCESSFUL; + + status = NtDeviceIoControlFile( + PhKphHandle, + NULL, + KphpWithKeyApcRoutine, + NULL, + &context.Iosb, + KPH_RETRIEVEKEY, + &input, + sizeof(input), + NULL, + 0 + ); + + NtTestAlert(); + + if (!NT_SUCCESS(status)) + return status; + + return context.Status; +} + +NTSTATUS KphpGetL1KeyContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPHP_GET_L1_KEY_CONTEXT context = Context; + + *context->Key = Key; + PhKphL1Key = Key; + + return STATUS_SUCCESS; +} + +NTSTATUS KphpGetL1Key( + _Out_ PKPH_KEY Key + ) +{ + KPHP_GET_L1_KEY_CONTEXT context; + + if (PhKphL1Key) + { + *Key = PhKphL1Key; + return STATUS_SUCCESS; + } + + context.Key = Key; + + return KphpWithKey(KphKeyLevel1, KphpGetL1KeyContinuation, &context); +} + +NTSTATUS KphpOpenProcessContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPH_OPEN_PROCESS_INPUT input = Context; + + input->Key = Key; + + return KphpDeviceIoControl( + KPH_OPENPROCESS, + input, + sizeof(*input) + ); +} + +NTSTATUS KphpOpenProcessTokenContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPH_OPEN_PROCESS_TOKEN_INPUT input = Context; + + input->Key = Key; + + return KphpDeviceIoControl( + KPH_OPENPROCESSTOKEN, + input, + sizeof(*input) + ); +} + +NTSTATUS KphpTerminateProcessContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPH_TERMINATE_PROCESS_INPUT input = Context; + + input->Key = Key; + + return KphpDeviceIoControl( + KPH_TERMINATEPROCESS, + input, + sizeof(*input) + ); +} + +NTSTATUS KphpReadVirtualMemoryUnsafeContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPH_READ_VIRTUAL_MEMORY_UNSAFE_INPUT input = Context; + + input->Key = Key; + + return KphpDeviceIoControl( + KPH_READVIRTUALMEMORYUNSAFE, + input, + sizeof(*input) + ); +} + +NTSTATUS KphpOpenThreadContinuation( + _In_ KPH_KEY Key, + _In_ PVOID Context + ) +{ + PKPH_OPEN_PROCESS_INPUT input = Context; + + input->Key = Key; + + return KphpDeviceIoControl( + KPH_OPENTHREAD, + input, + sizeof(*input) + ); +} diff --git a/phlib/kphdata.c b/phlib/kphdata.c new file mode 100644 index 0000000..b956868 --- /dev/null +++ b/phlib/kphdata.c @@ -0,0 +1,276 @@ +/* + * Process Hacker - + * KProcessHacker dynamic data definitions + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#ifdef _WIN64 + +ULONG KphpGetKernelRevisionNumber( + VOID + ) +{ + ULONG result; + PPH_STRING kernelFileName; + PVOID versionInfo; + VS_FIXEDFILEINFO *rootBlock; + ULONG rootBlockLength; + + result = 0; + kernelFileName = PhGetKernelFileName(); + PhMoveReference(&kernelFileName, PhGetFileName(kernelFileName)); + versionInfo = PhGetFileVersionInfo(kernelFileName->Buffer); + PhDereferenceObject(kernelFileName); + + if (versionInfo && VerQueryValue(versionInfo, L"\\", &rootBlock, &rootBlockLength) && rootBlockLength != 0) + result = rootBlock->dwFileVersionLS & 0xffff; + + PhFree(versionInfo); + + return result; +} + +NTSTATUS KphInitializeDynamicPackage( + _Out_ PKPH_DYN_PACKAGE Package + ) +{ + ULONG majorVersion, minorVersion, servicePack, buildNumber; + + majorVersion = PhOsVersion.dwMajorVersion; + minorVersion = PhOsVersion.dwMinorVersion; + servicePack = PhOsVersion.wServicePackMajor; + buildNumber = PhOsVersion.dwBuildNumber; + + memset(&Package->StructData, -1, sizeof(KPH_DYN_STRUCT_DATA)); + + Package->MajorVersion = (USHORT)majorVersion; + Package->MinorVersion = (USHORT)minorVersion; + Package->ServicePackMajor = (USHORT)servicePack; + Package->BuildNumber = -1; + + // Windows 7, Windows Server 2008 R2 + if (majorVersion == 6 && minorVersion == 1) + { + Package->ResultingNtVersion = PHNT_WIN7; + + if (servicePack == 0) + { + } + else if (servicePack == 1) + { + } + else + { + return STATUS_NOT_SUPPORTED; + } + + Package->StructData.EgeGuid = 0x14; + Package->StructData.EpObjectTable = 0x200; + Package->StructData.EreGuidEntry = 0x10; + Package->StructData.OtName = 0x10; + Package->StructData.OtIndex = 0x28; // now only a UCHAR, not a ULONG + } + // Windows 8, Windows Server 2012 + else if (majorVersion == 6 && minorVersion == 2 && buildNumber == 9200) + { + Package->BuildNumber = 9200; + Package->ResultingNtVersion = PHNT_WIN8; + + Package->StructData.EgeGuid = 0x14; + Package->StructData.EpObjectTable = 0x408; + Package->StructData.EreGuidEntry = 0x10; + Package->StructData.HtHandleContentionEvent = 0x30; + Package->StructData.OtName = 0x10; + Package->StructData.OtIndex = 0x28; + Package->StructData.ObDecodeShift = 19; + Package->StructData.ObAttributesShift = 20; + } + // Windows 8.1, Windows Server 2012 R2 + else if (majorVersion == 6 && minorVersion == 3 && buildNumber == 9600) + { + ULONG revisionNumber = KphpGetKernelRevisionNumber(); + + Package->BuildNumber = 9600; + Package->ResultingNtVersion = PHNT_WINBLUE; + + Package->StructData.EgeGuid = 0x18; + Package->StructData.EpObjectTable = 0x408; + Package->StructData.EreGuidEntry = revisionNumber >= 17736 ? 0x20 : 0x10; + Package->StructData.HtHandleContentionEvent = 0x30; + Package->StructData.OtName = 0x10; + Package->StructData.OtIndex = 0x28; + Package->StructData.ObDecodeShift = 16; + Package->StructData.ObAttributesShift = 17; + } + // Windows 10 + else if (majorVersion == 10 && minorVersion == 0 && buildNumber == 10240) + { + Package->BuildNumber = 10240; + Package->ResultingNtVersion = PHNT_THRESHOLD; + + Package->StructData.EgeGuid = 0x18; + Package->StructData.EpObjectTable = 0x418; + Package->StructData.EreGuidEntry = 0x20; + Package->StructData.HtHandleContentionEvent = 0x30; + Package->StructData.OtName = 0x10; + Package->StructData.OtIndex = 0x28; + Package->StructData.ObDecodeShift = 16; + Package->StructData.ObAttributesShift = 17; + } + else if (majorVersion == 10 && minorVersion == 0 && buildNumber == 10586) + { + Package->BuildNumber = 10586; + Package->ResultingNtVersion = PHNT_THRESHOLD2; + + Package->StructData.EgeGuid = 0x18; + Package->StructData.EpObjectTable = 0x418; + Package->StructData.EreGuidEntry = 0x20; + Package->StructData.HtHandleContentionEvent = 0x30; + Package->StructData.OtName = 0x10; + Package->StructData.OtIndex = 0x28; + Package->StructData.ObDecodeShift = 16; + Package->StructData.ObAttributesShift = 17; + } + else + { + return STATUS_NOT_SUPPORTED; + } + + return STATUS_SUCCESS; +} + +#else + +NTSTATUS KphInitializeDynamicPackage( + _Out_ PKPH_DYN_PACKAGE Package + ) +{ + ULONG majorVersion, minorVersion, servicePack, buildNumber; + + majorVersion = PhOsVersion.dwMajorVersion; + minorVersion = PhOsVersion.dwMinorVersion; + servicePack = PhOsVersion.wServicePackMajor; + buildNumber = PhOsVersion.dwBuildNumber; + + memset(&Package->StructData, -1, sizeof(KPH_DYN_STRUCT_DATA)); + + Package->MajorVersion = (USHORT)majorVersion; + Package->MinorVersion = (USHORT)minorVersion; + Package->ServicePackMajor = (USHORT)servicePack; + Package->BuildNumber = -1; + + // Windows 7, Windows Server 2008 R2 + if (majorVersion == 6 && minorVersion == 1) + { + Package->ResultingNtVersion = PHNT_WIN7; + + if (servicePack == 0) + { + NOTHING; + } + else if (servicePack == 1) + { + NOTHING; + } + else + { + return STATUS_NOT_SUPPORTED; + } + + Package->StructData.EgeGuid = 0xc; + Package->StructData.EpObjectTable = 0xf4; + Package->StructData.EreGuidEntry = 0x8; + Package->StructData.OtName = 0x8; + Package->StructData.OtIndex = 0x14; // now only a UCHAR, not a ULONG + } + // Windows 8, Windows Server 2012 + else if (majorVersion == 6 && minorVersion == 2) + { + Package->ResultingNtVersion = PHNT_WIN8; + + if (servicePack == 0) + { + NOTHING; + } + else + { + return STATUS_NOT_SUPPORTED; + } + + Package->StructData.EgeGuid = 0xc; + Package->StructData.EpObjectTable = 0x150; + Package->StructData.EreGuidEntry = 0x8; + Package->StructData.OtName = 0x8; + Package->StructData.OtIndex = 0x14; + } + // Windows 8.1, Windows Server 2012 R2 + else if (majorVersion == 6 && minorVersion == 3) + { + Package->ResultingNtVersion = PHNT_WINBLUE; + + if (servicePack == 0) + { + NOTHING; + } + else + { + return STATUS_NOT_SUPPORTED; + } + + Package->StructData.EgeGuid = 0xc; + Package->StructData.EpObjectTable = 0x150; + Package->StructData.EreGuidEntry = 0x8; + Package->StructData.OtName = 0x8; + Package->StructData.OtIndex = 0x14; + } + // Windows 10 + else if (majorVersion == 10 && minorVersion == 0 && buildNumber == 10240) + { + Package->BuildNumber = 10240; + Package->ResultingNtVersion = PHNT_THRESHOLD; + + Package->StructData.EgeGuid = 0xc; + Package->StructData.EpObjectTable = 0x154; + Package->StructData.EreGuidEntry = 0x10; + Package->StructData.OtName = 0x8; + Package->StructData.OtIndex = 0x14; + } + else if (majorVersion == 10 && minorVersion == 0 && buildNumber == 10586) + { + Package->BuildNumber = 10586; + Package->ResultingNtVersion = PHNT_THRESHOLD2; + + Package->StructData.EgeGuid = 0xc; + Package->StructData.EpObjectTable = 0x154; + Package->StructData.EreGuidEntry = 0x10; + Package->StructData.OtName = 0x8; + Package->StructData.OtIndex = 0x14; + } + else + { + return STATUS_NOT_SUPPORTED; + } + + return STATUS_SUCCESS; +} + +#endif diff --git a/phlib/lsasup.c b/phlib/lsasup.c new file mode 100644 index 0000000..c810514 --- /dev/null +++ b/phlib/lsasup.c @@ -0,0 +1,480 @@ +/* + * Process Hacker - + * LSA support functions + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * These are functions which communicate with LSA or are support functions. They replace certain + * Win32 security-related functions such as LookupAccountName, LookupAccountSid and + * LookupPrivilege*, which are badly designed. (LSA already allocates the return values for the + * caller, yet the Win32 functions insist on their callers providing their own buffers.) + */ + +#include +#include + +static LSA_HANDLE PhLookupPolicyHandle = NULL; + +NTSTATUS PhOpenLsaPolicy( + _Out_ PLSA_HANDLE PolicyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ PUNICODE_STRING SystemName + ) +{ + OBJECT_ATTRIBUTES oa = { 0 }; + + return LsaOpenPolicy( + SystemName, + &oa, + DesiredAccess, + PolicyHandle + ); +} + +/** + * Retrieves a handle to the local LSA policy with POLICY_LOOKUP_NAMES access. + * + * \remarks Do not close the handle; it is cached. + */ +LSA_HANDLE PhGetLookupPolicyHandle( + VOID + ) +{ + LSA_HANDLE lookupPolicyHandle; + LSA_HANDLE newLookupPolicyHandle; + + lookupPolicyHandle = PhLookupPolicyHandle; + + // If there is no cached handle, open one. + + if (!lookupPolicyHandle) + { + if (NT_SUCCESS(PhOpenLsaPolicy( + &newLookupPolicyHandle, + POLICY_LOOKUP_NAMES, + NULL + ))) + { + // We succeeded in opening a policy handle, and since we did not have a cached handle + // before, we will now store it. + + lookupPolicyHandle = _InterlockedCompareExchangePointer( + &PhLookupPolicyHandle, + newLookupPolicyHandle, + NULL + ); + + if (!lookupPolicyHandle) + { + // Success. Use our handle. + lookupPolicyHandle = newLookupPolicyHandle; + } + else + { + // Someone already placed a handle in the cache. Close our handle and use their + // handle. + LsaClose(newLookupPolicyHandle); + } + } + } + + return lookupPolicyHandle; +} + +/** + * Gets the name of a privilege from its LUID. + * + * \param PrivilegeValue The LUID of a privilege. + * \param PrivilegeName A variable which receives a pointer to a string containing the privilege + * name. You must free the string using PhDereferenceObject() when you no longer need it. + */ +BOOLEAN PhLookupPrivilegeName( + _In_ PLUID PrivilegeValue, + _Out_ PPH_STRING *PrivilegeName + ) +{ + NTSTATUS status; + PUNICODE_STRING name; + + status = LsaLookupPrivilegeName( + PhGetLookupPolicyHandle(), + PrivilegeValue, + &name + ); + + if (!NT_SUCCESS(status)) + return FALSE; + + *PrivilegeName = PhCreateStringFromUnicodeString(name); + LsaFreeMemory(name); + + return TRUE; +} + +/** + * Gets the display name of a privilege from its name. + * + * \param PrivilegeName The name of a privilege. + * \param PrivilegeDisplayName A variable which receives a pointer to a string containing the + * privilege's display name. You must free the string using PhDereferenceObject() when you no longer + * need it. + */ +BOOLEAN PhLookupPrivilegeDisplayName( + _In_ PPH_STRINGREF PrivilegeName, + _Out_ PPH_STRING *PrivilegeDisplayName + ) +{ + NTSTATUS status; + UNICODE_STRING privilegeName; + PUNICODE_STRING displayName; + SHORT language; + + if (!PhStringRefToUnicodeString(PrivilegeName, &privilegeName)) + return FALSE; + + status = LsaLookupPrivilegeDisplayName( + PhGetLookupPolicyHandle(), + &privilegeName, + &displayName, + &language + ); + + if (!NT_SUCCESS(status)) + return FALSE; + + *PrivilegeDisplayName = PhCreateStringFromUnicodeString(displayName); + LsaFreeMemory(displayName); + + return TRUE; +} + +/** + * Gets the LUID of a privilege from its name. + * + * \param PrivilegeName The name of a privilege. + * \param PrivilegeValue A variable which receives the LUID of the privilege. + */ +BOOLEAN PhLookupPrivilegeValue( + _In_ PPH_STRINGREF PrivilegeName, + _Out_ PLUID PrivilegeValue + ) +{ + UNICODE_STRING privilegeName; + + if (!PhStringRefToUnicodeString(PrivilegeName, &privilegeName)) + return FALSE; + + return NT_SUCCESS(LsaLookupPrivilegeValue( + PhGetLookupPolicyHandle(), + &privilegeName, + PrivilegeValue + )); +} + +/** + * Gets information about a SID. + * + * \param Sid A SID to query. + * \param Name A variable which receives a pointer to a string containing the SID's name. You must + * free the string using PhDereferenceObject() when you no longer need it. + * \param DomainName A variable which receives a pointer to a string containing the SID's domain + * name. You must free the string using PhDereferenceObject() when you no longer need it. + * \param NameUse A variable which receives the SID's usage. + */ +NTSTATUS PhLookupSid( + _In_ PSID Sid, + _Out_opt_ PPH_STRING *Name, + _Out_opt_ PPH_STRING *DomainName, + _Out_opt_ PSID_NAME_USE NameUse + ) +{ + NTSTATUS status; + LSA_HANDLE policyHandle; + PLSA_REFERENCED_DOMAIN_LIST referencedDomains; + PLSA_TRANSLATED_NAME names; + + policyHandle = PhGetLookupPolicyHandle(); + + referencedDomains = NULL; + names = NULL; + + if (NT_SUCCESS(status = LsaLookupSids( + policyHandle, + 1, + &Sid, + &referencedDomains, + &names + ))) + { + if (names[0].Use != SidTypeInvalid && names[0].Use != SidTypeUnknown) + { + if (Name) + { + *Name = PhCreateStringFromUnicodeString(&names[0].Name); + } + + if (DomainName) + { + if (names[0].DomainIndex >= 0) + { + PLSA_TRUST_INFORMATION trustInfo; + + trustInfo = &referencedDomains->Domains[names[0].DomainIndex]; + *DomainName = PhCreateStringFromUnicodeString(&trustInfo->Name); + } + else + { + *DomainName = PhReferenceEmptyString(); + } + } + + if (NameUse) + { + *NameUse = names[0].Use; + } + } + else + { + status = STATUS_NONE_MAPPED; + } + } + + // LsaLookupSids allocates memory even if it returns STATUS_NONE_MAPPED. + if (referencedDomains) + LsaFreeMemory(referencedDomains); + if (names) + LsaFreeMemory(names); + + return status; +} + +/** + * Gets information about a name. + * + * \param Name A name to query. + * \param Sid A variable which receives a pointer to a SID. You must free the SID using PhFree() + * when you no longer need it. + * \param DomainName A variable which receives a pointer to a string containing the SID's domain + * name. You must free the string using PhDereferenceObject() when you no longer need it. + * \param NameUse A variable which receives the SID's usage. + */ +NTSTATUS PhLookupName( + _In_ PPH_STRINGREF Name, + _Out_opt_ PSID *Sid, + _Out_opt_ PPH_STRING *DomainName, + _Out_opt_ PSID_NAME_USE NameUse + ) +{ + NTSTATUS status; + LSA_HANDLE policyHandle; + UNICODE_STRING name; + PLSA_REFERENCED_DOMAIN_LIST referencedDomains; + PLSA_TRANSLATED_SID2 sids; + + if (!PhStringRefToUnicodeString(Name, &name)) + return STATUS_NAME_TOO_LONG; + + policyHandle = PhGetLookupPolicyHandle(); + referencedDomains = NULL; + sids = NULL; + + if (NT_SUCCESS(status = LsaLookupNames2( + policyHandle, + 0, + 1, + &name, + &referencedDomains, + &sids + ))) + { + if (sids[0].Use != SidTypeInvalid && sids[0].Use != SidTypeUnknown) + { + if (Sid) + { + PSID sid; + ULONG sidLength; + + sidLength = RtlLengthSid(sids[0].Sid); + sid = PhAllocate(sidLength); + memcpy(sid, sids[0].Sid, sidLength); + + *Sid = sid; + } + + if (DomainName) + { + if (sids[0].DomainIndex >= 0) + { + PLSA_TRUST_INFORMATION trustInfo; + + trustInfo = &referencedDomains->Domains[sids[0].DomainIndex]; + *DomainName = PhCreateStringFromUnicodeString(&trustInfo->Name); + } + else + { + *DomainName = PhReferenceEmptyString(); + } + } + + if (NameUse) + { + *NameUse = sids[0].Use; + } + } + else + { + status = STATUS_NONE_MAPPED; + } + } + + // LsaLookupNames2 allocates memory even if it returns STATUS_NONE_MAPPED. + if (referencedDomains) + LsaFreeMemory(referencedDomains); + if (sids) + LsaFreeMemory(sids); + + return status; +} + +/** + * Gets the name of a SID. + * + * \param Sid A SID to query. + * \param IncludeDomain TRUE to include the domain name, otherwise FALSE. + * \param NameUse A variable which receives the SID's usage. + * + * \return A pointer to a string containing the name of the SID in the following format: + * domain\\name. You must free the string using PhDereferenceObject() when you no longer need it. If + * an error occurs, the function returns NULL. + */ +PPH_STRING PhGetSidFullName( + _In_ PSID Sid, + _In_ BOOLEAN IncludeDomain, + _Out_opt_ PSID_NAME_USE NameUse + ) +{ + NTSTATUS status; + PPH_STRING fullName; + LSA_HANDLE policyHandle; + PLSA_REFERENCED_DOMAIN_LIST referencedDomains; + PLSA_TRANSLATED_NAME names; + + policyHandle = PhGetLookupPolicyHandle(); + + referencedDomains = NULL; + names = NULL; + + if (NT_SUCCESS(status = LsaLookupSids( + policyHandle, + 1, + &Sid, + &referencedDomains, + &names + ))) + { + if (names[0].Use != SidTypeInvalid && names[0].Use != SidTypeUnknown) + { + PWSTR domainNameBuffer; + ULONG domainNameLength; + + if (IncludeDomain && names[0].DomainIndex >= 0) + { + PLSA_TRUST_INFORMATION trustInfo; + + trustInfo = &referencedDomains->Domains[names[0].DomainIndex]; + domainNameBuffer = trustInfo->Name.Buffer; + domainNameLength = trustInfo->Name.Length; + } + else + { + domainNameBuffer = NULL; + domainNameLength = 0; + } + + if (domainNameBuffer && domainNameLength != 0) + { + fullName = PhCreateStringEx(NULL, domainNameLength + sizeof(WCHAR) + names[0].Name.Length); + memcpy(&fullName->Buffer[0], domainNameBuffer, domainNameLength); + fullName->Buffer[domainNameLength / sizeof(WCHAR)] = '\\'; + memcpy(&fullName->Buffer[domainNameLength / sizeof(WCHAR) + 1], names[0].Name.Buffer, names[0].Name.Length); + } + else + { + fullName = PhCreateStringFromUnicodeString(&names[0].Name); + } + + if (NameUse) + { + *NameUse = names[0].Use; + } + } + else + { + fullName = NULL; + } + } + else + { + fullName = NULL; + } + + if (referencedDomains) + LsaFreeMemory(referencedDomains); + if (names) + LsaFreeMemory(names); + + return fullName; +} + +/** + * Gets a SDDL string representation of a SID. + * + * \param Sid A SID to query. + * + * \return A pointer to a string containing the SDDL representation of the SID. You must free the + * string using PhDereferenceObject() when you no longer need it. If an error occurs, the function + * returns NULL. + */ +PPH_STRING PhSidToStringSid( + _In_ PSID Sid + ) +{ + PPH_STRING string; + UNICODE_STRING us; + + string = PhCreateStringEx(NULL, MAX_UNICODE_STACK_BUFFER_LENGTH * sizeof(WCHAR)); + PhStringRefToUnicodeString(&string->sr, &us); + + if (NT_SUCCESS(RtlConvertSidToUnicodeString( + &us, + Sid, + FALSE + ))) + { + string->Length = us.Length; + string->Buffer[us.Length / sizeof(WCHAR)] = 0; + + return string; + } + else + { + return NULL; + } +} diff --git a/phlib/mapimg.c b/phlib/mapimg.c new file mode 100644 index 0000000..2368b09 --- /dev/null +++ b/phlib/mapimg.c @@ -0,0 +1,1218 @@ +/* + * Process Hacker - + * mapped image + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains functions to load and retrieve information for image files (exe, dll). The + * file format for image files is explained in the PE/COFF specification located at: + * + * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + */ + +#include +#include +#include + +VOID PhpMappedImageProbe( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ PVOID Address, + _In_ SIZE_T Length + ); + +ULONG PhpLookupMappedImageExportName( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ PSTR Name + ); + +NTSTATUS PhInitializeMappedImage( + _Out_ PPH_MAPPED_IMAGE MappedImage, + _In_ PVOID ViewBase, + _In_ SIZE_T Size + ) +{ + PIMAGE_DOS_HEADER dosHeader; + ULONG ntHeadersOffset; + + MappedImage->ViewBase = ViewBase; + MappedImage->Size = Size; + + dosHeader = (PIMAGE_DOS_HEADER)ViewBase; + + __try + { + PhpMappedImageProbe(MappedImage, dosHeader, sizeof(IMAGE_DOS_HEADER)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + // Check the initial MZ. + + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return STATUS_INVALID_IMAGE_NOT_MZ; + + // Get a pointer to the NT headers and probe it. + + ntHeadersOffset = (ULONG)dosHeader->e_lfanew; + + if (ntHeadersOffset == 0) + return STATUS_INVALID_IMAGE_FORMAT; + if (ntHeadersOffset >= 0x10000000 || ntHeadersOffset >= Size) + return STATUS_INVALID_IMAGE_FORMAT; + + MappedImage->NtHeaders = (PIMAGE_NT_HEADERS)PTR_ADD_OFFSET(ViewBase, ntHeadersOffset); + + __try + { + PhpMappedImageProbe( + MappedImage, + MappedImage->NtHeaders, + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + ); + PhpMappedImageProbe( + MappedImage, + MappedImage->NtHeaders, + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + + MappedImage->NtHeaders->FileHeader.SizeOfOptionalHeader + + MappedImage->NtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) + ); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + // Check the signature and verify the magic. + + if (MappedImage->NtHeaders->Signature != IMAGE_NT_SIGNATURE) + return STATUS_INVALID_IMAGE_FORMAT; + + MappedImage->Magic = MappedImage->NtHeaders->OptionalHeader.Magic; + + if ( + MappedImage->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && + MappedImage->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC + ) + return STATUS_INVALID_IMAGE_FORMAT; + + // Get a pointer to the first section. + + MappedImage->NumberOfSections = MappedImage->NtHeaders->FileHeader.NumberOfSections; + + MappedImage->Sections = (PIMAGE_SECTION_HEADER)( + ((PCHAR)&MappedImage->NtHeaders->OptionalHeader) + + MappedImage->NtHeaders->FileHeader.SizeOfOptionalHeader + ); + + return STATUS_SUCCESS; +} + +NTSTATUS PhLoadMappedImage( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PPH_MAPPED_IMAGE MappedImage + ) +{ + NTSTATUS status; + + status = PhMapViewOfEntireFile( + FileName, + FileHandle, + ReadOnly, + &MappedImage->ViewBase, + &MappedImage->Size + ); + + if (NT_SUCCESS(status)) + { + status = PhInitializeMappedImage( + MappedImage, + MappedImage->ViewBase, + MappedImage->Size + ); + + if (!NT_SUCCESS(status)) + { + NtUnmapViewOfSection(NtCurrentProcess(), MappedImage->ViewBase); + } + } + + return status; +} + +NTSTATUS PhUnloadMappedImage( + _Inout_ PPH_MAPPED_IMAGE MappedImage + ) +{ + return NtUnmapViewOfSection( + NtCurrentProcess(), + MappedImage->ViewBase + ); +} + +NTSTATUS PhMapViewOfEntireFile( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PVOID *ViewBase, + _Out_ PSIZE_T Size + ) +{ + NTSTATUS status; + BOOLEAN openedFile = FALSE; + LARGE_INTEGER size; + HANDLE sectionHandle = NULL; + SIZE_T viewSize; + PVOID viewBase; + + if (!FileName && !FileHandle) + return STATUS_INVALID_PARAMETER_MIX; + + // Open the file if we weren't supplied a file handle. + if (!FileHandle) + { + status = PhCreateFileWin32( + &FileHandle, + FileName, + ((FILE_EXECUTE | FILE_READ_ATTRIBUTES | FILE_READ_DATA) | + (!ReadOnly ? (FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA) : 0)) | SYNCHRONIZE, + 0, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + return status; + + openedFile = TRUE; + } + + // Get the file size and create the section. + + status = PhGetFileSize(FileHandle, &size); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + status = NtCreateSection( + §ionHandle, + SECTION_ALL_ACCESS, + NULL, + &size, + ReadOnly ? PAGE_EXECUTE_READ : PAGE_EXECUTE_READWRITE, + SEC_COMMIT, + FileHandle + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + // Map the section. + + viewSize = (SIZE_T)size.QuadPart; + viewBase = NULL; + + status = NtMapViewOfSection( + sectionHandle, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + ReadOnly ? PAGE_EXECUTE_READ : PAGE_EXECUTE_READWRITE + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + *ViewBase = viewBase; + *Size = (SIZE_T)size.QuadPart; + +CleanupExit: + if (sectionHandle) + NtClose(sectionHandle); + if (openedFile) + NtClose(FileHandle); + + return status; +} + +VOID PhpMappedImageProbe( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ PVOID Address, + _In_ SIZE_T Length + ) +{ + PhProbeAddress(Address, Length, MappedImage->ViewBase, MappedImage->Size, 1); +} + +PIMAGE_SECTION_HEADER PhMappedImageRvaToSection( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Rva + ) +{ + ULONG i; + + for (i = 0; i < MappedImage->NumberOfSections; i++) + { + if ( + (Rva >= MappedImage->Sections[i].VirtualAddress) && + (Rva < MappedImage->Sections[i].VirtualAddress + MappedImage->Sections[i].SizeOfRawData) + ) + { + return &MappedImage->Sections[i]; + } + } + + return NULL; +} + +PVOID PhMappedImageRvaToVa( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Rva, + _Out_opt_ PIMAGE_SECTION_HEADER *Section + ) +{ + PIMAGE_SECTION_HEADER section; + + section = PhMappedImageRvaToSection(MappedImage, Rva); + + if (!section) + return NULL; + + if (Section) + *Section = section; + + return (PVOID)( + (ULONG_PTR)MappedImage->ViewBase + + (Rva - section->VirtualAddress) + + section->PointerToRawData + ); +} + +BOOLEAN PhGetMappedImageSectionName( + _In_ PIMAGE_SECTION_HEADER Section, + _Out_writes_opt_z_(Count) PSTR Buffer, + _In_ ULONG Count, + _Out_opt_ PULONG ReturnCount + ) +{ + BOOLEAN result; + SIZE_T returnCount; + + result = PhCopyBytesZ( + Section->Name, + IMAGE_SIZEOF_SHORT_NAME, + Buffer, + Count, + &returnCount + ); + + if (ReturnCount) + *ReturnCount = (ULONG)returnCount; + + return result; +} + +NTSTATUS PhGetMappedImageDataEntry( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ ULONG Index, + _Out_ PIMAGE_DATA_DIRECTORY *Entry + ) +{ + if (MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + PIMAGE_OPTIONAL_HEADER32 optionalHeader; + + optionalHeader = (PIMAGE_OPTIONAL_HEADER32)&MappedImage->NtHeaders->OptionalHeader; + + if (Index >= optionalHeader->NumberOfRvaAndSizes) + return STATUS_INVALID_PARAMETER_2; + + *Entry = &optionalHeader->DataDirectory[Index]; + } + else if (MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + { + PIMAGE_OPTIONAL_HEADER64 optionalHeader; + + optionalHeader = (PIMAGE_OPTIONAL_HEADER64)&MappedImage->NtHeaders->OptionalHeader; + + if (Index >= optionalHeader->NumberOfRvaAndSizes) + return STATUS_INVALID_PARAMETER_2; + + *Entry = &optionalHeader->DataDirectory[Index]; + } + else + { + return STATUS_INVALID_PARAMETER; + } + + return STATUS_SUCCESS; +} + +FORCEINLINE NTSTATUS PhpGetMappedImageLoadConfig( + _In_ PPH_MAPPED_IMAGE MappedImage, + _In_ USHORT Magic, + _In_ ULONG ProbeLength, + _Out_ PVOID *LoadConfig + ) +{ + NTSTATUS status; + PIMAGE_DATA_DIRECTORY entry; + PVOID loadConfig; + + if (MappedImage->Magic != Magic) + return STATUS_INVALID_PARAMETER; + + status = PhGetMappedImageDataEntry(MappedImage, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &entry); + + if (!NT_SUCCESS(status)) + return status; + + loadConfig = PhMappedImageRvaToVa(MappedImage, entry->VirtualAddress, NULL); + + if (!loadConfig) + return STATUS_INVALID_PARAMETER; + + __try + { + PhpMappedImageProbe(MappedImage, loadConfig, ProbeLength); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + *LoadConfig = loadConfig; + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageLoadConfig32( + _In_ PPH_MAPPED_IMAGE MappedImage, + _Out_ PIMAGE_LOAD_CONFIG_DIRECTORY32 *LoadConfig + ) +{ + return PhpGetMappedImageLoadConfig( + MappedImage, + IMAGE_NT_OPTIONAL_HDR32_MAGIC, + sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32), + LoadConfig + ); +} + +NTSTATUS PhGetMappedImageLoadConfig64( + _In_ PPH_MAPPED_IMAGE MappedImage, + _Out_ PIMAGE_LOAD_CONFIG_DIRECTORY64 *LoadConfig + ) +{ + return PhpGetMappedImageLoadConfig( + MappedImage, + IMAGE_NT_OPTIONAL_HDR64_MAGIC, + sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64), + LoadConfig + ); +} + +NTSTATUS PhLoadRemoteMappedImage( + _In_ HANDLE ProcessHandle, + _In_ PVOID ViewBase, + _Out_ PPH_REMOTE_MAPPED_IMAGE RemoteMappedImage + ) +{ + NTSTATUS status; + IMAGE_DOS_HEADER dosHeader; + ULONG ntHeadersOffset; + IMAGE_NT_HEADERS32 ntHeaders; + ULONG ntHeadersSize; + + RemoteMappedImage->ViewBase = ViewBase; + + status = NtReadVirtualMemory( + ProcessHandle, + ViewBase, + &dosHeader, + sizeof(IMAGE_DOS_HEADER), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Check the initial MZ. + + if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) + return STATUS_INVALID_IMAGE_NOT_MZ; + + // Get a pointer to the NT headers and read it in for some basic information. + + ntHeadersOffset = (ULONG)dosHeader.e_lfanew; + + if (ntHeadersOffset == 0 || ntHeadersOffset >= 0x10000000) + return STATUS_INVALID_IMAGE_FORMAT; + + status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(ViewBase, ntHeadersOffset), + &ntHeaders, + sizeof(IMAGE_NT_HEADERS32), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Check the signature and verify the magic. + + if (ntHeaders.Signature != IMAGE_NT_SIGNATURE) + return STATUS_INVALID_IMAGE_FORMAT; + + RemoteMappedImage->Magic = ntHeaders.OptionalHeader.Magic; + + if ( + RemoteMappedImage->Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && + RemoteMappedImage->Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC + ) + return STATUS_INVALID_IMAGE_FORMAT; + + // Get the real size and read in the whole thing. + + RemoteMappedImage->NumberOfSections = ntHeaders.FileHeader.NumberOfSections; + ntHeadersSize = FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + + ntHeaders.FileHeader.SizeOfOptionalHeader + + RemoteMappedImage->NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + + if (ntHeadersSize > 1024 * 1024) // 1 MB + return STATUS_INVALID_IMAGE_FORMAT; + + RemoteMappedImage->NtHeaders = PhAllocate(ntHeadersSize); + + status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(ViewBase, ntHeadersOffset), + RemoteMappedImage->NtHeaders, + ntHeadersSize, + NULL + ); + + if (!NT_SUCCESS(status)) + { + PhFree(RemoteMappedImage->NtHeaders); + return status; + } + + RemoteMappedImage->Sections = (PIMAGE_SECTION_HEADER)( + (PCHAR)RemoteMappedImage->NtHeaders + + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + ntHeaders.FileHeader.SizeOfOptionalHeader + ); + + return STATUS_SUCCESS; +} + +NTSTATUS PhUnloadRemoteMappedImage( + _Inout_ PPH_REMOTE_MAPPED_IMAGE RemoteMappedImage + ) +{ + PhFree(RemoteMappedImage->NtHeaders); + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageExports( + _Out_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ PPH_MAPPED_IMAGE MappedImage + ) +{ + NTSTATUS status; + PIMAGE_EXPORT_DIRECTORY exportDirectory; + + Exports->MappedImage = MappedImage; + + // Get a pointer to the export directory. + + status = PhGetMappedImageDataEntry( + MappedImage, + IMAGE_DIRECTORY_ENTRY_EXPORT, + &Exports->DataDirectory + ); + + if (!NT_SUCCESS(status)) + return status; + + exportDirectory = PhMappedImageRvaToVa( + MappedImage, + Exports->DataDirectory->VirtualAddress, + NULL + ); + + if (!exportDirectory) + return STATUS_INVALID_PARAMETER; + + __try + { + PhpMappedImageProbe(MappedImage, exportDirectory, sizeof(IMAGE_EXPORT_DIRECTORY)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + Exports->ExportDirectory = exportDirectory; + Exports->NumberOfEntries = exportDirectory->NumberOfFunctions; + + // Get pointers to the various tables and probe them. + + Exports->AddressTable = (PULONG)PhMappedImageRvaToVa( + MappedImage, + exportDirectory->AddressOfFunctions, + NULL + ); + Exports->NamePointerTable = (PULONG)PhMappedImageRvaToVa( + MappedImage, + exportDirectory->AddressOfNames, + NULL + ); + Exports->OrdinalTable = (PUSHORT)PhMappedImageRvaToVa( + MappedImage, + exportDirectory->AddressOfNameOrdinals, + NULL + ); + + if ( + !Exports->AddressTable || + !Exports->NamePointerTable || + !Exports->OrdinalTable + ) + return STATUS_INVALID_PARAMETER; + + __try + { + PhpMappedImageProbe( + MappedImage, + Exports->AddressTable, + exportDirectory->NumberOfFunctions * sizeof(ULONG) + ); + PhpMappedImageProbe( + MappedImage, + Exports->NamePointerTable, + exportDirectory->NumberOfNames * sizeof(ULONG) + ); + PhpMappedImageProbe( + MappedImage, + Exports->OrdinalTable, + exportDirectory->NumberOfFunctions * sizeof(USHORT) + ); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + // The ordinal and name tables are parallel. + // Getting an index into the name table (e.g. by doing a binary + // search) and indexing into the ordinal table will produce the + // ordinal for that name, *unbiased* (unlike in the specification). + // The unbiased ordinal is an index into the address table. + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageExportEntry( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_EXPORT_ENTRY Entry + ) +{ + PSTR name; + + if (Index >= Exports->ExportDirectory->NumberOfFunctions) + return STATUS_PROCEDURE_NOT_FOUND; + + Entry->Ordinal = Exports->OrdinalTable[Index] + (USHORT)Exports->ExportDirectory->Base; + + if (Index < Exports->ExportDirectory->NumberOfNames) + { + name = PhMappedImageRvaToVa( + Exports->MappedImage, + Exports->NamePointerTable[Index], + NULL + ); + + if (!name) + return STATUS_INVALID_PARAMETER; + + // TODO: Probe the name. + + Entry->Name = name; + } + else + { + Entry->Name = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageExportFunction( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_opt_ PSTR Name, + _In_opt_ USHORT Ordinal, + _Out_ PPH_MAPPED_IMAGE_EXPORT_FUNCTION Function + ) +{ + ULONG rva; + + if (Name) + { + ULONG index; + + index = PhpLookupMappedImageExportName(Exports, Name); + + if (index == -1) + return STATUS_PROCEDURE_NOT_FOUND; + + Ordinal = Exports->OrdinalTable[index] + (USHORT)Exports->ExportDirectory->Base; + } + + Ordinal -= (USHORT)Exports->ExportDirectory->Base; + + if (Ordinal >= Exports->ExportDirectory->NumberOfFunctions) + return STATUS_PROCEDURE_NOT_FOUND; + + rva = Exports->AddressTable[Ordinal]; + + if ( + (rva >= Exports->DataDirectory->VirtualAddress) && + (rva < Exports->DataDirectory->VirtualAddress + Exports->DataDirectory->Size) + ) + { + // This is a forwarder RVA. + + Function->ForwardedName = PhMappedImageRvaToVa( + Exports->MappedImage, + rva, + NULL + ); + + if (!Function->ForwardedName) + return STATUS_INVALID_PARAMETER; + + // TODO: Probe the name. + + Function->Function = NULL; + } + else + { + Function->Function = PhMappedImageRvaToVa( + Exports->MappedImage, + rva, + NULL + ); + Function->ForwardedName = NULL; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageExportFunctionRemote( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_opt_ PSTR Name, + _In_opt_ USHORT Ordinal, + _In_ PVOID RemoteBase, + _Out_ PVOID *Function + ) +{ + ULONG rva; + + if (Name) + { + ULONG index; + + index = PhpLookupMappedImageExportName(Exports, Name); + + if (index == -1) + return STATUS_PROCEDURE_NOT_FOUND; + + Ordinal = Exports->OrdinalTable[index] + (USHORT)Exports->ExportDirectory->Base; + } + + Ordinal -= (USHORT)Exports->ExportDirectory->Base; + + if (Ordinal >= Exports->ExportDirectory->NumberOfFunctions) + return STATUS_PROCEDURE_NOT_FOUND; + + rva = Exports->AddressTable[Ordinal]; + + if ( + (rva >= Exports->DataDirectory->VirtualAddress) && + (rva < Exports->DataDirectory->VirtualAddress + Exports->DataDirectory->Size) + ) + { + // This is a forwarder RVA. Not supported for remote lookup. + return STATUS_NOT_SUPPORTED; + } + else + { + *Function = PTR_ADD_OFFSET(RemoteBase, rva); + } + + return STATUS_SUCCESS; +} + +ULONG PhpLookupMappedImageExportName( + _In_ PPH_MAPPED_IMAGE_EXPORTS Exports, + _In_ PSTR Name + ) +{ + LONG low; + LONG high; + LONG i; + + if (Exports->ExportDirectory->NumberOfNames == 0) + return -1; + + low = 0; + high = Exports->ExportDirectory->NumberOfNames - 1; + + do + { + PSTR name; + INT comparison; + + i = (low + high) / 2; + + name = PhMappedImageRvaToVa( + Exports->MappedImage, + Exports->NamePointerTable[i], + NULL + ); + + if (!name) + return -1; + + // TODO: Probe the name. + + comparison = strcmp(Name, name); + + if (comparison == 0) + return i; + else if (comparison < 0) + high = i - 1; + else + low = i + 1; + } while (low <= high); + + return -1; +} + +NTSTATUS PhGetMappedImageImports( + _Out_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ PPH_MAPPED_IMAGE MappedImage + ) +{ + NTSTATUS status; + PIMAGE_DATA_DIRECTORY dataDirectory; + PIMAGE_IMPORT_DESCRIPTOR descriptor; + ULONG i; + + Imports->MappedImage = MappedImage; + Imports->Flags = 0; + + status = PhGetMappedImageDataEntry( + MappedImage, + IMAGE_DIRECTORY_ENTRY_IMPORT, + &dataDirectory + ); + + if (!NT_SUCCESS(status)) + return status; + + descriptor = PhMappedImageRvaToVa( + MappedImage, + dataDirectory->VirtualAddress, + NULL + ); + + if (!descriptor) + return STATUS_INVALID_PARAMETER; + + Imports->DescriptorTable = descriptor; + + // Do a scan to determine how many import descriptors there are. + + i = 0; + + __try + { + while (TRUE) + { + PhpMappedImageProbe(MappedImage, descriptor, sizeof(IMAGE_IMPORT_DESCRIPTOR)); + + if (descriptor->OriginalFirstThunk == 0 && descriptor->FirstThunk == 0) + break; + + descriptor++; + i++; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + Imports->NumberOfDlls = i; + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageImportDll( + _In_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_IMPORT_DLL ImportDll + ) +{ + ULONG i; + + if (Index >= Imports->NumberOfDlls) + return STATUS_INVALID_PARAMETER_2; + + ImportDll->MappedImage = Imports->MappedImage; + ImportDll->Flags = Imports->Flags; + + if (!(ImportDll->Flags & PH_MAPPED_IMAGE_DELAY_IMPORTS)) + { + ImportDll->Descriptor = &Imports->DescriptorTable[Index]; + + ImportDll->Name = PhMappedImageRvaToVa( + ImportDll->MappedImage, + ImportDll->Descriptor->Name, + NULL + ); + + if (!ImportDll->Name) + return STATUS_INVALID_PARAMETER; + + // TODO: Probe the name. + + if (ImportDll->Descriptor->OriginalFirstThunk) + { + ImportDll->LookupTable = PhMappedImageRvaToVa( + ImportDll->MappedImage, + ImportDll->Descriptor->OriginalFirstThunk, + NULL + ); + } + else + { + ImportDll->LookupTable = PhMappedImageRvaToVa( + ImportDll->MappedImage, + ImportDll->Descriptor->FirstThunk, + NULL + ); + } + } + else + { + ImportDll->DelayDescriptor = &((PImgDelayDescr)Imports->DelayDescriptorTable)[Index]; + + ImportDll->Name = PhMappedImageRvaToVa( + ImportDll->MappedImage, + ((PImgDelayDescr)ImportDll->DelayDescriptor)->rvaDLLName, + NULL + ); + + if (!ImportDll->Name) + return STATUS_INVALID_PARAMETER; + + // TODO: Probe the name. + + ImportDll->LookupTable = PhMappedImageRvaToVa( + ImportDll->MappedImage, + ((PImgDelayDescr)ImportDll->DelayDescriptor)->rvaINT, + NULL + ); + } + + if (!ImportDll->LookupTable) + return STATUS_INVALID_PARAMETER; + + // Do a scan to determine how many entries there are. + + i = 0; + + if (ImportDll->MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + PULONG entry; + + entry = (PULONG)ImportDll->LookupTable; + + __try + { + while (TRUE) + { + PhpMappedImageProbe( + ImportDll->MappedImage, + entry, + sizeof(ULONG) + ); + + if (*entry == 0) + break; + + entry++; + i++; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else if (ImportDll->MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + { + PULONG64 entry; + + entry = (PULONG64)ImportDll->LookupTable; + + __try + { + while (TRUE) + { + PhpMappedImageProbe( + ImportDll->MappedImage, + entry, + sizeof(ULONG64) + ); + + if (*entry == 0) + break; + + entry++; + i++; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + } + else + { + return STATUS_INVALID_PARAMETER; + } + + ImportDll->NumberOfEntries = i; + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageImportEntry( + _In_ PPH_MAPPED_IMAGE_IMPORT_DLL ImportDll, + _In_ ULONG Index, + _Out_ PPH_MAPPED_IMAGE_IMPORT_ENTRY Entry + ) +{ + PIMAGE_IMPORT_BY_NAME importByName; + + if (Index >= ImportDll->NumberOfEntries) + return STATUS_INVALID_PARAMETER_2; + + if (ImportDll->MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + ULONG entry; + + entry = ((PULONG)ImportDll->LookupTable)[Index]; + + // Is this entry using an ordinal? + if (entry & IMAGE_ORDINAL_FLAG32) + { + Entry->Name = NULL; + Entry->Ordinal = (USHORT)IMAGE_ORDINAL32(entry); + + return STATUS_SUCCESS; + } + else + { + importByName = PhMappedImageRvaToVa( + ImportDll->MappedImage, + entry, + NULL + ); + } + } + else if (ImportDll->MappedImage->Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) + { + ULONG64 entry; + + entry = ((PULONG64)ImportDll->LookupTable)[Index]; + + // Is this entry using an ordinal? + if (entry & IMAGE_ORDINAL_FLAG64) + { + Entry->Name = NULL; + Entry->Ordinal = (USHORT)IMAGE_ORDINAL64(entry); + + return STATUS_SUCCESS; + } + else + { + importByName = PhMappedImageRvaToVa( + ImportDll->MappedImage, + (ULONG)entry, + NULL + ); + } + } + else + { + return STATUS_INVALID_PARAMETER; + } + + if (!importByName) + return STATUS_INVALID_PARAMETER; + + __try + { + PhpMappedImageProbe( + ImportDll->MappedImage, + importByName, + sizeof(IMAGE_IMPORT_BY_NAME) + ); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + Entry->Name = (PSTR)importByName->Name; + Entry->NameHint = importByName->Hint; + + // TODO: Probe the name. + + return STATUS_SUCCESS; +} + +NTSTATUS PhGetMappedImageDelayImports( + _Out_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ PPH_MAPPED_IMAGE MappedImage + ) +{ + NTSTATUS status; + PIMAGE_DATA_DIRECTORY dataDirectory; + PImgDelayDescr descriptor; + ULONG i; + + Imports->MappedImage = MappedImage; + Imports->Flags = PH_MAPPED_IMAGE_DELAY_IMPORTS; + + status = PhGetMappedImageDataEntry( + MappedImage, + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, + &dataDirectory + ); + + if (!NT_SUCCESS(status)) + return status; + + descriptor = PhMappedImageRvaToVa( + MappedImage, + dataDirectory->VirtualAddress, + NULL + ); + + if (!descriptor) + return STATUS_INVALID_PARAMETER; + + Imports->DelayDescriptorTable = descriptor; + + // Do a scan to determine how many import descriptors there are. + + i = 0; + + __try + { + while (TRUE) + { + PhpMappedImageProbe(MappedImage, descriptor, sizeof(ImgDelayDescr)); + + if (descriptor->rvaIAT == 0 && descriptor->rvaINT == 0) + break; + + descriptor++; + i++; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + Imports->NumberOfDlls = i; + + return STATUS_SUCCESS; +} + +USHORT PhCheckSum( + _In_ ULONG Sum, + _In_reads_(Count) PUSHORT Buffer, + _In_ ULONG Count + ) +{ + while (Count--) + { + Sum += *Buffer++; + Sum = (Sum >> 16) + (Sum & 0xffff); + } + + Sum = (Sum >> 16) + Sum; + + return (USHORT)Sum; +} + +ULONG PhCheckSumMappedImage( + _In_ PPH_MAPPED_IMAGE MappedImage + ) +{ + ULONG checkSum; + USHORT partialSum; + PUSHORT adjust; + + partialSum = PhCheckSum(0, (PUSHORT)MappedImage->ViewBase, (ULONG)(MappedImage->Size + 1) / 2); + + // This is actually the same for 32-bit and 64-bit executables. + adjust = (PUSHORT)&MappedImage->NtHeaders->OptionalHeader.CheckSum; + + // Subtract the existing check sum (with carry). + partialSum -= partialSum < adjust[0]; + partialSum -= adjust[0]; + partialSum -= partialSum < adjust[1]; + partialSum -= adjust[1]; + + checkSum = partialSum + (ULONG)MappedImage->Size; + + return checkSum; +} diff --git a/phlib/maplib.c b/phlib/maplib.c new file mode 100644 index 0000000..41b80cd --- /dev/null +++ b/phlib/maplib.c @@ -0,0 +1,402 @@ +/* + * Process Hacker - + * mapped library + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains functions to load and retrieve information for library/archive files (lib). + * The file format for archive files is explained in the PE/COFF specification located at: + * + * http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + */ + +#include +#include + +VOID PhpMappedArchiveProbe( + _In_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PVOID Address, + _In_ SIZE_T Length + ); + +NTSTATUS PhpGetMappedArchiveMemberFromHeader( + _In_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PIMAGE_ARCHIVE_MEMBER_HEADER Header, + _Out_ PPH_MAPPED_ARCHIVE_MEMBER Member + ); + +NTSTATUS PhInitializeMappedArchive( + _Out_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PVOID ViewBase, + _In_ SIZE_T Size + ) +{ + NTSTATUS status; + PCHAR start; + + start = (PCHAR)ViewBase; + + memset(MappedArchive, 0, sizeof(PH_MAPPED_ARCHIVE)); + MappedArchive->ViewBase = ViewBase; + MappedArchive->Size = Size; + + __try + { + // Verify the file signature. + + PhpMappedArchiveProbe(MappedArchive, start, IMAGE_ARCHIVE_START_SIZE); + + if (memcmp(start, IMAGE_ARCHIVE_START, IMAGE_ARCHIVE_START_SIZE) != 0) + PhRaiseStatus(STATUS_INVALID_IMAGE_FORMAT); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + // Get the members. + // Note: the names are checked. + + // First linker member + + status = PhpGetMappedArchiveMemberFromHeader( + MappedArchive, + (PIMAGE_ARCHIVE_MEMBER_HEADER)(start + IMAGE_ARCHIVE_START_SIZE), + &MappedArchive->FirstLinkerMember + ); + + if (!NT_SUCCESS(status)) + return status; + + if (MappedArchive->FirstLinkerMember.Type != LinkerArchiveMemberType) + return STATUS_INVALID_PARAMETER; + + MappedArchive->FirstStandardMember = &MappedArchive->FirstLinkerMember; + + // Second linker member + + status = PhGetNextMappedArchiveMember( + &MappedArchive->FirstLinkerMember, + &MappedArchive->SecondLinkerMember + ); + + if (!NT_SUCCESS(status)) + return status; + + if (MappedArchive->SecondLinkerMember.Type != LinkerArchiveMemberType) + return STATUS_INVALID_PARAMETER; + + // Longnames member + // This member doesn't seem to be mandatory, contrary to the specification. + // So we'll check if it's actually a longnames member, and if not, ignore it. + + status = PhGetNextMappedArchiveMember( + &MappedArchive->SecondLinkerMember, + &MappedArchive->LongnamesMember + ); + + if ( + NT_SUCCESS(status) && + MappedArchive->LongnamesMember.Type == LongnamesArchiveMemberType + ) + { + MappedArchive->HasLongnamesMember = TRUE; + MappedArchive->LastStandardMember = &MappedArchive->LongnamesMember; + } + else + { + MappedArchive->LastStandardMember = &MappedArchive->SecondLinkerMember; + } + + return STATUS_SUCCESS; +} + +NTSTATUS PhLoadMappedArchive( + _In_opt_ PWSTR FileName, + _In_opt_ HANDLE FileHandle, + _In_ BOOLEAN ReadOnly, + _Out_ PPH_MAPPED_ARCHIVE MappedArchive + ) +{ + NTSTATUS status; + + status = PhMapViewOfEntireFile( + FileName, + FileHandle, + ReadOnly, + &MappedArchive->ViewBase, + &MappedArchive->Size + ); + + if (NT_SUCCESS(status)) + { + status = PhInitializeMappedArchive( + MappedArchive, + MappedArchive->ViewBase, + MappedArchive->Size + ); + + if (!NT_SUCCESS(status)) + { + NtUnmapViewOfSection(NtCurrentProcess(), MappedArchive->ViewBase); + } + } + + return status; +} + +NTSTATUS PhUnloadMappedArchive( + _Inout_ PPH_MAPPED_ARCHIVE MappedArchive + ) +{ + return NtUnmapViewOfSection( + NtCurrentProcess(), + MappedArchive->ViewBase + ); +} + +VOID PhpMappedArchiveProbe( + _In_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PVOID Address, + _In_ SIZE_T Length + ) +{ + PhProbeAddress(Address, Length, MappedArchive->ViewBase, MappedArchive->Size, 1); +} + +/** + * Gets the next archive member. + * + * \param Member An archive member structure. + * \param NextMember A variable which receives a structure describing the next archive member. This + * pointer may be the same as the pointer specified in \a Member. + */ +NTSTATUS PhGetNextMappedArchiveMember( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, + _Out_ PPH_MAPPED_ARCHIVE_MEMBER NextMember + ) +{ + PIMAGE_ARCHIVE_MEMBER_HEADER nextHeader; + + nextHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)PTR_ADD_OFFSET( + Member->Data, + Member->Size + ); + + // 2 byte alignment. + if ((ULONG_PTR)nextHeader & 0x1) + nextHeader = (PIMAGE_ARCHIVE_MEMBER_HEADER)PTR_ADD_OFFSET(nextHeader, 1); + + return PhpGetMappedArchiveMemberFromHeader( + Member->MappedArchive, + nextHeader, + NextMember + ); +} + +NTSTATUS PhpGetMappedArchiveMemberFromHeader( + _In_ PPH_MAPPED_ARCHIVE MappedArchive, + _In_ PIMAGE_ARCHIVE_MEMBER_HEADER Header, + _Out_ PPH_MAPPED_ARCHIVE_MEMBER Member + ) +{ + WCHAR integerString[11]; + ULONG64 size; + PH_STRINGREF string; + PWSTR digit; + PSTR slash; + + if ((ULONG_PTR)Header >= (ULONG_PTR)MappedArchive->ViewBase + MappedArchive->Size) + return STATUS_NO_MORE_ENTRIES; + + __try + { + PhpMappedArchiveProbe(MappedArchive, Header, sizeof(IMAGE_ARCHIVE_MEMBER_HEADER)); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + Member->MappedArchive = MappedArchive; + Member->Header = Header; + Member->Data = PTR_ADD_OFFSET(Header, sizeof(IMAGE_ARCHIVE_MEMBER_HEADER)); + Member->Type = NormalArchiveMemberType; + + // Read the size string, terminate it after the last digit and parse it. + + if (!PhCopyStringZFromBytes(Header->Size, 10, integerString, 11, NULL)) + return STATUS_INVALID_PARAMETER; + + string.Buffer = integerString; + string.Length = 0; + digit = string.Buffer; + + while (iswdigit(*digit++)) + string.Length += sizeof(WCHAR); + + if (!PhStringToInteger64(&string, 10, &size)) + return STATUS_INVALID_PARAMETER; + + Member->Size = (ULONG)size; + + __try + { + PhpMappedArchiveProbe(MappedArchive, Member->Data, Member->Size); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return GetExceptionCode(); + } + + // Parse the name. + + if (!PhCopyBytesZ(Header->Name, 16, Member->NameBuffer, 20, NULL)) + return STATUS_INVALID_PARAMETER; + + Member->Name = Member->NameBuffer; + + slash = strchr(Member->NameBuffer, '/'); + + if (!slash) + return STATUS_INVALID_PARAMETER; + + // Special names: + // * If the slash is the first character, then this is a linker member. + // * If there is a slash after the slash which is a first character, then this is the longnames + // member. + // * If there are digits after the slash, then the real name is stored in the longnames member. + + if (slash == Member->NameBuffer) + { + if (Member->NameBuffer[1] == '/') + { + // Longnames member. Set the name to "/". + Member->NameBuffer[0] = '/'; + Member->NameBuffer[1] = 0; + + Member->Type = LongnamesArchiveMemberType; + } + else + { + // Linker member. Set the name to "". + Member->NameBuffer[0] = 0; + + Member->Type = LinkerArchiveMemberType; + } + } + else + { + if (isdigit(slash[1])) + { + PSTR digita; + ULONG64 offset64; + ULONG offset; + + // The name is stored in the longnames member. + // Note: we make sure we have the longnames member first. + + if (!MappedArchive->LongnamesMember.Header) + return STATUS_INVALID_PARAMETER; + + // Find the last digit and null terminate the string there. + + digita = slash + 2; + + while (isdigit(*digita)) + digita++; + + *digita = 0; + + // Parse the offset and make sure it lies within the longnames member. + + if (!PhCopyStringZFromBytes(slash + 1, -1, integerString, 11, NULL)) + return STATUS_INVALID_PARAMETER; + PhInitializeStringRefLongHint(&string, integerString); + if (!PhStringToInteger64(&string, 10, &offset64)) + return STATUS_INVALID_PARAMETER; + + offset = (ULONG)offset64; + + if (offset >= MappedArchive->LongnamesMember.Size) + return STATUS_INVALID_PARAMETER; + + // TODO: Probe the name. + Member->Name = (PSTR)PTR_ADD_OFFSET(MappedArchive->LongnamesMember.Data, offset); + } + else + { + // Null terminate the string. + slash[0] = 0; + } + } + + return STATUS_SUCCESS; +} + +BOOLEAN PhIsMappedArchiveMemberShortFormat( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member + ) +{ + PIMAGE_FILE_HEADER header; + + header = (PIMAGE_FILE_HEADER)Member->Data; + + return header->Machine != IMAGE_FILE_MACHINE_UNKNOWN; +} + +NTSTATUS PhGetMappedArchiveImportEntry( + _In_ PPH_MAPPED_ARCHIVE_MEMBER Member, + _Out_ PPH_MAPPED_ARCHIVE_IMPORT_ENTRY Entry + ) +{ + IMPORT_OBJECT_HEADER *importHeader; + + importHeader = (IMPORT_OBJECT_HEADER *)Member->Data; + + if (Member->Type != NormalArchiveMemberType) + return STATUS_INVALID_PARAMETER; + if ( + importHeader->Sig1 != IMAGE_FILE_MACHINE_UNKNOWN || + importHeader->Sig2 != IMPORT_OBJECT_HDR_SIG2 + ) + return STATUS_INVALID_PARAMETER; + + Entry->Type = (BYTE)importHeader->Type; + Entry->NameType = (BYTE)importHeader->NameType; + Entry->Machine = importHeader->Machine; + + // TODO: Probe the name. + Entry->Name = (PSTR)PTR_ADD_OFFSET(importHeader, sizeof(IMPORT_OBJECT_HEADER)); + Entry->DllName = (PSTR)PTR_ADD_OFFSET(Entry->Name, strlen(Entry->Name) + 1); + + // Ordinal/NameHint are union'ed, so these statements are exactly the same. + // It's there in case this changes in the future. + if (Entry->NameType == IMPORT_OBJECT_ORDINAL) + { + Entry->Ordinal = importHeader->Ordinal; + } + else + { + Entry->NameHint = importHeader->Hint; + } + + return STATUS_SUCCESS; +} diff --git a/phlib/md5.c b/phlib/md5.c new file mode 100644 index 0000000..b984655 --- /dev/null +++ b/phlib/md5.c @@ -0,0 +1,225 @@ +/* + * MD5 hash implementation and interface functions + * Copyright (c) 2003-2005, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* This code was modified for Process Hacker. */ + +#include +#include "md5.h" + +void MD5Transform(ULONG buf[4], ULONG in[16]); + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +VOID MD5Init( + _Out_ MD5_CTX *Context + ) +{ + Context->buf[0] = 0x67452301; + Context->buf[1] = 0xefcdab89; + Context->buf[2] = 0x98badcfe; + Context->buf[3] = 0x10325476; + + Context->i[0] = 0; + Context->i[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +VOID MD5Update( + _Inout_ MD5_CTX *Context, + _In_reads_bytes_(Length) UCHAR *Input, + _In_ ULONG Length + ) +{ + ULONG t; + + /* Update bitcount */ + + t = Context->i[0]; + if ((Context->i[0] = t + ((ULONG) Length << 3)) < t) + Context->i[1]++; /* Carry from low to high */ + Context->i[1] += Length >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) Context->in + t; + + t = 64 - t; + if (Length < t) { + memcpy(p, Input, Length); + return; + } + memcpy(p, Input, t); + MD5Transform(Context->buf, (ULONG *) Context->in); + Input += t; + Length -= t; + } + /* Process data in 64-byte chunks */ + + while (Length >= 64) { + memcpy(Context->in, Input, 64); + MD5Transform(Context->buf, (ULONG *) Context->in); + Input += 64; + Length -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(Context->in, Input, Length); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +VOID MD5Final( + _Inout_ MD5_CTX *Context + ) +{ + unsigned int count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (Context->i[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = Context->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + MD5Transform(Context->buf, (ULONG *) Context->in); + + /* Now fill the next block with 56 bytes */ + memset(Context->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + + /* Append length in bits and transform */ + ((ULONG *) Context->in)[14] = Context->i[0]; + ((ULONG *) Context->in)[15] = Context->i[1]; + + MD5Transform(Context->buf, (ULONG *) Context->in); + memcpy(Context->digest, Context->buf, 16); +} + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = _rotl(w, s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(ULONG buf[4], ULONG in[16]) +{ + register ULONG a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/phlib/md5.h b/phlib/md5.h new file mode 100644 index 0000000..692d3f9 --- /dev/null +++ b/phlib/md5.h @@ -0,0 +1,26 @@ +#ifndef _MD5_H +#define _MD5_H + +typedef struct +{ + ULONG i[2]; + ULONG buf[4]; + UCHAR in[64]; + UCHAR digest[16]; +} MD5_CTX; + +VOID MD5Init( + _Out_ MD5_CTX *Context + ); + +VOID MD5Update( + _Inout_ MD5_CTX *Context, + _In_reads_bytes_(Length) UCHAR *Input, + _In_ ULONG Length + ); + +VOID MD5Final( + _Inout_ MD5_CTX *Context + ); + +#endif diff --git a/phlib/native.c b/phlib/native.c new file mode 100644 index 0000000..a29221a --- /dev/null +++ b/phlib/native.c @@ -0,0 +1,6460 @@ +/* + * Process Hacker - + * native wrapper and support functions + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +#define PH_DEVICE_PREFIX_LENGTH 64 +#define PH_DEVICE_MUP_PREFIX_MAX_COUNT 16 + +typedef BOOLEAN (NTAPI *PPHP_ENUM_PROCESS_MODULES_CALLBACK)( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY Entry, + _In_ PVOID AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ); + +typedef BOOLEAN (NTAPI *PPHP_ENUM_PROCESS_MODULES32_CALLBACK)( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY32 Entry, + _In_ ULONG AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ); + +static PH_INITONCE PhDevicePrefixesInitOnce = PH_INITONCE_INIT; + +static UNICODE_STRING PhDevicePrefixes[26]; +static PH_QUEUED_LOCK PhDevicePrefixesLock = PH_QUEUED_LOCK_INIT; + +static PPH_STRING PhDeviceMupPrefixes[PH_DEVICE_MUP_PREFIX_MAX_COUNT] = { 0 }; +static ULONG PhDeviceMupPrefixesCount = 0; +static PH_QUEUED_LOCK PhDeviceMupPrefixesLock = PH_QUEUED_LOCK_INIT; + +static PH_INITONCE PhPredefineKeyInitOnce = PH_INITONCE_INIT; +static UNICODE_STRING PhPredefineKeyNames[PH_KEY_MAXIMUM_PREDEFINE] = +{ + RTL_CONSTANT_STRING(L"\\Registry\\Machine"), + RTL_CONSTANT_STRING(L"\\Registry\\User"), + RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Classes"), + { 0, 0, NULL } +}; +static HANDLE PhPredefineKeyHandles[PH_KEY_MAXIMUM_PREDEFINE] = { 0 }; + +/** + * Queries information about the token of the current process. + */ +PH_TOKEN_ATTRIBUTES PhGetOwnTokenAttributes( + VOID + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static PH_TOKEN_ATTRIBUTES attributes; + + if (PhBeginInitOnce(&initOnce)) + { + if (NT_SUCCESS(NtOpenProcessToken( + NtCurrentProcess(), + TOKEN_QUERY, + &attributes.TokenHandle + ))) + { + BOOLEAN elevated = TRUE; + TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeFull; + + if (WINDOWS_HAS_UAC) + { + PhGetTokenIsElevated(attributes.TokenHandle, &elevated); + PhGetTokenElevationType(attributes.TokenHandle, &elevationType); + } + + attributes.Elevated = elevated; + attributes.ElevationType = elevationType; + } + + PhEndInitOnce(&initOnce); + } + + return attributes; +} + +/** + * Opens a process. + * + * \param ProcessHandle A variable which receives a handle to the process. + * \param DesiredAccess The desired access to the process. + * \param ProcessId The ID of the process. + */ +NTSTATUS PhOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ) +{ + NTSTATUS status; + OBJECT_ATTRIBUTES objectAttributes; + CLIENT_ID clientId; + + clientId.UniqueProcess = ProcessId; + clientId.UniqueThread = NULL; + + if (KphIsVerified() && (DesiredAccess & KPH_PROCESS_READ_ACCESS) == DesiredAccess) + { + status = KphOpenProcess( + ProcessHandle, + DesiredAccess, + &clientId + ); + } + else + { + InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL); + status = NtOpenProcess( + ProcessHandle, + DesiredAccess, + &objectAttributes, + &clientId + ); + + if (status == STATUS_ACCESS_DENIED && KphIsVerified()) + { + status = KphOpenProcess( + ProcessHandle, + DesiredAccess, + &clientId + ); + } + } + + return status; +} + +/** Limited API for untrusted/external code. */ +NTSTATUS PhOpenProcessPublic( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ProcessId + ) +{ + OBJECT_ATTRIBUTES objectAttributes; + CLIENT_ID clientId; + + InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL); + clientId.UniqueProcess = ProcessId; + clientId.UniqueThread = NULL; + + return NtOpenProcess( + ProcessHandle, + DesiredAccess, + &objectAttributes, + &clientId + ); +} + +/** + * Opens a thread. + * + * \param ThreadHandle A variable which receives a handle to the thread. + * \param DesiredAccess The desired access to the thread. + * \param ThreadId The ID of the thread. + */ +NTSTATUS PhOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ThreadId + ) +{ + NTSTATUS status; + OBJECT_ATTRIBUTES objectAttributes; + CLIENT_ID clientId; + + clientId.UniqueProcess = NULL; + clientId.UniqueThread = ThreadId; + + if (KphIsVerified() && (DesiredAccess & KPH_THREAD_READ_ACCESS) == DesiredAccess) + { + status = KphOpenThread( + ThreadHandle, + DesiredAccess, + &clientId + ); + } + else + { + InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL); + status = NtOpenThread( + ThreadHandle, + DesiredAccess, + &objectAttributes, + &clientId + ); + + if (status == STATUS_ACCESS_DENIED && KphIsVerified()) + { + status = KphOpenThread( + ThreadHandle, + DesiredAccess, + &clientId + ); + } + } + + return status; +} + +/** Limited API for untrusted/external code. */ +NTSTATUS PhOpenThreadPublic( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ThreadId + ) +{ + OBJECT_ATTRIBUTES objectAttributes; + CLIENT_ID clientId; + + clientId.UniqueProcess = NULL; + clientId.UniqueThread = ThreadId; + + InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL); + + return NtOpenThread( + ThreadHandle, + DesiredAccess, + &objectAttributes, + &clientId + ); +} + +NTSTATUS PhOpenThreadProcess( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE ProcessHandle + ) +{ + if (KphIsConnected()) + { + return KphOpenThreadProcess( + ThreadHandle, + DesiredAccess, + ProcessHandle + ); + } + else + { + NTSTATUS status; + THREAD_BASIC_INFORMATION basicInfo; + + if (!NT_SUCCESS(status = PhGetThreadBasicInformation( + ThreadHandle, + &basicInfo + ))) + return status; + + return PhOpenProcess( + ProcessHandle, + DesiredAccess, + basicInfo.ClientId.UniqueProcess + ); + } +} + +/** + * Opens a process token. + * + * \param ProcessHandle A handle to a process. + * \param DesiredAccess The desired access to the token. + * \param TokenHandle A variable which receives a handle to the token. + */ +NTSTATUS PhOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ) +{ + NTSTATUS status; + + if (KphIsVerified() && (DesiredAccess & KPH_TOKEN_READ_ACCESS) == DesiredAccess) + { + status = KphOpenProcessToken( + ProcessHandle, + DesiredAccess, + TokenHandle + ); + } + else + { + status = NtOpenProcessToken( + ProcessHandle, + DesiredAccess, + TokenHandle + ); + + if (status == STATUS_ACCESS_DENIED && KphIsVerified()) + { + status = KphOpenProcessToken( + ProcessHandle, + DesiredAccess, + TokenHandle + ); + } + } + + return status; +} + +NTSTATUS PhGetObjectSecurity( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ) +{ + NTSTATUS status; + ULONG bufferSize; + PVOID buffer; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + // This is required (especially for File objects) because some drivers don't seem to handle + // QuerySecurity properly. + memset(buffer, 0, bufferSize); + + status = NtQuerySecurityObject( + Handle, + SecurityInformation, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_BUFFER_TOO_SMALL) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + memset(buffer, 0, bufferSize); + + status = NtQuerySecurityObject( + Handle, + SecurityInformation, + buffer, + bufferSize, + &bufferSize + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *SecurityDescriptor = (PSECURITY_DESCRIPTOR)buffer; + + return status; +} + +NTSTATUS PhSetObjectSecurity( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ) +{ + return NtSetSecurityObject( + Handle, + SecurityInformation, + SecurityDescriptor + ); +} + +/** + * Terminates a process. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_TERMINATE access. + * \param ExitStatus A status value that indicates why the process is being terminated. + */ +NTSTATUS PhTerminateProcess( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ) +{ + NTSTATUS status; + + if (KphIsVerified()) + { + status = KphTerminateProcess( + ProcessHandle, + ExitStatus + ); + + if (status != STATUS_NOT_SUPPORTED) + return status; + } + + return NtTerminateProcess( + ProcessHandle, + ExitStatus + ); +} + +/** Limited API for untrusted/external code. */ +NTSTATUS PhTerminateProcessPublic( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ) +{ + return NtTerminateProcess( + ProcessHandle, + ExitStatus + ); +} + +/** + * Queries variable-sized information for a process. The function allocates a buffer to contain the + * information. + * + * \param ProcessHandle A handle to a process. The access required depends on the information class + * specified. + * \param ProcessInformationClass The information class to retrieve. + * \param Buffer A variable which receives a pointer to a buffer containing the information. You + * must free the buffer using PhFree() when you no longer need it. + */ +NTSTATUS PhpQueryProcessVariableSize( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG returnLength = 0; + + status = NtQueryInformationProcess( + ProcessHandle, + ProcessInformationClass, + NULL, + 0, + &returnLength + ); + + if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL && status != STATUS_INFO_LENGTH_MISMATCH) + return status; + + buffer = PhAllocate(returnLength); + status = NtQueryInformationProcess( + ProcessHandle, + ProcessInformationClass, + buffer, + returnLength, + &returnLength + ); + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +/** + * Gets the file name of the process' image. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param FileName A variable which receives a pointer to a string containing the file name. You + * must free the string using PhDereferenceObject() when you no longer need it. + */ +NTSTATUS PhGetProcessImageFileName( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *FileName + ) +{ + NTSTATUS status; + PUNICODE_STRING fileName; + + status = PhpQueryProcessVariableSize( + ProcessHandle, + ProcessImageFileName, + &fileName + ); + + if (!NT_SUCCESS(status)) + return status; + + *FileName = PhCreateStringFromUnicodeString(fileName); + PhFree(fileName); + + return status; +} + +/** + * Gets the Win32 file name of the process' image. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION access. + * \param FileName A variable which receives a pointer to a string containing the file name. You + * must free the string using PhDereferenceObject() when you no longer need it. + * + * \remarks This function is only available on Windows Vista and above. + */ +NTSTATUS PhGetProcessImageFileNameWin32( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *FileName + ) +{ + NTSTATUS status; + PUNICODE_STRING fileName; + + status = PhpQueryProcessVariableSize( + ProcessHandle, + ProcessImageFileNameWin32, + &fileName + ); + + if (!NT_SUCCESS(status)) + return status; + + *FileName = PhCreateStringFromUnicodeString(fileName); + PhFree(fileName); + + return status; +} + +/** + * Gets a string stored in a process' parameters structure. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. + * \param Offset The string to retrieve. + * \param String A variable which receives a pointer to the requested string. You must free the + * string using PhDereferenceObject() when you no longer need it. + * + * \retval STATUS_INVALID_PARAMETER_2 An invalid value was specified in the Offset parameter. + */ +NTSTATUS PhGetProcessPebString( + _In_ HANDLE ProcessHandle, + _In_ PH_PEB_OFFSET Offset, + _Out_ PPH_STRING *String + ) +{ + NTSTATUS status; + PPH_STRING string; + ULONG offset; + +#define PEB_OFFSET_CASE(Enum, Field) \ + case Enum: offset = FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, Field); break; \ + case Enum | PhpoWow64: offset = FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, Field); break + + switch (Offset) + { + PEB_OFFSET_CASE(PhpoCurrentDirectory, CurrentDirectory); + PEB_OFFSET_CASE(PhpoDllPath, DllPath); + PEB_OFFSET_CASE(PhpoImagePathName, ImagePathName); + PEB_OFFSET_CASE(PhpoCommandLine, CommandLine); + PEB_OFFSET_CASE(PhpoWindowTitle, WindowTitle); + PEB_OFFSET_CASE(PhpoDesktopInfo, DesktopInfo); + PEB_OFFSET_CASE(PhpoShellInfo, ShellInfo); + PEB_OFFSET_CASE(PhpoRuntimeData, RuntimeData); + default: + return STATUS_INVALID_PARAMETER_2; + } + + if (!(Offset & PhpoWow64)) + { + PROCESS_BASIC_INFORMATION basicInfo; + PVOID processParameters; + UNICODE_STRING unicodeString; + + // Get the PEB address. + if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo))) + return status; + + // Read the address of the process parameters. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)), + &processParameters, + sizeof(PVOID), + NULL + ))) + return status; + + // Read the string structure. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters, offset), + &unicodeString, + sizeof(UNICODE_STRING), + NULL + ))) + return status; + + string = PhCreateStringEx(NULL, unicodeString.Length); + + // Read the string contents. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + unicodeString.Buffer, + string->Buffer, + string->Length, + NULL + ))) + { + PhDereferenceObject(string); + return status; + } + } + else + { + PVOID peb32; + ULONG processParameters32; + UNICODE_STRING32 unicodeString32; + + if (!NT_SUCCESS(status = PhGetProcessPeb32(ProcessHandle, &peb32))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)), + &processParameters32, + sizeof(ULONG), + NULL + ))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters32, offset), + &unicodeString32, + sizeof(UNICODE_STRING32), + NULL + ))) + return status; + + string = PhCreateStringEx(NULL, unicodeString32.Length); + + // Read the string contents. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + UlongToPtr(unicodeString32.Buffer), + string->Buffer, + string->Length, + NULL + ))) + { + PhDereferenceObject(string); + return status; + } + } + + *String = string; + + return status; +} + +/** + * Gets a process' command line. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION. Before Windows 8.1, the handle must also have PROCESS_VM_READ + * access. + * \param String A variable which receives a pointer to a string containing the command line. You + * must free the string using PhDereferenceObject() when you no longer need it. + */ +NTSTATUS PhGetProcessCommandLine( + _In_ HANDLE ProcessHandle, + _Out_ PPH_STRING *CommandLine + ) +{ + NTSTATUS status; + + if (WindowsVersion >= WINDOWS_8_1) + { + PUNICODE_STRING commandLine; + + status = PhpQueryProcessVariableSize( + ProcessHandle, + ProcessCommandLineInformation, + &commandLine + ); + + if (NT_SUCCESS(status)) + { + *CommandLine = PhCreateStringFromUnicodeString(commandLine); + PhFree(commandLine); + + return status; + } + } + + return PhGetProcessPebString(ProcessHandle, PhpoCommandLine, CommandLine); +} + +/** + * Gets the window flags and window title of a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION. Before Windows 7 SP1, the handle must also have + * PROCESS_VM_READ access. + * \param WindowFlags A variable which receives the window flags. + * \param WindowTitle A variable which receives a pointer to the window title. You must free the + * string using PhDereferenceObject() when you no longer need it. + */ +NTSTATUS PhGetProcessWindowTitle( + _In_ HANDLE ProcessHandle, + _Out_ PULONG WindowFlags, + _Out_ PPH_STRING *WindowTitle + ) +{ + NTSTATUS status; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; +#endif + ULONG windowFlags; + + if (WindowsVersion >= WINDOWS_7) + { + PPROCESS_WINDOW_INFORMATION windowInfo; + + status = PhpQueryProcessVariableSize( + ProcessHandle, + ProcessWindowInformation, + &windowInfo + ); + + if (NT_SUCCESS(status)) + { + *WindowFlags = windowInfo->WindowFlags; + *WindowTitle = PhCreateStringEx(windowInfo->WindowTitle, windowInfo->WindowTitleLength); + PhFree(windowInfo); + + return status; + } + } + +#ifdef _WIN64 + PhGetProcessIsWow64(ProcessHandle, &isWow64); + + if (!isWow64) +#endif + { + PROCESS_BASIC_INFORMATION basicInfo; + PVOID processParameters; + + // Get the PEB address. + if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo))) + return status; + + // Read the address of the process parameters. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)), + &processParameters, + sizeof(PVOID), + NULL + ))) + return status; + + // Read the window flags. + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, WindowFlags)), + &windowFlags, + sizeof(ULONG), + NULL + ))) + return status; + } +#ifdef _WIN64 + else + { + PVOID peb32; + ULONG processParameters32; + + if (!NT_SUCCESS(status = PhGetProcessPeb32(ProcessHandle, &peb32))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)), + &processParameters32, + sizeof(ULONG), + NULL + ))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters32, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, WindowFlags)), + &windowFlags, + sizeof(ULONG), + NULL + ))) + return status; + } +#endif + +#ifdef _WIN64 + status = PhGetProcessPebString(ProcessHandle, PhpoWindowTitle | (isWow64 ? PhpoWow64 : 0), WindowTitle); +#else + status = PhGetProcessPebString(ProcessHandle, PhpoWindowTitle, WindowTitle); +#endif + + if (NT_SUCCESS(status)) + *WindowFlags = windowFlags; + + return status; +} + +NTSTATUS PhGetProcessDepStatus( + _In_ HANDLE ProcessHandle, + _Out_ PULONG DepStatus + ) +{ + NTSTATUS status; + ULONG executeFlags; + ULONG depStatus; + + if (!NT_SUCCESS(status = PhGetProcessExecuteFlags( + ProcessHandle, + &executeFlags + ))) + return status; + + // Check if execution of data pages is enabled. + if (executeFlags & MEM_EXECUTE_OPTION_ENABLE) + depStatus = 0; + else + depStatus = PH_PROCESS_DEP_ENABLED; + + if (executeFlags & MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION) + depStatus |= PH_PROCESS_DEP_ATL_THUNK_EMULATION_DISABLED; + if (executeFlags & MEM_EXECUTE_OPTION_PERMANENT) + depStatus |= PH_PROCESS_DEP_PERMANENT; + + *DepStatus = depStatus; + + return status; +} + +/** + * Gets a process' environment block. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION and + * PROCESS_VM_READ access. + * \param Flags A combination of flags. + * \li \c PH_GET_PROCESS_ENVIRONMENT_WOW64 Retrieve the environment block from the WOW64 PEB. + * \param Environment A variable which will receive a pointer to the environment block copied from + * the process. You must free the block using PhFreePage() when you no longer need it. + * \param EnvironmentLength A variable which will receive the length of the environment block, in + * bytes. + */ +NTSTATUS PhGetProcessEnvironment( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _Out_ PVOID *Environment, + _Out_ PULONG EnvironmentLength + ) +{ + NTSTATUS status; + PVOID environmentRemote; + MEMORY_BASIC_INFORMATION mbi; + PVOID environment; + ULONG environmentLength; + + if (!(Flags & PH_GET_PROCESS_ENVIRONMENT_WOW64)) + { + PROCESS_BASIC_INFORMATION basicInfo; + PVOID processParameters; + + status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo); + + if (!NT_SUCCESS(status)) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)), + &processParameters, + sizeof(PVOID), + NULL + ))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, Environment)), + &environmentRemote, + sizeof(PVOID), + NULL + ))) + return status; + } + else + { + PVOID peb32; + ULONG processParameters32; + ULONG environmentRemote32; + + status = PhGetProcessPeb32(ProcessHandle, &peb32); + + if (!NT_SUCCESS(status)) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)), + &processParameters32, + sizeof(ULONG), + NULL + ))) + return status; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(processParameters32, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, Environment)), + &environmentRemote32, + sizeof(ULONG), + NULL + ))) + return status; + + environmentRemote = UlongToPtr(environmentRemote32); + } + + if (!NT_SUCCESS(status = NtQueryVirtualMemory( + ProcessHandle, + environmentRemote, + MemoryBasicInformation, + &mbi, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + return status; + + environmentLength = (ULONG)(mbi.RegionSize - + ((ULONG_PTR)environmentRemote - (ULONG_PTR)mbi.BaseAddress)); + + // Read in the entire region of memory. + + environment = PhAllocatePage(environmentLength, NULL); + + if (!environment) + return STATUS_NO_MEMORY; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + environmentRemote, + environment, + environmentLength, + NULL + ))) + { + PhFreePage(environment); + return status; + } + + *Environment = environment; + + if (EnvironmentLength) + *EnvironmentLength = environmentLength; + + return status; +} + +BOOLEAN PhEnumProcessEnvironmentVariables( + _In_ PVOID Environment, + _In_ ULONG EnvironmentLength, + _Inout_ PULONG EnumerationKey, + _Out_ PPH_ENVIRONMENT_VARIABLE Variable + ) +{ + ULONG length; + ULONG startIndex; + PWCHAR name; + ULONG nameLength; + PWCHAR value; + ULONG valueLength; + PWCHAR currentChar; + ULONG currentIndex; + + length = EnvironmentLength / sizeof(WCHAR); + + currentIndex = *EnumerationKey; + currentChar = (PWCHAR)Environment + currentIndex; + startIndex = currentIndex; + name = currentChar; + + // Find the end of the name. + while (TRUE) + { + if (currentIndex >= length) + return FALSE; + if (*currentChar == '=') + break; + if (*currentChar == 0) + return FALSE; // no more variables + + currentIndex++; + currentChar++; + } + + nameLength = currentIndex - startIndex; + + currentIndex++; + currentChar++; + startIndex = currentIndex; + value = currentChar; + + // Find the end of the value. + while (TRUE) + { + if (currentIndex >= length) + return FALSE; + if (*currentChar == 0) + break; + + currentIndex++; + currentChar++; + } + + valueLength = currentIndex - startIndex; + + currentIndex++; + *EnumerationKey = currentIndex; + + Variable->Name.Buffer = name; + Variable->Name.Length = nameLength * sizeof(WCHAR); + Variable->Value.Buffer = value; + Variable->Value.Length = valueLength * sizeof(WCHAR); + + return TRUE; +} + +/** + * Gets the file name of a mapped section. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param BaseAddress The base address of the section view. + * \param FileName A variable which receives a pointer to a string containing the file name of the + * section. You must free the string using PhDereferenceObject() when you no longer need it. + */ +NTSTATUS PhGetProcessMappedFileName( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _Out_ PPH_STRING *FileName + ) +{ + NTSTATUS status; + PVOID buffer; + SIZE_T bufferSize; + SIZE_T returnLength; + PUNICODE_STRING unicodeString; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + + status = NtQueryVirtualMemory( + ProcessHandle, + BaseAddress, + MemoryMappedFilenameInformation, + buffer, + bufferSize, + &returnLength + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + bufferSize = returnLength; + buffer = PhAllocate(bufferSize); + + status = NtQueryVirtualMemory( + ProcessHandle, + BaseAddress, + MemoryMappedFilenameInformation, + buffer, + bufferSize, + &returnLength + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + unicodeString = (PUNICODE_STRING)buffer; + *FileName = PhCreateStringEx( + unicodeString->Buffer, + unicodeString->Length + ); + PhFree(buffer); + + return status; +} + +/** + * Gets working set information for a process. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param WorkingSetInformation A variable which receives a pointer to the information. You must + * free the buffer using PhFree() when you no longer need it. + */ +NTSTATUS PhGetProcessWorkingSetInformation( + _In_ HANDLE ProcessHandle, + _Out_ PMEMORY_WORKING_SET_INFORMATION *WorkingSetInformation + ) +{ + NTSTATUS status; + PVOID buffer; + SIZE_T bufferSize; + + bufferSize = 0x8000; + buffer = PhAllocate(bufferSize); + + while ((status = NtQueryVirtualMemory( + ProcessHandle, + NULL, + MemoryWorkingSetInformation, + buffer, + bufferSize, + NULL + )) == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + + // Fail if we're resizing the buffer to something very large. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *WorkingSetInformation = (PMEMORY_WORKING_SET_INFORMATION)buffer; + + return status; +} + +/** + * Gets working set counters for a process. + * + * \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION + * access. + * \param WsCounters A variable which receives the counters. + */ +NTSTATUS PhGetProcessWsCounters( + _In_ HANDLE ProcessHandle, + _Out_ PPH_PROCESS_WS_COUNTERS WsCounters + ) +{ + NTSTATUS status; + PMEMORY_WORKING_SET_INFORMATION wsInfo; + PH_PROCESS_WS_COUNTERS wsCounters; + ULONG_PTR i; + + if (!NT_SUCCESS(status = PhGetProcessWorkingSetInformation( + ProcessHandle, + &wsInfo + ))) + return status; + + memset(&wsCounters, 0, sizeof(PH_PROCESS_WS_COUNTERS)); + + for (i = 0; i < wsInfo->NumberOfEntries; i++) + { + wsCounters.NumberOfPages++; + + if (wsInfo->WorkingSetInfo[i].ShareCount > 1) + wsCounters.NumberOfSharedPages++; + if (wsInfo->WorkingSetInfo[i].ShareCount == 0) + wsCounters.NumberOfPrivatePages++; + if (wsInfo->WorkingSetInfo[i].Shared) + wsCounters.NumberOfShareablePages++; + } + + PhFree(wsInfo); + + *WsCounters = wsCounters; + + return status; +} + +/** + * Causes a process to load a DLL. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ + * and PROCESS_VM_WRITE access. + * \param FileName The file name of the DLL to inject. + * \param Timeout The timeout, in milliseconds, for the process to load the DLL. + * + * \remarks If the process does not load the DLL before the timeout expires it may crash. Choose the + * timeout value carefully. + */ +NTSTATUS PhInjectDllProcess( + _In_ HANDLE ProcessHandle, + _In_ PWSTR FileName, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ +#ifdef _WIN64 + static PVOID loadLibraryW32 = NULL; +#endif + + NTSTATUS status; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; + BOOLEAN isModule32 = FALSE; + PH_MAPPED_IMAGE mappedImage; +#endif + PVOID threadStart; + PH_STRINGREF fileName; + PVOID baseAddress = NULL; + SIZE_T allocSize; + HANDLE threadHandle; + +#ifdef _WIN64 + PhGetProcessIsWow64(ProcessHandle, &isWow64); + + if (isWow64) + { + if (!NT_SUCCESS(status = PhLoadMappedImage(FileName, NULL, TRUE, &mappedImage))) + return status; + + isModule32 = mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC; + PhUnloadMappedImage(&mappedImage); + } + + if (!isModule32) + { +#endif + threadStart = PhGetModuleProcAddress(L"kernel32.dll", "LoadLibraryW"); +#ifdef _WIN64 + } + else + { + threadStart = loadLibraryW32; + + if (!threadStart) + { + PPH_STRING kernel32FileName; + + kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\kernel32.dll"); + status = PhGetProcedureAddressRemote( + ProcessHandle, + kernel32FileName->Buffer, + "LoadLibraryW", + 0, + &loadLibraryW32, + NULL + ); + PhDereferenceObject(kernel32FileName); + + if (!NT_SUCCESS(status)) + return status; + + threadStart = loadLibraryW32; + } + } +#endif + + PhInitializeStringRefLongHint(&fileName, FileName); + allocSize = fileName.Length + sizeof(WCHAR); + + if (!NT_SUCCESS(status = NtAllocateVirtualMemory( + ProcessHandle, + &baseAddress, + 0, + &allocSize, + MEM_COMMIT, + PAGE_READWRITE + ))) + return status; + + if (!NT_SUCCESS(status = NtWriteVirtualMemory( + ProcessHandle, + baseAddress, + fileName.Buffer, + fileName.Length + sizeof(WCHAR), + NULL + ))) + goto FreeExit; + + // Vista seems to support native threads better than XP. + if (WindowsVersion >= WINDOWS_VISTA) + { + if (!NT_SUCCESS(status = RtlCreateUserThread( + ProcessHandle, + NULL, + FALSE, + 0, + 0, + 0, + (PUSER_THREAD_START_ROUTINE)threadStart, + baseAddress, + &threadHandle, + NULL + ))) + goto FreeExit; + } + else + { + if (!(threadHandle = CreateRemoteThread( + ProcessHandle, + NULL, + 0, + (PTHREAD_START_ROUTINE)threadStart, + baseAddress, + 0, + NULL + ))) + { + status = PhGetLastWin32ErrorAsNtStatus(); + goto FreeExit; + } + } + + // Wait for the thread to finish. + status = NtWaitForSingleObject(threadHandle, FALSE, Timeout); + NtClose(threadHandle); + +FreeExit: + // Size needs to be zero if we're freeing. + allocSize = 0; + NtFreeVirtualMemory( + ProcessHandle, + &baseAddress, + &allocSize, + MEM_RELEASE + ); + + return status; +} + +/** + * Causes a process to unload a DLL. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ + * and PROCESS_VM_WRITE access. + * \param BaseAddress The base address of the DLL to unload. + * \param Timeout The timeout, in milliseconds, for the process to unload the DLL. + */ +NTSTATUS PhUnloadDllProcess( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ +#ifdef _WIN64 + static PVOID ldrUnloadDll32 = NULL; +#endif + + NTSTATUS status; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; + BOOLEAN isModule32 = FALSE; +#endif + HANDLE threadHandle; + THREAD_BASIC_INFORMATION basicInfo; + PVOID threadStart; + +#ifdef _WIN64 + PhGetProcessIsWow64(ProcessHandle, &isWow64); +#endif + + // No point trying to set the load count on Windows 8 and higher, because NT now uses a DAG of + // loader nodes. + if (WindowsVersion < WINDOWS_8) + { + status = PhSetProcessModuleLoadCount( + ProcessHandle, + BaseAddress, + 1 + ); + +#ifdef _WIN64 + if (isWow64 && status == STATUS_DLL_NOT_FOUND) + { + // The DLL might be 32-bit. + status = PhSetProcessModuleLoadCount32( + ProcessHandle, + BaseAddress, + 1 + ); + + if (NT_SUCCESS(status)) + isModule32 = TRUE; + } +#endif + + if (!NT_SUCCESS(status)) + return status; + } + +#ifdef _WIN64 + if (!isModule32) + { +#endif + threadStart = PhGetModuleProcAddress(L"ntdll.dll", "LdrUnloadDll"); +#ifdef _WIN64 + } + else + { + threadStart = ldrUnloadDll32; + + if (!threadStart) + { + PPH_STRING ntdll32FileName; + + ntdll32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\ntdll.dll"); + status = PhGetProcedureAddressRemote( + ProcessHandle, + ntdll32FileName->Buffer, + "LdrUnloadDll", + 0, + &ldrUnloadDll32, + NULL + ); + PhDereferenceObject(ntdll32FileName); + + if (!NT_SUCCESS(status)) + return status; + + threadStart = ldrUnloadDll32; + } + } +#endif + + if (WindowsVersion >= WINDOWS_VISTA) + { + status = RtlCreateUserThread( + ProcessHandle, + NULL, + FALSE, + 0, + 0, + 0, + (PUSER_THREAD_START_ROUTINE)threadStart, + BaseAddress, + &threadHandle, + NULL + ); + } + else + { + if (!(threadHandle = CreateRemoteThread( + ProcessHandle, + NULL, + 0, + (PTHREAD_START_ROUTINE)threadStart, + BaseAddress, + 0, + NULL + ))) + { + status = PhGetLastWin32ErrorAsNtStatus(); + } + } + + if (!NT_SUCCESS(status)) + return status; + + status = NtWaitForSingleObject(threadHandle, FALSE, Timeout); + + if (status == STATUS_WAIT_0) + { + status = PhGetThreadBasicInformation(threadHandle, &basicInfo); + + if (NT_SUCCESS(status)) + status = basicInfo.ExitStatus; + } + + NtClose(threadHandle); + + return status; +} + +// Contributed by dmex +/** + * Sets an environment variable in a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ + * and PROCESS_VM_WRITE access. + * \param Name The name of the environment variable to set. + * \param Value The new value of the environment variable. If this parameter is NULL, the + * environment variable is deleted. + * \param Timeout The timeout, in milliseconds, for the process to set the environment variable. + */ +NTSTATUS PhSetEnvironmentVariableRemote( + _In_ HANDLE ProcessHandle, + _In_ PPH_STRINGREF Name, + _In_opt_ PPH_STRINGREF Value, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + NTSTATUS status; +#ifdef _WIN64 + BOOLEAN isWow64; +#endif + PPH_STRING ntdllFileName = NULL; + PPH_STRING kernel32FileName = NULL; + PVOID nameBaseAddress = NULL; + PVOID valueBaseAddress = NULL; + SIZE_T nameAllocationSize = 0; + SIZE_T valueAllocationSize = 0; + PVOID rtlExitUserThread = NULL; + PVOID setEnvironmentVariableW = NULL; + HANDLE threadHandle = NULL; + + nameAllocationSize = Name->Length + sizeof(WCHAR); + + if (Value) + valueAllocationSize = Value->Length + sizeof(WCHAR); + +#ifdef _WIN64 + if (!NT_SUCCESS(status = PhGetProcessIsWow64(ProcessHandle, &isWow64))) + goto CleanupExit; + + if (isWow64) + { + ntdllFileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\ntdll.dll"); + kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\kernel32.dll"); + } + else + { +#endif + ntdllFileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\System32\\ntdll.dll"); + kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\System32\\kernel32.dll"); +#ifdef _WIN64 + } +#endif + + if (!NT_SUCCESS(status = PhGetProcedureAddressRemote( + ProcessHandle, + ntdllFileName->Buffer, + "RtlExitUserThread", + 0, + &rtlExitUserThread, + NULL + ))) + { + goto CleanupExit; + } + if (!NT_SUCCESS(status = PhGetProcedureAddressRemote( + ProcessHandle, + kernel32FileName->Buffer, + "SetEnvironmentVariableW", + 0, + &setEnvironmentVariableW, + NULL + ))) + { + goto CleanupExit; + } + if (!NT_SUCCESS(status = NtAllocateVirtualMemory( + ProcessHandle, + &nameBaseAddress, + 0, + &nameAllocationSize, + MEM_COMMIT, + PAGE_READWRITE + ))) + { + goto CleanupExit; + } + if (!NT_SUCCESS(status = NtWriteVirtualMemory( + ProcessHandle, + nameBaseAddress, + Name->Buffer, + Name->Length, + NULL + ))) + { + goto CleanupExit; + } + + if (Value) + { + if (!NT_SUCCESS(status = NtAllocateVirtualMemory( + ProcessHandle, + &valueBaseAddress, + 0, + &valueAllocationSize, + MEM_COMMIT, + PAGE_READWRITE + ))) + { + goto CleanupExit; + } + if (!NT_SUCCESS(status = NtWriteVirtualMemory( + ProcessHandle, + valueBaseAddress, + Value->Buffer, + Value->Length, + NULL + ))) + { + goto CleanupExit; + } + } + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (!NT_SUCCESS(status = RtlCreateUserThread( + ProcessHandle, + NULL, + TRUE, + 0, + 0, + 0, + (PUSER_THREAD_START_ROUTINE)rtlExitUserThread, + NULL, + &threadHandle, + NULL + ))) + { + goto CleanupExit; + } + } + else + { + if (!(threadHandle = CreateRemoteThread( + ProcessHandle, + NULL, + 0, + (PTHREAD_START_ROUTINE)rtlExitUserThread, + NULL, + CREATE_SUSPENDED, + NULL + ))) + { + status = PhGetLastWin32ErrorAsNtStatus(); + goto CleanupExit; + } + } + +#ifdef _WIN64 + if (isWow64) + { + // NtQueueApcThread doesn't work for WOW64 processes - we need to use RtlQueueApcWow64Thread + // instead. + if (!NT_SUCCESS(status = RtlQueueApcWow64Thread( + threadHandle, + setEnvironmentVariableW, + nameBaseAddress, + valueBaseAddress, + NULL + ))) + { + goto CleanupExit; + } + } + else + { +#endif + if (!NT_SUCCESS(status = NtQueueApcThread( + threadHandle, + setEnvironmentVariableW, + nameBaseAddress, + valueBaseAddress, + NULL + ))) + { + goto CleanupExit; + } +#ifdef _WIN64 + } +#endif + + // This causes our APC to be executed. + NtResumeThread(threadHandle, NULL); + status = NtWaitForSingleObject(threadHandle, FALSE, Timeout); + +CleanupExit: + if (threadHandle) + NtClose(threadHandle); + if (nameBaseAddress) + { + nameAllocationSize = 0; + NtFreeVirtualMemory( + ProcessHandle, + &nameBaseAddress, + &nameAllocationSize, + MEM_RELEASE + ); + } + if (valueBaseAddress) + { + valueAllocationSize = 0; + NtFreeVirtualMemory( + ProcessHandle, + &valueBaseAddress, + &valueAllocationSize, + MEM_RELEASE + ); + } + PhClearReference(&ntdllFileName); + PhClearReference(&kernel32FileName); + + return status; +} + +NTSTATUS PhGetJobProcessIdList( + _In_ HANDLE JobHandle, + _Out_ PJOBOBJECT_BASIC_PROCESS_ID_LIST *ProcessIdList + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + + status = NtQueryInformationJobObject( + JobHandle, + JobObjectBasicProcessIdList, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + status = NtQueryInformationJobObject( + JobHandle, + JobObjectBasicProcessIdList, + buffer, + bufferSize, + NULL + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *ProcessIdList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)buffer; + + return status; +} + +/** + * Queries variable-sized information for a token. The function allocates a buffer to contain the + * information. + * + * \param TokenHandle A handle to a token. The access required depends on the information class + * specified. + * \param TokenInformationClass The information class to retrieve. + * \param Buffer A variable which receives a pointer to a buffer containing the information. You + * must free the buffer using PhFree() when you no longer need it. + */ +NTSTATUS PhpQueryTokenVariableSize( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG returnLength = 0; + + NtQueryInformationToken( + TokenHandle, + TokenInformationClass, + NULL, + 0, + &returnLength + ); + buffer = PhAllocate(returnLength); + status = NtQueryInformationToken( + TokenHandle, + TokenInformationClass, + buffer, + returnLength, + &returnLength + ); + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +/** + * Queries variable-sized information for a token. The function allocates a buffer to contain the + * information. + * + * \param TokenHandle A handle to a token. The access required depends on the information class + * specified. + * \param TokenInformationClass The information class to retrieve. + * \param Buffer A variable which receives a pointer to a buffer containing the information. You + * must free the buffer using PhFree() when you no longer need it. + */ +NTSTATUS PhQueryTokenVariableSize( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _Out_ PVOID *Buffer + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenInformationClass, + Buffer + ); +} + +/** + * Gets a token's user. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param User A variable which receives a pointer to a structure containing the token's user. You + * must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhGetTokenUser( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_USER *User + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenUser, + User + ); +} + +/** + * Gets a token's owner. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param Owner A variable which receives a pointer to a structure containing the token's owner. You + * must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhGetTokenOwner( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_OWNER *Owner + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenOwner, + Owner + ); +} + +/** + * Gets a token's primary group. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param PrimaryGroup A variable which receives a pointer to a structure containing the token's + * primary group. You must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhGetTokenPrimaryGroup( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_PRIMARY_GROUP *PrimaryGroup + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenPrimaryGroup, + PrimaryGroup + ); +} + +/** + * Gets a token's groups. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param Groups A variable which receives a pointer to a structure containing the token's groups. + * You must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhGetTokenGroups( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_GROUPS *Groups + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenGroups, + Groups + ); +} + +/** + * Gets a token's privileges. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param Privileges A variable which receives a pointer to a structure containing the token's + * privileges. You must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhGetTokenPrivileges( + _In_ HANDLE TokenHandle, + _Out_ PTOKEN_PRIVILEGES *Privileges + ) +{ + return PhpQueryTokenVariableSize( + TokenHandle, + TokenPrivileges, + Privileges + ); +} + +NTSTATUS PhSetTokenSessionId( + _In_ HANDLE TokenHandle, + _In_ ULONG SessionId + ) +{ + return NtSetInformationToken( + TokenHandle, + TokenSessionId, + &SessionId, + sizeof(ULONG) + ); +} + +/** + * Modifies a token privilege. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_ADJUST_PRIVILEGES access. + * \param PrivilegeName The name of the privilege to modify. If this parameter is NULL, you must + * specify a LUID in the \a PrivilegeLuid parameter. + * \param PrivilegeLuid The LUID of the privilege to modify. If this parameter is NULL, you must + * specify a name in the \a PrivilegeName parameter. + * \param Attributes The new attributes of the privilege. + */ +BOOLEAN PhSetTokenPrivilege( + _In_ HANDLE TokenHandle, + _In_opt_ PWSTR PrivilegeName, + _In_opt_ PLUID PrivilegeLuid, + _In_ ULONG Attributes + ) +{ + NTSTATUS status; + TOKEN_PRIVILEGES privileges; + + privileges.PrivilegeCount = 1; + privileges.Privileges[0].Attributes = Attributes; + + if (PrivilegeLuid) + { + privileges.Privileges[0].Luid = *PrivilegeLuid; + } + else if (PrivilegeName) + { + PH_STRINGREF privilegeName; + + PhInitializeStringRef(&privilegeName, PrivilegeName); + + if (!PhLookupPrivilegeValue( + &privilegeName, + &privileges.Privileges[0].Luid + )) + return FALSE; + } + else + { + return FALSE; + } + + if (!NT_SUCCESS(status = NtAdjustPrivilegesToken( + TokenHandle, + FALSE, + &privileges, + 0, + NULL, + NULL + ))) + return FALSE; + + if (status == STATUS_NOT_ALL_ASSIGNED) + return FALSE; + + return TRUE; +} + +BOOLEAN PhSetTokenPrivilege2( + _In_ HANDLE TokenHandle, + _In_ LONG Privilege, + _In_ ULONG Attributes + ) +{ + LUID privilegeLuid; + + privilegeLuid = RtlConvertLongToLuid(Privilege); + + return PhSetTokenPrivilege(TokenHandle, NULL, &privilegeLuid, Attributes); +} + +/** + * Sets whether virtualization is enabled for a token. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_WRITE access. + * \param IsVirtualizationEnabled A boolean indicating whether virtualization is to be enabled for + * the token. + */ +NTSTATUS PhSetTokenIsVirtualizationEnabled( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN IsVirtualizationEnabled + ) +{ + ULONG virtualizationEnabled; + + virtualizationEnabled = IsVirtualizationEnabled; + + return NtSetInformationToken( + TokenHandle, + TokenVirtualizationEnabled, + &virtualizationEnabled, + sizeof(ULONG) + ); +} + +/** + * Gets a token's integrity level. + * + * \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access. + * \param IntegrityLevel A variable which receives the integrity level of the token. + * \param IntegrityString A variable which receives a pointer to a string containing a string + * representation of the integrity level. + */ +NTSTATUS PhGetTokenIntegrityLevel( + _In_ HANDLE TokenHandle, + _Out_opt_ PMANDATORY_LEVEL IntegrityLevel, + _Out_opt_ PWSTR *IntegrityString + ) +{ + NTSTATUS status; + PTOKEN_MANDATORY_LABEL mandatoryLabel; + ULONG subAuthority; + MANDATORY_LEVEL integrityLevel; + PWSTR integrityString; + + status = PhpQueryTokenVariableSize(TokenHandle, TokenIntegrityLevel, &mandatoryLabel); + + if (!NT_SUCCESS(status)) + return status; + + subAuthority = *RtlSubAuthoritySid(mandatoryLabel->Label.Sid, 0); + PhFree(mandatoryLabel); + + switch (subAuthority) + { + case SECURITY_MANDATORY_UNTRUSTED_RID: + integrityLevel = MandatoryLevelUntrusted; + integrityString = L"Untrusted"; + break; + case SECURITY_MANDATORY_LOW_RID: + integrityLevel = MandatoryLevelLow; + integrityString = L"Low"; + break; + case SECURITY_MANDATORY_MEDIUM_RID: + integrityLevel = MandatoryLevelMedium; + integrityString = L"Medium"; + break; + case SECURITY_MANDATORY_HIGH_RID: + integrityLevel = MandatoryLevelHigh; + integrityString = L"High"; + break; + case SECURITY_MANDATORY_SYSTEM_RID: + integrityLevel = MandatoryLevelSystem; + integrityString = L"System"; + break; + case SECURITY_MANDATORY_PROTECTED_PROCESS_RID: + integrityLevel = MandatoryLevelSecureProcess; + integrityString = L"Protected"; + break; + default: + return STATUS_UNSUCCESSFUL; + } + + if (IntegrityLevel) + *IntegrityLevel = integrityLevel; + if (IntegrityString) + *IntegrityString = integrityString; + + return status; +} + +NTSTATUS PhpQueryFileVariableSize( + _In_ HANDLE FileHandle, + _In_ FILE_INFORMATION_CLASS FileInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + PVOID buffer; + ULONG bufferSize = 0x200; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = NtQueryInformationFile( + FileHandle, + &isb, + buffer, + bufferSize, + FileInformationClass + ); + + if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +NTSTATUS PhGetFileSize( + _In_ HANDLE FileHandle, + _Out_ PLARGE_INTEGER Size + ) +{ + NTSTATUS status; + FILE_STANDARD_INFORMATION standardInfo; + IO_STATUS_BLOCK isb; + + status = NtQueryInformationFile( + FileHandle, + &isb, + &standardInfo, + sizeof(FILE_STANDARD_INFORMATION), + FileStandardInformation + ); + + if (!NT_SUCCESS(status)) + return status; + + *Size = standardInfo.EndOfFile; + + return status; +} + +NTSTATUS PhSetFileSize( + _In_ HANDLE FileHandle, + _In_ PLARGE_INTEGER Size + ) +{ + FILE_END_OF_FILE_INFORMATION endOfFileInfo; + IO_STATUS_BLOCK isb; + + endOfFileInfo.EndOfFile = *Size; + + return NtSetInformationFile( + FileHandle, + &isb, + &endOfFileInfo, + sizeof(FILE_END_OF_FILE_INFORMATION), + FileEndOfFileInformation + ); +} + +NTSTATUS PhpQueryTransactionManagerVariableSize( + _In_ HANDLE TransactionManagerHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 0x100; + + if (!NtQueryInformationTransactionManager_Import()) + return STATUS_NOT_SUPPORTED; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = NtQueryInformationTransactionManager_Import()( + TransactionManagerHandle, + TransactionManagerInformationClass, + buffer, + bufferSize, + NULL + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + bufferSize *= 2; + + if (bufferSize > 1 * 1024 * 1024) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +NTSTATUS PhGetTransactionManagerBasicInformation( + _In_ HANDLE TransactionManagerHandle, + _Out_ PTRANSACTIONMANAGER_BASIC_INFORMATION BasicInformation + ) +{ + if (NtQueryInformationTransactionManager_Import()) + { + return NtQueryInformationTransactionManager_Import()( + TransactionManagerHandle, + TransactionManagerBasicInformation, + BasicInformation, + sizeof(TRANSACTIONMANAGER_BASIC_INFORMATION), + NULL + ); + } + else + { + return STATUS_NOT_SUPPORTED; + } +} + +NTSTATUS PhGetTransactionManagerLogFileName( + _In_ HANDLE TransactionManagerHandle, + _Out_ PPH_STRING *LogFileName + ) +{ + NTSTATUS status; + PTRANSACTIONMANAGER_LOGPATH_INFORMATION logPathInfo; + + status = PhpQueryTransactionManagerVariableSize( + TransactionManagerHandle, + TransactionManagerLogPathInformation, + &logPathInfo + ); + + if (!NT_SUCCESS(status)) + return status; + + *LogFileName = PhCreateStringEx( + logPathInfo->LogPath, + logPathInfo->LogPathLength + ); + PhFree(logPathInfo); + + return status; +} + +NTSTATUS PhpQueryTransactionVariableSize( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 0x100; + + if (!NtQueryInformationTransaction_Import()) + return STATUS_NOT_SUPPORTED; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = NtQueryInformationTransaction_Import()( + TransactionHandle, + TransactionInformationClass, + buffer, + bufferSize, + NULL + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + bufferSize *= 2; + + if (bufferSize > 1 * 1024 * 1024) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +NTSTATUS PhGetTransactionBasicInformation( + _In_ HANDLE TransactionHandle, + _Out_ PTRANSACTION_BASIC_INFORMATION BasicInformation + ) +{ + if (NtQueryInformationTransaction_Import()) + { + return NtQueryInformationTransaction_Import()( + TransactionHandle, + TransactionBasicInformation, + BasicInformation, + sizeof(TRANSACTION_BASIC_INFORMATION), + NULL + ); + } + else + { + return STATUS_NOT_SUPPORTED; + } +} + +NTSTATUS PhGetTransactionPropertiesInformation( + _In_ HANDLE TransactionHandle, + _Out_opt_ PLARGE_INTEGER Timeout, + _Out_opt_ TRANSACTION_OUTCOME *Outcome, + _Out_opt_ PPH_STRING *Description + ) +{ + NTSTATUS status; + PTRANSACTION_PROPERTIES_INFORMATION propertiesInfo; + + status = PhpQueryTransactionVariableSize( + TransactionHandle, + TransactionPropertiesInformation, + &propertiesInfo + ); + + if (!NT_SUCCESS(status)) + return status; + + if (Timeout) + { + *Timeout = propertiesInfo->Timeout; + } + + if (Outcome) + { + *Outcome = propertiesInfo->Outcome; + } + + if (Description) + { + *Description = PhCreateStringEx( + propertiesInfo->Description, + propertiesInfo->DescriptionLength + ); + } + + PhFree(propertiesInfo); + + return status; +} + +NTSTATUS PhpQueryResourceManagerVariableSize( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 0x100; + + if (!NtQueryInformationResourceManager_Import()) + return STATUS_NOT_SUPPORTED; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = NtQueryInformationResourceManager_Import()( + ResourceManagerHandle, + ResourceManagerInformationClass, + buffer, + bufferSize, + NULL + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + bufferSize *= 2; + + if (bufferSize > 1 * 1024 * 1024) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +NTSTATUS PhGetResourceManagerBasicInformation( + _In_ HANDLE ResourceManagerHandle, + _Out_opt_ PGUID Guid, + _Out_opt_ PPH_STRING *Description + ) +{ + NTSTATUS status; + PRESOURCEMANAGER_BASIC_INFORMATION basicInfo; + + status = PhpQueryResourceManagerVariableSize( + ResourceManagerHandle, + ResourceManagerBasicInformation, + &basicInfo + ); + + if (!NT_SUCCESS(status)) + return status; + + if (Guid) + { + *Guid = basicInfo->ResourceManagerId; + } + + if (Description) + { + *Description = PhCreateStringEx( + basicInfo->Description, + basicInfo->DescriptionLength + ); + } + + PhFree(basicInfo); + + return status; +} + +NTSTATUS PhGetEnlistmentBasicInformation( + _In_ HANDLE EnlistmentHandle, + _Out_ PENLISTMENT_BASIC_INFORMATION BasicInformation + ) +{ + if (NtQueryInformationEnlistment_Import()) + { + return NtQueryInformationEnlistment_Import()( + EnlistmentHandle, + EnlistmentBasicInformation, + BasicInformation, + sizeof(ENLISTMENT_BASIC_INFORMATION), + NULL + ); + } + else + { + return STATUS_NOT_SUPPORTED; + } +} + +typedef struct _OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT +{ + NTSTATUS Status; + PVOID BaseAddress; + HANDLE DriverHandle; +} OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT, *POPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT; + +BOOLEAN NTAPI PhpOpenDriverByBaseAddressCallback( + _In_ PPH_STRINGREF Name, + _In_ PPH_STRINGREF TypeName, + _In_opt_ PVOID Context + ) +{ + static PH_STRINGREF driverDirectoryName = PH_STRINGREF_INIT(L"\\Driver\\"); + + NTSTATUS status; + POPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT context = Context; + PPH_STRING driverName; + UNICODE_STRING driverNameUs; + OBJECT_ATTRIBUTES objectAttributes; + HANDLE driverHandle; + DRIVER_BASIC_INFORMATION basicInfo; + + driverName = PhConcatStringRef2(&driverDirectoryName, Name); + + if (!PhStringRefToUnicodeString(&driverName->sr, &driverNameUs)) + { + PhDereferenceObject(driverName); + return TRUE; + } + + InitializeObjectAttributes( + &objectAttributes, + &driverNameUs, + 0, + NULL, + NULL + ); + + status = KphOpenDriver(&driverHandle, SYNCHRONIZE, &objectAttributes); + PhDereferenceObject(driverName); + + if (!NT_SUCCESS(status)) + return TRUE; + + status = KphQueryInformationDriver( + driverHandle, + DriverBasicInformation, + &basicInfo, + sizeof(DRIVER_BASIC_INFORMATION), + NULL + ); + + if (NT_SUCCESS(status)) + { + if (basicInfo.DriverStart == context->BaseAddress) + { + context->Status = STATUS_SUCCESS; + context->DriverHandle = driverHandle; + + return FALSE; + } + } + + NtClose(driverHandle); + + return TRUE; +} + +/** + * Opens a driver object using a base address. + * + * \param DriverHandle A variable which receives a handle to the driver object. + * \param BaseAddress The base address of the driver to open. + * + * \retval STATUS_OBJECT_NAME_NOT_FOUND The driver could not be found. + * + * \remarks This function requires a valid KProcessHacker handle. + */ +NTSTATUS PhOpenDriverByBaseAddress( + _Out_ PHANDLE DriverHandle, + _In_ PVOID BaseAddress + ) +{ + NTSTATUS status; + UNICODE_STRING driverDirectoryName; + OBJECT_ATTRIBUTES objectAttributes; + HANDLE driverDirectoryHandle; + OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT context; + + RtlInitUnicodeString( + &driverDirectoryName, + L"\\Driver" + ); + InitializeObjectAttributes( + &objectAttributes, + &driverDirectoryName, + 0, + NULL, + NULL + ); + + if (!NT_SUCCESS(status = NtOpenDirectoryObject( + &driverDirectoryHandle, + DIRECTORY_QUERY, + &objectAttributes + ))) + return status; + + context.Status = STATUS_OBJECT_NAME_NOT_FOUND; + context.BaseAddress = BaseAddress; + + status = PhEnumDirectoryObjects( + driverDirectoryHandle, + PhpOpenDriverByBaseAddressCallback, + &context + ); + NtClose(driverDirectoryHandle); + + if (!NT_SUCCESS(status) && !NT_SUCCESS(context.Status)) + return status; + + if (NT_SUCCESS(context.Status)) + { + *DriverHandle = context.DriverHandle; + } + + return context.Status; +} + +/** + * Queries variable-sized information for a driver. The function allocates a buffer to contain the + * information. + * + * \param DriverHandle A handle to a driver. The access required depends on the information class + * specified. + * \param DriverInformationClass The information class to retrieve. + * \param Buffer A variable which receives a pointer to a buffer containing the information. You + * must free the buffer using PhFree() when you no longer need it. + * + * \remarks This function requires a valid KProcessHacker handle. + */ +NTSTATUS PhpQueryDriverVariableSize( + _In_ HANDLE DriverHandle, + _In_ DRIVER_INFORMATION_CLASS DriverInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG returnLength = 0; + + KphQueryInformationDriver( + DriverHandle, + DriverInformationClass, + NULL, + 0, + &returnLength + ); + buffer = PhAllocate(returnLength); + status = KphQueryInformationDriver( + DriverHandle, + DriverInformationClass, + buffer, + returnLength, + &returnLength + ); + + if (NT_SUCCESS(status)) + { + *Buffer = buffer; + } + else + { + PhFree(buffer); + } + + return status; +} + +/** + * Gets the object name of a driver. + * + * \param DriverHandle A handle to a driver. + * \param Name A variable which receives a pointer to a string containing the object name. You must + * free the string using PhDereferenceObject() when you no longer need it. + * + * \remarks This function requires a valid KProcessHacker handle. + */ +NTSTATUS PhGetDriverName( + _In_ HANDLE DriverHandle, + _Out_ PPH_STRING *Name + ) +{ + NTSTATUS status; + PUNICODE_STRING unicodeString; + + if (!NT_SUCCESS(status = PhpQueryDriverVariableSize( + DriverHandle, + DriverNameInformation, + &unicodeString + ))) + return status; + + *Name = PhCreateStringEx( + unicodeString->Buffer, + unicodeString->Length + ); + PhFree(unicodeString); + + return status; +} + +/** + * Gets the service key name of a driver. + * + * \param DriverHandle A handle to a driver. + * \param ServiceKeyName A variable which receives a pointer to a string containing the service key + * name. You must free the string using PhDereferenceObject() when you no longer need it. + * + * \remarks This function requires a valid KProcessHacker handle. + */ +NTSTATUS PhGetDriverServiceKeyName( + _In_ HANDLE DriverHandle, + _Out_ PPH_STRING *ServiceKeyName + ) +{ + NTSTATUS status; + PUNICODE_STRING unicodeString; + + if (!NT_SUCCESS(status = PhpQueryDriverVariableSize( + DriverHandle, + DriverServiceKeyNameInformation, + &unicodeString + ))) + return status; + + *ServiceKeyName = PhCreateStringEx( + unicodeString->Buffer, + unicodeString->Length + ); + PhFree(unicodeString); + + return status; +} + +NTSTATUS PhpUnloadDriver( + _In_ PPH_STRING ServiceKeyName + ) +{ + static PH_STRINGREF fullServicesKeyName = PH_STRINGREF_INIT(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"); + + NTSTATUS status; + PPH_STRING fullServiceKeyName; + UNICODE_STRING fullServiceKeyNameUs; + HANDLE serviceKeyHandle; + ULONG disposition; + + fullServiceKeyName = PhConcatStringRef2(&fullServicesKeyName, &ServiceKeyName->sr); + + if (!PhStringRefToUnicodeString(&fullServiceKeyName->sr, &fullServiceKeyNameUs)) + { + PhDereferenceObject(fullServiceKeyName); + return STATUS_NAME_TOO_LONG; + } + + if (NT_SUCCESS(status = PhCreateKey( + &serviceKeyHandle, + KEY_WRITE | DELETE, + NULL, + &fullServiceKeyName->sr, + 0, + 0, + &disposition + ))) + { + if (disposition == REG_CREATED_NEW_KEY) + { + static UNICODE_STRING imagePath = RTL_CONSTANT_STRING(L"\\SystemRoot\\system32\\drivers\\ntfs.sys"); + + UNICODE_STRING valueName; + ULONG dword; + + // Set up the required values. + dword = 1; + RtlInitUnicodeString(&valueName, L"ErrorControl"); + NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG)); + RtlInitUnicodeString(&valueName, L"Start"); + NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG)); + RtlInitUnicodeString(&valueName, L"Type"); + NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG)); + + // Use a bogus name. + RtlInitUnicodeString(&valueName, L"ImagePath"); + NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_SZ, imagePath.Buffer, imagePath.Length + 2); + } + + status = NtUnloadDriver(&fullServiceKeyNameUs); + + if (disposition == REG_CREATED_NEW_KEY) + { + // We added values, not subkeys, so this function will work correctly. + NtDeleteKey(serviceKeyHandle); + } + + NtClose(serviceKeyHandle); + } + + PhDereferenceObject(fullServiceKeyName); + + return status; +} + +/** + * Unloads a driver. + * + * \param BaseAddress The base address of the driver. This parameter can be NULL if a value is + * specified in \c Name. + * \param Name The base name of the driver. This parameter can be NULL if a value is specified in + * \c BaseAddress and KProcessHacker is loaded. + * + * \retval STATUS_INVALID_PARAMETER_MIX Both \c BaseAddress and \c Name were null, or \c Name was + * not specified and KProcessHacker is not loaded. + * \retval STATUS_OBJECT_NAME_NOT_FOUND The driver could not be found. + */ +NTSTATUS PhUnloadDriver( + _In_opt_ PVOID BaseAddress, + _In_opt_ PWSTR Name + ) +{ + NTSTATUS status; + HANDLE driverHandle; + PPH_STRING serviceKeyName = NULL; + + if (!BaseAddress && !Name) + return STATUS_INVALID_PARAMETER_MIX; + if (!Name && !KphIsConnected()) + return STATUS_INVALID_PARAMETER_MIX; + + // Try to get the service key name by scanning the Driver directory. + + if (KphIsConnected() && BaseAddress) + { + if (NT_SUCCESS(PhOpenDriverByBaseAddress( + &driverHandle, + BaseAddress + ))) + { + PhGetDriverServiceKeyName(driverHandle, &serviceKeyName); + NtClose(driverHandle); + } + } + + // Use the base name if we didn't get the service key name. + + if (!serviceKeyName && Name) + { + PPH_STRING name; + + name = PhCreateString(Name); + + // Remove the extension if it is present. + if (PhEndsWithString2(name, L".sys", TRUE)) + { + serviceKeyName = PhSubstring(name, 0, name->Length / 2 - 4); + PhDereferenceObject(name); + } + else + { + serviceKeyName = name; + } + } + + if (!serviceKeyName) + return STATUS_OBJECT_NAME_NOT_FOUND; + + status = PhpUnloadDriver(serviceKeyName); + PhDereferenceObject(serviceKeyName); + + return status; +} + +NTSTATUS PhpEnumProcessModules( + _In_ HANDLE ProcessHandle, + _In_ PPHP_ENUM_PROCESS_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + NTSTATUS status; + PROCESS_BASIC_INFORMATION basicInfo; + PPEB_LDR_DATA ldr; + PEB_LDR_DATA pebLdrData; + PLIST_ENTRY startLink; + PLIST_ENTRY currentLink; + ULONG dataTableEntrySize; + LDR_DATA_TABLE_ENTRY currentEntry; + ULONG i; + + // Get the PEB address. + status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo); + + if (!NT_SUCCESS(status)) + return status; + + // Read the address of the loader data. + status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, Ldr)), + &ldr, + sizeof(PVOID), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Read the loader data. + status = NtReadVirtualMemory( + ProcessHandle, + ldr, + &pebLdrData, + sizeof(PEB_LDR_DATA), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (!pebLdrData.Initialized) + return STATUS_UNSUCCESSFUL; + + if (WindowsVersion >= WINDOWS_8) + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN8; + else if (WindowsVersion >= WINDOWS_7) + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN7; + else + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WINXP; + + // Traverse the linked list (in load order). + + i = 0; + startLink = PTR_ADD_OFFSET(ldr, FIELD_OFFSET(PEB_LDR_DATA, InLoadOrderModuleList)); + currentLink = pebLdrData.InLoadOrderModuleList.Flink; + + while ( + currentLink != startLink && + i <= PH_ENUM_PROCESS_MODULES_LIMIT + ) + { + PVOID addressOfEntry; + + addressOfEntry = CONTAINING_RECORD(currentLink, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); + status = NtReadVirtualMemory( + ProcessHandle, + addressOfEntry, + ¤tEntry, + dataTableEntrySize, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Make sure the entry is valid. + if (currentEntry.DllBase) + { + // Execute the callback. + if (!Callback( + ProcessHandle, + ¤tEntry, + addressOfEntry, + Context1, + Context2 + )) + break; + } + + currentLink = currentEntry.InLoadOrderLinks.Flink; + i++; + } + + return status; +} + +BOOLEAN NTAPI PhpEnumProcessModulesCallback( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY Entry, + _In_ PVOID AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + NTSTATUS status; + PPH_ENUM_PROCESS_MODULES_PARAMETERS parameters; + BOOLEAN cont; + PPH_STRING mappedFileName; + PWSTR fullDllNameOriginal; + PWSTR fullDllNameBuffer; + PWSTR baseDllNameOriginal; + PWSTR baseDllNameBuffer; + + parameters = Context1; + mappedFileName = NULL; + + if (parameters->Flags & PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME) + { + PhGetProcessMappedFileName(ProcessHandle, Entry->DllBase, &mappedFileName); + } + + if (mappedFileName) + { + ULONG_PTR indexOfLastBackslash; + + PhStringRefToUnicodeString(&mappedFileName->sr, &Entry->FullDllName); + indexOfLastBackslash = PhFindLastCharInString(mappedFileName, 0, '\\'); + + if (indexOfLastBackslash != -1) + { + Entry->BaseDllName.Buffer = Entry->FullDllName.Buffer + indexOfLastBackslash + 1; + Entry->BaseDllName.Length = Entry->FullDllName.Length - (USHORT)indexOfLastBackslash * 2 - 2; + Entry->BaseDllName.MaximumLength = Entry->BaseDllName.Length; + } + else + { + Entry->BaseDllName = Entry->FullDllName; + } + } + else + { + // Read the full DLL name string and add a null terminator. + + fullDllNameOriginal = Entry->FullDllName.Buffer; + fullDllNameBuffer = PhAllocate(Entry->FullDllName.Length + 2); + Entry->FullDllName.Buffer = fullDllNameBuffer; + + if (NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + fullDllNameOriginal, + fullDllNameBuffer, + Entry->FullDllName.Length, + NULL + ))) + { + fullDllNameBuffer[Entry->FullDllName.Length / 2] = 0; + } + else + { + fullDllNameBuffer[0] = 0; + Entry->FullDllName.Length = 0; + } + + baseDllNameOriginal = Entry->BaseDllName.Buffer; + + // Try to use the buffer we just read in. + if ( + NT_SUCCESS(status) && + (ULONG_PTR)baseDllNameOriginal >= (ULONG_PTR)fullDllNameOriginal && + (ULONG_PTR)baseDllNameOriginal + Entry->BaseDllName.Length >= (ULONG_PTR)baseDllNameOriginal && + (ULONG_PTR)baseDllNameOriginal + Entry->BaseDllName.Length <= (ULONG_PTR)fullDllNameOriginal + Entry->FullDllName.Length + ) + { + baseDllNameBuffer = NULL; + + Entry->BaseDllName.Buffer = (PWCHAR)((ULONG_PTR)Entry->FullDllName.Buffer + + ((ULONG_PTR)baseDllNameOriginal - (ULONG_PTR)fullDllNameOriginal)); + } + else + { + // Read the base DLL name string and add a null terminator. + + baseDllNameBuffer = PhAllocate(Entry->BaseDllName.Length + 2); + Entry->BaseDllName.Buffer = baseDllNameBuffer; + + if (NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + baseDllNameOriginal, + baseDllNameBuffer, + Entry->BaseDllName.Length, + NULL + ))) + { + baseDllNameBuffer[Entry->BaseDllName.Length / 2] = 0; + } + else + { + baseDllNameBuffer[0] = 0; + Entry->BaseDllName.Length = 0; + } + } + } + + // Execute the callback. + cont = parameters->Callback(Entry, parameters->Context); + + if (mappedFileName) + { + PhDereferenceObject(mappedFileName); + } + else + { + PhFree(fullDllNameBuffer); + + if (baseDllNameBuffer) + PhFree(baseDllNameBuffer); + } + + return cont; +} + +/** + * Enumerates the modules loaded by a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. + * \param Callback A callback function which is executed for each process module. + * \param Context A user-defined value to pass to the callback function. + */ +NTSTATUS PhEnumProcessModules( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + PH_ENUM_PROCESS_MODULES_PARAMETERS parameters; + + parameters.Callback = Callback; + parameters.Context = Context; + parameters.Flags = 0; + + return PhEnumProcessModulesEx(ProcessHandle, ¶meters); +} + +/** + * Enumerates the modules loaded by a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. If + * \c PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME is specified in \a Parameters, the handle should + * have PROCESS_QUERY_INFORMATION access. + * \param Parameters The enumeration parameters. + */ +NTSTATUS PhEnumProcessModulesEx( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters + ) +{ + return PhpEnumProcessModules( + ProcessHandle, + PhpEnumProcessModulesCallback, + Parameters, + NULL + ); +} + +typedef struct _SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT +{ + NTSTATUS Status; + PVOID BaseAddress; + ULONG LoadCount; +} SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT, *PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT; + +BOOLEAN NTAPI PhpSetProcessModuleLoadCountCallback( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY Entry, + _In_ PVOID AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context = Context1; + + if (Entry->DllBase == context->BaseAddress) + { + context->Status = NtWriteVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(AddressOfEntry, FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, ObsoleteLoadCount)), + &context->LoadCount, + sizeof(USHORT), + NULL + ); + + return FALSE; + } + + return TRUE; +} + +/** + * Sets the load count of a process module. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ and PROCESS_VM_WRITE access. + * \param BaseAddress The base address of a module. + * \param LoadCount The new load count of the module. + * + * \retval STATUS_DLL_NOT_FOUND The module was not found. + */ +NTSTATUS PhSetProcessModuleLoadCount( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ ULONG LoadCount + ) +{ + NTSTATUS status; + SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context; + + context.Status = STATUS_DLL_NOT_FOUND; + context.BaseAddress = BaseAddress; + context.LoadCount = LoadCount; + + status = PhpEnumProcessModules( + ProcessHandle, + PhpSetProcessModuleLoadCountCallback, + &context, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + return context.Status; +} + +NTSTATUS PhpEnumProcessModules32( + _In_ HANDLE ProcessHandle, + _In_ PPHP_ENUM_PROCESS_MODULES32_CALLBACK Callback, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + NTSTATUS status; + PPEB32 peb; + ULONG ldr; // PEB_LDR_DATA32 *32 + PEB_LDR_DATA32 pebLdrData; + ULONG startLink; // LIST_ENTRY32 *32 + ULONG currentLink; // LIST_ENTRY32 *32 + ULONG dataTableEntrySize; + LDR_DATA_TABLE_ENTRY32 currentEntry; + ULONG i; + + // Get the 32-bit PEB address. + status = PhGetProcessPeb32(ProcessHandle, &peb); + + if (!NT_SUCCESS(status)) + return status; + + if (!peb) + return STATUS_NOT_SUPPORTED; // not a WOW64 process + + // Read the address of the loader data. + status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(peb, FIELD_OFFSET(PEB32, Ldr)), + &ldr, + sizeof(ULONG), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Read the loader data. + status = NtReadVirtualMemory( + ProcessHandle, + UlongToPtr(ldr), + &pebLdrData, + sizeof(PEB_LDR_DATA32), + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + if (!pebLdrData.Initialized) + return STATUS_UNSUCCESSFUL; + + if (WindowsVersion >= WINDOWS_8) + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN8_32; + else if (WindowsVersion >= WINDOWS_7) + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN7_32; + else + dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WINXP_32; + + // Traverse the linked list (in load order). + + i = 0; + startLink = (ULONG)(ldr + FIELD_OFFSET(PEB_LDR_DATA32, InLoadOrderModuleList)); + currentLink = pebLdrData.InLoadOrderModuleList.Flink; + + while ( + currentLink != startLink && + i <= PH_ENUM_PROCESS_MODULES_LIMIT + ) + { + ULONG addressOfEntry; + + addressOfEntry = PtrToUlong(CONTAINING_RECORD(UlongToPtr(currentLink), LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks)); + status = NtReadVirtualMemory( + ProcessHandle, + UlongToPtr(addressOfEntry), + ¤tEntry, + dataTableEntrySize, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + // Make sure the entry is valid. + if (currentEntry.DllBase) + { + // Execute the callback. + if (!Callback( + ProcessHandle, + ¤tEntry, + addressOfEntry, + Context1, + Context2 + )) + break; + } + + currentLink = currentEntry.InLoadOrderLinks.Flink; + i++; + } + + return status; +} + +BOOLEAN NTAPI PhpEnumProcessModules32Callback( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY32 Entry, + _In_ ULONG AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + static PH_STRINGREF system32String = PH_STRINGREF_INIT(L"\\system32\\"); + + PPH_ENUM_PROCESS_MODULES_PARAMETERS parameters; + BOOLEAN cont; + LDR_DATA_TABLE_ENTRY nativeEntry; + PPH_STRING mappedFileName; + PWSTR baseDllNameBuffer; + PWSTR fullDllNameBuffer; + PH_STRINGREF fullDllName; + PH_STRINGREF systemRootString; + + parameters = Context1; + + // Convert the 32-bit entry to a native-sized entry. + + memset(&nativeEntry, 0, sizeof(LDR_DATA_TABLE_ENTRY)); + nativeEntry.DllBase = UlongToPtr(Entry->DllBase); + nativeEntry.EntryPoint = UlongToPtr(Entry->EntryPoint); + nativeEntry.SizeOfImage = Entry->SizeOfImage; + UStr32ToUStr(&nativeEntry.FullDllName, &Entry->FullDllName); + UStr32ToUStr(&nativeEntry.BaseDllName, &Entry->BaseDllName); + nativeEntry.Flags = Entry->Flags; + nativeEntry.ObsoleteLoadCount = Entry->ObsoleteLoadCount; + nativeEntry.TlsIndex = Entry->TlsIndex; + nativeEntry.TimeDateStamp = Entry->TimeDateStamp; + nativeEntry.OriginalBase = Entry->OriginalBase; + nativeEntry.LoadTime = Entry->LoadTime; + nativeEntry.BaseNameHashValue = Entry->BaseNameHashValue; + nativeEntry.LoadReason = Entry->LoadReason; + + mappedFileName = NULL; + + if (parameters->Flags & PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME) + { + PhGetProcessMappedFileName(ProcessHandle, nativeEntry.DllBase, &mappedFileName); + } + + if (mappedFileName) + { + ULONG_PTR indexOfLastBackslash; + + PhStringRefToUnicodeString(&mappedFileName->sr, &nativeEntry.FullDllName); + indexOfLastBackslash = PhFindLastCharInString(mappedFileName, 0, '\\'); + + if (indexOfLastBackslash != -1) + { + nativeEntry.BaseDllName.Buffer = nativeEntry.FullDllName.Buffer + indexOfLastBackslash + 1; + nativeEntry.BaseDllName.Length = nativeEntry.FullDllName.Length - (USHORT)indexOfLastBackslash * 2 - 2; + nativeEntry.BaseDllName.MaximumLength = nativeEntry.BaseDllName.Length; + } + else + { + nativeEntry.BaseDllName = nativeEntry.FullDllName; + } + } + else + { + // Read the base DLL name string and add a null terminator. + + baseDllNameBuffer = PhAllocate(nativeEntry.BaseDllName.Length + 2); + + if (NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + nativeEntry.BaseDllName.Buffer, + baseDllNameBuffer, + nativeEntry.BaseDllName.Length, + NULL + ))) + { + baseDllNameBuffer[nativeEntry.BaseDllName.Length / 2] = 0; + } + else + { + baseDllNameBuffer[0] = 0; + nativeEntry.BaseDllName.Length = 0; + } + + nativeEntry.BaseDllName.Buffer = baseDllNameBuffer; + + // Read the full DLL name string and add a null terminator. + + fullDllNameBuffer = PhAllocate(nativeEntry.FullDllName.Length + 2); + + if (NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + nativeEntry.FullDllName.Buffer, + fullDllNameBuffer, + nativeEntry.FullDllName.Length, + NULL + ))) + { + fullDllNameBuffer[nativeEntry.FullDllName.Length / 2] = 0; + + if (!(parameters->Flags & PH_ENUM_PROCESS_MODULES_DONT_RESOLVE_WOW64_FS)) + { + // WOW64 file system redirection - convert "system32" to "SysWOW64". + if (!(nativeEntry.FullDllName.Length & 1)) // validate the string length + { + fullDllName.Buffer = fullDllNameBuffer; + fullDllName.Length = nativeEntry.FullDllName.Length; + + PhGetSystemRoot(&systemRootString); + + if (PhStartsWithStringRef(&fullDllName, &systemRootString, TRUE)) + { + PhSkipStringRef(&fullDllName, systemRootString.Length); + + if (PhStartsWithStringRef(&fullDllName, &system32String, TRUE)) + { + fullDllName.Buffer[1] = 'S'; + fullDllName.Buffer[4] = 'W'; + fullDllName.Buffer[5] = 'O'; + fullDllName.Buffer[6] = 'W'; + fullDllName.Buffer[7] = '6'; + fullDllName.Buffer[8] = '4'; + } + } + } + } + } + else + { + fullDllNameBuffer[0] = 0; + nativeEntry.FullDllName.Length = 0; + } + + nativeEntry.FullDllName.Buffer = fullDllNameBuffer; + } + + // Execute the callback. + cont = parameters->Callback(&nativeEntry, parameters->Context); + + if (mappedFileName) + { + PhDereferenceObject(mappedFileName); + } + else + { + PhFree(baseDllNameBuffer); + PhFree(fullDllNameBuffer); + } + + return cont; +} + +/** + * Enumerates the 32-bit modules loaded by a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. + * \param Callback A callback function which is executed for each process module. + * \param Context A user-defined value to pass to the callback function. + * + * \retval STATUS_NOT_SUPPORTED The process is not running under WOW64. + * + * \remarks Do not use this function under a 32-bit environment. + */ +NTSTATUS PhEnumProcessModules32( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + PH_ENUM_PROCESS_MODULES_PARAMETERS parameters; + + parameters.Callback = Callback; + parameters.Context = Context; + parameters.Flags = 0; + + return PhEnumProcessModules32Ex(ProcessHandle, ¶meters); +} + +/** + * Enumerates the 32-bit modules loaded by a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. If + * \c PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME is specified in \a Parameters, the handle should + * have PROCESS_QUERY_INFORMATION access. + * \param Parameters The enumeration parameters. + * + * \retval STATUS_NOT_SUPPORTED The process is not running under WOW64. + * + * \remarks Do not use this function under a 32-bit environment. + */ +NTSTATUS PhEnumProcessModules32Ex( + _In_ HANDLE ProcessHandle, + _In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters + ) +{ + return PhpEnumProcessModules32( + ProcessHandle, + PhpEnumProcessModules32Callback, + Parameters, + NULL + ); +} + +BOOLEAN NTAPI PhpSetProcessModuleLoadCount32Callback( + _In_ HANDLE ProcessHandle, + _In_ PLDR_DATA_TABLE_ENTRY32 Entry, + _In_ ULONG AddressOfEntry, + _In_opt_ PVOID Context1, + _In_opt_ PVOID Context2 + ) +{ + PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context = Context1; + + if (UlongToPtr(Entry->DllBase) == context->BaseAddress) + { + context->Status = NtWriteVirtualMemory( + ProcessHandle, + UlongToPtr(AddressOfEntry + FIELD_OFFSET(LDR_DATA_TABLE_ENTRY32, ObsoleteLoadCount)), + &context->LoadCount, + sizeof(USHORT), + NULL + ); + + return FALSE; + } + + return TRUE; +} + +/** + * Sets the load count of a 32-bit process module. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ and PROCESS_VM_WRITE access. + * \param BaseAddress The base address of a module. + * \param LoadCount The new load count of the module. + * + * \retval STATUS_DLL_NOT_FOUND The module was not found. + * \retval STATUS_NOT_SUPPORTED The process is not running under WOW64. + * + * \remarks Do not use this function under a 32-bit environment. + */ +NTSTATUS PhSetProcessModuleLoadCount32( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ ULONG LoadCount + ) +{ + NTSTATUS status; + SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context; + + context.Status = STATUS_DLL_NOT_FOUND; + context.BaseAddress = BaseAddress; + context.LoadCount = LoadCount; + + status = PhpEnumProcessModules32( + ProcessHandle, + PhpSetProcessModuleLoadCount32Callback, + &context, + NULL + ); + + if (!NT_SUCCESS(status)) + return status; + + return context.Status; +} + +typedef struct _GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT +{ + PH_STRINGREF FileName; + PVOID DllBase; +} GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT, *PGET_PROCEDURE_ADDRESS_REMOTE_CONTEXT; + +static BOOLEAN PhpGetProcedureAddressRemoteCallback( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ) +{ + PGET_PROCEDURE_ADDRESS_REMOTE_CONTEXT context = Context; + PH_STRINGREF fullDllName; + + PhUnicodeStringToStringRef(&Module->FullDllName, &fullDllName); + + if (PhEqualStringRef(&fullDllName, &context->FileName, TRUE)) + { + context->DllBase = Module->DllBase; + return FALSE; + } + + return TRUE; +} + +/** + * Gets the address of a procedure in a process. + * + * \param ProcessHandle A handle to a process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. + * \param FileName The file name of the DLL containing the procedure. + * \param ProcedureName The name of the procedure. + * \param ProcedureNumber The ordinal of the procedure. + * \param ProcedureAddress A variable which receives the address of the procedure in the address + * space of the process. + * \param DllBase A variable which receives the base address of the DLL containing the procedure. + */ +NTSTATUS PhGetProcedureAddressRemote( + _In_ HANDLE ProcessHandle, + _In_ PWSTR FileName, + _In_opt_ PSTR ProcedureName, + _In_opt_ ULONG ProcedureNumber, + _Out_ PVOID *ProcedureAddress, + _Out_opt_ PVOID *DllBase + ) +{ + NTSTATUS status; + PH_MAPPED_IMAGE mappedImage; + PH_MAPPED_IMAGE_EXPORTS exports; + GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT context; + + if (!NT_SUCCESS(status = PhLoadMappedImage(FileName, NULL, TRUE, &mappedImage))) + return status; + + PhInitializeStringRef(&context.FileName, FileName); + context.DllBase = NULL; + + if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { +#ifdef _WIN64 + status = PhEnumProcessModules32(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context); +#else + status = PhEnumProcessModules(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context); +#endif + } + else + { +#ifdef _WIN64 + status = PhEnumProcessModules(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context); +#else + status = STATUS_NOT_SUPPORTED; +#endif + } + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + if (!NT_SUCCESS(status = PhGetMappedImageExports(&exports, &mappedImage))) + goto CleanupExit; + + status = PhGetMappedImageExportFunctionRemote( + &exports, + ProcedureName, + (USHORT)ProcedureNumber, + context.DllBase, + ProcedureAddress + ); + + if (NT_SUCCESS(status)) + { + if (DllBase) + *DllBase = context.DllBase; + } + +CleanupExit: + PhUnloadMappedImage(&mappedImage); + + return status; +} + +/** + * Enumerates the modules loaded by the kernel. + * + * \param Modules A variable which receives a pointer to a structure containing information about + * the kernel modules. You must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhEnumKernelModules( + _Out_ PRTL_PROCESS_MODULES *Modules + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 2048; + + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemModuleInformation, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemModuleInformation, + buffer, + bufferSize, + &bufferSize + ); + } + + if (!NT_SUCCESS(status)) + return status; + + *Modules = buffer; + + return status; +} + +/** + * Enumerates the modules loaded by the kernel. + * + * \param Modules A variable which receives a pointer to a structure containing information about + * the kernel modules. You must free the structure using PhFree() when you no longer need it. + */ +NTSTATUS PhEnumKernelModulesEx( + _Out_ PRTL_PROCESS_MODULE_INFORMATION_EX *Modules + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 2048; + + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemModuleInformationEx, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + status = NtQuerySystemInformation( + SystemModuleInformationEx, + buffer, + bufferSize, + &bufferSize + ); + } + + if (!NT_SUCCESS(status)) + return status; + + *Modules = buffer; + + return status; +} + +/** + * Gets the file name of the kernel image. + * + * \return A pointer to a string containing the kernel image file name. You must free the string + * using PhDereferenceObject() when you no longer need it. + */ +PPH_STRING PhGetKernelFileName( + VOID + ) +{ + PRTL_PROCESS_MODULES modules; + PPH_STRING fileName = NULL; + + if (!NT_SUCCESS(PhEnumKernelModules(&modules))) + return NULL; + + if (modules->NumberOfModules >= 1) + { + fileName = PhConvertMultiByteToUtf16(modules->Modules[0].FullPathName); + } + + PhFree(modules); + + return fileName; +} + +/** + * Enumerates the running processes. + * + * \param Processes A variable which receives a pointer to a buffer containing process information. + * You must free the buffer using PhFree() when you no longer need it. + * + * \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the + * information contained in the buffer. + */ +NTSTATUS PhEnumProcesses( + _Out_ PVOID *Processes + ) +{ + return PhEnumProcessesEx(Processes, SystemProcessInformation); +} + +/** + * Enumerates the running processes. + * + * \param Processes A variable which receives a pointer to a buffer containing process information. + * You must free the buffer using PhFree() when you no longer need it. + * + * \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the + * information contained in the buffer. + */ +NTSTATUS PhEnumProcessesEx( + _Out_ PVOID *Processes, + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass + ) +{ + static ULONG initialBufferSize[3] = { 0x4000, 0x4000, 0x4000 }; + NTSTATUS status; + ULONG classIndex; + PVOID buffer; + ULONG bufferSize; + + switch (SystemInformationClass) + { + case SystemProcessInformation: + classIndex = 0; + break; + case SystemExtendedProcessInformation: + classIndex = 1; + break; + case SystemFullProcessInformation: + classIndex = 2; + break; + default: + return STATUS_INVALID_INFO_CLASS; + } + + bufferSize = initialBufferSize[classIndex]; + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + status = NtQuerySystemInformation( + SystemInformationClass, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + if (bufferSize <= 0x100000) initialBufferSize[classIndex] = bufferSize; + *Processes = buffer; + + return status; +} + +/** + * Enumerates the running processes for a session. + * + * \param Processes A variable which receives a pointer to a buffer containing process information. + * You must free the buffer using PhFree() when you no longer need it. + * \param SessionId A session ID. + * + * \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the + * information contained in the buffer. + */ +NTSTATUS PhEnumProcessesForSession( + _Out_ PVOID *Processes, + _In_ ULONG SessionId + ) +{ + static ULONG initialBufferSize = 0x4000; + NTSTATUS status; + SYSTEM_SESSION_PROCESS_INFORMATION sessionProcessInfo; + PVOID buffer; + ULONG bufferSize; + + bufferSize = initialBufferSize; + buffer = PhAllocate(bufferSize); + + sessionProcessInfo.SessionId = SessionId; + + while (TRUE) + { + sessionProcessInfo.SizeOfBuf = bufferSize; + sessionProcessInfo.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemSessionProcessInformation, + &sessionProcessInfo, + sizeof(SYSTEM_SESSION_PROCESS_INFORMATION), + &bufferSize // size of the inner buffer gets returned + ); + + if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + if (bufferSize <= 0x100000) initialBufferSize = bufferSize; + *Processes = buffer; + + return status; +} + +/** + * Finds the process information structure for a specific process. + * + * \param Processes A pointer to a buffer returned by PhEnumProcesses(). + * \param ProcessId The ID of the process. + * + * \return A pointer to the process information structure for the specified process, or NULL if the + * structure could not be found. + */ +PSYSTEM_PROCESS_INFORMATION PhFindProcessInformation( + _In_ PVOID Processes, + _In_ HANDLE ProcessId + ) +{ + PSYSTEM_PROCESS_INFORMATION process; + + process = PH_FIRST_PROCESS(Processes); + + do + { + if (process->UniqueProcessId == ProcessId) + return process; + } while (process = PH_NEXT_PROCESS(process)); + + return NULL; +} + +/** + * Finds the process information structure for a specific process. + * + * \param Processes A pointer to a buffer returned by PhEnumProcesses(). + * \param ImageName The image name to search for. + * + * \return A pointer to the process information structure for the specified process, or NULL if the + * structure could not be found. + */ +PSYSTEM_PROCESS_INFORMATION PhFindProcessInformationByImageName( + _In_ PVOID Processes, + _In_ PPH_STRINGREF ImageName + ) +{ + PSYSTEM_PROCESS_INFORMATION process; + PH_STRINGREF processImageName; + + process = PH_FIRST_PROCESS(Processes); + + do + { + PhUnicodeStringToStringRef(&process->ImageName, &processImageName); + + if (PhEqualStringRef(&processImageName, ImageName, TRUE)) + return process; + } while (process = PH_NEXT_PROCESS(process)); + + return NULL; +} + +/** + * Enumerates all open handles. + * + * \param Handles A variable which receives a pointer to a structure containing information about + * all opened handles. You must free the structure using PhFree() when you no longer need it. + * + * \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large. + */ +NTSTATUS PhEnumHandles( + _Out_ PSYSTEM_HANDLE_INFORMATION *Handles + ) +{ + static ULONG initialBufferSize = 0x4000; + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + + bufferSize = initialBufferSize; + buffer = PhAllocate(bufferSize); + + while ((status = NtQuerySystemInformation( + SystemHandleInformation, + buffer, + bufferSize, + NULL + )) == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + + // Fail if we're resizing the buffer to something very large. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + if (bufferSize <= 0x100000) initialBufferSize = bufferSize; + *Handles = (PSYSTEM_HANDLE_INFORMATION)buffer; + + return status; +} + +/** + * Enumerates all open handles. + * + * \param Handles A variable which receives a pointer to a structure containing information about + * all opened handles. You must free the structure using PhFree() when you no longer need it. + * + * \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large. + * + * \remarks This function is only available starting with Windows XP. + */ +NTSTATUS PhEnumHandlesEx( + _Out_ PSYSTEM_HANDLE_INFORMATION_EX *Handles + ) +{ + static ULONG initialBufferSize = 0x10000; + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + + bufferSize = initialBufferSize; + buffer = PhAllocate(bufferSize); + + while ((status = NtQuerySystemInformation( + SystemExtendedHandleInformation, + buffer, + bufferSize, + NULL + )) == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + + // Fail if we're resizing the buffer to something very large. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + if (bufferSize <= 0x200000) initialBufferSize = bufferSize; + *Handles = (PSYSTEM_HANDLE_INFORMATION_EX)buffer; + + return status; +} + +/** + * Enumerates all pagefiles. + * + * \param Pagefiles A variable which receives a pointer to a buffer containing information about all + * active pagefiles. You must free the structure using PhFree() when you no longer need it. + * + * \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large. + */ +NTSTATUS PhEnumPagefiles( + _Out_ PVOID *Pagefiles + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 0x200; + + buffer = PhAllocate(bufferSize); + + while ((status = NtQuerySystemInformation( + SystemPageFileInformation, + buffer, + bufferSize, + NULL + )) == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + + // Fail if we're resizing the buffer to something very large. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + return STATUS_INSUFFICIENT_RESOURCES; + + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *Pagefiles = buffer; + + return status; +} + +/** + * Gets the file name of a process' image. + * + * \param ProcessId The ID of the process. + * \param FileName A variable which receives a pointer to a string containing the file name. You + * must free the string using PhDereferenceObject() when you no longer need it. + * + * \remarks This function only works on Windows Vista and above. There does not appear to be any + * access checking performed by the kernel for this. + */ +NTSTATUS PhGetProcessImageFileNameByProcessId( + _In_ HANDLE ProcessId, + _Out_ PPH_STRING *FileName + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize = 0x100; + SYSTEM_PROCESS_ID_INFORMATION processIdInfo; + + buffer = PhAllocate(bufferSize); + + processIdInfo.ProcessId = ProcessId; + processIdInfo.ImageName.Length = 0; + processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize; + processIdInfo.ImageName.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemProcessIdInformation, + &processIdInfo, + sizeof(SYSTEM_PROCESS_ID_INFORMATION), + NULL + ); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + // Required length is stored in MaximumLength. + + PhFree(buffer); + buffer = PhAllocate(processIdInfo.ImageName.MaximumLength); + processIdInfo.ImageName.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemProcessIdInformation, + &processIdInfo, + sizeof(SYSTEM_PROCESS_ID_INFORMATION), + NULL + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *FileName = PhCreateStringFromUnicodeString(&processIdInfo.ImageName); + PhFree(buffer); + + return status; +} + +/** + * Determines if a process is managed. + * + * \param ProcessId The ID of the process. + * \param IsDotNet A variable which receives a boolean indicating whether the process is managed. + */ +NTSTATUS PhGetProcessIsDotNet( + _In_ HANDLE ProcessId, + _Out_ PBOOLEAN IsDotNet + ) +{ + return PhGetProcessIsDotNetEx(ProcessId, NULL, 0, IsDotNet, NULL); +} + +BOOLEAN NTAPI PhpIsDotNetEnumProcessModulesCallback( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ) +{ + static UNICODE_STRING clrString = RTL_CONSTANT_STRING(L"clr.dll"); + static UNICODE_STRING mscorwksString = RTL_CONSTANT_STRING(L"mscorwks.dll"); + static UNICODE_STRING mscorsvrString = RTL_CONSTANT_STRING(L"mscorsvr.dll"); + static UNICODE_STRING mscorlibString = RTL_CONSTANT_STRING(L"mscorlib.dll"); + static UNICODE_STRING mscorlibNiString = RTL_CONSTANT_STRING(L"mscorlib.ni.dll"); + static UNICODE_STRING clrjitString = RTL_CONSTANT_STRING(L"clrjit.dll"); + static UNICODE_STRING frameworkString = RTL_CONSTANT_STRING(L"\\Microsoft.NET\\Framework\\"); + static UNICODE_STRING framework64String = RTL_CONSTANT_STRING(L"\\Microsoft.NET\\Framework64\\"); + + if ( + RtlEqualUnicodeString(&Module->BaseDllName, &clrString, TRUE) || + RtlEqualUnicodeString(&Module->BaseDllName, &mscorwksString, TRUE) || + RtlEqualUnicodeString(&Module->BaseDllName, &mscorsvrString, TRUE) + ) + { + UNICODE_STRING fileName; + PH_STRINGREF systemRootSr; + UNICODE_STRING systemRoot; + PUNICODE_STRING frameworkPart; + +#ifdef _WIN64 + if (*(PULONG)Context & PH_CLR_PROCESS_IS_WOW64) + { +#endif + frameworkPart = &frameworkString; +#ifdef _WIN64 + } + else + { + frameworkPart = &framework64String; + } +#endif + + fileName = Module->FullDllName; + PhGetSystemRoot(&systemRootSr); + PhStringRefToUnicodeString(&systemRootSr, &systemRoot); + + if (RtlPrefixUnicodeString(&systemRoot, &fileName, TRUE)) + { + fileName.Buffer = (PWCHAR)((PCHAR)fileName.Buffer + systemRoot.Length); + fileName.Length -= systemRoot.Length; + + if (RtlPrefixUnicodeString(frameworkPart, &fileName, TRUE)) + { + fileName.Buffer = (PWCHAR)((PCHAR)fileName.Buffer + frameworkPart->Length); + fileName.Length -= frameworkPart->Length; + + if (fileName.Length >= 4 * sizeof(WCHAR)) // vx.x + { + if (fileName.Buffer[1] == '1') + { + if (fileName.Buffer[3] == '0') + *(PULONG)Context |= PH_CLR_VERSION_1_0; + else if (fileName.Buffer[3] == '1') + *(PULONG)Context |= PH_CLR_VERSION_1_1; + } + else if (fileName.Buffer[1] == '2') + { + *(PULONG)Context |= PH_CLR_VERSION_2_0; + } + else if (fileName.Buffer[1] >= '4' && fileName.Buffer[1] <= '9') + { + *(PULONG)Context |= PH_CLR_VERSION_4_ABOVE; + } + } + } + } + } + else if ( + RtlEqualUnicodeString(&Module->BaseDllName, &mscorlibString, TRUE) || + RtlEqualUnicodeString(&Module->BaseDllName, &mscorlibNiString, TRUE) + ) + { + *(PULONG)Context |= PH_CLR_MSCORLIB_PRESENT; + } + else if (RtlEqualUnicodeString(&Module->BaseDllName, &clrjitString, TRUE)) + { + *(PULONG)Context |= PH_CLR_JIT_PRESENT; + } + + return TRUE; +} + +/** + * Determines if a process is managed. + * + * \param ProcessId The ID of the process. + * \param ProcessHandle An optional handle to the process. The handle must have + * PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. + * \param InFlags A combination of flags. + * \li \c PH_CLR_USE_SECTION_CHECK Checks for the existence of related section objects to determine + * whether the process is managed. + * \li \c PH_CLR_NO_WOW64_CHECK Instead of a separate query, uses the presence of the + * \c PH_CLR_KNOWN_IS_WOW64 flag to determine whether the process is running under WOW64. + * \li \c PH_CLR_KNOWN_IS_WOW64 When \c PH_CLR_NO_WOW64_CHECK is specified, indicates that the + * process is managed. + * \param IsDotNet A variable which receives a boolean indicating whether the process is managed. + * \param Flags A variable which receives additional flags. + */ +NTSTATUS PhGetProcessIsDotNetEx( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle, + _In_ ULONG InFlags, + _Out_opt_ PBOOLEAN IsDotNet, + _Out_opt_ PULONG Flags + ) +{ + NTSTATUS status = STATUS_SUCCESS; + HANDLE processHandle; + ULONG flags; +#ifdef _WIN64 + BOOLEAN isWow64; +#endif + + if (InFlags & PH_CLR_USE_SECTION_CHECK) + { + HANDLE sectionHandle; + OBJECT_ATTRIBUTES objectAttributes; + PPH_STRING sectionName; + UNICODE_STRING sectionNameUs; + PH_FORMAT format[2]; + + // Most .NET processes have a handle open to a section named + // \BaseNamedObjects\Cor_Private_IPCBlock(_v4)_. This is the same object used by + // the ICorPublish::GetProcess function. Instead of calling that function, we simply check + // for the existence of that section object. This means: + // * Better performance. + // * No need for admin rights to get .NET status of processes owned by other users. + + PhInitFormatIU(&format[1], (ULONG_PTR)ProcessId); + + // Version 4 section object + + PhInitFormatS(&format[0], L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_"); + sectionName = PhFormat(format, 2, 96); + PhStringRefToUnicodeString(§ionName->sr, §ionNameUs); + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + status = NtOpenSection( + §ionHandle, + SECTION_QUERY, + &objectAttributes + ); + PhDereferenceObject(sectionName); + + if (NT_SUCCESS(status) || status == STATUS_ACCESS_DENIED) + { + if (NT_SUCCESS(status)) + NtClose(sectionHandle); + + if (IsDotNet) + *IsDotNet = TRUE; + + if (Flags) + *Flags = PH_CLR_VERSION_4_ABOVE; + + return STATUS_SUCCESS; + } + + // Version 2 section object + + PhInitFormatS(&format[0], L"\\BaseNamedObjects\\Cor_Private_IPCBlock_"); + sectionName = PhFormat(format, 2, 90); + PhStringRefToUnicodeString(§ionName->sr, §ionNameUs); + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + status = NtOpenSection( + §ionHandle, + SECTION_QUERY, + &objectAttributes + ); + PhDereferenceObject(sectionName); + + if (NT_SUCCESS(status) || status == STATUS_ACCESS_DENIED) + { + if (NT_SUCCESS(status)) + NtClose(sectionHandle); + + if (IsDotNet) + *IsDotNet = TRUE; + + if (Flags) + *Flags = PH_CLR_VERSION_2_0; + + return STATUS_SUCCESS; + } + } + + flags = 0; + processHandle = NULL; + + if (!ProcessHandle) + { + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessId))) + return status; + + ProcessHandle = processHandle; + } + +#ifdef _WIN64 + if (InFlags & PH_CLR_NO_WOW64_CHECK) + { + isWow64 = !!(InFlags & PH_CLR_KNOWN_IS_WOW64); + } + else + { + isWow64 = FALSE; + PhGetProcessIsWow64(ProcessHandle, &isWow64); + } + + if (isWow64) + { + flags |= PH_CLR_PROCESS_IS_WOW64; + PhEnumProcessModules32(ProcessHandle, PhpIsDotNetEnumProcessModulesCallback, &flags); + } + else + { +#endif + PhEnumProcessModules(ProcessHandle, PhpIsDotNetEnumProcessModulesCallback, &flags); +#ifdef _WIN64 + } +#endif + + if (processHandle) + NtClose(processHandle); + + if (IsDotNet) + *IsDotNet = (flags & PH_CLR_VERSION_MASK) && (flags & (PH_CLR_MSCORLIB_PRESENT | PH_CLR_JIT_PRESENT)); + + if (Flags) + *Flags = flags; + + return status; +} + +/** + * Enumerates the objects in a directory object. + * + * \param DirectoryHandle A handle to a directory. The handle must have DIRECTORY_QUERY access. + * \param Callback A callback function which is executed for each object. + * \param Context A user-defined value to pass to the callback function. + */ +NTSTATUS PhEnumDirectoryObjects( + _In_ HANDLE DirectoryHandle, + _In_ PPH_ENUM_DIRECTORY_OBJECTS Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + ULONG context = 0; + BOOLEAN firstTime = TRUE; + ULONG bufferSize; + POBJECT_DIRECTORY_INFORMATION buffer; + ULONG i; + BOOLEAN cont; + + bufferSize = 0x200; + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + // Get a batch of entries. + + while ((status = NtQueryDirectoryObject( + DirectoryHandle, + buffer, + bufferSize, + FALSE, + firstTime, + &context, + NULL + )) == STATUS_MORE_ENTRIES) + { + // Check if we have at least one entry. If not, we'll double the buffer size and try + // again. + if (buffer[0].Name.Buffer) + break; + + // Make sure we don't use too much memory. + if (bufferSize > PH_LARGE_BUFFER_SIZE) + { + PhFree(buffer); + return STATUS_INSUFFICIENT_RESOURCES; + } + + PhFree(buffer); + bufferSize *= 2; + buffer = PhAllocate(bufferSize); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + // Read the batch and execute the callback function for each object. + + i = 0; + cont = TRUE; + + while (TRUE) + { + POBJECT_DIRECTORY_INFORMATION info; + PH_STRINGREF name; + PH_STRINGREF typeName; + + info = &buffer[i]; + + if (!info->Name.Buffer) + break; + + PhUnicodeStringToStringRef(&info->Name, &name); + PhUnicodeStringToStringRef(&info->TypeName, &typeName); + + cont = Callback(&name, &typeName, Context); + + if (!cont) + break; + + i++; + } + + if (!cont) + break; + + if (status != STATUS_MORE_ENTRIES) + break; + + firstTime = FALSE; + } + + PhFree(buffer); + + return STATUS_SUCCESS; +} + +NTSTATUS PhEnumDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ PUNICODE_STRING SearchPattern, + _In_ PPH_ENUM_DIRECTORY_FILE Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + BOOLEAN firstTime = TRUE; + PVOID buffer; + ULONG bufferSize = 0x400; + ULONG i; + BOOLEAN cont; + + buffer = PhAllocate(bufferSize); + + while (TRUE) + { + // Query the directory, doubling the buffer each time NtQueryDirectoryFile fails. + while (TRUE) + { + status = NtQueryDirectoryFile( + FileHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + bufferSize, + FileDirectoryInformation, + FALSE, + SearchPattern, + firstTime + ); + + // Our ISB is on the stack, so we have to wait for the operation to complete before + // continuing. + if (status == STATUS_PENDING) + { + status = NtWaitForSingleObject(FileHandle, FALSE, NULL); + + if (NT_SUCCESS(status)) + status = isb.Status; + } + + if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(buffer); + bufferSize *= 2; + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + // If we don't have any entries to read, exit. + if (status == STATUS_NO_MORE_FILES) + { + status = STATUS_SUCCESS; + break; + } + + if (!NT_SUCCESS(status)) + break; + + // Read the batch and execute the callback function for each file. + + i = 0; + cont = TRUE; + + while (TRUE) + { + PFILE_DIRECTORY_INFORMATION information; + + information = (PFILE_DIRECTORY_INFORMATION)(PTR_ADD_OFFSET(buffer, i)); + + if (!Callback( + information, + Context + )) + { + cont = FALSE; + break; + } + + if (information->NextEntryOffset != 0) + i += information->NextEntryOffset; + else + break; + } + + if (!cont) + break; + + firstTime = FALSE; + } + + PhFree(buffer); + + return status; +} + +NTSTATUS PhEnumFileStreams( + _In_ HANDLE FileHandle, + _Out_ PVOID *Streams + ) +{ + return PhpQueryFileVariableSize( + FileHandle, + FileStreamInformation, + Streams + ); +} + +/** + * Initializes the device prefixes module. + */ +VOID PhpInitializeDevicePrefixes( + VOID + ) +{ + ULONG i; + PUCHAR buffer; + + // Allocate one buffer for all 26 prefixes to reduce overhead. + buffer = PhAllocate(PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR) * 26); + + for (i = 0; i < 26; i++) + { + PhDevicePrefixes[i].Length = 0; + PhDevicePrefixes[i].MaximumLength = PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR); + PhDevicePrefixes[i].Buffer = (PWCHAR)buffer; + buffer += PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR); + } +} + +VOID PhUpdateMupDevicePrefixes( + VOID + ) +{ + static PH_STRINGREF orderKeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Control\\NetworkProvider\\Order"); + static PH_STRINGREF servicesStringPart = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\"); + static PH_STRINGREF networkProviderStringPart = PH_STRINGREF_INIT(L"\\NetworkProvider"); + + HANDLE orderKeyHandle; + PPH_STRING providerOrder = NULL; + ULONG i; + PH_STRINGREF remainingPart; + PH_STRINGREF part; + + // The provider names are stored in the ProviderOrder value in this key: + // HKLM\System\CurrentControlSet\Control\NetworkProvider\Order + // Each name can then be looked up, its device name in the DeviceName value in: + // HKLM\System\CurrentControlSet\Services\\NetworkProvider + + // Note that we assume the providers only claim their device name. Some providers such as DFS + // claim an extra part, and are not resolved correctly here. + + if (NT_SUCCESS(PhOpenKey( + &orderKeyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &orderKeyName, + 0 + ))) + { + providerOrder = PhQueryRegistryString(orderKeyHandle, L"ProviderOrder"); + NtClose(orderKeyHandle); + } + + if (!providerOrder) + return; + + PhAcquireQueuedLockExclusive(&PhDeviceMupPrefixesLock); + + for (i = 0; i < PhDeviceMupPrefixesCount; i++) + { + PhDereferenceObject(PhDeviceMupPrefixes[i]); + PhDeviceMupPrefixes[i] = NULL; + } + + PhDeviceMupPrefixesCount = 0; + + PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\Mup"); + + // DFS claims an extra part of file names, which we don't handle. + /*if (WindowsVersion >= WINDOWS_VISTA) + PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\DfsClient"); + else + PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\WinDfs");*/ + + remainingPart = providerOrder->sr; + + while (remainingPart.Length != 0) + { + PPH_STRING serviceKeyName; + HANDLE networkProviderKeyHandle; + PPH_STRING deviceName; + + if (PhDeviceMupPrefixesCount == PH_DEVICE_MUP_PREFIX_MAX_COUNT) + break; + + PhSplitStringRefAtChar(&remainingPart, ',', &part, &remainingPart); + + if (part.Length != 0) + { + serviceKeyName = PhConcatStringRef3(&servicesStringPart, &part, &networkProviderStringPart); + + if (NT_SUCCESS(PhOpenKey( + &networkProviderKeyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &serviceKeyName->sr, + 0 + ))) + { + if (deviceName = PhQueryRegistryString(networkProviderKeyHandle, L"DeviceName")) + { + PhDeviceMupPrefixes[PhDeviceMupPrefixesCount] = deviceName; + PhDeviceMupPrefixesCount++; + } + + NtClose(networkProviderKeyHandle); + } + + PhDereferenceObject(serviceKeyName); + } + } + + PhReleaseQueuedLockExclusive(&PhDeviceMupPrefixesLock); + + PhDereferenceObject(providerOrder); +} + +/** + * Updates the DOS device names array. + */ +VOID PhUpdateDosDevicePrefixes( + VOID + ) +{ + WCHAR deviceNameBuffer[7] = L"\\??\\ :"; + ULONG i; + + for (i = 0; i < 26; i++) + { + HANDLE linkHandle; + OBJECT_ATTRIBUTES oa; + UNICODE_STRING deviceName; + + deviceNameBuffer[4] = (WCHAR)('A' + i); + deviceName.Buffer = deviceNameBuffer; + deviceName.Length = 6 * sizeof(WCHAR); + + InitializeObjectAttributes( + &oa, + &deviceName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + if (NT_SUCCESS(NtOpenSymbolicLinkObject( + &linkHandle, + SYMBOLIC_LINK_QUERY, + &oa + ))) + { + PhAcquireQueuedLockExclusive(&PhDevicePrefixesLock); + + if (!NT_SUCCESS(NtQuerySymbolicLinkObject( + linkHandle, + &PhDevicePrefixes[i], + NULL + ))) + { + PhDevicePrefixes[i].Length = 0; + } + + PhReleaseQueuedLockExclusive(&PhDevicePrefixesLock); + + NtClose(linkHandle); + } + else + { + PhDevicePrefixes[i].Length = 0; + } + } +} + +/** + * Resolves a NT path into a Win32 path. + * + * \param Name A string containing the path to resolve. + * + * \return A pointer to a string containing the Win32 path. You must free the string using + * PhDereferenceObject() when you no longer need it. + */ +PPH_STRING PhResolveDevicePrefix( + _In_ PPH_STRING Name + ) +{ + ULONG i; + PPH_STRING newName = NULL; + + if (PhBeginInitOnce(&PhDevicePrefixesInitOnce)) + { + PhpInitializeDevicePrefixes(); + PhUpdateDosDevicePrefixes(); + PhUpdateMupDevicePrefixes(); + + PhEndInitOnce(&PhDevicePrefixesInitOnce); + } + + // Go through the DOS devices and try to find a matching prefix. + for (i = 0; i < 26; i++) + { + BOOLEAN isPrefix = FALSE; + PH_STRINGREF prefix; + + PhAcquireQueuedLockShared(&PhDevicePrefixesLock); + + PhUnicodeStringToStringRef(&PhDevicePrefixes[i], &prefix); + + if (prefix.Length != 0) + { + if (PhStartsWithStringRef(&Name->sr, &prefix, TRUE)) + { + // To ensure we match the longest prefix, make sure the next character is a + // backslash or the path is equal to the prefix. + if (Name->Length == prefix.Length || Name->Buffer[prefix.Length / sizeof(WCHAR)] == '\\') + { + isPrefix = TRUE; + } + } + } + + PhReleaseQueuedLockShared(&PhDevicePrefixesLock); + + if (isPrefix) + { + // :path + newName = PhCreateStringEx(NULL, 2 * sizeof(WCHAR) + Name->Length - prefix.Length); + newName->Buffer[0] = (WCHAR)('A' + i); + newName->Buffer[1] = ':'; + memcpy( + &newName->Buffer[2], + &Name->Buffer[prefix.Length / sizeof(WCHAR)], + Name->Length - prefix.Length + ); + + break; + } + } + + if (i == 26) + { + // Resolve network providers. + + PhAcquireQueuedLockShared(&PhDeviceMupPrefixesLock); + + for (i = 0; i < PhDeviceMupPrefixesCount; i++) + { + BOOLEAN isPrefix = FALSE; + SIZE_T prefixLength; + + prefixLength = PhDeviceMupPrefixes[i]->Length; + + if (prefixLength != 0) + { + if (PhStartsWithString(Name, PhDeviceMupPrefixes[i], TRUE)) + { + // To ensure we match the longest prefix, make sure the next character is a + // backslash. Don't resolve if the name *is* the prefix. Otherwise, we will end + // up with a useless string like "\". + if (Name->Length != prefixLength && Name->Buffer[prefixLength / sizeof(WCHAR)] == '\\') + { + isPrefix = TRUE; + } + } + } + + if (isPrefix) + { + // \path + newName = PhCreateStringEx(NULL, 1 * sizeof(WCHAR) + Name->Length - prefixLength); + newName->Buffer[0] = '\\'; + memcpy( + &newName->Buffer[1], + &Name->Buffer[prefixLength / sizeof(WCHAR)], + Name->Length - prefixLength + ); + + break; + } + } + + PhReleaseQueuedLockShared(&PhDeviceMupPrefixesLock); + } + + return newName; +} + +/** + * Converts a file name into Win32 format. + * + * \param FileName A string containing a file name. + * + * \return A pointer to a string containing the Win32 file name. You must free the string using + * PhDereferenceObject() when you no longer need it. + * + * \remarks This function may convert NT object name paths to invalid ones. If the path to be + * converted is not necessarily a file name, use PhResolveDevicePrefix(). + */ +PPH_STRING PhGetFileName( + _In_ PPH_STRING FileName + ) +{ + PPH_STRING newFileName; + + newFileName = FileName; + + // "\??\" refers to \GLOBAL??\. Just remove it. + if (PhStartsWithString2(FileName, L"\\??\\", FALSE)) + { + newFileName = PhCreateStringEx(NULL, FileName->Length - 4 * 2); + memcpy(newFileName->Buffer, &FileName->Buffer[4], FileName->Length - 4 * 2); + } + // "\SystemRoot" means "C:\Windows". + else if (PhStartsWithString2(FileName, L"\\SystemRoot", TRUE)) + { + PH_STRINGREF systemRoot; + + PhGetSystemRoot(&systemRoot); + newFileName = PhCreateStringEx(NULL, systemRoot.Length + FileName->Length - 11 * 2); + memcpy(newFileName->Buffer, systemRoot.Buffer, systemRoot.Length); + memcpy((PCHAR)newFileName->Buffer + systemRoot.Length, &FileName->Buffer[11], FileName->Length - 11 * 2); + } + // "system32\" means "C:\Windows\system32\". + else if (PhStartsWithString2(FileName, L"system32\\", TRUE)) + { + PH_STRINGREF systemRoot; + + PhGetSystemRoot(&systemRoot); + newFileName = PhCreateStringEx(NULL, systemRoot.Length + 2 + FileName->Length); + memcpy(newFileName->Buffer, systemRoot.Buffer, systemRoot.Length); + newFileName->Buffer[systemRoot.Length / 2] = '\\'; + memcpy((PCHAR)newFileName->Buffer + systemRoot.Length + 2, FileName->Buffer, FileName->Length); + } + else if (FileName->Length != 0 && FileName->Buffer[0] == '\\') + { + PPH_STRING resolvedName; + + resolvedName = PhResolveDevicePrefix(FileName); + + if (resolvedName) + { + newFileName = resolvedName; + } + else + { + // We didn't find a match. + // If the file name starts with "\Windows", prepend the system drive. + if (PhStartsWithString2(newFileName, L"\\Windows", TRUE)) + { + newFileName = PhCreateStringEx(NULL, FileName->Length + 2 * 2); + newFileName->Buffer[0] = USER_SHARED_DATA->NtSystemRoot[0]; + newFileName->Buffer[1] = ':'; + memcpy(&newFileName->Buffer[2], FileName->Buffer, FileName->Length); + } + else + { + PhReferenceObject(newFileName); + } + } + } + else + { + // Just return the supplied file name. Note that we need to add a reference. + PhReferenceObject(newFileName); + } + + return newFileName; +} + +typedef struct _ENUM_GENERIC_PROCESS_MODULES_CONTEXT +{ + PPH_ENUM_GENERIC_MODULES_CALLBACK Callback; + PVOID Context; + ULONG Type; + PPH_HASHTABLE BaseAddressHashtable; + + ULONG LoadOrderIndex; +} ENUM_GENERIC_PROCESS_MODULES_CONTEXT, *PENUM_GENERIC_PROCESS_MODULES_CONTEXT; + +static BOOLEAN EnumGenericProcessModulesCallback( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ) +{ + PENUM_GENERIC_PROCESS_MODULES_CONTEXT context; + PH_MODULE_INFO moduleInfo; + PPH_STRING fileName; + BOOLEAN cont; + + context = (PENUM_GENERIC_PROCESS_MODULES_CONTEXT)Context; + + // Check if we have a duplicate base address. + if (PhFindEntryHashtable(context->BaseAddressHashtable, &Module->DllBase)) + { + return TRUE; + } + else + { + PhAddEntryHashtable(context->BaseAddressHashtable, &Module->DllBase); + } + + fileName = PhCreateStringFromUnicodeString(&Module->FullDllName); + + moduleInfo.Type = context->Type; + moduleInfo.BaseAddress = Module->DllBase; + moduleInfo.Size = Module->SizeOfImage; + moduleInfo.EntryPoint = Module->EntryPoint; + moduleInfo.Flags = Module->Flags; + moduleInfo.Name = PhCreateStringFromUnicodeString(&Module->BaseDllName); + moduleInfo.FileName = PhGetFileName(fileName); + moduleInfo.LoadOrderIndex = (USHORT)(context->LoadOrderIndex++); + moduleInfo.LoadCount = Module->ObsoleteLoadCount; + + if (WindowsVersion >= WINDOWS_8) + { + moduleInfo.LoadReason = (USHORT)Module->LoadReason; + moduleInfo.LoadTime = Module->LoadTime; + } + else + { + moduleInfo.LoadReason = -1; + moduleInfo.LoadTime.QuadPart = 0; + } + + PhDereferenceObject(fileName); + + cont = context->Callback(&moduleInfo, context->Context); + + PhDereferenceObject(moduleInfo.Name); + PhDereferenceObject(moduleInfo.FileName); + + return cont; +} + +VOID PhpRtlModulesToGenericModules( + _In_ PRTL_PROCESS_MODULES Modules, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context, + _In_ PPH_HASHTABLE BaseAddressHashtable + ) +{ + PRTL_PROCESS_MODULE_INFORMATION module; + ULONG i; + PH_MODULE_INFO moduleInfo; + BOOLEAN cont; + + for (i = 0; i < Modules->NumberOfModules; i++) + { + PPH_STRING fileName; + + module = &Modules->Modules[i]; + + // Check if we have a duplicate base address. + if (PhFindEntryHashtable(BaseAddressHashtable, &module->ImageBase)) + { + continue; + } + else + { + PhAddEntryHashtable(BaseAddressHashtable, &module->ImageBase); + } + + fileName = PhConvertMultiByteToUtf16(module->FullPathName); + + if ((ULONG_PTR)module->ImageBase <= PhSystemBasicInformation.MaximumUserModeAddress) + moduleInfo.Type = PH_MODULE_TYPE_MODULE; + else + moduleInfo.Type = PH_MODULE_TYPE_KERNEL_MODULE; + + moduleInfo.BaseAddress = module->ImageBase; + moduleInfo.Size = module->ImageSize; + moduleInfo.EntryPoint = NULL; + moduleInfo.Flags = module->Flags; + moduleInfo.Name = PhConvertMultiByteToUtf16(&module->FullPathName[module->OffsetToFileName]); + moduleInfo.FileName = PhGetFileName(fileName); // convert to DOS file name + moduleInfo.LoadOrderIndex = module->LoadOrderIndex; + moduleInfo.LoadCount = module->LoadCount; + moduleInfo.LoadReason = -1; + moduleInfo.LoadTime.QuadPart = 0; + + PhDereferenceObject(fileName); + + if (module->OffsetToFileName == 0) + { + static PH_STRINGREF driversString = PH_STRINGREF_INIT(L"\\System32\\Drivers\\"); + PH_STRINGREF systemRoot; + PPH_STRING newFileName; + + // We only have the file name, without a path. The driver must be in the default drivers + // directory. + PhGetSystemRoot(&systemRoot); + newFileName = PhConcatStringRef3(&systemRoot, &driversString, &moduleInfo.Name->sr); + PhDereferenceObject(moduleInfo.FileName); + moduleInfo.FileName = newFileName; + } + + cont = Callback(&moduleInfo, Context); + + PhDereferenceObject(moduleInfo.Name); + PhDereferenceObject(moduleInfo.FileName); + + if (!cont) + break; + } +} + +VOID PhpRtlModulesExToGenericModules( + _In_ PRTL_PROCESS_MODULE_INFORMATION_EX Modules, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context, + _In_ PPH_HASHTABLE BaseAddressHashtable + ) +{ + PRTL_PROCESS_MODULE_INFORMATION_EX module; + PH_MODULE_INFO moduleInfo; + BOOLEAN cont; + + module = Modules; + + while (module->NextOffset != 0) + { + PPH_STRING fileName; + + // Check if we have a duplicate base address. + if (PhFindEntryHashtable(BaseAddressHashtable, &module->BaseInfo.ImageBase)) + { + continue; + } + else + { + PhAddEntryHashtable(BaseAddressHashtable, &module->BaseInfo.ImageBase); + } + + fileName = PhConvertMultiByteToUtf16(module->BaseInfo.FullPathName); + + if ((ULONG_PTR)module->BaseInfo.ImageBase <= PhSystemBasicInformation.MaximumUserModeAddress) + moduleInfo.Type = PH_MODULE_TYPE_MODULE; + else + moduleInfo.Type = PH_MODULE_TYPE_KERNEL_MODULE; + + moduleInfo.BaseAddress = module->BaseInfo.ImageBase; + moduleInfo.Size = module->BaseInfo.ImageSize; + moduleInfo.EntryPoint = NULL; + moduleInfo.Flags = module->BaseInfo.Flags; + moduleInfo.Name = PhConvertMultiByteToUtf16(&module->BaseInfo.FullPathName[module->BaseInfo.OffsetToFileName]); + moduleInfo.FileName = PhGetFileName(fileName); // convert to DOS file name + moduleInfo.LoadOrderIndex = module->BaseInfo.LoadOrderIndex; + moduleInfo.LoadCount = module->BaseInfo.LoadCount; + moduleInfo.LoadReason = -1; + moduleInfo.LoadTime.QuadPart = 0; + + PhDereferenceObject(fileName); + + cont = Callback(&moduleInfo, Context); + + PhDereferenceObject(moduleInfo.Name); + PhDereferenceObject(moduleInfo.FileName); + + if (!cont) + break; + + module = PTR_ADD_OFFSET(module, module->NextOffset); + } +} + +BOOLEAN PhpCallbackMappedFileOrImage( + _In_ PVOID AllocationBase, + _In_ SIZE_T AllocationSize, + _In_ ULONG Type, + _In_ PPH_STRING FileName, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context, + _In_ PPH_HASHTABLE BaseAddressHashtable + ) +{ + PH_MODULE_INFO moduleInfo; + BOOLEAN cont; + + moduleInfo.Type = Type; + moduleInfo.BaseAddress = AllocationBase; + moduleInfo.Size = (ULONG)AllocationSize; + moduleInfo.EntryPoint = NULL; + moduleInfo.Flags = 0; + moduleInfo.FileName = PhGetFileName(FileName); + moduleInfo.Name = PhGetBaseName(moduleInfo.FileName); + moduleInfo.LoadOrderIndex = -1; + moduleInfo.LoadCount = -1; + moduleInfo.LoadReason = -1; + moduleInfo.LoadTime.QuadPart = 0; + + cont = Callback(&moduleInfo, Context); + + PhDereferenceObject(moduleInfo.FileName); + PhDereferenceObject(moduleInfo.Name); + + return cont; +} + +VOID PhpEnumGenericMappedFilesAndImages( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context, + _In_ PPH_HASHTABLE BaseAddressHashtable + ) +{ + BOOLEAN querySucceeded; + PVOID baseAddress; + MEMORY_BASIC_INFORMATION basicInfo; + + baseAddress = (PVOID)0; + + if (!NT_SUCCESS(NtQueryVirtualMemory( + ProcessHandle, + baseAddress, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + return; + } + + querySucceeded = TRUE; + + while (querySucceeded) + { + PVOID allocationBase; + SIZE_T allocationSize; + ULONG type; + PPH_STRING fileName; + BOOLEAN cont; + + if (basicInfo.Type == MEM_MAPPED || basicInfo.Type == MEM_IMAGE) + { + if (basicInfo.Type == MEM_MAPPED) + type = PH_MODULE_TYPE_MAPPED_FILE; + else + type = PH_MODULE_TYPE_MAPPED_IMAGE; + + // Find the total allocation size. + + allocationBase = basicInfo.AllocationBase; + allocationSize = 0; + + do + { + baseAddress = (PVOID)((ULONG_PTR)baseAddress + basicInfo.RegionSize); + allocationSize += basicInfo.RegionSize; + + if (!NT_SUCCESS(NtQueryVirtualMemory( + ProcessHandle, + baseAddress, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + querySucceeded = FALSE; + break; + } + } while (basicInfo.AllocationBase == allocationBase); + + if ((type == PH_MODULE_TYPE_MAPPED_FILE && !(Flags & PH_ENUM_GENERIC_MAPPED_FILES)) || + (type == PH_MODULE_TYPE_MAPPED_IMAGE && !(Flags & PH_ENUM_GENERIC_MAPPED_IMAGES))) + { + // The user doesn't want this type of entry. + continue; + } + + // Check if we have a duplicate base address. + if (PhFindEntryHashtable(BaseAddressHashtable, &allocationBase)) + { + continue; + } + + if (!NT_SUCCESS(PhGetProcessMappedFileName( + ProcessHandle, + allocationBase, + &fileName + ))) + { + continue; + } + + PhAddEntryHashtable(BaseAddressHashtable, &allocationBase); + + cont = PhpCallbackMappedFileOrImage( + allocationBase, + allocationSize, + type, + fileName, + Callback, + Context, + BaseAddressHashtable + ); + + PhDereferenceObject(fileName); + + if (!cont) + break; + } + else + { + baseAddress = (PVOID)((ULONG_PTR)baseAddress + basicInfo.RegionSize); + + if (!NT_SUCCESS(NtQueryVirtualMemory( + ProcessHandle, + baseAddress, + MemoryBasicInformation, + &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION), + NULL + ))) + { + querySucceeded = FALSE; + } + } + } +} + +BOOLEAN NTAPI PhpBaseAddressHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + return *(PVOID *)Entry1 == *(PVOID *)Entry2; +} + +ULONG NTAPI PhpBaseAddressHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)*(PVOID *)Entry); +} + +/** + * Enumerates the modules loaded by a process. + * + * \param ProcessId The ID of a process. If \ref SYSTEM_PROCESS_ID is specified the function + * enumerates the kernel modules. + * \param ProcessHandle A handle to the process. + * \param Flags Flags controlling the information to retrieve. + * \li \c PH_ENUM_GENERIC_MAPPED_FILES Enumerate mapped files. + * \li \c PH_ENUM_GENERIC_MAPPED_IMAGES Enumerate mapped images (those which are not mapped by the + * loader). + * \param Callback A callback function which is executed for each module. + * \param Context A user-defined value to pass to the callback function. + */ +NTSTATUS PhEnumGenericModules( + _In_ HANDLE ProcessId, + _In_opt_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PPH_HASHTABLE baseAddressHashtable; + + baseAddressHashtable = PhCreateHashtable( + sizeof(PVOID), + PhpBaseAddressHashtableEqualFunction, + PhpBaseAddressHashtableHashFunction, + 32 + ); + + if (ProcessId == SYSTEM_PROCESS_ID) + { + // Kernel modules + + PVOID modules; + + if (NT_SUCCESS(status = PhEnumKernelModules((PRTL_PROCESS_MODULES *)&modules))) + { + PhpRtlModulesToGenericModules( + modules, + Callback, + Context, + baseAddressHashtable + ); + PhFree(modules); + } + } + else + { + // Process modules + + BOOLEAN opened = FALSE; +#ifdef _WIN64 + BOOLEAN isWow64 = FALSE; +#endif + ENUM_GENERIC_PROCESS_MODULES_CONTEXT context; + PH_ENUM_PROCESS_MODULES_PARAMETERS parameters; + + if (!ProcessHandle) + { + if (!NT_SUCCESS(status = PhOpenProcess( + &ProcessHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, // needed for enumerating mapped files + ProcessId + ))) + { + if (!NT_SUCCESS(status = PhOpenProcess( + &ProcessHandle, + ProcessQueryAccess | PROCESS_VM_READ, + ProcessId + ))) + { + goto CleanupExit; + } + } + + opened = TRUE; + } + + context.Callback = Callback; + context.Context = Context; + context.Type = PH_MODULE_TYPE_MODULE; + context.BaseAddressHashtable = baseAddressHashtable; + context.LoadOrderIndex = 0; + + parameters.Callback = EnumGenericProcessModulesCallback; + parameters.Context = &context; + parameters.Flags = PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME; + + status = PhEnumProcessModulesEx( + ProcessHandle, + ¶meters + ); + +#ifdef _WIN64 + PhGetProcessIsWow64(ProcessHandle, &isWow64); + + // 32-bit process modules + if (isWow64) + { + context.Callback = Callback; + context.Context = Context; + context.Type = PH_MODULE_TYPE_WOW64_MODULE; + context.BaseAddressHashtable = baseAddressHashtable; + context.LoadOrderIndex = 0; + + status = PhEnumProcessModules32Ex( + ProcessHandle, + ¶meters + ); + } +#endif + + // Mapped files and mapped images + // This is done last because it provides the least amount of information. + + if (Flags & (PH_ENUM_GENERIC_MAPPED_FILES | PH_ENUM_GENERIC_MAPPED_IMAGES)) + { + PhpEnumGenericMappedFilesAndImages( + ProcessHandle, + Flags, + Callback, + Context, + baseAddressHashtable + ); + } + + if (opened) + NtClose(ProcessHandle); + } + +CleanupExit: + PhDereferenceObject(baseAddressHashtable); + + return status; +} + +/** + * Initializes usage of predefined keys. + */ +VOID PhpInitializePredefineKeys( + VOID + ) +{ + static UNICODE_STRING currentUserPrefix = RTL_CONSTANT_STRING(L"\\Registry\\User\\"); + + NTSTATUS status; + HANDLE tokenHandle; + PTOKEN_USER tokenUser; + UNICODE_STRING stringSid; + WCHAR stringSidBuffer[MAX_UNICODE_STACK_BUFFER_LENGTH]; + PUNICODE_STRING currentUserKeyName; + + // Get the string SID of the current user. + if (NT_SUCCESS(status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &tokenHandle))) + { + if (NT_SUCCESS(status = PhGetTokenUser(tokenHandle, &tokenUser))) + { + stringSid.Buffer = stringSidBuffer; + stringSid.MaximumLength = sizeof(stringSidBuffer); + + status = RtlConvertSidToUnicodeString( + &stringSid, + tokenUser->User.Sid, + FALSE + ); + + PhFree(tokenUser); + } + + NtClose(tokenHandle); + } + + // Construct the current user key name. + if (NT_SUCCESS(status)) + { + currentUserKeyName = &PhPredefineKeyNames[PH_KEY_CURRENT_USER_NUMBER]; + currentUserKeyName->Length = currentUserPrefix.Length + stringSid.Length; + currentUserKeyName->Buffer = PhAllocate(currentUserKeyName->Length + sizeof(WCHAR)); + memcpy(currentUserKeyName->Buffer, currentUserPrefix.Buffer, currentUserPrefix.Length); + memcpy(¤tUserKeyName->Buffer[currentUserPrefix.Length / sizeof(WCHAR)], stringSid.Buffer, stringSid.Length); + } +} + +/** + * Initializes the attributes of a key object for creating/opening. + * + * \param RootDirectory A handle to a root key, or one of the predefined keys. See PhCreateKey() for + * details. + * \param ObjectName The path to the key. + * \param Attributes Additional object flags. + * \param ObjectAttributes The OBJECT_ATTRIBUTES structure to initialize. + * \param NeedsClose A variable which receives a handle that must be closed when the create/open + * operation is finished. The variable may be set to NULL if no handle needs to be closed. + */ +NTSTATUS PhpInitializeKeyObjectAttributes( + _In_opt_ HANDLE RootDirectory, + _In_ PUNICODE_STRING ObjectName, + _In_ ULONG Attributes, + _Out_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PHANDLE NeedsClose + ) +{ + NTSTATUS status; + ULONG predefineIndex; + HANDLE predefineHandle; + OBJECT_ATTRIBUTES predefineObjectAttributes; + + InitializeObjectAttributes( + ObjectAttributes, + ObjectName, + Attributes | OBJ_CASE_INSENSITIVE, + RootDirectory, + NULL + ); + + *NeedsClose = NULL; + + if (RootDirectory && PH_KEY_IS_PREDEFINED(RootDirectory)) + { + predefineIndex = PH_KEY_PREDEFINE_TO_NUMBER(RootDirectory); + + if (predefineIndex < PH_KEY_MAXIMUM_PREDEFINE) + { + if (PhBeginInitOnce(&PhPredefineKeyInitOnce)) + { + PhpInitializePredefineKeys(); + PhEndInitOnce(&PhPredefineKeyInitOnce); + } + + predefineHandle = PhPredefineKeyHandles[predefineIndex]; + + if (!predefineHandle) + { + // The predefined key has not been opened yet. Do so now. + + if (!PhPredefineKeyNames[predefineIndex].Buffer) // we may have failed in getting the current user key name + return STATUS_UNSUCCESSFUL; + + InitializeObjectAttributes( + &predefineObjectAttributes, + &PhPredefineKeyNames[predefineIndex], + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtOpenKey( + &predefineHandle, + KEY_READ, + &predefineObjectAttributes + ); + + if (!NT_SUCCESS(status)) + return status; + + if (_InterlockedCompareExchangePointer( + &PhPredefineKeyHandles[predefineIndex], + predefineHandle, + NULL + ) != NULL) + { + // Someone else already opened the key and cached it. Indicate that the caller + // needs to close the handle later, since it isn't shared. + *NeedsClose = predefineHandle; + } + } + + ObjectAttributes->RootDirectory = predefineHandle; + } + } + + return STATUS_SUCCESS; +} + +/** + * Creates or opens a registry key. + * + * \param KeyHandle A variable which receives a handle to the key. + * \param DesiredAccess The desired access to the key. + * \param RootDirectory A handle to a root key, or one of the following predefined keys: + * \li \c PH_KEY_LOCAL_MACHINE Represents \\Registry\\Machine. + * \li \c PH_KEY_USERS Represents \\Registry\\User. + * \li \c PH_KEY_CLASSES_ROOT Represents \\Registry\\Machine\\Software\\Classes. + * \li \c PH_KEY_CURRENT_USER Represents \\Registry\\User\\[SID of current user]. + * \param ObjectName The path to the key. + * \param Attributes Additional object flags. + * \param CreateOptions The options to apply when creating or opening the key. + * \param Disposition A variable which receives a value indicating whether a new key was created or + * an existing key was opened: + * \li \c REG_CREATED_NEW_KEY A new key was created. + * \li \c REG_OPENED_EXISTING_KEY An existing key was opened. + */ +NTSTATUS PhCreateKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ HANDLE RootDirectory, + _In_ PPH_STRINGREF ObjectName, + _In_ ULONG Attributes, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG Disposition + ) +{ + NTSTATUS status; + UNICODE_STRING objectName; + OBJECT_ATTRIBUTES objectAttributes; + HANDLE needsClose; + + if (!PhStringRefToUnicodeString(ObjectName, &objectName)) + return STATUS_NAME_TOO_LONG; + + if (!NT_SUCCESS(status = PhpInitializeKeyObjectAttributes( + RootDirectory, + &objectName, + Attributes, + &objectAttributes, + &needsClose + ))) + { + return status; + } + + status = NtCreateKey( + KeyHandle, + DesiredAccess, + &objectAttributes, + 0, + NULL, + CreateOptions, + Disposition + ); + + if (needsClose) + NtClose(needsClose); + + return status; +} + +/** + * Opens a registry key. + * + * \param KeyHandle A variable which receives a handle to the key. + * \param DesiredAccess The desired access to the key. + * \param RootDirectory A handle to a root key, or one of the predefined keys. See PhCreateKey() for + * details. + * \param ObjectName The path to the key. + * \param Attributes Additional object flags. + */ +NTSTATUS PhOpenKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ HANDLE RootDirectory, + _In_ PPH_STRINGREF ObjectName, + _In_ ULONG Attributes + ) +{ + NTSTATUS status; + UNICODE_STRING objectName; + OBJECT_ATTRIBUTES objectAttributes; + HANDLE needsClose; + + if (!PhStringRefToUnicodeString(ObjectName, &objectName)) + return STATUS_NAME_TOO_LONG; + + if (!NT_SUCCESS(status = PhpInitializeKeyObjectAttributes( + RootDirectory, + &objectName, + Attributes, + &objectAttributes, + &needsClose + ))) + { + return status; + } + + status = NtOpenKey( + KeyHandle, + DesiredAccess, + &objectAttributes + ); + + if (needsClose) + NtClose(needsClose); + + return status; +} + +/** + * Gets information about a registry key. + * + * \param KeyHandle A handle to the key. + * \param KeyInformationClass The information class to query. + * \param Buffer A variable which receives a pointer to a buffer containing information about the + * registry key. You must free the buffer with PhFree() when you no longer need it. + */ +NTSTATUS PhQueryKey( + _In_ HANDLE KeyHandle, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + ULONG attempts = 16; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + + do + { + status = NtQueryKey( + KeyHandle, + KeyInformationClass, + buffer, + bufferSize, + &bufferSize + ); + + if (NT_SUCCESS(status)) + break; + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + PhFree(buffer); + return status; + } + } while (--attempts); + + *Buffer = buffer; + + return status; +} + +/** + * Gets a registry value of any type. + * + * \param KeyHandle A handle to the key. + * \param ValueName The name of the value. + * \param KeyValueInformationClass The information class to query. + * \param Buffer A variable which receives a pointer to a buffer containing information about the + * registry value. You must free the buffer with PhFree() when you no longer need it. + */ +NTSTATUS PhQueryValueKey( + _In_ HANDLE KeyHandle, + _In_opt_ PPH_STRINGREF ValueName, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_ PVOID *Buffer + ) +{ + NTSTATUS status; + UNICODE_STRING valueName; + PVOID buffer; + ULONG bufferSize; + ULONG attempts = 16; + + if (ValueName) + { + if (!PhStringRefToUnicodeString(ValueName, &valueName)) + return STATUS_NAME_TOO_LONG; + } + else + { + RtlInitUnicodeString(&valueName, NULL); + } + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + + do + { + status = NtQueryValueKey( + KeyHandle, + &valueName, + KeyValueInformationClass, + buffer, + bufferSize, + &bufferSize + ); + + if (NT_SUCCESS(status)) + break; + + if (status == STATUS_BUFFER_OVERFLOW) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + PhFree(buffer); + return status; + } + } while (--attempts); + + *Buffer = buffer; + + return status; +} + +/** + * Creates or opens a file. + * + * \param FileHandle A variable that receives the file handle. + * \param FileName The Win32 file name. + * \param DesiredAccess The desired access to the file. + * \param FileAttributes File attributes applied if the file is created or overwritten. + * \param ShareAccess The file access granted to other threads. + * \li \c FILE_SHARE_READ Allows other threads to read from the file. + * \li \c FILE_SHARE_WRITE Allows other threads to write to the file. + * \li \c FILE_SHARE_DELETE Allows other threads to delete the file. + * \param CreateDisposition The action to perform if the file does or does not exist. + * \li \c FILE_SUPERSEDE If the file exists, replace it. Otherwise, create the file. + * \li \c FILE_CREATE If the file exists, fail. Otherwise, create the file. + * \li \c FILE_OPEN If the file exists, open it. Otherwise, fail. + * \li \c FILE_OPEN_IF If the file exists, open it. Otherwise, create the file. + * \li \c FILE_OVERWRITE If the file exists, open and overwrite it. Otherwise, fail. + * \li \c FILE_OVERWRITE_IF If the file exists, open and overwrite it. Otherwise, create the file. + * \param CreateOptions The options to apply when the file is opened or created. + */ +NTSTATUS PhCreateFileWin32( + _Out_ PHANDLE FileHandle, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions + ) +{ + return PhCreateFileWin32Ex( + FileHandle, + FileName, + DesiredAccess, + FileAttributes, + ShareAccess, + CreateDisposition, + CreateOptions, + NULL + ); +} + +/** + * Creates or opens a file. + * + * \param FileHandle A variable that receives the file handle. + * \param FileName The Win32 file name. + * \param DesiredAccess The desired access to the file. + * \param FileAttributes File attributes applied if the file is created or overwritten. + * \param ShareAccess The file access granted to other threads. + * \li \c FILE_SHARE_READ Allows other threads to read from the file. + * \li \c FILE_SHARE_WRITE Allows other threads to write to the file. + * \li \c FILE_SHARE_DELETE Allows other threads to delete the file. + * \param CreateDisposition The action to perform if the file does or does not exist. + * \li \c FILE_SUPERSEDE If the file exists, replace it. Otherwise, create the file. + * \li \c FILE_CREATE If the file exists, fail. Otherwise, create the file. + * \li \c FILE_OPEN If the file exists, open it. Otherwise, fail. + * \li \c FILE_OPEN_IF If the file exists, open it. Otherwise, create the file. + * \li \c FILE_OVERWRITE If the file exists, open and overwrite it. Otherwise, fail. + * \li \c FILE_OVERWRITE_IF If the file exists, open and overwrite it. Otherwise, create the file. + * \param CreateOptions The options to apply when the file is opened or created. + * \param CreateStatus A variable that receives creation information. + * \li \c FILE_SUPERSEDED The file was replaced because \c FILE_SUPERSEDE was specified in + * \a CreateDisposition. + * \li \c FILE_OPENED The file was opened because \c FILE_OPEN or \c FILE_OPEN_IF was specified in + * \a CreateDisposition. + * \li \c FILE_CREATED The file was created because \c FILE_CREATE or \c FILE_OPEN_IF was specified + * in \a CreateDisposition. + * \li \c FILE_OVERWRITTEN The file was overwritten because \c FILE_OVERWRITE or + * \c FILE_OVERWRITE_IF was specified in \a CreateDisposition. + * \li \c FILE_EXISTS The file was not opened because it already existed and \c FILE_CREATE was + * specified in \a CreateDisposition. + * \li \c FILE_DOES_NOT_EXIST The file was not opened because it did not exist and \c FILE_OPEN or + * \c FILE_OVERWRITE was specified in \a CreateDisposition. + */ +NTSTATUS PhCreateFileWin32Ex( + _Out_ PHANDLE FileHandle, + _In_ PWSTR FileName, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG CreateStatus + ) +{ + NTSTATUS status; + HANDLE fileHandle; + UNICODE_STRING fileName; + OBJECT_ATTRIBUTES oa; + IO_STATUS_BLOCK isb; + + if (!FileAttributes) + FileAttributes = FILE_ATTRIBUTE_NORMAL; + + if (!RtlDosPathNameToNtPathName_U( + FileName, + &fileName, + NULL, + NULL + )) + return STATUS_OBJECT_NAME_NOT_FOUND; + + InitializeObjectAttributes( + &oa, + &fileName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtCreateFile( + &fileHandle, + DesiredAccess, + &oa, + &isb, + NULL, + FileAttributes, + ShareAccess, + CreateDisposition, + CreateOptions, + NULL, + 0 + ); + + RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); + + if (NT_SUCCESS(status)) + { + *FileHandle = fileHandle; + } + + if (CreateStatus) + *CreateStatus = (ULONG)isb.Information; + + return status; +} + +/** + * Queries file attributes. + * + * \param FileName The Win32 file name. + * \param FileInformation A variable that receives the file information. + */ +NTSTATUS PhQueryFullAttributesFileWin32( + _In_ PWSTR FileName, + _Out_ PFILE_NETWORK_OPEN_INFORMATION FileInformation + ) +{ + NTSTATUS status; + UNICODE_STRING fileName; + OBJECT_ATTRIBUTES oa; + + if (!RtlDosPathNameToNtPathName_U( + FileName, + &fileName, + NULL, + NULL + )) + return STATUS_OBJECT_NAME_NOT_FOUND; + + InitializeObjectAttributes( + &oa, + &fileName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtQueryFullAttributesFile(&oa, FileInformation); + RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); + + return status; +} + +/** + * Deletes a file. + * + * \param FileName The Win32 file name. + */ +NTSTATUS PhDeleteFileWin32( + _In_ PWSTR FileName + ) +{ + NTSTATUS status; + HANDLE fileHandle; + + status = PhCreateFileWin32( + &fileHandle, + FileName, + DELETE, + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DELETE_ON_CLOSE + ); + + if (!NT_SUCCESS(status)) + return status; + + NtClose(fileHandle); + + return status; +} + +NTSTATUS PhListenNamedPipe( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ) +{ + return NtFsControlFile( + FileHandle, + Event, + ApcRoutine, + ApcContext, + IoStatusBlock, + FSCTL_PIPE_LISTEN, + NULL, + 0, + NULL, + 0 + ); +} + +NTSTATUS PhDisconnectNamedPipe( + _In_ HANDLE FileHandle + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + + status = NtFsControlFile( + FileHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_PIPE_DISCONNECT, + NULL, + 0, + NULL, + 0 + ); + + if (status == STATUS_PENDING) + { + status = NtWaitForSingleObject(FileHandle, FALSE, NULL); + + if (NT_SUCCESS(status)) + status = isb.Status; + } + + return status; +} + +NTSTATUS PhPeekNamedPipe( + _In_ HANDLE FileHandle, + _Out_writes_bytes_opt_(Length) PVOID Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG NumberOfBytesRead, + _Out_opt_ PULONG NumberOfBytesAvailable, + _Out_opt_ PULONG NumberOfBytesLeftInMessage + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + PFILE_PIPE_PEEK_BUFFER peekBuffer; + ULONG peekBufferLength; + + peekBufferLength = FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data) + Length; + peekBuffer = PhAllocate(peekBufferLength); + + status = NtFsControlFile( + FileHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_PIPE_PEEK, + NULL, + 0, + peekBuffer, + peekBufferLength + ); + + if (status == STATUS_PENDING) + { + status = NtWaitForSingleObject(FileHandle, FALSE, NULL); + + if (NT_SUCCESS(status)) + status = isb.Status; + } + + // STATUS_BUFFER_OVERFLOW means that there is data remaining; this is normal. + if (status == STATUS_BUFFER_OVERFLOW) + status = STATUS_SUCCESS; + + if (NT_SUCCESS(status)) + { + ULONG numberOfBytesRead; + + if (Buffer || NumberOfBytesRead || NumberOfBytesLeftInMessage) + numberOfBytesRead = (ULONG)(isb.Information - FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data)); + + if (Buffer) + memcpy(Buffer, peekBuffer->Data, numberOfBytesRead); + + if (NumberOfBytesRead) + *NumberOfBytesRead = numberOfBytesRead; + + if (NumberOfBytesAvailable) + *NumberOfBytesAvailable = peekBuffer->ReadDataAvailable; + + if (NumberOfBytesLeftInMessage) + *NumberOfBytesLeftInMessage = peekBuffer->MessageLength - numberOfBytesRead; + } + + PhFree(peekBuffer); + + return status; +} + +NTSTATUS PhTransceiveNamedPipe( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ) +{ + return NtFsControlFile( + FileHandle, + Event, + ApcRoutine, + ApcContext, + IoStatusBlock, + FSCTL_PIPE_TRANSCEIVE, + InputBuffer, + InputBufferLength, + OutputBuffer, + OutputBufferLength + ); +} + +NTSTATUS PhWaitForNamedPipe( + _In_opt_ PUNICODE_STRING FileSystemName, + _In_ PUNICODE_STRING Name, + _In_opt_ PLARGE_INTEGER Timeout, + _In_ BOOLEAN UseDefaultTimeout + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + UNICODE_STRING localNpfsName; + HANDLE fileSystemHandle; + OBJECT_ATTRIBUTES oa; + PFILE_PIPE_WAIT_FOR_BUFFER waitForBuffer; + ULONG waitForBufferLength; + + if (!FileSystemName) + { + RtlInitUnicodeString(&localNpfsName, L"\\Device\\NamedPipe"); + FileSystemName = &localNpfsName; + } + + InitializeObjectAttributes( + &oa, + FileSystemName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtOpenFile( + &fileSystemHandle, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &oa, + &isb, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + return status; + + waitForBufferLength = FIELD_OFFSET(FILE_PIPE_WAIT_FOR_BUFFER, Name) + Name->Length; + waitForBuffer = PhAllocate(waitForBufferLength); + + if (UseDefaultTimeout) + { + waitForBuffer->TimeoutSpecified = FALSE; + } + else + { + if (Timeout) + { + waitForBuffer->Timeout = *Timeout; + } + else + { + waitForBuffer->Timeout.LowPart = 0; + waitForBuffer->Timeout.HighPart = MINLONG; // a very long time + } + + waitForBuffer->TimeoutSpecified = TRUE; + } + + waitForBuffer->NameLength = (ULONG)Name->Length; + memcpy(waitForBuffer->Name, Name->Buffer, Name->Length); + + status = NtFsControlFile( + fileSystemHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_PIPE_WAIT, + waitForBuffer, + waitForBufferLength, + NULL, + 0 + ); + + PhFree(waitForBuffer); + NtClose(fileSystemHandle); + + return status; +} + +NTSTATUS PhImpersonateClientOfNamedPipe( + _In_ HANDLE FileHandle + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + + status = NtFsControlFile( + FileHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_PIPE_IMPERSONATE, + NULL, + 0, + NULL, + 0 + ); + + return status; +} diff --git a/phlib/phlib.vcxproj b/phlib/phlib.vcxproj new file mode 100644 index 0000000..1100ea0 --- /dev/null +++ b/phlib/phlib.vcxproj @@ -0,0 +1,247 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {477D0215-F252-41A1-874B-F27E3EA1ED17} + phlib + Win32Proj + 10.0.10586.0 + + + + StaticLibrary + Unicode + true + v140 + + + StaticLibrary + Unicode + v140 + + + StaticLibrary + Unicode + true + v140 + + + StaticLibrary + Unicode + v140 + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + + + + Disabled + ..\phnt\include;include;%(AdditionalIncludeDirectories) + DEBUG;_PHLIB_;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + + + Disabled + ..\phnt\include;include;%(AdditionalIncludeDirectories) + DEBUG;_PHLIB_;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + + + MaxSpeed + true + ..\phnt\include;include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_PHLIB_;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + Level3 + ProgramDatabase + StdCall + true + true + StreamingSIMDExtensions + + + + + MaxSpeed + true + ..\phnt\include;include;%(AdditionalIncludeDirectories) + WIN64;NDEBUG;_WINDOWS;_PHLIB_;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + Level3 + ProgramDatabase + StdCall + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phlib/phlib.vcxproj.filters b/phlib/phlib.vcxproj.filters new file mode 100644 index 0000000..1919676 --- /dev/null +++ b/phlib/phlib.vcxproj.filters @@ -0,0 +1,314 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/phlib/provider.c b/phlib/provider.c new file mode 100644 index 0000000..99d97a8 --- /dev/null +++ b/phlib/provider.c @@ -0,0 +1,470 @@ +/* + * Process Hacker - + * provider system + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * Provider objects allow a function to be executed periodically. This is managed by a + * synchronization timer object which is signaled periodically. The use of a timer object as opposed + * to a simple sleep call means that the length of time a provider function takes to execute has no + * effect on the interval between runs. + * + * In contrast to callback objects, the context passed to provider functions must be + * reference-counted objects. This means that it is not guaranteed that the function will not be in + * execution after the unregister operation is complete. However, the since the context object is + * reference-counted, there are no safety issues. + * + * Providers can be boosted, which causes them to be run immediately ignoring the interval. This is + * separate to the periodic runs, and does not cause the next periodic run to be missed. Providers, + * even when boosted, always run on the same provider thread. The other option would be to have the + * boosting thread run the provider function directly, which would involve unnecessary blocking and + * synchronization. + */ + +#include +#include + +#ifdef DEBUG +PPH_LIST PhDbgProviderList; +PH_QUEUED_LOCK PhDbgProviderListLock = PH_QUEUED_LOCK_INIT; +#endif + +/** + * Initializes a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + * \param Interval The interval between each run, in milliseconds. + */ +VOID PhInitializeProviderThread( + _Out_ PPH_PROVIDER_THREAD ProviderThread, + _In_ ULONG Interval + ) +{ + ProviderThread->ThreadHandle = NULL; + ProviderThread->TimerHandle = NULL; + ProviderThread->Interval = Interval; + ProviderThread->State = ProviderThreadStopped; + + PhInitializeQueuedLock(&ProviderThread->Lock); + InitializeListHead(&ProviderThread->ListHead); + ProviderThread->BoostCount = 0; + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgProviderListLock); + if (!PhDbgProviderList) + PhDbgProviderList = PhCreateList(4); + PhAddItemList(PhDbgProviderList, ProviderThread); + PhReleaseQueuedLockExclusive(&PhDbgProviderListLock); +#endif +} + +/** + * Frees resources used by a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + */ +VOID PhDeleteProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ) +{ +#ifdef DEBUG + ULONG index; +#endif + // Nothing + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgProviderListLock); + if ((index = PhFindItemList(PhDbgProviderList, ProviderThread)) != -1) + PhRemoveItemList(PhDbgProviderList, index); + PhReleaseQueuedLockExclusive(&PhDbgProviderListLock); +#endif +} + +NTSTATUS NTAPI PhpProviderThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + PPH_PROVIDER_THREAD providerThread = (PPH_PROVIDER_THREAD)Parameter; + NTSTATUS status = STATUS_SUCCESS; + PLIST_ENTRY listEntry; + PPH_PROVIDER_REGISTRATION registration; + PPH_PROVIDER_FUNCTION providerFunction; + PVOID object; + LIST_ENTRY tempListHead; + + PhInitializeAutoPool(&autoPool); + + while (providerThread->State != ProviderThreadStopping) + { + // Keep removing and executing providers from the list until there are no more. Each removed + // provider will be placed on the temporary list. After this is done, all providers on the + // temporary list will be re-added to the list again. + // + // The key to this working safely with the other functions (boost, register, unregister) is + // that at all times when the mutex is not acquired every single provider must be in a list + // (main list or the temp list). + + InitializeListHead(&tempListHead); + + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + // Main loop. + + // We check the status variable for STATUS_ALERTED, which means that someone is requesting + // that a provider be boosted. Note that if they alert this thread while we are not waiting + // on the timer, when we do perform the wait it will return immediately with STATUS_ALERTED. + + while (TRUE) + { + if (status == STATUS_ALERTED) + { + // Check if we have any more providers to boost. Note that this always works because + // boosted providers are always in front of normal providers. Therefore we will + // never mistakenly boost normal providers. + + if (providerThread->BoostCount == 0) + break; + } + + listEntry = RemoveHeadList(&providerThread->ListHead); + + if (listEntry == &providerThread->ListHead) + break; + + registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry); + + // Add the provider to the temp list. + InsertTailList(&tempListHead, listEntry); + + if (status != STATUS_ALERTED) + { + if (!registration->Enabled || registration->Unregistering) + continue; + } + else + { + // If we're boosting providers, we don't care if they are enabled or not. However, + // we have to make sure any providers which are being unregistered get a chance to + // fix the boost count. + + if (registration->Unregistering) + { + PhReleaseQueuedLockExclusive(&providerThread->Lock); + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + continue; + } + } + + if (status == STATUS_ALERTED) + { + assert(registration->Boosting); + registration->Boosting = FALSE; + providerThread->BoostCount--; + } + + providerFunction = registration->Function; + object = registration->Object; + + if (object) + PhReferenceObject(object); + + registration->RunId++; + + PhReleaseQueuedLockExclusive(&providerThread->Lock); + providerFunction(object); + PhDrainAutoPool(&autoPool); + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + if (object) + PhDereferenceObject(object); + } + + // Re-add the items in the temp list to the main list. + + while ((listEntry = RemoveHeadList(&tempListHead)) != &tempListHead) + { + registration = CONTAINING_RECORD(listEntry, PH_PROVIDER_REGISTRATION, ListEntry); + + // We must insert boosted providers at the front of the list in order to maintain the + // condition that boosted providers are always in front of normal providers. This occurs + // when the timer is signaled just before a boosting provider alerts our thread. + if (!registration->Boosting) + InsertTailList(&providerThread->ListHead, listEntry); + else + InsertHeadList(&providerThread->ListHead, listEntry); + } + + PhReleaseQueuedLockExclusive(&providerThread->Lock); + + // Perform an alertable wait so we can be woken up by someone telling us to boost providers, + // or to terminate. + status = NtWaitForSingleObject( + providerThread->TimerHandle, + TRUE, + NULL + ); + } + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +/** + * Starts a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + */ +VOID PhStartProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ) +{ + if (ProviderThread->State != ProviderThreadStopped) + return; + + // Create and set the timer. + NtCreateTimer(&ProviderThread->TimerHandle, TIMER_ALL_ACCESS, NULL, SynchronizationTimer); + PhSetIntervalProviderThread(ProviderThread, ProviderThread->Interval); + + // Create and start the thread. + ProviderThread->ThreadHandle = PhCreateThread( + 0, + PhpProviderThreadStart, + ProviderThread + ); + + ProviderThread->State = ProviderThreadRunning; +} + +/** + * Stops a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + */ +VOID PhStopProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread + ) +{ + if (ProviderThread->State != ProviderThreadRunning) + return; + + // Signal to the thread that we are shutting down, and wait for it to exit. + ProviderThread->State = ProviderThreadStopping; + NtAlertThread(ProviderThread->ThreadHandle); // wake it up + NtWaitForSingleObject(ProviderThread->ThreadHandle, FALSE, NULL); + + // Free resources. + NtClose(ProviderThread->ThreadHandle); + NtClose(ProviderThread->TimerHandle); + ProviderThread->ThreadHandle = NULL; + ProviderThread->TimerHandle = NULL; + + ProviderThread->State = ProviderThreadStopped; +} + +/** + * Sets the run interval for a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + * \param Interval The interval between each run, in milliseconds. + */ +VOID PhSetIntervalProviderThread( + _Inout_ PPH_PROVIDER_THREAD ProviderThread, + _In_ ULONG Interval + ) +{ + ProviderThread->Interval = Interval; + + if (ProviderThread->TimerHandle) + { + LARGE_INTEGER interval; + + interval.QuadPart = -(LONGLONG)Interval * PH_TIMEOUT_MS; + NtSetTimer(ProviderThread->TimerHandle, &interval, NULL, NULL, FALSE, Interval, NULL); + } +} + +/** + * Registers a provider with a provider thread. + * + * \param ProviderThread A pointer to a provider thread object. + * \param Function The provider function. + * \param Object A pointer to an object to pass to the provider function. The object must be managed + * by the reference-counting system. + * \param Registration A variable which receives registration information for the provider. + * + * \remarks The provider is initially disabled. Call PhSetEnabledProvider() to enable it. + */ +VOID PhRegisterProvider( + _Inout_ PPH_PROVIDER_THREAD ProviderThread, + _In_ PPH_PROVIDER_FUNCTION Function, + _In_opt_ PVOID Object, + _Out_ PPH_PROVIDER_REGISTRATION Registration + ) +{ + Registration->ProviderThread = ProviderThread; + Registration->Function = Function; + Registration->Object = Object; + Registration->RunId = 0; + Registration->Enabled = FALSE; + Registration->Unregistering = FALSE; + Registration->Boosting = FALSE; + + if (Object) + PhReferenceObject(Object); + + PhAcquireQueuedLockExclusive(&ProviderThread->Lock); + InsertTailList(&ProviderThread->ListHead, &Registration->ListEntry); + PhReleaseQueuedLockExclusive(&ProviderThread->Lock); +} + +/** + * Unregisters a provider. + * + * \param Registration A pointer to the registration object for a provider. + * + * \remarks The provider function may still be in execution once this function returns. + */ +VOID PhUnregisterProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration + ) +{ + PPH_PROVIDER_THREAD providerThread; + + providerThread = Registration->ProviderThread; + + Registration->Unregistering = TRUE; + + // There are two possibilities for removal: + // 1. The provider is removed while the thread is not in the main loop. This is the normal case. + // 2. The provider is removed while the thread is in the main loop. In that case the provider + // will be removed from the temp list and so it won't be re-added to the main list. + + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + RemoveEntryList(&Registration->ListEntry); + + // Fix the boost count. + if (Registration->Boosting) + providerThread->BoostCount--; + + // The user-supplied object must be dereferenced + // while the mutex is held. + if (Registration->Object) + PhDereferenceObject(Registration->Object); + + PhReleaseQueuedLockExclusive(&providerThread->Lock); +} + +/** + * Causes a provider to be queued for immediate execution. + * + * \param Registration A pointer to the registration object for a provider. + * \param FutureRunId A variable which receives the run ID of the future run. + * + * \return TRUE if the operation was successful; FALSE if the provider is being unregistered, the + * provider is already being boosted, or the provider thread is not running. + * + * \remarks Boosted providers will be run immediately, ignoring the run interval. Boosting will not + * however affect the normal runs. + */ +BOOLEAN PhBoostProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration, + _Out_opt_ PULONG FutureRunId + ) +{ + PPH_PROVIDER_THREAD providerThread; + ULONG futureRunId; + + if (Registration->Unregistering) + return FALSE; + + providerThread = Registration->ProviderThread; + + // Simply move to the provider to the front of the list. This works even if the provider is + // currently in the temp list. + + PhAcquireQueuedLockExclusive(&providerThread->Lock); + + // Abort if the provider is already being boosted or the provider thread is stopping/stopped. + if (Registration->Boosting || providerThread->State != ProviderThreadRunning) + { + PhReleaseQueuedLockExclusive(&providerThread->Lock); + return FALSE; + } + + RemoveEntryList(&Registration->ListEntry); + InsertHeadList(&providerThread->ListHead, &Registration->ListEntry); + + Registration->Boosting = TRUE; + providerThread->BoostCount++; + + futureRunId = Registration->RunId + 1; + + PhReleaseQueuedLockExclusive(&providerThread->Lock); + + // Wake up the thread. + NtAlertThread(providerThread->ThreadHandle); + + if (FutureRunId) + *FutureRunId = futureRunId; + + return TRUE; +} + +/** + * Gets the current run ID of a provider. + * + * \param Registration A pointer to the registration object for a provider. + */ +ULONG PhGetRunIdProvider( + _In_ PPH_PROVIDER_REGISTRATION Registration + ) +{ + return Registration->RunId; +} + +/** + * Gets whether a provider is enabled. + * + * \param Registration A pointer to the registration object for a provider. + */ +BOOLEAN PhGetEnabledProvider( + _In_ PPH_PROVIDER_REGISTRATION Registration + ) +{ + return Registration->Enabled; +} + +/** + * Sets whether a provider is enabled. + * + * \param Registration A pointer to the registration object for a provider. + * \param Enabled TRUE if the provider is enabled, otherwise FALSE. + */ +VOID PhSetEnabledProvider( + _Inout_ PPH_PROVIDER_REGISTRATION Registration, + _In_ BOOLEAN Enabled + ) +{ + Registration->Enabled = Enabled; +} diff --git a/phlib/queuedlock.c b/phlib/queuedlock.c new file mode 100644 index 0000000..5b9cf97 --- /dev/null +++ b/phlib/queuedlock.c @@ -0,0 +1,1201 @@ +/* + * Process Hacker - + * queued lock + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * Queued lock, a.k.a. push lock (kernel-mode) or slim reader-writer lock (user-mode). + * + * The queued lock is: + * * Around 10% faster than the fast lock. + * * Only the size of a pointer. + * * Low on resource usage (no additional kernel objects are created for blocking). + * + * The usual flags are used for contention-free acquire/release. When there is contention, + * stack-based wait blocks are chained. The first wait block contains the shared owners count which + * is decremented by shared releasers. + * + * Naturally these wait blocks would be chained in FILO order, but list optimization is done for two + * purposes: + * * Finding the last wait block (where the shared owners count is stored). This is implemented by + * the Last pointer. + * * Unblocking the wait blocks in FIFO order. This is implemented by the Previous pointer. + * + * The optimization is incremental - each optimization run will stop at the first optimized wait + * block. Any needed optimization is completed just before waking waiters. + * + * The waiters list/chain has the following restrictions: + * * At any time wait blocks may be pushed onto the list. + * * While waking waiters, the list may not be traversed nor optimized. + * * When there are multiple shared owners, shared releasers may traverse the list (to find the last + * wait block). This is not an issue because waiters wouldn't be woken until there are no more + * shared owners. + * * List optimization may be done at any time except for when someone else is waking waiters. This + * is controlled by the traversing bit. + * + * The traversing bit has the following rules: + * * The list may be optimized only after the traversing bit is set, checking that it wasn't set + * already. If it was set, it would indicate that someone else is optimizing the list or waking + * waiters. + * * Before waking waiters the traversing bit must be set. If it was set already, just clear the + * owned bit. + * * If during list optimization the owned bit is detected to be cleared, the function begins waking + * waiters. This is because the owned bit is cleared when a releaser fails to set the traversing + * bit. + * + * Blocking is implemented through a process-wide keyed event. A spin count is also used before + * blocking on the keyed event. + * + * Queued locks can act as condition variables, with wait, pulse and pulse all support. Waiters are + * released in FIFO order. + * + * Queued locks can act as wake events. These are designed for tiny one-bit locks which share a + * single event to block on. Spurious wake-ups are a part of normal operation. + */ + +#include +#include +#include +#include + +VOID FASTCALL PhpfOptimizeQueuedLockList( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ); + +VOID FASTCALL PhpfWakeQueuedLock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ); + +VOID FASTCALL PhpfWakeQueuedLockEx( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value, + _In_ BOOLEAN IgnoreOwned, + _In_ BOOLEAN WakeAll + ); + +static HANDLE PhQueuedLockKeyedEventHandle; +static ULONG PhQueuedLockSpinCount = 2000; + +BOOLEAN PhQueuedLockInitialization( + VOID + ) +{ + if (!NT_SUCCESS(NtCreateKeyedEvent( + &PhQueuedLockKeyedEventHandle, + KEYEDEVENT_ALL_ACCESS, + NULL, + 0 + ))) + return FALSE; + + if ((ULONG)PhSystemBasicInformation.NumberOfProcessors > 1) + PhQueuedLockSpinCount = 4000; + else + PhQueuedLockSpinCount = 0; + + return TRUE; +} + +/** + * Pushes a wait block onto a queued lock's waiters list. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * \param Exclusive Whether the wait block is in exclusive mode. + * \param WaitBlock A variable which receives the resulting wait block structure. + * \param Optimize A variable which receives a boolean indicating whether to optimize the waiters + * list. + * \param NewValue The old value of the queued lock. This value is useful only if the function + * returns FALSE. + * \param CurrentValue The new value of the queued lock. This value is useful only if the function + * returns TRUE. + * + * \return TRUE if the wait block was pushed onto the waiters list, otherwise FALSE. + * + * \remarks + * \li The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_OWNED. + * \li Do not move the wait block location after this function is called. + * \li The \a Optimize boolean is a hint to call PhpfOptimizeQueuedLockList() if the function + * succeeds. It is recommended, but not essential that this occurs. + * \li Call PhpBlockOnQueuedWaitBlock() to wait for the wait block to be unblocked. + */ +FORCEINLINE BOOLEAN PhpPushQueuedWaitBlock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value, + _In_ BOOLEAN Exclusive, + _Out_ PPH_QUEUED_WAIT_BLOCK WaitBlock, + _Out_ PBOOLEAN Optimize, + _Out_ PULONG_PTR NewValue, + _Out_ PULONG_PTR CurrentValue + ) +{ + ULONG_PTR newValue; + BOOLEAN optimize; + + WaitBlock->Previous = NULL; // set later by optimization + optimize = FALSE; + + if (Exclusive) + WaitBlock->Flags = PH_QUEUED_WAITER_EXCLUSIVE | PH_QUEUED_WAITER_SPINNING; + else + WaitBlock->Flags = PH_QUEUED_WAITER_SPINNING; + + if (Value & PH_QUEUED_LOCK_WAITERS) + { + // We're not the first waiter. + + WaitBlock->Last = NULL; // set later by optimization + WaitBlock->Next = PhGetQueuedLockWaitBlock(Value); + WaitBlock->SharedOwners = 0; + + // Push our wait block onto the list. + // Set the traversing bit because we'll be optimizing the list. + newValue = ((ULONG_PTR)WaitBlock) | (Value & PH_QUEUED_LOCK_FLAGS) | + PH_QUEUED_LOCK_TRAVERSING; + + if (!(Value & PH_QUEUED_LOCK_TRAVERSING)) + optimize = TRUE; + } + else + { + // We're the first waiter. + + WaitBlock->Last = WaitBlock; // indicate that this is the last wait block + + if (Exclusive) + { + // We're the first waiter. Save the shared owners count. + WaitBlock->SharedOwners = (ULONG)PhGetQueuedLockSharedOwners(Value); + + if (WaitBlock->SharedOwners > 1) + { + newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED | + PH_QUEUED_LOCK_WAITERS | PH_QUEUED_LOCK_MULTIPLE_SHARED; + } + else + { + newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED | + PH_QUEUED_LOCK_WAITERS; + } + } + else + { + // We're waiting in shared mode, which means there can't be any shared owners (otherwise + // we would've acquired the lock already). + + WaitBlock->SharedOwners = 0; + + newValue = ((ULONG_PTR)WaitBlock) | PH_QUEUED_LOCK_OWNED | + PH_QUEUED_LOCK_WAITERS; + } + } + + *Optimize = optimize; + *CurrentValue = newValue; + + newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)Value + ); + + *NewValue = newValue; + + return newValue == Value; +} + +/** + * Finds the last wait block in the waiters list. + * + * \param Value The current value of the queued lock. + * + * \return A pointer to the last wait block. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS, + * \ref PH_QUEUED_LOCK_MULTIPLE_SHARED or + * \ref PH_QUEUED_LOCK_TRAVERSING. + */ +FORCEINLINE PPH_QUEUED_WAIT_BLOCK PhpFindLastQueuedWaitBlock( + _In_ ULONG_PTR Value + ) +{ + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK lastWaitBlock; + + waitBlock = PhGetQueuedLockWaitBlock(Value); + + // Traverse the list until we find the last wait block. + // The Last pointer should be set by list optimization, allowing us to skip all, if not most of + // the wait blocks. + + while (TRUE) + { + lastWaitBlock = waitBlock->Last; + + if (lastWaitBlock) + { + // Follow the Last pointer. This can mean two things: the pointer was set by list + // optimization, or this wait block is actually the last wait block (set when it was + // pushed onto the list). + waitBlock = lastWaitBlock; + break; + } + + waitBlock = waitBlock->Next; + } + + return waitBlock; +} + +/** + * Waits for a wait block to be unblocked. + * + * \param WaitBlock A wait block. + * \param Spin TRUE to spin, FALSE to block immediately. + * \param Timeout A timeout value. + */ +_May_raise_ FORCEINLINE NTSTATUS PhpBlockOnQueuedWaitBlock( + _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock, + _In_ BOOLEAN Spin, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + NTSTATUS status; + ULONG i; + + if (Spin) + { + PHLIB_INC_STATISTIC(QlBlockSpins); + + for (i = PhQueuedLockSpinCount; i != 0; i--) + { + if (!(*(volatile ULONG *)&WaitBlock->Flags & PH_QUEUED_WAITER_SPINNING)) + return STATUS_SUCCESS; + + YieldProcessor(); + } + } + + if (_interlockedbittestandreset((PLONG)&WaitBlock->Flags, PH_QUEUED_WAITER_SPINNING_SHIFT)) + { + PHLIB_INC_STATISTIC(QlBlockWaits); + + status = NtWaitForKeyedEvent( + PhQueuedLockKeyedEventHandle, + WaitBlock, + FALSE, + Timeout + ); + + // If an error occurred (timeout is not an error), raise an exception as it is nearly + // impossible to recover from this situation. + if (!NT_SUCCESS(status)) + PhRaiseStatus(status); + } + else + { + status = STATUS_SUCCESS; + } + + return status; +} + +/** + * Unblocks a wait block. + * + * \param WaitBlock A wait block. + * + * \remarks The wait block is in an undefined state after it is unblocked. Do not attempt to read + * any values from it. All relevant information should be saved before unblocking the wait block. + */ +_May_raise_ FORCEINLINE VOID PhpUnblockQueuedWaitBlock( + _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ) +{ + NTSTATUS status; + + if (!_interlockedbittestandreset((PLONG)&WaitBlock->Flags, PH_QUEUED_WAITER_SPINNING_SHIFT)) + { + if (!NT_SUCCESS(status = NtReleaseKeyedEvent( + PhQueuedLockKeyedEventHandle, + WaitBlock, + FALSE, + NULL + ))) + PhRaiseStatus(status); + } +} + +/** + * Optimizes a queued lock waiters list. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * \param IgnoreOwned TRUE to ignore lock state, FALSE to conduct normal checks. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS, \ref PH_QUEUED_LOCK_TRAVERSING. + */ +FORCEINLINE VOID PhpOptimizeQueuedLockListEx( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value, + _In_ BOOLEAN IgnoreOwned + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK firstWaitBlock; + PPH_QUEUED_WAIT_BLOCK lastWaitBlock; + PPH_QUEUED_WAIT_BLOCK previousWaitBlock; + + value = Value; + + while (TRUE) + { + assert(value & PH_QUEUED_LOCK_TRAVERSING); + + if (!IgnoreOwned && !(value & PH_QUEUED_LOCK_OWNED)) + { + // Someone has requested that we wake waiters. + PhpfWakeQueuedLock(QueuedLock, value); + break; + } + + // Perform the optimization. + + waitBlock = PhGetQueuedLockWaitBlock(value); + firstWaitBlock = waitBlock; + + while (TRUE) + { + lastWaitBlock = waitBlock->Last; + + if (lastWaitBlock) + { + // Save a pointer to the last wait block in the first wait block and stop + // optimizing. + // + // We don't need to continue setting Previous pointers because the last optimization + // run would have set them already. + + firstWaitBlock->Last = lastWaitBlock; + break; + } + + previousWaitBlock = waitBlock; + waitBlock = waitBlock->Next; + waitBlock->Previous = previousWaitBlock; + } + + // Try to clear the traversing bit. + + newValue = value - PH_QUEUED_LOCK_TRAVERSING; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + break; + + // Either someone pushed a wait block onto the list or someone released ownership. In either + // case we need to go back. + + value = newValue; + } +} + +/** + * Optimizes a queued lock waiters list. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS, \ref PH_QUEUED_LOCK_TRAVERSING. + */ +VOID FASTCALL PhpfOptimizeQueuedLockList( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ) +{ + PhpOptimizeQueuedLockListEx(QueuedLock, Value, FALSE); +} + +/** + * Dequeues the appropriate number of wait blocks in a queued lock. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * \param IgnoreOwned TRUE to ignore lock state, FALSE to conduct normal checks. + * \param WakeAll TRUE to remove all wait blocks, FALSE to decide based on the wait block type. + */ +FORCEINLINE PPH_QUEUED_WAIT_BLOCK PhpPrepareToWakeQueuedLock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value, + _In_ BOOLEAN IgnoreOwned, + _In_ BOOLEAN WakeAll + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK firstWaitBlock; + PPH_QUEUED_WAIT_BLOCK lastWaitBlock; + PPH_QUEUED_WAIT_BLOCK previousWaitBlock; + + value = Value; + + while (TRUE) + { + // If there are multiple shared owners, no one is going to wake waiters since the lock would + // still be owned. Also if there are multiple shared owners they may be traversing the list. + // While that is safe when done concurrently with list optimization, we may be removing and + // waking waiters. + assert(!(value & PH_QUEUED_LOCK_MULTIPLE_SHARED)); + assert(IgnoreOwned || (value & PH_QUEUED_LOCK_TRAVERSING)); + + // There's no point in waking a waiter if the lock is owned. Clear the traversing bit. + while (!IgnoreOwned && (value & PH_QUEUED_LOCK_OWNED)) + { + newValue = value - PH_QUEUED_LOCK_TRAVERSING; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + return NULL; + + value = newValue; + } + + // Finish up any needed optimization (setting the Previous pointers) while finding the last + // wait block. + + waitBlock = PhGetQueuedLockWaitBlock(value); + firstWaitBlock = waitBlock; + + while (TRUE) + { + lastWaitBlock = waitBlock->Last; + + if (lastWaitBlock) + { + waitBlock = lastWaitBlock; + break; + } + + previousWaitBlock = waitBlock; + waitBlock = waitBlock->Next; + waitBlock->Previous = previousWaitBlock; + } + + // Unlink the relevant wait blocks and clear the traversing bit before we wake waiters. + + if ( + !WakeAll && + (waitBlock->Flags & PH_QUEUED_WAITER_EXCLUSIVE) && + (previousWaitBlock = waitBlock->Previous) + ) + { + // We have an exclusive waiter and there are multiple waiters. We'll only be waking this + // waiter. + + // Unlink the wait block from the list. Although other wait blocks may have their Last + // pointers set to this wait block, the algorithm to find the last wait block will stop + // here. Likewise the Next pointers are never followed beyond this point, so we don't + // need to clear those. + firstWaitBlock->Last = previousWaitBlock; + + // Make sure we only wake this waiter. + waitBlock->Previous = NULL; + + if (!IgnoreOwned) + { + // Clear the traversing bit. + _InterlockedExchangeAddPointer((PLONG_PTR)&QueuedLock->Value, -(LONG_PTR)PH_QUEUED_LOCK_TRAVERSING); + } + + break; + } + else + { + // We're waking an exclusive waiter and there is only one waiter, or we are waking a + // shared waiter and possibly others. + + newValue = 0; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + break; + + // Someone changed the lock (acquired it or pushed a wait block). + + value = newValue; + } + } + + return waitBlock; +} + +/** + * Wakes waiters in a queued lock. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS, \ref PH_QUEUED_LOCK_TRAVERSING. The function assumes the following + * flags are not set: + * \ref PH_QUEUED_LOCK_MULTIPLE_SHARED. + */ +VOID FASTCALL PhpfWakeQueuedLock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ) +{ + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK previousWaitBlock; + + waitBlock = PhpPrepareToWakeQueuedLock(QueuedLock, Value, FALSE, FALSE); + + // Wake waiters. + + while (waitBlock) + { + previousWaitBlock = waitBlock->Previous; + PhpUnblockQueuedWaitBlock(waitBlock); + waitBlock = previousWaitBlock; + } +} + +/** + * Wakes waiters in a queued lock. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * \param IgnoreOwned TRUE to ignore lock state, FALSE to conduct normal checks. + * \param WakeAll TRUE to wake all waiters, FALSE to decide based on the wait block type. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS, \ref PH_QUEUED_LOCK_TRAVERSING. The function assumes the following + * flags are not set: + * \ref PH_QUEUED_LOCK_MULTIPLE_SHARED. + */ +VOID FASTCALL PhpfWakeQueuedLockEx( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value, + _In_ BOOLEAN IgnoreOwned, + _In_ BOOLEAN WakeAll + ) +{ + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK previousWaitBlock; + + waitBlock = PhpPrepareToWakeQueuedLock(QueuedLock, Value, IgnoreOwned, WakeAll); + + // Wake waiters. + + while (waitBlock) + { + previousWaitBlock = waitBlock->Previous; + PhpUnblockQueuedWaitBlock(waitBlock); + waitBlock = previousWaitBlock; + } +} + +/** + * Acquires a queued lock in exclusive mode. + * + * \param QueuedLock A queued lock. + */ +VOID FASTCALL PhfAcquireQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + ULONG_PTR currentValue; + BOOLEAN optimize; + PH_QUEUED_WAIT_BLOCK waitBlock; + + value = QueuedLock->Value; + + while (TRUE) + { + if (!(value & PH_QUEUED_LOCK_OWNED)) + { + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)(value + PH_QUEUED_LOCK_OWNED), + (PVOID)value + )) == value) + break; + } + else + { + if (PhpPushQueuedWaitBlock( + QueuedLock, + value, + TRUE, + &waitBlock, + &optimize, + &newValue, + ¤tValue + )) + { + if (optimize) + PhpfOptimizeQueuedLockList(QueuedLock, currentValue); + + PHLIB_INC_STATISTIC(QlAcquireExclusiveBlocks); + PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL); + } + } + + value = newValue; + } +} + +/** + * Acquires a queued lock in shared mode. + * + * \param QueuedLock A queued lock. + */ +VOID FASTCALL PhfAcquireQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + ULONG_PTR currentValue; + BOOLEAN optimize; + PH_QUEUED_WAIT_BLOCK waitBlock; + + value = QueuedLock->Value; + + while (TRUE) + { + // We can't acquire if there are waiters for two reasons: + // + // We want to prioritize exclusive acquires over shared acquires. There's currently no fast, + // safe way of finding the last wait block and incrementing the shared owners count here. + if ( + !(value & PH_QUEUED_LOCK_WAITERS) && + (!(value & PH_QUEUED_LOCK_OWNED) || (PhGetQueuedLockSharedOwners(value) > 0)) + ) + { + newValue = (value + PH_QUEUED_LOCK_SHARED_INC) | PH_QUEUED_LOCK_OWNED; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + break; + } + else + { + if (PhpPushQueuedWaitBlock( + QueuedLock, + value, + FALSE, + &waitBlock, + &optimize, + &newValue, + ¤tValue + )) + { + if (optimize) + PhpfOptimizeQueuedLockList(QueuedLock, currentValue); + + PHLIB_INC_STATISTIC(QlAcquireSharedBlocks); + PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL); + } + } + + value = newValue; + } +} + +/** + * Releases a queued lock in exclusive mode. + * + * \param QueuedLock A queued lock. + */ +VOID FASTCALL PhfReleaseQueuedLockExclusive( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + ULONG_PTR currentValue; + + value = QueuedLock->Value; + + while (TRUE) + { + assert(value & PH_QUEUED_LOCK_OWNED); + assert((value & PH_QUEUED_LOCK_WAITERS) || (PhGetQueuedLockSharedOwners(value) == 0)); + + if ((value & (PH_QUEUED_LOCK_WAITERS | PH_QUEUED_LOCK_TRAVERSING)) != PH_QUEUED_LOCK_WAITERS) + { + // There are no waiters or someone is traversing the list. + // + // If there are no waiters, we're simply releasing ownership. If someone is traversing + // the list, clearing the owned bit is a signal for them to wake waiters. + + newValue = value - PH_QUEUED_LOCK_OWNED; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + break; + } + else + { + // We need to wake waiters and no one is traversing the list. + // Try to set the traversing bit and wake waiters. + + newValue = value - PH_QUEUED_LOCK_OWNED + PH_QUEUED_LOCK_TRAVERSING; + currentValue = newValue; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + { + PhpfWakeQueuedLock(QueuedLock, currentValue); + break; + } + } + + value = newValue; + } +} + +/** + * Wakes waiters in a queued lock for releasing it in exclusive mode. + * + * \param QueuedLock A queued lock. + * \param Value The current value of the queued lock. + * + * \remarks The function assumes the following flags are set: + * \ref PH_QUEUED_LOCK_WAITERS. The function assumes the following flags are not set: + * \ref PH_QUEUED_LOCK_MULTIPLE_SHARED, \ref PH_QUEUED_LOCK_TRAVERSING. + */ +VOID FASTCALL PhfWakeForReleaseQueuedLock( + _Inout_ PPH_QUEUED_LOCK QueuedLock, + _In_ ULONG_PTR Value + ) +{ + ULONG_PTR newValue; + + newValue = Value + PH_QUEUED_LOCK_TRAVERSING; + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)Value + ) == Value) + { + PhpfWakeQueuedLock(QueuedLock, newValue); + } +} + +/** + * Releases a queued lock in shared mode. + * + * \param QueuedLock A queued lock. + */ +VOID FASTCALL PhfReleaseQueuedLockShared( + _Inout_ PPH_QUEUED_LOCK QueuedLock + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + ULONG_PTR currentValue; + PPH_QUEUED_WAIT_BLOCK waitBlock; + + value = QueuedLock->Value; + + while (!(value & PH_QUEUED_LOCK_WAITERS)) + { + assert(value & PH_QUEUED_LOCK_OWNED); + assert((value & PH_QUEUED_LOCK_WAITERS) || (PhGetQueuedLockSharedOwners(value) > 0)); + + if (PhGetQueuedLockSharedOwners(value) > 1) + newValue = value - PH_QUEUED_LOCK_SHARED_INC; + else + newValue = 0; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + return; + + value = newValue; + } + + if (value & PH_QUEUED_LOCK_MULTIPLE_SHARED) + { + // Unfortunately we have to find the last wait block and decrement the shared owners count. + waitBlock = PhpFindLastQueuedWaitBlock(value); + + if ((ULONG)_InterlockedDecrement((PLONG)&waitBlock->SharedOwners) > 0) + return; + } + + while (TRUE) + { + if (value & PH_QUEUED_LOCK_TRAVERSING) + { + newValue = value & ~(PH_QUEUED_LOCK_OWNED | PH_QUEUED_LOCK_MULTIPLE_SHARED); + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + break; + } + else + { + newValue = (value & ~(PH_QUEUED_LOCK_OWNED | PH_QUEUED_LOCK_MULTIPLE_SHARED)) | + PH_QUEUED_LOCK_TRAVERSING; + currentValue = newValue; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&QueuedLock->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + { + PhpfWakeQueuedLock(QueuedLock, currentValue); + break; + } + } + + value = newValue; + } +} + +/** + * Wakes one thread sleeping on a condition variable. + * + * \param Condition A condition variable. + * + * \remarks The associated lock must be acquired before calling the function. + */ +VOID FASTCALL PhfPulseCondition( + _Inout_ PPH_CONDITION Condition + ) +{ + if (Condition->Value & PH_QUEUED_LOCK_WAITERS) + PhpfWakeQueuedLockEx(Condition, Condition->Value, TRUE, FALSE); +} + +/** + * Wakes all threads sleeping on a condition variable. + * + * \param Condition A condition variable. + * + * \remarks The associated lock must be acquired before calling the function. + */ +VOID FASTCALL PhfPulseAllCondition( + _Inout_ PPH_CONDITION Condition + ) +{ + if (Condition->Value & PH_QUEUED_LOCK_WAITERS) + PhpfWakeQueuedLockEx(Condition, Condition->Value, TRUE, TRUE); +} + +/** + * Sleeps on a condition variable. + * + * \param Condition A condition variable. + * \param Lock A queued lock to release/acquire in exclusive mode. + * \param Timeout Not implemented. + * + * \remarks The associated lock must be acquired before calling the function. + */ +VOID FASTCALL PhfWaitForCondition( + _Inout_ PPH_CONDITION Condition, + _Inout_ PPH_QUEUED_LOCK Lock, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + ULONG_PTR value; + ULONG_PTR currentValue; + PH_QUEUED_WAIT_BLOCK waitBlock; + BOOLEAN optimize; + + value = Condition->Value; + + while (TRUE) + { + if (PhpPushQueuedWaitBlock( + Condition, + value, + TRUE, + &waitBlock, + &optimize, + &value, + ¤tValue + )) + { + if (optimize) + { + PhpOptimizeQueuedLockListEx(Condition, currentValue, TRUE); + } + + PhReleaseQueuedLockExclusive(Lock); + + PhpBlockOnQueuedWaitBlock(&waitBlock, FALSE, NULL); + + // Don't use the inline variant; it is extremely likely that the lock is still owned. + PhfAcquireQueuedLockExclusive(Lock); + + break; + } + } +} + +/** + * Sleeps on a condition variable. + * + * \param Condition A condition variable. + * \param Lock A pointer to a lock. + * \param Flags A combination of flags controlling the operation. + * \param Timeout Not implemented. + */ +VOID FASTCALL PhfWaitForConditionEx( + _Inout_ PPH_CONDITION Condition, + _Inout_ PVOID Lock, + _In_ ULONG Flags, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + ULONG_PTR value; + ULONG_PTR currentValue; + PH_QUEUED_WAIT_BLOCK waitBlock; + BOOLEAN optimize; + + value = Condition->Value; + + while (TRUE) + { + if (PhpPushQueuedWaitBlock( + Condition, + value, + TRUE, + &waitBlock, + &optimize, + &value, + ¤tValue + )) + { + if (optimize) + { + PhpOptimizeQueuedLockListEx(Condition, currentValue, TRUE); + } + + switch (Flags & PH_CONDITION_WAIT_LOCK_TYPE_MASK) + { + case PH_CONDITION_WAIT_QUEUED_LOCK: + if (!(Flags & PH_CONDITION_WAIT_SHARED)) + PhReleaseQueuedLockExclusive((PPH_QUEUED_LOCK)Lock); + else + PhReleaseQueuedLockShared((PPH_QUEUED_LOCK)Lock); + break; + case PH_CONDITION_WAIT_CRITICAL_SECTION: + RtlLeaveCriticalSection((PRTL_CRITICAL_SECTION)Lock); + break; + case PH_CONDITION_WAIT_FAST_LOCK: + if (!(Flags & PH_CONDITION_WAIT_SHARED)) + PhReleaseFastLockExclusive((PPH_FAST_LOCK)Lock); + else + PhReleaseFastLockShared((PPH_FAST_LOCK)Lock); + break; + } + + if (!(Flags & PH_CONDITION_WAIT_SPIN)) + { + PhpBlockOnQueuedWaitBlock(&waitBlock, FALSE, NULL); + } + else + { + PhpBlockOnQueuedWaitBlock(&waitBlock, TRUE, NULL); + } + + switch (Flags & PH_CONDITION_WAIT_LOCK_TYPE_MASK) + { + case PH_CONDITION_WAIT_QUEUED_LOCK: + if (!(Flags & PH_CONDITION_WAIT_SHARED)) + PhfAcquireQueuedLockExclusive((PPH_QUEUED_LOCK)Lock); + else + PhfAcquireQueuedLockShared((PPH_QUEUED_LOCK)Lock); + break; + case PH_CONDITION_WAIT_CRITICAL_SECTION: + RtlEnterCriticalSection((PRTL_CRITICAL_SECTION)Lock); + break; + case PH_CONDITION_WAIT_FAST_LOCK: + if (!(Flags & PH_CONDITION_WAIT_SHARED)) + PhAcquireFastLockExclusive((PPH_FAST_LOCK)Lock); + else + PhAcquireFastLockShared((PPH_FAST_LOCK)Lock); + break; + } + + break; + } + } +} + +/** + * Queues a wait block to a wake event. + * + * \param WakeEvent A wake event. + * \param WaitBlock A wait block. + * + * \remarks If you later determine that the wait should not occur, you must call PhfSetWakeEvent() + * to dequeue the wait block. + */ +VOID FASTCALL PhfQueueWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Out_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ) +{ + PPH_QUEUED_WAIT_BLOCK value; + PPH_QUEUED_WAIT_BLOCK newValue; + + WaitBlock->Flags = PH_QUEUED_WAITER_SPINNING; + + value = (PPH_QUEUED_WAIT_BLOCK)WakeEvent->Value; + + while (TRUE) + { + WaitBlock->Next = value; + + if ((newValue = _InterlockedCompareExchangePointer( + (PVOID *)&WakeEvent->Value, + WaitBlock, + value + )) == value) + break; + + value = newValue; + } +} + +/** + * Sets a wake event, unblocking all queued wait blocks. + * + * \param WakeEvent A wake event. + * \param WaitBlock A wait block for a cancelled wait, otherwise NULL. + */ +VOID FASTCALL PhfSetWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Inout_opt_ PPH_QUEUED_WAIT_BLOCK WaitBlock + ) +{ + PPH_QUEUED_WAIT_BLOCK waitBlock; + PPH_QUEUED_WAIT_BLOCK nextWaitBlock; + + // Pop all waiters and unblock them. + + waitBlock = _InterlockedExchangePointer((PVOID *)&WakeEvent->Value, NULL); + + while (waitBlock) + { + nextWaitBlock = waitBlock->Next; + PhpUnblockQueuedWaitBlock(waitBlock); + waitBlock = nextWaitBlock; + } + + if (WaitBlock) + { + // We're cancelling a wait; the thread called this function instead of PhfWaitForWakeEvent. + // This will remove all waiters from the list. However, we may not have popped and unblocked + // the cancelled wait block ourselves. Another thread may have popped all waiters but not + // unblocked them yet at this point: + // + // 1. This thread: calls PhfQueueWakeEvent. + // 2. This thread: code determines that the wait should be cancelled. + // 3. Other thread: calls PhfSetWakeEvent and pops our wait block off. It hasn't unblocked + // any wait blocks yet. + // 4. This thread: calls PhfSetWakeEvent. Since all wait blocks have been popped, we don't + // do anything. The caller function exits, making our wait block invalid. + // 5. Other thread: tries to unblock our wait block. Anything could happen, since our caller + // already returned. + // + // The solution is to (always) wait for an unblock. Note that the check below for the + // spinning flag is not required, but it is a small optimization. If the wait block has been + // unblocked (i.e. the spinning flag is cleared), then there's no danger. + + if (WaitBlock->Flags & PH_QUEUED_WAITER_SPINNING) + PhpBlockOnQueuedWaitBlock(WaitBlock, FALSE, NULL); + } +} + +/** + * Waits for a wake event to be set. + * + * \param WakeEvent A wake event. + * \param WaitBlock A wait block previously queued to the wake event using PhfQueueWakeEvent(). + * \param Spin TRUE to spin on the wake event before blocking, FALSE to block immediately. + * \param Timeout A timeout value. + * + * \remarks Wake events are subject to spurious wakeups. You should call this function in a loop + * which checks a predicate. + */ +NTSTATUS FASTCALL PhfWaitForWakeEvent( + _Inout_ PPH_WAKE_EVENT WakeEvent, + _Inout_ PPH_QUEUED_WAIT_BLOCK WaitBlock, + _In_ BOOLEAN Spin, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + NTSTATUS status; + + status = PhpBlockOnQueuedWaitBlock(WaitBlock, Spin, Timeout); + + if (status != STATUS_SUCCESS) + { + // Probably a timeout. There's no way of unlinking the wait block safely, so just wake + // everyone. + PhSetWakeEvent(WakeEvent, WaitBlock); + } + + return status; +} diff --git a/phlib/ref.c b/phlib/ref.c new file mode 100644 index 0000000..315f74f --- /dev/null +++ b/phlib/ref.c @@ -0,0 +1,753 @@ +/* + * Process Hacker - + * object manager + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +PPH_OBJECT_TYPE PhObjectTypeObject = NULL; +SLIST_HEADER PhObjectDeferDeleteListHead; +PH_FREE_LIST PhObjectSmallFreeList; +PPH_OBJECT_TYPE PhAllocType = NULL; + +ULONG PhObjectTypeCount = 0; +PPH_OBJECT_TYPE PhObjectTypeTable[PH_OBJECT_TYPE_TABLE_SIZE]; + +static ULONG PhpAutoPoolTlsIndex; + +#ifdef DEBUG +LIST_ENTRY PhDbgObjectListHead; +PH_QUEUED_LOCK PhDbgObjectListLock = PH_QUEUED_LOCK_INIT; +PPH_CREATE_OBJECT_HOOK PhDbgCreateObjectHook = NULL; +#endif + +#define REF_STAT_UP(Name) PHLIB_INC_STATISTIC(Name) + +/** + * Initializes the object manager module. + */ +NTSTATUS PhRefInitialization( + VOID + ) +{ + PH_OBJECT_TYPE dummyObjectType; + +#ifdef DEBUG + InitializeListHead(&PhDbgObjectListHead); +#endif + + RtlInitializeSListHead(&PhObjectDeferDeleteListHead); + PhInitializeFreeList( + &PhObjectSmallFreeList, + PhAddObjectHeaderSize(PH_OBJECT_SMALL_OBJECT_SIZE), + PH_OBJECT_SMALL_OBJECT_COUNT + ); + + // Create the fundamental object type. + + memset(&dummyObjectType, 0, sizeof(PH_OBJECT_TYPE)); + PhObjectTypeObject = &dummyObjectType; // PhCreateObject expects an object type. + PhObjectTypeTable[0] = &dummyObjectType; // PhCreateObject also expects PhObjectTypeTable[0] to be filled in. + PhObjectTypeObject = PhCreateObjectType(L"Type", 0, NULL); + + // Now that the fundamental object type exists, fix it up. + PhObjectToObjectHeader(PhObjectTypeObject)->TypeIndex = PhObjectTypeObject->TypeIndex; + PhObjectTypeObject->NumberOfObjects = 1; + + // Create the allocated memory object type. + PhAllocType = PhCreateObjectType(L"Alloc", 0, NULL); + + // Reserve a slot for the auto pool. + PhpAutoPoolTlsIndex = TlsAlloc(); + + if (PhpAutoPoolTlsIndex == TLS_OUT_OF_INDEXES) + return STATUS_INSUFFICIENT_RESOURCES; + + return STATUS_SUCCESS; +} + +/** + * Allocates a object. + * + * \param ObjectSize The size of the object. + * \param ObjectType The type of the object. + * + * \return A pointer to the newly allocated object. + */ +_May_raise_ PVOID PhCreateObject( + _In_ SIZE_T ObjectSize, + _In_ PPH_OBJECT_TYPE ObjectType + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PPH_OBJECT_HEADER objectHeader; + + // Allocate storage for the object. Note that this includes the object header followed by the + // object body. + objectHeader = PhpAllocateObject(ObjectType, ObjectSize); + + // Object type statistics. + _InterlockedIncrement((PLONG)&ObjectType->NumberOfObjects); + + // Initialize the object header. + objectHeader->RefCount = 1; + objectHeader->TypeIndex = ObjectType->TypeIndex; + // objectHeader->Flags is set by PhpAllocateObject. + + REF_STAT_UP(RefObjectsCreated); + +#ifdef DEBUG + { + USHORT capturedFrames; + + capturedFrames = RtlCaptureStackBackTrace(1, 16, objectHeader->StackBackTrace, NULL); + memset( + &objectHeader->StackBackTrace[capturedFrames], + 0, + sizeof(objectHeader->StackBackTrace) - capturedFrames * sizeof(PVOID) + ); + } + + PhAcquireQueuedLockExclusive(&PhDbgObjectListLock); + InsertTailList(&PhDbgObjectListHead, &objectHeader->ObjectListEntry); + PhReleaseQueuedLockExclusive(&PhDbgObjectListLock); + + { + PPH_CREATE_OBJECT_HOOK dbgCreateObjectHook; + + dbgCreateObjectHook = PhDbgCreateObjectHook; + + if (dbgCreateObjectHook) + { + dbgCreateObjectHook( + PhObjectHeaderToObject(objectHeader), + ObjectSize, + 0, + ObjectType + ); + } + } +#endif + + return PhObjectHeaderToObject(objectHeader); +} + +/** + * References the specified object. + * + * \param Object A pointer to the object to reference. + * + * \return The object. + */ +PVOID PhReferenceObject( + _In_ PVOID Object + ) +{ + PPH_OBJECT_HEADER objectHeader; + + objectHeader = PhObjectToObjectHeader(Object); + // Increment the reference count. + _InterlockedIncrement(&objectHeader->RefCount); + + return Object; +} + +/** + * References the specified object. + * + * \param Object A pointer to the object to reference. + * \param RefCount The number of references to add. + * + * \return The object. + */ +_May_raise_ PVOID PhReferenceObjectEx( + _In_ PVOID Object, + _In_ LONG RefCount + ) +{ + PPH_OBJECT_HEADER objectHeader; + LONG oldRefCount; + + assert(!(RefCount < 0)); + + objectHeader = PhObjectToObjectHeader(Object); + // Increase the reference count. + oldRefCount = _InterlockedExchangeAdd(&objectHeader->RefCount, RefCount); + + return Object; +} + +/** + * Attempts to reference an object and fails if it is being destroyed. + * + * \param Object The object to reference if it is not being deleted. + * + * \return The object itself if the object was referenced, NULL if it was being deleted and was not + * referenced. + * + * \remarks This function is useful if a reference to an object is held, protected by a mutex, and + * the delete procedure of the object's type attempts to acquire the mutex. If this function is + * called while the mutex is owned, you can avoid referencing an object that is being destroyed. + */ +PVOID PhReferenceObjectSafe( + _In_ PVOID Object + ) +{ + PPH_OBJECT_HEADER objectHeader; + + objectHeader = PhObjectToObjectHeader(Object); + + // Increase the reference count only if it positive already (atomically). + if (PhpInterlockedIncrementSafe(&objectHeader->RefCount)) + return Object; + else + return NULL; +} + +/** + * Dereferences the specified object. + * The object will be freed if its reference count reaches 0. + * + * \param Object A pointer to the object to dereference. + */ +VOID PhDereferenceObject( + _In_ PVOID Object + ) +{ + PPH_OBJECT_HEADER objectHeader; + LONG newRefCount; + + objectHeader = PhObjectToObjectHeader(Object); + // Decrement the reference count. + newRefCount = _InterlockedDecrement(&objectHeader->RefCount); + ASSUME_ASSERT(newRefCount >= 0); + + // Free the object if it has 0 references. + if (newRefCount == 0) + { + PhpFreeObject(objectHeader); + } +} + +/** + * Dereferences the specified object. + * The object will be freed in a worker thread if its reference count reaches 0. + * + * \param Object A pointer to the object to dereference. + */ +VOID PhDereferenceObjectDeferDelete( + _In_ PVOID Object + ) +{ + PhDereferenceObjectEx(Object, 1, TRUE); +} + +/** + * Dereferences the specified object. + * The object will be freed if its reference count reaches 0. + * + * \param Object A pointer to the object to dereference. + * \param RefCount The number of references to remove. + * \param DeferDelete Whether to defer deletion of the object. + */ +_May_raise_ VOID PhDereferenceObjectEx( + _In_ PVOID Object, + _In_ LONG RefCount, + _In_ BOOLEAN DeferDelete + ) +{ + PPH_OBJECT_HEADER objectHeader; + LONG oldRefCount; + LONG newRefCount; + + assert(!(RefCount < 0)); + + objectHeader = PhObjectToObjectHeader(Object); + + // Decrease the reference count. + oldRefCount = _InterlockedExchangeAdd(&objectHeader->RefCount, -RefCount); + newRefCount = oldRefCount - RefCount; + + // Free the object if it has 0 references. + if (newRefCount == 0) + { + if (DeferDelete) + { + PhpDeferDeleteObject(objectHeader); + } + else + { + // Free the object. + PhpFreeObject(objectHeader); + } + } + else if (newRefCount < 0) + { + PhRaiseStatus(STATUS_INVALID_PARAMETER); + } +} + +/** + * Gets an object's type. + * + * \param Object A pointer to an object. + * + * \return A pointer to a type object. + */ +PPH_OBJECT_TYPE PhGetObjectType( + _In_ PVOID Object + ) +{ + return PhObjectTypeTable[PhObjectToObjectHeader(Object)->TypeIndex]; +} + +/** + * Creates an object type. + * + * \param Name The name of the type. + * \param Flags A combination of flags affecting the behaviour of the object type. + * \param DeleteProcedure A callback function that is executed when an object of this type is about + * to be freed (i.e. when its reference count is 0). + * + * \return A pointer to the newly created object type. + * + * \remarks Do not reference or dereference the object type once it is created. + */ +PPH_OBJECT_TYPE PhCreateObjectType( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ PPH_TYPE_DELETE_PROCEDURE DeleteProcedure + ) +{ + return PhCreateObjectTypeEx( + Name, + Flags, + DeleteProcedure, + NULL + ); +} + +/** + * Creates an object type. + * + * \param Name The name of the type. + * \param Flags A combination of flags affecting the behaviour of the object type. + * \param DeleteProcedure A callback function that is executed when an object of this type is about + * to be freed (i.e. when its reference count is 0). + * \param Parameters A structure containing additional parameters for the object type. + * + * \return A pointer to the newly created object type. + * + * \remarks Do not reference or dereference the object type once it is created. + */ +PPH_OBJECT_TYPE PhCreateObjectTypeEx( + _In_ PWSTR Name, + _In_ ULONG Flags, + _In_opt_ PPH_TYPE_DELETE_PROCEDURE DeleteProcedure, + _In_opt_ PPH_OBJECT_TYPE_PARAMETERS Parameters + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PPH_OBJECT_TYPE objectType; + + // Check the flags. + if ((Flags & PH_OBJECT_TYPE_VALID_FLAGS) != Flags) /* Valid flag mask */ + PhRaiseStatus(STATUS_INVALID_PARAMETER_3); + if ((Flags & PH_OBJECT_TYPE_USE_FREE_LIST) && !Parameters) + PhRaiseStatus(STATUS_INVALID_PARAMETER_MIX); + + // Create the type object. + objectType = PhCreateObject(sizeof(PH_OBJECT_TYPE), PhObjectTypeObject); + + // Initialize the type object. + objectType->Flags = (USHORT)Flags; + objectType->TypeIndex = (USHORT)_InterlockedIncrement(&PhObjectTypeCount) - 1; + objectType->NumberOfObjects = 0; + objectType->DeleteProcedure = DeleteProcedure; + objectType->Name = Name; + + if (objectType->TypeIndex < PH_OBJECT_TYPE_TABLE_SIZE) + PhObjectTypeTable[objectType->TypeIndex] = objectType; + else + PhRaiseStatus(STATUS_UNSUCCESSFUL); + + if (Parameters) + { + if (Flags & PH_OBJECT_TYPE_USE_FREE_LIST) + { + PhInitializeFreeList( + &objectType->FreeList, + PhAddObjectHeaderSize(Parameters->FreeListSize), + Parameters->FreeListCount + ); + } + } + + return objectType; +} + +/** + * Gets information about an object type. + * + * \param ObjectType A pointer to an object type. + * \param Information A variable which receives information about the object type. + */ +VOID PhGetObjectTypeInformation( + _In_ PPH_OBJECT_TYPE ObjectType, + _Out_ PPH_OBJECT_TYPE_INFORMATION Information + ) +{ + Information->Name = ObjectType->Name; + Information->NumberOfObjects = ObjectType->NumberOfObjects; + Information->Flags = ObjectType->Flags; + Information->TypeIndex = ObjectType->TypeIndex; +} + +/** + * Allocates storage for an object. + * + * \param ObjectType The type of the object. + * \param ObjectSize The size of the object, excluding the header. + */ +PPH_OBJECT_HEADER PhpAllocateObject( + _In_ PPH_OBJECT_TYPE ObjectType, + _In_ SIZE_T ObjectSize + ) +{ + PPH_OBJECT_HEADER objectHeader; + + if (ObjectType->Flags & PH_OBJECT_TYPE_USE_FREE_LIST) + { + assert(ObjectType->FreeList.Size == PhAddObjectHeaderSize(ObjectSize)); + + objectHeader = PhAllocateFromFreeList(&ObjectType->FreeList); + objectHeader->Flags = PH_OBJECT_FROM_TYPE_FREE_LIST; + REF_STAT_UP(RefObjectsAllocatedFromTypeFreeList); + } + else if (ObjectSize <= PH_OBJECT_SMALL_OBJECT_SIZE) + { + objectHeader = PhAllocateFromFreeList(&PhObjectSmallFreeList); + objectHeader->Flags = PH_OBJECT_FROM_SMALL_FREE_LIST; + REF_STAT_UP(RefObjectsAllocatedFromSmallFreeList); + } + else + { + objectHeader = PhAllocate(PhAddObjectHeaderSize(ObjectSize)); + objectHeader->Flags = 0; + REF_STAT_UP(RefObjectsAllocated); + } + + return objectHeader; +} + +/** + * Calls the delete procedure for an object and frees its allocated storage. + * + * \param ObjectHeader A pointer to the object header of an allocated object. + */ +VOID PhpFreeObject( + _In_ PPH_OBJECT_HEADER ObjectHeader + ) +{ + PPH_OBJECT_TYPE objectType; + + objectType = PhObjectTypeTable[ObjectHeader->TypeIndex]; + + // Object type statistics. + _InterlockedDecrement(&objectType->NumberOfObjects); + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgObjectListLock); + RemoveEntryList(&ObjectHeader->ObjectListEntry); + PhReleaseQueuedLockExclusive(&PhDbgObjectListLock); +#endif + + REF_STAT_UP(RefObjectsDestroyed); + + // Call the delete procedure if we have one. + if (objectType->DeleteProcedure) + { + objectType->DeleteProcedure(PhObjectHeaderToObject(ObjectHeader), 0); + } + + if (ObjectHeader->Flags & PH_OBJECT_FROM_TYPE_FREE_LIST) + { + PhFreeToFreeList(&objectType->FreeList, ObjectHeader); + REF_STAT_UP(RefObjectsFreedToTypeFreeList); + } + else if (ObjectHeader->Flags & PH_OBJECT_FROM_SMALL_FREE_LIST) + { + PhFreeToFreeList(&PhObjectSmallFreeList, ObjectHeader); + REF_STAT_UP(RefObjectsFreedToSmallFreeList); + } + else + { + PhFree(ObjectHeader); + REF_STAT_UP(RefObjectsFreed); + } +} + +/** + * Queues an object for deletion. + * + * \param ObjectHeader A pointer to the object header of the object to delete. + */ +VOID PhpDeferDeleteObject( + _In_ PPH_OBJECT_HEADER ObjectHeader + ) +{ + PSLIST_ENTRY oldFirstEntry; + + // Save TypeIndex and Flags since they get overwritten when we push the object onto the defer + // delete list. + ObjectHeader->DeferDelete = 1; + MemoryBarrier(); + ObjectHeader->SavedTypeIndex = ObjectHeader->TypeIndex; + ObjectHeader->SavedFlags = ObjectHeader->Flags; + + oldFirstEntry = RtlFirstEntrySList(&PhObjectDeferDeleteListHead); + RtlInterlockedPushEntrySList(&PhObjectDeferDeleteListHead, &ObjectHeader->DeferDeleteListEntry); + REF_STAT_UP(RefObjectsDeleteDeferred); + + // Was the to-free list empty before? If so, we need to queue a work item. + if (!oldFirstEntry) + { + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhpDeferDeleteObjectRoutine, NULL); + } +} + +/** + * Removes and frees objects from the to-free list. + */ +NTSTATUS PhpDeferDeleteObjectRoutine( + _In_ PVOID Parameter + ) +{ + PSLIST_ENTRY listEntry; + PPH_OBJECT_HEADER objectHeader; + + // Clear the list and obtain the first object to free. + listEntry = RtlInterlockedFlushSList(&PhObjectDeferDeleteListHead); + + while (listEntry) + { + objectHeader = CONTAINING_RECORD(listEntry, PH_OBJECT_HEADER, DeferDeleteListEntry); + listEntry = listEntry->Next; + + // Restore TypeIndex and Flags. + objectHeader->TypeIndex = (USHORT)objectHeader->SavedTypeIndex; + objectHeader->Flags = (UCHAR)objectHeader->SavedFlags; + + PhpFreeObject(objectHeader); + } + + return STATUS_SUCCESS; +} + +/** + * Creates a reference-counted memory block. + * + * \param Size The number of bytes to allocate. + * + * \return A pointer to the memory block. + */ +PVOID PhCreateAlloc( + _In_ SIZE_T Size + ) +{ + return PhCreateObject(Size, PhAllocType); +} + +/** + * Gets the current auto-dereference pool for the current thread. + */ +FORCEINLINE PPH_AUTO_POOL PhpGetCurrentAutoPool( + VOID + ) +{ + return (PPH_AUTO_POOL)TlsGetValue(PhpAutoPoolTlsIndex); +} + +/** + * Sets the current auto-dereference pool for the current thread. + */ +_May_raise_ FORCEINLINE VOID PhpSetCurrentAutoPool( + _In_ PPH_AUTO_POOL AutoPool + ) +{ + if (!TlsSetValue(PhpAutoPoolTlsIndex, AutoPool)) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + +#ifdef DEBUG + { + PPHP_BASE_THREAD_DBG dbg; + + dbg = (PPHP_BASE_THREAD_DBG)TlsGetValue(PhDbgThreadDbgTlsIndex); + + if (dbg) + { + dbg->CurrentAutoPool = AutoPool; + } + } +#endif +} + +/** + * Initializes an auto-dereference pool and sets it as the current pool for the current thread. You + * must call PhDeleteAutoPool() before storage for the auto-dereference pool is freed. + * + * \remarks Always store auto-dereference pools in local variables, and do not share the pool with + * any other functions. + */ +VOID PhInitializeAutoPool( + _Out_ PPH_AUTO_POOL AutoPool + ) +{ + AutoPool->StaticCount = 0; + AutoPool->DynamicCount = 0; + AutoPool->DynamicAllocated = 0; + AutoPool->DynamicObjects = NULL; + + // Add the pool to the stack. + AutoPool->NextPool = PhpGetCurrentAutoPool(); + PhpSetCurrentAutoPool(AutoPool); + + REF_STAT_UP(RefAutoPoolsCreated); +} + +/** + * Deletes an auto-dereference pool. The function will dereference any objects currently in the + * pool. If a pool other than the current pool is passed to the function, an exception is raised. + * + * \param AutoPool The auto-dereference pool to delete. + */ +_May_raise_ VOID PhDeleteAutoPool( + _Inout_ PPH_AUTO_POOL AutoPool + ) +{ + PhDrainAutoPool(AutoPool); + + if (PhpGetCurrentAutoPool() != AutoPool) + PhRaiseStatus(STATUS_UNSUCCESSFUL); + + // Remove the pool from the stack. + PhpSetCurrentAutoPool(AutoPool->NextPool); + + // Free the dynamic array if it hasn't been freed yet. + if (AutoPool->DynamicObjects) + PhFree(AutoPool->DynamicObjects); + + REF_STAT_UP(RefAutoPoolsDestroyed); +} + +/** + * Dereferences and removes all objects in an auto-release pool. + * + * \param AutoPool The auto-release pool to drain. + */ +VOID PhDrainAutoPool( + _In_ PPH_AUTO_POOL AutoPool + ) +{ + ULONG i; + + for (i = 0; i < AutoPool->StaticCount; i++) + PhDereferenceObject(AutoPool->StaticObjects[i]); + + AutoPool->StaticCount = 0; + + if (AutoPool->DynamicObjects) + { + for (i = 0; i < AutoPool->DynamicCount; i++) + { + PhDereferenceObject(AutoPool->DynamicObjects[i]); + } + + AutoPool->DynamicCount = 0; + + if (AutoPool->DynamicAllocated > PH_AUTO_POOL_DYNAMIC_BIG_SIZE) + { + AutoPool->DynamicAllocated = 0; + PhFree(AutoPool->DynamicObjects); + AutoPool->DynamicObjects = NULL; + } + } +} + +/** + * Adds an object to the current auto-dereference pool for the current thread. If the current thread + * does not have an auto-dereference pool, the function raises an exception. + * + * \param Object A pointer to an object. The object will be dereferenced when the current + * auto-dereference pool is drained or freed. + */ +_May_raise_ PVOID PhAutoDereferenceObject( + _In_opt_ PVOID Object + ) +{ + PPH_AUTO_POOL autoPool = PhpGetCurrentAutoPool(); + +#ifdef DEBUG + // If we don't have an auto-dereference pool, we don't want to leak the object (unlike what + // Apple does with NSAutoreleasePool). + if (!autoPool) + PhRaiseStatus(STATUS_UNSUCCESSFUL); +#endif + + if (!Object) + return NULL; + + // See if we can use the static array. + if (autoPool->StaticCount < PH_AUTO_POOL_STATIC_SIZE) + { + autoPool->StaticObjects[autoPool->StaticCount++] = Object; + return Object; + } + + // Use the dynamic array. + + // Allocate the array if we haven't already. + if (!autoPool->DynamicObjects) + { + autoPool->DynamicAllocated = 64; + autoPool->DynamicObjects = PhAllocate( + sizeof(PVOID) * autoPool->DynamicAllocated + ); + REF_STAT_UP(RefAutoPoolsDynamicAllocated); + } + + // See if we need to resize the array. + if (autoPool->DynamicCount == autoPool->DynamicAllocated) + { + autoPool->DynamicAllocated *= 2; + autoPool->DynamicObjects = PhReAllocate( + autoPool->DynamicObjects, + sizeof(PVOID) * autoPool->DynamicAllocated + ); + REF_STAT_UP(RefAutoPoolsDynamicResized); + } + + autoPool->DynamicObjects[autoPool->DynamicCount++] = Object; + + return Object; +} diff --git a/phlib/secdata.c b/phlib/secdata.c new file mode 100644 index 0000000..9f765ac --- /dev/null +++ b/phlib/secdata.c @@ -0,0 +1,786 @@ +/* + * Process Hacker - + * object security data + * + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#define ACCESS_ENTRIES(Type) static PH_ACCESS_ENTRY Ph##Type##AccessEntries[] = +#define ACCESS_ENTRY(Type, HasSynchronize) \ + { L#Type, Ph##Type##AccessEntries, sizeof(Ph##Type##AccessEntries), HasSynchronize } + +typedef struct _PH_SPECIFIC_TYPE +{ + PWSTR Type; + PPH_ACCESS_ENTRY AccessEntries; + ULONG SizeOfAccessEntries; + BOOLEAN HasSynchronize; +} PH_SPECIFIC_TYPE, *PPH_SPECIFIC_TYPE; + +ACCESS_ENTRIES(Standard) +{ + { L"Synchronize", SYNCHRONIZE, FALSE, TRUE }, + { L"Delete", DELETE, FALSE, TRUE }, + { L"Read permissions", READ_CONTROL, FALSE, TRUE, L"Read control" }, + { L"Change permissions", WRITE_DAC, FALSE, TRUE, L"Write DAC" }, + { L"Take ownership", WRITE_OWNER, FALSE, TRUE, L"Write owner" } +}; + +ACCESS_ENTRIES(AlpcPort) +{ + { L"Full control", PORT_ALL_ACCESS, TRUE, TRUE }, + { L"Connect", PORT_CONNECT, TRUE, TRUE } +}; + +ACCESS_ENTRIES(DebugObject) +{ + { L"Full control", DEBUG_ALL_ACCESS, TRUE, TRUE }, + { L"Read events", DEBUG_READ_EVENT, TRUE, TRUE }, + { L"Assign processes", DEBUG_PROCESS_ASSIGN, TRUE, TRUE }, + { L"Query information", DEBUG_QUERY_INFORMATION, TRUE, TRUE }, + { L"Set information", DEBUG_SET_INFORMATION, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Desktop) +{ + { L"Full control", DESKTOP_ALL_ACCESS, TRUE, TRUE }, + { L"Read", DESKTOP_GENERIC_READ, TRUE, FALSE }, + { L"Write", DESKTOP_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", DESKTOP_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Enumerate", DESKTOP_ENUMERATE, FALSE, TRUE }, + { L"Read objects", DESKTOP_READOBJECTS, FALSE, TRUE }, + { L"Playback journals", DESKTOP_JOURNALPLAYBACK, FALSE, TRUE }, + { L"Write objects", DESKTOP_WRITEOBJECTS, FALSE, TRUE }, + { L"Create windows", DESKTOP_CREATEWINDOW, FALSE, TRUE }, + { L"Create menus", DESKTOP_CREATEMENU, FALSE, TRUE }, + { L"Create window hooks", DESKTOP_HOOKCONTROL, FALSE, TRUE }, + { L"Record journals", DESKTOP_JOURNALRECORD, FALSE, TRUE }, + { L"Switch desktop", DESKTOP_SWITCHDESKTOP, FALSE, TRUE } +}; + +ACCESS_ENTRIES(Directory) +{ + { L"Full control", DIRECTORY_ALL_ACCESS, TRUE, TRUE }, + { L"Query", DIRECTORY_QUERY, TRUE, TRUE}, + { L"Traverse", DIRECTORY_TRAVERSE, TRUE, TRUE}, + { L"Create objects", DIRECTORY_CREATE_OBJECT, TRUE, TRUE}, + { L"Create subdirectories", DIRECTORY_CREATE_SUBDIRECTORY, TRUE, TRUE} +}; + +ACCESS_ENTRIES(Event) +{ + { L"Full control", EVENT_ALL_ACCESS, TRUE, TRUE }, + { L"Query", EVENT_QUERY_STATE, TRUE, TRUE }, + { L"Modify", EVENT_MODIFY_STATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(EventPair) +{ + { L"Full control", EVENT_PAIR_ALL_ACCESS, TRUE, TRUE } +}; + +ACCESS_ENTRIES(File) +{ + { L"Full control", FILE_ALL_ACCESS, TRUE, TRUE }, + { L"Read & execute", FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Read", FILE_GENERIC_READ, TRUE, FALSE }, + { L"Write", FILE_GENERIC_WRITE, TRUE, FALSE }, + { L"Traverse folder / execute file", FILE_EXECUTE, FALSE, TRUE, L"Execute" }, + { L"List folder / read data", FILE_READ_DATA, FALSE, TRUE, L"Read data" }, + { L"Read attributes", FILE_READ_ATTRIBUTES, FALSE, TRUE }, + { L"Read extended attributes", FILE_READ_EA, FALSE, TRUE, L"Read EA" }, + { L"Create files / write data", FILE_WRITE_DATA, FALSE, TRUE, L"Write data" }, + { L"Create folders / append data", FILE_APPEND_DATA, FALSE, TRUE, L"Append data" }, + { L"Write attributes", FILE_WRITE_ATTRIBUTES, FALSE, TRUE }, + { L"Write extended attributes", FILE_WRITE_EA, FALSE, TRUE, L"Write EA" }, + { L"Delete subfolders and files", FILE_DELETE_CHILD, FALSE, TRUE, L"Delete child" } +}; + +ACCESS_ENTRIES(FilterConnectionPort) +{ + { L"Full control", FLT_PORT_ALL_ACCESS, TRUE, TRUE }, + { L"Connect", FLT_PORT_CONNECT, TRUE, TRUE } +}; + +ACCESS_ENTRIES(IoCompletion) +{ + { L"Full control", IO_COMPLETION_ALL_ACCESS, TRUE, TRUE }, + { L"Query", IO_COMPLETION_QUERY_STATE, TRUE, TRUE }, + { L"Modify", IO_COMPLETION_MODIFY_STATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Job) +{ + { L"Full control", JOB_OBJECT_ALL_ACCESS, TRUE, TRUE }, + { L"Query", JOB_OBJECT_QUERY, TRUE, TRUE }, + { L"Assign processes", JOB_OBJECT_ASSIGN_PROCESS, TRUE, TRUE }, + { L"Set attributes", JOB_OBJECT_SET_ATTRIBUTES, TRUE, TRUE }, + { L"Set security attributes", JOB_OBJECT_SET_SECURITY_ATTRIBUTES, TRUE, TRUE }, + { L"Terminate", JOB_OBJECT_TERMINATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Key) +{ + { L"Full control", KEY_ALL_ACCESS, TRUE, TRUE }, + { L"Read", KEY_READ, TRUE, FALSE }, + { L"Write", KEY_WRITE, TRUE, FALSE }, + { L"Execute", KEY_EXECUTE, TRUE, FALSE }, + { L"Enumerate subkeys", KEY_ENUMERATE_SUB_KEYS, FALSE, TRUE }, + { L"Query values", KEY_QUERY_VALUE, FALSE, TRUE }, + { L"Notify", KEY_NOTIFY, FALSE, TRUE }, + { L"Set values", KEY_SET_VALUE, FALSE, TRUE }, + { L"Create subkeys", KEY_CREATE_SUB_KEY, FALSE, TRUE }, + { L"Create links", KEY_CREATE_LINK, FALSE, TRUE } +}; + +ACCESS_ENTRIES(KeyedEvent) +{ + { L"Full control", KEYEDEVENT_ALL_ACCESS, TRUE, TRUE }, + { L"Wait", KEYEDEVENT_WAIT, TRUE, TRUE }, + { L"Wake", KEYEDEVENT_WAKE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(LsaAccount) +{ + { L"Full control", ACCOUNT_ALL_ACCESS, TRUE, TRUE }, + { L"Read", ACCOUNT_READ, TRUE, FALSE }, + { L"Write", ACCOUNT_WRITE, TRUE, FALSE }, + { L"Execute", ACCOUNT_EXECUTE, TRUE, FALSE }, + { L"View", ACCOUNT_VIEW, FALSE, TRUE }, + { L"Adjust privileges", ACCOUNT_ADJUST_PRIVILEGES, FALSE, TRUE }, + { L"Adjust quotas", ACCOUNT_ADJUST_QUOTAS, FALSE, TRUE }, + { L"Adjust system access", ACCOUNT_ADJUST_SYSTEM_ACCESS, FALSE, TRUE } +}; + +ACCESS_ENTRIES(LsaPolicy) +{ + { L"Full control", POLICY_ALL_ACCESS | POLICY_NOTIFICATION, TRUE, TRUE }, + { L"Read", POLICY_READ, TRUE, FALSE }, + { L"Write", POLICY_WRITE, TRUE, FALSE }, + { L"Execute", POLICY_EXECUTE | POLICY_NOTIFICATION, TRUE, FALSE }, + { L"View local information", POLICY_VIEW_LOCAL_INFORMATION, FALSE, TRUE }, + { L"View audit information", POLICY_VIEW_AUDIT_INFORMATION, FALSE, TRUE }, + { L"Get private information", POLICY_GET_PRIVATE_INFORMATION, FALSE, TRUE }, + { L"Administer trust", POLICY_TRUST_ADMIN, FALSE, TRUE }, + { L"Create account", POLICY_CREATE_ACCOUNT, FALSE, TRUE }, + { L"Create secret", POLICY_CREATE_SECRET, FALSE, TRUE }, + { L"Create privilege", POLICY_CREATE_PRIVILEGE, FALSE, TRUE }, + { L"Set default quota limits", POLICY_SET_DEFAULT_QUOTA_LIMITS, FALSE, TRUE }, + { L"Set audit requirements", POLICY_SET_AUDIT_REQUIREMENTS, FALSE, TRUE }, + { L"Administer audit log", POLICY_AUDIT_LOG_ADMIN, FALSE, TRUE }, + { L"Administer server", POLICY_SERVER_ADMIN, FALSE, TRUE }, + { L"Lookup names", POLICY_LOOKUP_NAMES, FALSE, TRUE }, + { L"Get notifications", POLICY_NOTIFICATION, FALSE, TRUE } +}; + +ACCESS_ENTRIES(LsaSecret) +{ + { L"Full control", SECRET_ALL_ACCESS, TRUE, TRUE }, + { L"Read", SECRET_READ, TRUE, FALSE }, + { L"Write", SECRET_WRITE, TRUE, FALSE }, + { L"Execute", SECRET_EXECUTE, TRUE, FALSE }, + { L"Set value", SECRET_SET_VALUE, FALSE, TRUE }, + { L"Query value", SECRET_QUERY_VALUE, FALSE, TRUE } +}; + +ACCESS_ENTRIES(LsaTrusted) +{ + { L"Full control", TRUSTED_ALL_ACCESS, TRUE, TRUE }, + { L"Read", TRUSTED_READ, TRUE, FALSE }, + { L"Write", TRUSTED_WRITE, TRUE, FALSE }, + { L"Execute", TRUSTED_EXECUTE, TRUE, FALSE }, + { L"Query domain name", TRUSTED_QUERY_DOMAIN_NAME, FALSE, TRUE }, + { L"Query controllers", TRUSTED_QUERY_CONTROLLERS, FALSE, TRUE }, + { L"Set controllers", TRUSTED_SET_CONTROLLERS, FALSE, TRUE }, + { L"Query POSIX", TRUSTED_QUERY_POSIX, FALSE, TRUE }, + { L"Set POSIX", TRUSTED_SET_POSIX, FALSE, TRUE }, + { L"Query authentication", TRUSTED_QUERY_AUTH, FALSE, TRUE }, + { L"Set authentication", TRUSTED_SET_AUTH, FALSE, TRUE } +}; + +ACCESS_ENTRIES(Mutant) +{ + { L"Full control", MUTANT_ALL_ACCESS, TRUE, TRUE }, + { L"Query", MUTANT_QUERY_STATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Partition) +{ + { L"Full control", MEMORY_PARTITION_ALL_ACCESS, TRUE, TRUE }, + { L"Query", MEMORY_PARTITION_QUERY_ACCESS, TRUE, TRUE }, + { L"Modify", MEMORY_PARTITION_MODIFY_ACCESS, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Process) +{ + { L"Full control", STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff, TRUE, TRUE }, + { L"Query information", PROCESS_QUERY_INFORMATION, TRUE, TRUE }, + { L"Set information", PROCESS_SET_INFORMATION, TRUE, TRUE }, + { L"Set quotas", PROCESS_SET_QUOTA, TRUE, TRUE }, + { L"Set session ID", PROCESS_SET_SESSIONID, TRUE, TRUE }, + { L"Create threads", PROCESS_CREATE_THREAD, TRUE, TRUE }, + { L"Create processes", PROCESS_CREATE_PROCESS, TRUE, TRUE }, + { L"Modify memory", PROCESS_VM_OPERATION, TRUE, TRUE, L"VM operation" }, + { L"Read memory", PROCESS_VM_READ, TRUE, TRUE, L"VM read" }, + { L"Write memory", PROCESS_VM_WRITE, TRUE, TRUE, L"VM write" }, + { L"Duplicate handles", PROCESS_DUP_HANDLE, TRUE, TRUE }, + { L"Suspend / resume / set port", PROCESS_SUSPEND_RESUME, TRUE, TRUE, L"Suspend/resume" }, + { L"Terminate", PROCESS_TERMINATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Process60) +{ + { L"Full control", STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xffff, TRUE, TRUE }, // PROCESS_ALL_ACCESS + { L"Query limited information", PROCESS_QUERY_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Query information", PROCESS_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Set information", PROCESS_SET_INFORMATION, TRUE, TRUE }, + { L"Set quotas", PROCESS_SET_QUOTA, TRUE, TRUE }, + { L"Set session ID", PROCESS_SET_SESSIONID, TRUE, TRUE }, + { L"Create threads", PROCESS_CREATE_THREAD, TRUE, TRUE }, + { L"Create processes", PROCESS_CREATE_PROCESS, TRUE, TRUE }, + { L"Modify memory", PROCESS_VM_OPERATION, TRUE, TRUE, L"VM operation" }, + { L"Read memory", PROCESS_VM_READ, TRUE, TRUE, L"VM read" }, + { L"Write memory", PROCESS_VM_WRITE, TRUE, TRUE, L"VM write" }, + { L"Duplicate handles", PROCESS_DUP_HANDLE, TRUE, TRUE }, + { L"Suspend / resume / set port", PROCESS_SUSPEND_RESUME, TRUE, TRUE, L"Suspend/resume" }, + { L"Terminate", PROCESS_TERMINATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Profile) +{ + { L"Full control", PROFILE_ALL_ACCESS, TRUE, TRUE }, + { L"Control", PROFILE_CONTROL, TRUE, TRUE } +}; + +ACCESS_ENTRIES(SamAlias) +{ + { L"Full control", ALIAS_ALL_ACCESS, TRUE, TRUE }, + { L"Read", ALIAS_READ, TRUE, FALSE }, + { L"Write", ALIAS_WRITE, TRUE, FALSE }, + { L"Execute", ALIAS_EXECUTE, TRUE, FALSE }, + { L"Read information", ALIAS_READ_INFORMATION, FALSE, TRUE }, + { L"Write account", ALIAS_WRITE_ACCOUNT, FALSE, TRUE }, + { L"Add member", ALIAS_ADD_MEMBER, FALSE, TRUE }, + { L"Remove member", ALIAS_REMOVE_MEMBER, FALSE, TRUE }, + { L"List members", ALIAS_LIST_MEMBERS, FALSE, TRUE } +}; + +ACCESS_ENTRIES(SamDomain) +{ + { L"Full control", DOMAIN_ALL_ACCESS, TRUE, TRUE }, + { L"Read", DOMAIN_READ, TRUE, FALSE }, + { L"Write", DOMAIN_WRITE, TRUE, FALSE }, + { L"Execute", DOMAIN_EXECUTE, TRUE, FALSE }, + { L"Read password parameters", DOMAIN_READ_PASSWORD_PARAMETERS, FALSE, TRUE }, + { L"Write password parameters", DOMAIN_WRITE_PASSWORD_PARAMS, FALSE, TRUE }, + { L"Read other parameters", DOMAIN_READ_OTHER_PARAMETERS, FALSE, TRUE }, + { L"Write other parameters", DOMAIN_WRITE_OTHER_PARAMETERS, FALSE, TRUE }, + { L"Create user", DOMAIN_CREATE_USER, FALSE, TRUE }, + { L"Create group", DOMAIN_CREATE_GROUP, FALSE, TRUE }, + { L"Create alias", DOMAIN_CREATE_ALIAS, FALSE, TRUE }, + { L"Get alias membership", DOMAIN_GET_ALIAS_MEMBERSHIP, FALSE, TRUE }, + { L"List accounts", DOMAIN_LIST_ACCOUNTS, FALSE, TRUE }, + { L"Lookup", DOMAIN_LOOKUP, FALSE, TRUE }, + { L"Administer server", DOMAIN_ADMINISTER_SERVER, FALSE, TRUE } +}; + +ACCESS_ENTRIES(SamGroup) +{ + { L"Full control", GROUP_ALL_ACCESS, TRUE, TRUE }, + { L"Read", GROUP_READ, TRUE, FALSE }, + { L"Write", GROUP_WRITE, TRUE, FALSE }, + { L"Execute", GROUP_EXECUTE, TRUE, FALSE }, + { L"Read information", GROUP_READ_INFORMATION, FALSE, TRUE }, + { L"Write account", GROUP_WRITE_ACCOUNT, FALSE, TRUE }, + { L"Add member", GROUP_ADD_MEMBER, FALSE, TRUE }, + { L"Remove member", GROUP_REMOVE_MEMBER, FALSE, TRUE }, + { L"List members", GROUP_LIST_MEMBERS, FALSE, TRUE } +}; + +ACCESS_ENTRIES(SamServer) +{ + { L"Full control", SAM_SERVER_ALL_ACCESS, TRUE, TRUE }, + { L"Read", SAM_SERVER_READ, TRUE, FALSE }, + { L"Write", SAM_SERVER_WRITE, TRUE, FALSE }, + { L"Execute", SAM_SERVER_EXECUTE, TRUE, FALSE }, + { L"Connect", SAM_SERVER_CONNECT, FALSE, TRUE }, + { L"Shutdown", SAM_SERVER_SHUTDOWN, FALSE, TRUE }, + { L"Initialize", SAM_SERVER_INITIALIZE, FALSE, TRUE }, + { L"Create domain", SAM_SERVER_CREATE_DOMAIN, FALSE, TRUE }, + { L"Enumerate domains", SAM_SERVER_ENUMERATE_DOMAINS, FALSE, TRUE }, + { L"Lookup domain", SAM_SERVER_LOOKUP_DOMAIN, FALSE, TRUE } +}; + +ACCESS_ENTRIES(SamUser) +{ + { L"Full control", USER_ALL_ACCESS, TRUE, TRUE }, + { L"Read", USER_READ, TRUE, FALSE }, + { L"Write", USER_WRITE, TRUE, FALSE }, + { L"Execute", USER_EXECUTE, TRUE, FALSE }, + { L"Read general", USER_READ_GENERAL, FALSE, TRUE }, + { L"Read preferences", USER_READ_PREFERENCES, FALSE, TRUE }, + { L"Write preferences", USER_WRITE_PREFERENCES, FALSE, TRUE }, + { L"Read logon", USER_READ_LOGON, FALSE, TRUE }, + { L"Read account", USER_READ_ACCOUNT, FALSE, TRUE }, + { L"Write account", USER_WRITE_ACCOUNT, FALSE, TRUE }, + { L"Change password", USER_CHANGE_PASSWORD, FALSE, TRUE }, + { L"Force password change", USER_FORCE_PASSWORD_CHANGE, FALSE, TRUE }, + { L"List groups", USER_LIST_GROUPS, FALSE, TRUE }, + { L"Read group information", USER_READ_GROUP_INFORMATION, FALSE, TRUE }, + { L"Write group information", USER_WRITE_GROUP_INFORMATION, FALSE, TRUE } +}; + +ACCESS_ENTRIES(Section) +{ + { L"Full control", SECTION_ALL_ACCESS, TRUE, TRUE }, + { L"Query", SECTION_QUERY, TRUE, TRUE }, + { L"Map for read", SECTION_MAP_READ, TRUE, TRUE, L"Map read" }, + { L"Map for write", SECTION_MAP_WRITE, TRUE, TRUE, L"Map write" }, + { L"Map for execute", SECTION_MAP_EXECUTE, TRUE, TRUE, L"Map execute" }, + { L"Map for execute (explicit)", SECTION_MAP_EXECUTE_EXPLICIT, TRUE, TRUE, L"Map execute explicit" }, + { L"Extend size", SECTION_EXTEND_SIZE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Semaphore) +{ + { L"Full control", SEMAPHORE_ALL_ACCESS, TRUE, TRUE }, + { L"Query", SEMAPHORE_QUERY_STATE, TRUE, TRUE }, + { L"Modify", SEMAPHORE_MODIFY_STATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Service) +{ + { L"Full control", SERVICE_ALL_ACCESS, TRUE, TRUE }, + { L"Query status", SERVICE_QUERY_STATUS, TRUE, TRUE }, + { L"Query configuration", SERVICE_QUERY_CONFIG, TRUE, TRUE }, + { L"Modify configuration", SERVICE_CHANGE_CONFIG, TRUE, TRUE }, + { L"Enumerate dependents", SERVICE_ENUMERATE_DEPENDENTS, TRUE, TRUE }, + { L"Start", SERVICE_START, TRUE, TRUE }, + { L"Stop", SERVICE_STOP, TRUE, TRUE }, + { L"Pause / continue", SERVICE_PAUSE_CONTINUE, TRUE, TRUE, L"Pause/continue" }, + { L"Interrogate", SERVICE_INTERROGATE, TRUE, TRUE }, + { L"User-defined control", SERVICE_USER_DEFINED_CONTROL, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Session) +{ + { L"Full control", SESSION_ALL_ACCESS, TRUE, TRUE }, + { L"Query", SESSION_QUERY_ACCESS, TRUE, TRUE }, + { L"Modify", SESSION_MODIFY_ACCESS, TRUE, TRUE } +}; + +ACCESS_ENTRIES(SymbolicLink) +{ + { L"Full control", SYMBOLIC_LINK_ALL_ACCESS, TRUE, TRUE }, + { L"Query", SYMBOLIC_LINK_QUERY, TRUE, TRUE } +}; + +ACCESS_ENTRIES(Thread) +{ + { L"Full control", STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3ff, TRUE, TRUE }, + { L"Query information", THREAD_QUERY_INFORMATION, TRUE, TRUE }, + { L"Set information", THREAD_SET_INFORMATION, TRUE, TRUE }, + { L"Get context", THREAD_GET_CONTEXT, TRUE, TRUE }, + { L"Set context", THREAD_SET_CONTEXT, TRUE, TRUE }, + { L"Set token", THREAD_SET_THREAD_TOKEN, TRUE, TRUE }, + { L"Alert", THREAD_ALERT, TRUE, TRUE }, + { L"Impersonate", THREAD_IMPERSONATE, TRUE, TRUE }, + { L"Direct impersonate", THREAD_DIRECT_IMPERSONATION, TRUE, TRUE }, + { L"Suspend / resume", THREAD_SUSPEND_RESUME, TRUE, TRUE, L"Suspend/resume" }, + { L"Terminate", THREAD_TERMINATE, TRUE, TRUE }, +}; + +ACCESS_ENTRIES(Thread60) +{ + { L"Full control", STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xffff, TRUE, TRUE }, // THREAD_ALL_ACCESS + { L"Query limited information", THREAD_QUERY_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Query information", THREAD_QUERY_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Set limited information", THREAD_SET_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Set information", THREAD_SET_INFORMATION | THREAD_SET_LIMITED_INFORMATION, TRUE, TRUE }, + { L"Get context", THREAD_GET_CONTEXT, TRUE, TRUE }, + { L"Set context", THREAD_SET_CONTEXT, TRUE, TRUE }, + { L"Set token", THREAD_SET_THREAD_TOKEN, TRUE, TRUE }, + { L"Alert", THREAD_ALERT, TRUE, TRUE }, + { L"Impersonate", THREAD_IMPERSONATE, TRUE, TRUE }, + { L"Direct impersonate", THREAD_DIRECT_IMPERSONATION, TRUE, TRUE }, + { L"Suspend / resume", THREAD_SUSPEND_RESUME, TRUE, TRUE, L"Suspend/resume" }, + { L"Terminate", THREAD_TERMINATE, TRUE, TRUE }, +}; + +ACCESS_ENTRIES(Timer) +{ + { L"Full control", TIMER_ALL_ACCESS, TRUE, TRUE }, + { L"Query", TIMER_QUERY_STATE, TRUE, TRUE }, + { L"Modify", TIMER_MODIFY_STATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(TmEn) +{ + { L"Full control", ENLISTMENT_ALL_ACCESS, TRUE, TRUE }, + { L"Read", ENLISTMENT_GENERIC_READ, TRUE, FALSE }, + { L"Write", ENLISTMENT_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", ENLISTMENT_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Query information", ENLISTMENT_QUERY_INFORMATION, FALSE, TRUE }, + { L"Set information", ENLISTMENT_SET_INFORMATION, FALSE, TRUE }, + { L"Recover", ENLISTMENT_RECOVER, FALSE, TRUE }, + { L"Subordinate rights", ENLISTMENT_SUBORDINATE_RIGHTS, FALSE, TRUE }, + { L"Superior rights", ENLISTMENT_SUPERIOR_RIGHTS, FALSE, TRUE } +}; + +ACCESS_ENTRIES(TmRm) +{ + { L"Full control", RESOURCEMANAGER_ALL_ACCESS, TRUE, TRUE }, + { L"Read", RESOURCEMANAGER_GENERIC_READ, TRUE, FALSE }, + { L"Write", RESOURCEMANAGER_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", RESOURCEMANAGER_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Query information", RESOURCEMANAGER_QUERY_INFORMATION, FALSE, TRUE }, + { L"Set information", RESOURCEMANAGER_SET_INFORMATION, FALSE, TRUE }, + { L"Get notifications", RESOURCEMANAGER_GET_NOTIFICATION, FALSE, TRUE }, + { L"Enlist", RESOURCEMANAGER_ENLIST, FALSE, TRUE }, + { L"Recover", RESOURCEMANAGER_RECOVER, FALSE, TRUE }, + { L"Register protocols", RESOURCEMANAGER_REGISTER_PROTOCOL, FALSE, TRUE }, + { L"Complete propagation", RESOURCEMANAGER_COMPLETE_PROPAGATION, FALSE, TRUE } +}; + +ACCESS_ENTRIES(TmTm) +{ + { L"Full control", TRANSACTIONMANAGER_ALL_ACCESS, TRUE, TRUE }, + { L"Read", TRANSACTIONMANAGER_GENERIC_READ, TRUE, FALSE }, + { L"Write", TRANSACTIONMANAGER_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", TRANSACTIONMANAGER_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Query information", TRANSACTIONMANAGER_QUERY_INFORMATION, FALSE, TRUE }, + { L"Set information", TRANSACTIONMANAGER_SET_INFORMATION, FALSE, TRUE }, + { L"Recover", TRANSACTIONMANAGER_RECOVER, FALSE, TRUE }, + { L"Rename", TRANSACTIONMANAGER_RENAME, FALSE, TRUE }, + { L"Create resource manager", TRANSACTIONMANAGER_CREATE_RM, FALSE, TRUE }, + { L"Bind transactions", TRANSACTIONMANAGER_BIND_TRANSACTION, FALSE, TRUE } +}; + +ACCESS_ENTRIES(TmTx) +{ + { L"Full control", TRANSACTION_ALL_ACCESS, TRUE, TRUE }, + { L"Read", TRANSACTION_GENERIC_READ, TRUE, FALSE }, + { L"Write", TRANSACTION_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", TRANSACTION_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Query information", TRANSACTION_QUERY_INFORMATION, FALSE, TRUE }, + { L"Set information", TRANSACTION_SET_INFORMATION, FALSE, TRUE }, + { L"Enlist", TRANSACTION_ENLIST, FALSE, TRUE }, + { L"Commit", TRANSACTION_COMMIT, FALSE, TRUE }, + { L"Rollback", TRANSACTION_ROLLBACK, FALSE, TRUE }, + { L"Propagate", TRANSACTION_PROPAGATE, FALSE, TRUE } +}; + +ACCESS_ENTRIES(Token) +{ + { L"Full control", TOKEN_ALL_ACCESS, TRUE, TRUE }, + { L"Read", TOKEN_READ, TRUE, FALSE }, + { L"Write", TOKEN_WRITE, TRUE, FALSE }, + { L"Execute", TOKEN_EXECUTE, TRUE, FALSE }, + { L"Adjust privileges", TOKEN_ADJUST_PRIVILEGES, FALSE, TRUE }, + { L"Adjust groups", TOKEN_ADJUST_GROUPS, FALSE, TRUE }, + { L"Adjust defaults", TOKEN_ADJUST_DEFAULT, FALSE, TRUE }, + { L"Adjust session ID", TOKEN_ADJUST_SESSIONID, FALSE, TRUE }, + { L"Assign as primary token", TOKEN_ASSIGN_PRIMARY, FALSE, TRUE, L"Assign primary" }, + { L"Duplicate", TOKEN_DUPLICATE, FALSE, TRUE }, + { L"Impersonate", TOKEN_IMPERSONATE, FALSE, TRUE }, + { L"Query", TOKEN_QUERY, FALSE, TRUE }, + { L"Query source", TOKEN_QUERY_SOURCE, FALSE, TRUE } +}; + +ACCESS_ENTRIES(TpWorkerFactory) +{ + { L"Full control", WORKER_FACTORY_ALL_ACCESS, TRUE, TRUE }, + { L"Release worker", WORKER_FACTORY_RELEASE_WORKER, FALSE, TRUE }, + { L"Ready worker", WORKER_FACTORY_READY_WORKER, FALSE, TRUE }, + { L"Wait", WORKER_FACTORY_WAIT, FALSE, TRUE }, + { L"Set information", WORKER_FACTORY_SET_INFORMATION, FALSE, TRUE }, + { L"Query information", WORKER_FACTORY_QUERY_INFORMATION, FALSE, TRUE }, + { L"Shutdown", WORKER_FACTORY_SHUTDOWN, FALSE, TRUE } +}; + +ACCESS_ENTRIES(Type) +{ + { L"Full control", OBJECT_TYPE_ALL_ACCESS, TRUE, TRUE }, + { L"Create", OBJECT_TYPE_CREATE, TRUE, TRUE } +}; + +ACCESS_ENTRIES(WindowStation) +{ + { L"Full control", WINSTA_ALL_ACCESS | STANDARD_RIGHTS_REQUIRED, TRUE, TRUE }, + { L"Read", WINSTA_GENERIC_READ, TRUE, FALSE }, + { L"Write", WINSTA_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", WINSTA_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Enumerate", WINSTA_ENUMERATE, FALSE, TRUE }, + { L"Enumerate desktops", WINSTA_ENUMDESKTOPS, FALSE, TRUE }, + { L"Read attributes", WINSTA_READATTRIBUTES, FALSE, TRUE }, + { L"Read screen", WINSTA_READSCREEN, FALSE, TRUE }, + { L"Access clipboard", WINSTA_ACCESSCLIPBOARD, FALSE, TRUE }, + { L"Access global atoms", WINSTA_ACCESSGLOBALATOMS, FALSE, TRUE }, + { L"Create desktop", WINSTA_CREATEDESKTOP, FALSE, TRUE }, + { L"Write attributes", WINSTA_WRITEATTRIBUTES, FALSE, TRUE }, + { L"Exit windows", WINSTA_EXITWINDOWS, FALSE, TRUE } +}; + +ACCESS_ENTRIES(WmiGuid) +{ + { L"Full control", WMIGUID_ALL_ACCESS, TRUE, TRUE }, + { L"Read", WMIGUID_GENERIC_READ, TRUE, FALSE }, + { L"Write", WMIGUID_GENERIC_WRITE, TRUE, FALSE }, + { L"Execute", WMIGUID_GENERIC_EXECUTE, TRUE, FALSE }, + { L"Query information", WMIGUID_QUERY, FALSE, TRUE }, + { L"Set information", WMIGUID_SET, FALSE, TRUE }, + { L"Get notifications", WMIGUID_NOTIFICATION, FALSE, TRUE }, + { L"Read description", WMIGUID_READ_DESCRIPTION, FALSE, TRUE }, + { L"Execute", WMIGUID_EXECUTE, FALSE, TRUE }, + { L"Create real-time logs", TRACELOG_CREATE_REALTIME, FALSE, TRUE, L"Create real-time" }, + { L"Create on disk logs", TRACELOG_CREATE_ONDISK, FALSE, TRUE, L"Create on disk" }, + { L"Enable provider GUIDs", TRACELOG_GUID_ENABLE, FALSE, TRUE, L"Enable GUIDs" }, + { L"Access kernel logger", TRACELOG_ACCESS_KERNEL_LOGGER, FALSE, TRUE }, + { L"Log events", TRACELOG_LOG_EVENT, FALSE, TRUE }, + { L"Access real-time events", TRACELOG_ACCESS_REALTIME, FALSE, TRUE, L"Access real-time" }, + { L"Register provider GUIDs", TRACELOG_REGISTER_GUIDS, FALSE, TRUE, L"Register GUIDs" } +}; + +static PH_SPECIFIC_TYPE PhSpecificTypes[] = +{ + ACCESS_ENTRY(AlpcPort, TRUE), + ACCESS_ENTRY(DebugObject, TRUE), + ACCESS_ENTRY(Desktop, FALSE), + ACCESS_ENTRY(Directory, FALSE), + ACCESS_ENTRY(Event, TRUE), + ACCESS_ENTRY(EventPair, TRUE), + ACCESS_ENTRY(File, TRUE), + ACCESS_ENTRY(FilterConnectionPort, FALSE), + ACCESS_ENTRY(IoCompletion, TRUE), + ACCESS_ENTRY(Job, TRUE), + ACCESS_ENTRY(Key, FALSE), + ACCESS_ENTRY(KeyedEvent, FALSE), + ACCESS_ENTRY(LsaAccount, FALSE), + ACCESS_ENTRY(LsaPolicy, FALSE), + ACCESS_ENTRY(LsaSecret, FALSE), + ACCESS_ENTRY(LsaTrusted, FALSE), + ACCESS_ENTRY(Mutant, TRUE), + ACCESS_ENTRY(Partition, TRUE), + ACCESS_ENTRY(Process, TRUE), + ACCESS_ENTRY(Process60, TRUE), + ACCESS_ENTRY(Profile, FALSE), + ACCESS_ENTRY(SamAlias, FALSE), + ACCESS_ENTRY(SamDomain, FALSE), + ACCESS_ENTRY(SamGroup, FALSE), + ACCESS_ENTRY(SamServer, FALSE), + ACCESS_ENTRY(SamUser, FALSE), + ACCESS_ENTRY(Section, FALSE), + ACCESS_ENTRY(Semaphore, TRUE), + ACCESS_ENTRY(Service, FALSE), + ACCESS_ENTRY(Session, FALSE), + ACCESS_ENTRY(SymbolicLink, FALSE), + ACCESS_ENTRY(Thread, TRUE), + ACCESS_ENTRY(Thread60, TRUE), + ACCESS_ENTRY(Timer, TRUE), + ACCESS_ENTRY(TmEn, FALSE), + ACCESS_ENTRY(TmRm, FALSE), + ACCESS_ENTRY(TmTm, FALSE), + ACCESS_ENTRY(TmTx, FALSE), + ACCESS_ENTRY(Token, FALSE), + ACCESS_ENTRY(TpWorkerFactory, FALSE), + ACCESS_ENTRY(Type, FALSE), + ACCESS_ENTRY(WindowStation, FALSE), + ACCESS_ENTRY(WmiGuid, TRUE) +}; + +/** + * Gets access entries for an object type. + * + * \param Type The name of the object type. + * \param AccessEntries A variable which receives an array of access entry structures. You must free + * the buffer with PhFree() when you no longer need it. + * \param NumberOfAccessEntries A variable which receives the number of access entry structures + * returned in + * \a AccessEntries. + */ +BOOLEAN PhGetAccessEntries( + _In_ PWSTR Type, + _Out_ PPH_ACCESS_ENTRY *AccessEntries, + _Out_ PULONG NumberOfAccessEntries + ) +{ + ULONG i; + PPH_SPECIFIC_TYPE specificType = NULL; + PPH_ACCESS_ENTRY accessEntries; + + if (PhEqualStringZ(Type, L"ALPC Port", TRUE)) + { + Type = L"AlpcPort"; + } + else if (PhEqualStringZ(Type, L"Port", TRUE)) + { + Type = L"AlpcPort"; + } + else if (PhEqualStringZ(Type, L"WaitablePort", TRUE)) + { + Type = L"AlpcPort"; + } + else if (PhEqualStringZ(Type, L"Process", TRUE)) + { + if (WindowsVersion >= WINDOWS_VISTA) + Type = L"Process60"; + } + else if (PhEqualStringZ(Type, L"Thread", TRUE)) + { + if (WindowsVersion >= WINDOWS_VISTA) + Type = L"Thread60"; + } + + // Find the specific type. + for (i = 0; i < sizeof(PhSpecificTypes) / sizeof(PH_SPECIFIC_TYPE); i++) + { + if (PhEqualStringZ(PhSpecificTypes[i].Type, Type, TRUE)) + { + specificType = &PhSpecificTypes[i]; + break; + } + } + + if (specificType) + { + ULONG sizeOfEntries; + + // Copy the specific access entries and append the standard access entries. + + if (specificType->HasSynchronize) + sizeOfEntries = specificType->SizeOfAccessEntries + sizeof(PhStandardAccessEntries); + else + sizeOfEntries = specificType->SizeOfAccessEntries + sizeof(PhStandardAccessEntries) - sizeof(PH_ACCESS_ENTRY); + + accessEntries = PhAllocate(sizeOfEntries); + memcpy(accessEntries, specificType->AccessEntries, specificType->SizeOfAccessEntries); + + if (specificType->HasSynchronize) + { + memcpy( + PTR_ADD_OFFSET(accessEntries, specificType->SizeOfAccessEntries), + PhStandardAccessEntries, + sizeof(PhStandardAccessEntries) + ); + } + else + { + memcpy( + PTR_ADD_OFFSET(accessEntries, specificType->SizeOfAccessEntries), + &PhStandardAccessEntries[1], + sizeof(PhStandardAccessEntries) - sizeof(PH_ACCESS_ENTRY) + ); + } + + *AccessEntries = accessEntries; + *NumberOfAccessEntries = sizeOfEntries / sizeof(PH_ACCESS_ENTRY); + } + else + { + accessEntries = PhAllocate(sizeof(PhStandardAccessEntries)); + memcpy(accessEntries, PhStandardAccessEntries, sizeof(PhStandardAccessEntries)); + + *AccessEntries = accessEntries; + *NumberOfAccessEntries = sizeof(PhStandardAccessEntries) / sizeof(PH_ACCESS_ENTRY); + } + + return TRUE; +} + +static int __cdecl PhpAccessEntryCompare( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_ACCESS_ENTRY entry1 = (PPH_ACCESS_ENTRY)elem1; + PPH_ACCESS_ENTRY entry2 = (PPH_ACCESS_ENTRY)elem2; + + return intcmp(PhCountBits(entry2->Access), PhCountBits(entry1->Access)); +} + +/** + * Creates a string representation of an access mask. + * + * \param Access The access mask. + * \param AccessEntries An array of access entry structures. You can call PhGetAccessEntries() to + * retrieve the access entry structures for a standard object type. + * \param NumberOfAccessEntries The number of elements in \a AccessEntries. + * + * \return The string representation of \a Access. + */ +PPH_STRING PhGetAccessString( + _In_ ACCESS_MASK Access, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ) +{ + PH_STRING_BUILDER stringBuilder; + PPH_ACCESS_ENTRY accessEntries; + PBOOLEAN matched; + ULONG i; + ULONG j; + + PhInitializeStringBuilder(&stringBuilder, 32); + + // Sort the access entries according to how many access rights they include. + accessEntries = PhAllocateCopy(AccessEntries, NumberOfAccessEntries * sizeof(PH_ACCESS_ENTRY)); + qsort(accessEntries, NumberOfAccessEntries, sizeof(PH_ACCESS_ENTRY), PhpAccessEntryCompare); + + matched = PhAllocate(NumberOfAccessEntries * sizeof(BOOLEAN)); + memset(matched, 0, NumberOfAccessEntries * sizeof(BOOLEAN)); + + for (i = 0; i < NumberOfAccessEntries; i++) + { + // We make sure we haven't matched this access entry yet. This ensures that we won't get + // duplicates, e.g. FILE_GENERIC_READ includes FILE_READ_DATA, and we don't want to display + // both to the user. + if ( + !matched[i] && + ((Access & accessEntries[i].Access) == accessEntries[i].Access) + ) + { + if (accessEntries[i].ShortName) + PhAppendStringBuilder2(&stringBuilder, accessEntries[i].ShortName); + else + PhAppendStringBuilder2(&stringBuilder, accessEntries[i].Name); + + PhAppendStringBuilder2(&stringBuilder, L", "); + + // Disable equal or more specific entries. + for (j = i; j < NumberOfAccessEntries; j++) + { + if ((accessEntries[i].Access | accessEntries[j].Access) == accessEntries[i].Access) + matched[j] = TRUE; + } + } + } + + // Remove the trailing ", ". + if (PhEndsWithString2(stringBuilder.String, L", ", FALSE)) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + PhFree(matched); + PhFree(accessEntries); + + return PhFinalStringBuilderString(&stringBuilder); +} diff --git a/phlib/secedit.c b/phlib/secedit.c new file mode 100644 index 0000000..8a13b5f --- /dev/null +++ b/phlib/secedit.c @@ -0,0 +1,593 @@ +/* + * Process Hacker - + * object security editor + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include + +static ISecurityInformationVtbl PhSecurityInformation_VTable = +{ + PhSecurityInformation_QueryInterface, + PhSecurityInformation_AddRef, + PhSecurityInformation_Release, + PhSecurityInformation_GetObjectInformation, + PhSecurityInformation_GetSecurity, + PhSecurityInformation_SetSecurity, + PhSecurityInformation_GetAccessRights, + PhSecurityInformation_MapGeneric, + PhSecurityInformation_GetInheritTypes, + PhSecurityInformation_PropertySheetPageCallback +}; + +static PH_INITONCE SecurityEditorInitOnce = PH_INITONCE_INIT; +static _CreateSecurityPage CreateSecurityPage_I; +static _EditSecurity EditSecurity_I; + +FORCEINLINE VOID PhpSecurityEditorInitialization( + VOID + ) +{ + if (PhBeginInitOnce(&SecurityEditorInitOnce)) + { + HMODULE aclui; + + aclui = LoadLibrary(L"aclui.dll"); + CreateSecurityPage_I = (PVOID)GetProcAddress(aclui, "CreateSecurityPage"); + EditSecurity_I = (PVOID)GetProcAddress(aclui, "EditSecurity"); + + PhEndInitOnce(&SecurityEditorInitOnce); + } +} + +/** + * Creates a security editor page. + * + * \param ObjectName The name of the object. + * \param GetObjectSecurity A callback function executed to retrieve the security descriptor of the + * object. + * \param SetObjectSecurity A callback function executed to modify the security descriptor of the + * object. + * \param Context A user-defined value to pass to the callback functions. + * \param AccessEntries An array of access mask descriptors. + * \param NumberOfAccessEntries The number of elements in \a AccessEntries. + */ +HPROPSHEETPAGE PhCreateSecurityPage( + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ) +{ + ISecurityInformation *info; + HPROPSHEETPAGE page; + + PhpSecurityEditorInitialization(); + + if (!CreateSecurityPage_I) + return NULL; + + info = PhSecurityInformation_Create( + ObjectName, + GetObjectSecurity, + SetObjectSecurity, + Context, + AccessEntries, + NumberOfAccessEntries + ); + + page = CreateSecurityPage_I(info); + + PhSecurityInformation_Release(info); + + return page; +} + +/** + * Displays a security editor dialog. + * + * \param hWnd The parent window of the dialog. + * \param ObjectName The name of the object. + * \param GetObjectSecurity A callback function executed to retrieve the security descriptor of the + * object. + * \param SetObjectSecurity A callback function executed to modify the security descriptor of the + * object. + * \param Context A user-defined value to pass to the callback functions. + * \param AccessEntries An array of access mask descriptors. + * \param NumberOfAccessEntries The number of elements in \a AccessEntries. + */ +VOID PhEditSecurity( + _In_ HWND hWnd, + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ) +{ + ISecurityInformation *info; + + PhpSecurityEditorInitialization(); + + if (!EditSecurity_I) + return; + + info = PhSecurityInformation_Create( + ObjectName, + GetObjectSecurity, + SetObjectSecurity, + Context, + AccessEntries, + NumberOfAccessEntries + ); + + EditSecurity_I(hWnd, info); + + PhSecurityInformation_Release(info); +} + +ISecurityInformation *PhSecurityInformation_Create( + _In_ PWSTR ObjectName, + _In_ PPH_GET_OBJECT_SECURITY GetObjectSecurity, + _In_ PPH_SET_OBJECT_SECURITY SetObjectSecurity, + _In_opt_ PVOID Context, + _In_ PPH_ACCESS_ENTRY AccessEntries, + _In_ ULONG NumberOfAccessEntries + ) +{ + PhSecurityInformation *info; + ULONG i; + + info = PhAllocate(sizeof(PhSecurityInformation)); + info->VTable = &PhSecurityInformation_VTable; + info->RefCount = 1; + + info->ObjectName = PhCreateString(ObjectName); + info->GetObjectSecurity = GetObjectSecurity; + info->SetObjectSecurity = SetObjectSecurity; + info->Context = Context; + info->AccessEntries = PhAllocate(sizeof(SI_ACCESS) * NumberOfAccessEntries); + info->NumberOfAccessEntries = NumberOfAccessEntries; + + for (i = 0; i < NumberOfAccessEntries; i++) + { + memset(&info->AccessEntries[i], 0, sizeof(SI_ACCESS)); + info->AccessEntries[i].pszName = AccessEntries[i].Name; + info->AccessEntries[i].mask = AccessEntries[i].Access; + + if (AccessEntries[i].General) + info->AccessEntries[i].dwFlags |= SI_ACCESS_GENERAL; + if (AccessEntries[i].Specific) + info->AccessEntries[i].dwFlags |= SI_ACCESS_SPECIFIC; + } + + return (ISecurityInformation *)info; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_QueryInterface( + _In_ ISecurityInformation *This, + _In_ REFIID Riid, + _Out_ PVOID *Object + ) +{ + if ( + IsEqualIID(Riid, &IID_IUnknown) || + IsEqualIID(Riid, &IID_ISecurityInformation) + ) + { + PhSecurityInformation_AddRef(This); + *Object = This; + return S_OK; + } + + *Object = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE PhSecurityInformation_AddRef( + _In_ ISecurityInformation *This + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + + this->RefCount++; + + return this->RefCount; +} + +ULONG STDMETHODCALLTYPE PhSecurityInformation_Release( + _In_ ISecurityInformation *This + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + + this->RefCount--; + + if (this->RefCount == 0) + { + if (this->ObjectName) PhDereferenceObject(this->ObjectName); + PhFree(this->AccessEntries); + + PhFree(this); + + return 0; + } + + return this->RefCount; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetObjectInformation( + _In_ ISecurityInformation *This, + _Out_ PSI_OBJECT_INFO ObjectInfo + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + + memset(ObjectInfo, 0, sizeof(SI_OBJECT_INFO)); + ObjectInfo->dwFlags = + SI_EDIT_AUDITS | + SI_EDIT_OWNER | + SI_EDIT_PERMS | + SI_ADVANCED | + SI_NO_ACL_PROTECT | + SI_NO_TREE_APPLY; + ObjectInfo->hInstance = NULL; + ObjectInfo->pszObjectName = this->ObjectName->Buffer; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetSecurity( + _In_ ISecurityInformation *This, + _In_ SECURITY_INFORMATION RequestedInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, + _In_ BOOL Default + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + NTSTATUS status; + PSECURITY_DESCRIPTOR securityDescriptor; + ULONG sdLength; + PSECURITY_DESCRIPTOR newSd; + + status = this->GetObjectSecurity( + &securityDescriptor, + RequestedInformation, + this->Context + ); + + if (!NT_SUCCESS(status)) + return HRESULT_FROM_WIN32(PhNtStatusToDosError(status)); + + sdLength = RtlLengthSecurityDescriptor(securityDescriptor); + newSd = LocalAlloc(0, sdLength); + memcpy(newSd, securityDescriptor, sdLength); + PhFree(securityDescriptor); + + *SecurityDescriptor = newSd; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_SetSecurity( + _In_ ISecurityInformation *This, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + NTSTATUS status; + + status = this->SetObjectSecurity( + SecurityDescriptor, + SecurityInformation, + this->Context + ); + + if (!NT_SUCCESS(status)) + return HRESULT_FROM_WIN32(PhNtStatusToDosError(status)); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetAccessRights( + _In_ ISecurityInformation *This, + _In_ const GUID *ObjectType, + _In_ ULONG Flags, + _Out_ PSI_ACCESS *Access, + _Out_ PULONG Accesses, + _Out_ PULONG DefaultAccess + ) +{ + PhSecurityInformation *this = (PhSecurityInformation *)This; + + *Access = this->AccessEntries; + *Accesses = this->NumberOfAccessEntries; + *DefaultAccess = 0; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_MapGeneric( + _In_ ISecurityInformation *This, + _In_ const GUID *ObjectType, + _In_ PUCHAR AceFlags, + _Inout_ PACCESS_MASK Mask + ) +{ + return S_OK; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_GetInheritTypes( + _In_ ISecurityInformation *This, + _Out_ PSI_INHERIT_TYPE *InheritTypes, + _Out_ PULONG InheritTypesCount + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE PhSecurityInformation_PropertySheetPageCallback( + _In_ ISecurityInformation *This, + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ SI_PAGE_TYPE uPage + ) +{ + return E_NOTIMPL; +} + +NTSTATUS PhpGetObjectSecurityWithTimeout( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ) +{ + NTSTATUS status; + ULONG bufferSize; + PVOID buffer; + + bufferSize = 0x100; + buffer = PhAllocate(bufferSize); + // This is required (especially for File objects) because some drivers don't seem to handle + // QuerySecurity properly. + memset(buffer, 0, bufferSize); + + status = PhCallNtQuerySecurityObjectWithTimeout( + Handle, + SecurityInformation, + buffer, + bufferSize, + &bufferSize + ); + + if (status == STATUS_BUFFER_TOO_SMALL) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + memset(buffer, 0, bufferSize); + + status = PhCallNtQuerySecurityObjectWithTimeout( + Handle, + SecurityInformation, + buffer, + bufferSize, + &bufferSize + ); + } + + if (!NT_SUCCESS(status)) + { + PhFree(buffer); + return status; + } + + *SecurityDescriptor = (PSECURITY_DESCRIPTOR)buffer; + + return status; +} + +/** + * Retrieves the security descriptor of an object. + * + * \param SecurityDescriptor A variable which receives a pointer to the security descriptor of the + * object. The security descriptor must be freed using PhFree() when no longer needed. + * \param SecurityInformation The security information to retrieve. + * \param Context A pointer to a PH_STD_OBJECT_SECURITY structure describing the object. + * + * \remarks This function may be used for the \a GetObjectSecurity callback in + * PhCreateSecurityPage() or PhEditSecurity(). + */ +_Callback_ NTSTATUS PhStdGetObjectSecurity( + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PPH_STD_OBJECT_SECURITY stdObjectSecurity; + HANDLE handle; + + stdObjectSecurity = (PPH_STD_OBJECT_SECURITY)Context; + + status = stdObjectSecurity->OpenObject( + &handle, + PhGetAccessForGetSecurity(SecurityInformation), + stdObjectSecurity->Context + ); + + if (!NT_SUCCESS(status)) + return status; + + if (PhEqualStringZ(stdObjectSecurity->ObjectType, L"Service", TRUE)) + { + status = PhGetSeObjectSecurity(handle, SE_SERVICE, SecurityInformation, SecurityDescriptor); + CloseServiceHandle(handle); + } + else if (PhEqualStringZ(stdObjectSecurity->ObjectType, L"File", TRUE)) + { + status = PhpGetObjectSecurityWithTimeout(handle, SecurityInformation, SecurityDescriptor); + NtClose(handle); + } + else + { + status = PhGetObjectSecurity(handle, SecurityInformation, SecurityDescriptor); + NtClose(handle); + } + + return status; +} + +/** + * Modifies the security descriptor of an object. + * + * \param SecurityDescriptor A security descriptor containing security information to set. + * \param SecurityInformation The security information to retrieve. + * \param Context A pointer to a PH_STD_OBJECT_SECURITY structure describing the object. + * + * \remarks This function may be used for the \a SetObjectSecurity callback in + * PhCreateSecurityPage() or PhEditSecurity(). + */ +_Callback_ NTSTATUS PhStdSetObjectSecurity( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status; + PPH_STD_OBJECT_SECURITY stdObjectSecurity; + HANDLE handle; + + stdObjectSecurity = (PPH_STD_OBJECT_SECURITY)Context; + + status = stdObjectSecurity->OpenObject( + &handle, + PhGetAccessForSetSecurity(SecurityInformation), + stdObjectSecurity->Context + ); + + if (!NT_SUCCESS(status)) + return status; + + if (PhEqualStringZ(stdObjectSecurity->ObjectType, L"Service", TRUE)) + { + status = PhSetSeObjectSecurity(handle, SE_SERVICE, SecurityInformation, SecurityDescriptor); + CloseServiceHandle(handle); + } + else + { + status = PhSetObjectSecurity(handle, SecurityInformation, SecurityDescriptor); + NtClose(handle); + } + + return status; +} + +NTSTATUS PhGetSeObjectSecurity( + _In_ HANDLE Handle, + _In_ ULONG ObjectType, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ) +{ + ULONG win32Result; + PSECURITY_DESCRIPTOR securityDescriptor; + + win32Result = GetSecurityInfo( + Handle, + ObjectType, + SecurityInformation, + NULL, + NULL, + NULL, + NULL, + &securityDescriptor + ); + + if (win32Result != ERROR_SUCCESS) + return NTSTATUS_FROM_WIN32(win32Result); + + *SecurityDescriptor = PhAllocateCopy(securityDescriptor, RtlLengthSecurityDescriptor(securityDescriptor)); + LocalFree(securityDescriptor); + + return STATUS_SUCCESS; +} + +NTSTATUS PhSetSeObjectSecurity( + _In_ HANDLE Handle, + _In_ ULONG ObjectType, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ) +{ + ULONG win32Result; + SECURITY_INFORMATION securityInformation = 0; + BOOLEAN present; + BOOLEAN defaulted; + PSID owner = NULL; + PSID group = NULL; + PACL dacl = NULL; + PACL sacl = NULL; + + if (SecurityInformation & OWNER_SECURITY_INFORMATION) + { + if (NT_SUCCESS(RtlGetOwnerSecurityDescriptor(SecurityDescriptor, &owner, &defaulted))) + securityInformation |= OWNER_SECURITY_INFORMATION; + } + + if (SecurityInformation & GROUP_SECURITY_INFORMATION) + { + if (NT_SUCCESS(RtlGetGroupSecurityDescriptor(SecurityDescriptor, &group, &defaulted))) + securityInformation |= GROUP_SECURITY_INFORMATION; + } + + if (SecurityInformation & DACL_SECURITY_INFORMATION) + { + if (NT_SUCCESS(RtlGetDaclSecurityDescriptor(SecurityDescriptor, &present, &dacl, &defaulted)) && present) + securityInformation |= DACL_SECURITY_INFORMATION; + } + + if (SecurityInformation & SACL_SECURITY_INFORMATION) + { + if (NT_SUCCESS(RtlGetSaclSecurityDescriptor(SecurityDescriptor, &present, &sacl, &defaulted)) && present) + securityInformation |= SACL_SECURITY_INFORMATION; + } + + win32Result = SetSecurityInfo( + Handle, + ObjectType, + SecurityInformation, + owner, + group, + dacl, + sacl + ); + + if (win32Result != ERROR_SUCCESS) + return NTSTATUS_FROM_WIN32(win32Result); + + return STATUS_SUCCESS; +} diff --git a/phlib/sha.c b/phlib/sha.c new file mode 100644 index 0000000..6b69dde --- /dev/null +++ b/phlib/sha.c @@ -0,0 +1,172 @@ +/* + * Copyright 2004 Filip Navara + * Based on public domain SHA code by Steve Reid + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* This code was modified for Process Hacker. */ + +#include +#include "sha.h" + +/* SHA1 Helper Macros */ + +//#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define rol(value, bits) (_rotl((value), (bits))) +#define DWORD2BE(x) (((x) >> 24) & 0xff) | (((x) >> 8) & 0xff00) | (((x) << 8) & 0xff0000) | (((x) << 24) & 0xff000000); +#define blk0(i) (Block[i] = (rol(Block[i],24)&0xFF00FF00)|(rol(Block[i],8)&0x00FF00FF)) +#define blk1(i) (Block[i&15] = rol(Block[(i+13)&15]^Block[(i+8)&15]^Block[(i+2)&15]^Block[i&15],1)) +#define f1(x,y,z) (z^(x&(y^z))) +#define f2(x,y,z) (x^y^z) +#define f3(x,y,z) ((x&y)|(z&(x|y))) +#define f4(x,y,z) (x^y^z) +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=f1(w,x,y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=f1(w,x,y)+blk1(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=f2(w,x,y)+blk1(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=f3(w,x,y)+blk1(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=f4(w,x,y)+blk1(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +/* Hash a single 512-bit block. This is the core of the algorithm. */ +static void SHATransform(ULONG State[5], UCHAR Buffer[64]) +{ + ULONG a, b, c, d, e; + ULONG *Block; + + Block = (ULONG*)Buffer; + + /* Copy Context->State[] to working variables */ + a = State[0]; + b = State[1]; + c = State[2]; + d = State[3]; + e = State[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working variables back into Context->State[] */ + State[0] += a; + State[1] += b; + State[2] += c; + State[3] += d; + State[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + +VOID A_SHAInit( + _Out_ A_SHA_CTX *Context + ) +{ + /* SHA1 initialization constants */ + Context->state[0] = 0x67452301; + Context->state[1] = 0xEFCDAB89; + Context->state[2] = 0x98BADCFE; + Context->state[3] = 0x10325476; + Context->state[4] = 0xC3D2E1F0; + Context->count[0] = 0; + Context->count[1] = 0; +} + +VOID A_SHAUpdate( + _Inout_ A_SHA_CTX *Context, + _In_reads_bytes_(Length) UCHAR *Input, + _In_ ULONG Length + ) +{ + ULONG InputContentSize; + + InputContentSize = Context->count[1] & 63; + Context->count[1] += Length; + if (Context->count[1] < Length) + Context->count[0]++; + Context->count[0] += (Length >> 29); + + if (InputContentSize + Length < 64) + { + RtlCopyMemory(&Context->buffer[InputContentSize], Input, + Length); + } + else + { + while (InputContentSize + Length >= 64) + { + RtlCopyMemory(Context->buffer + InputContentSize, Input, + 64 - InputContentSize); + Input += 64 - InputContentSize; + Length -= 64 - InputContentSize; + SHATransform(Context->state, Context->buffer); + InputContentSize = 0; + } + RtlCopyMemory(Context->buffer + InputContentSize, Input, Length); + } +} + +VOID A_SHAFinal( + _Inout_ A_SHA_CTX *Context, + _Out_writes_bytes_(20) UCHAR *Hash + ) +{ + INT Pad, Index; + UCHAR Buffer[72]; + ULONG *Count; + ULONG BufferContentSize, LengthHi, LengthLo; + ULONG *Result; + + BufferContentSize = Context->count[1] & 63; + if (BufferContentSize >= 56) + Pad = 56 + 64 - BufferContentSize; + else + Pad = 56 - BufferContentSize; + + LengthHi = (Context->count[0] << 3) | (Context->count[1] >> (32 - 3)); + LengthLo = (Context->count[1] << 3); + + RtlZeroMemory(Buffer + 1, Pad - 1); + Buffer[0] = 0x80; + Count = (ULONG*)(Buffer + Pad); + Count[0] = DWORD2BE(LengthHi); + Count[1] = DWORD2BE(LengthLo); + A_SHAUpdate(Context, Buffer, Pad + 8); + + Result = (ULONG *)Hash; + + for (Index = 0; Index < 5; Index++) + Result[Index] = DWORD2BE(Context->state[Index]); + + A_SHAInit(Context); +} diff --git a/phlib/sha.h b/phlib/sha.h new file mode 100644 index 0000000..2f2c696 --- /dev/null +++ b/phlib/sha.h @@ -0,0 +1,28 @@ +#ifndef _SHA_H +#define _SHA_H + +typedef struct +{ + ULONG flag; + UCHAR hash[20]; + ULONG state[5]; + ULONG count[2]; + UCHAR buffer[64]; +} A_SHA_CTX; + +VOID A_SHAInit( + _Out_ A_SHA_CTX *Context + ); + +VOID A_SHAUpdate( + _Inout_ A_SHA_CTX *Context, + _In_reads_bytes_(Length) UCHAR *Input, + _In_ ULONG Length + ); + +VOID A_SHAFinal( + _Inout_ A_SHA_CTX *Context, + _Out_writes_bytes_(20) UCHAR *Hash + ); + +#endif diff --git a/phlib/svcsup.c b/phlib/svcsup.c new file mode 100644 index 0000000..cd281d1 --- /dev/null +++ b/phlib/svcsup.c @@ -0,0 +1,530 @@ +/* + * Process Hacker - + * service support functions + * + * Copyright (C) 2010-2012 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +static PH_KEY_VALUE_PAIR PhpServiceStatePairs[] = +{ + SIP(L"Stopped", SERVICE_STOPPED), + SIP(L"Start pending", SERVICE_START_PENDING), + SIP(L"Stop pending", SERVICE_STOP_PENDING), + SIP(L"Running", SERVICE_RUNNING), + SIP(L"Continue pending", SERVICE_CONTINUE_PENDING), + SIP(L"Pause pending", SERVICE_PAUSE_PENDING), + SIP(L"Paused", SERVICE_PAUSED) +}; + +static PH_KEY_VALUE_PAIR PhpServiceTypePairs[] = +{ + SIP(L"Driver", SERVICE_KERNEL_DRIVER), + SIP(L"FS driver", SERVICE_FILE_SYSTEM_DRIVER), + SIP(L"Own process", SERVICE_WIN32_OWN_PROCESS), + SIP(L"Share process", SERVICE_WIN32_SHARE_PROCESS), + SIP(L"Own interactive process", SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS), + SIP(L"Share interactive process", SERVICE_WIN32_SHARE_PROCESS | SERVICE_INTERACTIVE_PROCESS), + SIP(L"User own process", SERVICE_USER_OWN_PROCESS), + SIP(L"User own process (instance)", SERVICE_USER_OWN_PROCESS | SERVICE_USERSERVICE_INSTANCE), + SIP(L"User share process", SERVICE_USER_SHARE_PROCESS), + SIP(L"User share process (instance)", SERVICE_USER_SHARE_PROCESS | SERVICE_USERSERVICE_INSTANCE), +}; + +static PH_KEY_VALUE_PAIR PhpServiceStartTypePairs[] = +{ + SIP(L"Disabled", SERVICE_DISABLED), + SIP(L"Boot start", SERVICE_BOOT_START), + SIP(L"System start", SERVICE_SYSTEM_START), + SIP(L"Auto start", SERVICE_AUTO_START), + SIP(L"Demand start", SERVICE_DEMAND_START) +}; + +static PH_KEY_VALUE_PAIR PhpServiceErrorControlPairs[] = +{ + SIP(L"Ignore", SERVICE_ERROR_IGNORE), + SIP(L"Normal", SERVICE_ERROR_NORMAL), + SIP(L"Severe", SERVICE_ERROR_SEVERE), + SIP(L"Critical", SERVICE_ERROR_CRITICAL) +}; + +WCHAR *PhServiceTypeStrings[10] = { L"Driver", L"FS driver", L"Own process", L"Share process", + L"Own interactive process", L"Share interactive process", L"User own process", L"User own process (instance)", + L"User share process", L"User share process (instance)" }; +WCHAR *PhServiceStartTypeStrings[5] = { L"Disabled", L"Boot start", L"System start", + L"Auto start", L"Demand start" }; +WCHAR *PhServiceErrorControlStrings[4] = { L"Ignore", L"Normal", L"Severe", L"Critical" }; + +PVOID PhEnumServices( + _In_ SC_HANDLE ScManagerHandle, + _In_opt_ ULONG Type, + _In_opt_ ULONG State, + _Out_ PULONG Count + ) +{ + static ULONG initialBufferSize = 0x8000; + LOGICAL result; + PVOID buffer; + ULONG bufferSize; + ULONG returnLength; + ULONG servicesReturned; + + if (!Type) + Type = WindowsVersion >= WINDOWS_10 ? SERVICE_TYPE_ALL : (SERVICE_DRIVER | SERVICE_WIN32); + if (!State) + State = SERVICE_STATE_ALL; + + bufferSize = initialBufferSize; + buffer = PhAllocate(bufferSize); + + if (!(result = EnumServicesStatusEx( + ScManagerHandle, + SC_ENUM_PROCESS_INFO, + Type, + State, + buffer, + bufferSize, + &returnLength, + &servicesReturned, + NULL, + NULL + ))) + { + if (GetLastError() == ERROR_MORE_DATA) + { + PhFree(buffer); + bufferSize += returnLength; + buffer = PhAllocate(bufferSize); + + result = EnumServicesStatusEx( + ScManagerHandle, + SC_ENUM_PROCESS_INFO, + Type, + State, + buffer, + bufferSize, + &returnLength, + &servicesReturned, + NULL, + NULL + ); + } + + if (!result) + { + PhFree(buffer); + return NULL; + } + } + + if (bufferSize <= 0x20000) initialBufferSize = bufferSize; + *Count = servicesReturned; + + return buffer; +} + +SC_HANDLE PhOpenService( + _In_ PWSTR ServiceName, + _In_ ACCESS_MASK DesiredAccess + ) +{ + SC_HANDLE scManagerHandle; + SC_HANDLE serviceHandle; + + scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + + if (!scManagerHandle) + return NULL; + + serviceHandle = OpenService(scManagerHandle, ServiceName, DesiredAccess); + CloseServiceHandle(scManagerHandle); + + return serviceHandle; +} + +PVOID PhGetServiceConfig( + _In_ SC_HANDLE ServiceHandle + ) +{ + PVOID buffer; + ULONG bufferSize = 0x200; + + buffer = PhAllocate(bufferSize); + + if (!QueryServiceConfig(ServiceHandle, buffer, bufferSize, &bufferSize)) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + if (!QueryServiceConfig(ServiceHandle, buffer, bufferSize, &bufferSize)) + { + PhFree(buffer); + return NULL; + } + } + + return buffer; +} + +PVOID PhQueryServiceVariableSize( + _In_ SC_HANDLE ServiceHandle, + _In_ ULONG InfoLevel + ) +{ + PVOID buffer; + ULONG bufferSize = 0x100; + + buffer = PhAllocate(bufferSize); + + if (!QueryServiceConfig2( + ServiceHandle, + InfoLevel, + (BYTE *)buffer, + bufferSize, + &bufferSize + )) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + + if (!QueryServiceConfig2( + ServiceHandle, + InfoLevel, + (BYTE *)buffer, + bufferSize, + &bufferSize + )) + { + PhFree(buffer); + return NULL; + } + } + + return buffer; +} + +PPH_STRING PhGetServiceDescription( + _In_ SC_HANDLE ServiceHandle + ) +{ + PPH_STRING description = NULL; + LPSERVICE_DESCRIPTION serviceDescription; + + serviceDescription = PhQueryServiceVariableSize(ServiceHandle, SERVICE_CONFIG_DESCRIPTION); + + if (serviceDescription) + { + if (serviceDescription->lpDescription) + description = PhCreateString(serviceDescription->lpDescription); + + PhFree(serviceDescription); + + return description; + } + else + { + return NULL; + } +} + +BOOLEAN PhGetServiceDelayedAutoStart( + _In_ SC_HANDLE ServiceHandle, + _Out_ PBOOLEAN DelayedAutoStart + ) +{ + SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo; + ULONG returnLength; + + if (QueryServiceConfig2( + ServiceHandle, + SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + (BYTE *)&delayedAutoStartInfo, + sizeof(SERVICE_DELAYED_AUTO_START_INFO), + &returnLength + )) + { + *DelayedAutoStart = !!delayedAutoStartInfo.fDelayedAutostart; + return TRUE; + } + else + { + return FALSE; + } +} + +BOOLEAN PhSetServiceDelayedAutoStart( + _In_ SC_HANDLE ServiceHandle, + _In_ BOOLEAN DelayedAutoStart + ) +{ + SERVICE_DELAYED_AUTO_START_INFO delayedAutoStartInfo; + + delayedAutoStartInfo.fDelayedAutostart = DelayedAutoStart; + + return !!ChangeServiceConfig2( + ServiceHandle, + SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + &delayedAutoStartInfo + ); +} + +PWSTR PhGetServiceStateString( + _In_ ULONG ServiceState + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + PhpServiceStatePairs, + sizeof(PhpServiceStatePairs), + ServiceState, + &string + )) + return string; + else + return L"Unknown"; +} + +PWSTR PhGetServiceTypeString( + _In_ ULONG ServiceType + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + PhpServiceTypePairs, + sizeof(PhpServiceTypePairs), + ServiceType, + &string + )) + return string; + else + return L"Unknown"; +} + +ULONG PhGetServiceTypeInteger( + _In_ PWSTR ServiceType + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs( + PhpServiceTypePairs, + sizeof(PhpServiceTypePairs), + ServiceType, + &integer + )) + return integer; + else + return -1; +} + +PWSTR PhGetServiceStartTypeString( + _In_ ULONG ServiceStartType + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + PhpServiceStartTypePairs, + sizeof(PhpServiceStartTypePairs), + ServiceStartType, + &string + )) + return string; + else + return L"Unknown"; +} + +ULONG PhGetServiceStartTypeInteger( + _In_ PWSTR ServiceStartType + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs( + PhpServiceStartTypePairs, + sizeof(PhpServiceStartTypePairs), + ServiceStartType, + &integer + )) + return integer; + else + return -1; +} + +PWSTR PhGetServiceErrorControlString( + _In_ ULONG ServiceErrorControl + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + PhpServiceErrorControlPairs, + sizeof(PhpServiceErrorControlPairs), + ServiceErrorControl, + &string + )) + return string; + else + return L"Unknown"; +} + +ULONG PhGetServiceErrorControlInteger( + _In_ PWSTR ServiceErrorControl + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs( + PhpServiceErrorControlPairs, + sizeof(PhpServiceErrorControlPairs), + ServiceErrorControl, + &integer + )) + return integer; + else + return -1; +} + +PPH_STRING PhGetServiceNameFromTag( + _In_ HANDLE ProcessId, + _In_ PVOID ServiceTag + ) +{ + static PQUERY_TAG_INFORMATION I_QueryTagInformation = NULL; + PPH_STRING serviceName = NULL; + TAG_INFO_NAME_FROM_TAG nameFromTag; + + if (!I_QueryTagInformation) + { + I_QueryTagInformation = PhGetModuleProcAddress(L"advapi32.dll", "I_QueryTagInformation"); + + if (!I_QueryTagInformation) + return NULL; + } + + memset(&nameFromTag, 0, sizeof(TAG_INFO_NAME_FROM_TAG)); + nameFromTag.InParams.dwPid = HandleToUlong(ProcessId); + nameFromTag.InParams.dwTag = PtrToUlong(ServiceTag); + + I_QueryTagInformation(NULL, eTagInfoLevelNameFromTag, &nameFromTag); + + if (nameFromTag.OutParams.pszName) + { + serviceName = PhCreateString(nameFromTag.OutParams.pszName); + LocalFree(nameFromTag.OutParams.pszName); + } + + return serviceName; +} + +NTSTATUS PhGetThreadServiceTag( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE ProcessHandle, + _Out_ PVOID *ServiceTag + ) +{ + NTSTATUS status; + THREAD_BASIC_INFORMATION basicInfo; + BOOLEAN openedProcessHandle = FALSE; + + if (!NT_SUCCESS(status = PhGetThreadBasicInformation(ThreadHandle, &basicInfo))) + return status; + + if (!ProcessHandle) + { + if (!NT_SUCCESS(status = PhOpenThreadProcess( + ThreadHandle, + PROCESS_VM_READ, + &ProcessHandle + ))) + return status; + + openedProcessHandle = TRUE; + } + + status = NtReadVirtualMemory( + ProcessHandle, + PTR_ADD_OFFSET(basicInfo.TebBaseAddress, FIELD_OFFSET(TEB, SubProcessTag)), + ServiceTag, + sizeof(PVOID), + NULL + ); + + if (openedProcessHandle) + NtClose(ProcessHandle); + + return status; +} + +NTSTATUS PhGetServiceDllParameter( + _In_ PPH_STRINGREF ServiceName, + _Out_ PPH_STRING *ServiceDll + ) +{ + static PH_STRINGREF servicesKeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\"); + static PH_STRINGREF parameters = PH_STRINGREF_INIT(L"\\Parameters"); + + NTSTATUS status; + HANDLE keyHandle; + PPH_STRING keyName; + + keyName = PhConcatStringRef3(&servicesKeyName, ServiceName, ¶meters); + + if (NT_SUCCESS(status = PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName->sr, + 0 + ))) + { + PPH_STRING serviceDllString; + + if (serviceDllString = PhQueryRegistryString(keyHandle, L"ServiceDll")) + { + PPH_STRING expandedString; + + if (expandedString = PhExpandEnvironmentStrings(&serviceDllString->sr)) + { + *ServiceDll = expandedString; + PhDereferenceObject(serviceDllString); + } + else + { + *ServiceDll = serviceDllString; + } + } + else + { + status = STATUS_NOT_FOUND; + } + + NtClose(keyHandle); + } + + PhDereferenceObject(keyName); + + return status; +} diff --git a/phlib/symprv.c b/phlib/symprv.c new file mode 100644 index 0000000..7dc60a5 --- /dev/null +++ b/phlib/symprv.c @@ -0,0 +1,1762 @@ +/* + * Process Hacker - + * symbol provider + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4091) // Ignore 'no variable declared on typedef' +#include +#pragma warning(pop) + +#include +#include + +typedef struct _PH_SYMBOL_MODULE +{ + LIST_ENTRY ListEntry; + PH_AVL_LINKS Links; + ULONG64 BaseAddress; + ULONG Size; + PPH_STRING FileName; + ULONG BaseNameIndex; +} PH_SYMBOL_MODULE, *PPH_SYMBOL_MODULE; + +VOID NTAPI PhpSymbolProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +VOID PhpRegisterSymbolProvider( + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider + ); + +VOID PhpFreeSymbolModule( + _In_ PPH_SYMBOL_MODULE SymbolModule + ); + +LONG NTAPI PhpSymbolModuleCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ); + +PPH_OBJECT_TYPE PhSymbolProviderType; + +static PH_INITONCE PhSymInitOnce = PH_INITONCE_INIT; +DECLSPEC_SELECTANY PH_CALLBACK_DECLARE(PhSymInitCallback); + +static HANDLE PhNextFakeHandle = (HANDLE)0; +static PH_FAST_LOCK PhSymMutex = PH_FAST_LOCK_INIT; + +#define PH_LOCK_SYMBOLS() PhAcquireFastLockExclusive(&PhSymMutex) +#define PH_UNLOCK_SYMBOLS() PhReleaseFastLockExclusive(&PhSymMutex) + +_SymInitialize SymInitialize_I; +_SymCleanup SymCleanup_I; +_SymEnumSymbols SymEnumSymbols_I; +_SymEnumSymbolsW SymEnumSymbolsW_I; +_SymFromAddr SymFromAddr_I; +_SymFromAddrW SymFromAddrW_I; +_SymFromName SymFromName_I; +_SymFromNameW SymFromNameW_I; +_SymGetLineFromAddr64 SymGetLineFromAddr64_I; +_SymGetLineFromAddrW64 SymGetLineFromAddrW64_I; +_SymLoadModule64 SymLoadModule64_I; +_SymLoadModuleExW SymLoadModuleExW_I; +_SymGetOptions SymGetOptions_I; +_SymSetOptions SymSetOptions_I; +_SymGetSearchPath SymGetSearchPath_I; +_SymGetSearchPathW SymGetSearchPathW_I; +_SymSetSearchPath SymSetSearchPath_I; +_SymSetSearchPathW SymSetSearchPathW_I; +_SymUnloadModule64 SymUnloadModule64_I; +_SymFunctionTableAccess64 SymFunctionTableAccess64_I; +_SymGetModuleBase64 SymGetModuleBase64_I; +_SymRegisterCallbackW64 SymRegisterCallbackW64_I; +_StackWalk64 StackWalk64_I; +_MiniDumpWriteDump MiniDumpWriteDump_I; +_SymbolServerGetOptions SymbolServerGetOptions; +_SymbolServerSetOptions SymbolServerSetOptions; + +BOOLEAN PhSymbolProviderInitialization( + VOID + ) +{ + PhSymbolProviderType = PhCreateObjectType(L"SymbolProvider", 0, PhpSymbolProviderDeleteProcedure); + + return TRUE; +} + +VOID PhSymbolProviderCompleteInitialization( + _In_opt_ PVOID DbgHelpBase + ) +{ + HMODULE dbghelpHandle; + HMODULE symsrvHandle; + + // The user should have loaded dbghelp.dll and symsrv.dll already. If not, it's not our problem. + + // The Unicode versions aren't available in dbghelp.dll 5.1, so we fallback on the ANSI versions. + + if (DbgHelpBase) + dbghelpHandle = DbgHelpBase; + else + dbghelpHandle = GetModuleHandle(L"dbghelp.dll"); + + symsrvHandle = GetModuleHandle(L"symsrv.dll"); + + SymInitialize_I = (PVOID)GetProcAddress(dbghelpHandle, "SymInitialize"); + SymCleanup_I = (PVOID)GetProcAddress(dbghelpHandle, "SymCleanup"); + if (!(SymEnumSymbolsW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbolsW"))) + SymEnumSymbols_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbols"); + if (!(SymFromAddrW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddrW"))) + SymFromAddr_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddr"); + if (!(SymFromNameW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromNameW"))) + SymFromName_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromName"); + if (!(SymGetLineFromAddrW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddrW64"))) + SymGetLineFromAddr64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddr64"); + if (!(SymLoadModuleExW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModuleExW"))) + SymLoadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModule64"); + SymGetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetOptions"); + SymSetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetOptions"); + if (!(SymGetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPathW"))) + SymGetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPath"); + if (!(SymSetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPathW"))) + SymSetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPath"); + SymUnloadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymUnloadModule64"); + SymFunctionTableAccess64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFunctionTableAccess64"); + SymGetModuleBase64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetModuleBase64"); + SymRegisterCallbackW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymRegisterCallbackW64"); + StackWalk64_I = (PVOID)GetProcAddress(dbghelpHandle, "StackWalk64"); + MiniDumpWriteDump_I = (PVOID)GetProcAddress(dbghelpHandle, "MiniDumpWriteDump"); + SymbolServerGetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerGetOptions"); + SymbolServerSetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerSetOptions"); + + if (SymGetOptions_I && SymSetOptions_I) + SymSetOptions_I(SymGetOptions_I() | SYMOPT_DEFERRED_LOADS | SYMOPT_FAVOR_COMPRESSED); +} + +PPH_SYMBOL_PROVIDER PhCreateSymbolProvider( + _In_opt_ HANDLE ProcessId + ) +{ + PPH_SYMBOL_PROVIDER symbolProvider; + + symbolProvider = PhCreateObject(sizeof(PH_SYMBOL_PROVIDER), PhSymbolProviderType); + memset(symbolProvider, 0, sizeof(PH_SYMBOL_PROVIDER)); + InitializeListHead(&symbolProvider->ModulesListHead); + PhInitializeQueuedLock(&symbolProvider->ModulesListLock); + PhInitializeAvlTree(&symbolProvider->ModulesSet, PhpSymbolModuleCompareFunction); + PhInitializeCallback(&symbolProvider->EventCallback); + PhInitializeInitOnce(&symbolProvider->InitOnce); + + if (ProcessId) + { + static ACCESS_MASK accesses[] = + { + STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff, // pre-Vista full access + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + MAXIMUM_ALLOWED + }; + + ULONG i; + + // Try to open the process with many different accesses. + // This handle will be re-used when walking stacks, and doing various other things. + for (i = 0; i < sizeof(accesses) / sizeof(ACCESS_MASK); i++) + { + if (NT_SUCCESS(PhOpenProcess(&symbolProvider->ProcessHandle, accesses[i], ProcessId))) + { + symbolProvider->IsRealHandle = TRUE; + break; + } + } + } + + if (!symbolProvider->IsRealHandle) + { + HANDLE fakeHandle; + + // Just generate a fake handle. + fakeHandle = (HANDLE)_InterlockedExchangeAddPointer((PLONG_PTR)&PhNextFakeHandle, 4); + // Add one to make sure it isn't divisible by 4 (so it can't be mistaken for a real handle). + symbolProvider->ProcessHandle = (HANDLE)((ULONG_PTR)fakeHandle + 1); + } + + return symbolProvider; +} + +VOID NTAPI PhpSymbolProviderDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)Object; + PLIST_ENTRY listEntry; + + PhDeleteCallback(&symbolProvider->EventCallback); + + if (SymCleanup_I) + { + PH_LOCK_SYMBOLS(); + + if (symbolProvider->IsRegistered) + SymCleanup_I(symbolProvider->ProcessHandle); + + PH_UNLOCK_SYMBOLS(); + } + + listEntry = symbolProvider->ModulesListHead.Flink; + + while (listEntry != &symbolProvider->ModulesListHead) + { + PPH_SYMBOL_MODULE module; + + module = CONTAINING_RECORD(listEntry, PH_SYMBOL_MODULE, ListEntry); + listEntry = listEntry->Flink; + + PhpFreeSymbolModule(module); + } + + if (symbolProvider->IsRealHandle) NtClose(symbolProvider->ProcessHandle); +} + +NTSTATUS PhpSymbolCallbackWorker( + _In_ PVOID Parameter + ) +{ + PPH_SYMBOL_EVENT_DATA data = Parameter; + + dprintf("symbol event %d: %S\n", data->Type, data->FileName->Buffer); + PhInvokeCallback(&data->SymbolProvider->EventCallback, data); + PhClearReference(&data->FileName); + PhDereferenceObject(data); + + return STATUS_SUCCESS; +} + +BOOL CALLBACK PhpSymbolCallbackFunction( + _In_ HANDLE hProcess, + _In_ ULONG ActionCode, + _In_opt_ ULONG64 CallbackData, + _In_opt_ ULONG64 UserContext + ) +{ + PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)UserContext; + PPH_SYMBOL_EVENT_DATA data; + PIMAGEHLP_DEFERRED_SYMBOL_LOADW64 callbackData; + + if (!IsListEmpty(&symbolProvider->EventCallback.ListHead)) + { + switch (ActionCode) + { + case SymbolDeferredSymbolLoadStart: + case SymbolDeferredSymbolLoadComplete: + case SymbolDeferredSymbolLoadFailure: + case SymbolSymbolsUnloaded: + case SymbolDeferredSymbolLoadCancel: + data = PhCreateAlloc(sizeof(PH_SYMBOL_EVENT_DATA)); + memset(data, 0, sizeof(PH_SYMBOL_EVENT_DATA)); + data->SymbolProvider = symbolProvider; + data->Type = ActionCode; + + if (ActionCode != SymbolSymbolsUnloaded) + { + callbackData = (PIMAGEHLP_DEFERRED_SYMBOL_LOADW64)CallbackData; + data->BaseAddress = callbackData->BaseOfImage; + data->CheckSum = callbackData->CheckSum; + data->TimeStamp = callbackData->TimeDateStamp; + data->FileName = PhCreateString(callbackData->FileName); + } + + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhpSymbolCallbackWorker, data); + + break; + } + } + + return FALSE; +} + +VOID PhpRegisterSymbolProvider( + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider + ) +{ + if (PhBeginInitOnce(&PhSymInitOnce)) + { + PhInvokeCallback(&PhSymInitCallback, NULL); + PhEndInitOnce(&PhSymInitOnce); + } + + if (!SymbolProvider) + return; + + if (PhBeginInitOnce(&SymbolProvider->InitOnce)) + { + if (SymInitialize_I) + { + PH_LOCK_SYMBOLS(); + + SymInitialize_I(SymbolProvider->ProcessHandle, NULL, FALSE); + + if (SymRegisterCallbackW64_I) + SymRegisterCallbackW64_I(SymbolProvider->ProcessHandle, PhpSymbolCallbackFunction, (ULONG64)SymbolProvider); + + PH_UNLOCK_SYMBOLS(); + + SymbolProvider->IsRegistered = TRUE; + } + + PhEndInitOnce(&SymbolProvider->InitOnce); + } +} + +VOID PhpFreeSymbolModule( + _In_ PPH_SYMBOL_MODULE SymbolModule + ) +{ + if (SymbolModule->FileName) PhDereferenceObject(SymbolModule->FileName); + + PhFree(SymbolModule); +} + +static LONG NTAPI PhpSymbolModuleCompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PPH_SYMBOL_MODULE symbolModule1 = CONTAINING_RECORD(Links1, PH_SYMBOL_MODULE, Links); + PPH_SYMBOL_MODULE symbolModule2 = CONTAINING_RECORD(Links2, PH_SYMBOL_MODULE, Links); + + return uint64cmp(symbolModule1->BaseAddress, symbolModule2->BaseAddress); +} + +BOOLEAN PhGetLineFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_ PPH_STRING *FileName, + _Out_opt_ PULONG Displacement, + _Out_opt_ PPH_SYMBOL_LINE_INFORMATION Information + ) +{ + IMAGEHLP_LINEW64 line; + BOOL result; + ULONG displacement; + PPH_STRING fileName; + + PhpRegisterSymbolProvider(SymbolProvider); + + if (!SymGetLineFromAddrW64_I && !SymGetLineFromAddr64_I) + return FALSE; + + line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64); + + PH_LOCK_SYMBOLS(); + + if (SymGetLineFromAddrW64_I) + { + result = SymGetLineFromAddrW64_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + &line + ); + + if (result) + fileName = PhCreateString(line.FileName); + } + else + { + IMAGEHLP_LINE64 lineA; + + lineA.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + result = SymGetLineFromAddr64_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + &lineA + ); + + if (result) + { + fileName = PhConvertMultiByteToUtf16(lineA.FileName); + line.LineNumber = lineA.LineNumber; + line.Address = lineA.Address; + } + } + + PH_UNLOCK_SYMBOLS(); + + if (!result) + return FALSE; + + *FileName = fileName; + + if (Displacement) + *Displacement = displacement; + + if (Information) + { + Information->LineNumber = line.LineNumber; + Information->Address = line.Address; + } + + return TRUE; +} + +ULONG64 PhGetModuleFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_opt_ PPH_STRING *FileName + ) +{ + PH_SYMBOL_MODULE lookupModule; + PPH_AVL_LINKS links; + PPH_SYMBOL_MODULE module; + PPH_STRING foundFileName; + ULONG64 foundBaseAddress; + + foundFileName = NULL; + foundBaseAddress = 0; + + PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock); + + // Do an approximate search on the modules set to locate the module with the largest + // base address that is still smaller than the given address. + lookupModule.BaseAddress = Address; + links = PhUpperDualBoundElementAvlTree(&SymbolProvider->ModulesSet, &lookupModule.Links); + + if (links) + { + module = CONTAINING_RECORD(links, PH_SYMBOL_MODULE, Links); + + if (Address < module->BaseAddress + module->Size) + { + PhSetReference(&foundFileName, module->FileName); + foundBaseAddress = module->BaseAddress; + } + } + + PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock); + + if (foundFileName) + { + if (FileName) + { + *FileName = foundFileName; + } + else + { + PhDereferenceObject(foundFileName); + } + } + + return foundBaseAddress; +} + +VOID PhpSymbolInfoAnsiToUnicode( + _Out_ PSYMBOL_INFOW SymbolInfoW, + _In_ PSYMBOL_INFO SymbolInfoA + ) +{ + SymbolInfoW->TypeIndex = SymbolInfoA->TypeIndex; + SymbolInfoW->Index = SymbolInfoA->Index; + SymbolInfoW->Size = SymbolInfoA->Size; + SymbolInfoW->ModBase = SymbolInfoA->ModBase; + SymbolInfoW->Flags = SymbolInfoA->Flags; + SymbolInfoW->Value = SymbolInfoA->Value; + SymbolInfoW->Address = SymbolInfoA->Address; + SymbolInfoW->Register = SymbolInfoA->Register; + SymbolInfoW->Scope = SymbolInfoA->Scope; + SymbolInfoW->Tag = SymbolInfoA->Tag; + SymbolInfoW->NameLen = 0; + + if (SymbolInfoA->NameLen != 0 && SymbolInfoW->MaxNameLen != 0) + { + ULONG copyCount; + + copyCount = min(SymbolInfoA->NameLen, SymbolInfoW->MaxNameLen - 1); + + if (PhCopyStringZFromMultiByte( + SymbolInfoA->Name, + copyCount, + SymbolInfoW->Name, + SymbolInfoW->MaxNameLen, + NULL + )) + { + SymbolInfoW->NameLen = copyCount; + } + } +} + +PPH_STRING PhGetSymbolFromAddress( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address, + _Out_opt_ PPH_SYMBOL_RESOLVE_LEVEL ResolveLevel, + _Out_opt_ PPH_STRING *FileName, + _Out_opt_ PPH_STRING *SymbolName, + _Out_opt_ PULONG64 Displacement + ) +{ + PSYMBOL_INFOW symbolInfo; + ULONG nameLength; + PPH_STRING symbol = NULL; + PH_SYMBOL_RESOLVE_LEVEL resolveLevel; + ULONG64 displacement; + PPH_STRING modFileName = NULL; + PPH_STRING modBaseName = NULL; + ULONG64 modBase; + PPH_STRING symbolName = NULL; + + if (Address == 0) + { + if (ResolveLevel) *ResolveLevel = PhsrlInvalid; + if (FileName) *FileName = NULL; + if (SymbolName) *SymbolName = NULL; + if (Displacement) *Displacement = 0; + + return NULL; + } + + PhpRegisterSymbolProvider(SymbolProvider); + + if (!SymFromAddrW_I && !SymFromAddr_I) + return NULL; + + symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2); + memset(symbolInfo, 0, sizeof(SYMBOL_INFOW)); + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW); + symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN; + + // Get the symbol name. + + PH_LOCK_SYMBOLS(); + + // Note that we don't care whether this call succeeds or not, based on the assumption that it + // will not write to the symbolInfo structure if it fails. We've already zeroed the structure, + // so we can deal with it. + + if (SymFromAddrW_I) + { + SymFromAddrW_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + symbolInfo + ); + nameLength = symbolInfo->NameLen; + + if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN) + { + PhFree(symbolInfo); + symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2); + memset(symbolInfo, 0, sizeof(SYMBOL_INFOW)); + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW); + symbolInfo->MaxNameLen = nameLength + 1; + + SymFromAddrW_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + symbolInfo + ); + } + } + else if (SymFromAddr_I) + { + PSYMBOL_INFO symbolInfoA; + + symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN); + memset(symbolInfoA, 0, sizeof(SYMBOL_INFO)); + symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN; + + SymFromAddr_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + symbolInfoA + ); + nameLength = symbolInfoA->NameLen; + + if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN) + { + PhFree(symbolInfoA); + symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + nameLength + 1); + memset(symbolInfoA, 0, sizeof(SYMBOL_INFO)); + symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfoA->MaxNameLen = nameLength + 1; + + SymFromAddr_I( + SymbolProvider->ProcessHandle, + Address, + &displacement, + symbolInfoA + ); + + // Also reallocate the Unicode-based buffer. + PhFree(symbolInfo); + symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2); + memset(symbolInfo, 0, sizeof(SYMBOL_INFOW)); + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW); + symbolInfo->MaxNameLen = nameLength + 1; + } + + PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA); + PhFree(symbolInfoA); + } + + PH_UNLOCK_SYMBOLS(); + + // Find the module name. + + if (symbolInfo->ModBase == 0) + { + modBase = PhGetModuleFromAddress( + SymbolProvider, + Address, + &modFileName + ); + } + else + { + PH_SYMBOL_MODULE lookupSymbolModule; + PPH_AVL_LINKS existingLinks; + PPH_SYMBOL_MODULE symbolModule; + + lookupSymbolModule.BaseAddress = symbolInfo->ModBase; + + PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock); + + existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links); + + if (existingLinks) + { + symbolModule = CONTAINING_RECORD(existingLinks, PH_SYMBOL_MODULE, Links); + PhSetReference(&modFileName, symbolModule->FileName); + } + + PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock); + } + + // If we don't have a module name, return an address. + if (!modFileName) + { + resolveLevel = PhsrlAddress; + symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2); + PhPrintPointer(symbol->Buffer, (PVOID)Address); + PhTrimToNullTerminatorString(symbol); + + goto CleanupExit; + } + + modBaseName = PhGetBaseName(modFileName); + + // If we have a module name but not a symbol name, return the module plus an offset: + // module+offset. + + if (symbolInfo->NameLen == 0) + { + PH_FORMAT format[3]; + + resolveLevel = PhsrlModule; + + PhInitFormatSR(&format[0], modBaseName->sr); + PhInitFormatS(&format[1], L"+0x"); + PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase)); + symbol = PhFormat(format, 3, modBaseName->Length + 6 + 32); + + goto CleanupExit; + } + + // If we have everything, return the full symbol name: module!symbol+offset. + + symbolName = PhCreateStringEx(symbolInfo->Name, symbolInfo->NameLen * 2); + resolveLevel = PhsrlFunction; + + if (displacement == 0) + { + PH_FORMAT format[3]; + + PhInitFormatSR(&format[0], modBaseName->sr); + PhInitFormatC(&format[1], '!'); + PhInitFormatSR(&format[2], symbolName->sr); + + symbol = PhFormat(format, 3, modBaseName->Length + 2 + symbolName->Length); + } + else + { + PH_FORMAT format[5]; + + PhInitFormatSR(&format[0], modBaseName->sr); + PhInitFormatC(&format[1], '!'); + PhInitFormatSR(&format[2], symbolName->sr); + PhInitFormatS(&format[3], L"+0x"); + PhInitFormatIX(&format[4], (ULONG_PTR)displacement); + + symbol = PhFormat(format, 5, modBaseName->Length + 2 + symbolName->Length + 6 + 32); + } + +CleanupExit: + + if (ResolveLevel) + *ResolveLevel = resolveLevel; + if (FileName) + PhSetReference(FileName, modFileName); + if (SymbolName) + PhSetReference(SymbolName, symbolName); + if (Displacement) + *Displacement = displacement; + + PhClearReference(&modFileName); + PhClearReference(&modBaseName); + PhClearReference(&symbolName); + PhFree(symbolInfo); + + return symbol; +} + +BOOLEAN PhGetSymbolFromName( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR Name, + _Out_ PPH_SYMBOL_INFORMATION Information + ) +{ + PSYMBOL_INFOW symbolInfo; + UCHAR symbolInfoBuffer[FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2]; + BOOL result; + + PhpRegisterSymbolProvider(SymbolProvider); + + if (!SymFromNameW_I && !SymFromName_I) + return FALSE; + + symbolInfo = (PSYMBOL_INFOW)symbolInfoBuffer; + memset(symbolInfo, 0, sizeof(SYMBOL_INFOW)); + symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW); + symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN; + + // Get the symbol information. + + PH_LOCK_SYMBOLS(); + + if (SymFromNameW_I) + { + result = SymFromNameW_I( + SymbolProvider->ProcessHandle, + Name, + symbolInfo + ); + } + else if (SymFromName_I) + { + UCHAR buffer[FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN]; + PSYMBOL_INFO symbolInfoA; + PPH_BYTES name; + + symbolInfoA = (PSYMBOL_INFO)buffer; + memset(symbolInfoA, 0, sizeof(SYMBOL_INFO)); + symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO); + symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN; + + name = PhConvertUtf16ToMultiByte(Name); + + if (result = SymFromName_I( + SymbolProvider->ProcessHandle, + name->Buffer, + symbolInfoA + )) + { + PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA); + } + + PhDereferenceObject(name); + } + else + { + result = FALSE; + } + + PH_UNLOCK_SYMBOLS(); + + if (!result) + return FALSE; + + Information->Address = symbolInfo->Address; + Information->ModuleBase = symbolInfo->ModBase; + Information->Index = symbolInfo->Index; + Information->Size = symbolInfo->Size; + + return TRUE; +} + +BOOLEAN PhLoadModuleSymbolProvider( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR FileName, + _In_ ULONG64 BaseAddress, + _In_ ULONG Size + ) +{ + ULONG64 baseAddress; + PPH_SYMBOL_MODULE symbolModule = NULL; + PPH_AVL_LINKS existingLinks; + PH_SYMBOL_MODULE lookupSymbolModule; + + PhpRegisterSymbolProvider(SymbolProvider); + + if (!SymLoadModuleExW_I && !SymLoadModule64_I) + return FALSE; + + // Check for duplicates. It is better to do this before calling SymLoadModuleExW, because it + // seems to force symbol loading when it is called twice on the same module even if deferred + // loading is enabled. + PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock); + lookupSymbolModule.BaseAddress = BaseAddress; + existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links); + PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock); + + if (existingLinks) + return TRUE; + + PH_LOCK_SYMBOLS(); + + if (SymLoadModuleExW_I) + { + baseAddress = SymLoadModuleExW_I( + SymbolProvider->ProcessHandle, + NULL, + FileName, + NULL, + BaseAddress, + Size, + NULL, + 0 + ); + } + else + { + PPH_BYTES fileName; + + fileName = PhConvertUtf16ToMultiByte(FileName); + baseAddress = SymLoadModule64_I( + SymbolProvider->ProcessHandle, + NULL, + fileName->Buffer, + NULL, + BaseAddress, + Size + ); + PhDereferenceObject(fileName); + } + + PH_UNLOCK_SYMBOLS(); + + // Add the module to the list, even if we couldn't load symbols for the module. + + PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock); + + // Check for duplicates again. + lookupSymbolModule.BaseAddress = BaseAddress; + existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links); + + if (!existingLinks) + { + symbolModule = PhAllocate(sizeof(PH_SYMBOL_MODULE)); + symbolModule->BaseAddress = BaseAddress; + symbolModule->Size = Size; + symbolModule->FileName = PhGetFullPath(FileName, &symbolModule->BaseNameIndex); + + existingLinks = PhAddElementAvlTree(&SymbolProvider->ModulesSet, &symbolModule->Links); + assert(!existingLinks); + InsertTailList(&SymbolProvider->ModulesListHead, &symbolModule->ListEntry); + } + + PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock); + + if (!baseAddress) + { + if (GetLastError() != ERROR_SUCCESS) + return FALSE; + else + return TRUE; + } + + return TRUE; +} + +VOID PhSetOptionsSymbolProvider( + _In_ ULONG Mask, + _In_ ULONG Value + ) +{ + ULONG options; + + PhpRegisterSymbolProvider(NULL); + + if (!SymGetOptions_I || !SymSetOptions_I) + return; + + PH_LOCK_SYMBOLS(); + + options = SymGetOptions_I(); + options &= ~Mask; + options |= Value; + SymSetOptions_I(options); + + PH_UNLOCK_SYMBOLS(); +} + +VOID PhSetSearchPathSymbolProvider( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ PWSTR Path + ) +{ + PhpRegisterSymbolProvider(SymbolProvider); + + if (!SymSetSearchPathW_I && !SymSetSearchPath_I) + return; + + PH_LOCK_SYMBOLS(); + + if (SymSetSearchPathW_I) + { + SymSetSearchPathW_I(SymbolProvider->ProcessHandle, Path); + } + else if (SymSetSearchPath_I) + { + PPH_BYTES path; + + path = PhConvertUtf16ToMultiByte(Path); + SymSetSearchPath_I(SymbolProvider->ProcessHandle, path->Buffer); + PhDereferenceObject(path); + } + + PH_UNLOCK_SYMBOLS(); +} + +#ifdef _WIN64 + +NTSTATUS PhpLookupDynamicFunctionTable( + _In_ HANDLE ProcessHandle, + _In_ ULONG64 Address, + _Out_opt_ PDYNAMIC_FUNCTION_TABLE *FunctionTableAddress, + _Out_opt_ PDYNAMIC_FUNCTION_TABLE FunctionTable, + _Out_writes_bytes_opt_(OutOfProcessCallbackDllBufferSize) PWCHAR OutOfProcessCallbackDllBuffer, + _In_ ULONG OutOfProcessCallbackDllBufferSize, + _Out_opt_ PUNICODE_STRING OutOfProcessCallbackDllString + ) +{ + NTSTATUS status; + PLIST_ENTRY (NTAPI *rtlGetFunctionTableListHead)(VOID); + PLIST_ENTRY tableListHead; + LIST_ENTRY tableListHeadEntry; + PLIST_ENTRY tableListEntry; + PDYNAMIC_FUNCTION_TABLE functionTableAddress; + DYNAMIC_FUNCTION_TABLE functionTable; + ULONG count; + SIZE_T numberOfBytesRead; + ULONG i; + BOOLEAN foundNull; + + rtlGetFunctionTableListHead = PhGetModuleProcAddress(L"ntdll.dll", "RtlGetFunctionTableListHead"); + + if (!rtlGetFunctionTableListHead) + return STATUS_PROCEDURE_NOT_FOUND; + + tableListHead = rtlGetFunctionTableListHead(); + + // Find the function table entry for this address. + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + tableListHead, + &tableListHeadEntry, + sizeof(LIST_ENTRY), + NULL + ))) + return status; + + tableListEntry = tableListHeadEntry.Flink; + count = 0; // make sure we can't be forced into an infinite loop by crafted data + + while (tableListEntry != tableListHead && count < PH_ENUM_PROCESS_MODULES_LIMIT) + { + functionTableAddress = CONTAINING_RECORD(tableListEntry, DYNAMIC_FUNCTION_TABLE, ListEntry); + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + ProcessHandle, + functionTableAddress, + &functionTable, + sizeof(DYNAMIC_FUNCTION_TABLE), + NULL + ))) + return status; + + if (Address >= functionTable.MinimumAddress && Address < functionTable.MaximumAddress) + { + if (OutOfProcessCallbackDllBuffer) + { + if (functionTable.OutOfProcessCallbackDll) + { + // Read the out-of-process callback DLL path. We don't have a length, so we'll + // just have to read as much as possible. + + memset(OutOfProcessCallbackDllBuffer, 0xff, OutOfProcessCallbackDllBufferSize); + status = NtReadVirtualMemory( + ProcessHandle, + functionTable.OutOfProcessCallbackDll, + OutOfProcessCallbackDllBuffer, + OutOfProcessCallbackDllBufferSize, + &numberOfBytesRead + ); + + if (status != STATUS_PARTIAL_COPY && !NT_SUCCESS(status)) + return status; + + foundNull = FALSE; + + for (i = 0; i < OutOfProcessCallbackDllBufferSize / sizeof(WCHAR); i++) + { + if (OutOfProcessCallbackDllBuffer[i] == 0) + { + foundNull = TRUE; + + if (OutOfProcessCallbackDllString) + { + OutOfProcessCallbackDllString->Buffer = OutOfProcessCallbackDllBuffer; + OutOfProcessCallbackDllString->Length = (USHORT)(i * sizeof(WCHAR)); + OutOfProcessCallbackDllString->MaximumLength = OutOfProcessCallbackDllString->Length; + } + + break; + } + } + + // If there was no null terminator, then we didn't read the whole string in. + // Fail the operation. + if (!foundNull) + return STATUS_BUFFER_OVERFLOW; + } + else + { + OutOfProcessCallbackDllBuffer[0] = 0; + + if (OutOfProcessCallbackDllString) + { + OutOfProcessCallbackDllString->Buffer = NULL; + OutOfProcessCallbackDllString->Length = 0; + OutOfProcessCallbackDllString->MaximumLength = 0; + } + } + } + + if (FunctionTableAddress) + *FunctionTableAddress = functionTableAddress; + + if (FunctionTable) + *FunctionTable = functionTable; + + return STATUS_SUCCESS; + } + + tableListEntry = functionTable.ListEntry.Flink; + count++; + } + + return STATUS_NOT_FOUND; +} + +PRUNTIME_FUNCTION PhpLookupFunctionEntry( + _In_ PRUNTIME_FUNCTION Functions, + _In_ ULONG NumberOfFunctions, + _In_ BOOLEAN Sorted, + _In_ ULONG64 RelativeControlPc + ) +{ + LONG low; + LONG high; + ULONG i; + + if (Sorted) + { + if (NumberOfFunctions == 0) + return NULL; + + low = 0; + high = NumberOfFunctions - 1; + + do + { + i = (low + high) / 2; + + if (RelativeControlPc < Functions[i].BeginAddress) + high = i - 1; + else if (RelativeControlPc >= Functions[i].EndAddress) + low = i + 1; + else + return &Functions[i]; + } while (low <= high); + } + else + { + for (i = 0; i < NumberOfFunctions; i++) + { + if (RelativeControlPc >= Functions[i].BeginAddress && RelativeControlPc < Functions[i].EndAddress) + return &Functions[i]; + } + } + + return NULL; +} + +NTSTATUS PhpAccessCallbackFunctionTable( + _In_ HANDLE ProcessHandle, + _In_ PVOID FunctionTableAddress, + _In_ PUNICODE_STRING OutOfProcessCallbackDllString, + _Out_ PRUNTIME_FUNCTION *Functions, + _Out_ PULONG NumberOfFunctions + ) +{ + static PH_STRINGREF knownFunctionTableDllsKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\KnownFunctionTableDlls"); + + NTSTATUS status; + HANDLE keyHandle; + ULONG returnLength; + PVOID dllHandle; + ANSI_STRING outOfProcessFunctionTableCallbackName; + POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK outOfProcessFunctionTableCallback; + + if (!OutOfProcessCallbackDllString->Buffer) + return STATUS_INVALID_PARAMETER; + + // Don't load DLLs from arbitrary locations. Check if this is a known function table DLL. + + if (!NT_SUCCESS(status = PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &knownFunctionTableDllsKeyName, + 0 + ))) + return status; + + status = NtQueryValueKey(keyHandle, OutOfProcessCallbackDllString, KeyValuePartialInformation, NULL, 0, &returnLength); + NtClose(keyHandle); + + if (status == STATUS_OBJECT_NAME_NOT_FOUND) + return STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT; + + status = LdrLoadDll(NULL, NULL, OutOfProcessCallbackDllString, &dllHandle); + + if (!NT_SUCCESS(status)) + return status; + + RtlInitAnsiString(&outOfProcessFunctionTableCallbackName, OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME); + status = LdrGetProcedureAddress(dllHandle, &outOfProcessFunctionTableCallbackName, 0, (PVOID *)&outOfProcessFunctionTableCallback); + + if (NT_SUCCESS(status)) + { + status = outOfProcessFunctionTableCallback( + ProcessHandle, + FunctionTableAddress, + NumberOfFunctions, + Functions + ); + } + + LdrUnloadDll(dllHandle); + + return status; +} + +NTSTATUS PhpAccessNormalFunctionTable( + _In_ HANDLE ProcessHandle, + _In_ PDYNAMIC_FUNCTION_TABLE FunctionTable, + _Out_ PRUNTIME_FUNCTION *Functions, + _Out_ PULONG NumberOfFunctions + ) +{ + NTSTATUS status; + SIZE_T bufferSize; + PRUNTIME_FUNCTION functions; + + // Put a reasonable limit on the number of entries we read. + if (FunctionTable->EntryCount > 0x100000) + return STATUS_BUFFER_OVERFLOW; + + bufferSize = FunctionTable->EntryCount * sizeof(RUNTIME_FUNCTION); + functions = PhAllocatePage(bufferSize, NULL); + + if (!functions) + return STATUS_NO_MEMORY; + + status = NtReadVirtualMemory(ProcessHandle, FunctionTable->FunctionTable, functions, bufferSize, NULL); + + if (NT_SUCCESS(status)) + { + *Functions = functions; + *NumberOfFunctions = FunctionTable->EntryCount; + } + else + { + PhFreePage(functions); + } + + return status; +} + +NTSTATUS PhAccessOutOfProcessFunctionEntry( + _In_ HANDLE ProcessHandle, + _In_ ULONG64 ControlPc, + _Out_ PRUNTIME_FUNCTION Function + ) +{ + NTSTATUS status; + PDYNAMIC_FUNCTION_TABLE functionTableAddress; + DYNAMIC_FUNCTION_TABLE functionTable; + WCHAR outOfProcessCallbackDll[512]; + UNICODE_STRING outOfProcessCallbackDllString; + PRUNTIME_FUNCTION functions; + ULONG numberOfFunctions; + PRUNTIME_FUNCTION function; + + if (!NT_SUCCESS(status = PhpLookupDynamicFunctionTable( + ProcessHandle, + ControlPc, + &functionTableAddress, + &functionTable, + outOfProcessCallbackDll, + sizeof(outOfProcessCallbackDll), + &outOfProcessCallbackDllString + ))) + { + return status; + } + + if (functionTable.Type == RF_CALLBACK) + { + if (!NT_SUCCESS(status = PhpAccessCallbackFunctionTable( + ProcessHandle, + functionTableAddress, + &outOfProcessCallbackDllString, + &functions, + &numberOfFunctions + ))) + { + return status; + } + + function = PhpLookupFunctionEntry(functions, numberOfFunctions, FALSE, ControlPc - functionTable.BaseAddress); + + if (function) + *Function = *function; + else + status = STATUS_NOT_FOUND; + + RtlFreeHeap(RtlProcessHeap(), 0, functions); + } + else + { + if (!NT_SUCCESS(status = PhpAccessNormalFunctionTable( + ProcessHandle, + &functionTable, + &functions, + &numberOfFunctions + ))) + { + return status; + } + + function = PhpLookupFunctionEntry(functions, numberOfFunctions, functionTable.Type == RF_SORTED, ControlPc - functionTable.BaseAddress); + + if (function) + *Function = *function; + else + status = STATUS_NOT_FOUND; + + PhFreePage(functions); + } + + return status; +} + +#endif + +ULONG64 __stdcall PhGetModuleBase64( + _In_ HANDLE hProcess, + _In_ DWORD64 dwAddr + ) +{ + ULONG64 base; +#ifdef _WIN64 + DYNAMIC_FUNCTION_TABLE functionTable; +#endif + +#ifdef _WIN64 + if (NT_SUCCESS(PhpLookupDynamicFunctionTable( + hProcess, + dwAddr, + NULL, + &functionTable, + NULL, + 0, + NULL + ))) + { + base = functionTable.BaseAddress; + } + else + { + base = 0; + } +#else + base = 0; +#endif + + if (base == 0 && SymGetModuleBase64_I) + base = SymGetModuleBase64_I(hProcess, dwAddr); + + return base; +} + +PVOID __stdcall PhFunctionTableAccess64( + _In_ HANDLE hProcess, + _In_ DWORD64 AddrBase + ) +{ +#ifdef _WIN64 + static RUNTIME_FUNCTION lastRuntimeFunction; +#endif + + PVOID entry; + +#ifdef _WIN64 + if (NT_SUCCESS(PhAccessOutOfProcessFunctionEntry(hProcess, AddrBase, &lastRuntimeFunction))) + entry = &lastRuntimeFunction; + else + entry = NULL; +#else + entry = NULL; +#endif + + if (!entry && SymFunctionTableAccess64_I) + entry = SymFunctionTableAccess64_I(hProcess, AddrBase); + + return entry; +} + +BOOLEAN PhStackWalk( + _In_ ULONG MachineType, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ThreadHandle, + _Inout_ LPSTACKFRAME64 StackFrame, + _Inout_ PVOID ContextRecord, + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ) +{ + BOOLEAN result; + + PhpRegisterSymbolProvider(SymbolProvider); + + if (!StackWalk64_I) + return FALSE; + + if (!FunctionTableAccessRoutine) + { + if (MachineType == IMAGE_FILE_MACHINE_AMD64) + FunctionTableAccessRoutine = PhFunctionTableAccess64; + else + FunctionTableAccessRoutine = SymFunctionTableAccess64_I; + } + + if (!GetModuleBaseRoutine) + { + if (MachineType == IMAGE_FILE_MACHINE_AMD64) + GetModuleBaseRoutine = PhGetModuleBase64; + else + GetModuleBaseRoutine = SymGetModuleBase64_I; + } + + PH_LOCK_SYMBOLS(); + result = StackWalk64_I( + MachineType, + ProcessHandle, + ThreadHandle, + StackFrame, + ContextRecord, + ReadMemoryRoutine, + FunctionTableAccessRoutine, + GetModuleBaseRoutine, + TranslateAddress + ); + PH_UNLOCK_SYMBOLS(); + + return result; +} + +BOOLEAN PhWriteMiniDumpProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _In_ HANDLE FileHandle, + _In_ MINIDUMP_TYPE DumpType, + _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ) +{ + PhpRegisterSymbolProvider(NULL); + + if (!MiniDumpWriteDump_I) + { + SetLastError(ERROR_PROC_NOT_FOUND); + return FALSE; + } + + return MiniDumpWriteDump_I( + ProcessHandle, + HandleToUlong(ProcessId), + FileHandle, + DumpType, + ExceptionParam, + UserStreamParam, + CallbackParam + ); +} + +/** + * Converts a STACKFRAME64 structure to a PH_THREAD_STACK_FRAME structure. + * + * \param StackFrame64 A pointer to the STACKFRAME64 structure to convert. + * \param Flags Flags to set in the resulting structure. + * \param ThreadStackFrame A pointer to the resulting PH_THREAD_STACK_FRAME structure. + */ +VOID PhpConvertStackFrame( + _In_ STACKFRAME64 *StackFrame64, + _In_ ULONG Flags, + _Out_ PPH_THREAD_STACK_FRAME ThreadStackFrame + ) +{ + ULONG i; + + ThreadStackFrame->PcAddress = (PVOID)StackFrame64->AddrPC.Offset; + ThreadStackFrame->ReturnAddress = (PVOID)StackFrame64->AddrReturn.Offset; + ThreadStackFrame->FrameAddress = (PVOID)StackFrame64->AddrFrame.Offset; + ThreadStackFrame->StackAddress = (PVOID)StackFrame64->AddrStack.Offset; + ThreadStackFrame->BStoreAddress = (PVOID)StackFrame64->AddrBStore.Offset; + + for (i = 0; i < 4; i++) + ThreadStackFrame->Params[i] = (PVOID)StackFrame64->Params[i]; + + ThreadStackFrame->Flags = Flags; + + if (StackFrame64->FuncTableEntry) + ThreadStackFrame->Flags |= PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT; +} + +/** + * Walks a thread's stack. + * + * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION, + * THREAD_GET_CONTEXT and THREAD_SUSPEND_RESUME access. The handle can have any access for kernel + * stack walking. + * \param ProcessHandle A handle to the thread's parent process. The handle must have + * PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access. If a symbol provider is being used, pass + * its process handle and specify the symbol provider in \a SymbolProvider. + * \param ClientId The client ID identifying the thread. + * \param SymbolProvider The associated symbol provider. + * \param Flags A combination of flags. + * \li \c PH_WALK_I386_STACK Walks the x86 stack. On AMD64 systems this flag walks the WOW64 stack. + * \li \c PH_WALK_AMD64_STACK Walks the AMD64 stack. On x86 systems this flag is ignored. + * \li \c PH_WALK_KERNEL_STACK Walks the kernel stack. This flag is ignored if there is no active + * KProcessHacker connection. + * \param Callback A callback function which is executed for each stack frame. + * \param Context A user-defined value to pass to the callback function. + */ +NTSTATUS PhWalkThreadStack( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE ProcessHandle, + _In_opt_ PCLIENT_ID ClientId, + _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG Flags, + _In_ PPH_WALK_THREAD_STACK_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN suspended = FALSE; + BOOLEAN processOpened = FALSE; + BOOLEAN isCurrentThread = FALSE; + BOOLEAN isSystemThread = FALSE; + THREAD_BASIC_INFORMATION basicInfo; + + // Open a handle to the process if we weren't given one. + if (!ProcessHandle) + { + if (KphIsConnected() || !ClientId) + { + if (!NT_SUCCESS(status = PhOpenThreadProcess( + ThreadHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + &ProcessHandle + ))) + return status; + } + else + { + if (!NT_SUCCESS(status = PhOpenProcess( + &ProcessHandle, + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + ClientId->UniqueProcess + ))) + return status; + } + + processOpened = TRUE; + } + + // Determine if the caller specified the current thread. + if (ClientId) + { + if (ClientId->UniqueThread == NtCurrentTeb()->ClientId.UniqueThread) + isCurrentThread = TRUE; + if (ClientId->UniqueProcess == SYSTEM_PROCESS_ID) + isSystemThread = TRUE; + } + else + { + if (ThreadHandle == NtCurrentThread()) + { + isCurrentThread = TRUE; + } + else if (NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo))) + { + if (basicInfo.ClientId.UniqueThread == NtCurrentTeb()->ClientId.UniqueThread) + isCurrentThread = TRUE; + if (basicInfo.ClientId.UniqueProcess == SYSTEM_PROCESS_ID) + isSystemThread = TRUE; + } + } + + // Make sure this isn't a kernel-mode thread. + if (!isSystemThread) + { + PVOID startAddress; + + if (NT_SUCCESS(NtQueryInformationThread(ThreadHandle, ThreadQuerySetWin32StartAddress, + &startAddress, sizeof(PVOID), NULL))) + { + if ((ULONG_PTR)startAddress > PhSystemBasicInformation.MaximumUserModeAddress) + isSystemThread = TRUE; + } + } + + // Suspend the thread to avoid inaccurate results. Don't suspend if we're walking the stack of + // the current thread or this is a kernel-mode thread. + if (!isCurrentThread && !isSystemThread) + { + if (NT_SUCCESS(NtSuspendThread(ThreadHandle, NULL))) + suspended = TRUE; + } + + // Kernel stack walk. + if ((Flags & PH_WALK_KERNEL_STACK) && KphIsConnected()) + { + PVOID stack[256 - 2]; // See MAX_STACK_DEPTH + ULONG capturedFrames; + ULONG i; + + if (NT_SUCCESS(KphCaptureStackBackTraceThread( + ThreadHandle, + 1, + sizeof(stack) / sizeof(PVOID), + stack, + &capturedFrames, + NULL + ))) + { + PH_THREAD_STACK_FRAME threadStackFrame; + + memset(&threadStackFrame, 0, sizeof(PH_THREAD_STACK_FRAME)); + + for (i = 0; i < capturedFrames; i++) + { + threadStackFrame.PcAddress = stack[i]; + threadStackFrame.Flags = PH_THREAD_STACK_FRAME_KERNEL; + + if (!Callback(&threadStackFrame, Context)) + { + goto ResumeExit; + } + } + } + } + +#ifdef _WIN64 + if (Flags & PH_WALK_AMD64_STACK) + { + STACKFRAME64 stackFrame; + PH_THREAD_STACK_FRAME threadStackFrame; + CONTEXT context; + + context.ContextFlags = CONTEXT_ALL; + + if (!NT_SUCCESS(status = NtGetContextThread( + ThreadHandle, + &context + ))) + goto SkipAmd64Stack; + + memset(&stackFrame, 0, sizeof(STACKFRAME64)); + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrPC.Offset = context.Rip; + stackFrame.AddrStack.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Rsp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Rbp; + + while (TRUE) + { + if (!PhStackWalk( + IMAGE_FILE_MACHINE_AMD64, + ProcessHandle, + ThreadHandle, + &stackFrame, + &context, + SymbolProvider, + NULL, + NULL, + NULL, + NULL + )) + break; + + // If we have an invalid instruction pointer, break. + if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1) + break; + + // Convert the stack frame and execute the callback. + + PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_AMD64, &threadStackFrame); + + if (!Callback(&threadStackFrame, Context)) + goto ResumeExit; + } + } + +SkipAmd64Stack: +#endif + + // x86/WOW64 stack walk. + if (Flags & PH_WALK_I386_STACK) + { + STACKFRAME64 stackFrame; + PH_THREAD_STACK_FRAME threadStackFrame; +#ifndef _WIN64 + CONTEXT context; + + context.ContextFlags = CONTEXT_ALL; + + if (!NT_SUCCESS(status = NtGetContextThread( + ThreadHandle, + &context + ))) + goto SkipI386Stack; +#else + WOW64_CONTEXT context; + + context.ContextFlags = WOW64_CONTEXT_ALL; + + if (!NT_SUCCESS(status = NtQueryInformationThread( + ThreadHandle, + ThreadWow64Context, + &context, + sizeof(WOW64_CONTEXT), + NULL + ))) + goto SkipI386Stack; +#endif + + memset(&stackFrame, 0, sizeof(STACKFRAME64)); + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrPC.Offset = context.Eip; + stackFrame.AddrStack.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Esp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Ebp; + + while (TRUE) + { + if (!PhStackWalk( + IMAGE_FILE_MACHINE_I386, + ProcessHandle, + ThreadHandle, + &stackFrame, + &context, + SymbolProvider, + NULL, + NULL, + NULL, + NULL + )) + break; + + // If we have an invalid instruction pointer, break. + if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1) + break; + + // Convert the stack frame and execute the callback. + + PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_I386, &threadStackFrame); + + if (!Callback(&threadStackFrame, Context)) + goto ResumeExit; + + // (x86 only) Allow the user to change Eip, Esp and Ebp. + context.Eip = PtrToUlong(threadStackFrame.PcAddress); + stackFrame.AddrPC.Offset = PtrToUlong(threadStackFrame.PcAddress); + context.Ebp = PtrToUlong(threadStackFrame.FrameAddress); + stackFrame.AddrFrame.Offset = PtrToUlong(threadStackFrame.FrameAddress); + context.Esp = PtrToUlong(threadStackFrame.StackAddress); + stackFrame.AddrStack.Offset = PtrToUlong(threadStackFrame.StackAddress); + } + } + +SkipI386Stack: + +ResumeExit: + if (suspended) + NtResumeThread(ThreadHandle, NULL); + + if (processOpened) + NtClose(ProcessHandle); + + return status; +} diff --git a/phlib/sync.c b/phlib/sync.c new file mode 100644 index 0000000..f624bd0 --- /dev/null +++ b/phlib/sync.c @@ -0,0 +1,496 @@ +/* + * Process Hacker - + * misc. synchronization utilities + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * This file contains code for several synchronization objects. + * + * Event. This is a lightweight notification event object that does not create a kernel event object + * until needed. Additionally the kernel event object is automatically freed when no longer needed. + * Note that PhfResetEvent is NOT thread-safe. + * + * Barrier. This is a non-traditional implementation of a barrier, built on the wake event object. I + * have identified three types of participants in this process: + * 1. The slaves, who wait for the master to release them. This is the main mechanism through which + * the threads are synchronized. + * 2. The master, who is the last thread to wait on the barrier. This thread triggers the waking + * process, and waits until all slaves have woken. + * 3. The observers, who are simply threads which were slaves before, were woken, and have tried to + * wait on the barrier again before all other slaves have woken. + * + * Rundown protection. This object allows a thread to wait until all other threads have finished + * using a particular resource before freeing the resource. + * + * Init-once. This is a lightweight one-time initialization mechanism which uses the event object + * for any required blocking. The overhead is very small - only a single inlined comparison. + */ + +#include + +/** + * Initializes an event object. + * + * \param Event A pointer to an event object. + */ +VOID FASTCALL PhfInitializeEvent( + _Out_ PPH_EVENT Event + ) +{ + Event->Value = PH_EVENT_REFCOUNT_INC; + Event->EventHandle = NULL; +} + +/** + * Dereferences the event object used by an event. + * + * \param Event A pointer to an event object. + * \param EventHandle The current value of the event object. + */ +FORCEINLINE VOID PhpDereferenceEvent( + _Inout_ PPH_EVENT Event, + _In_opt_ HANDLE EventHandle + ) +{ + ULONG_PTR value; + + value = _InterlockedExchangeAddPointer((PLONG_PTR)&Event->Value, -PH_EVENT_REFCOUNT_INC); + + // See if the reference count has become 0. + if (((value >> PH_EVENT_REFCOUNT_SHIFT) & PH_EVENT_REFCOUNT_MASK) - 1 == 0) + { + if (EventHandle) + { + NtClose(EventHandle); + Event->EventHandle = NULL; + } + } +} + +/** + * References the event object used by an event. + * + * \param Event A pointer to an event object. + */ +FORCEINLINE VOID PhpReferenceEvent( + _Inout_ PPH_EVENT Event + ) +{ + _InterlockedExchangeAddPointer((PLONG_PTR)&Event->Value, PH_EVENT_REFCOUNT_INC); +} + +/** + * Sets an event object. Any threads waiting on the event will be released. + * + * \param Event A pointer to an event object. + */ +VOID FASTCALL PhfSetEvent( + _Inout_ PPH_EVENT Event + ) +{ + HANDLE eventHandle; + + // Only proceed if the event isn't set already. + if (!_InterlockedBitTestAndSetPointer((PLONG_PTR)&Event->Value, PH_EVENT_SET_SHIFT)) + { + eventHandle = Event->EventHandle; + + if (eventHandle) + { + NtSetEvent(eventHandle, NULL); + } + + PhpDereferenceEvent(Event, eventHandle); + } +} + +/** + * Waits for an event object to be set. + * + * \param Event A pointer to an event object. + * \param Timeout The timeout value. + * + * \return TRUE if the event object was set before the timeout period expired, otherwise FALSE. + * + * \remarks To test the event, use PhTestEvent() instead of using a timeout of zero. + */ +BOOLEAN FASTCALL PhfWaitForEvent( + _Inout_ PPH_EVENT Event, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + BOOLEAN result; + ULONG_PTR value; + HANDLE eventHandle; + + value = Event->Value; + + // Shortcut: if the event is set, return immediately. + if (value & PH_EVENT_SET) + return TRUE; + + // Shortcut: if the timeout is 0, return immediately if the event isn't set. + if (Timeout && Timeout->QuadPart == 0) + return FALSE; + + // Prevent the event from being invalidated. + PhpReferenceEvent(Event); + + eventHandle = Event->EventHandle; + + if (!eventHandle) + { + NtCreateEvent(&eventHandle, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + assert(eventHandle); + + // Try to set the event handle to our event. + if (_InterlockedCompareExchangePointer( + &Event->EventHandle, + eventHandle, + NULL + ) != NULL) + { + // Someone else set the event before we did. + NtClose(eventHandle); + eventHandle = Event->EventHandle; + } + } + + // Essential: check the event one last time to see if it is set. + if (!(Event->Value & PH_EVENT_SET)) + { + result = NtWaitForSingleObject(eventHandle, FALSE, Timeout) == STATUS_WAIT_0; + } + else + { + result = TRUE; + } + + PhpDereferenceEvent(Event, eventHandle); + + return result; +} + +/** + * Resets an event's state. + * + * \param Event A pointer to an event object. + * + * \remarks This function is not thread-safe. Make sure no other threads are using the event when + * you call this function. + */ +VOID FASTCALL PhfResetEvent( + _Inout_ PPH_EVENT Event + ) +{ + assert(!Event->EventHandle); + + if (PhTestEvent(Event)) + Event->Value = PH_EVENT_REFCOUNT_INC; +} + +VOID FASTCALL PhfInitializeBarrier( + _Out_ PPH_BARRIER Barrier, + _In_ ULONG_PTR Target + ) +{ + Barrier->Value = Target << PH_BARRIER_TARGET_SHIFT; + PhInitializeWakeEvent(&Barrier->WakeEvent); +} + +FORCEINLINE VOID PhpBlockOnBarrier( + _Inout_ PPH_BARRIER Barrier, + _In_ ULONG Role, + _In_ BOOLEAN Spin + ) +{ + PH_QUEUED_WAIT_BLOCK waitBlock; + ULONG_PTR cancel; + + PhQueueWakeEvent(&Barrier->WakeEvent, &waitBlock); + + cancel = 0; + + switch (Role) + { + case PH_BARRIER_MASTER: + cancel = ((Barrier->Value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) == 1; + break; + case PH_BARRIER_SLAVE: + cancel = Barrier->Value & PH_BARRIER_WAKING; + break; + case PH_BARRIER_OBSERVER: + cancel = !(Barrier->Value & PH_BARRIER_WAKING); + break; + default: + ASSUME_NO_DEFAULT; + } + + if (cancel) + { + PhSetWakeEvent(&Barrier->WakeEvent, &waitBlock); + return; + } + + PhWaitForWakeEvent(&Barrier->WakeEvent, &waitBlock, Spin, NULL); +} + +/** + * Waits until all threads are blocking on the barrier, and resets the state of the barrier. + * + * \param Barrier A barrier. + * \param Spin TRUE to spin on the barrier before blocking, FALSE to block immediately. + * + * \return TRUE for an unspecified thread after each phase, and FALSE for all other threads. + * + * \remarks By checking the return value of the function, in each phase an action can be performed + * exactly once. This could, for example, involve merging the results of calculations. + */ +BOOLEAN FASTCALL PhfWaitForBarrier( + _Inout_ PPH_BARRIER Barrier, + _In_ BOOLEAN Spin + ) +{ + ULONG_PTR value; + ULONG_PTR newValue; + ULONG_PTR count; + ULONG_PTR target; + + value = Barrier->Value; + + while (TRUE) + { + if (!(value & PH_BARRIER_WAKING)) + { + count = (value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK; + target = (value >> PH_BARRIER_TARGET_SHIFT) & PH_BARRIER_TARGET_MASK; + assert(count != target); + + count++; + + if (count != target) + newValue = value + PH_BARRIER_COUNT_INC; + else + newValue = value + PH_BARRIER_COUNT_INC + PH_BARRIER_WAKING; + + if ((newValue = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Barrier->Value, + (PVOID)newValue, + (PVOID)value + )) == value) + { + if (count != target) + { + // Wait for the master signal (the last thread to reach the barrier). + // Once we get it, decrement the count to allow the master to continue. + + do + { + PhpBlockOnBarrier(Barrier, PH_BARRIER_SLAVE, Spin); + } while (!(Barrier->Value & PH_BARRIER_WAKING)); + + value = _InterlockedExchangeAddPointer((PLONG_PTR)&Barrier->Value, -PH_BARRIER_COUNT_INC); + + if (((value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) - 1 == 1) + { + PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for the master + } + + return FALSE; + } + else + { + // We're the last one to reach the barrier, so we become the master. + // Wake the slaves and wait for them to decrease the count to 1. This is so that + // we know the slaves have woken and we don't clear the waking bit before they + // wake. + + PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for slaves + + do + { + PhpBlockOnBarrier(Barrier, PH_BARRIER_MASTER, Spin); + } while (((Barrier->Value >> PH_BARRIER_COUNT_SHIFT) & PH_BARRIER_COUNT_MASK) != 1); + + _InterlockedExchangeAddPointer((PLONG_PTR)&Barrier->Value, -(PH_BARRIER_WAKING + PH_BARRIER_COUNT_INC)); + PhSetWakeEvent(&Barrier->WakeEvent, NULL); // for observers + + return TRUE; + } + } + } + else + { + // We're too early; other threads are still waking. Wait for them to finish. + + PhpBlockOnBarrier(Barrier, PH_BARRIER_OBSERVER, Spin); + newValue = Barrier->Value; + } + + value = newValue; + } +} + +VOID FASTCALL PhfInitializeRundownProtection( + _Out_ PPH_RUNDOWN_PROTECT Protection + ) +{ + Protection->Value = 0; +} + +BOOLEAN FASTCALL PhfAcquireRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + + // Increment the reference count only if rundown has not started. + + while (TRUE) + { + value = Protection->Value; + + if (value & PH_RUNDOWN_ACTIVE) + return FALSE; + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)(value + PH_RUNDOWN_REF_INC), + (PVOID)value + ) == value) + return TRUE; + } +} + +VOID FASTCALL PhfReleaseRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + + while (TRUE) + { + value = Protection->Value; + + if (value & PH_RUNDOWN_ACTIVE) + { + PPH_RUNDOWN_WAIT_BLOCK waitBlock; + + // Since rundown is active, the reference count has been moved to the waiter's wait + // block. If we are the last user, we must wake up the waiter. + + waitBlock = (PPH_RUNDOWN_WAIT_BLOCK)(value & ~PH_RUNDOWN_ACTIVE); + + if (_InterlockedDecrementPointer(&waitBlock->Count) == 0) + { + PhSetEvent(&waitBlock->WakeEvent); + } + + break; + } + else + { + // Decrement the reference count normally. + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)(value - PH_RUNDOWN_REF_INC), + (PVOID)value + ) == value) + break; + } + } +} + +VOID FASTCALL PhfWaitForRundownProtection( + _Inout_ PPH_RUNDOWN_PROTECT Protection + ) +{ + ULONG_PTR value; + ULONG_PTR count; + PH_RUNDOWN_WAIT_BLOCK waitBlock; + BOOLEAN waitBlockInitialized; + + // Fast path. If the reference count is 0 or rundown has already been completed, return. + value = (ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)PH_RUNDOWN_ACTIVE, + (PVOID)0 + ); + + if (value == 0 || value == PH_RUNDOWN_ACTIVE) + return; + + waitBlockInitialized = FALSE; + + while (TRUE) + { + value = Protection->Value; + count = value >> PH_RUNDOWN_REF_SHIFT; + + // Initialize the wait block if necessary. + if (count != 0 && !waitBlockInitialized) + { + PhInitializeEvent(&waitBlock.WakeEvent); + waitBlockInitialized = TRUE; + } + + // Save the existing reference count. + waitBlock.Count = count; + + if ((ULONG_PTR)_InterlockedCompareExchangePointer( + (PVOID *)&Protection->Value, + (PVOID)((ULONG_PTR)&waitBlock | PH_RUNDOWN_ACTIVE), + (PVOID)value + ) == value) + { + if (count != 0) + PhWaitForEvent(&waitBlock.WakeEvent, NULL); + + break; + } + } +} + +VOID FASTCALL PhfInitializeInitOnce( + _Out_ PPH_INITONCE InitOnce + ) +{ + PhInitializeEvent(&InitOnce->Event); +} + +BOOLEAN FASTCALL PhfBeginInitOnce( + _Inout_ PPH_INITONCE InitOnce + ) +{ + if (!_InterlockedBitTestAndSetPointer(&InitOnce->Event.Value, PH_INITONCE_INITIALIZING_SHIFT)) + return TRUE; + + PhWaitForEvent(&InitOnce->Event, NULL); + + return FALSE; +} + +VOID FASTCALL PhfEndInitOnce( + _Inout_ PPH_INITONCE InitOnce + ) +{ + PhSetEvent(&InitOnce->Event); +} diff --git a/phlib/treenew.c b/phlib/treenew.c new file mode 100644 index 0000000..383fb51 --- /dev/null +++ b/phlib/treenew.c @@ -0,0 +1,6662 @@ +/* + * Process Hacker - + * tree new (tree list control) + * + * Copyright (C) 2011-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * The tree new is a tree view with columns. Unlike the old tree list control, which was a wrapper + * around the list view control, this control was written from scratch. + * + * Current issues not included in any comments: + * * Adding, removing or changing columns does not cause invalidation. + * * It is not possible to change a column to make it fixed. The current fixed column must be + * removed and the new fixed column must then be added. + * * When there are no visible normal columns, the space usually occupied by the normal column + * headers is filled with a solid background color. We should catch this and paint the usual + * themed background there instead. + * * It is not possible to update any TN_STYLE_* flags after the control is created. + * + * Possible additions: + * * More flexible mouse input callbacks to allow custom controls inside columns. + * * Allow custom drawn columns to customize their behaviour when TN_FLAG_ITEM_DRAG_SELECT is set + * (e.g. disable drag selection over certain areas). + * * Virtual mode + */ + +#include +#include +#include +#include +#include +#include +#include + +static PVOID ComCtl32Handle; +static LONG SmallIconWidth; +static LONG SmallIconHeight; + +BOOLEAN PhTreeNewInitialization( + VOID + ) +{ + WNDCLASSEX c = { sizeof(c) }; + + c.style = CS_DBLCLKS | CS_GLOBALCLASS; + c.lpfnWndProc = PhTnpWndProc; + c.cbClsExtra = 0; + c.cbWndExtra = sizeof(PVOID); + c.hInstance = PhLibImageBase; + c.hIcon = NULL; + c.hCursor = LoadCursor(NULL, IDC_ARROW); + c.hbrBackground = NULL; + c.lpszMenuName = NULL; + c.lpszClassName = PH_TREENEW_CLASSNAME; + c.hIconSm = NULL; + + if (!RegisterClassEx(&c)) + return FALSE; + + ComCtl32Handle = GetModuleHandle(L"comctl32.dll"); + SmallIconWidth = GetSystemMetrics(SM_CXSMICON); + SmallIconHeight = GetSystemMetrics(SM_CYSMICON); + + return TRUE; +} + +LRESULT CALLBACK PhTnpWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_TREENEW_CONTEXT context; + + context = (PPH_TREENEW_CONTEXT)GetWindowLongPtr(hwnd, 0); + + if (uMsg == WM_CREATE) + { + PhTnpCreateTreeNewContext(&context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)context); + } + + if (!context) + return DefWindowProc(hwnd, uMsg, wParam, lParam); + + if (context->Tracking && (GetAsyncKeyState(VK_ESCAPE) & 0x1)) + { + PhTnpCancelTrack(context); + } + + // Note: if we have suspended restructuring, we *cannot* access any nodes, because all node + // pointers are now invalid. Below, we disable all input. + + switch (uMsg) + { + case WM_CREATE: + { + if (!PhTnpOnCreate(hwnd, context, (CREATESTRUCT *)lParam)) + return -1; + } + return 0; + case WM_NCDESTROY: + { + context->Callback(hwnd, TreeNewDestroying, NULL, NULL, context->CallbackContext); + PhTnpDestroyTreeNewContext(context); + SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL); + } + return 0; + case WM_SIZE: + { + PhTnpOnSize(hwnd, context); + } + break; + case WM_ERASEBKGND: + return TRUE; + case WM_PAINT: + { + PhTnpOnPaint(hwnd, context); + } + return 0; + case WM_PRINTCLIENT: + { + if (!context->SuspendUpdateStructure) + PhTnpOnPrintClient(hwnd, context, (HDC)wParam, (ULONG)lParam); + } + return 0; + case WM_NCPAINT: + { + if (PhTnpOnNcPaint(hwnd, context, (HRGN)wParam)) + return 0; + } + break; + case WM_GETFONT: + return (LRESULT)context->Font; + case WM_SETFONT: + { + PhTnpOnSetFont(hwnd, context, (HFONT)wParam, LOWORD(lParam)); + } + break; + case WM_STYLECHANGED: + { + PhTnpOnStyleChanged(hwnd, context, (LONG)wParam, (STYLESTRUCT *)lParam); + } + break; + case WM_SETTINGCHANGE: + { + PhTnpOnSettingChange(hwnd, context); + } + break; + case WM_THEMECHANGED: + { + PhTnpOnThemeChanged(hwnd, context); + } + break; + case WM_GETDLGCODE: + return PhTnpOnGetDlgCode(hwnd, context, (ULONG)wParam, (PMSG)lParam); + case WM_SETFOCUS: + { + context->HasFocus = TRUE; + InvalidateRect(context->Handle, NULL, FALSE); + } + return 0; + case WM_KILLFOCUS: + { + context->HasFocus = FALSE; + InvalidateRect(context->Handle, NULL, FALSE); + } + return 0; + case WM_SETCURSOR: + { + if (PhTnpOnSetCursor(hwnd, context, (HWND)wParam)) + return TRUE; + } + break; + case WM_TIMER: + { + PhTnpOnTimer(hwnd, context, (ULONG)wParam); + } + return 0; + case WM_MOUSEMOVE: + { + if (!context->SuspendUpdateStructure) + PhTnpOnMouseMove(hwnd, context, (ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + else + context->SuspendUpdateMoveMouse = TRUE; + } + break; + case WM_MOUSELEAVE: + { + if (!context->SuspendUpdateStructure) + PhTnpOnMouseLeave(hwnd, context); + } + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + { + if (!context->SuspendUpdateStructure) + PhTnpOnXxxButtonXxx(hwnd, context, uMsg, (ULONG)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + } + break; + case WM_CAPTURECHANGED: + { + PhTnpOnCaptureChanged(hwnd, context); + } + break; + case WM_KEYDOWN: + { + if (!context->SuspendUpdateStructure) + PhTnpOnKeyDown(hwnd, context, (ULONG)wParam, (ULONG)lParam); + } + break; + case WM_CHAR: + { + if (!context->SuspendUpdateStructure) + PhTnpOnChar(hwnd, context, (ULONG)wParam, (ULONG)lParam); + } + return 0; + case WM_MOUSEWHEEL: + { + PhTnpOnMouseWheel(hwnd, context, (SHORT)HIWORD(wParam), LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + } + break; + case WM_MOUSEHWHEEL: + { + PhTnpOnMouseHWheel(hwnd, context, (SHORT)HIWORD(wParam), LOWORD(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + } + break; + case WM_CONTEXTMENU: + { + if (!context->SuspendUpdateStructure) + PhTnpOnContextMenu(hwnd, context, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + } + return 0; + case WM_VSCROLL: + { + PhTnpOnVScroll(hwnd, context, LOWORD(wParam), HIWORD(wParam)); + } + return 0; + case WM_HSCROLL: + { + PhTnpOnHScroll(hwnd, context, LOWORD(wParam), HIWORD(wParam)); + } + return 0; + case WM_NOTIFY: + { + LRESULT result; + + if (PhTnpOnNotify(hwnd, context, (NMHDR *)lParam, &result)) + return result; + } + break; + } + + if (uMsg >= TNM_FIRST && uMsg <= TNM_LAST) + { + return PhTnpOnUserMessage(hwnd, context, uMsg, wParam, lParam); + } + + switch (uMsg) + { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + { + if (context->TooltipsHandle) + { + MSG message; + + message.hwnd = hwnd; + message.message = uMsg; + message.wParam = wParam; + message.lParam = lParam; + SendMessage(context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message); + } + } + break; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +BOOLEAN NTAPI PhTnpNullCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + return FALSE; +} + +VOID PhTnpCreateTreeNewContext( + _Out_ PPH_TREENEW_CONTEXT *Context + ) +{ + PPH_TREENEW_CONTEXT context; + + context = PhAllocate(sizeof(PH_TREENEW_CONTEXT)); + memset(context, 0, sizeof(PH_TREENEW_CONTEXT)); + + context->FixedWidthMinimum = 20; + context->RowHeight = 1; // must never be 0 + context->HotNodeIndex = -1; + context->Callback = PhTnpNullCallback; + context->FlatList = PhCreateList(64); + context->TooltipIndex = -1; + context->TooltipId = -1; + context->TooltipColumnId = -1; + context->EnableRedraw = 1; + + *Context = context; +} + +VOID PhTnpDestroyTreeNewContext( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + ULONG i; + + if (Context->Columns) + { + for (i = 0; i < Context->NextId; i++) + { + if (Context->Columns[i]) + PhFree(Context->Columns[i]); + } + + PhFree(Context->Columns); + } + + if (Context->ColumnsByDisplay) + PhFree(Context->ColumnsByDisplay); + + PhDereferenceObject(Context->FlatList); + + if (Context->FontOwned) + DeleteObject(Context->Font); + + if (Context->ThemeData) + CloseThemeData(Context->ThemeData); + + if (Context->SearchString) + PhFree(Context->SearchString); + + if (Context->TooltipText) + PhDereferenceObject(Context->TooltipText); + + if (Context->BufferedContext) + PhTnpDestroyBufferedContext(Context); + + if (Context->SuspendUpdateRegion) + DeleteObject(Context->SuspendUpdateRegion); + + PhFree(Context); +} + +BOOLEAN PhTnpOnCreate( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ CREATESTRUCT *CreateStruct + ) +{ + ULONG headerStyle; + + Context->Handle = hwnd; + Context->InstanceHandle = CreateStruct->hInstance; + Context->Style = CreateStruct->style; + Context->ExtendedStyle = CreateStruct->dwExStyle; + + if (Context->Style & TN_STYLE_DOUBLE_BUFFERED) + Context->DoubleBuffered = TRUE; + if ((Context->Style & TN_STYLE_ANIMATE_DIVIDER) && Context->DoubleBuffered) + Context->AnimateDivider = TRUE; + + headerStyle = HDS_HORZ | HDS_FULLDRAG; + + if (!(Context->Style & TN_STYLE_NO_COLUMN_SORT)) + headerStyle |= HDS_BUTTONS; + if (!(Context->Style & TN_STYLE_NO_COLUMN_HEADER)) + headerStyle |= WS_VISIBLE; + + if (!(Context->FixedHeaderHandle = CreateWindow( + WC_HEADER, + NULL, + WS_CHILD | WS_CLIPSIBLINGS | headerStyle, + 0, + 0, + 0, + 0, + hwnd, + NULL, + CreateStruct->hInstance, + NULL + ))) + { + return FALSE; + } + + if (!(Context->Style & TN_STYLE_NO_COLUMN_REORDER)) + headerStyle |= HDS_DRAGDROP; + + if (!(Context->HeaderHandle = CreateWindow( + WC_HEADER, + NULL, + WS_CHILD | WS_CLIPSIBLINGS | headerStyle, + 0, + 0, + 0, + 0, + hwnd, + NULL, + CreateStruct->hInstance, + NULL + ))) + { + return FALSE; + } + + if (!(Context->VScrollHandle = CreateWindow( + L"SCROLLBAR", + NULL, + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | SBS_VERT, + 0, + 0, + 0, + 0, + hwnd, + NULL, + CreateStruct->hInstance, + NULL + ))) + { + return FALSE; + } + + if (!(Context->HScrollHandle = CreateWindow( + L"SCROLLBAR", + NULL, + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | SBS_HORZ, + 0, + 0, + 0, + 0, + hwnd, + NULL, + CreateStruct->hInstance, + NULL + ))) + { + return FALSE; + } + + if (!(Context->FillerBoxHandle = CreateWindow( + L"STATIC", + NULL, + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, + 0, + 0, + 0, + 0, + hwnd, + NULL, + CreateStruct->hInstance, + NULL + ))) + { + return FALSE; + } + + PhTnpSetFont(Context, NULL, FALSE); // use default font + PhTnpUpdateSystemMetrics(Context); + PhTnpInitializeTooltips(Context); + + return TRUE; +} + +VOID PhTnpOnSize( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + GetClientRect(hwnd, &Context->ClientRect); + + if (Context->BufferedContext && ( + Context->BufferedContextRect.right < Context->ClientRect.right || + Context->BufferedContextRect.bottom < Context->ClientRect.bottom)) + { + // Invalidate the buffered context because the client size has increased. + PhTnpDestroyBufferedContext(Context); + } + + PhTnpLayout(Context); + + if (Context->TooltipsHandle) + { + TOOLINFO toolInfo; + + memset(&toolInfo, 0, sizeof(TOOLINFO)); + toolInfo.cbSize = sizeof(TOOLINFO); + toolInfo.hwnd = hwnd; + toolInfo.uId = TNP_TOOLTIPS_ITEM; + toolInfo.rect = Context->ClientRect; + SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo); + } +} + +VOID PhTnpOnSetFont( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HFONT Font, + _In_ LOGICAL Redraw + ) +{ + PhTnpSetFont(Context, Font, !!Redraw); + PhTnpLayout(Context); +} + +VOID PhTnpOnStyleChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Type, + _In_ STYLESTRUCT *StyleStruct + ) +{ + if (Type == GWL_EXSTYLE) + Context->ExtendedStyle = StyleStruct->styleNew; +} + +VOID PhTnpOnSettingChange( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + PhTnpUpdateSystemMetrics(Context); + PhTnpUpdateTextMetrics(Context); + PhTnpLayout(Context); +} + +VOID PhTnpOnThemeChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + PhTnpUpdateThemeData(Context); +} + +ULONG PhTnpOnGetDlgCode( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey, + _In_opt_ PMSG Message + ) +{ + ULONG code; + + if (Context->Callback(hwnd, TreeNewGetDialogCode, UlongToPtr(VirtualKey), &code, Context->CallbackContext)) + { + return code; + } + + return DLGC_WANTARROWS | DLGC_WANTCHARS; +} + +VOID PhTnpOnPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT updateRect; + HDC hdc; + PAINTSTRUCT paintStruct; + + if (GetUpdateRect(hwnd, &updateRect, FALSE) && (updateRect.left | updateRect.right | updateRect.top | updateRect.bottom)) + { + if (Context->EnableRedraw <= 0) + { + HRGN updateRegion; + + updateRegion = CreateRectRgn(0, 0, 0, 0); + GetUpdateRgn(hwnd, updateRegion, FALSE); + + if (!Context->SuspendUpdateRegion) + { + Context->SuspendUpdateRegion = updateRegion; + } + else + { + CombineRgn(Context->SuspendUpdateRegion, Context->SuspendUpdateRegion, updateRegion, RGN_OR); + DeleteObject(updateRegion); + } + + // Pretend we painted something; this ensures the update region is validated properly. + if (BeginPaint(hwnd, &paintStruct)) + EndPaint(hwnd, &paintStruct); + + return; + } + + if (Context->DoubleBuffered) + { + if (!Context->BufferedContext) + { + PhTnpCreateBufferedContext(Context); + } + } + + if (hdc = BeginPaint(hwnd, &paintStruct)) + { + updateRect = paintStruct.rcPaint; + + if (Context->BufferedContext) + { + PhTnpPaint(hwnd, Context, Context->BufferedContext, &updateRect); + BitBlt( + hdc, + updateRect.left, + updateRect.top, + updateRect.right - updateRect.left, + updateRect.bottom - updateRect.top, + Context->BufferedContext, + updateRect.left, + updateRect.top, + SRCCOPY + ); + } + else + { + PhTnpPaint(hwnd, Context, hdc, &updateRect); + } + + EndPaint(hwnd, &paintStruct); + } + } +} + +VOID PhTnpOnPrintClient( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ ULONG Flags + ) +{ + PhTnpPaint(hwnd, Context, hdc, &Context->ClientRect); +} + +BOOLEAN PhTnpOnNcPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HRGN UpdateRegion + ) +{ + PhTnpInitializeThemeData(Context); + + // Themed border + if ((Context->ExtendedStyle & WS_EX_CLIENTEDGE) && Context->ThemeData) + { + HDC hdc; + ULONG flags; + + if (UpdateRegion == HRGN_FULL) + UpdateRegion = NULL; + + // Note the use of undocumented flags below. GetDCEx doesn't work without these. + + flags = DCX_WINDOW | DCX_LOCKWINDOWUPDATE | 0x10000; + + if (UpdateRegion) + flags |= DCX_INTERSECTRGN | 0x40000; + + if (hdc = GetDCEx(hwnd, UpdateRegion, flags)) + { + PhTnpDrawThemedBorder(Context, hdc); + ReleaseDC(hwnd, hdc); + return TRUE; + } + } + + return FALSE; +} + +BOOLEAN PhTnpOnSetCursor( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HWND CursorWindowHandle + ) +{ + POINT point; + + PhTnpGetMessagePos(hwnd, &point); + + if (TNP_HIT_TEST_FIXED_DIVIDER(point.x, Context)) + { + if (!Context->DividerCursor) + Context->DividerCursor = LoadCursor(ComCtl32Handle, MAKEINTRESOURCE(106)); // HACK (the divider icon resource has been 106 for quite a while...) + + SetCursor(Context->DividerCursor); + return TRUE; + } + + if (Context->Cursor) + { + SetCursor(Context->Cursor); + return TRUE; + } + + return FALSE; +} + +VOID PhTnpOnTimer( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ) +{ + if (Id == TNP_TIMER_ANIMATE_DIVIDER) + { + RECT dividerRect; + + dividerRect.left = Context->FixedWidth; + dividerRect.top = Context->HeaderHeight; + dividerRect.right = Context->FixedWidth + 1; + dividerRect.bottom = Context->ClientRect.bottom; + + if (Context->AnimateDividerFadingIn) + { + Context->DividerHot += TNP_ANIMATE_DIVIDER_INCREMENT; + + if (Context->DividerHot >= 100) + { + Context->DividerHot = 100; + Context->AnimateDividerFadingIn = FALSE; + KillTimer(hwnd, TNP_TIMER_ANIMATE_DIVIDER); + } + + InvalidateRect(hwnd, ÷rRect, FALSE); + } + else if (Context->AnimateDividerFadingOut) + { + if (Context->DividerHot <= TNP_ANIMATE_DIVIDER_DECREMENT) + { + Context->DividerHot = 0; + Context->AnimateDividerFadingOut = FALSE; + KillTimer(hwnd, TNP_TIMER_ANIMATE_DIVIDER); + } + else + { + Context->DividerHot -= TNP_ANIMATE_DIVIDER_DECREMENT; + } + + InvalidateRect(hwnd, ÷rRect, FALSE); + } + } +} + +VOID PhTnpOnMouseMove( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = hwnd; + trackMouseEvent.dwHoverTime = 0; + TrackMouseEvent(&trackMouseEvent); + + if (Context->Tracking) + { + ULONG newFixedWidth; + + newFixedWidth = Context->TrackOldFixedWidth + (CursorX - Context->TrackStartX); + PhTnpSetFixedWidth(Context, newFixedWidth); + } + + PhTnpProcessMoveMouse(Context, CursorX, CursorY); +} + +VOID PhTnpOnMouseLeave( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT rect; + + if (Context->HotNodeIndex != -1 && Context->ThemeData) + { + // Update the old hot node because it may have a different non-hot background and plus minus part. + if (PhTnpGetRowRects(Context, Context->HotNodeIndex, Context->HotNodeIndex, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + + Context->HotNodeIndex = -1; + + if (Context->AnimateDivider && Context->FixedDividerVisible) + { + if ((Context->DividerHot != 0 || Context->AnimateDividerFadingIn) && !Context->AnimateDividerFadingOut) + { + // Fade out the divider. + Context->AnimateDividerFadingOut = TRUE; + Context->AnimateDividerFadingIn = FALSE; + SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL); + } + } +} + +VOID PhTnpOnXxxButtonXxx( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Message, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + BOOLEAN startingTracking; + PH_TREENEW_HIT_TEST hitTest; + LOGICAL controlKey; + LOGICAL shiftKey; + RECT rect; + ULONG changedStart; + ULONG changedEnd; + PH_TREENEW_MESSAGE clickMessage; + + // Focus + + if (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN) + SetFocus(hwnd); + + // Divider tracking + + startingTracking = FALSE; + + switch (Message) + { + case WM_LBUTTONDOWN: + { + if (TNP_HIT_TEST_FIXED_DIVIDER(CursorX, Context)) + { + startingTracking = TRUE; + Context->Tracking = TRUE; + Context->TrackStartX = CursorX; + Context->TrackOldFixedWidth = Context->FixedWidth; + SetCapture(hwnd); + + SetTimer(hwnd, TNP_TIMER_NULL, 100, NULL); // make sure we get messages once in a while so we can detect the escape key + GetAsyncKeyState(VK_ESCAPE); + } + } + break; + case WM_LBUTTONUP: + { + if (Context->Tracking) + { + ReleaseCapture(); + } + } + break; + case WM_RBUTTONDOWN: + { + if (Context->Tracking) + { + PhTnpCancelTrack(Context); + } + } + break; + } + + if (!startingTracking && Context->Tracking) // still OK to process further if the user is only starting to drag the divider + return; + + hitTest.Point.x = CursorX; + hitTest.Point.y = CursorY; + hitTest.InFlags = TN_TEST_COLUMN | TN_TEST_SUBITEM; + PhTnpHitTest(Context, &hitTest); + + controlKey = VirtualKeys & MK_CONTROL; + shiftKey = VirtualKeys & MK_SHIFT; + + // Plus minus glyph + + if ((hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && Message == WM_LBUTTONDOWN) + { + PhTnpSetExpandedNode(Context, hitTest.Node, !hitTest.Node->Expanded); + } + + // Selection + + if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN)) + { + LOGICAL allowDragSelect; + PH_TREENEW_CELL_PARTS parts; + + PhTnpPopTooltip(Context); + allowDragSelect = TRUE; + + if (hitTest.Flags & TN_HIT_ITEM) + { + allowDragSelect = FALSE; + Context->FocusNode = hitTest.Node; + + if (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT) + { + // To allow drag selection to begin even if the cursor is on an item, we check if + // the cursor is on the item icon or text. Exceptions are: + // * When the item is already selected + // * When user is beginning to drag the divider + + if (!hitTest.Node->Selected && !startingTracking) + { + if (PhTnpGetCellParts(Context, hitTest.Node->Index, hitTest.Column, TN_MEASURE_TEXT, &parts)) + { + allowDragSelect = TRUE; + + if ((parts.Flags & TN_PART_ICON) && CursorX >= parts.IconRect.left && CursorX < parts.IconRect.right) + allowDragSelect = FALSE; + + if ((parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT)) + { + if (CursorX >= parts.TextRect.left && CursorX < parts.TextRect.right) + allowDragSelect = FALSE; + } + } + } + } + + PhTnpProcessSelectNode(Context, hitTest.Node, controlKey, shiftKey, Message == WM_RBUTTONDOWN); + } + + if (allowDragSelect) + { + BOOLEAN dragSelect; + ULONG indexToSelect; + BOOLEAN selectionProcessed; + BOOLEAN showContextMenu; + + dragSelect = FALSE; + indexToSelect = -1; + selectionProcessed = FALSE; + showContextMenu = FALSE; + + if (!(hitTest.Flags & (TN_HIT_LEFT | TN_HIT_RIGHT | TN_HIT_ABOVE | TN_HIT_BELOW)) && !startingTracking) // don't interfere with divider + { + BOOLEAN result; + ULONG saveIndex; + ULONG saveId; + ULONG cancelledByMessage; + + // Check for drag selection. PhTnpDetectDrag has its own message loop, so we need to + // clear our pointers before we continue or we will have some access violations when + // items get deleted. + + if (hitTest.Node) + saveIndex = hitTest.Node->Index; + else + saveIndex = -1; + + if (hitTest.Column) + saveId = hitTest.Column->Id; + else + saveId = -1; + + result = PhTnpDetectDrag(Context, CursorX, CursorY, TRUE, &cancelledByMessage); + + // Restore the pointers. + + if (saveIndex == -1) + hitTest.Node = NULL; + else if (saveIndex < Context->FlatList->Count) + hitTest.Node = Context->FlatList->Items[saveIndex]; + else + return; + + if (saveId != -1 && !(hitTest.Column = PhTnpLookupColumnById(Context, saveId))) + return; + + if (result) + { + dragSelect = TRUE; + + if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT)) + { + // Include the current node before starting the drag selection, otherwise + // the user will never be able to select the current node. + indexToSelect = hitTest.Node->Index; + } + } + else + { + if ((Message == WM_LBUTTONDOWN && cancelledByMessage == WM_LBUTTONUP) || + (Message == WM_RBUTTONDOWN && cancelledByMessage == WM_RBUTTONUP)) + { + POINT point; + + if ((hitTest.Flags & TN_HIT_ITEM) && (Context->ExtendedFlags & TN_FLAG_ITEM_DRAG_SELECT)) + { + // The user isn't performing a drag selection, so prevent deselection. + selectionProcessed = TRUE; + } + + // The button up message gets consumed by PhTnpDetectDrag, so send the mouse + // event here. + // Check if the cursor stayed in the same place. + + PhTnpGetMessagePos(Context->Handle, &point); + + if (point.x == CursorX && point.y == CursorY) + { + PhTnpSendMouseEvent( + Context, + Message == WM_LBUTTONDOWN ? TreeNewLeftClick : TreeNewRightClick, + CursorX, + CursorY, + hitTest.Node, + hitTest.Column, + VirtualKeys + ); + } + + if (Message == WM_RBUTTONDOWN) + showContextMenu = TRUE; + } + } + } + + if (!selectionProcessed && !controlKey && !shiftKey) + { + // Nothing: deselect everything. + + PhTnpSelectRange(Context, indexToSelect, indexToSelect, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(hwnd, &rect, FALSE); + } + } + + if (dragSelect) + { + PhTnpDragSelect(Context, CursorX, CursorY); + } + + if (showContextMenu) + { + SendMessage(Context->Handle, WM_CONTEXTMENU, (WPARAM)Context->Handle, GetMessagePos()); + } + + return; + } + } + + // Click, double-click + // Note: If TN_FLAG_ITEM_DRAG_SELECT is enabled, the code below that processes WM_xBUTTONDOWN + // and WM_xBUTTONUP messages only takes effect when the user clicks directly on an item's icon + // or text. + + clickMessage = -1; + + if (Message == WM_LBUTTONDOWN || Message == WM_RBUTTONDOWN) + { + if (Context->MouseDownLast != 0 && Context->MouseDownLast != Message) + { + // User pressed one button and pressed the other without letting go of the first one. + // This counts as a click. + + if (Context->MouseDownLast == WM_LBUTTONDOWN) + clickMessage = TreeNewLeftClick; + else + clickMessage = TreeNewRightClick; + } + + Context->MouseDownLast = Message; + Context->MouseDownLocation.x = CursorX; + Context->MouseDownLocation.y = CursorY; + } + else if (Message == WM_LBUTTONUP || Message == WM_RBUTTONUP) + { + if (Context->MouseDownLast != 0 && + Context->MouseDownLocation.x == CursorX && Context->MouseDownLocation.y == CursorY) + { + if (Context->MouseDownLast == WM_LBUTTONDOWN) + clickMessage = TreeNewLeftClick; + else + clickMessage = TreeNewRightClick; + } + + Context->MouseDownLast = 0; + } + else if (Message == WM_LBUTTONDBLCLK) + { + clickMessage = TreeNewLeftDoubleClick; + } + else if (Message == WM_RBUTTONDBLCLK) + { + clickMessage = TreeNewRightDoubleClick; + } + + if (!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS) && clickMessage != -1) + { + PhTnpSendMouseEvent(Context, clickMessage, CursorX, CursorY, hitTest.Node, hitTest.Column, VirtualKeys); + } +} + +VOID PhTnpOnCaptureChanged( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + Context->Tracking = FALSE; + KillTimer(hwnd, TNP_TIMER_NULL); +} + +VOID PhTnpOnKeyDown( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey, + _In_ ULONG Data + ) +{ + PH_TREENEW_KEY_EVENT keyEvent; + + keyEvent.Handled = FALSE; + keyEvent.VirtualKey = VirtualKey; + keyEvent.Data = Data; + Context->Callback(Context->Handle, TreeNewKeyDown, &keyEvent, NULL, Context->CallbackContext); + + if (keyEvent.Handled) + return; + + if (PhTnpProcessFocusKey(Context, VirtualKey)) + return; + if (PhTnpProcessNodeKey(Context, VirtualKey)) + return; +} + +VOID PhTnpOnChar( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Character, + _In_ ULONG Data + ) +{ + // Make sure the character is printable. + if (Character >= ' ' && Character <= '~') + { + PhTnpProcessSearchKey(Context, Character); + } +} + +VOID PhTnpOnMouseWheel( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + // The normal mouse wheel can affect both the vertical scrollbar and the horizontal scrollbar, + // but the vertical scrollbar takes precedence. + if (Context->VScrollVisible) + { + PhTnpProcessMouseVWheel(Context, -Distance); + } + else if (Context->HScrollVisible) + { + PhTnpProcessMouseHWheel(Context, -Distance); + } +} + +VOID PhTnpOnMouseHWheel( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance, + _In_ ULONG VirtualKeys, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + PhTnpProcessMouseHWheel(Context, Distance); +} + +VOID PhTnpOnContextMenu( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorScreenX, + _In_ LONG CursorScreenY + ) +{ + POINT clientPoint; + BOOLEAN keyboardInvoked; + PH_TREENEW_HIT_TEST hitTest; + PH_TREENEW_CONTEXT_MENU contextMenu; + + if (CursorScreenX == -1 && CursorScreenY == -1) + { + ULONG i; + BOOLEAN found; + RECT windowRect; + RECT rect; + + keyboardInvoked = TRUE; + + // Context menu was invoked via keyboard. Display the context menu at the selected item. + + found = FALSE; + + for (i = 0; i < Context->FlatList->Count; i++) + { + if (((PPH_TREENEW_NODE)Context->FlatList->Items[i])->Selected) + { + found = TRUE; + break; + } + } + + if (found && PhTnpGetRowRects(Context, i, i, FALSE, &rect) && + rect.top >= Context->ClientRect.top && rect.top < Context->ClientRect.bottom) + { + clientPoint.x = rect.left + SmallIconWidth / 2; + clientPoint.y = rect.top + Context->RowHeight / 2; + } + else + { + clientPoint.x = 0; + clientPoint.y = 0; + } + + GetWindowRect(hwnd, &windowRect); + CursorScreenX = windowRect.left + clientPoint.x; + CursorScreenY = windowRect.top + clientPoint.y; + } + else + { + keyboardInvoked = FALSE; + + clientPoint.x = CursorScreenX; + clientPoint.y = CursorScreenY; + ScreenToClient(hwnd, &clientPoint); + + if (clientPoint.y < Context->HeaderHeight) + { + // Already handled by TreeNewHeaderRightClick. + return; + } + } + + hitTest.Point = clientPoint; + hitTest.InFlags = TN_TEST_COLUMN; + PhTnpHitTest(Context, &hitTest); + + contextMenu.Location.x = CursorScreenX; + contextMenu.Location.y = CursorScreenY; + contextMenu.ClientLocation = clientPoint; + contextMenu.Node = hitTest.Node; + contextMenu.Column = hitTest.Column; + contextMenu.KeyboardInvoked = keyboardInvoked; + Context->Callback(hwnd, TreeNewContextMenu, &contextMenu, NULL, Context->CallbackContext); +} + +VOID PhTnpOnVScroll( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Request, + _In_ USHORT Position + ) +{ + SCROLLINFO scrollInfo; + LONG oldPosition; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_ALL; + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + switch (Request) + { + case SB_LINEUP: + scrollInfo.nPos--; + break; + case SB_LINEDOWN: + scrollInfo.nPos++; + break; + case SB_PAGEUP: + scrollInfo.nPos -= scrollInfo.nPage; + break; + case SB_PAGEDOWN: + scrollInfo.nPos += scrollInfo.nPage; + break; + case SB_THUMBPOSITION: + // Touch scrolling seems to give us Position but not nTrackPos. The problem is that Position + // is a 16-bit value, so don't use it if we have too many rows. + if (Context->FlatList->Count <= 0xffff) + scrollInfo.nPos = Position; + break; + case SB_THUMBTRACK: + scrollInfo.nPos = scrollInfo.nTrackPos; + break; + case SB_TOP: + scrollInfo.nPos = 0; + break; + case SB_BOTTOM: + scrollInfo.nPos = MAXINT; + break; + } + + scrollInfo.fMask = SIF_POS; + SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + + if (scrollInfo.nPos != oldPosition) + { + Context->VScrollPosition = scrollInfo.nPos; + PhTnpProcessScroll(Context, scrollInfo.nPos - oldPosition, 0); + } +} + +VOID PhTnpOnHScroll( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Request, + _In_ USHORT Position + ) +{ + SCROLLINFO scrollInfo; + LONG oldPosition; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_ALL; + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + switch (Request) + { + case SB_LINELEFT: + scrollInfo.nPos -= Context->TextMetrics.tmAveCharWidth; + break; + case SB_LINERIGHT: + scrollInfo.nPos += Context->TextMetrics.tmAveCharWidth; + break; + case SB_PAGELEFT: + scrollInfo.nPos -= scrollInfo.nPage; + break; + case SB_PAGERIGHT: + scrollInfo.nPos += scrollInfo.nPage; + break; + case SB_THUMBPOSITION: + // Touch scrolling seems to give us Position but not nTrackPos. The problem is that Position + // is a 16-bit value, so don't use it if we have too many rows. + if (Context->FlatList->Count <= 0xffff) + scrollInfo.nPos = Position; + break; + case SB_THUMBTRACK: + scrollInfo.nPos = scrollInfo.nTrackPos; + break; + case SB_LEFT: + scrollInfo.nPos = 0; + break; + case SB_RIGHT: + scrollInfo.nPos = MAXINT; + break; + } + + scrollInfo.fMask = SIF_POS; + SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + + if (scrollInfo.nPos != oldPosition) + { + Context->HScrollPosition = scrollInfo.nPos; + PhTnpProcessScroll(Context, 0, scrollInfo.nPos - oldPosition); + } +} + +BOOLEAN PhTnpOnNotify( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ NMHDR *Header, + _Out_ LRESULT *Result + ) +{ + switch (Header->code) + { + case HDN_ITEMCHANGING: + case HDN_ITEMCHANGED: + { + NMHEADER *nmHeader = (NMHEADER *)Header; + + if (Header->code == HDN_ITEMCHANGING && Header->hwndFrom == Context->FixedHeaderHandle) + { + if (nmHeader->pitem->mask & HDI_WIDTH) + { + if (Context->FixedColumnVisible) + { + Context->FixedWidth = nmHeader->pitem->cxy - 1; + + if (Context->FixedWidth < Context->FixedWidthMinimum) + Context->FixedWidth = Context->FixedWidthMinimum; + + Context->NormalLeft = Context->FixedWidth + 1; + nmHeader->pitem->cxy = Context->FixedWidth + 1; + } + else + { + Context->FixedWidth = 0; + Context->NormalLeft = 0; + } + } + } + + if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle) + { + if (nmHeader->pitem->mask & HDI_WIDTH) + { + // A column has been resized. Update our stored information. + PhTnpUpdateColumnHeaders(Context); + PhTnpUpdateColumnMaps(Context); + + if (Header->code == HDN_ITEMCHANGING) + { + HDITEM item; + + item.mask = HDI_WIDTH | HDI_LPARAM; + + if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item)) + { + Context->ResizingColumn = (PPH_TREENEW_COLUMN)item.lParam; + Context->OldColumnWidth = item.cxy; + } + else + { + Context->ResizingColumn = NULL; + Context->OldColumnWidth = -1; + } + } + else if (Header->code == HDN_ITEMCHANGED) + { + if (Context->ResizingColumn) + { + LONG delta; + + delta = nmHeader->pitem->cxy - Context->OldColumnWidth; + + if (delta != 0) + { + PhTnpProcessResizeColumn(Context, Context->ResizingColumn, delta); + Context->Callback(Context->Handle, TreeNewColumnResized, Context->ResizingColumn, NULL, Context->CallbackContext); + } + + Context->ResizingColumn = NULL; + + // Redraw the entire window if we are displaying empty text. + if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0) + InvalidateRect(Context->Handle, NULL, FALSE); + } + else + { + // An error occurred during HDN_ITEMCHANGED, so redraw the entire window. + InvalidateRect(Context->Handle, NULL, FALSE); + } + } + } + } + } + break; + case HDN_ITEMCLICK: + { + if ((Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle) && + !(Context->Style & TN_STYLE_NO_COLUMN_SORT)) + { + NMHEADER *nmHeader = (NMHEADER *)Header; + HDITEM item; + PPH_TREENEW_COLUMN column; + + // A column has been clicked, so update the sort state. + + item.mask = HDI_LPARAM; + + if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item)) + { + column = (PPH_TREENEW_COLUMN)item.lParam; + PhTnpProcessSortColumn(Context, column); + } + } + } + break; + case HDN_ENDDRAG: + case NM_RELEASEDCAPTURE: + { + if (Header->hwndFrom == Context->HeaderHandle) + { + // Columns have been re-ordered, so refresh our information. + // Note: The fixed column cannot be re-ordered. + PhTnpUpdateColumnHeaders(Context); + PhTnpUpdateColumnMaps(Context); + Context->Callback(Context->Handle, TreeNewColumnReordered, NULL, NULL, Context->CallbackContext); + InvalidateRect(Context->Handle, NULL, FALSE); + } + } + break; + case HDN_DIVIDERDBLCLICK: + { + if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle) + { + NMHEADER *nmHeader = (NMHEADER *)Header; + HDITEM item; + + if (Context->SuspendUpdateStructure) + break; + + item.mask = HDI_LPARAM; + + if (Header_GetItem(Header->hwndFrom, nmHeader->iItem, &item)) + { + PhTnpAutoSizeColumnHeader( + Context, + Header->hwndFrom, + (PPH_TREENEW_COLUMN)item.lParam, + 0 + ); + } + } + } + break; + case NM_RCLICK: + { + if (Header->hwndFrom == Context->FixedHeaderHandle || Header->hwndFrom == Context->HeaderHandle) + { + PH_TREENEW_HEADER_MOUSE_EVENT mouseEvent; + ULONG position; + + position = GetMessagePos(); + mouseEvent.ScreenLocation.x = GET_X_LPARAM(position); + mouseEvent.ScreenLocation.y = GET_Y_LPARAM(position); + + mouseEvent.Location = mouseEvent.ScreenLocation; + ScreenToClient(hwnd, &mouseEvent.Location); + mouseEvent.HeaderLocation = mouseEvent.ScreenLocation; + ScreenToClient(Header->hwndFrom, &mouseEvent.HeaderLocation); + mouseEvent.Column = PhTnpHitTestHeader(Context, Header->hwndFrom == Context->FixedHeaderHandle, &mouseEvent.HeaderLocation, NULL); + Context->Callback(hwnd, TreeNewHeaderRightClick, &mouseEvent, NULL, Context->CallbackContext); + } + } + break; + case TTN_GETDISPINFO: + { + if (Header->hwndFrom == Context->TooltipsHandle) + { + NMTTDISPINFO *info = (NMTTDISPINFO *)Header; + POINT point; + + PhTnpGetMessagePos(hwnd, &point); + PhTnpGetTooltipText(Context, &point, &info->lpszText); + } + } + break; + case TTN_SHOW: + { + if (Header->hwndFrom == Context->TooltipsHandle) + { + *Result = PhTnpPrepareTooltipShow(Context); + return TRUE; + } + } + break; + case TTN_POP: + { + if (Header->hwndFrom == Context->TooltipsHandle) + { + PhTnpPrepareTooltipPop(Context); + } + } + break; + } + + return FALSE; +} + +ULONG_PTR PhTnpOnUserMessage( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Message, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam + ) +{ + switch (Message) + { + case TNM_SETCALLBACK: + { + Context->Callback = (PPH_TREENEW_CALLBACK)LParam; + Context->CallbackContext = (PVOID)WParam; + + if (!Context->Callback) + Context->Callback = PhTnpNullCallback; + } + return TRUE; + case TNM_NODESSTRUCTURED: + { + if (Context->EnableRedraw <= 0) + { + Context->SuspendUpdateStructure = TRUE; + Context->SuspendUpdateLayout = TRUE; + InvalidateRect(Context->Handle, NULL, FALSE); + return TRUE; + } + + PhTnpRestructureNodes(Context); + PhTnpLayout(Context); + InvalidateRect(Context->Handle, NULL, FALSE); + } + return TRUE; + case TNM_ADDCOLUMN: + return PhTnpAddColumn(Context, (PPH_TREENEW_COLUMN)LParam); + case TNM_REMOVECOLUMN: + return PhTnpRemoveColumn(Context, (ULONG)WParam); + case TNM_GETCOLUMN: + return PhTnpCopyColumn(Context, (ULONG)WParam, (PPH_TREENEW_COLUMN)LParam); + case TNM_SETCOLUMN: + { + PPH_TREENEW_COLUMN column = (PPH_TREENEW_COLUMN)LParam; + + return PhTnpChangeColumn(Context, (ULONG)WParam, column->Id, column); + } + break; + case TNM_GETCOLUMNORDERARRAY: + { + ULONG count = (ULONG)WParam; + PULONG order = (PULONG)LParam; + ULONG i; + + if (count != Context->NumberOfColumnsByDisplay) + return FALSE; + + for (i = 0; i < count; i++) + { + order[i] = Context->ColumnsByDisplay[i]->Id; + } + } + return TRUE; + case TNM_SETCOLUMNORDERARRAY: + { + ULONG count = (ULONG)WParam; + PULONG order = (PULONG)LParam; + ULONG i; + PULONG newOrder; + PPH_TREENEW_COLUMN column; + + newOrder = PhAllocate(count * sizeof(ULONG)); + + for (i = 0; i < count; i++) + { + if (!(column = PhTnpLookupColumnById(Context, order[i]))) + { + PhFree(newOrder); + return FALSE; + } + + newOrder[i] = column->s.ViewIndex; + } + + if (!Header_SetOrderArray(Context->HeaderHandle, count, newOrder)) + { + PhFree(newOrder); + return FALSE; + } + + PhFree(newOrder); + + PhTnpUpdateColumnHeaders(Context); + PhTnpUpdateColumnMaps(Context); + } + return TRUE; + case TNM_SETCURSOR: + { + Context->Cursor = (HCURSOR)LParam; + } + return TRUE; + case TNM_GETSORT: + { + PULONG sortColumn = (PULONG)WParam; + PPH_SORT_ORDER sortOrder = (PPH_SORT_ORDER)LParam; + + if (sortColumn) + *sortColumn = Context->SortColumn; + if (sortOrder) + *sortOrder = Context->SortOrder; + } + return TRUE; + case TNM_SETSORT: + { + ULONG sortColumn = (ULONG)WParam; + PH_SORT_ORDER sortOrder = (PH_SORT_ORDER)LParam; + PPH_TREENEW_COLUMN column; + + if (sortOrder != NoSortOrder) + { + if (!(column = PhTnpLookupColumnById(Context, sortColumn))) + return FALSE; + } + else + { + sortColumn = 0; + column = NULL; + } + + Context->SortColumn = sortColumn; + Context->SortOrder = sortOrder; + + PhTnpSetColumnHeaderSortIcon(Context, column); + + Context->Callback(Context->Handle, TreeNewSortChanged, NULL, NULL, Context->CallbackContext); + } + return TRUE; + case TNM_SETTRISTATE: + Context->TriState = !!WParam; + return TRUE; + case TNM_ENSUREVISIBLE: + return PhTnpEnsureVisibleNode(Context, ((PPH_TREENEW_NODE)LParam)->Index); + case TNM_SCROLL: + PhTnpScroll(Context, (LONG)WParam, (LONG)LParam); + return TRUE; + case TNM_GETFLATNODECOUNT: + if (!Context->SuspendUpdateStructure) + return (LRESULT)Context->FlatList->Count; + else + return 0; + case TNM_GETFLATNODE: + { + ULONG index = (ULONG)WParam; + + if (index >= Context->FlatList->Count) + return (LRESULT)NULL; + + return (LRESULT)Context->FlatList->Items[index]; + } + break; + case TNM_GETCELLTEXT: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = (PPH_TREENEW_GET_CELL_TEXT)LParam; + + return PhTnpGetCellText( + Context, + getCellText->Node, + getCellText->Id, + &getCellText->Text + ); + } + break; + case TNM_SETNODEEXPANDED: + PhTnpSetExpandedNode(Context, (PPH_TREENEW_NODE)LParam, !!WParam); + return TRUE; + case TNM_GETMAXID: + return (LRESULT)(Context->NextId - 1); + case TNM_SETMAXID: + { + ULONG maxId = (ULONG)WParam; + + if (Context->NextId < maxId + 1) + { + Context->NextId = maxId + 1; + + if (Context->AllocatedColumns < Context->NextId) + { + PhTnpExpandAllocatedColumns(Context); + } + } + } + return TRUE; + case TNM_INVALIDATENODE: + { + PPH_TREENEW_NODE node = (PPH_TREENEW_NODE)LParam; + RECT rect; + + if (!node->Visible) + return FALSE; + + if (!PhTnpGetRowRects(Context, node->Index, node->Index, TRUE, &rect)) + return FALSE; + + InvalidateRect(hwnd, &rect, FALSE); + } + return TRUE; + case TNM_INVALIDATENODES: + { + RECT rect; + + if (!PhTnpGetRowRects(Context, (ULONG)WParam, (ULONG)LParam, TRUE, &rect)) + return FALSE; + + InvalidateRect(hwnd, &rect, FALSE); + } + return TRUE; + case TNM_GETFIXEDHEADER: + return (LRESULT)Context->FixedHeaderHandle; + case TNM_GETHEADER: + return (LRESULT)Context->HeaderHandle; + case TNM_GETTOOLTIPS: + return (LRESULT)Context->TooltipsHandle; + case TNM_SELECTRANGE: + case TNM_DESELECTRANGE: + { + ULONG flags; + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + flags = 0; + + if (Message == TNM_DESELECTRANGE) + flags |= TN_SELECT_DESELECT; + + PhTnpSelectRange(Context, (ULONG)WParam, (ULONG)LParam, flags, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(hwnd, &rect, FALSE); + } + } + return TRUE; + case TNM_GETCOLUMNCOUNT: + return (LRESULT)Context->NumberOfColumns; + case TNM_SETREDRAW: + PhTnpSetRedraw(Context, !!WParam); + return (LRESULT)Context->EnableRedraw; + case TNM_GETVIEWPARTS: + { + PPH_TREENEW_VIEW_PARTS parts = (PPH_TREENEW_VIEW_PARTS)LParam; + + parts->ClientRect = Context->ClientRect; + parts->HeaderHeight = Context->HeaderHeight; + parts->RowHeight = Context->RowHeight; + parts->VScrollWidth = Context->VScrollVisible ? Context->VScrollWidth : 0; + parts->HScrollHeight = Context->HScrollHeight ? Context->HScrollHeight : 0; + parts->VScrollPosition = Context->VScrollPosition; + parts->HScrollPosition = Context->HScrollPosition; + parts->FixedWidth = Context->FixedWidth; + parts->NormalLeft = Context->NormalLeft; + parts->NormalWidth = Context->TotalViewX; + } + return TRUE; + case TNM_GETFIXEDCOLUMN: + return (LRESULT)Context->FixedColumn; + case TNM_GETFIRSTCOLUMN: + return (LRESULT)Context->FirstColumn; + case TNM_SETFOCUSNODE: + Context->FocusNode = (PPH_TREENEW_NODE)LParam; + return TRUE; + case TNM_SETMARKNODE: + Context->MarkNodeIndex = ((PPH_TREENEW_NODE)LParam)->Index; + return TRUE; + case TNM_SETHOTNODE: + PhTnpSetHotNode(Context, (PPH_TREENEW_NODE)LParam, FALSE); + return TRUE; + case TNM_SETEXTENDEDFLAGS: + Context->ExtendedFlags = (Context->ExtendedFlags & ~(ULONG)WParam) | ((ULONG)LParam & (ULONG)WParam); + return TRUE; + case TNM_GETCALLBACK: + { + PPH_TREENEW_CALLBACK *callback = (PPH_TREENEW_CALLBACK *)LParam; + PVOID *callbackContext = (PVOID *)WParam; + + if (callback) + { + if (Context->Callback != PhTnpNullCallback) + *callback = Context->Callback; + else + *callback = NULL; + } + + if (callbackContext) + { + *callbackContext = Context->CallbackContext; + } + } + return TRUE; + case TNM_HITTEST: + PhTnpHitTest(Context, (PPH_TREENEW_HIT_TEST)LParam); + return TRUE; + case TNM_GETVISIBLECOLUMNCOUNT: + return Context->NumberOfColumnsByDisplay + (Context->FixedColumnVisible ? 1 : 0); + case TNM_AUTOSIZECOLUMN: + { + ULONG id = (ULONG)WParam; + ULONG flags = (ULONG)LParam; + PPH_TREENEW_COLUMN column; + + if (!(column = PhTnpLookupColumnById(Context, id))) + return FALSE; + + if (!column->Visible) + return FALSE; + + PhTnpAutoSizeColumnHeader( + Context, + column->Fixed ? Context->FixedHeaderHandle : Context->HeaderHandle, + column, + flags + ); + } + return TRUE; + case TNM_SETEMPTYTEXT: + { + PPH_STRINGREF text = (PPH_STRINGREF)LParam; + ULONG flags = (ULONG)WParam; + + Context->EmptyText = *text; + } + return TRUE; + case TNM_SETROWHEIGHT: + { + LONG rowHeight = (LONG)WParam; + + if (rowHeight != 0) + { + Context->CustomRowHeight = TRUE; + Context->RowHeight = rowHeight; + } + else + { + Context->CustomRowHeight = FALSE; + PhTnpUpdateTextMetrics(Context); + } + } + return TRUE; + case TNM_ISFLATNODEVALID: + return !Context->SuspendUpdateStructure; + } + + return 0; +} + +VOID PhTnpSetFont( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ HFONT Font, + _In_ BOOLEAN Redraw + ) +{ + if (Context->FontOwned) + { + DeleteObject(Context->Font); + Context->FontOwned = FALSE; + } + + Context->Font = Font; + + if (!Context->Font) + { + LOGFONT logFont; + + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + Context->Font = CreateFontIndirect(&logFont); + Context->FontOwned = TRUE; + } + } + + SendMessage(Context->FixedHeaderHandle, WM_SETFONT, (WPARAM)Context->Font, Redraw); + SendMessage(Context->HeaderHandle, WM_SETFONT, (WPARAM)Context->Font, Redraw); + + if (Context->TooltipsHandle) + { + SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->Font, FALSE); + Context->TooltipFont = Context->Font; + } + + PhTnpUpdateTextMetrics(Context); +} + +VOID PhTnpUpdateSystemMetrics( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + Context->VScrollWidth = GetSystemMetrics(SM_CXVSCROLL); + Context->HScrollHeight = GetSystemMetrics(SM_CYHSCROLL); + Context->SystemBorderX = GetSystemMetrics(SM_CXBORDER); + Context->SystemBorderY = GetSystemMetrics(SM_CYBORDER); + Context->SystemEdgeX = GetSystemMetrics(SM_CXEDGE); + Context->SystemEdgeY = GetSystemMetrics(SM_CYEDGE); + Context->SystemDragX = GetSystemMetrics(SM_CXDRAG); + Context->SystemDragY = GetSystemMetrics(SM_CYDRAG); + + if (Context->SystemDragX < 2) + Context->SystemDragX = 2; + if (Context->SystemDragY < 2) + Context->SystemDragY = 2; +} + +VOID PhTnpUpdateTextMetrics( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + HDC hdc; + + if (hdc = GetDC(Context->Handle)) + { + SelectObject(hdc, Context->Font); + GetTextMetrics(hdc, &Context->TextMetrics); + + if (!Context->CustomRowHeight) + { + // Below we try to match the row height as calculated by the list view, even if it + // involves magic numbers. On Vista and above there seems to be extra padding. + + Context->RowHeight = Context->TextMetrics.tmHeight; + + if (Context->Style & TN_STYLE_ICONS) + { + if (Context->RowHeight < SmallIconHeight) + Context->RowHeight = SmallIconHeight; + } + else + { + if (WindowsVersion >= WINDOWS_VISTA && !(Context->Style & TN_STYLE_THIN_ROWS)) + Context->RowHeight += 1; // HACK + } + + Context->RowHeight += 1; // HACK + + if (WindowsVersion >= WINDOWS_VISTA && !(Context->Style & TN_STYLE_THIN_ROWS)) + Context->RowHeight += 2; // HACK + } + + ReleaseDC(Context->Handle, hdc); + } +} + +VOID PhTnpUpdateThemeData( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + Context->ThemeActive = !!IsThemeActive(); + + if (Context->ThemeData) + { + CloseThemeData(Context->ThemeData); + Context->ThemeData = NULL; + } + + Context->ThemeData = OpenThemeData(Context->Handle, L"TREEVIEW"); + + if (Context->ThemeData) + { + Context->ThemeHasItemBackground = !!IsThemePartDefined(Context->ThemeData, TVP_TREEITEM, 0); + Context->ThemeHasGlyph = !!IsThemePartDefined(Context->ThemeData, TVP_GLYPH, 0); + Context->ThemeHasHotGlyph = !!IsThemePartDefined(Context->ThemeData, TVP_HOTGLYPH, 0); + } + else + { + Context->ThemeHasItemBackground = FALSE; + Context->ThemeHasGlyph = FALSE; + Context->ThemeHasHotGlyph = FALSE; + } +} + +VOID PhTnpInitializeThemeData( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + if (!Context->ThemeInitialized) + { + PhTnpUpdateThemeData(Context); + Context->ThemeInitialized = TRUE; + } +} + +VOID PhTnpCancelTrack( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + PhTnpSetFixedWidth(Context, Context->TrackOldFixedWidth); + ReleaseCapture(); +} + +VOID PhTnpLayout( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT clientRect; + + if (Context->EnableRedraw <= 0) + { + Context->SuspendUpdateLayout = TRUE; + return; + } + + clientRect = Context->ClientRect; + + PhTnpUpdateScrollBars(Context); + + // Vertical scroll bar + if (Context->VScrollVisible) + { + MoveWindow( + Context->VScrollHandle, + clientRect.right - Context->VScrollWidth, + 0, + Context->VScrollWidth, + clientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0), + TRUE + ); + } + + // Horizontal scroll bar + if (Context->HScrollVisible) + { + MoveWindow( + Context->HScrollHandle, + Context->NormalLeft, + clientRect.bottom - Context->HScrollHeight, + clientRect.right - Context->NormalLeft - (Context->VScrollVisible ? Context->VScrollWidth : 0), + Context->HScrollHeight, + TRUE + ); + } + + // Filler box + if (Context->VScrollVisible && Context->HScrollVisible) + { + MoveWindow( + Context->FillerBoxHandle, + clientRect.right - Context->VScrollWidth, + clientRect.bottom - Context->HScrollHeight, + Context->VScrollWidth, + Context->HScrollHeight, + TRUE + ); + } + + PhTnpLayoutHeader(Context); + + // Redraw the entire window if we are displaying empty text. + if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0) + InvalidateRect(Context->Handle, NULL, FALSE); +} + +VOID PhTnpLayoutHeader( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT rect; + HDLAYOUT hdl; + WINDOWPOS windowPos; + + hdl.prc = ▭ + hdl.pwpos = &windowPos; + + if (!(Context->Style & TN_STYLE_NO_COLUMN_HEADER)) + { + // Fixed portion header control + rect.left = 0; + rect.top = 0; + rect.right = Context->NormalLeft; + rect.bottom = Context->ClientRect.bottom; + Header_Layout(Context->FixedHeaderHandle, &hdl); + SetWindowPos(Context->FixedHeaderHandle, NULL, windowPos.x, windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags); + Context->HeaderHeight = windowPos.cy; + + // Normal portion header control + rect.left = Context->NormalLeft - Context->HScrollPosition; + rect.top = 0; + rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + rect.bottom = Context->ClientRect.bottom; + Header_Layout(Context->HeaderHandle, &hdl); + SetWindowPos(Context->HeaderHandle, NULL, windowPos.x, windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags); + } + else + { + Context->HeaderHeight = 0; + } + + if (Context->TooltipsHandle) + { + TOOLINFO toolInfo; + + memset(&toolInfo, 0, sizeof(TOOLINFO)); + toolInfo.cbSize = sizeof(TOOLINFO); + toolInfo.hwnd = Context->FixedHeaderHandle; + toolInfo.uId = TNP_TOOLTIPS_FIXED_HEADER; + GetClientRect(Context->FixedHeaderHandle, &toolInfo.rect); + SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo); + + toolInfo.hwnd = Context->HeaderHandle; + toolInfo.uId = TNP_TOOLTIPS_HEADER; + GetClientRect(Context->HeaderHandle, &toolInfo.rect); + SendMessage(Context->TooltipsHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo); + } +} + +VOID PhTnpSetFixedWidth( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG FixedWidth + ) +{ + HDITEM item; + + if (Context->FixedColumnVisible) + { + Context->FixedWidth = FixedWidth; + + if (Context->FixedWidth < Context->FixedWidthMinimum) + Context->FixedWidth = Context->FixedWidthMinimum; + + Context->NormalLeft = Context->FixedWidth + 1; + + item.mask = HDI_WIDTH; + item.cxy = Context->FixedWidth + 1; + Header_SetItem(Context->FixedHeaderHandle, 0, &item); + } + else + { + Context->FixedWidth = 0; + Context->NormalLeft = 0; + } +} + +VOID PhTnpSetRedraw( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Redraw + ) +{ + if (Redraw) + Context->EnableRedraw++; + else + Context->EnableRedraw--; + + if (Context->EnableRedraw == 1) + { + if (Context->SuspendUpdateStructure) + { + PhTnpRestructureNodes(Context); + } + + if (Context->SuspendUpdateLayout) + { + PhTnpLayout(Context); + } + + if (Context->SuspendUpdateMoveMouse) + { + POINT point; + + PhTnpGetMessagePos(Context->Handle, &point); + PhTnpProcessMoveMouse(Context, point.x, point.y); + } + + Context->SuspendUpdateStructure = FALSE; + Context->SuspendUpdateLayout = FALSE; + Context->SuspendUpdateMoveMouse = FALSE; + + if (Context->SuspendUpdateRegion) + { + InvalidateRgn(Context->Handle, Context->SuspendUpdateRegion, FALSE); + DeleteObject(Context->SuspendUpdateRegion); + Context->SuspendUpdateRegion = NULL; + } + } +} + +VOID PhTnpSendMouseEvent( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PH_TREENEW_MESSAGE Message, + _In_ LONG CursorX, + _In_ LONG CursorY, + _In_ PPH_TREENEW_NODE Node, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG VirtualKeys + ) +{ + PH_TREENEW_MOUSE_EVENT mouseEvent; + + mouseEvent.Location.x = CursorX; + mouseEvent.Location.y = CursorY; + mouseEvent.Node = Node; + mouseEvent.Column = Column; + mouseEvent.KeyFlags = VirtualKeys; + Context->Callback(Context->Handle, Message, &mouseEvent, NULL, Context->CallbackContext); +} + +PPH_TREENEW_COLUMN PhTnpLookupColumnById( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ) +{ + if (Id >= Context->AllocatedColumns) + return NULL; + + return Context->Columns[Id]; +} + +BOOLEAN PhTnpAddColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column + ) +{ + PPH_TREENEW_COLUMN realColumn; + + // Check if a column with the same ID already exists. + if (Column->Id < Context->AllocatedColumns && Context->Columns[Column->Id]) + return FALSE; + + if (Context->NextId < Column->Id + 1) + Context->NextId = Column->Id + 1; + + realColumn = PhAllocateCopy(Column, sizeof(PH_TREENEW_COLUMN)); + + if (realColumn->DpiScaleOnAdd) + { + realColumn->Width = PhMultiplyDivide(realColumn->Width, PhGlobalDpi, 96); + realColumn->DpiScaleOnAdd = FALSE; + } + + if (Context->AllocatedColumns < Context->NextId) + { + PhTnpExpandAllocatedColumns(Context); + } + + Context->Columns[Column->Id] = realColumn; + Context->NumberOfColumns++; + + if (realColumn->Fixed) + { + if (Context->FixedColumn) + { + // We already have a fixed column, and we can't have two. Make this new column un-fixed. + realColumn->Fixed = FALSE; + } + else + { + Context->FixedColumn = realColumn; + } + + realColumn->DisplayIndex = 0; + realColumn->s.ViewX = 0; + } + + if (realColumn->Visible) + { + BOOLEAN updateHeaders; + + updateHeaders = FALSE; + + if (!realColumn->Fixed && realColumn->DisplayIndex != Header_GetItemCount(Context->HeaderHandle)) + updateHeaders = TRUE; + + realColumn->s.ViewIndex = PhTnpInsertColumnHeader(Context, realColumn); + + if (updateHeaders) + PhTnpUpdateColumnHeaders(Context); + } + else + { + realColumn->s.ViewIndex = -1; + } + + PhTnpUpdateColumnMaps(Context); + + if (realColumn->Visible) + PhTnpLayout(Context); + + return TRUE; +} + +BOOLEAN PhTnpRemoveColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id + ) +{ + PPH_TREENEW_COLUMN realColumn; + BOOLEAN updateLayout; + + if (!(realColumn = PhTnpLookupColumnById(Context, Id))) + return FALSE; + + updateLayout = FALSE; + + if (realColumn->Visible) + updateLayout = TRUE; + + PhTnpDeleteColumnHeader(Context, realColumn); + Context->Columns[realColumn->Id] = NULL; + PhFree(realColumn); + PhTnpUpdateColumnMaps(Context); + + if (updateLayout) + PhTnpLayout(Context); + + Context->NumberOfColumns--; + + return TRUE; +} + +BOOLEAN PhTnpCopyColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Id, + _Out_ PPH_TREENEW_COLUMN Column + ) +{ + PPH_TREENEW_COLUMN realColumn; + + if (!(realColumn = PhTnpLookupColumnById(Context, Id))) + return FALSE; + + memcpy(Column, realColumn, sizeof(PH_TREENEW_COLUMN)); + + return TRUE; +} + +BOOLEAN PhTnpChangeColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Mask, + _In_ ULONG Id, + _In_ PPH_TREENEW_COLUMN Column + ) +{ + PPH_TREENEW_COLUMN realColumn; + BOOLEAN addingOrRemoving; + + if (!(realColumn = PhTnpLookupColumnById(Context, Id))) + return FALSE; + + addingOrRemoving = FALSE; + + if (Mask & TN_COLUMN_FLAG_VISIBLE) + { + if (realColumn->Visible != Column->Visible) + { + addingOrRemoving = TRUE; + } + } + + if (Mask & TN_COLUMN_FLAG_CUSTOMDRAW) + { + realColumn->CustomDraw = Column->CustomDraw; + } + + if (Mask & TN_COLUMN_FLAG_SORTDESCENDING) + { + realColumn->SortDescending = Column->SortDescending; + } + + if (Mask & (TN_COLUMN_TEXT | TN_COLUMN_WIDTH | TN_COLUMN_ALIGNMENT | TN_COLUMN_DISPLAYINDEX)) + { + BOOLEAN updateHeaders; + BOOLEAN updateMaps; + BOOLEAN updateLayout; + + updateHeaders = FALSE; + updateMaps = FALSE; + updateLayout = FALSE; + + if (Mask & TN_COLUMN_TEXT) + { + realColumn->Text = Column->Text; + } + + if (Mask & TN_COLUMN_WIDTH) + { + realColumn->Width = Column->Width; + updateMaps = TRUE; + } + + if (Mask & TN_COLUMN_ALIGNMENT) + { + realColumn->Alignment = Column->Alignment; + } + + if (Mask & TN_COLUMN_DISPLAYINDEX) + { + realColumn->DisplayIndex = Column->DisplayIndex; + updateHeaders = TRUE; + updateMaps = TRUE; + updateLayout = TRUE; + } + + if (!addingOrRemoving && realColumn->Visible) + { + PhTnpChangeColumnHeader(Context, Mask, realColumn); + + if (updateHeaders) + PhTnpUpdateColumnHeaders(Context); + if (updateMaps) + PhTnpUpdateColumnMaps(Context); + if (updateLayout) + PhTnpLayout(Context); + } + } + + if (Mask & TN_COLUMN_CONTEXT) + { + realColumn->Context = Column->Context; + } + + if (Mask & TN_COLUMN_TEXTFLAGS) + { + realColumn->TextFlags = Column->TextFlags; + } + + if (addingOrRemoving) + { + if (Column->Visible) + { + BOOLEAN updateHeaders; + + updateHeaders = FALSE; + + if (realColumn->Fixed) + { + realColumn->DisplayIndex = 0; + } + else + { + if (Mask & TN_COLUMN_DISPLAYINDEX) + updateHeaders = TRUE; + else + realColumn->DisplayIndex = Header_GetItemCount(Context->HeaderHandle); + } + + realColumn->s.ViewIndex = PhTnpInsertColumnHeader(Context, realColumn); + + if (updateHeaders) + PhTnpUpdateColumnHeaders(Context); + } + else + { + PhTnpDeleteColumnHeader(Context, realColumn); + } + + PhTnpUpdateColumnMaps(Context); + PhTnpLayout(Context); + } + + return TRUE; +} + +VOID PhTnpExpandAllocatedColumns( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + if (Context->Columns) + { + ULONG oldAllocatedColumns; + + oldAllocatedColumns = Context->AllocatedColumns; + Context->AllocatedColumns *= 2; + + if (Context->AllocatedColumns < Context->NextId) + Context->AllocatedColumns = Context->NextId; + + Context->Columns = PhReAllocate( + Context->Columns, + Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN) + ); + + // Zero the newly allocated portion. + memset( + &Context->Columns[oldAllocatedColumns], + 0, + (Context->AllocatedColumns - oldAllocatedColumns) * sizeof(PPH_TREENEW_COLUMN) + ); + } + else + { + Context->AllocatedColumns = 16; + + if (Context->AllocatedColumns < Context->NextId) + Context->AllocatedColumns = Context->NextId; + + Context->Columns = PhAllocate( + Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN) + ); + memset(Context->Columns, 0, Context->AllocatedColumns * sizeof(PPH_TREENEW_COLUMN)); + } +} + +VOID PhTnpUpdateColumnMaps( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + ULONG i; + LONG x; + + if (Context->AllocatedColumnsByDisplay < Context->NumberOfColumns) + { + if (Context->ColumnsByDisplay) + PhFree(Context->ColumnsByDisplay); + + Context->ColumnsByDisplay = PhAllocate(sizeof(PPH_TREENEW_COLUMN) * Context->NumberOfColumns); + Context->AllocatedColumnsByDisplay = Context->NumberOfColumns; + } + + memset(Context->ColumnsByDisplay, 0, sizeof(PPH_TREENEW_COLUMN) * Context->AllocatedColumnsByDisplay); + + for (i = 0; i < Context->NextId; i++) + { + if (!Context->Columns[i]) + continue; + + if (Context->Columns[i]->Visible && !Context->Columns[i]->Fixed && Context->Columns[i]->DisplayIndex != -1) + { + if (Context->Columns[i]->DisplayIndex >= Context->NumberOfColumns) + PhRaiseStatus(STATUS_INTERNAL_ERROR); + + Context->ColumnsByDisplay[Context->Columns[i]->DisplayIndex] = Context->Columns[i]; + } + } + + x = 0; + + for (i = 0; i < Context->AllocatedColumnsByDisplay; i++) + { + if (!Context->ColumnsByDisplay[i]) + break; + + Context->ColumnsByDisplay[i]->s.ViewX = x; + x += Context->ColumnsByDisplay[i]->Width; + } + + Context->NumberOfColumnsByDisplay = i; + Context->TotalViewX = x; + + if (Context->FixedColumnVisible) + Context->FirstColumn = Context->FixedColumn; + else if (Context->NumberOfColumnsByDisplay != 0) + Context->FirstColumn = Context->ColumnsByDisplay[0]; + else + Context->FirstColumn = NULL; + + if (Context->NumberOfColumnsByDisplay != 0) + Context->LastColumn = Context->ColumnsByDisplay[Context->NumberOfColumnsByDisplay - 1]; + else if (Context->FixedColumnVisible) + Context->LastColumn = Context->FixedColumn; + else + Context->LastColumn = NULL; +} + +LONG PhTnpInsertColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column + ) +{ + HDITEM item; + + if (Column->Fixed) + { + if (Column->Width < Context->FixedWidthMinimum) + Column->Width = Context->FixedWidthMinimum; + + Context->FixedWidth = Column->Width; + Context->NormalLeft = Context->FixedWidth + 1; + Context->FixedColumnVisible = TRUE; + + if (!(Context->Style & TN_STYLE_NO_DIVIDER)) + Context->FixedDividerVisible = TRUE; + } + + memset(&item, 0, sizeof(HDITEM)); + item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT | HDI_LPARAM | HDI_ORDER; + item.cxy = Column->Width; + item.pszText = Column->Text; + item.fmt = 0; + item.lParam = (LPARAM)Column; + + if (Column->Fixed) + item.cxy++; + + if (Column->Fixed) + item.iOrder = 0; + else + item.iOrder = Column->DisplayIndex; + + if (Column->Alignment & PH_ALIGN_LEFT) + item.fmt |= HDF_LEFT; + else if (Column->Alignment & PH_ALIGN_RIGHT) + item.fmt |= HDF_RIGHT; + else + item.fmt |= HDF_CENTER; + + if (Column->Id == Context->SortColumn) + { + if (Context->SortOrder == AscendingSortOrder) + item.fmt |= HDF_SORTUP; + else if (Context->SortOrder == DescendingSortOrder) + item.fmt |= HDF_SORTDOWN; + } + + Column->Visible = TRUE; + + if (Column->Fixed) + return Header_InsertItem(Context->FixedHeaderHandle, 0, &item); + else + return Header_InsertItem(Context->HeaderHandle, MAXINT, &item); +} + +VOID PhTnpChangeColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Mask, + _In_ PPH_TREENEW_COLUMN Column + ) +{ + HDITEM item; + + memset(&item, 0, sizeof(HDITEM)); + item.mask = 0; + + if (Mask & TN_COLUMN_TEXT) + { + item.mask |= HDI_TEXT; + item.pszText = Column->Text; + } + + if (Mask & TN_COLUMN_WIDTH) + { + item.mask |= HDI_WIDTH; + item.cxy = Column->Width; + + if (Column->Fixed) + item.cxy++; + } + + if (Mask & TN_COLUMN_ALIGNMENT) + { + item.mask |= HDI_FORMAT; + item.fmt = 0; + + if (Column->Alignment & PH_ALIGN_LEFT) + item.fmt |= HDF_LEFT; + else if (Column->Alignment & PH_ALIGN_RIGHT) + item.fmt |= HDF_RIGHT; + else + item.fmt |= HDF_CENTER; + + if (Column->Id == Context->SortColumn) + { + if (Context->SortOrder == AscendingSortOrder) + item.fmt |= HDF_SORTUP; + else if (Context->SortOrder == DescendingSortOrder) + item.fmt |= HDF_SORTDOWN; + } + } + + if (Mask & TN_COLUMN_DISPLAYINDEX) + { + item.mask |= HDI_ORDER; + + if (Column->Fixed) + item.iOrder = 0; + else + item.iOrder = Column->DisplayIndex; + } + + if (Column->Fixed) + Header_SetItem(Context->FixedHeaderHandle, 0, &item); + else + Header_SetItem(Context->HeaderHandle, Column->s.ViewIndex, &item); +} + +VOID PhTnpDeleteColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_COLUMN Column + ) +{ + if (Column->Fixed) + { + Context->FixedColumn = NULL; + Context->FixedWidth = 0; + Context->NormalLeft = 0; + Context->FixedColumnVisible = FALSE; + Context->FixedDividerVisible = FALSE; + } + + if (Column->Fixed) + Header_DeleteItem(Context->FixedHeaderHandle, Column->s.ViewIndex); + else + Header_DeleteItem(Context->HeaderHandle, Column->s.ViewIndex); + + Column->Visible = FALSE; + Column->s.ViewIndex = -1; + PhTnpUpdateColumnHeaders(Context); +} + +VOID PhTnpUpdateColumnHeaders( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + ULONG count; + ULONG i; + HDITEM item; + PPH_TREENEW_COLUMN column; + + item.mask = HDI_WIDTH | HDI_LPARAM | HDI_ORDER; + + // Fixed column + + if (Context->FixedColumnVisible && Header_GetItem(Context->FixedHeaderHandle, 0, &item)) + { + column = Context->FixedColumn; + column->Width = item.cxy - 1; + } + + // Normal columns + + count = Header_GetItemCount(Context->HeaderHandle); + + if (count != -1) + { + for (i = 0; i < count; i++) + { + if (Header_GetItem(Context->HeaderHandle, i, &item)) + { + column = (PPH_TREENEW_COLUMN)item.lParam; + column->s.ViewIndex = i; + column->Width = item.cxy; + column->DisplayIndex = item.iOrder; + } + } + } +} + +VOID PhTnpProcessResizeColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN Column, + _In_ LONG Delta + ) +{ + RECT rect; + LONG columnLeft; + + if (Column->Fixed) + { + columnLeft = 0; + } + else + { + columnLeft = Context->NormalLeft + Column->s.ViewX - Context->HScrollPosition; + } + + // Scroll the content to the right of the column. + // + // Clip the scroll area to the new width, or the old width if that is further to the left. We + // may have the WS_CLIPCHILDREN style set, so we need to remove the horizontal scrollbar from + // the rectangle, otherwise ScrollWindowEx will want to invalidate the entire region! (The + // horizontal scrollbar is an overlapping child control.) + rect.left = columnLeft + Column->Width; + rect.top = Context->HeaderHeight; + rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + rect.bottom = Context->ClientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0); + + if (Delta > 0) + rect.left -= Delta; // old width + + // Scroll the window. + ScrollWindowEx( + Context->Handle, + Delta, + 0, + &rect, + &rect, + NULL, + NULL, + SW_INVALIDATE + ); + + UpdateWindow(Context->Handle); // required + + if (Context->HScrollVisible) + { + // We excluded the bottom region - invalidate it now. + rect.top = rect.bottom; + rect.bottom = Context->ClientRect.bottom; + InvalidateRect(Context->Handle, &rect, FALSE); + } + + PhTnpLayout(Context); + + // Redraw the whole column because the content may depend on the width (e.g. text ellipsis). + rect.left = columnLeft; + rect.top = Context->HeaderHeight; + rect.right = columnLeft + Column->Width; + RedrawWindow(Context->Handle, &rect, NULL, RDW_INVALIDATE | RDW_UPDATENOW); // must be RedrawWindow +} + +VOID PhTnpProcessSortColumn( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_COLUMN NewColumn + ) +{ + if (NewColumn->Id == Context->SortColumn) + { + if (Context->TriState) + { + if (!NewColumn->SortDescending) + { + // Ascending -> Descending -> None + + if (Context->SortOrder == AscendingSortOrder) + Context->SortOrder = DescendingSortOrder; + else if (Context->SortOrder == DescendingSortOrder) + Context->SortOrder = NoSortOrder; + else + Context->SortOrder = AscendingSortOrder; + } + else + { + // Descending -> Ascending -> None + + if (Context->SortOrder == DescendingSortOrder) + Context->SortOrder = AscendingSortOrder; + else if (Context->SortOrder == AscendingSortOrder) + Context->SortOrder = NoSortOrder; + else + Context->SortOrder = DescendingSortOrder; + } + } + else + { + if (Context->SortOrder == AscendingSortOrder) + Context->SortOrder = DescendingSortOrder; + else + Context->SortOrder = AscendingSortOrder; + } + } + else + { + Context->SortColumn = NewColumn->Id; + + if (!NewColumn->SortDescending) + Context->SortOrder = AscendingSortOrder; + else + Context->SortOrder = DescendingSortOrder; + } + + PhTnpSetColumnHeaderSortIcon(Context, NewColumn); + + Context->Callback(Context->Handle, TreeNewSortChanged, NULL, NULL, Context->CallbackContext); +} + +BOOLEAN PhTnpSetColumnHeaderSortIcon( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_COLUMN SortColumnPointer + ) +{ + if (Context->SortOrder == NoSortOrder) + { + PhSetHeaderSortIcon( + Context->FixedHeaderHandle, + -1, + NoSortOrder + ); + PhSetHeaderSortIcon( + Context->HeaderHandle, + -1, + NoSortOrder + ); + + return TRUE; + } + + if (!SortColumnPointer) + { + if (!(SortColumnPointer = PhTnpLookupColumnById(Context, Context->SortColumn))) + return FALSE; + } + + if (SortColumnPointer->Fixed) + { + PhSetHeaderSortIcon( + Context->FixedHeaderHandle, + 0, + Context->SortOrder + ); + PhSetHeaderSortIcon( + Context->HeaderHandle, + -1, + NoSortOrder + ); + } + else + { + PhSetHeaderSortIcon( + Context->FixedHeaderHandle, + -1, + NoSortOrder + ); + PhSetHeaderSortIcon( + Context->HeaderHandle, + SortColumnPointer->s.ViewIndex, + Context->SortOrder + ); + } + + return TRUE; +} + +VOID PhTnpAutoSizeColumnHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HWND HeaderHandle, + _In_ PPH_TREENEW_COLUMN Column, + _In_ ULONG Flags + ) +{ + LONG newWidth; + HDITEM item; + + if (Flags & TN_AUTOSIZE_REMAINING_SPACE) + { + newWidth = Context->ClientRect.right - (Context->TotalViewX - Column->Width); + + if (Context->FixedColumn) + newWidth -= Context->FixedColumn->Width; + if (Context->VScrollVisible) + newWidth -= Context->VScrollWidth; + + if (newWidth <= 0) + return; + } + else + { + ULONG i; + LONG maximumWidth; + PH_TREENEW_CELL_PARTS parts; + LONG width; + + if (Context->FlatList->Count == 0) + return; + if (Column->CustomDraw) + return; + + maximumWidth = 0; + + for (i = 0; i < Context->FlatList->Count; i++) + { + if (PhTnpGetCellParts(Context, i, Column, TN_MEASURE_TEXT, &parts) && + (parts.Flags & TN_PART_CELL) && (parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT)) + { + width = parts.TextRect.right - parts.TextRect.left; // text width + width += parts.ContentRect.left - parts.CellRect.left; // left padding + + if (maximumWidth < width) + maximumWidth = width; + } + } + + newWidth = maximumWidth + TNP_CELL_RIGHT_MARGIN; // right padding + + if (Column->Fixed) + newWidth++; + } + + item.mask = HDI_WIDTH; + item.cxy = newWidth; + + Header_SetItem(HeaderHandle, Column->s.ViewIndex, &item); +} + +BOOLEAN PhTnpGetNodeChildren( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_NODE Node, + _Out_ PPH_TREENEW_NODE **Children, + _Out_ PULONG NumberOfChildren + ) +{ + PH_TREENEW_GET_CHILDREN getChildren; + + getChildren.Flags = 0; + getChildren.Node = Node; + getChildren.Children = NULL; + getChildren.NumberOfChildren = 0; + + if (Context->Callback( + Context->Handle, + TreeNewGetChildren, + &getChildren, + NULL, + Context->CallbackContext + )) + { + *Children = getChildren.Children; + *NumberOfChildren = getChildren.NumberOfChildren; + + return TRUE; + } + + return FALSE; +} + +BOOLEAN PhTnpIsNodeLeaf( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node + ) +{ + PH_TREENEW_IS_LEAF isLeaf; + + isLeaf.Flags = 0; + isLeaf.Node = Node; + isLeaf.IsLeaf = TRUE; + + if (Context->Callback( + Context->Handle, + TreeNewIsLeaf, + &isLeaf, + NULL, + Context->CallbackContext + )) + { + return isLeaf.IsLeaf; + } + + // Doesn't matter, decide when we do the get-children callback. + return FALSE; +} + +BOOLEAN PhTnpGetCellText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ ULONG Id, + _Out_ PPH_STRINGREF Text + ) +{ + PH_TREENEW_GET_CELL_TEXT getCellText; + + if (Id < Node->TextCacheSize && Node->TextCache[Id].Buffer) + { + *Text = Node->TextCache[Id]; + return TRUE; + } + + getCellText.Flags = 0; + getCellText.Node = Node; + getCellText.Id = Id; + PhInitializeEmptyStringRef(&getCellText.Text); + + if (Context->Callback( + Context->Handle, + TreeNewGetCellText, + &getCellText, + NULL, + Context->CallbackContext + ) && getCellText.Text.Buffer) + { + *Text = getCellText.Text; + + if ((getCellText.Flags & TN_CACHE) && Id < Node->TextCacheSize) + Node->TextCache[Id] = getCellText.Text; + + return TRUE; + } + + return FALSE; +} + +VOID PhTnpRestructureNodes( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + PPH_TREENEW_NODE *children; + ULONG numberOfChildren; + ULONG i; + + if (!PhTnpGetNodeChildren(Context, NULL, &children, &numberOfChildren)) + return; + + // We try to preserve the hot node, the focused node and the selection mark node. At this point + // all node pointers must be regarded as invalid, so we must not follow any pointers. + + Context->FocusNodeFound = FALSE; + + PhClearList(Context->FlatList); + Context->CanAnyExpand = FALSE; + + for (i = 0; i < numberOfChildren; i++) + { + PhTnpInsertNodeChildren(Context, children[i], 0); + } + + if (!Context->FocusNodeFound) + Context->FocusNode = NULL; // focused node is no longer present + + if (Context->HotNodeIndex >= Context->FlatList->Count) // covers -1 case as well + Context->HotNodeIndex = -1; + + if (Context->MarkNodeIndex >= Context->FlatList->Count) + Context->MarkNodeIndex = -1; +} + +VOID PhTnpInsertNodeChildren( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ ULONG Level + ) +{ + PPH_TREENEW_NODE *children; + ULONG numberOfChildren; + ULONG i; + ULONG nextLevel; + + if (Node->Visible) + { + Node->Level = Level; + + Node->Index = Context->FlatList->Count; + PhAddItemList(Context->FlatList, Node); + + if (Context->FocusNode == Node) + Context->FocusNodeFound = TRUE; + + nextLevel = Level + 1; + } + else + { + nextLevel = 0; // children of this node should be level 0 + } + + if (!(Node->s.IsLeaf = PhTnpIsNodeLeaf(Context, Node))) + { + Context->CanAnyExpand = TRUE; + + if (Node->Expanded) + { + if (PhTnpGetNodeChildren(Context, Node, &children, &numberOfChildren)) + { + for (i = 0; i < numberOfChildren; i++) + { + PhTnpInsertNodeChildren(Context, children[i], nextLevel); + } + + if (numberOfChildren == 0) + Node->s.IsLeaf = TRUE; + } + } + } +} + +VOID PhTnpSetExpandedNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ BOOLEAN Expanded + ) +{ + if (Node->Expanded != Expanded) + { + PH_TREENEW_NODE_EVENT nodeEvent; + + memset(&nodeEvent, 0, sizeof(PH_TREENEW_NODE_EVENT)); + Context->Callback(Context->Handle, TreeNewNodeExpanding, Node, &nodeEvent, Context->CallbackContext); + + if (!nodeEvent.Handled) + { + if (!Expanded) + { + ULONG i; + PPH_TREENEW_NODE node; + BOOLEAN changed; + + // Make sure no children are selected - we don't want invisible selected nodes. Note + // that this does not cause any UI changes by itself, since we are hiding the nodes. + + changed = FALSE; + + for (i = Node->Index + 1; i < Context->FlatList->Count; i++) + { + node = Context->FlatList->Items[i]; + + if (node->Level <= Node->Level) + break; // no more children + + if (node->Selected) + { + node->Selected = FALSE; + changed = TRUE; + } + } + + if (changed) + { + Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext); + } + } + + Node->Expanded = Expanded; + PhTnpRestructureNodes(Context); + // We need to update the window before the scrollbars get updated in order for the + // scroll processing to work properly. + InvalidateRect(Context->Handle, NULL, FALSE); + UpdateWindow(Context->Handle); + PhTnpLayout(Context); + } + } +} + +BOOLEAN PhTnpGetCellParts( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Index, + _In_opt_ PPH_TREENEW_COLUMN Column, + _In_ ULONG Flags, + _Out_ PPH_TREENEW_CELL_PARTS Parts + ) +{ + PPH_TREENEW_NODE node; + LONG viewWidth; + LONG nodeY; + LONG iconVerticalMargin; + LONG currentX; + + if (Index >= Context->FlatList->Count) + return FALSE; + + node = Context->FlatList->Items[Index]; + nodeY = Context->HeaderHeight + ((LONG)Index - Context->VScrollPosition) * Context->RowHeight; + + Parts->Flags = 0; + Parts->RowRect.left = 0; + Parts->RowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition; + Parts->RowRect.top = nodeY; + Parts->RowRect.bottom = nodeY + Context->RowHeight; + + viewWidth = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + + if (Parts->RowRect.right > viewWidth) + Parts->RowRect.right = viewWidth; + + if (!Column) + return TRUE; + if (!Column->Visible) + return FALSE; + + iconVerticalMargin = (Context->RowHeight - SmallIconHeight) / 2; + + if (Column->Fixed) + { + currentX = 0; + } + else + { + currentX = Context->NormalLeft + Column->s.ViewX - Context->HScrollPosition; + } + + Parts->Flags |= TN_PART_CELL; + Parts->CellRect.left = currentX; + Parts->CellRect.right = currentX + Column->Width; + Parts->CellRect.top = Parts->RowRect.top; + Parts->CellRect.bottom = Parts->RowRect.bottom; + + currentX += TNP_CELL_LEFT_MARGIN; + + if (Column == Context->FirstColumn) + { + currentX += (LONG)node->Level * SmallIconWidth; + + if (Context->CanAnyExpand) + { + if (!node->s.IsLeaf) + { + Parts->Flags |= TN_PART_PLUSMINUS; + Parts->PlusMinusRect.left = currentX; + Parts->PlusMinusRect.right = currentX + SmallIconWidth; + Parts->PlusMinusRect.top = Parts->RowRect.top + iconVerticalMargin; + Parts->PlusMinusRect.bottom = Parts->RowRect.bottom - iconVerticalMargin; + } + + currentX += SmallIconWidth; + } + + if (node->Icon) + { + Parts->Flags |= TN_PART_ICON; + Parts->IconRect.left = currentX; + Parts->IconRect.right = currentX + SmallIconWidth; + Parts->IconRect.top = Parts->RowRect.top + iconVerticalMargin; + Parts->IconRect.bottom = Parts->RowRect.bottom - iconVerticalMargin; + + currentX += SmallIconWidth + TNP_ICON_RIGHT_PADDING; + } + } + + Parts->Flags |= TN_PART_CONTENT; + Parts->ContentRect.left = currentX; + Parts->ContentRect.right = Parts->CellRect.right - TNP_CELL_RIGHT_MARGIN; + Parts->ContentRect.top = Parts->RowRect.top; + Parts->ContentRect.bottom = Parts->RowRect.bottom; + + if (Flags & TN_MEASURE_TEXT) + { + HDC hdc; + PH_STRINGREF text; + HFONT font; + SIZE textSize; + + if (hdc = GetDC(Context->Handle)) + { + PhTnpPrepareRowForDraw(Context, hdc, node); + + if (PhTnpGetCellText(Context, node, Column->Id, &text)) + { + if (node->Font) + font = node->Font; + else + font = Context->Font; + + SelectObject(hdc, font); + + if (GetTextExtentPoint32(hdc, text.Buffer, (ULONG)text.Length / sizeof(WCHAR), &textSize)) + { + Parts->Flags |= TN_PART_TEXT; + Parts->TextRect.left = currentX; + Parts->TextRect.right = currentX + textSize.cx; + Parts->TextRect.top = Parts->RowRect.top + (Context->RowHeight - textSize.cy) / 2; + Parts->TextRect.bottom = Parts->RowRect.bottom - (Context->RowHeight - textSize.cy) / 2; + + if (Column->TextFlags & DT_CENTER) + { + Parts->TextRect.left = Parts->ContentRect.left / 2 + (Parts->ContentRect.right - textSize.cx) / 2; + Parts->TextRect.right = Parts->ContentRect.left + textSize.cx; + } + else if (Column->TextFlags & DT_RIGHT) + { + Parts->TextRect.right = Parts->ContentRect.right; + Parts->TextRect.left = Parts->TextRect.right - textSize.cx; + } + + Parts->Text = text; + Parts->Font = font; + } + } + + ReleaseDC(Context->Handle, hdc); + } + } + + return TRUE; +} + +BOOLEAN PhTnpGetRowRects( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Start, + _In_ ULONG End, + _In_ BOOLEAN Clip, + _Out_ PRECT Rect + ) +{ + LONG startY; + LONG endY; + LONG viewWidth; + + if (End >= Context->FlatList->Count) + return FALSE; + if (Start > End) + return FALSE; + + startY = Context->HeaderHeight + ((LONG)Start - Context->VScrollPosition) * Context->RowHeight; + endY = Context->HeaderHeight + ((LONG)End - Context->VScrollPosition) * Context->RowHeight; + + Rect->left = 0; + Rect->right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition; + Rect->top = startY; + Rect->bottom = endY + Context->RowHeight; + + viewWidth = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + + if (Rect->right > viewWidth) + Rect->right = viewWidth; + + if (Clip) + { + if (Rect->top < Context->HeaderHeight) + Rect->top = Context->HeaderHeight; + if (Rect->bottom > Context->ClientRect.bottom) + Rect->bottom = Context->ClientRect.bottom; + } + + return TRUE; +} + +VOID PhTnpHitTest( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_HIT_TEST HitTest + ) +{ + RECT clientRect; + LONG x; + LONG y; + ULONG index; + PPH_TREENEW_NODE node; + + HitTest->Flags = 0; + HitTest->Node = NULL; + HitTest->Column = NULL; + + clientRect = Context->ClientRect; + x = HitTest->Point.x; + y = HitTest->Point.y; + + if (x < 0) + HitTest->Flags |= TN_HIT_LEFT; + if (x >= clientRect.right) + HitTest->Flags |= TN_HIT_RIGHT; + if (y < 0) + HitTest->Flags |= TN_HIT_ABOVE; + if (y >= clientRect.bottom) + HitTest->Flags |= TN_HIT_BELOW; + + if (HitTest->Flags == 0) + { + if (TNP_HIT_TEST_FIXED_DIVIDER(x, Context)) + { + HitTest->Flags |= TN_HIT_DIVIDER; + } + + if (y >= Context->HeaderHeight && x < Context->FixedWidth + Context->TotalViewX) + { + index = (y - Context->HeaderHeight) / Context->RowHeight + Context->VScrollPosition; + + if (index < Context->FlatList->Count) + { + HitTest->Flags |= TN_HIT_ITEM; + node = Context->FlatList->Items[index]; + HitTest->Node = node; + + if (HitTest->InFlags & TN_TEST_COLUMN) + { + PPH_TREENEW_COLUMN column; + LONG columnX; + + column = NULL; + + if (x < Context->FixedWidth && Context->FixedColumnVisible) + { + column = Context->FixedColumn; + columnX = 0; + } + else + { + LONG currentX; + ULONG i; + PPH_TREENEW_COLUMN currentColumn; + + currentX = Context->NormalLeft - Context->HScrollPosition; + + for (i = 0; i < Context->NumberOfColumnsByDisplay; i++) + { + currentColumn = Context->ColumnsByDisplay[i]; + + if (x >= currentX && x < currentX + currentColumn->Width) + { + column = currentColumn; + columnX = currentX; + break; + } + + currentX += currentColumn->Width; + } + } + + HitTest->Column = column; + + if (column && (HitTest->InFlags & TN_TEST_SUBITEM)) + { + BOOLEAN isFirstColumn; + LONG currentX; + + isFirstColumn = HitTest->Column == Context->FirstColumn; + + currentX = columnX; + currentX += TNP_CELL_LEFT_MARGIN; + + if (isFirstColumn) + { + currentX += (LONG)node->Level * SmallIconWidth; + + if (!node->s.IsLeaf) + { + if (x >= currentX && x < currentX + SmallIconWidth) + HitTest->Flags |= TN_HIT_ITEM_PLUSMINUS; + + currentX += SmallIconWidth; + } + + if (node->Icon) + { + if (x >= currentX && x < currentX + SmallIconWidth) + HitTest->Flags |= TN_HIT_ITEM_ICON; + + currentX += SmallIconWidth + TNP_ICON_RIGHT_PADDING; + } + } + + if (x >= currentX) + { + HitTest->Flags |= TN_HIT_ITEM_CONTENT; + } + } + } + } + } + } +} + +VOID PhTnpSelectRange( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Start, + _In_ ULONG End, + _In_ ULONG Flags, + _Out_opt_ PULONG ChangedStart, + _Out_opt_ PULONG ChangedEnd + ) +{ + ULONG maximum; + ULONG i; + PPH_TREENEW_NODE node; + BOOLEAN targetValue; + ULONG changedStart; + ULONG changedEnd; + + if (Context->FlatList->Count == 0) + return; + + maximum = Context->FlatList->Count - 1; + + if (End > maximum) + { + End = maximum; + } + + if (Start > End) + { + // Start is too big, so the selection range becomes empty. + // Set it to max + 1 so that Reset still works. + Start = maximum + 1; + End = 0; + } + + targetValue = !(Flags & TN_SELECT_DESELECT); + changedStart = maximum; + changedEnd = 0; + + if (Flags & TN_SELECT_RESET) + { + for (i = 0; i < Start; i++) + { + node = Context->FlatList->Items[i]; + + if (node->Selected) + { + node->Selected = FALSE; + + if (changedStart > i) + changedStart = i; + if (changedEnd < i) + changedEnd = i; + } + } + } + + for (i = Start; i <= End; i++) + { + node = Context->FlatList->Items[i]; + + if (!node->Unselectable && ((Flags & TN_SELECT_TOGGLE) || node->Selected != targetValue)) + { + node->Selected = !node->Selected; + + if (changedStart > i) + changedStart = i; + if (changedEnd < i) + changedEnd = i; + } + } + + if (Flags & TN_SELECT_RESET) + { + for (i = End + 1; i <= maximum; i++) + { + node = Context->FlatList->Items[i]; + + if (node->Selected) + { + node->Selected = FALSE; + + if (changedStart > i) + changedStart = i; + if (changedEnd < i) + changedEnd = i; + } + } + } + + if (changedStart <= changedEnd) + { + Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext); + } + + if (ChangedStart) + *ChangedStart = changedStart; + if (ChangedEnd) + *ChangedEnd = changedEnd; +} + +VOID PhTnpSetHotNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_opt_ PPH_TREENEW_NODE NewHotNode, + _In_ BOOLEAN NewPlusMinusHot + ) +{ + ULONG newHotNodeIndex; + RECT rowRect; + BOOLEAN needsInvalidate; + + if (NewHotNode) + newHotNodeIndex = NewHotNode->Index; + else + newHotNodeIndex = -1; + + needsInvalidate = FALSE; + + if (Context->HotNodeIndex != newHotNodeIndex) + { + if (Context->HotNodeIndex != -1) + { + if (Context->ThemeData && PhTnpGetRowRects(Context, Context->HotNodeIndex, Context->HotNodeIndex, TRUE, &rowRect)) + { + // Update the old hot node because it may have a different non-hot background and + // plus minus part. + InvalidateRect(Context->Handle, &rowRect, FALSE); + } + } + + Context->HotNodeIndex = newHotNodeIndex; + + if (NewHotNode) + { + needsInvalidate = TRUE; + } + } + + if (NewHotNode) + { + if (NewHotNode->s.PlusMinusHot != NewPlusMinusHot) + { + NewHotNode->s.PlusMinusHot = NewPlusMinusHot; + needsInvalidate = TRUE; + } + + if (needsInvalidate && Context->ThemeData && PhTnpGetRowRects(Context, newHotNodeIndex, newHotNodeIndex, TRUE, &rowRect)) + { + InvalidateRect(Context->Handle, &rowRect, FALSE); + } + } +} + +VOID PhTnpProcessSelectNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPH_TREENEW_NODE Node, + _In_ LOGICAL ControlKey, + _In_ LOGICAL ShiftKey, + _In_ LOGICAL RightButton + ) +{ + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + if (RightButton) + { + // Right button: + // If the current node is selected, then do nothing. This is to allow context menus to + // operate on multiple items. + // If the current node is not selected, select only that node. + + if (!ControlKey && !ShiftKey && !Node->Selected) + { + PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd); + Context->MarkNodeIndex = Node->Index; + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + } + else if (ShiftKey && Context->MarkNodeIndex != -1) + { + ULONG start; + ULONG end; + + // Shift key: select a range from the selection mark node to the current node. + + if (Node->Index > Context->MarkNodeIndex) + { + start = Context->MarkNodeIndex; + end = Node->Index; + } + else + { + start = Node->Index; + end = Context->MarkNodeIndex; + } + + PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + else if (ControlKey) + { + // Control key: toggle the selection on the current node, and also make it the selection + // mark. + + PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_TOGGLE, NULL, NULL); + Context->MarkNodeIndex = Node->Index; + + if (PhTnpGetRowRects(Context, Node->Index, Node->Index, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + else + { + // Normal: select the current node, and also make it the selection mark. + + PhTnpSelectRange(Context, Node->Index, Node->Index, TN_SELECT_RESET, &changedStart, &changedEnd); + Context->MarkNodeIndex = Node->Index; + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } +} + +BOOLEAN PhTnpEnsureVisibleNode( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Index + ) +{ + LONG viewTop; + LONG viewBottom; + LONG rowTop; + LONG rowBottom; + LONG deltaY; + LONG deltaRows; + + if (Index >= Context->FlatList->Count) + return FALSE; + + viewTop = Context->HeaderHeight; + viewBottom = Context->ClientRect.bottom - (Context->HScrollVisible ? Context->HScrollHeight : 0); + rowTop = Context->HeaderHeight + ((LONG)Index - Context->VScrollPosition) * Context->RowHeight; + rowBottom = rowTop + Context->RowHeight; + + // Check if the row is fully visible. + if (rowTop >= viewTop && rowBottom <= viewBottom) + return TRUE; + + deltaY = rowTop - viewTop; + + if (deltaY > 0) + { + // The row is below the view area. We want to scroll the row into view at the bottom of the + // screen. We need to round up when dividing to make sure the node becomes fully visible. + deltaY = rowBottom - viewBottom; + deltaRows = (deltaY + Context->RowHeight - 1) / Context->RowHeight; // divide, round up + } + else + { + deltaRows = deltaY / Context->RowHeight; + } + + PhTnpScroll(Context, deltaRows, 0); + + return TRUE; +} + +VOID PhTnpProcessMoveMouse( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + PH_TREENEW_HIT_TEST hitTest; + PPH_TREENEW_NODE hotNode; + + hitTest.Point.x = CursorX; + hitTest.Point.y = CursorY; + hitTest.InFlags = TN_TEST_COLUMN | TN_TEST_SUBITEM; + PhTnpHitTest(Context, &hitTest); + + if (hitTest.Flags & TN_HIT_ITEM) + hotNode = hitTest.Node; + else + hotNode = NULL; + + PhTnpSetHotNode(Context, hotNode, !!(hitTest.Flags & TN_HIT_ITEM_PLUSMINUS)); + + if (Context->AnimateDivider && Context->FixedDividerVisible) + { + if (hitTest.Flags & TN_HIT_DIVIDER) + { + if ((Context->DividerHot < 100 || Context->AnimateDividerFadingOut) && !Context->AnimateDividerFadingIn) + { + // Begin fading in the divider. + Context->AnimateDividerFadingIn = TRUE; + Context->AnimateDividerFadingOut = FALSE; + SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL); + } + } + else + { + if ((Context->DividerHot != 0 || Context->AnimateDividerFadingIn) && !Context->AnimateDividerFadingOut) + { + Context->AnimateDividerFadingOut = TRUE; + Context->AnimateDividerFadingIn = FALSE; + SetTimer(Context->Handle, TNP_TIMER_ANIMATE_DIVIDER, TNP_ANIMATE_DIVIDER_INTERVAL, NULL); + } + } + } + + if (Context->TooltipsHandle) + { + ULONG index; + ULONG id; + + if (!(hitTest.Flags & TN_HIT_DIVIDER)) + { + index = hitTest.Node ? hitTest.Node->Index : -1; + id = hitTest.Column ? hitTest.Column->Id : -1; + } + else + { + index = -1; + id = -1; + } + + // This pops unnecessarily - when the cell has no tooltip text, and the user is moving the + // mouse over it. However these unnecessary calls seem to fix a certain tooltip bug (move + // the mouse around very quickly over the last column and the blank space to the right, and + // no more tooltips will appear). + if (Context->TooltipIndex != index || Context->TooltipId != id) + { + PhTnpPopTooltip(Context); + } + } +} + +VOID PhTnpProcessMouseVWheel( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance + ) +{ + ULONG wheelScrollLines; + FLOAT linesToScroll; + LONG wholeLinesToScroll; + SCROLLINFO scrollInfo; + LONG oldPosition; + + if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0)) + wheelScrollLines = 3; + + // If page scrolling is enabled, use the number of visible rows. + if (wheelScrollLines == -1) + wheelScrollLines = (Context->ClientRect.bottom - Context->HeaderHeight - (Context->HScrollVisible ? Context->HScrollHeight : 0)) / Context->RowHeight; + + // Zero the remainder if the direction changed. + if ((Context->VScrollRemainder > 0) != (Distance > 0)) + Context->VScrollRemainder = 0; + + linesToScroll = (FLOAT)wheelScrollLines * Distance / WHEEL_DELTA + Context->VScrollRemainder; + wholeLinesToScroll = (LONG)linesToScroll; + Context->VScrollRemainder = linesToScroll - wholeLinesToScroll; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_ALL; + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + scrollInfo.nPos += wholeLinesToScroll; + + scrollInfo.fMask = SIF_POS; + SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + + if (scrollInfo.nPos != oldPosition) + { + Context->VScrollPosition = scrollInfo.nPos; + PhTnpProcessScroll(Context, scrollInfo.nPos - oldPosition, 0); + + if (Context->TooltipsHandle) + { + MSG message; + POINT point; + + PhTnpPopTooltip(Context); + PhTnpGetMessagePos(Context->Handle, &point); + + if (point.x >= 0 && point.y >= 0 && point.x < Context->ClientRect.right && point.y < Context->ClientRect.bottom) + { + // Send a fake mouse move message for the new node that the mouse may be hovering over. + message.hwnd = Context->Handle; + message.message = WM_MOUSEMOVE; + message.wParam = 0; + message.lParam = MAKELPARAM(point.x, point.y); + SendMessage(Context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message); + } + } + } +} + +VOID PhTnpProcessMouseHWheel( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG Distance + ) +{ + ULONG wheelScrollChars; + FLOAT pixelsToScroll; + LONG wholePixelsToScroll; + SCROLLINFO scrollInfo; + LONG oldPosition; + + if (!SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &wheelScrollChars, 0)) + wheelScrollChars = 3; + + // Zero the remainder if the direction changed. + if ((Context->HScrollRemainder > 0) != (Distance > 0)) + Context->HScrollRemainder = 0; + + pixelsToScroll = (FLOAT)wheelScrollChars * Context->TextMetrics.tmAveCharWidth * Distance / WHEEL_DELTA + Context->HScrollRemainder; + wholePixelsToScroll = (LONG)pixelsToScroll; + Context->HScrollRemainder = pixelsToScroll - wholePixelsToScroll; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_ALL; + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + scrollInfo.nPos += wholePixelsToScroll; + + scrollInfo.fMask = SIF_POS; + SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + + if (scrollInfo.nPos != oldPosition) + { + Context->HScrollPosition = scrollInfo.nPos; + PhTnpProcessScroll(Context, 0, scrollInfo.nPos - oldPosition); + } +} + +BOOLEAN PhTnpProcessFocusKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey + ) +{ + ULONG count; + ULONG index; + BOOLEAN controlKey; + BOOLEAN shiftKey; + ULONG start; + ULONG end; + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + if (VirtualKey != VK_UP && VirtualKey != VK_DOWN && + VirtualKey != VK_HOME && VirtualKey != VK_END && + VirtualKey != VK_PRIOR && VirtualKey != VK_NEXT) + { + return FALSE; + } + + count = Context->FlatList->Count; + + if (count == 0) + return TRUE; + + // Find the new node to focus. + + switch (VirtualKey) + { + case VK_UP: + { + if (Context->FocusNode && Context->FocusNode->Index > 0) + { + index = Context->FocusNode->Index - 1; + } + else + { + index = 0; + } + } + break; + case VK_DOWN: + { + if (Context->FocusNode) + { + index = Context->FocusNode->Index + 1; + + if (index >= count) + index = count - 1; + } + else + { + index = 0; + } + } + break; + case VK_HOME: + index = 0; + break; + case VK_END: + index = count - 1; + break; + case VK_PRIOR: + case VK_NEXT: + { + LONG rowsPerPage; + + if (Context->FocusNode) + index = Context->FocusNode->Index; + else + index = 0; + + rowsPerPage = Context->ClientRect.bottom - Context->HeaderHeight - (Context->HScrollVisible ? Context->HScrollHeight : 0); + + if (rowsPerPage < 0) + return TRUE; + + rowsPerPage = rowsPerPage / Context->RowHeight - 1; + + if (rowsPerPage < 0) + return TRUE; + + if (VirtualKey == VK_PRIOR) + { + ULONG startOfPageIndex; + + startOfPageIndex = Context->VScrollPosition; + + if (index > startOfPageIndex) + { + index = startOfPageIndex; + } + else + { + // Already at or before the start of the page. Go back a page. + if (index >= (ULONG)rowsPerPage) + index -= rowsPerPage; + else + index = 0; + } + } + else + { + ULONG endOfPageIndex; + + endOfPageIndex = Context->VScrollPosition + rowsPerPage; + + if (endOfPageIndex >= count) + endOfPageIndex = count - 1; + + if (index < endOfPageIndex) + { + index = endOfPageIndex; + } + else + { + // Already at or after the end of the page. Go forward a page. + index += rowsPerPage; + + if (index >= count) + index = count - 1; + } + } + } + break; + } + + // Select the relevant nodes. + + controlKey = GetKeyState(VK_CONTROL) < 0; + shiftKey = GetKeyState(VK_SHIFT) < 0; + + Context->FocusNode = Context->FlatList->Items[index]; + PhTnpSetHotNode(Context, Context->FocusNode, FALSE); + + if (shiftKey && Context->MarkNodeIndex != -1) + { + if (index > Context->MarkNodeIndex) + { + start = Context->MarkNodeIndex; + end = index; + } + else + { + start = index; + end = Context->MarkNodeIndex; + } + + PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + else if (!controlKey) + { + Context->MarkNodeIndex = Context->FocusNode->Index; + PhTnpSelectRange(Context, index, index, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + + PhTnpEnsureVisibleNode(Context, index); + PhTnpPopTooltip(Context); + + return TRUE; +} + +BOOLEAN PhTnpProcessNodeKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKey + ) +{ + BOOLEAN controlKey; + BOOLEAN shiftKey; + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + if (VirtualKey != VK_SPACE && VirtualKey != VK_LEFT && VirtualKey != VK_RIGHT && VirtualKey != VK_ADD && VirtualKey != VK_OEM_PLUS) + { + return FALSE; + } + + if (!Context->FocusNode) + return TRUE; + + controlKey = GetKeyState(VK_CONTROL) < 0; + shiftKey = GetKeyState(VK_SHIFT) < 0; + + switch (VirtualKey) + { + case VK_SPACE: + { + if (controlKey) + { + // Control key: toggle the selection on the focused node. + + Context->MarkNodeIndex = Context->FocusNode->Index; + PhTnpSelectRange(Context, Context->FocusNode->Index, Context->FocusNode->Index, TN_SELECT_TOGGLE, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + else if (shiftKey) + { + ULONG start; + ULONG end; + + // Shift key: select a range from the selection mark node to the focused node. + + if (Context->FocusNode->Index > Context->MarkNodeIndex) + { + start = Context->MarkNodeIndex; + end = Context->FocusNode->Index; + } + else + { + start = Context->FocusNode->Index; + end = Context->MarkNodeIndex; + } + + PhTnpSelectRange(Context, start, end, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + } + } + break; + case VK_LEFT: + { + ULONG i; + ULONG targetLevel; + PPH_TREENEW_NODE newNode; + + // If the node is expanded, collapse it. Otherwise, select the node's parent. + if (!Context->FocusNode->s.IsLeaf && Context->FocusNode->Expanded) + { + PhTnpSetExpandedNode(Context, Context->FocusNode, FALSE); + } + else if (Context->FocusNode->Level != 0) + { + i = Context->FocusNode->Index; + targetLevel = Context->FocusNode->Level - 1; + + while (i != 0) + { + i--; + newNode = Context->FlatList->Items[i]; + + if (newNode->Level == targetLevel) + { + Context->FocusNode = newNode; + Context->MarkNodeIndex = newNode->Index; + PhTnpEnsureVisibleNode(Context, i); + PhTnpSetHotNode(Context, newNode, FALSE); + PhTnpSelectRange(Context, i, i, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + + PhTnpPopTooltip(Context); + + break; + } + } + } + } + break; + case VK_RIGHT: + { + PPH_TREENEW_NODE newNode; + + if (!Context->FocusNode->s.IsLeaf) + { + // If the node is collapsed, expand it. Otherwise, select the node's first child. + if (!Context->FocusNode->Expanded) + { + PhTnpSetExpandedNode(Context, Context->FocusNode, TRUE); + } + else + { + if (Context->FocusNode->Index + 1 < Context->FlatList->Count) + { + newNode = Context->FlatList->Items[Context->FocusNode->Index + 1]; + + if (newNode->Level == Context->FocusNode->Level + 1) + { + Context->FocusNode = newNode; + Context->MarkNodeIndex = newNode->Index; + PhTnpEnsureVisibleNode(Context, Context->FocusNode->Index + 1); + PhTnpSetHotNode(Context, newNode, FALSE); + PhTnpSelectRange(Context, Context->FocusNode->Index, Context->FocusNode->Index, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + + PhTnpPopTooltip(Context); + } + } + } + } + } + break; + case VK_ADD: + case VK_OEM_PLUS: + { + if ((VirtualKey == VK_ADD && controlKey) || + (VirtualKey == VK_OEM_PLUS && controlKey && shiftKey)) + { + ULONG i; + + if (Context->FixedColumnVisible) + PhTnpAutoSizeColumnHeader(Context, Context->FixedHeaderHandle, Context->FixedColumn, 0); + + for (i = 0; i < Context->NumberOfColumnsByDisplay; i++) + PhTnpAutoSizeColumnHeader(Context, Context->HeaderHandle, Context->ColumnsByDisplay[i], 0); + } + } + break; + } + + return TRUE; +} + +VOID PhTnpProcessSearchKey( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG Character + ) +{ + LONG messageTime; + BOOLEAN newSearch; + PH_TREENEW_SEARCH_EVENT searchEvent; + PPH_TREENEW_NODE foundNode; + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + if (Context->FlatList->Count == 0) + return; + + messageTime = GetMessageTime(); + newSearch = FALSE; + + // Check if the search timed out. + if (messageTime - Context->SearchMessageTime > PH_TREENEW_SEARCH_TIMEOUT) + { + Context->SearchStringCount = 0; + Context->SearchFailed = FALSE; + newSearch = TRUE; + Context->SearchSingleCharMode = TRUE; + } + + Context->SearchMessageTime = messageTime; + + // Append the character to the search buffer. + + if (!Context->SearchString) + { + Context->AllocatedSearchString = 32; + Context->SearchString = PhAllocate(Context->AllocatedSearchString * sizeof(WCHAR)); + newSearch = TRUE; + Context->SearchSingleCharMode = TRUE; + } + + if (Context->SearchStringCount > PH_TREENEW_SEARCH_MAXIMUM_LENGTH) + { + // The search string has become too long. Fail the search. + if (!Context->SearchFailed) + { + MessageBeep(0); + Context->SearchFailed = TRUE; + return; + } + } + else if (Context->SearchStringCount == Context->AllocatedSearchString) + { + Context->AllocatedSearchString *= 2; + Context->SearchString = PhReAllocate(Context->SearchString, Context->AllocatedSearchString * sizeof(WCHAR)); + } + + Context->SearchString[Context->SearchStringCount++] = (WCHAR)Character; + + if (Context->SearchString[Context->SearchStringCount - 1] != Context->SearchString[0]) + { + // The user has stopped typing the same character (or never started). Turn single-character + // search off. + Context->SearchSingleCharMode = FALSE; + } + + searchEvent.FoundIndex = -1; + + if (Context->FocusNode) + { + searchEvent.StartIndex = Context->FocusNode->Index; + + if (newSearch || Context->SearchSingleCharMode) + { + // If it's a new search, start at the next item so the user doesn't find the same item + // again. + searchEvent.StartIndex++; + + if (searchEvent.StartIndex == Context->FlatList->Count) + searchEvent.StartIndex = 0; + } + } + else + { + searchEvent.StartIndex = 0; + } + + searchEvent.String.Buffer = Context->SearchString; + + if (!Context->SearchSingleCharMode) + searchEvent.String.Length = Context->SearchStringCount * sizeof(WCHAR); + else + searchEvent.String.Length = sizeof(WCHAR); + + // Give the user a chance to modify how the search is performed. + if (!Context->Callback(Context->Handle, TreeNewIncrementalSearch, &searchEvent, NULL, Context->CallbackContext)) + { + // Use the default search function. + if (!PhTnpDefaultIncrementalSearch(Context, &searchEvent, TRUE, TRUE)) + { + return; + } + } + + if (searchEvent.FoundIndex == -1 && !Context->SearchFailed) + { + // No search result. Beep to indicate an error, and set the flag so we don't beep again. But + // don't beep if the first character was a space, because that's used for other purposes + // elsewhere (see PhTnpProcessNodeKey). + if (searchEvent.String.Buffer[0] != ' ') + { + MessageBeep(0); + } + + Context->SearchFailed = TRUE; + return; + } + + if (searchEvent.FoundIndex < 0 || searchEvent.FoundIndex >= (LONG)Context->FlatList->Count) + return; + + foundNode = Context->FlatList->Items[searchEvent.FoundIndex]; + Context->FocusNode = foundNode; + PhTnpEnsureVisibleNode(Context, searchEvent.FoundIndex); + PhTnpSetHotNode(Context, foundNode, FALSE); + PhTnpSelectRange(Context, searchEvent.FoundIndex, searchEvent.FoundIndex, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + + PhTnpPopTooltip(Context); +} + +BOOLEAN PhTnpDefaultIncrementalSearch( + _In_ PPH_TREENEW_CONTEXT Context, + _Inout_ PPH_TREENEW_SEARCH_EVENT SearchEvent, + _In_ BOOLEAN Partial, + _In_ BOOLEAN Wrap + ) +{ + LONG startIndex; + LONG currentIndex; + LONG foundIndex; + BOOLEAN firstTime; + + if (Context->FlatList->Count == 0) + return FALSE; + if (!Context->FirstColumn) + return FALSE; + + startIndex = SearchEvent->StartIndex; + currentIndex = startIndex; + foundIndex = -1; + firstTime = TRUE; + + while (TRUE) + { + PH_STRINGREF text; + + if (currentIndex >= (LONG)Context->FlatList->Count) + { + if (Wrap) + currentIndex = 0; + else + break; + } + + // We use the firstTime variable instead of a simpler check because we want to include the + // current item in the search. E.g. the current item is the only item beginning with "Z". If + // the user searches for "Z", we want to return the current item as being found. + if (!firstTime && currentIndex == startIndex) + break; + + if (PhTnpGetCellText(Context, Context->FlatList->Items[currentIndex], Context->FirstColumn->Id, &text)) + { + if (Partial) + { + if (PhStartsWithStringRef(&text, &SearchEvent->String, TRUE)) + { + foundIndex = currentIndex; + break; + } + } + else + { + if (PhEqualStringRef(&text, &SearchEvent->String, TRUE)) + { + foundIndex = currentIndex; + break; + } + } + } + + currentIndex++; + firstTime = FALSE; + } + + SearchEvent->FoundIndex = foundIndex; + + return TRUE; +} + +VOID PhTnpUpdateScrollBars( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT clientRect; + LONG width; + LONG height; + LONG contentWidth; + LONG contentHeight; + SCROLLINFO scrollInfo; + LONG oldPosition; + LONG deltaRows; + LONG deltaX; + LOGICAL oldHScrollVisible; + RECT rect; + + clientRect = Context->ClientRect; + width = clientRect.right - Context->FixedWidth; + height = clientRect.bottom - Context->HeaderHeight; + + contentWidth = Context->TotalViewX; + contentHeight = (LONG)Context->FlatList->Count * Context->RowHeight; + + if (contentHeight > height) + { + // We need a vertical scrollbar, so we can't use that area of the screen for content. + width -= Context->VScrollWidth; + } + + if (contentWidth > width) + { + height -= Context->HScrollHeight; + } + + deltaRows = 0; + deltaX = 0; + + // Vertical scroll bar + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_POS; + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + scrollInfo.fMask = SIF_RANGE | SIF_PAGE; + scrollInfo.nMin = 0; + scrollInfo.nMax = Context->FlatList->Count != 0 ? Context->FlatList->Count - 1 : 0; + scrollInfo.nPage = height / Context->RowHeight; + SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE); + + // The scroll position may have changed due to the modified scroll range. + scrollInfo.fMask = SIF_POS; + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + deltaRows = scrollInfo.nPos - oldPosition; + Context->VScrollPosition = scrollInfo.nPos; + + if (contentHeight > height && contentHeight != 0) + { + ShowWindow(Context->VScrollHandle, SW_SHOW); + Context->VScrollVisible = TRUE; + } + else + { + ShowWindow(Context->VScrollHandle, SW_HIDE); + Context->VScrollVisible = FALSE; + } + + // Horizontal scroll bar + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_POS; + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + scrollInfo.fMask = SIF_RANGE | SIF_PAGE; + scrollInfo.nMin = 0; + scrollInfo.nMax = contentWidth != 0 ? contentWidth - 1 : 0; + scrollInfo.nPage = width; + SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE); + + scrollInfo.fMask = SIF_POS; + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + deltaX = scrollInfo.nPos - oldPosition; + Context->HScrollPosition = scrollInfo.nPos; + + oldHScrollVisible = Context->HScrollVisible; + + if (contentWidth > width && contentWidth != 0) + { + ShowWindow(Context->HScrollHandle, SW_SHOW); + Context->HScrollVisible = TRUE; + } + else + { + ShowWindow(Context->HScrollHandle, SW_HIDE); + Context->HScrollVisible = FALSE; + } + + if ((Context->HScrollVisible != oldHScrollVisible) && Context->FixedDividerVisible && Context->AnimateDivider) + { + rect.left = Context->FixedWidth; + rect.top = Context->HeaderHeight; + rect.right = Context->FixedWidth + 1; + rect.bottom = Context->ClientRect.bottom; + InvalidateRect(Context->Handle, &rect, FALSE); + } + + if (deltaRows != 0 || deltaX != 0) + PhTnpProcessScroll(Context, deltaRows, deltaX); + + ShowWindow(Context->FillerBoxHandle, (Context->VScrollVisible && Context->HScrollVisible) ? SW_SHOW : SW_HIDE); +} + +VOID PhTnpScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG DeltaRows, + _In_ LONG DeltaX + ) +{ + SCROLLINFO scrollInfo; + LONG oldPosition; + LONG deltaRows; + LONG deltaX; + + deltaRows = 0; + deltaX = 0; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_POS; + + if (DeltaRows != 0 && Context->VScrollVisible) + { + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + if (DeltaRows == MINLONG) + scrollInfo.nPos = 0; + else if (DeltaRows == MAXLONG) + scrollInfo.nPos = Context->FlatList->Count - 1; + else + scrollInfo.nPos += DeltaRows; + + SetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + Context->VScrollPosition = scrollInfo.nPos; + deltaRows = scrollInfo.nPos - oldPosition; + } + + if (DeltaX != 0 && Context->HScrollVisible) + { + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + oldPosition = scrollInfo.nPos; + + if (DeltaX == MINLONG) + scrollInfo.nPos = 0; + else if (DeltaX == MAXLONG) + scrollInfo.nPos = Context->TotalViewX; + else + scrollInfo.nPos += DeltaX; + + SetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo, TRUE); + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + Context->HScrollPosition = scrollInfo.nPos; + deltaX = scrollInfo.nPos - oldPosition; + } + + if (deltaRows != 0 || deltaX != 0) + PhTnpProcessScroll(Context, deltaRows, deltaX); +} + +VOID PhTnpProcessScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG DeltaRows, + _In_ LONG DeltaX + ) +{ + RECT rect; + LONG deltaY; + + rect.top = Context->HeaderHeight; + rect.bottom = Context->ClientRect.bottom; + + if (DeltaX == 0) + { + rect.left = 0; + rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + ScrollWindowEx( + Context->Handle, + 0, + -DeltaRows * Context->RowHeight, + &rect, + NULL, + NULL, + NULL, + SW_INVALIDATE + ); + } + else + { + // Don't scroll if there are no rows. This is especially important if the user wants us to + // display empty text. + if (Context->FlatList->Count != 0) + { + deltaY = DeltaRows * Context->RowHeight; + + // If we're scrolling vertically as well, we need to scroll the fixed part and the + // normal part separately. + + if (DeltaRows != 0) + { + rect.left = 0; + rect.right = Context->NormalLeft; + ScrollWindowEx( + Context->Handle, + 0, + -deltaY, + &rect, + &rect, + NULL, + NULL, + SW_INVALIDATE + ); + } + + rect.left = Context->NormalLeft; + rect.right = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + ScrollWindowEx( + Context->Handle, + -DeltaX, + -deltaY, + &rect, + &rect, + NULL, + NULL, + SW_INVALIDATE + ); + } + + PhTnpLayoutHeader(Context); + } +} + +BOOLEAN PhTnpCanScroll( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Horizontal, + _In_ BOOLEAN Positive + ) +{ + SCROLLINFO scrollInfo; + + scrollInfo.cbSize = sizeof(SCROLLINFO); + scrollInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; + + if (!Horizontal) + GetScrollInfo(Context->VScrollHandle, SB_CTL, &scrollInfo); + else + GetScrollInfo(Context->HScrollHandle, SB_CTL, &scrollInfo); + + if (Positive) + { + if (scrollInfo.nPage != 0) + scrollInfo.nMax -= scrollInfo.nPage - 1; + + return scrollInfo.nPos < scrollInfo.nMax; + } + else + { + return scrollInfo.nPos > scrollInfo.nMin; + } +} + +VOID PhTnpPaint( + _In_ HWND hwnd, + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT PaintRect + ) +{ + RECT viewRect; + LONG vScrollPosition; + LONG hScrollPosition; + LONG firstRowToUpdate; + LONG lastRowToUpdate; + LONG i; + LONG j; + PPH_TREENEW_NODE node; + PPH_TREENEW_COLUMN column; + RECT rowRect; + LONG x; + BOOLEAN fixedUpdate; + LONG normalUpdateLeftX; + LONG normalUpdateRightX; + LONG normalUpdateLeftIndex; + LONG normalUpdateRightIndex; + LONG normalTotalX; + RECT cellRect; + HBRUSH backBrush; + HRGN oldClipRegion; + + PhTnpInitializeThemeData(Context); + + viewRect = Context->ClientRect; + + if (Context->VScrollVisible) + viewRect.right -= Context->VScrollWidth; + + vScrollPosition = Context->VScrollPosition; + hScrollPosition = Context->HScrollPosition; + + // Calculate the indicies of the first and last rows that need painting. These indicies are + // relative to the top of the view area. + + firstRowToUpdate = (PaintRect->top - Context->HeaderHeight) / Context->RowHeight; + lastRowToUpdate = (PaintRect->bottom - 1 - Context->HeaderHeight) / Context->RowHeight; // minus one since bottom is exclusive + + if (firstRowToUpdate < 0) + firstRowToUpdate = 0; + + rowRect.left = 0; + rowRect.top = Context->HeaderHeight + firstRowToUpdate * Context->RowHeight; + rowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition; + rowRect.bottom = rowRect.top + Context->RowHeight; + + // Change the indicies to absolute row indicies. + + firstRowToUpdate += vScrollPosition; + lastRowToUpdate += vScrollPosition; + + if (lastRowToUpdate >= (LONG)Context->FlatList->Count) + lastRowToUpdate = Context->FlatList->Count - 1; // becomes -1 when there are no items, handled correctly by loop below + + // Determine whether the fixed column needs painting, and which normal columns need painting. + + fixedUpdate = FALSE; + + if (Context->FixedColumnVisible && PaintRect->left < Context->FixedWidth) + fixedUpdate = TRUE; + + x = Context->NormalLeft - hScrollPosition; + normalUpdateLeftX = viewRect.right; + normalUpdateLeftIndex = 0; + normalUpdateRightX = 0; + normalUpdateRightIndex = -1; + + for (j = 0; j < (LONG)Context->NumberOfColumnsByDisplay; j++) + { + column = Context->ColumnsByDisplay[j]; + + if (x + column->Width >= Context->NormalLeft && x + column->Width > PaintRect->left && x < PaintRect->right) + { + if (normalUpdateLeftX > x) + { + normalUpdateLeftX = x; + normalUpdateLeftIndex = j; + } + + if (normalUpdateRightX < x + column->Width) + { + normalUpdateRightX = x + column->Width; + normalUpdateRightIndex = j; + } + } + + x += column->Width; + } + + normalTotalX = x; + + if (normalUpdateRightIndex >= (LONG)Context->NumberOfColumnsByDisplay) + normalUpdateRightIndex = Context->NumberOfColumnsByDisplay - 1; + + // Paint the rows. + + SelectObject(hdc, Context->Font); + SetBkMode(hdc, TRANSPARENT); + + for (i = firstRowToUpdate; i <= lastRowToUpdate; i++) + { + node = Context->FlatList->Items[i]; + + // Prepare the row for drawing. + + PhTnpPrepareRowForDraw(Context, hdc, node); + + if (node->Selected && !Context->ThemeHasItemBackground) + { + // Non-themed background + if (Context->HasFocus) + { + SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + backBrush = GetSysColorBrush(COLOR_HIGHLIGHT); + } + else + { + SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); + backBrush = GetSysColorBrush(COLOR_BTNFACE); + } + } + else + { + SetTextColor(hdc, node->s.DrawForeColor); + SetDCBrushColor(hdc, node->s.DrawBackColor); + backBrush = GetStockObject(DC_BRUSH); + } + + FillRect(hdc, &rowRect, backBrush); + + if (Context->ThemeHasItemBackground) + { + INT stateId; + + // Themed background + + if (node->Selected) + { + if (i == Context->HotNodeIndex) + stateId = TREIS_HOTSELECTED; + else if (!Context->HasFocus) + stateId = TREIS_SELECTEDNOTFOCUS; + else + stateId = TREIS_SELECTED; + } + else + { + if (i == Context->HotNodeIndex) + stateId = TREIS_HOT; + else + stateId = -1; + } + + if (stateId != -1) + { + if (!Context->FixedColumnVisible) + { + rowRect.left = Context->NormalLeft - hScrollPosition; + } + + DrawThemeBackground( + Context->ThemeData, + hdc, + TVP_TREEITEM, + stateId, + &rowRect, + PaintRect + ); + } + } + + // Paint the fixed column. + + cellRect.top = rowRect.top; + cellRect.bottom = rowRect.bottom; + + if (fixedUpdate) + { + cellRect.left = 0; + cellRect.right = Context->FixedWidth; + PhTnpDrawCell(Context, hdc, &cellRect, node, Context->FixedColumn, i, -1); + } + + // Paint the normal columns. + + if (normalUpdateLeftX < normalUpdateRightX) + { + cellRect.left = normalUpdateLeftX; + cellRect.right = cellRect.left; + + oldClipRegion = CreateRectRgn(0, 0, 0, 0); + + if (GetClipRgn(hdc, oldClipRegion) != 1) + { + DeleteObject(oldClipRegion); + oldClipRegion = NULL; + } + + IntersectClipRect(hdc, Context->NormalLeft, cellRect.top, viewRect.right, cellRect.bottom); + + for (j = normalUpdateLeftIndex; j <= normalUpdateRightIndex; j++) + { + column = Context->ColumnsByDisplay[j]; + + cellRect.left = cellRect.right; + cellRect.right = cellRect.left + column->Width; + PhTnpDrawCell(Context, hdc, &cellRect, node, column, i, j); + } + + SelectClipRgn(hdc, oldClipRegion); + + if (oldClipRegion) + { + DeleteObject(oldClipRegion); + } + } + + rowRect.top += Context->RowHeight; + rowRect.bottom += Context->RowHeight; + } + + if (lastRowToUpdate == Context->FlatList->Count - 1) // works even if there are no items + { + // Fill the rest of the space on the bottom with the window color. + rowRect.bottom = viewRect.bottom; + FillRect(hdc, &rowRect, GetSysColorBrush(COLOR_WINDOW)); + } + + if (normalTotalX < viewRect.right && viewRect.right > PaintRect->left && normalTotalX < PaintRect->right) + { + // Fill the rest of the space on the right with the window color. + rowRect.left = normalTotalX; + rowRect.top = Context->HeaderHeight; + rowRect.right = viewRect.right; + rowRect.bottom = viewRect.bottom; + FillRect(hdc, &rowRect, GetSysColorBrush(COLOR_WINDOW)); + } + + if (Context->FlatList->Count == 0 && Context->EmptyText.Length != 0) + { + RECT textRect; + + textRect.left = 20; + textRect.top = Context->HeaderHeight + 10; + textRect.right = viewRect.right - 20; + textRect.bottom = viewRect.bottom - 5; + + SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); + DrawText( + hdc, + Context->EmptyText.Buffer, + (ULONG)Context->EmptyText.Length / 2, + &textRect, + DT_NOPREFIX | DT_CENTER | DT_END_ELLIPSIS + ); + } + + if (Context->FixedDividerVisible && Context->FixedWidth >= PaintRect->left && Context->FixedWidth < PaintRect->right) + { + PhTnpDrawDivider(Context, hdc); + } + + if (Context->DragSelectionActive) + { + PhTnpDrawSelectionRectangle(Context, hdc, &Context->DragRect); + } +} + +VOID PhTnpPrepareRowForDraw( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _Inout_ PPH_TREENEW_NODE Node + ) +{ + if (!Node->s.CachedColorValid) + { + PH_TREENEW_GET_NODE_COLOR getNodeColor; + + getNodeColor.Flags = 0; + getNodeColor.Node = Node; + getNodeColor.BackColor = RGB(0xff, 0xff, 0xff); + getNodeColor.ForeColor = RGB(0x00, 0x00, 0x00); + + if (Context->Callback( + Context->Handle, + TreeNewGetNodeColor, + &getNodeColor, + NULL, + Context->CallbackContext + )) + { + Node->BackColor = getNodeColor.BackColor; + Node->ForeColor = getNodeColor.ForeColor; + Node->UseAutoForeColor = !!(getNodeColor.Flags & TN_AUTO_FORECOLOR); + + if (getNodeColor.Flags & TN_CACHE) + Node->s.CachedColorValid = TRUE; + } + else + { + Node->BackColor = getNodeColor.BackColor; + Node->ForeColor = getNodeColor.ForeColor; + } + } + + Node->s.DrawForeColor = Node->ForeColor; + + if (Node->UseTempBackColor) + Node->s.DrawBackColor = Node->TempBackColor; + else + Node->s.DrawBackColor = Node->BackColor; + + if (!Node->s.CachedFontValid) + { + PH_TREENEW_GET_NODE_FONT getNodeFont; + + getNodeFont.Flags = 0; + getNodeFont.Node = Node; + getNodeFont.Font = NULL; + + if (Context->Callback( + Context->Handle, + TreeNewGetNodeFont, + &getNodeFont, + NULL, + Context->CallbackContext + )) + { + Node->Font = getNodeFont.Font; + + if (getNodeFont.Flags & TN_CACHE) + Node->s.CachedFontValid = TRUE; + } + else + { + Node->Font = NULL; + } + } + + if (!Node->s.CachedIconValid) + { + PH_TREENEW_GET_NODE_ICON getNodeIcon; + + getNodeIcon.Flags = 0; + getNodeIcon.Node = Node; + getNodeIcon.Icon = NULL; + + if (Context->Callback( + Context->Handle, + TreeNewGetNodeIcon, + &getNodeIcon, + NULL, + Context->CallbackContext + )) + { + Node->Icon = getNodeIcon.Icon; + + if (getNodeIcon.Flags & TN_CACHE) + Node->s.CachedIconValid = TRUE; + } + else + { + Node->Icon = NULL; + } + } + + if (Node->UseAutoForeColor || Node->UseTempBackColor) + { + if (PhGetColorBrightness(Node->s.DrawBackColor) > 100) // slightly less than half + Node->s.DrawForeColor = RGB(0x00, 0x00, 0x00); + else + Node->s.DrawForeColor = RGB(0xff, 0xff, 0xff); + } +} + +VOID PhTnpDrawCell( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT CellRect, + _In_ PPH_TREENEW_NODE Node, + _In_ PPH_TREENEW_COLUMN Column, + _In_ LONG RowIndex, + _In_ LONG ColumnIndex + ) +{ + HFONT font; // font to use + HFONT oldFont; + PH_STRINGREF text; // text to draw + RECT textRect; // working rectangle, modified as needed + ULONG textFlags; // DT_* flags + LONG iconVerticalMargin; // top/bottom margin for icons (determined using height of small icon) + + font = Node->Font; + textFlags = Column->TextFlags; + + textRect = *CellRect; + + // Initial margins used by default list view + textRect.left += TNP_CELL_LEFT_MARGIN; + textRect.right -= TNP_CELL_RIGHT_MARGIN; + + // icon margin = (height of row - height of small icon) / 2 + iconVerticalMargin = ((textRect.bottom - textRect.top) - SmallIconHeight) / 2; + + textRect.top += iconVerticalMargin; + textRect.bottom -= iconVerticalMargin; + + if (Column == Context->FirstColumn) + { + BOOLEAN needsClip; + HRGN oldClipRegion; + + textRect.left += Node->Level * SmallIconWidth; + + // The icon may need to be clipped if the column is too small. + needsClip = Column->Width < textRect.left + (Context->CanAnyExpand ? SmallIconWidth : 0) + (Node->Icon ? SmallIconWidth : 0); + + if (needsClip) + { + oldClipRegion = CreateRectRgn(0, 0, 0, 0); + + if (GetClipRgn(hdc, oldClipRegion) != 1) + { + DeleteObject(oldClipRegion); + oldClipRegion = NULL; + } + + // Clip contents to the column. + IntersectClipRect(hdc, CellRect->left, textRect.top, CellRect->right, textRect.bottom); + } + + if (Context->CanAnyExpand) // flag is used so we can avoid indenting when it's a flat list + { + BOOLEAN drewUsingTheme = FALSE; + RECT themeRect; + + if (!Node->s.IsLeaf) + { + // Draw the plus/minus glyph. + + themeRect.left = textRect.left; + themeRect.right = themeRect.left + SmallIconWidth; + themeRect.top = textRect.top; + themeRect.bottom = themeRect.top + SmallIconHeight; + + if (Context->ThemeHasGlyph) + { + INT partId; + INT stateId; + + partId = (RowIndex == Context->HotNodeIndex && Node->s.PlusMinusHot && Context->ThemeHasHotGlyph) ? TVP_HOTGLYPH : TVP_GLYPH; + stateId = Node->Expanded ? GLPS_OPENED : GLPS_CLOSED; + + if (SUCCEEDED(DrawThemeBackground( + Context->ThemeData, + hdc, + partId, + stateId, + &themeRect, + NULL + ))) + drewUsingTheme = TRUE; + } + + if (!drewUsingTheme) + { + ULONG glyphWidth; + ULONG glyphHeight; + RECT glyphRect; + + glyphWidth = SmallIconWidth / 2; + glyphHeight = SmallIconHeight / 2; + + glyphRect.left = textRect.left + (SmallIconWidth - glyphWidth) / 2; + glyphRect.right = glyphRect.left + glyphWidth; + glyphRect.top = textRect.top + (SmallIconHeight - glyphHeight) / 2; + glyphRect.bottom = glyphRect.top + glyphHeight; + + PhTnpDrawPlusMinusGlyph(hdc, &glyphRect, !Node->Expanded); + } + } + + textRect.left += SmallIconWidth; + } + + // Draw the icon. + if (Node->Icon) + { + DrawIconEx( + hdc, + textRect.left, + textRect.top, + Node->Icon, + SmallIconWidth, + SmallIconHeight, + 0, + NULL, + DI_NORMAL + ); + + textRect.left += SmallIconWidth + TNP_ICON_RIGHT_PADDING; + } + + if (needsClip) + { + SelectClipRgn(hdc, oldClipRegion); + + if (oldClipRegion) + DeleteObject(oldClipRegion); + } + + if (textRect.left > textRect.right) + textRect.left = textRect.right; + } + + if (Column->CustomDraw) + { + BOOLEAN result; + PH_TREENEW_CUSTOM_DRAW customDraw; + INT savedDc; + + customDraw.Node = Node; + customDraw.Column = Column; + customDraw.Dc = hdc; + customDraw.CellRect = *CellRect; + customDraw.TextRect = textRect; + + // Fix up the rectangles before giving them to the user. + if (customDraw.CellRect.left > customDraw.CellRect.right) + customDraw.CellRect.left = customDraw.CellRect.right; + if (customDraw.TextRect.left > customDraw.TextRect.right) + customDraw.TextRect.left = customDraw.TextRect.right; + + savedDc = SaveDC(hdc); + result = Context->Callback(Context->Handle, TreeNewCustomDraw, &customDraw, NULL, Context->CallbackContext); + RestoreDC(hdc, savedDc); + + if (result) + return; + } + + if (PhTnpGetCellText(Context, Node, Column->Id, &text)) + { + if (!(textFlags & (DT_PATH_ELLIPSIS | DT_WORD_ELLIPSIS))) + textFlags |= DT_END_ELLIPSIS; + + textFlags |= DT_NOPREFIX | DT_VCENTER | DT_SINGLELINE; + + textRect.top = CellRect->top; + textRect.bottom = CellRect->bottom; + + if (font) + oldFont = SelectObject(hdc, font); + + DrawText( + hdc, + text.Buffer, + (ULONG)text.Length / 2, + &textRect, + textFlags + ); + + if (font) + SelectObject(hdc, oldFont); + } +} + +VOID PhTnpDrawDivider( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc + ) +{ + POINT points[2]; + + if (Context->AnimateDivider) + { + if (Context->DividerHot == 0 && !Context->HScrollVisible) + return; // divider is invisible + + if (Context->DividerHot < 100) + { + BLENDFUNCTION blendFunction; + + // We need to draw and alpha blend the divider. + // We can use the extra column allocated in the buffered context to initially draw the + // divider. + + points[0].x = Context->ClientRect.right; + points[0].y = Context->HeaderHeight; + points[1].x = Context->ClientRect.right; + points[1].y = Context->ClientRect.bottom; + SetDCPenColor(Context->BufferedContext, RGB(0x77, 0x77, 0x77)); + SelectObject(Context->BufferedContext, GetStockObject(DC_PEN)); + Polyline(Context->BufferedContext, points, 2); + + blendFunction.BlendOp = AC_SRC_OVER; + blendFunction.BlendFlags = 0; + blendFunction.AlphaFormat = 0; + + // If the horizontal scroll bar is visible, we need to display a line even if the + // divider is not hot. In this case we increase the base alpha value. + if (!Context->HScrollVisible) + blendFunction.SourceConstantAlpha = (UCHAR)(Context->DividerHot * 255 / 100); + else + blendFunction.SourceConstantAlpha = 55 + (UCHAR)(Context->DividerHot * 2); + + GdiAlphaBlend( + hdc, + Context->FixedWidth, + Context->HeaderHeight, + 1, + Context->ClientRect.bottom - Context->HeaderHeight, + Context->BufferedContext, + Context->ClientRect.right, + Context->HeaderHeight, + 1, + Context->ClientRect.bottom - Context->HeaderHeight, + blendFunction + ); + + return; + } + } + + points[0].x = Context->FixedWidth; + points[0].y = Context->HeaderHeight; + points[1].x = Context->FixedWidth; + points[1].y = Context->ClientRect.bottom; + SetDCPenColor(hdc, RGB(0x77, 0x77, 0x77)); + SelectObject(hdc, GetStockObject(DC_PEN)); + Polyline(hdc, points, 2); +} + +VOID PhTnpDrawPlusMinusGlyph( + _In_ HDC hdc, + _In_ PRECT Rect, + _In_ BOOLEAN Plus + ) +{ + INT savedDc; + ULONG width; + ULONG height; + POINT points[2]; + + savedDc = SaveDC(hdc); + + SelectObject(hdc, GetStockObject(DC_PEN)); + SetDCPenColor(hdc, RGB(0x55, 0x55, 0x55)); + SelectObject(hdc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(hdc, RGB(0xff, 0xff, 0xff)); + + width = Rect->right - Rect->left; + height = Rect->bottom - Rect->top; + + // Draw the rectangle. + Rectangle(hdc, Rect->left, Rect->top, Rect->right + 1, Rect->bottom + 1); + + SetDCPenColor(hdc, RGB(0x00, 0x00, 0x00)); + + // Draw the horizontal line. + points[0].x = Rect->left + 2; + points[0].y = Rect->top + height / 2; + points[1].x = Rect->right - 2 + 1; + points[1].y = points[0].y; + Polyline(hdc, points, 2); + + if (Plus) + { + // Draw the vertical line. + points[0].x = Rect->left + width / 2; + points[0].y = Rect->top + 2; + points[1].x = points[0].x; + points[1].y = Rect->bottom - 2 + 1; + Polyline(hdc, points, 2); + } + + RestoreDC(hdc, savedDc); +} + +VOID PhTnpDrawSelectionRectangle( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc, + _In_ PRECT Rect + ) +{ + RECT rect; + BOOLEAN drewWithAlpha; + + rect = *Rect; + + // MSDN says FrameRect/DrawFocusRect doesn't draw anything if bottom <= top or right <= left. + // That's complete rubbish. + if (rect.right - rect.left == 0 || rect.bottom - rect.top == 0) + return; + + drewWithAlpha = FALSE; + + if (Context->SelectionRectangleAlpha) + { + HDC tempDc; + BITMAPINFOHEADER header; + HBITMAP bitmap; + HBITMAP oldBitmap; + PVOID bits; + RECT tempRect; + BLENDFUNCTION blendFunction; + + tempDc = CreateCompatibleDC(hdc); + + if (tempDc) + { + memset(&header, 0, sizeof(BITMAPINFOHEADER)); + header.biSize = sizeof(BITMAPINFOHEADER); + header.biWidth = 1; + header.biHeight = 1; + header.biPlanes = 1; + header.biBitCount = 24; + bitmap = CreateDIBSection(tempDc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &bits, NULL, 0); + + if (bitmap) + { + // Draw the outline of the selection rectangle. + FrameRect(hdc, &rect, GetSysColorBrush(COLOR_HIGHLIGHT)); + + // Fill in the selection rectangle. + + oldBitmap = SelectObject(tempDc, bitmap); + tempRect.left = 0; + tempRect.top = 0; + tempRect.right = 1; + tempRect.bottom = 1; + FillRect(tempDc, &tempRect, GetSysColorBrush(COLOR_HOTLIGHT)); + + blendFunction.BlendOp = AC_SRC_OVER; + blendFunction.BlendFlags = 0; + blendFunction.SourceConstantAlpha = 70; + blendFunction.AlphaFormat = 0; + + GdiAlphaBlend( + hdc, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + tempDc, + 0, + 0, + 1, + 1, + blendFunction + ); + + drewWithAlpha = TRUE; + + SelectObject(tempDc, oldBitmap); + DeleteObject(bitmap); + } + + DeleteDC(tempDc); + } + } + + if (!drewWithAlpha) + { + DrawFocusRect(hdc, &rect); + } +} + +VOID PhTnpDrawThemedBorder( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ HDC hdc + ) +{ + RECT windowRect; + RECT clientRect; + LONG sizingBorderWidth; + LONG borderX; + LONG borderY; + + GetWindowRect(Context->Handle, &windowRect); + windowRect.right -= windowRect.left; + windowRect.bottom -= windowRect.top; + windowRect.left = 0; + windowRect.top = 0; + + clientRect.left = windowRect.left + Context->SystemEdgeX; + clientRect.top = windowRect.top + Context->SystemEdgeY; + clientRect.right = windowRect.right - Context->SystemEdgeX; + clientRect.bottom = windowRect.bottom - Context->SystemEdgeY; + + // Make sure we don't paint in the client area. + ExcludeClipRect(hdc, clientRect.left, clientRect.top, clientRect.right, clientRect.bottom); + + // Draw the themed border. + DrawThemeBackground(Context->ThemeData, hdc, 0, 0, &windowRect, NULL); + + // Calculate the size of the border we just drew, and fill in the rest of the space if we didn't + // fully paint the region. + + if (SUCCEEDED(GetThemeInt(Context->ThemeData, 0, 0, TMT_SIZINGBORDERWIDTH, &sizingBorderWidth))) + { + borderX = sizingBorderWidth; + borderY = sizingBorderWidth; + } + else + { + borderX = Context->SystemBorderX; + borderY = Context->SystemBorderY; + } + + if (borderX < Context->SystemEdgeX || borderY < Context->SystemEdgeY) + { + windowRect.left += Context->SystemEdgeX - borderX; + windowRect.top += Context->SystemEdgeY - borderY; + windowRect.right -= Context->SystemEdgeX - borderX; + windowRect.bottom -= Context->SystemEdgeY - borderY; + FillRect(hdc, &windowRect, GetSysColorBrush(COLOR_WINDOW)); + } +} + +VOID PhTnpInitializeTooltips( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + TOOLINFO toolInfo; + + Context->TooltipsHandle = CreateWindowEx( + WS_EX_TRANSPARENT, // solves double-click problem + TOOLTIPS_CLASS, + NULL, + WS_POPUP | TTS_NOPREFIX, + 0, + 0, + 0, + 0, + NULL, + NULL, + Context->InstanceHandle, + NULL + ); + + if (!Context->TooltipsHandle) + return; + + // Item tooltips + memset(&toolInfo, 0, sizeof(TOOLINFO)); + toolInfo.cbSize = sizeof(TOOLINFO); + toolInfo.uFlags = TTF_TRANSPARENT; + toolInfo.hwnd = Context->Handle; + toolInfo.uId = TNP_TOOLTIPS_ITEM; + toolInfo.lpszText = LPSTR_TEXTCALLBACK; + toolInfo.lParam = TNP_TOOLTIPS_ITEM; + SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + + // Fixed column tooltips + toolInfo.uFlags = 0; + toolInfo.hwnd = Context->FixedHeaderHandle; + toolInfo.uId = TNP_TOOLTIPS_FIXED_HEADER; + toolInfo.lpszText = LPSTR_TEXTCALLBACK; + toolInfo.lParam = TNP_TOOLTIPS_FIXED_HEADER; + SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + + // Normal column tooltips + toolInfo.uFlags = 0; + toolInfo.hwnd = Context->HeaderHandle; + toolInfo.uId = TNP_TOOLTIPS_HEADER; + toolInfo.lpszText = LPSTR_TEXTCALLBACK; + toolInfo.lParam = TNP_TOOLTIPS_HEADER; + SendMessage(Context->TooltipsHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + + // Hook the header control window procedures so we can forward mouse messages to the tooltip + // control. + Context->FixedHeaderOldWndProc = (WNDPROC)GetWindowLongPtr(Context->FixedHeaderHandle, GWLP_WNDPROC); + SetProp(Context->FixedHeaderHandle, PhTnpMakeContextAtom(), (HANDLE)Context); + SetWindowLongPtr(Context->FixedHeaderHandle, GWLP_WNDPROC, (LONG_PTR)PhTnpHeaderHookWndProc); + Context->HeaderOldWndProc = (WNDPROC)GetWindowLongPtr(Context->HeaderHandle, GWLP_WNDPROC); + SetProp(Context->HeaderHandle, PhTnpMakeContextAtom(), (HANDLE)Context); + SetWindowLongPtr(Context->HeaderHandle, GWLP_WNDPROC, (LONG_PTR)PhTnpHeaderHookWndProc); + + SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT); // no limit + SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->Font, FALSE); + Context->TooltipFont = Context->Font; +} + +VOID PhTnpGetTooltipText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ PPOINT Point, + _Out_ PWSTR *Text + ) +{ + PH_TREENEW_HIT_TEST hitTest; + BOOLEAN unfoldingTooltip; + BOOLEAN unfoldingTooltipFromViewCancelled; + PH_TREENEW_CELL_PARTS parts; + LONG viewRight; + PH_TREENEW_GET_CELL_TOOLTIP getCellTooltip; + + hitTest.Point = *Point; + hitTest.InFlags = TN_TEST_COLUMN | TN_TEST_SUBITEM; + PhTnpHitTest(Context, &hitTest); + + if (Context->DragSelectionActive) + return; + if (!(hitTest.Flags & TN_HIT_ITEM)) + return; + if (hitTest.Flags & (TN_HIT_ITEM_PLUSMINUS | TN_HIT_DIVIDER)) + return; + if (!hitTest.Column) + return; + + if (Context->TooltipIndex != hitTest.Node->Index || Context->TooltipId != hitTest.Column->Id) + { + Context->TooltipIndex = hitTest.Node->Index; + Context->TooltipId = hitTest.Column->Id; + + getCellTooltip.Flags = 0; + getCellTooltip.Node = hitTest.Node; + getCellTooltip.Column = hitTest.Column; + getCellTooltip.Unfolding = FALSE; + PhInitializeEmptyStringRef(&getCellTooltip.Text); + getCellTooltip.Font = Context->Font; + getCellTooltip.MaximumWidth = -1; + + unfoldingTooltip = FALSE; + unfoldingTooltipFromViewCancelled = FALSE; + + if (!(Context->ExtendedFlags & TN_FLAG_NO_UNFOLDING_TOOLTIPS) && + PhTnpGetCellParts(Context, hitTest.Node->Index, hitTest.Column, TN_MEASURE_TEXT, &parts) && + (parts.Flags & TN_PART_CONTENT) && (parts.Flags & TN_PART_TEXT)) + { + viewRight = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + + // Use an unfolding tooltip if the text was truncated within the column, or the text + // extends beyond the view area in either direction. + + if (parts.TextRect.left < parts.ContentRect.left || parts.TextRect.right > parts.ContentRect.right) + { + unfoldingTooltip = TRUE; + } + else if ((!hitTest.Column->Fixed && parts.TextRect.left < Context->NormalLeft) || parts.TextRect.right > viewRight) + { + // Only show view-based unfolding tooltips if the mouse is over the text itself. + if (Point->x >= parts.TextRect.left && Point->x < parts.TextRect.right) + unfoldingTooltip = TRUE; + else + unfoldingTooltipFromViewCancelled = TRUE; + } + + if (unfoldingTooltip) + { + getCellTooltip.Unfolding = TRUE; + getCellTooltip.Text = parts.Text; + getCellTooltip.Font = parts.Font; // try to use the same font as the cell + + Context->TooltipRect = parts.TextRect; + } + } + + Context->Callback(Context->Handle, TreeNewGetCellTooltip, &getCellTooltip, NULL, Context->CallbackContext); + + Context->TooltipUnfolding = getCellTooltip.Unfolding; + + if (getCellTooltip.Text.Buffer && getCellTooltip.Text.Length != 0) + { + PhMoveReference(&Context->TooltipText, PhCreateString2(&getCellTooltip.Text)); + } + else + { + PhClearReference(&Context->TooltipText); + + if (unfoldingTooltipFromViewCancelled) + { + // We may need to show the view-based unfolding tooltip if the mouse moves over the + // text in the future. Reset the index and ID to make sure we keep checking. + Context->TooltipIndex = -1; + Context->TooltipId = -1; + } + } + + Context->NewTooltipFont = getCellTooltip.Font; + + if (!Context->NewTooltipFont) + Context->NewTooltipFont = Context->Font; + + if (getCellTooltip.MaximumWidth <= MAXSHORT) // seems to be the maximum value that the tooltip control supports + SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, getCellTooltip.MaximumWidth); + else + SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT); + } + + if (Context->TooltipText) + *Text = Context->TooltipText->Buffer; +} + +BOOLEAN PhTnpPrepareTooltipShow( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + RECT rect; + + if (Context->TooltipFont != Context->NewTooltipFont) + { + Context->TooltipFont = Context->NewTooltipFont; + SendMessage(Context->TooltipsHandle, WM_SETFONT, (WPARAM)Context->TooltipFont, FALSE); + } + + if (!Context->TooltipUnfolding) + { + SetWindowPos( + Context->TooltipsHandle, + HWND_TOPMOST, + 0, + 0, + 0, + 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_HIDEWINDOW + ); + + return FALSE; + } + + rect = Context->TooltipRect; + SendMessage(Context->TooltipsHandle, TTM_ADJUSTRECT, TRUE, (LPARAM)&rect); + MapWindowPoints(Context->Handle, NULL, (POINT *)&rect, 2); + SetWindowPos( + Context->TooltipsHandle, + HWND_TOPMOST, + rect.left, + rect.top, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_HIDEWINDOW + ); + + return TRUE; +} + +VOID PhTnpPrepareTooltipPop( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + Context->TooltipIndex = -1; + Context->TooltipId = -1; + Context->TooltipColumnId = -1; +} + +VOID PhTnpPopTooltip( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + if (Context->TooltipsHandle) + { + SendMessage(Context->TooltipsHandle, TTM_POP, 0, 0); + PhTnpPrepareTooltipPop(Context); + } +} + +PPH_TREENEW_COLUMN PhTnpHitTestHeader( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Fixed, + _In_ PPOINT Point, + _Out_opt_ PRECT ItemRect + ) +{ + PPH_TREENEW_COLUMN column; + RECT itemRect; + + if (Fixed) + { + if (!Context->FixedColumnVisible) + return NULL; + + column = Context->FixedColumn; + + if (!Header_GetItemRect(Context->FixedHeaderHandle, 0, &itemRect)) + return NULL; + } + else + { + HDHITTESTINFO hitTestInfo; + + hitTestInfo.pt = *Point; + hitTestInfo.flags = 0; + hitTestInfo.iItem = -1; + + if (SendMessage(Context->HeaderHandle, HDM_HITTEST, 0, (LPARAM)&hitTestInfo) != -1 && hitTestInfo.iItem != -1) + { + HDITEM item; + + item.mask = HDI_LPARAM; + + if (!Header_GetItem(Context->HeaderHandle, hitTestInfo.iItem, &item)) + return NULL; + + column = (PPH_TREENEW_COLUMN)item.lParam; + + if (!Header_GetItemRect(Context->HeaderHandle, hitTestInfo.iItem, &itemRect)) + return NULL; + } + else + { + return NULL; + } + } + + if (ItemRect) + *ItemRect = itemRect; + + return column; +} + +VOID PhTnpGetHeaderTooltipText( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ BOOLEAN Fixed, + _In_ PPOINT Point, + _Out_ PWSTR *Text + ) +{ + LOGICAL result; + PPH_TREENEW_COLUMN column; + RECT itemRect; + PWSTR text; + SIZE_T textCount; + HDC hdc; + SIZE textSize; + + column = PhTnpHitTestHeader(Context, Fixed, Point, &itemRect); + + if (!column) + return; + + if (Context->TooltipColumnId != column->Id) + { + // Determine if the tooltip needs to be shown. + + text = column->Text; + textCount = PhCountStringZ(text); + + if (!(hdc = GetDC(Context->Handle))) + return; + + SelectObject(hdc, Context->Font); + + result = GetTextExtentPoint32(hdc, text, (ULONG)textCount, &textSize); + ReleaseDC(Context->Handle, hdc); + + if (!result) + return; + + if (textSize.cx + 6 + 6 <= itemRect.right - itemRect.left) // HACK: Magic values (same as our cell margins?) + return; + + Context->TooltipColumnId = column->Id; + PhMoveReference(&Context->TooltipText, PhCreateStringEx(text, textCount * sizeof(WCHAR))); + } + + *Text = Context->TooltipText->Buffer; + + // Always use the default parameters for column header tooltips. + Context->NewTooltipFont = Context->Font; + Context->TooltipUnfolding = FALSE; + SendMessage(Context->TooltipsHandle, TTM_SETMAXTIPWIDTH, 0, TNP_TOOLTIPS_DEFAULT_MAXIMUM_WIDTH); +} + +PWSTR PhTnpMakeContextAtom( + VOID + ) +{ + PH_DEFINE_MAKE_ATOM(L"PhLib_TreeNewContext"); +} + +LRESULT CALLBACK PhTnpHeaderHookWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_TREENEW_CONTEXT context; + WNDPROC oldWndProc; + + context = (PPH_TREENEW_CONTEXT)GetProp(hwnd, PhTnpMakeContextAtom()); + + if (hwnd == context->FixedHeaderHandle) + oldWndProc = context->FixedHeaderOldWndProc; + else + oldWndProc = context->HeaderOldWndProc; + + switch (uMsg) + { + case WM_DESTROY: + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + + RemoveProp(hwnd, PhTnpMakeContextAtom()); + } + break; + case WM_MOUSEMOVE: + { + POINT point; + PPH_TREENEW_COLUMN column; + ULONG id; + + point.x = GET_X_LPARAM(lParam); + point.y = GET_Y_LPARAM(lParam); + column = PhTnpHitTestHeader(context, hwnd == context->FixedHeaderHandle, &point, NULL); + + if (column) + id = column->Id; + else + id = -1; + + if (context->TooltipColumnId != id) + { + PhTnpPopTooltip(context); + } + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + switch (header->code) + { + case TTN_GETDISPINFO: + { + if (header->hwndFrom == context->TooltipsHandle) + { + NMTTDISPINFO *info = (NMTTDISPINFO *)header; + POINT point; + + PhTnpGetMessagePos(hwnd, &point); + PhTnpGetHeaderTooltipText(context, info->lParam == TNP_TOOLTIPS_FIXED_HEADER, &point, &info->lpszText); + } + } + break; + case TTN_SHOW: + { + if (header->hwndFrom == context->TooltipsHandle) + { + return PhTnpPrepareTooltipShow(context); + } + } + break; + case TTN_POP: + { + if (header->hwndFrom == context->TooltipsHandle) + { + PhTnpPrepareTooltipPop(context); + } + } + break; + } + } + break; + } + + switch (uMsg) + { + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + { + if (context->TooltipsHandle) + { + MSG message; + + message.hwnd = hwnd; + message.message = uMsg; + message.wParam = wParam; + message.lParam = lParam; + SendMessage(context->TooltipsHandle, TTM_RELAYEVENT, 0, (LPARAM)&message); + } + } + break; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +BOOLEAN PhTnpDetectDrag( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY, + _In_ BOOLEAN DispatchMessages, + _Out_opt_ PULONG CancelledByMessage + ) +{ + RECT dragRect; + MSG msg; + + // Capture mouse input and see if the user moves the mouse beyond the drag rectangle. + + dragRect.left = CursorX - Context->SystemDragX; + dragRect.top = CursorY - Context->SystemDragY; + dragRect.right = CursorX + Context->SystemDragX; + dragRect.bottom = CursorY + Context->SystemDragY; + MapWindowPoints(Context->Handle, NULL, (POINT *)&dragRect, 2); + + SetCapture(Context->Handle); + + if (CancelledByMessage) + *CancelledByMessage = 0; + + do + { + // It seems that GetMessage dispatches nonqueued messages directly from kernel-mode, so we + // have to use PeekMessage and WaitMessage in order to process WM_CAPTURECHANGED messages. + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + switch (msg.message) + { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + ReleaseCapture(); + + if (CancelledByMessage) + *CancelledByMessage = msg.message; + + break; + case WM_MOUSEMOVE: + if (msg.pt.x < dragRect.left || msg.pt.x >= dragRect.right || + msg.pt.y < dragRect.top || msg.pt.y >= dragRect.bottom) + { + if (IsWindow(Context->Handle)) + return TRUE; + else + return FALSE; + } + break; + default: + if (DispatchMessages) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + break; + } + } + else + { + WaitMessage(); + } + } while (IsWindow(Context->Handle) && GetCapture() == Context->Handle); + + return FALSE; +} + +VOID PhTnpDragSelect( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ LONG CursorX, + _In_ LONG CursorY + ) +{ + MSG msg; + LONG cursorX; + LONG cursorY; + BOOLEAN originFixed; + RECT dragRect; + RECT oldDragRect; + RECT windowRect; + POINT cursorPoint; + BOOLEAN showContextMenu; + + cursorX = CursorX; + cursorY = CursorY; + originFixed = cursorX < Context->FixedWidth; + + dragRect.left = cursorX; + dragRect.top = cursorY; + dragRect.right = cursorX; + dragRect.bottom = cursorY; + oldDragRect = dragRect; + Context->DragRect = dragRect; + Context->DragSelectionActive = TRUE; + + if (Context->DoubleBuffered) + Context->SelectionRectangleAlpha = TRUE; + // TODO: Make sure the monitor's color depth is sufficient for alpha-blended selection + // rectangles. + + GetWindowRect(Context->Handle, &windowRect); + + cursorPoint.x = windowRect.left + cursorX; + cursorPoint.y = windowRect.top + cursorY; + + showContextMenu = FALSE; + + SetCapture(Context->Handle); + + while (TRUE) + { + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + BOOLEAN leftOrRight; + BOOLEAN aboveOrBelow; + + // If the cursor is outside of the window, generate some messages so the window keeps + // scrolling. + + leftOrRight = cursorPoint.x < windowRect.left || cursorPoint.x > windowRect.right; + aboveOrBelow = cursorPoint.y < windowRect.top || cursorPoint.y > windowRect.bottom; + + if ((Context->VScrollVisible && aboveOrBelow && PhTnpCanScroll(Context, FALSE, cursorPoint.y > windowRect.bottom)) || + (Context->HScrollVisible && leftOrRight && PhTnpCanScroll(Context, TRUE, cursorPoint.x > windowRect.right))) + { + SetCursorPos(cursorPoint.x, cursorPoint.y); + } + else + { + WaitMessage(); + } + + goto EndOfLoop; + } + + cursorPoint = msg.pt; + + switch (msg.message) + { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + ReleaseCapture(); + goto EndOfLoop; + case WM_RBUTTONUP: + ReleaseCapture(); + showContextMenu = TRUE; + goto EndOfLoop; + case WM_MOUSEMOVE: + { + LONG newCursorX; + LONG newCursorY; + LONG deltaRows; + LONG deltaX; + LONG oldVScrollPosition; + LONG oldHScrollPosition; + LONG newDeltaX; + LONG newDeltaY; + LONG viewLeft; + LONG viewTop; + LONG viewRight; + LONG viewBottom; + LONG temp; + RECT totalRect; + + newCursorX = GET_X_LPARAM(msg.lParam); + newCursorY = GET_Y_LPARAM(msg.lParam); + + // Scroll the window if the cursor is outside of it. + + deltaRows = 0; + deltaX = 0; + + if (Context->VScrollVisible) + { + if (cursorPoint.y < windowRect.top) + deltaRows = -(windowRect.top - cursorPoint.y + Context->RowHeight - 1) / Context->RowHeight; // scroll up + else if (cursorPoint.y >= windowRect.bottom) + deltaRows = (cursorPoint.y - windowRect.bottom + Context->RowHeight - 1) / Context->RowHeight; // scroll down + } + + if (Context->HScrollVisible) + { + if (cursorPoint.x < windowRect.left) + deltaX = -(windowRect.left - cursorPoint.x); // scroll left + else if (cursorPoint.x >= windowRect.right) + deltaX = cursorPoint.x - windowRect.right; // scroll right + } + + oldVScrollPosition = Context->VScrollPosition; + oldHScrollPosition = Context->HScrollPosition; + + if (deltaRows != 0 || deltaX != 0) + PhTnpScroll(Context, deltaRows, deltaX); + + newDeltaX = oldHScrollPosition - Context->HScrollPosition; + newDeltaY = (oldVScrollPosition - Context->VScrollPosition) * Context->RowHeight; + + // Adjust our original drag point for the scrolling. + if (!originFixed) + cursorX += newDeltaX; + cursorY += newDeltaY; + + // Adjust the old drag rectangle for the scrolling. + if (!originFixed) + oldDragRect.left += newDeltaX; + oldDragRect.top += newDeltaY; + if (!originFixed) + oldDragRect.right += newDeltaX; + oldDragRect.bottom += newDeltaY; + + // Ensure that the new cursor position is within the content area. + + viewLeft = Context->FixedColumnVisible ? 0 : -Context->HScrollPosition; + viewTop = Context->HeaderHeight - Context->VScrollPosition; + viewRight = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition; + viewBottom = Context->HeaderHeight + ((LONG)Context->FlatList->Count - Context->VScrollPosition) * Context->RowHeight; + + temp = Context->ClientRect.right - (Context->VScrollVisible ? Context->VScrollWidth : 0); + viewRight = max(viewRight, temp); + temp = Context->ClientRect.bottom - ((!Context->FixedColumnVisible && Context->HScrollVisible) ? Context->HScrollHeight : 0); + viewBottom = max(viewBottom, temp); + + if (newCursorX < viewLeft) + newCursorX = viewLeft; + if (newCursorX > viewRight) + newCursorX = viewRight; + if (newCursorY < viewTop) + newCursorY = viewTop; + if (newCursorY > viewBottom) + newCursorY = viewBottom; + + // Create the new drag rectangle. + + if (cursorX < newCursorX) + { + dragRect.left = cursorX; + dragRect.right = newCursorX; + } + else + { + dragRect.left = newCursorX; + dragRect.right = cursorX; + } + + if (cursorY < newCursorY) + { + dragRect.top = cursorY; + dragRect.bottom = newCursorY; + } + else + { + dragRect.top = newCursorY; + dragRect.bottom = cursorY; + } + + // Has anything changed from before? + if (dragRect.left == oldDragRect.left && dragRect.top == oldDragRect.top && + dragRect.right == oldDragRect.right && dragRect.bottom == oldDragRect.bottom) + { + break; + } + + Context->DragRect = dragRect; + + // Process the selection. + totalRect.left = min(dragRect.left, oldDragRect.left); + totalRect.top = min(dragRect.top, oldDragRect.top); + totalRect.right = max(dragRect.right, oldDragRect.right); + totalRect.bottom = max(dragRect.bottom, oldDragRect.bottom); + PhTnpProcessDragSelect(Context, (ULONG)msg.wParam, &oldDragRect, &dragRect, &totalRect); + + // Redraw the drag rectangle. + RedrawWindow(Context->Handle, &totalRect, NULL, RDW_INVALIDATE | RDW_UPDATENOW); + + oldDragRect = dragRect; + } + break; + case WM_MOUSELEAVE: + break; // don't process + case WM_MOUSEWHEEL: + break; // don't process + case WM_KEYDOWN: + if (msg.wParam == VK_ESCAPE) + { + ULONG changedStart; + ULONG changedEnd; + RECT rect; + + PhTnpSelectRange(Context, -1, -1, TN_SELECT_RESET, &changedStart, &changedEnd); + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } + + ReleaseCapture(); + } + break; // don't process + case WM_CHAR: + break; // don't process + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + break; + } + +EndOfLoop: + if (GetCapture() != Context->Handle) + break; + } + + Context->DragSelectionActive = FALSE; + RedrawWindow(Context->Handle, &dragRect, NULL, RDW_INVALIDATE | RDW_UPDATENOW); + + if (showContextMenu) + { + // Display a context menu at the original drag point. + SendMessage(Context->Handle, WM_CONTEXTMENU, (WPARAM)Context->Handle, MAKELPARAM(windowRect.left + CursorX, windowRect.top + CursorY)); + } +} + +VOID PhTnpProcessDragSelect( + _In_ PPH_TREENEW_CONTEXT Context, + _In_ ULONG VirtualKeys, + _In_ PRECT OldRect, + _In_ PRECT NewRect, + _In_ PRECT TotalRect + ) +{ + LONG firstRow; + LONG lastRow; + RECT rowRect; + LONG i; + PPH_TREENEW_NODE node; + LONG changedStart; + LONG changedEnd; + RECT rect; + + // Determine which rows we need to test. The divisions below must be done on positive integers + // to ensure correct rounding. + + firstRow = (TotalRect->top - Context->HeaderHeight + Context->VScrollPosition * Context->RowHeight) / Context->RowHeight; + lastRow = (TotalRect->bottom - 1 - Context->HeaderHeight + Context->VScrollPosition * Context->RowHeight) / Context->RowHeight; + + if (firstRow < 0) + firstRow = 0; + if (lastRow >= (LONG)Context->FlatList->Count) + lastRow = Context->FlatList->Count - 1; + + rowRect.left = 0; + rowRect.top = Context->HeaderHeight + (firstRow - Context->VScrollPosition) * Context->RowHeight; + rowRect.right = Context->NormalLeft + Context->TotalViewX - Context->HScrollPosition; + rowRect.bottom = rowRect.top + Context->RowHeight; + + changedStart = lastRow; + changedEnd = firstRow; + + // Process the rows. + for (i = firstRow; i <= lastRow; i++) + { + BOOLEAN inOldRect; + BOOLEAN inNewRect; + + node = Context->FlatList->Items[i]; + + inOldRect = rowRect.top < OldRect->bottom && rowRect.bottom > OldRect->top && + rowRect.left < OldRect->right && rowRect.right > OldRect->left; + inNewRect = rowRect.top < NewRect->bottom && rowRect.bottom > NewRect->top && + rowRect.left < NewRect->right && rowRect.right > NewRect->left; + + if (VirtualKeys & MK_CONTROL) + { + if (!node->Unselectable && inOldRect != inNewRect) + { + node->Selected = !node->Selected; + + if (changedStart > i) + changedStart = i; + if (changedEnd < i) + changedEnd = i; + } + } + else + { + if (!node->Unselectable && inOldRect != inNewRect) + { + node->Selected = inNewRect; + + if (changedStart > i) + changedStart = i; + if (changedEnd < i) + changedEnd = i; + } + } + + rowRect.top = rowRect.bottom; + rowRect.bottom += Context->RowHeight; + } + + if (changedStart <= changedEnd) + { + Context->Callback(Context->Handle, TreeNewSelectionChanged, NULL, NULL, Context->CallbackContext); + } + + if (PhTnpGetRowRects(Context, changedStart, changedEnd, TRUE, &rect)) + { + InvalidateRect(Context->Handle, &rect, FALSE); + } +} + +VOID PhTnpCreateBufferedContext( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + HDC hdc; + + if (hdc = GetDC(Context->Handle)) + { + Context->BufferedContext = CreateCompatibleDC(hdc); + + if (!Context->BufferedContext) + return; + + Context->BufferedContextRect = Context->ClientRect; + Context->BufferedBitmap = CreateCompatibleBitmap( + hdc, + Context->BufferedContextRect.right + 1, // leave one extra pixel for divider animation + Context->BufferedContextRect.bottom + ); + + if (!Context->BufferedBitmap) + { + DeleteDC(Context->BufferedContext); + Context->BufferedContext = NULL; + return; + } + + ReleaseDC(Context->Handle, hdc); + Context->BufferedOldBitmap = SelectObject(Context->BufferedContext, Context->BufferedBitmap); + } +} + +VOID PhTnpDestroyBufferedContext( + _In_ PPH_TREENEW_CONTEXT Context + ) +{ + // The original bitmap must be selected back into the context, otherwise the bitmap can't be + // deleted. + SelectObject(Context->BufferedContext, Context->BufferedOldBitmap); + DeleteObject(Context->BufferedBitmap); + DeleteDC(Context->BufferedContext); + + Context->BufferedContext = NULL; + Context->BufferedBitmap = NULL; +} + +VOID PhTnpGetMessagePos( + _In_ HWND hwnd, + _Out_ PPOINT ClientPoint + ) +{ + ULONG position; + POINT point; + + position = GetMessagePos(); + point.x = GET_X_LPARAM(position); + point.y = GET_Y_LPARAM(position); + ScreenToClient(hwnd, &point); + + *ClientPoint = point; +} diff --git a/phlib/util.c b/phlib/util.c new file mode 100644 index 0000000..b315609 --- /dev/null +++ b/phlib/util.c @@ -0,0 +1,4970 @@ +/* + * Process Hacker - + * general support functions + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#define CINTERFACE +#define COBJMACROS +#include +#undef CINTERFACE +#undef COBJMACROS +#include "md5.h" +#include "sha.h" + +// We may want to change this for debugging purposes. +#define PHP_USE_IFILEDIALOG (WINDOWS_HAS_IFILEDIALOG) + +typedef BOOLEAN (NTAPI *_WinStationQueryInformationW)( + _In_opt_ HANDLE ServerHandle, + _In_ ULONG LogonId, + _In_ WINSTATIONINFOCLASS WinStationInformationClass, + _Out_writes_bytes_(WinStationInformationLength) PVOID WinStationInformation, + _In_ ULONG WinStationInformationLength, + _Out_ PULONG ReturnLength + ); + +typedef BOOL (WINAPI *_CreateEnvironmentBlock)( + _Out_ LPVOID *lpEnvironment, + _In_opt_ HANDLE hToken, + _In_ BOOL bInherit + ); + +typedef BOOL (WINAPI *_DestroyEnvironmentBlock)( + _In_ LPVOID lpEnvironment + ); + +DECLSPEC_SELECTANY WCHAR *PhSizeUnitNames[7] = { L"B", L"kB", L"MB", L"GB", L"TB", L"PB", L"EB" }; +DECLSPEC_SELECTANY ULONG PhMaxSizeUnit = MAXULONG32; + +/** + * Ensures a rectangle is positioned within the specified bounds. + * + * \param Rectangle The rectangle to be adjusted. + * \param Bounds The bounds. + * + * \remarks If the rectangle is too large to fit inside the bounds, it is positioned at the top-left + * of the bounds. + */ +VOID PhAdjustRectangleToBounds( + _Inout_ PPH_RECTANGLE Rectangle, + _In_ PPH_RECTANGLE Bounds + ) +{ + if (Rectangle->Left + Rectangle->Width > Bounds->Left + Bounds->Width) + Rectangle->Left = Bounds->Left + Bounds->Width - Rectangle->Width; + if (Rectangle->Top + Rectangle->Height > Bounds->Top + Bounds->Height) + Rectangle->Top = Bounds->Top + Bounds->Height - Rectangle->Height; + + if (Rectangle->Left < Bounds->Left) + Rectangle->Left = Bounds->Left; + if (Rectangle->Top < Bounds->Top) + Rectangle->Top = Bounds->Top; +} + +/** + * Positions a rectangle in the center of the specified bounds. + * + * \param Rectangle The rectangle to be adjusted. + * \param Bounds The bounds. + */ +VOID PhCenterRectangle( + _Inout_ PPH_RECTANGLE Rectangle, + _In_ PPH_RECTANGLE Bounds + ) +{ + Rectangle->Left = Bounds->Left + (Bounds->Width - Rectangle->Width) / 2; + Rectangle->Top = Bounds->Top + (Bounds->Height - Rectangle->Height) / 2; +} + +/** + * Ensures a rectangle is positioned within the working area of the specified window's monitor. + * + * \param hWnd A handle to a window. If NULL, the monitor closest to \a Rectangle is used. + * \param Rectangle The rectangle to be adjusted. + */ +VOID PhAdjustRectangleToWorkingArea( + _In_opt_ HWND hWnd, + _Inout_ PPH_RECTANGLE Rectangle + ) +{ + HMONITOR monitor; + MONITORINFO monitorInfo = { sizeof(monitorInfo) }; + + if (hWnd) + { + monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + } + else + { + RECT rect; + + rect = PhRectangleToRect(*Rectangle); + monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST); + } + + if (GetMonitorInfo(monitor, &monitorInfo)) + { + PH_RECTANGLE bounds; + + bounds = PhRectToRectangle(monitorInfo.rcWork); + PhAdjustRectangleToBounds(Rectangle, &bounds); + } +} + +/** + * Centers a window. + * + * \param WindowHandle The window to center. + * \param ParentWindowHandle If specified, the window will be positioned at the center of this + * window. Otherwise, the window will be positioned at the center of the monitor. + */ +VOID PhCenterWindow( + _In_ HWND WindowHandle, + _In_opt_ HWND ParentWindowHandle + ) +{ + if (ParentWindowHandle) + { + RECT rect, parentRect; + PH_RECTANGLE rectangle, parentRectangle; + + GetWindowRect(WindowHandle, &rect); + GetWindowRect(ParentWindowHandle, &parentRect); + rectangle = PhRectToRectangle(rect); + parentRectangle = PhRectToRectangle(parentRect); + + PhCenterRectangle(&rectangle, &parentRectangle); + PhAdjustRectangleToWorkingArea(WindowHandle, &rectangle); + MoveWindow(WindowHandle, rectangle.Left, rectangle.Top, + rectangle.Width, rectangle.Height, FALSE); + } + else + { + MONITORINFO monitorInfo = { sizeof(monitorInfo) }; + + if (GetMonitorInfo( + MonitorFromWindow(WindowHandle, MONITOR_DEFAULTTONEAREST), + &monitorInfo + )) + { + RECT rect; + PH_RECTANGLE rectangle; + PH_RECTANGLE bounds; + + GetWindowRect(WindowHandle, &rect); + rectangle = PhRectToRectangle(rect); + bounds = PhRectToRectangle(monitorInfo.rcWork); + + PhCenterRectangle(&rectangle, &bounds); + MoveWindow(WindowHandle, rectangle.Left, rectangle.Top, + rectangle.Width, rectangle.Height, FALSE); + } + } +} + +/** + * References an array of objects. + * + * \param Objects An array of objects. + * \param NumberOfObjects The number of elements in \a Objects. + */ +VOID PhReferenceObjects( + _In_reads_(NumberOfObjects) PVOID *Objects, + _In_ ULONG NumberOfObjects + ) +{ + ULONG i; + + for (i = 0; i < NumberOfObjects; i++) + PhReferenceObject(Objects[i]); +} + +/** + * Dereferences an array of objects. + * + * \param Objects An array of objects. + * \param NumberOfObjects The number of elements in \a Objects. + */ +VOID PhDereferenceObjects( + _In_reads_(NumberOfObjects) PVOID *Objects, + _In_ ULONG NumberOfObjects + ) +{ + ULONG i; + + for (i = 0; i < NumberOfObjects; i++) + PhDereferenceObject(Objects[i]); +} + +/** + * Gets a string stored in a DLL's message table. + * + * \param DllHandle The base address of the DLL. + * \param MessageTableId The identifier of the message table. + * \param MessageLanguageId The language ID of the message. + * \param MessageId The identifier of the message. + * + * \return A pointer to a string containing the message. You must free the string using + * PhDereferenceObject() when you no longer need it. + */ +PPH_STRING PhGetMessage( + _In_ PVOID DllHandle, + _In_ ULONG MessageTableId, + _In_ ULONG MessageLanguageId, + _In_ ULONG MessageId + ) +{ + NTSTATUS status; + PMESSAGE_RESOURCE_ENTRY messageEntry; + + status = RtlFindMessage( + DllHandle, + MessageTableId, + MessageLanguageId, + MessageId, + &messageEntry + ); + + // Try using the system LANGID. + if (!NT_SUCCESS(status)) + { + status = RtlFindMessage( + DllHandle, + MessageTableId, + GetSystemDefaultLangID(), + MessageId, + &messageEntry + ); + } + + // Try using U.S. English. + if (!NT_SUCCESS(status)) + { + status = RtlFindMessage( + DllHandle, + MessageTableId, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + MessageId, + &messageEntry + ); + } + + if (!NT_SUCCESS(status)) + return NULL; + + if (messageEntry->Flags & MESSAGE_RESOURCE_UNICODE) + { + return PhCreateStringEx((PWCHAR)messageEntry->Text, messageEntry->Length); + } + else + { + return PhConvertMultiByteToUtf16Ex((PCHAR)messageEntry->Text, messageEntry->Length); + } +} + +/** + * Gets a message describing a NT status value. + * + * \param Status The NT status value. + */ +PPH_STRING PhGetNtMessage( + _In_ NTSTATUS Status + ) +{ + PPH_STRING message; + + if (!NT_NTWIN32(Status)) + message = PhGetMessage(GetModuleHandle(L"ntdll.dll"), 0xb, GetUserDefaultLangID(), (ULONG)Status); + else + message = PhGetWin32Message(WIN32_FROM_NTSTATUS(Status)); + + if (PhIsNullOrEmptyString(message)) + return message; + + PhTrimToNullTerminatorString(message); + + // Remove any trailing newline. + if (message->Length >= 2 * sizeof(WCHAR) && + message->Buffer[message->Length / sizeof(WCHAR) - 2] == '\r' && + message->Buffer[message->Length / sizeof(WCHAR) - 1] == '\n') + { + PhMoveReference(&message, PhCreateStringEx(message->Buffer, message->Length - 2 * sizeof(WCHAR))); + } + + // Fix those messages which are formatted like: + // {Asdf}\r\nAsdf asdf asdf... + if (message->Buffer[0] == '{') + { + PH_STRINGREF titlePart; + PH_STRINGREF remainingPart; + + if (PhSplitStringRefAtChar(&message->sr, '\n', &titlePart, &remainingPart)) + PhMoveReference(&message, PhCreateString2(&remainingPart)); + } + + return message; +} + +/** + * Gets a message describing a Win32 error code. + * + * \param Result The Win32 error code. + */ +PPH_STRING PhGetWin32Message( + _In_ ULONG Result + ) +{ + PPH_STRING message; + + message = PhGetMessage(GetModuleHandle(L"kernel32.dll"), 0xb, GetUserDefaultLangID(), Result); + + if (message) + PhTrimToNullTerminatorString(message); + + // Remove any trailing newline. + if (message && message->Length >= 2 * sizeof(WCHAR) && + message->Buffer[message->Length / sizeof(WCHAR) - 2] == '\r' && + message->Buffer[message->Length / sizeof(WCHAR) - 1] == '\n') + { + PhMoveReference(&message, PhCreateStringEx(message->Buffer, message->Length - 2 * sizeof(WCHAR))); + } + + return message; +} + +/** + * Displays a message box. + * + * \param hWnd The owner window of the message box. + * \param Type The type of message box to display. + * \param Format A format string. + * + * \return The user's response. + */ +INT PhShowMessage( + _In_ HWND hWnd, + _In_ ULONG Type, + _In_ PWSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + + return PhShowMessage_V(hWnd, Type, Format, argptr); +} + +INT PhShowMessage_V( + _In_ HWND hWnd, + _In_ ULONG Type, + _In_ PWSTR Format, + _In_ va_list ArgPtr + ) +{ + INT result; + PPH_STRING message; + + message = PhFormatString_V(Format, ArgPtr); + + if (!message) + return -1; + + result = MessageBox(hWnd, message->Buffer, PhApplicationName, Type); + PhDereferenceObject(message); + + return result; +} + +PPH_STRING PhGetStatusMessage( + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + if (!Win32Result) + { + // In some cases we want the simple Win32 messages. + if ( + Status == STATUS_ACCESS_DENIED || + Status == STATUS_ACCESS_VIOLATION + ) + { + Win32Result = RtlNtStatusToDosError(Status); + } + // Process NTSTATUS values with the NT-Win32 facility. + else if (NT_NTWIN32(Status)) + { + Win32Result = WIN32_FROM_NTSTATUS(Status); + } + } + + if (!Win32Result) + return PhGetNtMessage(Status); + else + return PhGetWin32Message(Win32Result); +} + +/** + * Displays an error message for a NTSTATUS value or Win32 error code. + * + * \param hWnd The owner window of the message box. + * \param Message A message describing the operation that failed. + * \param Status A NTSTATUS value, or 0 if there is none. + * \param Win32Result A Win32 error code, or 0 if there is none. + */ +VOID PhShowStatus( + _In_ HWND hWnd, + _In_opt_ PWSTR Message, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + PPH_STRING statusMessage; + + statusMessage = PhGetStatusMessage(Status, Win32Result); + + if (!statusMessage) + { + if (Message) + { + PhShowError(hWnd, L"%s.", Message); + } + else + { + PhShowError(hWnd, L"Unable to perform the operation."); + } + + return; + } + + if (Message) + { + PhShowError(hWnd, L"%s: %s", Message, statusMessage->Buffer); + } + else + { + PhShowError(hWnd, L"%s", statusMessage->Buffer); + } + + PhDereferenceObject(statusMessage); +} + +/** + * Displays an error message for a NTSTATUS value or Win32 error code, and allows the user to cancel + * the current operation. + * + * \param hWnd The owner window of the message box. + * \param Message A message describing the operation that failed. + * \param Status A NTSTATUS value, or 0 if there is none. + * \param Win32Result A Win32 error code, or 0 if there is none. + * + * \return TRUE if the user wishes to continue with the current operation, otherwise FALSE. + */ +BOOLEAN PhShowContinueStatus( + _In_ HWND hWnd, + _In_opt_ PWSTR Message, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + PPH_STRING statusMessage; + INT result; + + statusMessage = PhGetStatusMessage(Status, Win32Result); + + if (!statusMessage) + { + if (Message) + { + result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s.", Message); + } + else + { + result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"Unable to perform the operation."); + } + + return result == IDOK; + } + + if (Message) + { + result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s: %s", Message, statusMessage->Buffer); + } + else + { + result = PhShowMessage(hWnd, MB_ICONERROR | MB_OKCANCEL, L"%s", statusMessage->Buffer); + } + + PhDereferenceObject(statusMessage); + + return result == IDOK; +} + +/** + * Displays a confirmation message. + * + * \param hWnd The owner window of the message box. + * \param Verb A verb describing the operation, e.g. "terminate". + * \param Object The object of the operation, e.g. "the process". + * \param Message A message describing the operation. + * \param Warning TRUE to display the confirmation message as a warning, otherwise FALSE. + * + * \return TRUE if the user wishes to continue, otherwise FALSE. + */ +BOOLEAN PhShowConfirmMessage( + _In_ HWND hWnd, + _In_ PWSTR Verb, + _In_ PWSTR Object, + _In_opt_ PWSTR Message, + _In_ BOOLEAN Warning + ) +{ + PPH_STRING verb; + PPH_STRING verbCaps; + PPH_STRING action; + + // Make sure the verb is all lowercase. + verb = PhaLowerString(PhaCreateString(Verb)); + + // "terminate" -> "Terminate" + verbCaps = PhaDuplicateString(verb); + if (verbCaps->Length > 0) verbCaps->Buffer[0] = towupper(verbCaps->Buffer[0]); + + // "terminate", "the process" -> "terminate the process" + action = PhaConcatStrings(3, verb->Buffer, L" ", Object); + + if (TaskDialogIndirect_Import()) + { + TASKDIALOGCONFIG config = { sizeof(config) }; + TASKDIALOG_BUTTON buttons[2]; + INT button; + + config.hwndParent = hWnd; + config.hInstance = PhLibImageBase; + config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | (IsWindowVisible(hWnd) ? TDF_POSITION_RELATIVE_TO_WINDOW : 0); + config.pszWindowTitle = PhApplicationName; + config.pszMainIcon = Warning ? TD_WARNING_ICON : NULL; + config.pszMainInstruction = PhaConcatStrings(3, L"Do you want to ", action->Buffer, L"?")->Buffer; + + if (Message) + config.pszContent = PhaConcatStrings2(Message, L" Are you sure you want to continue?")->Buffer; + + buttons[0].nButtonID = IDYES; + buttons[0].pszButtonText = verbCaps->Buffer; + buttons[1].nButtonID = IDNO; + buttons[1].pszButtonText = L"Cancel"; + + config.cButtons = 2; + config.pButtons = buttons; + config.nDefaultButton = IDYES; + + if (TaskDialogIndirect_Import()( + &config, + &button, + NULL, + NULL + ) == S_OK) + { + return button == IDYES; + } + else + { + return FALSE; + } + } + else + { + return PhShowMessage( + hWnd, + MB_YESNO | MB_ICONWARNING, + L"Are you sure you want to %s?", + action->Buffer + ) == IDYES; + } +} + +/** + * Finds an integer in an array of string-integer pairs. + * + * \param KeyValuePairs The array. + * \param SizeOfKeyValuePairs The size of the array, in bytes. + * \param String The string to search for. + * \param Integer A variable which receives the found integer. + * + * \return TRUE if the string was found, otherwise FALSE. + * + * \remarks The search is case-sensitive. + */ +BOOLEAN PhFindIntegerSiKeyValuePairs( + _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, + _In_ ULONG SizeOfKeyValuePairs, + _In_ PWSTR String, + _Out_ PULONG Integer + ) +{ + ULONG i; + + for (i = 0; i < SizeOfKeyValuePairs / sizeof(PH_KEY_VALUE_PAIR); i++) + { + if (PhEqualStringZ(KeyValuePairs[i].Key, String, TRUE)) + { + *Integer = PtrToUlong(KeyValuePairs[i].Value); + return TRUE; + } + } + + return FALSE; +} + +/** + * Finds a string in an array of string-integer pairs. + * + * \param KeyValuePairs The array. + * \param SizeOfKeyValuePairs The size of the array, in bytes. + * \param Integer The integer to search for. + * \param String A variable which receives the found string. + * + * \return TRUE if the integer was found, otherwise FALSE. + */ +BOOLEAN PhFindStringSiKeyValuePairs( + _In_ PPH_KEY_VALUE_PAIR KeyValuePairs, + _In_ ULONG SizeOfKeyValuePairs, + _In_ ULONG Integer, + _Out_ PWSTR *String + ) +{ + ULONG i; + + for (i = 0; i < SizeOfKeyValuePairs / sizeof(PH_KEY_VALUE_PAIR); i++) + { + if (PtrToUlong(KeyValuePairs[i].Value) == Integer) + { + *String = (PWSTR)KeyValuePairs[i].Key; + return TRUE; + } + } + + return FALSE; +} + +/** + * Creates a random (type 4) UUID. + * + * \param Guid The destination UUID. + */ +VOID PhGenerateGuid( + _Out_ PGUID Guid + ) +{ + static ULONG seed = 0; + // The top/sign bit is always unusable for RtlRandomEx (the result is always unsigned), so we'll + // take the bottom 24 bits. We need 128 bits in total, so we'll call the function 6 times. + ULONG random[6]; + ULONG i; + + for (i = 0; i < 6; i++) + random[i] = RtlRandomEx(&seed); + + // random[0] is usable + *(PUSHORT)&Guid->Data1 = (USHORT)random[0]; + // top byte from random[0] is usable + *((PUSHORT)&Guid->Data1 + 1) = (USHORT)((random[0] >> 16) | (random[1] & 0xff)); + // top 2 bytes from random[1] are usable + Guid->Data2 = (SHORT)(random[1] >> 8); + // random[2] is usable + Guid->Data3 = (SHORT)random[2]; + // top byte from random[2] is usable + *(PUSHORT)&Guid->Data4[0] = (USHORT)((random[2] >> 16) | (random[3] & 0xff)); + // top 2 bytes from random[3] are usable + *(PUSHORT)&Guid->Data4[2] = (USHORT)(random[3] >> 8); + // random[4] is usable + *(PUSHORT)&Guid->Data4[4] = (USHORT)random[4]; + // top byte from random[4] is usable + *(PUSHORT)&Guid->Data4[6] = (USHORT)((random[4] >> 16) | (random[5] & 0xff)); + + ((PGUID_EX)Guid)->s2.Version = GUID_VERSION_RANDOM; + ((PGUID_EX)Guid)->s2.Variant &= ~GUID_VARIANT_STANDARD_MASK; + ((PGUID_EX)Guid)->s2.Variant |= GUID_VARIANT_STANDARD; +} + +FORCEINLINE VOID PhpReverseGuid( + _Inout_ PGUID Guid + ) +{ + Guid->Data1 = _byteswap_ulong(Guid->Data1); + Guid->Data2 = _byteswap_ushort(Guid->Data2); + Guid->Data3 = _byteswap_ushort(Guid->Data3); +} + +/** + * Creates a name-based (type 3 or 5) UUID. + * + * \param Guid The destination UUID. + * \param Namespace The UUID of the namespace. + * \param Name The input name. + * \param NameLength The length of the input name, not including the null terminator if present. + * \param Version The type of UUID. + * \li \c GUID_VERSION_MD5 Creates a type 3, MD5-based UUID. + * \li \c GUID_VERSION_SHA1 Creates a type 5, SHA1-based UUID. + */ +VOID PhGenerateGuidFromName( + _Out_ PGUID Guid, + _In_ PGUID Namespace, + _In_ PCHAR Name, + _In_ ULONG NameLength, + _In_ UCHAR Version + ) +{ + PGUID_EX guid; + PUCHAR data; + ULONG dataLength; + GUID ns; + UCHAR hash[20]; + + // Convert the namespace to big endian. + + ns = *Namespace; + PhpReverseGuid(&ns); + + // Compute the hash of the namespace concatenated with the name. + + dataLength = 16 + NameLength; + data = PhAllocate(dataLength); + memcpy(data, &ns, 16); + memcpy(&data[16], Name, NameLength); + + if (Version == GUID_VERSION_MD5) + { + MD5_CTX context; + + MD5Init(&context); + MD5Update(&context, data, dataLength); + MD5Final(&context); + + memcpy(hash, context.digest, 16); + } + else + { + A_SHA_CTX context; + + A_SHAInit(&context); + A_SHAUpdate(&context, data, dataLength); + A_SHAFinal(&context, hash); + + Version = GUID_VERSION_SHA1; + } + + PhFree(data); + + guid = (PGUID_EX)Guid; + memcpy(guid->Data, hash, 16); + PhpReverseGuid(&guid->Guid); + guid->s2.Version = Version; + guid->s2.Variant &= ~GUID_VARIANT_STANDARD_MASK; + guid->s2.Variant |= GUID_VARIANT_STANDARD; +} + +/** + * Fills a buffer with random uppercase alphabetical characters. + * + * \param Buffer The buffer to fill with random characters, plus a null terminator. + * \param Count The number of characters available in the buffer, including space for the null + * terminator. + */ +VOID PhGenerateRandomAlphaString( + _Out_writes_z_(Count) PWSTR Buffer, + _In_ ULONG Count + ) +{ + static ULONG seed = 0; + ULONG i; + + if (Count == 0) + return; + + for (i = 0; i < Count - 1; i++) + { + Buffer[i] = 'A' + (RtlRandomEx(&seed) % 26); + } + + Buffer[Count - 1] = 0; +} + +/** + * Modifies a string to ensure it is within the specified length. + * + * \param String The input string. + * \param DesiredCount The desired number of characters in the new string. If necessary, parts of + * the string are replaced with an ellipsis to indicate characters have been omitted. + * + * \return The new string. + */ +PPH_STRING PhEllipsisString( + _In_ PPH_STRING String, + _In_ ULONG DesiredCount + ) +{ + if ( + (ULONG)String->Length / 2 <= DesiredCount || + DesiredCount < 3 + ) + { + return PhReferenceObject(String); + } + else + { + PPH_STRING string; + + string = PhCreateStringEx(NULL, DesiredCount * 2); + memcpy(string->Buffer, String->Buffer, (DesiredCount - 3) * 2); + memcpy(&string->Buffer[DesiredCount - 3], L"...", 6); + + return string; + } +} + +/** + * Modifies a string to ensure it is within the specified length, parsing the string as a path. + * + * \param String The input string. + * \param DesiredCount The desired number of characters in the new string. If necessary, parts of + * the string are replaced with an ellipsis to indicate characters have been omitted. + * + * \return The new string. + */ +PPH_STRING PhEllipsisStringPath( + _In_ PPH_STRING String, + _In_ ULONG DesiredCount + ) +{ + ULONG_PTR secondPartIndex; + + secondPartIndex = PhFindLastCharInString(String, 0, L'\\'); + + if (secondPartIndex == -1) + secondPartIndex = PhFindLastCharInString(String, 0, L'/'); + if (secondPartIndex == -1) + return PhEllipsisString(String, DesiredCount); + + if ( + String->Length / 2 <= DesiredCount || + DesiredCount < 3 + ) + { + return PhReferenceObject(String); + } + else + { + PPH_STRING string; + ULONG_PTR firstPartCopyLength; + ULONG_PTR secondPartCopyLength; + + string = PhCreateStringEx(NULL, DesiredCount * 2); + secondPartCopyLength = String->Length / 2 - secondPartIndex; + + // Check if we have enough space for the entire second part of the string. + if (secondPartCopyLength + 3 <= DesiredCount) + { + // Yes, copy part of the first part and the entire second part. + firstPartCopyLength = DesiredCount - secondPartCopyLength - 3; + } + else + { + // No, copy part of both, from the beginning of the first part and the end of the second + // part. + firstPartCopyLength = (DesiredCount - 3) / 2; + secondPartCopyLength = DesiredCount - 3 - firstPartCopyLength; + secondPartIndex = String->Length / 2 - secondPartCopyLength; + } + + memcpy( + string->Buffer, + String->Buffer, + firstPartCopyLength * 2 + ); + memcpy( + &string->Buffer[firstPartCopyLength], + L"...", + 6 + ); + memcpy( + &string->Buffer[firstPartCopyLength + 3], + &String->Buffer[secondPartIndex], + secondPartCopyLength * 2 + ); + + return string; + } +} + +FORCEINLINE BOOLEAN PhpMatchWildcards( + _In_ PWSTR Pattern, + _In_ PWSTR String, + _In_ BOOLEAN IgnoreCase + ) +{ + PWCHAR s, p; + BOOLEAN star = FALSE; + + // Code is from http://xoomer.virgilio.it/acantato/dev/wildcard/wildmatch.html + +LoopStart: + for (s = String, p = Pattern; *s; s++, p++) + { + switch (*p) + { + case '?': + break; + case '*': + star = TRUE; + String = s; + Pattern = p; + + do + { + Pattern++; + } while (*Pattern == '*'); + + if (!*Pattern) return TRUE; + + goto LoopStart; + default: + if (!IgnoreCase) + { + if (*s != *p) + goto StarCheck; + } + else + { + if (towupper(*s) != towupper(*p)) + goto StarCheck; + } + + break; + } + } + + while (*p == '*') + p++; + + return (!*p); + +StarCheck: + if (!star) + return FALSE; + + String++; + goto LoopStart; +} + +/** + * Matches a pattern against a string. + * + * \param Pattern The pattern, which can contain asterisks and question marks. + * \param String The string which the pattern is matched against. + * \param IgnoreCase Whether to ignore character cases. + */ +BOOLEAN PhMatchWildcards( + _In_ PWSTR Pattern, + _In_ PWSTR String, + _In_ BOOLEAN IgnoreCase + ) +{ + if (!IgnoreCase) + return PhpMatchWildcards(Pattern, String, FALSE); + else + return PhpMatchWildcards(Pattern, String, TRUE); +} + +/** + * Escapes a string for prefix characters (ampersands). + * + * \param String The string to process. + * + * \return The escaped string, with each ampersand replaced by 2 ampersands. + */ +PPH_STRING PhEscapeStringForMenuPrefix( + _In_ PPH_STRINGREF String + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T i; + SIZE_T length; + PWCHAR runStart; + SIZE_T runCount; + + length = String->Length / sizeof(WCHAR); + runStart = NULL; + + PhInitializeStringBuilder(&stringBuilder, String->Length); + + for (i = 0; i < length; i++) + { + switch (String->Buffer[i]) + { + case '&': + if (runStart) + { + PhAppendStringBuilderEx(&stringBuilder, runStart, runCount * sizeof(WCHAR)); + runStart = NULL; + } + + PhAppendStringBuilder2(&stringBuilder, L"&&"); + + break; + default: + if (runStart) + { + runCount++; + } + else + { + runStart = &String->Buffer[i]; + runCount = 1; + } + + break; + } + } + + if (runStart) + PhAppendStringBuilderEx(&stringBuilder, runStart, runCount * sizeof(WCHAR)); + + return PhFinalStringBuilderString(&stringBuilder); +} + +/** + * Compares two strings, ignoring prefix characters (ampersands). + * + * \param A The first string. + * \param B The second string. + * \param IgnoreCase Whether to ignore character cases. + * \param MatchIfPrefix Specify TRUE to return 0 when \a A is a prefix of \a B. + */ +LONG PhCompareUnicodeStringZIgnoreMenuPrefix( + _In_ PWSTR A, + _In_ PWSTR B, + _In_ BOOLEAN IgnoreCase, + _In_ BOOLEAN MatchIfPrefix + ) +{ + WCHAR t; + + if (!IgnoreCase) + { + while (TRUE) + { + // This takes care of double ampersands as well (they are treated as one literal + // ampersand). + if (*A == '&') + A++; + if (*B == '&') + B++; + + t = *A; + + if (t == 0) + { + if (MatchIfPrefix) + return 0; + + break; + } + + if (t != *B) + break; + + A++; + B++; + } + + return C_2uTo4(t) - C_2uTo4(*B); + } + else + { + while (TRUE) + { + if (*A == '&') + A++; + if (*B == '&') + B++; + + t = *A; + + if (t == 0) + { + if (MatchIfPrefix) + return 0; + + break; + } + + if (towupper(t) != towupper(*B)) + break; + + A++; + B++; + } + + return C_2uTo4(t) - C_2uTo4(*B); + } +} + +/** + * Formats a date using the user's default locale. + * + * \param Date The time structure. If NULL, the current time is used. + * \param Format The format of the date. If NULL, the format appropriate to the user's locale is + * used. + */ +PPH_STRING PhFormatDate( + _In_opt_ PSYSTEMTIME Date, + _In_opt_ PWSTR Format + ) +{ + PPH_STRING string; + ULONG bufferSize; + + bufferSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, Date, Format, NULL, 0); + string = PhCreateStringEx(NULL, bufferSize * 2); + + if (!GetDateFormat(LOCALE_USER_DEFAULT, 0, Date, Format, string->Buffer, bufferSize)) + { + PhDereferenceObject(string); + return NULL; + } + + PhTrimToNullTerminatorString(string); + + return string; +} + +/** + * Formats a time using the user's default locale. + * + * \param Time The time structure. If NULL, the current time is used. + * \param Format The format of the time. If NULL, the format appropriate to the user's locale is + * used. + */ +PPH_STRING PhFormatTime( + _In_opt_ PSYSTEMTIME Time, + _In_opt_ PWSTR Format + ) +{ + PPH_STRING string; + ULONG bufferSize; + + bufferSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, Time, Format, NULL, 0); + string = PhCreateStringEx(NULL, bufferSize * 2); + + if (!GetTimeFormat(LOCALE_USER_DEFAULT, 0, Time, Format, string->Buffer, bufferSize)) + { + PhDereferenceObject(string); + return NULL; + } + + PhTrimToNullTerminatorString(string); + + return string; +} + +/** + * Formats a date and time using the user's default locale. + * + * \param DateTime The time structure. If NULL, the current time is used. + * + * \return A string containing the time, a space character, then the date. + */ +PPH_STRING PhFormatDateTime( + _In_opt_ PSYSTEMTIME DateTime + ) +{ + PPH_STRING string; + ULONG timeBufferSize; + ULONG dateBufferSize; + ULONG count; + + timeBufferSize = GetTimeFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, NULL, 0); + dateBufferSize = GetDateFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, NULL, 0); + + string = PhCreateStringEx(NULL, (timeBufferSize + 1 + dateBufferSize) * 2); + + if (!GetTimeFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, &string->Buffer[0], timeBufferSize)) + { + PhDereferenceObject(string); + return NULL; + } + + count = (ULONG)PhCountStringZ(string->Buffer); + string->Buffer[count] = ' '; + + if (!GetDateFormat(LOCALE_USER_DEFAULT, 0, DateTime, NULL, &string->Buffer[count + 1], dateBufferSize)) + { + PhDereferenceObject(string); + return NULL; + } + + PhTrimToNullTerminatorString(string); + + return string; +} + +PPH_STRING PhFormatTimeSpan( + _In_ ULONG64 Ticks, + _In_opt_ ULONG Mode + ) +{ + PPH_STRING string; + + string = PhCreateStringEx(NULL, PH_TIMESPAN_STR_LEN); + PhPrintTimeSpan(string->Buffer, Ticks, Mode); + PhTrimToNullTerminatorString(string); + + return string; +} + +/** + * Formats a relative time span. + * + * \param TimeSpan The time span, in ticks. + */ +PPH_STRING PhFormatTimeSpanRelative( + _In_ ULONG64 TimeSpan + ) +{ + PH_AUTO_POOL autoPool; + PPH_STRING string; + DOUBLE days; + DOUBLE weeks; + DOUBLE fortnights; + DOUBLE months; + DOUBLE years; + DOUBLE centuries; + + PhInitializeAutoPool(&autoPool); + + days = (DOUBLE)TimeSpan / PH_TICKS_PER_DAY; + weeks = days / 7; + fortnights = weeks / 2; + years = days / 365.2425; + months = years * 12; + centuries = years / 100; + + if (centuries >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)centuries, (ULONG)centuries == 1 ? L"century" : L"centuries"); + } + else if (years >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)years, (ULONG)years == 1 ? L"year" : L"years"); + } + else if (months >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)months, (ULONG)months == 1 ? L"month" : L"months"); + } + else if (fortnights >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)fortnights, (ULONG)fortnights == 1 ? L"fortnight" : L"fortnights"); + } + else if (weeks >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)weeks, (ULONG)weeks == 1 ? L"week" : L"weeks"); + } + else + { + DOUBLE milliseconds; + DOUBLE seconds; + DOUBLE minutes; + DOUBLE hours; + ULONG secondsPartial; + ULONG minutesPartial; + ULONG hoursPartial; + + milliseconds = (DOUBLE)TimeSpan / PH_TICKS_PER_MS; + seconds = (DOUBLE)TimeSpan / PH_TICKS_PER_SEC; + minutes = (DOUBLE)TimeSpan / PH_TICKS_PER_MIN; + hours = (DOUBLE)TimeSpan / PH_TICKS_PER_HOUR; + + if (days >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)days, (ULONG)days == 1 ? L"day" : L"days"); + hoursPartial = (ULONG)PH_TICKS_PARTIAL_HOURS(TimeSpan); + + if (hoursPartial >= 1) + { + string = PhaFormatString(L"%s and %u %s", string->Buffer, hoursPartial, hoursPartial == 1 ? L"hour" : L"hours"); + } + } + else if (hours >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)hours, (ULONG)hours == 1 ? L"hour" : L"hours"); + minutesPartial = (ULONG)PH_TICKS_PARTIAL_MIN(TimeSpan); + + if (minutesPartial >= 1) + { + string = PhaFormatString(L"%s and %u %s", string->Buffer, (ULONG)minutesPartial, (ULONG)minutesPartial == 1 ? L"minute" : L"minutes"); + } + } + else if (minutes >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)minutes, (ULONG)minutes == 1 ? L"minute" : L"minutes"); + secondsPartial = (ULONG)PH_TICKS_PARTIAL_SEC(TimeSpan); + + if (secondsPartial >= 1) + { + string = PhaFormatString(L"%s and %u %s", string->Buffer, (ULONG)secondsPartial, (ULONG)secondsPartial == 1 ? L"second" : L"seconds"); + } + } + else if (seconds >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)seconds, (ULONG)seconds == 1 ? L"second" : L"seconds"); + } + else if (milliseconds >= 1) + { + string = PhaFormatString(L"%u %s", (ULONG)milliseconds, (ULONG)milliseconds == 1 ? L"millisecond" : L"milliseconds"); + } + else + { + string = PhaCreateString(L"a very short time"); + } + } + + // Turn 1 into "a", e.g. 1 minute -> a minute + if (PhStartsWithString2(string, L"1 ", FALSE)) + { + // Special vowel case: a hour -> an hour + if (string->Buffer[2] != 'h') + string = PhaConcatStrings2(L"a ", &string->Buffer[2]); + else + string = PhaConcatStrings2(L"an ", &string->Buffer[2]); + } + + PhReferenceObject(string); + PhDeleteAutoPool(&autoPool); + + return string; +} + +/** + * Formats a 64-bit unsigned integer. + * + * \param Value The integer. + * \param GroupDigits TRUE to group digits, otherwise FALSE. + */ +PPH_STRING PhFormatUInt64( + _In_ ULONG64 Value, + _In_ BOOLEAN GroupDigits + ) +{ + PH_FORMAT format; + + format.Type = UInt64FormatType | (GroupDigits ? FormatGroupDigits : 0); + format.u.UInt64 = Value; + + return PhFormat(&format, 1, 0); +} + +PPH_STRING PhFormatDecimal( + _In_ PWSTR Value, + _In_ ULONG FractionalDigits, + _In_ BOOLEAN GroupDigits + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static WCHAR decimalSeparator[4]; + static WCHAR thousandSeparator[4]; + + PPH_STRING string; + NUMBERFMT format; + ULONG bufferSize; + + if (PhBeginInitOnce(&initOnce)) + { + if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimalSeparator, 4)) + { + decimalSeparator[0] = '.'; + decimalSeparator[1] = 0; + } + + if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousandSeparator, 4)) + { + thousandSeparator[0] = ','; + thousandSeparator[1] = 0; + } + + PhEndInitOnce(&initOnce); + } + + format.NumDigits = FractionalDigits; + format.LeadingZero = 0; + format.Grouping = GroupDigits ? 3 : 0; + format.lpDecimalSep = decimalSeparator; + format.lpThousandSep = thousandSeparator; + format.NegativeOrder = 1; + + bufferSize = GetNumberFormat(LOCALE_USER_DEFAULT, 0, Value, &format, NULL, 0); + string = PhCreateStringEx(NULL, bufferSize * 2); + + if (!GetNumberFormat(LOCALE_USER_DEFAULT, 0, Value, &format, string->Buffer, bufferSize)) + { + PhDereferenceObject(string); + return NULL; + } + + PhTrimToNullTerminatorString(string); + + return string; +} + +/** + * Gets a string representing a size. + * + * \param Size The size value. + * \param MaxSizeUnit The largest unit of size to use, -1 to use PhMaxSizeUnit, or -2 for no limit. + * \li \c 0 Bytes. + * \li \c 1 Kilobytes. + * \li \c 2 Megabytes. + * \li \c 3 Gigabytes. + * \li \c 4 Terabytes. + * \li \c 5 Petabytes. + * \li \c 6 Exabytes. + */ +PPH_STRING PhFormatSize( + _In_ ULONG64 Size, + _In_ ULONG MaxSizeUnit + ) +{ + PH_FORMAT format; + + // PhFormat handles this better than the old method. + + format.Type = SizeFormatType | FormatUseRadix; + format.Radix = (UCHAR)(MaxSizeUnit != -1 ? MaxSizeUnit : PhMaxSizeUnit); + format.u.Size = Size; + + return PhFormat(&format, 1, 0); +} + +/** + * Converts a UUID to its string representation. + * + * \param Guid A UUID. + */ +PPH_STRING PhFormatGuid( + _In_ PGUID Guid + ) +{ + PPH_STRING string; + UNICODE_STRING unicodeString; + + if (!NT_SUCCESS(RtlStringFromGUID(Guid, &unicodeString))) + return NULL; + + string = PhCreateStringFromUnicodeString(&unicodeString); + RtlFreeUnicodeString(&unicodeString); + + return string; +} + +/** + * Retrieves image version information for a file. + * + * \param FileName The file name. + * + * \return A version information block. You must free this using PhFree() when you no longer need + * it. + */ +PVOID PhGetFileVersionInfo( + _In_ PWSTR FileName + ) +{ + ULONG versionInfoSize; + ULONG dummy; + PVOID versionInfo; + + versionInfoSize = GetFileVersionInfoSize( + FileName, + &dummy + ); + + if (versionInfoSize) + { + versionInfo = PhAllocate(versionInfoSize); + + if (!GetFileVersionInfo( + FileName, + 0, + versionInfoSize, + versionInfo + )) + { + PhFree(versionInfo); + + return NULL; + } + } + else + { + return NULL; + } + + return versionInfo; +} + +/** + * Retrieves the language ID and code page used by a version information block. + * + * \param VersionInfo The version information block. + */ +ULONG PhGetFileVersionInfoLangCodePage( + _In_ PVOID VersionInfo + ) +{ + PVOID buffer; + ULONG length; + + if (VerQueryValue(VersionInfo, L"\\VarFileInfo\\Translation", &buffer, &length)) + { + // Combine the language ID and code page. + return (*(PUSHORT)buffer << 16) + *((PUSHORT)buffer + 1); + } + else + { + return (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 1252; + } +} + +/** + * Retrieves a string in a version information block. + * + * \param VersionInfo The version information block. + * \param SubBlock The path to the sub-block. + */ +PPH_STRING PhGetFileVersionInfoString( + _In_ PVOID VersionInfo, + _In_ PWSTR SubBlock + ) +{ + PVOID buffer; + ULONG length; + + if (VerQueryValue(VersionInfo, SubBlock, &buffer, &length)) + { + PPH_STRING string; + + string = PhCreateStringEx((PWCHAR)buffer, length * sizeof(WCHAR)); + // length may include the null terminator. + PhTrimToNullTerminatorString(string); + + return string; + } + else + { + return NULL; + } +} + +/** + * Retrieves a string in a version information block. + * + * \param VersionInfo The version information block. + * \param LangCodePage The language ID and code page of the string. + * \param StringName The name of the string. + */ +PPH_STRING PhGetFileVersionInfoString2( + _In_ PVOID VersionInfo, + _In_ ULONG LangCodePage, + _In_ PWSTR StringName + ) +{ + WCHAR subBlock[65]; + PH_FORMAT format[4]; + + PhInitFormatS(&format[0], L"\\StringFileInfo\\"); + PhInitFormatX(&format[1], LangCodePage); + format[1].Type |= FormatPadZeros | FormatUpperCase; + format[1].Width = 8; + PhInitFormatC(&format[2], '\\'); + PhInitFormatS(&format[3], StringName); + + if (PhFormatToBuffer(format, 4, subBlock, sizeof(subBlock), NULL)) + return PhGetFileVersionInfoString(VersionInfo, subBlock); + else + return NULL; +} + +VOID PhpGetImageVersionInfoFields( + _Out_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, + _In_ PVOID VersionInfo, + _In_ ULONG LangCodePage + ) +{ + ImageVersionInfo->CompanyName = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"CompanyName"); + ImageVersionInfo->FileDescription = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"FileDescription"); + ImageVersionInfo->ProductName = PhGetFileVersionInfoString2(VersionInfo, LangCodePage, L"ProductName"); +} + +/** + * Initializes a structure with version information. + * + * \param ImageVersionInfo The version information structure. + * \param FileName The file name of an image. + */ +BOOLEAN PhInitializeImageVersionInfo( + _Out_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, + _In_ PWSTR FileName + ) +{ + PVOID versionInfo; + ULONG langCodePage; + VS_FIXEDFILEINFO *rootBlock; + ULONG rootBlockLength; + PH_FORMAT fileVersionFormat[7]; + + versionInfo = PhGetFileVersionInfo(FileName); + + if (!versionInfo) + return FALSE; + + langCodePage = PhGetFileVersionInfoLangCodePage(versionInfo); + PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, langCodePage); + + if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) + { + // Use the windows-1252 code page. + PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (langCodePage & 0xffff0000) + 1252); + + // Use the default language (US English). + if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) + { + PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 1252); + + if (!ImageVersionInfo->CompanyName && !ImageVersionInfo->FileDescription && !ImageVersionInfo->ProductName) + PhpGetImageVersionInfoFields(ImageVersionInfo, versionInfo, (MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) << 16) + 0); + } + } + + // The version information is language-independent and must be read from the root block. + if (VerQueryValue(versionInfo, L"\\", &rootBlock, &rootBlockLength) && rootBlockLength != 0) + { + PhInitFormatU(&fileVersionFormat[0], rootBlock->dwFileVersionMS >> 16); + PhInitFormatC(&fileVersionFormat[1], '.'); + PhInitFormatU(&fileVersionFormat[2], rootBlock->dwFileVersionMS & 0xffff); + PhInitFormatC(&fileVersionFormat[3], '.'); + PhInitFormatU(&fileVersionFormat[4], rootBlock->dwFileVersionLS >> 16); + PhInitFormatC(&fileVersionFormat[5], '.'); + PhInitFormatU(&fileVersionFormat[6], rootBlock->dwFileVersionLS & 0xffff); + + ImageVersionInfo->FileVersion = PhFormat(fileVersionFormat, 7, 30); + } + else + { + ImageVersionInfo->FileVersion = NULL; + } + + PhFree(versionInfo); + + return TRUE; +} + +/** + * Frees a version information structure initialized by PhInitializeImageVersionInfo(). + * + * \param ImageVersionInfo The version information structure. + */ +VOID PhDeleteImageVersionInfo( + _Inout_ PPH_IMAGE_VERSION_INFO ImageVersionInfo + ) +{ + if (ImageVersionInfo->CompanyName) PhDereferenceObject(ImageVersionInfo->CompanyName); + if (ImageVersionInfo->FileDescription) PhDereferenceObject(ImageVersionInfo->FileDescription); + if (ImageVersionInfo->FileVersion) PhDereferenceObject(ImageVersionInfo->FileVersion); + if (ImageVersionInfo->ProductName) PhDereferenceObject(ImageVersionInfo->ProductName); +} + +PPH_STRING PhFormatImageVersionInfo( + _In_opt_ PPH_STRING FileName, + _In_ PPH_IMAGE_VERSION_INFO ImageVersionInfo, + _In_opt_ PPH_STRINGREF Indent, + _In_opt_ ULONG LineLimit + ) +{ + PH_STRING_BUILDER stringBuilder; + + if (LineLimit == 0) + LineLimit = MAXULONG32; + + PhInitializeStringBuilder(&stringBuilder, 40); + + // File name + + if (!PhIsNullOrEmptyString(FileName)) + { + PPH_STRING temp; + + if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); + + temp = PhEllipsisStringPath(FileName, LineLimit); + PhAppendStringBuilder(&stringBuilder, &temp->sr); + PhDereferenceObject(temp); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + // File description & version + + if (!( + PhIsNullOrEmptyString(ImageVersionInfo->FileDescription) && + PhIsNullOrEmptyString(ImageVersionInfo->FileVersion) + )) + { + PPH_STRING tempDescription = NULL; + PPH_STRING tempVersion = NULL; + ULONG limitForDescription; + ULONG limitForVersion; + + if (LineLimit != MAXULONG32) + { + limitForVersion = (LineLimit - 1) / 4; // 1/4 space for version (and space character) + limitForDescription = LineLimit - limitForVersion; + } + else + { + limitForDescription = MAXULONG32; + limitForVersion = MAXULONG32; + } + + if (!PhIsNullOrEmptyString(ImageVersionInfo->FileDescription)) + { + tempDescription = PhEllipsisString( + ImageVersionInfo->FileDescription, + limitForDescription + ); + } + + if (!PhIsNullOrEmptyString(ImageVersionInfo->FileVersion)) + { + tempVersion = PhEllipsisString( + ImageVersionInfo->FileVersion, + limitForVersion + ); + } + + if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); + + if (tempDescription) + { + PhAppendStringBuilder(&stringBuilder, &tempDescription->sr); + + if (tempVersion) + PhAppendCharStringBuilder(&stringBuilder, ' '); + } + + if (tempVersion) + PhAppendStringBuilder(&stringBuilder, &tempVersion->sr); + + if (tempDescription) + PhDereferenceObject(tempDescription); + if (tempVersion) + PhDereferenceObject(tempVersion); + + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + // File company + + if (!PhIsNullOrEmptyString(ImageVersionInfo->CompanyName)) + { + PPH_STRING temp; + + if (Indent) PhAppendStringBuilder(&stringBuilder, Indent); + + temp = PhEllipsisString(ImageVersionInfo->CompanyName, LineLimit); + PhAppendStringBuilder(&stringBuilder, &temp->sr); + PhDereferenceObject(temp); + PhAppendCharStringBuilder(&stringBuilder, '\n'); + } + + // Remove the extra newline. + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} + +/** + * Gets an absolute file name. + * + * \param FileName A file name. + * \param IndexOfFileName A variable which receives the index of the base name. + * + * \return An absolute file name, or NULL if the function failed. + */ +PPH_STRING PhGetFullPath( + _In_ PWSTR FileName, + _Out_opt_ PULONG IndexOfFileName + ) +{ + PPH_STRING fullPath; + ULONG bufferSize; + ULONG returnLength; + PWSTR filePart; + + bufferSize = 0x80; + fullPath = PhCreateStringEx(NULL, bufferSize * 2); + + returnLength = RtlGetFullPathName_U(FileName, bufferSize, fullPath->Buffer, &filePart); + + if (returnLength > bufferSize) + { + PhDereferenceObject(fullPath); + bufferSize = returnLength; + fullPath = PhCreateStringEx(NULL, bufferSize * 2); + + returnLength = RtlGetFullPathName_U(FileName, bufferSize, fullPath->Buffer, &filePart); + } + + if (returnLength == 0) + { + PhDereferenceObject(fullPath); + return NULL; + } + + PhTrimToNullTerminatorString(fullPath); + + if (IndexOfFileName) + { + if (filePart) + { + // The path points to a file. + *IndexOfFileName = (ULONG)(filePart - fullPath->Buffer); + } + else + { + // The path points to a directory. + *IndexOfFileName = -1; + } + } + + return fullPath; +} + +/** + * Expands environment variables in a string. + * + * \param String The string. + */ +PPH_STRING PhExpandEnvironmentStrings( + _In_ PPH_STRINGREF String + ) +{ + NTSTATUS status; + UNICODE_STRING inputString; + UNICODE_STRING outputString; + PPH_STRING string; + ULONG bufferLength; + + if (!PhStringRefToUnicodeString(String, &inputString)) + return NULL; + + bufferLength = 0x40; + string = PhCreateStringEx(NULL, bufferLength); + outputString.MaximumLength = (USHORT)bufferLength; + outputString.Length = 0; + outputString.Buffer = string->Buffer; + + status = RtlExpandEnvironmentStrings_U( + NULL, + &inputString, + &outputString, + &bufferLength + ); + + if (status == STATUS_BUFFER_TOO_SMALL) + { + PhDereferenceObject(string); + string = PhCreateStringEx(NULL, bufferLength); + outputString.MaximumLength = (USHORT)bufferLength; + outputString.Length = 0; + outputString.Buffer = string->Buffer; + + status = RtlExpandEnvironmentStrings_U( + NULL, + &inputString, + &outputString, + &bufferLength + ); + } + + if (!NT_SUCCESS(status)) + { + PhDereferenceObject(string); + return NULL; + } + + string->Length = outputString.Length; + string->Buffer[string->Length / 2] = 0; // make sure there is a null terminator + + return string; +} + +/** + * Gets the base name from a file name. + * + * \param FileName The file name. + */ +PPH_STRING PhGetBaseName( + _In_ PPH_STRING FileName + ) +{ + PH_STRINGREF pathPart; + PH_STRINGREF baseNamePart; + + if (!PhSplitStringRefAtLastChar(&FileName->sr, '\\', &pathPart, &baseNamePart)) + return PhReferenceObject(FileName); + + return PhCreateString2(&baseNamePart); +} + +/** + * Retrieves the system directory path. + */ +PPH_STRING PhGetSystemDirectory( + VOID + ) +{ + static PPH_STRING cachedSystemDirectory = NULL; + + PPH_STRING systemDirectory; + ULONG bufferSize; + ULONG returnLength; + + // Use the cached value if possible. + + systemDirectory = cachedSystemDirectory; + + if (systemDirectory) + return PhReferenceObject(systemDirectory); + + bufferSize = 0x40; + systemDirectory = PhCreateStringEx(NULL, bufferSize * 2); + + returnLength = GetSystemDirectory(systemDirectory->Buffer, bufferSize); + + if (returnLength > bufferSize) + { + PhDereferenceObject(systemDirectory); + bufferSize = returnLength; + systemDirectory = PhCreateStringEx(NULL, bufferSize * 2); + + returnLength = GetSystemDirectory(systemDirectory->Buffer, bufferSize); + } + + if (returnLength == 0) + { + PhDereferenceObject(systemDirectory); + return NULL; + } + + PhTrimToNullTerminatorString(systemDirectory); + + // Try to cache the value. + if (_InterlockedCompareExchangePointer( + &cachedSystemDirectory, + systemDirectory, + NULL + ) == NULL) + { + // Success, add one more reference for the cache. + PhReferenceObject(systemDirectory); + } + + return systemDirectory; +} + +/** + * Retrieves the Windows directory path. + */ +VOID PhGetSystemRoot( + _Out_ PPH_STRINGREF SystemRoot + ) +{ + static PH_STRINGREF systemRoot; + + PH_STRINGREF localSystemRoot; + SIZE_T count; + + if (systemRoot.Buffer) + { + *SystemRoot = systemRoot; + return; + } + + localSystemRoot.Buffer = USER_SHARED_DATA->NtSystemRoot; + count = PhCountStringZ(localSystemRoot.Buffer); + localSystemRoot.Length = count * sizeof(WCHAR); + + // Make sure the system root string doesn't have a trailing backslash. + if (localSystemRoot.Buffer[count - 1] == '\\') + localSystemRoot.Length -= sizeof(WCHAR); + + *SystemRoot = localSystemRoot; + + systemRoot.Length = localSystemRoot.Length; + MemoryBarrier(); + systemRoot.Buffer = localSystemRoot.Buffer; +} + +/** + * Locates a loader entry in the current process. + * + * \param DllBase The base address of the DLL. Specify NULL if this is not a search criteria. + * \param FullDllName The full name of the DLL. Specify NULL if this is not a search criteria. + * \param BaseDllName The base name of the DLL. Specify NULL if this is not a search criteria. + * + * \remarks This function must be called with the loader lock acquired. The first entry matching all + * of the specified values is returned. + */ +PLDR_DATA_TABLE_ENTRY PhFindLoaderEntry( + _In_opt_ PVOID DllBase, + _In_opt_ PPH_STRINGREF FullDllName, + _In_opt_ PPH_STRINGREF BaseDllName + ) +{ + PLDR_DATA_TABLE_ENTRY result = NULL; + PLDR_DATA_TABLE_ENTRY entry; + PH_STRINGREF fullDllName; + PH_STRINGREF baseDllName; + PLIST_ENTRY listHead; + PLIST_ENTRY listEntry; + + listHead = &NtCurrentPeb()->Ldr->InLoadOrderModuleList; + listEntry = listHead->Flink; + + while (listEntry != listHead) + { + entry = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); + PhUnicodeStringToStringRef(&entry->FullDllName, &fullDllName); + PhUnicodeStringToStringRef(&entry->BaseDllName, &baseDllName); + + if ( + (!DllBase || entry->DllBase == DllBase) && + (!FullDllName || PhEqualStringRef(&fullDllName, FullDllName, TRUE)) && + (!BaseDllName || PhEqualStringRef(&baseDllName, BaseDllName, TRUE)) + ) + { + result = entry; + break; + } + + listEntry = listEntry->Flink; + } + + return result; +} + +/** + * Retrieves the file name of a DLL loaded by the current process. + * + * \param DllHandle The base address of the DLL. + * \param IndexOfFileName A variable which receives the index of the base name of the DLL in the + * returned string. + * + * \return The file name of the DLL, or NULL if the DLL could not be found. + */ +PPH_STRING PhGetDllFileName( + _In_ PVOID DllHandle, + _Out_opt_ PULONG IndexOfFileName + ) +{ + PLDR_DATA_TABLE_ENTRY entry; + PPH_STRING fileName; + PPH_STRING newFileName; + ULONG_PTR indexOfFileName; + + RtlEnterCriticalSection(NtCurrentPeb()->LoaderLock); + + entry = PhFindLoaderEntry(DllHandle, NULL, NULL); + + if (entry) + fileName = PhCreateStringFromUnicodeString(&entry->FullDllName); + else + fileName = NULL; + + RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock); + + if (!fileName) + return NULL; + + newFileName = PhGetFileName(fileName); + PhDereferenceObject(fileName); + fileName = newFileName; + + if (IndexOfFileName) + { + indexOfFileName = PhFindLastCharInString(fileName, 0, '\\'); + + if (indexOfFileName != -1) + indexOfFileName++; + else + indexOfFileName = 0; + + *IndexOfFileName = (ULONG)indexOfFileName; + } + + return fileName; +} + +/** + * Retrieves the file name of the current process image. + */ +PPH_STRING PhGetApplicationFileName( + VOID + ) +{ + return PhGetDllFileName(NtCurrentPeb()->ImageBaseAddress, NULL); +} + +/** + * Retrieves the directory of the current process image. + */ +PPH_STRING PhGetApplicationDirectory( + VOID + ) +{ + PPH_STRING fileName; + ULONG indexOfFileName; + PPH_STRING path = NULL; + + fileName = PhGetDllFileName(NtCurrentPeb()->ImageBaseAddress, &indexOfFileName); + + if (fileName) + { + if (indexOfFileName != 0) + { + // Remove the file name from the path. + path = PhSubstring(fileName, 0, indexOfFileName); + } + + PhDereferenceObject(fileName); + } + + return path; +} + +/** + * Gets a known location as a file name. + * + * \param Folder A CSIDL value representing the known location. + * \param AppendPath A string to append to the folder path. + */ +PPH_STRING PhGetKnownLocation( + _In_ ULONG Folder, + _In_opt_ PWSTR AppendPath + ) +{ + PPH_STRING path; + SIZE_T appendPathLength; + + if (AppendPath) + appendPathLength = PhCountStringZ(AppendPath) * 2; + else + appendPathLength = 0; + + path = PhCreateStringEx(NULL, MAX_PATH * 2 + appendPathLength); + + if (SUCCEEDED(SHGetFolderPath( + NULL, + Folder, + NULL, + SHGFP_TYPE_CURRENT, + path->Buffer + ))) + { + PhTrimToNullTerminatorString(path); + + if (AppendPath) + { + memcpy(&path->Buffer[path->Length / 2], AppendPath, appendPathLength + 2); // +2 for null terminator + path->Length += appendPathLength; + } + + return path; + } + + PhDereferenceObject(path); + + return NULL; +} + +/** + * Waits on multiple objects while processing window messages. + * + * \param hWnd The window to process messages for, or NULL to process all messages for the current + * thread. + * \param NumberOfHandles The number of handles specified in \a Handles. This must not be greater + * than MAXIMUM_WAIT_OBJECTS - 1. + * \param Handles An array of handles. + * \param Timeout The number of milliseconds to wait on the objects, or INFINITE for no timeout. + * + * \remarks The wait is always in WaitAny mode. + */ +NTSTATUS PhWaitForMultipleObjectsAndPump( + _In_opt_ HWND hWnd, + _In_ ULONG NumberOfHandles, + _In_ PHANDLE Handles, + _In_ ULONG Timeout + ) +{ + NTSTATUS status; + ULONG startTickCount; + ULONG currentTickCount; + LONG currentTimeout; + + startTickCount = GetTickCount(); + currentTimeout = Timeout; + + while (TRUE) + { + status = MsgWaitForMultipleObjects( + NumberOfHandles, + Handles, + FALSE, + (ULONG)currentTimeout, + QS_ALLEVENTS + ); + + if (status >= STATUS_WAIT_0 && status < (NTSTATUS)(STATUS_WAIT_0 + NumberOfHandles)) + { + return status; + } + else if (status == (STATUS_WAIT_0 + NumberOfHandles)) + { + MSG msg; + + // Pump messages + + while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + return status; + } + + // Recompute the timeout value. + + if (Timeout != INFINITE) + { + currentTickCount = GetTickCount(); + currentTimeout = Timeout - (currentTickCount - startTickCount); + + if (currentTimeout < 0) + return STATUS_TIMEOUT; + } + } +} + +/** + * Creates a native process and an initial thread. + * + * \param FileName The Win32 file name of the image. + * \param CommandLine The command line string to pass to the process. This string cannot be used to + * specify the image to execute. + * \param Environment The environment block for the process. Specify NULL to use the environment of + * the current process. + * \param CurrentDirectory The current directory string to pass to the process. + * \param Information Additional parameters to pass to the process. + * \param Flags A combination of the following: + * \li \c PH_CREATE_PROCESS_INHERIT_HANDLES Inheritable handles will be duplicated to the process + * from the parent process. + * \li \c PH_CREATE_PROCESS_SUSPENDED The initial thread will be created suspended. + * \li \c PH_CREATE_PROCESS_BREAKAWAY_FROM_JOB The process will not be assigned to the job object + * associated with the parent process. + * \li \c PH_CREATE_PROCESS_NEW_CONSOLE The process will have its own console, instead of inheriting + * the console of the parent process. + * \param ParentProcessHandle The process from which the new process will inherit attributes. + * Specify NULL for the current process. + * \param ClientId A variable which recieves the identifier of the initial thread. + * \param ProcessHandle A variable which receives a handle to the process. + * \param ThreadHandle A variable which receives a handle to the initial thread. + */ +NTSTATUS PhCreateProcess( + _In_ PWSTR FileName, + _In_opt_ PPH_STRINGREF CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PPH_STRINGREF CurrentDirectory, + _In_opt_ PPH_CREATE_PROCESS_INFO Information, + _In_ ULONG Flags, + _In_opt_ HANDLE ParentProcessHandle, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ) +{ + NTSTATUS status; + RTL_USER_PROCESS_INFORMATION processInfo; + PRTL_USER_PROCESS_PARAMETERS parameters; + UNICODE_STRING fileName; + UNICODE_STRING commandLine; + UNICODE_STRING currentDirectory; + PUNICODE_STRING windowTitle; + PUNICODE_STRING desktopInfo; + + if (!RtlDosPathNameToNtPathName_U( + FileName, + &fileName, + NULL, + NULL + )) + return STATUS_OBJECT_NAME_NOT_FOUND; + + if (CommandLine) + { + if (!PhStringRefToUnicodeString(CommandLine, &commandLine)) + return STATUS_NAME_TOO_LONG; + } + + if (CurrentDirectory) + { + if (!PhStringRefToUnicodeString(CurrentDirectory, ¤tDirectory)) + return STATUS_NAME_TOO_LONG; + } + + if (Information) + { + windowTitle = Information->WindowTitle; + desktopInfo = Information->DesktopInfo; + } + else + { + windowTitle = NULL; + desktopInfo = NULL; + } + + if (!windowTitle) + windowTitle = &fileName; + + if (!desktopInfo) + desktopInfo = &NtCurrentPeb()->ProcessParameters->DesktopInfo; + + status = RtlCreateProcessParameters( + ¶meters, + &fileName, + Information ? Information->DllPath : NULL, + CurrentDirectory ? ¤tDirectory : NULL, + CommandLine ? &commandLine : &fileName, + Environment, + windowTitle, + desktopInfo, + Information ? Information->ShellInfo : NULL, + Information ? Information->RuntimeData : NULL + ); + + if (NT_SUCCESS(status)) + { + status = RtlCreateUserProcess( + &fileName, + OBJ_CASE_INSENSITIVE, + parameters, + NULL, + NULL, + ParentProcessHandle, + !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), + NULL, + NULL, + &processInfo + ); + RtlDestroyProcessParameters(parameters); + } + + RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); + + if (NT_SUCCESS(status)) + { + if (!(Flags & PH_CREATE_PROCESS_SUSPENDED)) + NtResumeThread(processInfo.Thread, NULL); + + if (ClientId) + *ClientId = processInfo.ClientId; + + if (ProcessHandle) + *ProcessHandle = processInfo.Process; + else + NtClose(processInfo.Process); + + if (ThreadHandle) + *ThreadHandle = processInfo.Thread; + else + NtClose(processInfo.Thread); + } + + return status; +} + +/** + * Creates a Win32 process and an initial thread. + * + * \param FileName The Win32 file name of the image. + * \param CommandLine The command line to execute. This can be specified instead of \a FileName to + * indicate the image to execute. + * \param Environment The environment block for the process. Specify NULL to use the environment of + * the current process. + * \param CurrentDirectory The current directory string to pass to the process. + * \param Flags See PhCreateProcess(). + * \param TokenHandle The token of the process. Specify NULL for the token of the parent process. + * \param ProcessHandle A variable which receives a handle to the process. + * \param ThreadHandle A variable which receives a handle to the initial thread. + */ +NTSTATUS PhCreateProcessWin32( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PWSTR CurrentDirectory, + _In_ ULONG Flags, + _In_opt_ HANDLE TokenHandle, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ) +{ + return PhCreateProcessWin32Ex( + FileName, + CommandLine, + Environment, + CurrentDirectory, + NULL, + Flags, + TokenHandle, + NULL, + ProcessHandle, + ThreadHandle + ); +} + +static const PH_FLAG_MAPPING PhpCreateProcessMappings[] = +{ + { PH_CREATE_PROCESS_UNICODE_ENVIRONMENT, CREATE_UNICODE_ENVIRONMENT }, + { PH_CREATE_PROCESS_SUSPENDED, CREATE_SUSPENDED }, + { PH_CREATE_PROCESS_BREAKAWAY_FROM_JOB, CREATE_BREAKAWAY_FROM_JOB }, + { PH_CREATE_PROCESS_NEW_CONSOLE, CREATE_NEW_CONSOLE } +}; + +FORCEINLINE VOID PhpConvertProcessInformation( + _In_ PPROCESS_INFORMATION ProcessInfo, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ) +{ + if (ClientId) + { + ClientId->UniqueProcess = UlongToHandle(ProcessInfo->dwProcessId); + ClientId->UniqueThread = UlongToHandle(ProcessInfo->dwThreadId); + } + + if (ProcessHandle) + *ProcessHandle = ProcessInfo->hProcess; + else + NtClose(ProcessInfo->hProcess); + + if (ThreadHandle) + *ThreadHandle = ProcessInfo->hThread; + else + NtClose(ProcessInfo->hThread); +} + +/** + * Creates a Win32 process and an initial thread. + * + * \param FileName The Win32 file name of the image. + * \param CommandLine The command line to execute. This can be specified instead of \a FileName to + * indicate the image to execute. + * \param Environment The environment block for the process. Specify NULL to use the environment of + * the current process. + * \param CurrentDirectory The current directory string to pass to the process. + * \param StartupInfo A STARTUPINFO structure containing additional parameters for the process. + * \param Flags See PhCreateProcess(). + * \param TokenHandle The token of the process. Specify NULL for the token of the parent process. + * \param ClientId A variable which recieves the identifier of the initial thread. + * \param ProcessHandle A variable which receives a handle to the process. + * \param ThreadHandle A variable which receives a handle to the initial thread. + */ +NTSTATUS PhCreateProcessWin32Ex( + _In_opt_ PWSTR FileName, + _In_opt_ PWSTR CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PWSTR CurrentDirectory, + _In_opt_ STARTUPINFO *StartupInfo, + _In_ ULONG Flags, + _In_opt_ HANDLE TokenHandle, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ) +{ + NTSTATUS status; + PPH_STRING commandLine = NULL; + STARTUPINFO startupInfo; + PROCESS_INFORMATION processInfo; + ULONG newFlags; + + if (CommandLine) // duplicate because CreateProcess modifies the string + commandLine = PhCreateString(CommandLine); + + newFlags = 0; + PhMapFlags1(&newFlags, Flags, PhpCreateProcessMappings, sizeof(PhpCreateProcessMappings) / sizeof(PH_FLAG_MAPPING)); + + if (StartupInfo) + { + startupInfo = *StartupInfo; + } + else + { + memset(&startupInfo, 0, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + } + + if (!TokenHandle) + { + if (CreateProcess( + FileName, + PhGetString(commandLine), + NULL, + NULL, + !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), + newFlags, + Environment, + CurrentDirectory, + &startupInfo, + &processInfo + )) + status = STATUS_SUCCESS; + else + status = PhGetLastWin32ErrorAsNtStatus(); + } + else + { + if (CreateProcessAsUser( + TokenHandle, + FileName, + PhGetString(commandLine), + NULL, + NULL, + !!(Flags & PH_CREATE_PROCESS_INHERIT_HANDLES), + newFlags, + Environment, + CurrentDirectory, + &startupInfo, + &processInfo + )) + status = STATUS_SUCCESS; + else + status = PhGetLastWin32ErrorAsNtStatus(); + } + + if (commandLine) + PhDereferenceObject(commandLine); + + if (NT_SUCCESS(status)) + { + PhpConvertProcessInformation(&processInfo, ClientId, ProcessHandle, ThreadHandle); + } + + return status; +} + +/** + * Creates a Win32 process and an initial thread under the specified user. + * + * \param Information Parameters specifying how to create the process. + * \param Flags See PhCreateProcess(). Additional flags may be used: + * \li \c PH_CREATE_PROCESS_USE_PROCESS_TOKEN Use the token of the process specified by + * \a ProcessIdWithToken in \a Information. + * \li \c PH_CREATE_PROCESS_USE_SESSION_TOKEN Use the token of the session specified by + * \a SessionIdWithToken in \a Information. + * \li \c PH_CREATE_PROCESS_USE_LINKED_TOKEN Use the linked token to create the process; this causes + * the process to be elevated or unelevated depending on the specified options. + * \li \c PH_CREATE_PROCESS_SET_SESSION_ID \a SessionId is specified in \a Information. + * \li \c PH_CREATE_PROCESS_WITH_PROFILE Load the user profile, if supported. + * \param ClientId A variable which recieves the identifier of the initial thread. + * \param ProcessHandle A variable which receives a handle to the process. + * \param ThreadHandle A variable which receives a handle to the initial thread. + */ +NTSTATUS PhCreateProcessAsUser( + _In_ PPH_CREATE_PROCESS_AS_USER_INFO Information, + _In_ ULONG Flags, + _Out_opt_ PCLIENT_ID ClientId, + _Out_opt_ PHANDLE ProcessHandle, + _Out_opt_ PHANDLE ThreadHandle + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static _WinStationQueryInformationW WinStationQueryInformationW_I = NULL; + static _CreateEnvironmentBlock CreateEnvironmentBlock_I = NULL; + static _DestroyEnvironmentBlock DestroyEnvironmentBlock_I = NULL; + + NTSTATUS status; + HANDLE tokenHandle; + PVOID defaultEnvironment = NULL; + STARTUPINFO startupInfo = { sizeof(startupInfo) }; + BOOLEAN needsDuplicate = FALSE; + + if (PhBeginInitOnce(&initOnce)) + { + HMODULE winsta; + HMODULE userEnv; + + winsta = LoadLibrary(L"winsta.dll"); + WinStationQueryInformationW_I = (_WinStationQueryInformationW)GetProcAddress(winsta, "WinStationQueryInformationW"); + + userEnv = LoadLibrary(L"userenv.dll"); + CreateEnvironmentBlock_I = (_CreateEnvironmentBlock)GetProcAddress(userEnv, "CreateEnvironmentBlock"); + DestroyEnvironmentBlock_I = (_DestroyEnvironmentBlock)GetProcAddress(userEnv, "DestroyEnvironmentBlock"); + + PhEndInitOnce(&initOnce); + } + + if ((Flags & PH_CREATE_PROCESS_USE_PROCESS_TOKEN) && (Flags & PH_CREATE_PROCESS_USE_SESSION_TOKEN)) + return STATUS_INVALID_PARAMETER_2; + if (!Information->ApplicationName && !Information->CommandLine) + return STATUS_INVALID_PARAMETER_MIX; + + startupInfo.lpDesktop = Information->DesktopName; + + // Try to use CreateProcessWithLogonW if we need to load the user profile. + // This isn't compatible with some options. + if (Flags & PH_CREATE_PROCESS_WITH_PROFILE) + { + BOOLEAN useWithLogon; + + useWithLogon = TRUE; + + if (Flags & (PH_CREATE_PROCESS_USE_PROCESS_TOKEN | PH_CREATE_PROCESS_USE_SESSION_TOKEN)) + useWithLogon = FALSE; + + if (Flags & PH_CREATE_PROCESS_USE_LINKED_TOKEN) + useWithLogon = FALSE; + + if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) + { + if (Information->SessionId != NtCurrentPeb()->SessionId) + useWithLogon = FALSE; + } + + if (Information->LogonType && Information->LogonType != LOGON32_LOGON_INTERACTIVE) + useWithLogon = FALSE; + + if (useWithLogon) + { + PPH_STRING commandLine; + PROCESS_INFORMATION processInfo; + ULONG newFlags; + + if (Information->CommandLine) // duplicate because CreateProcess modifies the string + commandLine = PhCreateString(Information->CommandLine); + else + commandLine = NULL; + + if (!Information->Environment) + Flags |= PH_CREATE_PROCESS_UNICODE_ENVIRONMENT; + + newFlags = 0; + PhMapFlags1(&newFlags, Flags, PhpCreateProcessMappings, sizeof(PhpCreateProcessMappings) / sizeof(PH_FLAG_MAPPING)); + + if (CreateProcessWithLogonW( + Information->UserName, + Information->DomainName, + Information->Password, + LOGON_WITH_PROFILE, + Information->ApplicationName, + PhGetString(commandLine), + newFlags, + Information->Environment, + Information->CurrentDirectory, + &startupInfo, + &processInfo + )) + status = STATUS_SUCCESS; + else + status = PhGetLastWin32ErrorAsNtStatus(); + + if (commandLine) + PhDereferenceObject(commandLine); + + if (NT_SUCCESS(status)) + { + PhpConvertProcessInformation(&processInfo, ClientId, ProcessHandle, ThreadHandle); + } + + return status; + } + } + + // Get the token handle. Various methods are supported. + + if (Flags & PH_CREATE_PROCESS_USE_PROCESS_TOKEN) + { + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + ProcessQueryAccess, + Information->ProcessIdWithToken + ))) + return status; + + status = PhOpenProcessToken( + processHandle, + TOKEN_ALL_ACCESS, + &tokenHandle + ); + NtClose(processHandle); + + if (!NT_SUCCESS(status)) + return status; + + if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) + needsDuplicate = TRUE; // can't set the session ID of a token in use by a process + } + else if (Flags & PH_CREATE_PROCESS_USE_SESSION_TOKEN) + { + WINSTATIONUSERTOKEN userToken; + ULONG returnLength; + + if (!WinStationQueryInformationW_I) + return STATUS_PROCEDURE_NOT_FOUND; + + if (!WinStationQueryInformationW_I( + NULL, + Information->SessionIdWithToken, + WinStationUserToken, + &userToken, + sizeof(WINSTATIONUSERTOKEN), + &returnLength + )) + { + return PhGetLastWin32ErrorAsNtStatus(); + } + + tokenHandle = userToken.UserToken; + + if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) + needsDuplicate = TRUE; // not sure if this is necessary + } + else + { + ULONG logonType; + + if (Information->LogonType) + { + logonType = Information->LogonType; + } + else + { + logonType = LOGON32_LOGON_INTERACTIVE; + + // Check if this is a service logon. + if (PhEqualStringZ(Information->DomainName, L"NT AUTHORITY", TRUE)) + { + if (PhEqualStringZ(Information->UserName, L"SYSTEM", TRUE)) + { + if (WindowsVersion >= WINDOWS_VISTA) + logonType = LOGON32_LOGON_SERVICE; + else + logonType = LOGON32_LOGON_NEW_CREDENTIALS; // HACK + } + + if (PhEqualStringZ(Information->UserName, L"LOCAL SERVICE", TRUE) || + PhEqualStringZ(Information->UserName, L"NETWORK SERVICE", TRUE)) + { + logonType = LOGON32_LOGON_SERVICE; + } + } + } + + if (!LogonUser( + Information->UserName, + Information->DomainName, + Information->Password, + logonType, + LOGON32_PROVIDER_DEFAULT, + &tokenHandle + )) + return PhGetLastWin32ErrorAsNtStatus(); + } + + if (Flags & PH_CREATE_PROCESS_USE_LINKED_TOKEN) + { + HANDLE linkedTokenHandle; + TOKEN_TYPE tokenType; + ULONG returnLength; + + // NtQueryInformationToken normally returns an impersonation token with + // SecurityIdentification, but if the process is running with SeTcbPrivilege, it returns a + // primary token. We can never duplicate a SecurityIdentification impersonation token to + // make it a primary token, so we just check if the token is primary before using it. + + if (NT_SUCCESS(PhGetTokenLinkedToken(tokenHandle, &linkedTokenHandle))) + { + if (NT_SUCCESS(NtQueryInformationToken( + linkedTokenHandle, + TokenType, + &tokenType, + sizeof(TOKEN_TYPE), + &returnLength + )) && tokenType == TokenPrimary) + { + NtClose(tokenHandle); + tokenHandle = linkedTokenHandle; + needsDuplicate = FALSE; // the linked token that is returned is always a copy, so no need to duplicate + } + else + { + NtClose(linkedTokenHandle); + } + } + } + + if (needsDuplicate) + { + HANDLE newTokenHandle; + OBJECT_ATTRIBUTES objectAttributes; + + InitializeObjectAttributes( + &objectAttributes, + NULL, + 0, + NULL, + NULL + ); + + status = NtDuplicateToken( + tokenHandle, + TOKEN_ALL_ACCESS, + &objectAttributes, + FALSE, + TokenPrimary, + &newTokenHandle + ); + NtClose(tokenHandle); + + if (!NT_SUCCESS(status)) + return status; + + tokenHandle = newTokenHandle; + } + + // Set the session ID if needed. + + if (Flags & PH_CREATE_PROCESS_SET_SESSION_ID) + { + if (!NT_SUCCESS(status = PhSetTokenSessionId( + tokenHandle, + Information->SessionId + ))) + { + NtClose(tokenHandle); + return status; + } + } + + if (!Information->Environment) + { + if (CreateEnvironmentBlock_I) + { + CreateEnvironmentBlock_I(&defaultEnvironment, tokenHandle, FALSE); + + if (defaultEnvironment) + Flags |= PH_CREATE_PROCESS_UNICODE_ENVIRONMENT; + } + } + + status = PhCreateProcessWin32Ex( + Information->ApplicationName, + Information->CommandLine, + Information->Environment ? Information->Environment : defaultEnvironment, + Information->CurrentDirectory, + &startupInfo, + Flags, + tokenHandle, + ClientId, + ProcessHandle, + ThreadHandle + ); + + if (defaultEnvironment) + { + if (DestroyEnvironmentBlock_I) + DestroyEnvironmentBlock_I(defaultEnvironment); + } + + NtClose(tokenHandle); + + return status; +} + +NTSTATUS PhpGetAccountPrivileges( + _In_ PSID AccountSid, + _Out_ PTOKEN_PRIVILEGES *Privileges + ) +{ + NTSTATUS status; + LSA_HANDLE accountHandle; + PPRIVILEGE_SET accountPrivileges; + PTOKEN_PRIVILEGES privileges; + + status = LsaOpenAccount(PhGetLookupPolicyHandle(), AccountSid, ACCOUNT_VIEW, &accountHandle); + + if (!NT_SUCCESS(status)) + return status; + + status = LsaEnumeratePrivilegesOfAccount(accountHandle, &accountPrivileges); + LsaClose(accountHandle); + + if (!NT_SUCCESS(status)) + return status; + + privileges = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES) * accountPrivileges->PrivilegeCount); + privileges->PrivilegeCount = accountPrivileges->PrivilegeCount; + memcpy(privileges->Privileges, accountPrivileges->Privilege, sizeof(LUID_AND_ATTRIBUTES) * accountPrivileges->PrivilegeCount); + + LsaFreeMemory(accountPrivileges); + + *Privileges = privileges; + + return status; +} + +/** + * Filters a token to create a limited user security context. + * + * \param TokenHandle A handle to an existing token. The handle must have TOKEN_DUPLICATE, + * TOKEN_QUERY, TOKEN_ADJUST_GROUPS, TOKEN_ADJUST_DEFAULT, READ_CONTROL and WRITE_DAC access. + * \param NewTokenHandle A variable which receives a handle to the filtered token. The handle will + * have the same granted access as \a TokenHandle. + */ +NTSTATUS PhFilterTokenForLimitedUser( + _In_ HANDLE TokenHandle, + _Out_ PHANDLE NewTokenHandle + ) +{ + static SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY; + static SID_IDENTIFIER_AUTHORITY mandatoryLabelAuthority = SECURITY_MANDATORY_LABEL_AUTHORITY; + static LUID_AND_ATTRIBUTES defaultAllowedPrivileges[] = + { + { { SE_SHUTDOWN_PRIVILEGE, 0 }, 0 }, + { { SE_CHANGE_NOTIFY_PRIVILEGE, 0 }, 0 }, + { { SE_UNDOCK_PRIVILEGE, 0 }, 0 }, + { { SE_INC_WORKING_SET_PRIVILEGE, 0 }, 0 }, + { { SE_TIME_ZONE_PRIVILEGE, 0 }, 0 } + }; + + NTSTATUS status; + UCHAR administratorsSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; + PSID administratorsSid; + UCHAR usersSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * 2]; + PSID usersSid; + UCHAR sidsToDisableBuffer[FIELD_OFFSET(TOKEN_GROUPS, Groups) + sizeof(SID_AND_ATTRIBUTES)]; + PTOKEN_GROUPS sidsToDisable; + ULONG i; + ULONG j; + ULONG deleteIndex; + BOOLEAN found; + PTOKEN_PRIVILEGES privilegesOfToken; + PTOKEN_PRIVILEGES privilegesOfUsers; + PTOKEN_PRIVILEGES privilegesToDelete; + UCHAR lowMandatoryLevelSidBuffer[FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; + PSID lowMandatoryLevelSid; + TOKEN_MANDATORY_LABEL mandatoryLabel; + PSECURITY_DESCRIPTOR currentSecurityDescriptor; + BOOLEAN currentDaclPresent; + BOOLEAN currentDaclDefaulted; + PACL currentDacl; + PTOKEN_USER currentUser; + PACE_HEADER currentAce; + ULONG newDaclLength; + PACL newDacl; + SECURITY_DESCRIPTOR newSecurityDescriptor; + TOKEN_DEFAULT_DACL newDefaultDacl; + HANDLE newTokenHandle; + + // Set up the SIDs to Disable structure. + + // Initialize the Administrators SID. + administratorsSid = (PSID)administratorsSidBuffer; + RtlInitializeSid(administratorsSid, &ntAuthority, 2); + *RtlSubAuthoritySid(administratorsSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; + *RtlSubAuthoritySid(administratorsSid, 1) = DOMAIN_ALIAS_RID_ADMINS; + + // Initialize the Users SID. + usersSid = (PSID)usersSidBuffer; + RtlInitializeSid(usersSid, &ntAuthority, 2); + *RtlSubAuthoritySid(usersSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; + *RtlSubAuthoritySid(usersSid, 1) = DOMAIN_ALIAS_RID_USERS; + + sidsToDisable = (PTOKEN_GROUPS)sidsToDisableBuffer; + sidsToDisable->GroupCount = 1; + sidsToDisable->Groups[0].Sid = administratorsSid; + sidsToDisable->Groups[0].Attributes = 0; + + // Set up the Privileges to Delete structure. + + // Get the privileges that the input token contains. + if (!NT_SUCCESS(status = PhGetTokenPrivileges(TokenHandle, &privilegesOfToken))) + return status; + + // Get the privileges of the Users group - the privileges that we are going to allow. + if (!NT_SUCCESS(PhpGetAccountPrivileges(usersSid, &privilegesOfUsers))) + { + // Unsuccessful, so use the default set of privileges. + privilegesOfUsers = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(defaultAllowedPrivileges)); + privilegesOfUsers->PrivilegeCount = sizeof(defaultAllowedPrivileges) / sizeof(LUID_AND_ATTRIBUTES); + memcpy(privilegesOfUsers->Privileges, defaultAllowedPrivileges, sizeof(defaultAllowedPrivileges)); + } + + // Allocate storage for the privileges we need to delete. The worst case scenario is that all + // privileges in the token need to be deleted. + privilegesToDelete = PhAllocate(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES) * privilegesOfToken->PrivilegeCount); + deleteIndex = 0; + + // Compute the privileges that we need to delete. + for (i = 0; i < privilegesOfToken->PrivilegeCount; i++) + { + found = FALSE; + + // Is the privilege allowed? + for (j = 0; j < privilegesOfUsers->PrivilegeCount; j++) + { + if (RtlIsEqualLuid(&privilegesOfToken->Privileges[i].Luid, &privilegesOfUsers->Privileges[j].Luid)) + { + found = TRUE; + break; + } + } + + if (!found) + { + // This privilege needs to be deleted. + privilegesToDelete->Privileges[deleteIndex].Attributes = 0; + privilegesToDelete->Privileges[deleteIndex].Luid = privilegesOfToken->Privileges[i].Luid; + deleteIndex++; + } + } + + privilegesToDelete->PrivilegeCount = deleteIndex; + + // Filter the token. + + status = NtFilterToken( + TokenHandle, + 0, + sidsToDisable, + privilegesToDelete, + NULL, + &newTokenHandle + ); + PhFree(privilegesToDelete); + PhFree(privilegesOfUsers); + PhFree(privilegesOfToken); + + if (!NT_SUCCESS(status)) + return status; + + // Set the integrity level to Low if we're on Vista and above. + if (WINDOWS_HAS_UAC) + { + lowMandatoryLevelSid = (PSID)lowMandatoryLevelSidBuffer; + RtlInitializeSid(lowMandatoryLevelSid, &mandatoryLabelAuthority, 1); + *RtlSubAuthoritySid(lowMandatoryLevelSid, 0) = SECURITY_MANDATORY_LOW_RID; + + mandatoryLabel.Label.Sid = lowMandatoryLevelSid; + mandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY; + + NtSetInformationToken(newTokenHandle, TokenIntegrityLevel, &mandatoryLabel, sizeof(TOKEN_MANDATORY_LABEL)); + } + + // Fix up the security descriptor and default DACL. + if (NT_SUCCESS(PhGetObjectSecurity(newTokenHandle, DACL_SECURITY_INFORMATION, ¤tSecurityDescriptor))) + { + if (NT_SUCCESS(PhGetTokenUser(TokenHandle, ¤tUser))) + { + if (!NT_SUCCESS(RtlGetDaclSecurityDescriptor( + currentSecurityDescriptor, + ¤tDaclPresent, + ¤tDacl, + ¤tDaclDefaulted + ))) + { + currentDaclPresent = FALSE; + } + + newDaclLength = sizeof(ACL) + FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart) + RtlLengthSid(currentUser->User.Sid); + + if (currentDaclPresent) + newDaclLength += currentDacl->AclSize - sizeof(ACL); + + newDacl = PhAllocate(newDaclLength); + RtlCreateAcl(newDacl, newDaclLength, ACL_REVISION); + + // Add the existing DACL entries. + if (currentDaclPresent) + { + for (i = 0; i < currentDacl->AceCount; i++) + { + if (NT_SUCCESS(RtlGetAce(currentDacl, i, ¤tAce))) + RtlAddAce(newDacl, ACL_REVISION, MAXULONG32, currentAce, currentAce->AceSize); + } + } + + // Allow access for the current user. + RtlAddAccessAllowedAce(newDacl, ACL_REVISION, GENERIC_ALL, currentUser->User.Sid); + + // Set the security descriptor of the new token. + + RtlCreateSecurityDescriptor(&newSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); + + if (NT_SUCCESS(RtlSetDaclSecurityDescriptor(&newSecurityDescriptor, TRUE, newDacl, FALSE))) + PhSetObjectSecurity(newTokenHandle, DACL_SECURITY_INFORMATION, &newSecurityDescriptor); + + // Set the default DACL. + + newDefaultDacl.DefaultDacl = newDacl; + NtSetInformationToken(newTokenHandle, TokenDefaultDacl, &newDefaultDacl, sizeof(TOKEN_DEFAULT_DACL)); + + PhFree(newDacl); + + PhFree(currentUser); + } + + PhFree(currentSecurityDescriptor); + } + + *NewTokenHandle = newTokenHandle; + + return STATUS_SUCCESS; +} + +/** + * Opens a file or location through the shell. + * + * \param hWnd The window to display user interface components on. + * \param FileName A file name or location. + * \param Parameters The parameters to pass to the executed application. + */ +VOID PhShellExecute( + _In_ HWND hWnd, + _In_ PWSTR FileName, + _In_opt_ PWSTR Parameters + ) +{ + SHELLEXECUTEINFO info = { sizeof(info) }; + + info.lpFile = FileName; + info.lpParameters = Parameters; + info.nShow = SW_SHOW; + info.hwnd = hWnd; + + if (!ShellExecuteEx(&info)) + { + // It already displays error messages by itself. + //PhShowStatus(hWnd, L"Unable to execute the program", 0, GetLastError()); + } +} + +/** + * Opens a file or location through the shell. + * + * \param hWnd The window to display user interface components on. + * \param FileName A file name or location. + * \param Parameters The parameters to pass to the executed application. + * \param ShowWindowType A value specifying how to show the application. + * \param Flags A combination of the following: + * \li \c PH_SHELL_EXECUTE_ADMIN Execute the application elevated. + * \li \c PH_SHELL_EXECUTE_PUMP_MESSAGES Waits on the application while pumping messages, if + * \a Timeout is specified. + * \param Timeout The number of milliseconds to wait on the application, or 0 to return immediately + * after the application is started. + * \param ProcessHandle A variable which receives a handle to the new process. + */ +BOOLEAN PhShellExecuteEx( + _In_opt_ HWND hWnd, + _In_ PWSTR FileName, + _In_opt_ PWSTR Parameters, + _In_ ULONG ShowWindowType, + _In_ ULONG Flags, + _In_opt_ ULONG Timeout, + _Out_opt_ PHANDLE ProcessHandle + ) +{ + SHELLEXECUTEINFO info = { sizeof(info) }; + + info.lpFile = FileName; + info.lpParameters = Parameters; + info.fMask = SEE_MASK_NOCLOSEPROCESS; + info.nShow = ShowWindowType; + info.hwnd = hWnd; + + if ((Flags & PH_SHELL_EXECUTE_ADMIN) && WINDOWS_HAS_UAC) + info.lpVerb = L"runas"; + + if (ShellExecuteEx(&info)) + { + if (Timeout) + { + if (!(Flags & PH_SHELL_EXECUTE_PUMP_MESSAGES)) + { + LARGE_INTEGER timeout; + + NtWaitForSingleObject(info.hProcess, FALSE, PhTimeoutFromMilliseconds(&timeout, Timeout)); + } + else + { + PhWaitForMultipleObjectsAndPump(NULL, 1, &info.hProcess, Timeout); + } + } + + if (ProcessHandle) + *ProcessHandle = info.hProcess; + else + NtClose(info.hProcess); + + return TRUE; + } + else + { + return FALSE; + } +} + +/** + * Opens Windows Explorer with a file selected. + * + * \param hWnd A handle to the parent window. + * \param FileName A file name. + */ +VOID PhShellExploreFile( + _In_ HWND hWnd, + _In_ PWSTR FileName + ) +{ + if (SHOpenFolderAndSelectItems_Import() && SHParseDisplayName_Import()) + { + LPITEMIDLIST item; + SFGAOF attributes; + + if (SUCCEEDED(SHParseDisplayName_Import()(FileName, NULL, &item, 0, &attributes))) + { + SHOpenFolderAndSelectItems_Import()(item, 0, NULL, 0); + CoTaskMemFree(item); + } + else + { + PhShowError(hWnd, L"The location \"%s\" could not be found.", FileName); + } + } + else + { + PPH_STRING selectFileName; + + selectFileName = PhConcatStrings2(L"/select,", FileName); + PhShellExecute(hWnd, L"explorer.exe", selectFileName->Buffer); + PhDereferenceObject(selectFileName); + } +} + +/** + * Shows properties for a file. + * + * \param hWnd A handle to the parent window. + * \param FileName A file name. + */ +VOID PhShellProperties( + _In_ HWND hWnd, + _In_ PWSTR FileName + ) +{ + SHELLEXECUTEINFO info = { sizeof(info) }; + + info.lpFile = FileName; + info.nShow = SW_SHOW; + info.fMask = SEE_MASK_INVOKEIDLIST; + info.lpVerb = L"properties"; + info.hwnd = hWnd; + + if (!ShellExecuteEx(&info)) + { + // It already displays error messages by itself. + //PhShowStatus(hWnd, L"Unable to execute the program", 0, GetLastError()); + } +} + +/** + * Expands registry name abbreviations. + * + * \param KeyName The key name. + * \param Computer TRUE to prepend "Computer" or "My Computer" for use with the Registry Editor. + */ +PPH_STRING PhExpandKeyName( + _In_ PPH_STRING KeyName, + _In_ BOOLEAN Computer + ) +{ + PPH_STRING keyName; + PPH_STRING tempString; + + if (PhStartsWithString2(KeyName, L"HKCU", TRUE)) + { + keyName = PhConcatStrings2(L"HKEY_CURRENT_USER", &KeyName->Buffer[4]); + } + else if (PhStartsWithString2(KeyName, L"HKU", TRUE)) + { + keyName = PhConcatStrings2(L"HKEY_USERS", &KeyName->Buffer[3]); + } + else if (PhStartsWithString2(KeyName, L"HKCR", TRUE)) + { + keyName = PhConcatStrings2(L"HKEY_CLASSES_ROOT", &KeyName->Buffer[4]); + } + else if (PhStartsWithString2(KeyName, L"HKLM", TRUE)) + { + keyName = PhConcatStrings2(L"HKEY_LOCAL_MACHINE", &KeyName->Buffer[4]); + } + else + { + PhSetReference(&keyName, KeyName); + } + + if (Computer) + { + if (WindowsVersion >= WINDOWS_VISTA) + tempString = PhConcatStrings2(L"Computer\\", keyName->Buffer); + else + tempString = PhConcatStrings2(L"My Computer\\", keyName->Buffer); + + PhDereferenceObject(keyName); + keyName = tempString; + } + + return keyName; +} + +/** + * Opens a key in the Registry Editor. + * + * \param hWnd A handle to the parent window. + * \param KeyName The key name to open. + */ +VOID PhShellOpenKey( + _In_ HWND hWnd, + _In_ PPH_STRING KeyName + ) +{ + static PH_STRINGREF regeditKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit"); + + PPH_STRING lastKey; + HANDLE regeditKeyHandle; + UNICODE_STRING valueName; + PPH_STRING regeditFileName; + + if (!NT_SUCCESS(PhCreateKey( + ®editKeyHandle, + KEY_WRITE, + PH_KEY_CURRENT_USER, + ®editKeyName, + 0, + 0, + NULL + ))) + return; + + RtlInitUnicodeString(&valueName, L"LastKey"); + lastKey = PhExpandKeyName(KeyName, TRUE); + NtSetValueKey(regeditKeyHandle, &valueName, 0, REG_SZ, lastKey->Buffer, (ULONG)lastKey->Length + 2); + PhDereferenceObject(lastKey); + + NtClose(regeditKeyHandle); + + // Start regedit. If we aren't elevated, request that regedit be elevated. This is so we can get + // the consent dialog in the center of the specified window. + + regeditFileName = PhGetKnownLocation(CSIDL_WINDOWS, L"\\regedit.exe"); + + if (!regeditFileName) + regeditFileName = PhCreateString(L"regedit.exe"); + + if (!PhGetOwnTokenAttributes().Elevated) + { + PhShellExecuteEx(hWnd, regeditFileName->Buffer, L"", SW_NORMAL, PH_SHELL_EXECUTE_ADMIN, 0, NULL); + } + else + { + PhShellExecute(hWnd, regeditFileName->Buffer, L""); + } + + PhDereferenceObject(regeditFileName); +} + +/** + * Gets a registry string value. + * + * \param KeyHandle A handle to the key. + * \param ValueName The name of the value. + * + * \return A pointer to a string containing the value, or NULL if the function failed. You must free + * the string using PhDereferenceObject() when you no longer need it. + */ +PPH_STRING PhQueryRegistryString( + _In_ HANDLE KeyHandle, + _In_opt_ PWSTR ValueName + ) +{ + PPH_STRING string = NULL; + PH_STRINGREF valueName; + PKEY_VALUE_PARTIAL_INFORMATION buffer; + + if (ValueName) + PhInitializeStringRef(&valueName, ValueName); + else + PhInitializeEmptyStringRef(&valueName); + + if (NT_SUCCESS(PhQueryValueKey(KeyHandle, &valueName, KeyValuePartialInformation, &buffer))) + { + if (buffer->Type == REG_SZ || + buffer->Type == REG_MULTI_SZ || + buffer->Type == REG_EXPAND_SZ) + { + if (buffer->DataLength >= sizeof(WCHAR)) + string = PhCreateStringEx((PWCHAR)buffer->Data, buffer->DataLength - sizeof(WCHAR)); + else + string = PhReferenceEmptyString(); + } + + PhFree(buffer); + } + + return string; +} + +VOID PhMapFlags1( + _Inout_ PULONG Value2, + _In_ ULONG Value1, + _In_ const PH_FLAG_MAPPING *Mappings, + _In_ ULONG NumberOfMappings + ) +{ + ULONG i; + ULONG value2; + + value2 = *Value2; + + if (value2 != 0) + { + // There are existing flags. Map the flags we know about by clearing/setting them. The flags + // we don't know about won't be affected. + + for (i = 0; i < NumberOfMappings; i++) + { + if (Value1 & Mappings[i].Flag1) + value2 |= Mappings[i].Flag2; + else + value2 &= ~Mappings[i].Flag2; + } + } + else + { + // There are no existing flags, which means we can build the value from scratch, with no + // clearing needed. + + for (i = 0; i < NumberOfMappings; i++) + { + if (Value1 & Mappings[i].Flag1) + value2 |= Mappings[i].Flag2; + } + } + + *Value2 = value2; +} + +VOID PhMapFlags2( + _Inout_ PULONG Value1, + _In_ ULONG Value2, + _In_ const PH_FLAG_MAPPING *Mappings, + _In_ ULONG NumberOfMappings + ) +{ + ULONG i; + ULONG value1; + + value1 = *Value1; + + if (value1 != 0) + { + for (i = 0; i < NumberOfMappings; i++) + { + if (Value2 & Mappings[i].Flag2) + value1 |= Mappings[i].Flag1; + else + value1 &= ~Mappings[i].Flag1; + } + } + else + { + for (i = 0; i < NumberOfMappings; i++) + { + if (Value2 & Mappings[i].Flag2) + value1 |= Mappings[i].Flag1; + } + } + + *Value1 = value1; +} + +UINT_PTR CALLBACK PhpOpenFileNameHookProc( + _In_ HWND hdlg, + _In_ UINT uiMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uiMsg) + { + case WM_NOTIFY: + { + LPOFNOTIFY header = (LPOFNOTIFY)lParam; + + // We can't use CDN_FILEOK because it's not sent if the buffer is too small, defeating + // the entire purpose of this callback function. + + switch (header->hdr.code) + { + case CDN_SELCHANGE: + { + ULONG returnLength; + + returnLength = CommDlg_OpenSave_GetFilePath( + header->hdr.hwndFrom, + header->lpOFN->lpstrFile, + header->lpOFN->nMaxFile + ); + + if ((LONG)returnLength > 0 && returnLength > header->lpOFN->nMaxFile) + { + PhFree(header->lpOFN->lpstrFile); + header->lpOFN->nMaxFile = returnLength + 0x200; // pre-allocate some more + header->lpOFN->lpstrFile = PhAllocate(header->lpOFN->nMaxFile * 2); + + returnLength = CommDlg_OpenSave_GetFilePath( + header->hdr.hwndFrom, + header->lpOFN->lpstrFile, + header->lpOFN->nMaxFile + ); + } + } + break; + } + } + break; + } + + return FALSE; +} + +OPENFILENAME *PhpCreateOpenFileName( + VOID + ) +{ + OPENFILENAME *ofn; + + ofn = PhAllocate(sizeof(OPENFILENAME)); + memset(ofn, 0, sizeof(OPENFILENAME)); + + ofn->lStructSize = sizeof(OPENFILENAME); + ofn->nMaxFile = 0x400; + ofn->lpstrFile = PhAllocate(ofn->nMaxFile * 2); + ofn->lpstrFileTitle = NULL; + ofn->Flags = OFN_ENABLEHOOK | OFN_EXPLORER; + ofn->lpfnHook = PhpOpenFileNameHookProc; + + ofn->lpstrFile[0] = 0; + + return ofn; +} + +VOID PhpFreeOpenFileName( + _In_ OPENFILENAME *OpenFileName + ) +{ + if (OpenFileName->lpstrFilter) PhFree((PVOID)OpenFileName->lpstrFilter); + if (OpenFileName->lpstrFile) PhFree((PVOID)OpenFileName->lpstrFile); + + PhFree(OpenFileName); +} + +typedef struct _PHP_FILE_DIALOG +{ + BOOLEAN UseIFileDialog; + BOOLEAN Save; + union + { + OPENFILENAME *OpenFileName; + IFileDialog *FileDialog; + } u; +} PHP_FILE_DIALOG, *PPHP_FILE_DIALOG; + +PPHP_FILE_DIALOG PhpCreateFileDialog( + _In_ BOOLEAN Save, + _In_opt_ OPENFILENAME *OpenFileName, + _In_opt_ IFileDialog *FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog; + + assert(!!OpenFileName != !!FileDialog); + fileDialog = PhAllocate(sizeof(PHP_FILE_DIALOG)); + fileDialog->Save = Save; + + if (OpenFileName) + { + fileDialog->UseIFileDialog = FALSE; + fileDialog->u.OpenFileName = OpenFileName; + } + else if (FileDialog) + { + fileDialog->UseIFileDialog = TRUE; + fileDialog->u.FileDialog = FileDialog; + } + else + { + PhRaiseStatus(STATUS_INVALID_PARAMETER); + } + + return fileDialog; +} + +/** + * Creates a file dialog for the user to select a file to open. + * + * \return An opaque pointer representing the file dialog. You must free the file dialog using + * PhFreeFileDialog() when you no longer need it. + */ +PVOID PhCreateOpenFileDialog( + VOID + ) +{ + OPENFILENAME *ofn; + PVOID ofnFileDialog; + + if (PHP_USE_IFILEDIALOG) + { + IFileDialog *fileDialog; + + if (SUCCEEDED(CoCreateInstance( + &CLSID_FileOpenDialog, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IFileDialog, + &fileDialog + ))) + { + // The default options are fine. + return PhpCreateFileDialog(FALSE, NULL, fileDialog); + } + } + + ofn = PhpCreateOpenFileName(); + ofnFileDialog = PhpCreateFileDialog(FALSE, ofn, NULL); + PhSetFileDialogOptions(ofnFileDialog, PH_FILEDIALOG_PATHMUSTEXIST | PH_FILEDIALOG_FILEMUSTEXIST | PH_FILEDIALOG_STRICTFILETYPES); + + return ofnFileDialog; +} + +/** + * Creates a file dialog for the user to select a file to save to. + * + * \return An opaque pointer representing the file dialog. You must free the file dialog using + * PhFreeFileDialog() when you no longer need it. + */ +PVOID PhCreateSaveFileDialog( + VOID + ) +{ + OPENFILENAME *ofn; + PVOID ofnFileDialog; + + if (PHP_USE_IFILEDIALOG) + { + IFileDialog *fileDialog; + + if (SUCCEEDED(CoCreateInstance( + &CLSID_FileSaveDialog, + NULL, + CLSCTX_INPROC_SERVER, + &IID_IFileDialog, + &fileDialog + ))) + { + // The default options are fine. + return PhpCreateFileDialog(TRUE, NULL, fileDialog); + } + } + + ofn = PhpCreateOpenFileName(); + ofnFileDialog = PhpCreateFileDialog(TRUE, ofn, NULL); + PhSetFileDialogOptions(ofnFileDialog, PH_FILEDIALOG_PATHMUSTEXIST | PH_FILEDIALOG_OVERWRITEPROMPT | PH_FILEDIALOG_STRICTFILETYPES); + + return ofnFileDialog; +} + +/** + * Frees a file dialog. + * + * \param FileDialog The file dialog. + */ +VOID PhFreeFileDialog( + _In_ PVOID FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + IFileDialog_Release(fileDialog->u.FileDialog); + } + else + { + PhpFreeOpenFileName(fileDialog->u.OpenFileName); + } + + PhFree(fileDialog); +} + +/** + * Shows a file dialog to the user. + * + * \param hWnd A handle to the parent window. + * \param FileDialog The file dialog. + * + * \return TRUE if the user selected a file, FALSE if the user cancelled the operation or an error + * occurred. + */ +BOOLEAN PhShowFileDialog( + _In_ HWND hWnd, + _In_ PVOID FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + // Set a blank default extension. This will have an effect when the user selects a different + // file type. + IFileDialog_SetDefaultExtension(fileDialog->u.FileDialog, L""); + + return SUCCEEDED(IFileDialog_Show(fileDialog->u.FileDialog, hWnd)); + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + + ofn->hwndOwner = hWnd; + + // Determine whether the structure represents a open or save dialog and call the appropriate + // function. + if (!fileDialog->Save) + { + return GetOpenFileName(ofn); + } + else + { + return GetSaveFileName(ofn); + } + } +} + +static const PH_FLAG_MAPPING PhpFileDialogIfdMappings[] = +{ + { PH_FILEDIALOG_CREATEPROMPT, FOS_CREATEPROMPT }, + { PH_FILEDIALOG_PATHMUSTEXIST, FOS_PATHMUSTEXIST }, + { PH_FILEDIALOG_FILEMUSTEXIST, FOS_FILEMUSTEXIST }, + { PH_FILEDIALOG_SHOWHIDDEN, FOS_FORCESHOWHIDDEN }, + { PH_FILEDIALOG_NODEREFERENCELINKS, FOS_NODEREFERENCELINKS }, + { PH_FILEDIALOG_OVERWRITEPROMPT, FOS_OVERWRITEPROMPT }, + { PH_FILEDIALOG_DEFAULTEXPANDED, FOS_DEFAULTNOMINIMODE }, + { PH_FILEDIALOG_STRICTFILETYPES, FOS_STRICTFILETYPES }, + { PH_FILEDIALOG_PICKFOLDERS, FOS_PICKFOLDERS } +}; + +static const PH_FLAG_MAPPING PhpFileDialogOfnMappings[] = +{ + { PH_FILEDIALOG_CREATEPROMPT, OFN_CREATEPROMPT }, + { PH_FILEDIALOG_PATHMUSTEXIST, OFN_PATHMUSTEXIST }, + { PH_FILEDIALOG_FILEMUSTEXIST, OFN_FILEMUSTEXIST }, + { PH_FILEDIALOG_SHOWHIDDEN, OFN_FORCESHOWHIDDEN }, + { PH_FILEDIALOG_NODEREFERENCELINKS, OFN_NODEREFERENCELINKS }, + { PH_FILEDIALOG_OVERWRITEPROMPT, OFN_OVERWRITEPROMPT } +}; + +/** + * Gets the options for a file dialog. + * + * \param FileDialog The file dialog. + * + * \return The currently enabled options. See the documentation for PhSetFileDialogOptions() for + * details. + */ +ULONG PhGetFileDialogOptions( + _In_ PVOID FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + FILEOPENDIALOGOPTIONS dialogOptions; + ULONG options; + + if (SUCCEEDED(IFileDialog_GetOptions(fileDialog->u.FileDialog, &dialogOptions))) + { + options = 0; + + PhMapFlags2( + &options, + dialogOptions, + PhpFileDialogIfdMappings, + sizeof(PhpFileDialogIfdMappings) / sizeof(PH_FLAG_MAPPING) + ); + + return options; + } + else + { + return 0; + } + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + ULONG options; + + options = 0; + + PhMapFlags2( + &options, + ofn->Flags, + PhpFileDialogOfnMappings, + sizeof(PhpFileDialogOfnMappings) / sizeof(PH_FLAG_MAPPING) + ); + + return options; + } +} + +/** + * Sets the options for a file dialog. + * + * \param FileDialog The file dialog. + * \param Options A combination of flags specifying the options. + * \li \c PH_FILEDIALOG_CREATEPROMPT A prompt for creation will be displayed when the selected item + * does not exist. This is only valid for Save dialogs. + * \li \c PH_FILEDIALOG_PATHMUSTEXIST The selected item must be in an existing folder. This is + * enabled by default. + * \li \c PH_FILEDIALOG_FILEMUSTEXIST The selected item must exist. This is enabled by default and + * is only valid for Open dialogs. + * \li \c PH_FILEDIALOG_SHOWHIDDEN Items with the System and Hidden attributes will be displayed. + * \li \c PH_FILEDIALOG_NODEREFERENCELINKS Shortcuts will not be followed, allowing .lnk files to be + * opened. + * \li \c PH_FILEDIALOG_OVERWRITEPROMPT An overwrite prompt will be displayed if an existing item is + * selected. This is enabled by default and is only valid for Save dialogs. + * \li \c PH_FILEDIALOG_DEFAULTEXPANDED The file dialog should be expanded by default (i.e. the + * folder browser should be displayed). This is only valid for Save dialogs. + */ +VOID PhSetFileDialogOptions( + _In_ PVOID FileDialog, + _In_ ULONG Options + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + FILEOPENDIALOGOPTIONS dialogOptions; + + if (SUCCEEDED(IFileDialog_GetOptions(fileDialog->u.FileDialog, &dialogOptions))) + { + PhMapFlags1( + &dialogOptions, + Options, + PhpFileDialogIfdMappings, + sizeof(PhpFileDialogIfdMappings) / sizeof(PH_FLAG_MAPPING) + ); + + IFileDialog_SetOptions(fileDialog->u.FileDialog, dialogOptions); + } + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + + PhMapFlags1( + &ofn->Flags, + Options, + PhpFileDialogOfnMappings, + sizeof(PhpFileDialogOfnMappings) / sizeof(PH_FLAG_MAPPING) + ); + } +} + +/** + * Gets the index of the currently selected file type filter for a file dialog. + * + * \param FileDialog The file dialog. + * + * \return The one-based index of the selected file type, or 0 if an error occurred. + */ +ULONG PhGetFileDialogFilterIndex( + _In_ PVOID FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + ULONG index; + + if (SUCCEEDED(IFileDialog_GetFileTypeIndex(fileDialog->u.FileDialog, &index))) + { + return index; + } + else + { + return 0; + } + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + + return ofn->nFilterIndex; + } +} + +/** + * Sets the file type filter for a file dialog. + * + * \param FileDialog The file dialog. + * \param Filters A pointer to an array of file type structures. + * \param NumberOfFilters The number of file types. + */ +VOID PhSetFileDialogFilter( + _In_ PVOID FileDialog, + _In_ PPH_FILETYPE_FILTER Filters, + _In_ ULONG NumberOfFilters + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + IFileDialog_SetFileTypes( + fileDialog->u.FileDialog, + NumberOfFilters, + (COMDLG_FILTERSPEC *)Filters + ); + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + PPH_STRING filterString; + PH_STRING_BUILDER filterBuilder; + ULONG i; + + PhInitializeStringBuilder(&filterBuilder, 10); + + for (i = 0; i < NumberOfFilters; i++) + { + PhAppendStringBuilder2(&filterBuilder, Filters[i].Name); + PhAppendCharStringBuilder(&filterBuilder, 0); + PhAppendStringBuilder2(&filterBuilder, Filters[i].Filter); + PhAppendCharStringBuilder(&filterBuilder, 0); + } + + filterString = PhFinalStringBuilderString(&filterBuilder); + + if (ofn->lpstrFilter) + PhFree((PVOID)ofn->lpstrFilter); + + ofn->lpstrFilter = PhAllocateCopy(filterString->Buffer, filterString->Length + 2); + PhDereferenceObject(filterString); + } +} + +/** + * Gets the file name selected in a file dialog. + * + * \param FileDialog The file dialog. + * + * \return A pointer to a string containing the file name. You must free the string using + * PhDereferenceObject() when you no longer need it. + */ +PPH_STRING PhGetFileDialogFileName( + _In_ PVOID FileDialog + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + + if (fileDialog->UseIFileDialog) + { + IShellItem *result; + PPH_STRING fileName = NULL; + + if (SUCCEEDED(IFileDialog_GetResult(fileDialog->u.FileDialog, &result))) + { + PWSTR name; + + if (SUCCEEDED(IShellItem_GetDisplayName(result, SIGDN_FILESYSPATH, &name))) + { + fileName = PhCreateString(name); + CoTaskMemFree(name); + } + + IShellItem_Release(result); + } + + if (!fileName) + { + PWSTR name; + + if (SUCCEEDED(IFileDialog_GetFileName(fileDialog->u.FileDialog, &name))) + { + fileName = PhCreateString(name); + CoTaskMemFree(name); + } + } + + return fileName; + } + else + { + return PhCreateString(fileDialog->u.OpenFileName->lpstrFile); + } +} + +/** + * Sets the file name of a file dialog. + * + * \param FileDialog The file dialog. + * \param FileName The new file name. + */ +VOID PhSetFileDialogFileName( + _In_ PVOID FileDialog, + _In_ PWSTR FileName + ) +{ + PPHP_FILE_DIALOG fileDialog = FileDialog; + PH_STRINGREF fileName; + + PhInitializeStringRefLongHint(&fileName, FileName); + + if (fileDialog->UseIFileDialog) + { + IShellItem *shellItem = NULL; + PH_STRINGREF pathNamePart; + PH_STRINGREF baseNamePart; + + if (PhSplitStringRefAtLastChar(&fileName, '\\', &pathNamePart, &baseNamePart) && + SHParseDisplayName_Import() && SHCreateShellItem_Import()) + { + LPITEMIDLIST item; + SFGAOF attributes; + PPH_STRING pathName; + + pathName = PhCreateString2(&pathNamePart); + + if (SUCCEEDED(SHParseDisplayName_Import()(pathName->Buffer, NULL, &item, 0, &attributes))) + { + SHCreateShellItem_Import()(NULL, NULL, item, &shellItem); + CoTaskMemFree(item); + } + + PhDereferenceObject(pathName); + } + + if (shellItem) + { + IFileDialog_SetFolder(fileDialog->u.FileDialog, shellItem); + IFileDialog_SetFileName(fileDialog->u.FileDialog, baseNamePart.Buffer); + IShellItem_Release(shellItem); + } + else + { + IFileDialog_SetFileName(fileDialog->u.FileDialog, FileName); + } + } + else + { + OPENFILENAME *ofn = fileDialog->u.OpenFileName; + + if (PhFindCharInStringRef(&fileName, '/', FALSE) != -1 || PhFindCharInStringRef(&fileName, '\"', FALSE) != -1) + { + // It refuses to take any filenames with a slash or quotation mark. + return; + } + + PhFree(ofn->lpstrFile); + + ofn->nMaxFile = (ULONG)max(fileName.Length / sizeof(WCHAR) + 1, 0x400); + ofn->lpstrFile = PhAllocate(ofn->nMaxFile * 2); + memcpy(ofn->lpstrFile, fileName.Buffer, fileName.Length + sizeof(WCHAR)); + } +} + +/** + * Determines if an executable image is packed. + * + * \param FileName The file name of the image. + * \param IsPacked A variable that receives TRUE if the image is packed, otherwise FALSE. + * \param NumberOfModules A variable that receives the number of DLLs that the image imports + * functions from. + * \param NumberOfFunctions A variable that receives the number of functions that the image imports. + */ +NTSTATUS PhIsExecutablePacked( + _In_ PWSTR FileName, + _Out_ PBOOLEAN IsPacked, + _Out_opt_ PULONG NumberOfModules, + _Out_opt_ PULONG NumberOfFunctions + ) +{ + // An image is packed if: + // + // 1. It references fewer than 3 modules, and + // 2. It imports fewer than 5 functions, and + // 3. It does not use the Native subsystem. + // + // Or: + // + // 1. The function-to-module ratio is lower than 3 (on average fewer than 3 functions are + // imported from each module), and + // 2. It references more than 2 modules but fewer than 6 modules. + // + // Or: + // + // 1. The function-to-module ratio is lower than 2 (on average fewer than 2 functions are + // imported from each module), and + // 2. It references more than 5 modules but fewer than 31 modules. + // + // Or: + // + // 1. It does not have a section named ".text". + // + // An image is not considered to be packed if it has only one import from a module named + // "mscoree.dll". + + NTSTATUS status; + PH_MAPPED_IMAGE mappedImage; + PH_MAPPED_IMAGE_IMPORTS imports; + PH_MAPPED_IMAGE_IMPORT_DLL importDll; + ULONG i; + //ULONG limitNumberOfSections; + ULONG limitNumberOfModules; + ULONG numberOfModules; + ULONG numberOfFunctions = 0; + BOOLEAN hasTextSection = FALSE; + BOOLEAN isModuleMscoree = FALSE; + BOOLEAN isPacked; + + status = PhLoadMappedImage( + FileName, + NULL, + TRUE, + &mappedImage + ); + + if (!NT_SUCCESS(status)) + return status; + + // Go through the sections and look for the ".text" section. + + // This rule is currently disabled. + hasTextSection = TRUE; + + //limitNumberOfSections = min(mappedImage.NumberOfSections, 64); + + //for (i = 0; i < limitNumberOfSections; i++) + //{ + // CHAR sectionName[IMAGE_SIZEOF_SHORT_NAME + 1]; + + // if (PhGetMappedImageSectionName( + // &mappedImage.Sections[i], + // sectionName, + // IMAGE_SIZEOF_SHORT_NAME + 1, + // NULL + // )) + // { + // if (STR_IEQUAL(sectionName, ".text")) + // { + // hasTextSection = TRUE; + // break; + // } + // } + //} + + status = PhGetMappedImageImports( + &imports, + &mappedImage + ); + + if (!NT_SUCCESS(status)) + goto CleanupExit; + + // Get the module and function totals. + + numberOfModules = imports.NumberOfDlls; + limitNumberOfModules = min(numberOfModules, 64); + + for (i = 0; i < limitNumberOfModules; i++) + { + if (!NT_SUCCESS(status = PhGetMappedImageImportDll( + &imports, + i, + &importDll + ))) + goto CleanupExit; + + if (PhEqualBytesZ(importDll.Name, "mscoree.dll", TRUE)) + isModuleMscoree = TRUE; + + numberOfFunctions += importDll.NumberOfEntries; + } + + // Determine if the image is packed. + + if ( + numberOfModules != 0 && + ( + // Rule 1 + (numberOfModules < 3 && numberOfFunctions < 5 && + mappedImage.NtHeaders->OptionalHeader.Subsystem != IMAGE_SUBSYSTEM_NATIVE) || + // Rule 2 + ((numberOfFunctions / numberOfModules) < 3 && + numberOfModules > 2 && numberOfModules < 5) || + // Rule 3 + ((numberOfFunctions / numberOfModules) < 2 && + numberOfModules > 4 && numberOfModules < 31) || + // Rule 4 + !hasTextSection + ) && + // Additional .NET rule + !(numberOfModules == 1 && numberOfFunctions == 1 && isModuleMscoree) + ) + { + isPacked = TRUE; + } + else + { + isPacked = FALSE; + } + + *IsPacked = isPacked; + + if (NumberOfModules) + *NumberOfModules = numberOfModules; + if (NumberOfFunctions) + *NumberOfFunctions = numberOfFunctions; + +CleanupExit: + PhUnloadMappedImage(&mappedImage); + + return status; +} + +ULONG PhCrc32( + _In_ ULONG Crc, + _In_reads_(Length) PCHAR Buffer, + _In_ SIZE_T Length + ) +{ + Crc ^= 0xffffffff; + + while (Length--) + Crc = (Crc >> 8) ^ PhCrc32Table[(Crc ^ *Buffer++) & 0xff]; + + return Crc ^ 0xffffffff; +} + +C_ASSERT(RTL_FIELD_SIZE(PH_HASH_CONTEXT, Context) >= sizeof(MD5_CTX)); +C_ASSERT(RTL_FIELD_SIZE(PH_HASH_CONTEXT, Context) >= sizeof(A_SHA_CTX)); + +/** + * Initializes hashing. + * + * \param Context A hashing context structure. + * \param Algorithm The hash algorithm to use: + * \li \c Md5HashAlgorithm MD5 (128 bits) + * \li \c Sha1HashAlgorithm SHA-1 (160 bits) + * \li \c Crc32HashAlgorithm CRC-32-IEEE 802.3 (32 bits) + */ +VOID PhInitializeHash( + _Out_ PPH_HASH_CONTEXT Context, + _In_ PH_HASH_ALGORITHM Algorithm + ) +{ + Context->Algorithm = Algorithm; + + switch (Algorithm) + { + case Md5HashAlgorithm: + MD5Init((MD5_CTX *)Context->Context); + break; + case Sha1HashAlgorithm: + A_SHAInit((A_SHA_CTX *)Context->Context); + break; + case Crc32HashAlgorithm: + Context->Context[0] = 0; + break; + default: + PhRaiseStatus(STATUS_INVALID_PARAMETER_2); + break; + } +} + +/** + * Hashes a block of data. + * + * \param Context A hashing context structure. + * \param Buffer The block of data. + * \param Length The number of bytes in the block. + */ +VOID PhUpdateHash( + _Inout_ PPH_HASH_CONTEXT Context, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ) +{ + switch (Context->Algorithm) + { + case Md5HashAlgorithm: + MD5Update((MD5_CTX *)Context->Context, (PUCHAR)Buffer, Length); + break; + case Sha1HashAlgorithm: + A_SHAUpdate((A_SHA_CTX *)Context->Context, (PUCHAR)Buffer, Length); + break; + case Crc32HashAlgorithm: + Context->Context[0] = PhCrc32(Context->Context[0], (PUCHAR)Buffer, Length); + break; + default: + PhRaiseStatus(STATUS_INVALID_PARAMETER); + } +} + +/** + * Computes the final hash value. + * + * \param Context A hashing context structure. + * \param Hash A buffer which receives the final hash value. + * \param HashLength The size of the buffer, in bytes. + * \param ReturnLength A variable which receives the required size of the buffer, in bytes. + */ +BOOLEAN PhFinalHash( + _Inout_ PPH_HASH_CONTEXT Context, + _Out_writes_bytes_(HashLength) PVOID Hash, + _In_ ULONG HashLength, + _Out_opt_ PULONG ReturnLength + ) +{ + BOOLEAN result; + ULONG returnLength; + + result = FALSE; + + switch (Context->Algorithm) + { + case Md5HashAlgorithm: + if (HashLength >= 16) + { + MD5Final((MD5_CTX *)Context->Context); + memcpy(Hash, ((MD5_CTX *)Context->Context)->digest, 16); + result = TRUE; + } + + returnLength = 16; + + break; + case Sha1HashAlgorithm: + if (HashLength >= 20) + { + A_SHAFinal((A_SHA_CTX *)Context->Context, (PUCHAR)Hash); + result = TRUE; + } + + returnLength = 20; + + break; + case Crc32HashAlgorithm: + if (HashLength >= 4) + { + *(PULONG)Hash = Context->Context[0]; + result = TRUE; + } + + returnLength = 4; + + break; + default: + PhRaiseStatus(STATUS_INVALID_PARAMETER); + } + + if (ReturnLength) + *ReturnLength = returnLength; + + return result; +} + +/** + * Parses one part of a command line string. Quotation marks and backslashes are handled + * appropriately. + * + * \param CommandLine The entire command line string. + * \param Index The starting index of the command line part to be parsed. There should be no leading + * whitespace at this index. The index is updated to point to the end of the command line part. + */ +PPH_STRING PhParseCommandLinePart( + _In_ PPH_STRINGREF CommandLine, + _Inout_ PULONG_PTR Index + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T length; + SIZE_T i; + + ULONG numberOfBackslashes; + BOOLEAN inQuote; + BOOLEAN endOfValue; + + length = CommandLine->Length / 2; + i = *Index; + + // This function follows the rules used by CommandLineToArgvW: + // + // * 2n backslashes and a quotation mark produces n backslashes and a quotation mark + // (non-literal). + // * 2n + 1 backslashes and a quotation mark produces n and a quotation mark (literal). + // * n backslashes and no quotation mark produces n backslashes. + + PhInitializeStringBuilder(&stringBuilder, 10); + numberOfBackslashes = 0; + inQuote = FALSE; + endOfValue = FALSE; + + for (; i < length; i++) + { + switch (CommandLine->Buffer[i]) + { + case '\\': + numberOfBackslashes++; + break; + case '\"': + if (numberOfBackslashes != 0) + { + if (numberOfBackslashes & 1) + { + numberOfBackslashes /= 2; + + if (numberOfBackslashes != 0) + { + PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); + numberOfBackslashes = 0; + } + + PhAppendCharStringBuilder(&stringBuilder, '\"'); + + break; + } + else + { + numberOfBackslashes /= 2; + PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); + numberOfBackslashes = 0; + } + } + + if (!inQuote) + inQuote = TRUE; + else + inQuote = FALSE; + + break; + default: + if (numberOfBackslashes != 0) + { + PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); + numberOfBackslashes = 0; + } + + if (CommandLine->Buffer[i] == ' ' && !inQuote) + { + endOfValue = TRUE; + } + else + { + PhAppendCharStringBuilder(&stringBuilder, CommandLine->Buffer[i]); + } + + break; + } + + if (endOfValue) + break; + } + + *Index = i; + + return PhFinalStringBuilderString(&stringBuilder); +} + +/** + * Parses a command line string. + * + * \param CommandLine The command line string. + * \param Options An array of supported command line options. + * \param NumberOfOptions The number of elements in \a Options. + * \param Flags A combination of flags. + * \li \c PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS Unknown command line options are ignored instead of + * failing the function. + * \li \c PH_COMMAND_LINE_IGNORE_FIRST_PART The first part of the command line string is ignored. + * This is used when the first part of the string contains the executable file name. + * \param Callback A callback function to execute for each command line option found. + * \param Context A user-defined value to pass to \a Callback. + */ +BOOLEAN PhParseCommandLine( + _In_ PPH_STRINGREF CommandLine, + _In_opt_ PPH_COMMAND_LINE_OPTION Options, + _In_ ULONG NumberOfOptions, + _In_ ULONG Flags, + _In_ PPH_COMMAND_LINE_CALLBACK Callback, + _In_opt_ PVOID Context + ) +{ + SIZE_T i; + SIZE_T j; + SIZE_T length; + BOOLEAN cont = TRUE; + BOOLEAN wasFirst = TRUE; + + PH_STRINGREF optionName; + PPH_COMMAND_LINE_OPTION option = NULL; + PPH_STRING optionValue; + + if (CommandLine->Length == 0) + return TRUE; + + i = 0; + length = CommandLine->Length / 2; + + while (TRUE) + { + // Skip spaces. + while (i < length && CommandLine->Buffer[i] == ' ') + i++; + + if (i >= length) + break; + + if (option && + (option->Type == MandatoryArgumentType || + (option->Type == OptionalArgumentType && CommandLine->Buffer[i] != '-'))) + { + // Read the value and execute the callback function. + + optionValue = PhParseCommandLinePart(CommandLine, &i); + cont = Callback(option, optionValue, Context); + PhDereferenceObject(optionValue); + + if (!cont) + break; + + option = NULL; + } + else if (CommandLine->Buffer[i] == '-') + { + ULONG_PTR originalIndex; + SIZE_T optionNameLength; + + // Read the option (only alphanumeric characters allowed). + + // Skip the dash. + i++; + + originalIndex = i; + + for (; i < length; i++) + { + if (!iswalnum(CommandLine->Buffer[i]) && CommandLine->Buffer[i] != '-') + break; + } + + optionNameLength = i - originalIndex; + + optionName.Buffer = &CommandLine->Buffer[originalIndex]; + optionName.Length = optionNameLength * 2; + + // Take care of any pending optional argument. + + if (option && option->Type == OptionalArgumentType) + { + cont = Callback(option, NULL, Context); + + if (!cont) + break; + + option = NULL; + } + + // Find the option descriptor. + + option = NULL; + + for (j = 0; j < NumberOfOptions; j++) + { + if (PhEqualStringRef2(&optionName, Options[j].Name, FALSE)) + { + option = &Options[j]; + break; + } + } + + if (!option && !(Flags & PH_COMMAND_LINE_IGNORE_UNKNOWN_OPTIONS)) + return FALSE; + + if (option && option->Type == NoArgumentType) + { + cont = Callback(option, NULL, Context); + + if (!cont) + break; + + option = NULL; + } + + wasFirst = FALSE; + } + else + { + PPH_STRING value; + + value = PhParseCommandLinePart(CommandLine, &i); + + if ((Flags & PH_COMMAND_LINE_IGNORE_FIRST_PART) && wasFirst) + { + PhDereferenceObject(value); + value = NULL; + } + + if (value) + { + cont = Callback(NULL, value, Context); + PhDereferenceObject(value); + + if (!cont) + break; + } + + wasFirst = FALSE; + } + } + + if (cont && option && option->Type == OptionalArgumentType) + Callback(option, NULL, Context); + + return TRUE; +} + +/** + * Escapes a string for use in a command line. + * + * \param String The string to escape. + * + * \return The escaped string. + * + * \remarks Only the double quotation mark is escaped. + */ +PPH_STRING PhEscapeCommandLinePart( + _In_ PPH_STRINGREF String + ) +{ + static PH_STRINGREF backslashAndQuote = PH_STRINGREF_INIT(L"\\\""); + + PH_STRING_BUILDER stringBuilder; + ULONG length; + ULONG i; + + ULONG numberOfBackslashes; + + length = (ULONG)String->Length / 2; + PhInitializeStringBuilder(&stringBuilder, String->Length / 2 * 3); + numberOfBackslashes = 0; + + // Simply replacing " with \" won't work here. See PhParseCommandLinePart for the quoting rules. + + for (i = 0; i < length; i++) + { + switch (String->Buffer[i]) + { + case '\\': + numberOfBackslashes++; + break; + case '\"': + if (numberOfBackslashes != 0) + { + PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes * 2); + numberOfBackslashes = 0; + } + + PhAppendStringBuilder(&stringBuilder, &backslashAndQuote); + + break; + default: + if (numberOfBackslashes != 0) + { + PhAppendCharStringBuilder2(&stringBuilder, '\\', numberOfBackslashes); + numberOfBackslashes = 0; + } + + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); + + break; + } + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +BOOLEAN PhpSearchFilePath( + _In_ PWSTR FileName, + _In_opt_ PWSTR Extension, + _Out_writes_(MAX_PATH) PWSTR Buffer + ) +{ + NTSTATUS status; + ULONG result; + UNICODE_STRING fileName; + OBJECT_ATTRIBUTES objectAttributes; + FILE_BASIC_INFORMATION basicInfo; + + result = SearchPath( + NULL, + FileName, + Extension, + MAX_PATH, + Buffer, + NULL + ); + + if (result == 0 || result >= MAX_PATH) + return FALSE; + + // Make sure this is not a directory. + + if (!NT_SUCCESS(RtlDosPathNameToNtPathName_U( + Buffer, + &fileName, + NULL, + NULL + ))) + return FALSE; + + InitializeObjectAttributes( + &objectAttributes, + &fileName, + OBJ_CASE_INSENSITIVE, + NULL, + NULL + ); + + status = NtQueryAttributesFile(&objectAttributes, &basicInfo); + RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer); + + if (!NT_SUCCESS(status)) + return FALSE; + if (basicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return FALSE; + + return TRUE; +} + +/** + * Parses a command line string. If the string does not contain quotation marks around the file name + * part, the function determines the file name to use. + * + * \param CommandLine The command line string. + * \param FileName A variable which receives the part of \a CommandLine that contains the file name. + * \param Arguments A variable which receives the part of \a CommandLine that contains the + * arguments. + * \param FullFileName A variable which receives the full path and file name. This may be NULL if + * the file was not found. + */ +BOOLEAN PhParseCommandLineFuzzy( + _In_ PPH_STRINGREF CommandLine, + _Out_ PPH_STRINGREF FileName, + _Out_ PPH_STRINGREF Arguments, + _Out_opt_ PPH_STRING *FullFileName + ) +{ + static PH_STRINGREF whitespace = PH_STRINGREF_INIT(L" \t"); + + PH_STRINGREF commandLine; + PH_STRINGREF temp; + PH_STRINGREF currentPart; + PH_STRINGREF remainingPart; + WCHAR buffer[MAX_PATH]; + WCHAR originalChar; + + commandLine = *CommandLine; + PhTrimStringRef(&commandLine, &whitespace, 0); + + if (commandLine.Length == 0) + { + PhInitializeEmptyStringRef(FileName); + PhInitializeEmptyStringRef(Arguments); + + if (FullFileName) + *FullFileName = NULL; + + return FALSE; + } + + if (*commandLine.Buffer == '"') + { + PH_STRINGREF arguments; + + PhSkipStringRef(&commandLine, sizeof(WCHAR)); + + // Find the matching quote character and we have our file name. + + if (!PhSplitStringRefAtChar(&commandLine, '"', &commandLine, &arguments)) + { + PhSkipStringRef(&commandLine, -(LONG_PTR)sizeof(WCHAR)); // Unskip the initial quote character + *FileName = commandLine; + PhInitializeEmptyStringRef(Arguments); + + if (FullFileName) + *FullFileName = NULL; + + return FALSE; + } + + PhTrimStringRef(&arguments, &whitespace, PH_TRIM_START_ONLY); + *FileName = commandLine; + *Arguments = arguments; + + if (FullFileName) + { + PPH_STRING tempCommandLine; + + tempCommandLine = PhCreateString2(&commandLine); + + if (PhpSearchFilePath(tempCommandLine->Buffer, L".exe", buffer)) + { + *FullFileName = PhCreateString(buffer); + } + else + { + *FullFileName = NULL; + } + + PhDereferenceObject(tempCommandLine); + } + + return TRUE; + } + + // Try to find an existing executable file, starting with the first part of the command line and + // successively restoring the rest of the command line. + // For example, in "C:\Program Files\Internet Explorer\iexplore", we try to match: + // * "C:\Program" + // * "C:\Program Files\Internet" + // * "C:\Program Files\Internet " + // * "C:\Program Files\Internet " + // * "C:\Program Files\Internet Explorer\iexplore" + // + // Note that we do not trim whitespace in each part because filenames can contain trailing + // whitespace before the extension (e.g. "Internet .exe"). + + temp.Buffer = PhAllocate(commandLine.Length + sizeof(WCHAR)); + memcpy(temp.Buffer, commandLine.Buffer, commandLine.Length); + temp.Buffer[commandLine.Length / sizeof(WCHAR)] = 0; + temp.Length = commandLine.Length; + remainingPart = temp; + + while (remainingPart.Length != 0) + { + BOOLEAN found; + BOOLEAN result; + + found = PhSplitStringRefAtChar(&remainingPart, ' ', ¤tPart, &remainingPart); + + if (found) + { + originalChar = *(remainingPart.Buffer - 1); + *(remainingPart.Buffer - 1) = 0; + } + + result = PhpSearchFilePath(temp.Buffer, L".exe", buffer); + + if (found) + { + *(remainingPart.Buffer - 1) = originalChar; + } + + if (result) + { + FileName->Buffer = commandLine.Buffer; + FileName->Length = ((PCHAR)currentPart.Buffer - (PCHAR)temp.Buffer) + currentPart.Length; + + PhTrimStringRef(&remainingPart, &whitespace, PH_TRIM_START_ONLY); + *Arguments = remainingPart; + + if (FullFileName) + *FullFileName = PhCreateString(buffer); + + PhFree(temp.Buffer); + + return TRUE; + } + } + + PhFree(temp.Buffer); + + *FileName = *CommandLine; + PhInitializeEmptyStringRef(Arguments); + + if (FullFileName) + *FullFileName = NULL; + + return FALSE; +} diff --git a/phlib/verify.c b/phlib/verify.c new file mode 100644 index 0000000..376f138 --- /dev/null +++ b/phlib/verify.c @@ -0,0 +1,671 @@ +/* + * Process Hacker - + * image verification + * + * Copyright (C) 2009-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +_CryptCATAdminCalcHashFromFileHandle CryptCATAdminCalcHashFromFileHandle; +_CryptCATAdminCalcHashFromFileHandle2 CryptCATAdminCalcHashFromFileHandle2; +_CryptCATAdminAcquireContext CryptCATAdminAcquireContext; +_CryptCATAdminAcquireContext2 CryptCATAdminAcquireContext2; +_CryptCATAdminEnumCatalogFromHash CryptCATAdminEnumCatalogFromHash; +_CryptCATCatalogInfoFromContext CryptCATCatalogInfoFromContext; +_CryptCATAdminReleaseCatalogContext CryptCATAdminReleaseCatalogContext; +_CryptCATAdminReleaseContext CryptCATAdminReleaseContext; +_WTHelperProvDataFromStateData WTHelperProvDataFromStateData_I; +_WTHelperGetProvSignerFromChain WTHelperGetProvSignerFromChain_I; +_WinVerifyTrust WinVerifyTrust_I; +_CertNameToStr CertNameToStr_I; +_CertDuplicateCertificateContext CertDuplicateCertificateContext_I; +_CertFreeCertificateContext CertFreeCertificateContext_I; +static PH_INITONCE PhpVerifyInitOnce = PH_INITONCE_INIT; + +static GUID WinTrustActionGenericVerifyV2 = WINTRUST_ACTION_GENERIC_VERIFY_V2; +static GUID DriverActionVerify = DRIVER_ACTION_VERIFY; + +static VOID PhpVerifyInitialization( + VOID + ) +{ + HMODULE wintrust; + HMODULE crypt32; + + wintrust = LoadLibrary(L"wintrust.dll"); + crypt32 = LoadLibrary(L"crypt32.dll"); + + CryptCATAdminCalcHashFromFileHandle = (PVOID)GetProcAddress(wintrust, "CryptCATAdminCalcHashFromFileHandle"); + CryptCATAdminCalcHashFromFileHandle2 = (PVOID)GetProcAddress(wintrust, "CryptCATAdminCalcHashFromFileHandle2"); + CryptCATAdminAcquireContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminAcquireContext"); + CryptCATAdminAcquireContext2 = (PVOID)GetProcAddress(wintrust, "CryptCATAdminAcquireContext2"); + CryptCATAdminEnumCatalogFromHash = (PVOID)GetProcAddress(wintrust, "CryptCATAdminEnumCatalogFromHash"); + CryptCATCatalogInfoFromContext = (PVOID)GetProcAddress(wintrust, "CryptCATCatalogInfoFromContext"); + CryptCATAdminReleaseCatalogContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminReleaseCatalogContext"); + CryptCATAdminReleaseContext = (PVOID)GetProcAddress(wintrust, "CryptCATAdminReleaseContext"); + WTHelperProvDataFromStateData_I = (PVOID)GetProcAddress(wintrust, "WTHelperProvDataFromStateData"); + WTHelperGetProvSignerFromChain_I = (PVOID)GetProcAddress(wintrust, "WTHelperGetProvSignerFromChain"); + WinVerifyTrust_I = (PVOID)GetProcAddress(wintrust, "WinVerifyTrust"); + CertNameToStr_I = (PVOID)GetProcAddress(crypt32, "CertNameToStrW"); + CertDuplicateCertificateContext_I = (PVOID)GetProcAddress(crypt32, "CertDuplicateCertificateContext"); + CertFreeCertificateContext_I = (PVOID)GetProcAddress(crypt32, "CertFreeCertificateContext"); +} + +VERIFY_RESULT PhpStatusToVerifyResult( + _In_ LONG Status + ) +{ + switch (Status) + { + case 0: + return VrTrusted; + case TRUST_E_NOSIGNATURE: + return VrNoSignature; + case CERT_E_EXPIRED: + return VrExpired; + case CERT_E_REVOKED: + return VrRevoked; + case TRUST_E_EXPLICIT_DISTRUST: + return VrDistrust; + case CRYPT_E_SECURITY_SETTINGS: + return VrSecuritySettings; + case TRUST_E_BAD_DIGEST: + return VrBadSignature; + default: + return VrSecuritySettings; + } +} + +BOOLEAN PhpGetSignaturesFromStateData( + _In_ HANDLE StateData, + _Out_ PCERT_CONTEXT **Signatures, + _Out_ PULONG NumberOfSignatures + ) +{ + PCRYPT_PROVIDER_DATA provData; + PCRYPT_PROVIDER_SGNR sgnr; + PCERT_CONTEXT *signatures; + ULONG i; + ULONG numberOfSignatures; + ULONG index; + + provData = WTHelperProvDataFromStateData_I(StateData); + + if (!provData) + { + *Signatures = NULL; + *NumberOfSignatures = 0; + return FALSE; + } + + i = 0; + numberOfSignatures = 0; + + while (sgnr = WTHelperGetProvSignerFromChain_I(provData, i, FALSE, 0)) + { + if (sgnr->csCertChain != 0) + numberOfSignatures++; + + i++; + } + + if (numberOfSignatures != 0) + { + signatures = PhAllocate(numberOfSignatures * sizeof(PCERT_CONTEXT)); + i = 0; + index = 0; + + while (sgnr = WTHelperGetProvSignerFromChain_I(provData, i, FALSE, 0)) + { + if (sgnr->csCertChain != 0) + signatures[index++] = (PCERT_CONTEXT)CertDuplicateCertificateContext_I(sgnr->pasCertChain[0].pCert); + + i++; + } + } + else + { + signatures = NULL; + } + + *Signatures = signatures; + *NumberOfSignatures = numberOfSignatures; + + return TRUE; +} + +VOID PhpViewSignerInfo( + _In_ PPH_VERIFY_FILE_INFO Information, + _In_ HANDLE StateData + ) +{ + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static _CryptUIDlgViewSignerInfo cryptUIDlgViewSignerInfo; + + if (PhBeginInitOnce(&initOnce)) + { + HMODULE cryptui = LoadLibrary(L"cryptui.dll"); + + cryptUIDlgViewSignerInfo = (PVOID)GetProcAddress(cryptui, "CryptUIDlgViewSignerInfoW"); + PhEndInitOnce(&initOnce); + } + + if (cryptUIDlgViewSignerInfo) + { + CRYPTUI_VIEWSIGNERINFO_STRUCT viewSignerInfo = { sizeof(CRYPTUI_VIEWSIGNERINFO_STRUCT) }; + PCRYPT_PROVIDER_DATA provData; + PCRYPT_PROVIDER_SGNR sgnr; + + if (!(provData = WTHelperProvDataFromStateData_I(StateData))) + return; + if (!(sgnr = WTHelperGetProvSignerFromChain_I(provData, 0, FALSE, 0))) + return; + + viewSignerInfo.hwndParent = Information->hWnd; + viewSignerInfo.pSignerInfo = sgnr->psSigner; + viewSignerInfo.hMsg = provData->hMsg; + viewSignerInfo.pszOID = szOID_PKIX_KP_CODE_SIGNING; + cryptUIDlgViewSignerInfo(&viewSignerInfo); + } +} + +VERIFY_RESULT PhpVerifyFile( + _In_ PPH_VERIFY_FILE_INFO Information, + _In_ HANDLE FileHandle, + _In_ ULONG UnionChoice, + _In_ PVOID UnionData, + _In_ PGUID ActionId, + _In_opt_ PVOID PolicyCallbackData, + _Out_ PCERT_CONTEXT **Signatures, + _Out_ PULONG NumberOfSignatures + ) +{ + LONG status; + WINTRUST_DATA trustData = { 0 }; + + trustData.cbStruct = sizeof(WINTRUST_DATA); + trustData.pPolicyCallbackData = PolicyCallbackData; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; + trustData.dwUnionChoice = UnionChoice; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.dwProvFlags = WTD_SAFER_FLAG; + + trustData.pFile = UnionData; + + if (UnionChoice == WTD_CHOICE_CATALOG) + trustData.pCatalog = UnionData; + + if (Information->Flags & PH_VERIFY_PREVENT_NETWORK_ACCESS) + { + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + + if (WindowsVersion >= WINDOWS_VISTA) + trustData.dwProvFlags |= WTD_CACHE_ONLY_URL_RETRIEVAL; + else + trustData.dwProvFlags |= WTD_REVOCATION_CHECK_NONE; + } + + status = WinVerifyTrust_I(NULL, ActionId, &trustData); + PhpGetSignaturesFromStateData(trustData.hWVTStateData, Signatures, NumberOfSignatures); + + if (status == 0 && (Information->Flags & PH_VERIFY_VIEW_PROPERTIES)) + PhpViewSignerInfo(Information, trustData.hWVTStateData); + + // Close the state data. + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust_I(NULL, ActionId, &trustData); + + return PhpStatusToVerifyResult(status); +} + +BOOLEAN PhpCalculateFileHash( + _In_ HANDLE FileHandle, + _In_ PWSTR HashAlgorithm, + _Out_ PUCHAR *FileHash, + _Out_ PULONG FileHashLength, + _Out_ HANDLE *CatAdminHandle + ) +{ + HANDLE catAdminHandle; + PUCHAR fileHash; + ULONG fileHashLength; + + if (CryptCATAdminAcquireContext2) + { + if (!CryptCATAdminAcquireContext2(&catAdminHandle, &DriverActionVerify, HashAlgorithm, NULL, 0)) + return FALSE; + } + else + { + if (!CryptCATAdminAcquireContext(&catAdminHandle, &DriverActionVerify, 0)) + return FALSE; + } + + fileHashLength = 32; + fileHash = PhAllocate(fileHashLength); + + if (CryptCATAdminCalcHashFromFileHandle2) + { + if (!CryptCATAdminCalcHashFromFileHandle2(catAdminHandle, FileHandle, &fileHashLength, fileHash, 0)) + { + PhFree(fileHash); + fileHash = PhAllocate(fileHashLength); + + if (!CryptCATAdminCalcHashFromFileHandle2(catAdminHandle, FileHandle, &fileHashLength, fileHash, 0)) + { + CryptCATAdminReleaseContext(catAdminHandle, 0); + PhFree(fileHash); + return FALSE; + } + } + } + else + { + if (!CryptCATAdminCalcHashFromFileHandle(FileHandle, &fileHashLength, fileHash, 0)) + { + PhFree(fileHash); + fileHash = PhAllocate(fileHashLength); + + if (!CryptCATAdminCalcHashFromFileHandle(FileHandle, &fileHashLength, fileHash, 0)) + { + CryptCATAdminReleaseContext(catAdminHandle, 0); + PhFree(fileHash); + return FALSE; + } + } + } + + *FileHash = fileHash; + *FileHashLength = fileHashLength; + *CatAdminHandle = catAdminHandle; + + return TRUE; +} + +VERIFY_RESULT PhpVerifyFileFromCatalog( + _In_ PPH_VERIFY_FILE_INFO Information, + _In_ HANDLE FileHandle, + _In_opt_ PWSTR HashAlgorithm, + _Out_ PCERT_CONTEXT **Signatures, + _Out_ PULONG NumberOfSignatures + ) +{ + VERIFY_RESULT verifyResult = VrNoSignature; + PCERT_CONTEXT *signatures; + ULONG numberOfSignatures; + WINTRUST_CATALOG_INFO catalogInfo = { 0 }; + LARGE_INTEGER fileSize; + ULONG fileSizeLimit; + PUCHAR fileHash; + ULONG fileHashLength; + PPH_STRING fileHashTag; + HANDLE catAdminHandle; + HANDLE catInfoHandle; + ULONG i; + + *Signatures = NULL; + *NumberOfSignatures = 0; + + if (!NT_SUCCESS(PhGetFileSize(FileHandle, &fileSize))) + return VrNoSignature; + + signatures = NULL; + numberOfSignatures = 0; + + if (Information->FileSizeLimitForHash != -1) + { + fileSizeLimit = PH_VERIFY_DEFAULT_SIZE_LIMIT; + + if (Information->FileSizeLimitForHash != 0) + fileSizeLimit = Information->FileSizeLimitForHash; + + if (fileSize.QuadPart > fileSizeLimit) + return VrNoSignature; + } + + if (PhpCalculateFileHash(FileHandle, HashAlgorithm, &fileHash, &fileHashLength, &catAdminHandle)) + { + fileHashTag = PhBufferToHexStringEx(fileHash, fileHashLength, TRUE); + + // Search the system catalogs. + + catInfoHandle = CryptCATAdminEnumCatalogFromHash( + catAdminHandle, + fileHash, + fileHashLength, + 0, + NULL + ); + + if (catInfoHandle) + { + CATALOG_INFO ci = { 0 }; + DRIVER_VER_INFO verInfo = { 0 }; + + if (CryptCATCatalogInfoFromContext(catInfoHandle, &ci, 0)) + { + // Disable OS version checking by passing in a DRIVER_VER_INFO structure. + verInfo.cbStruct = sizeof(DRIVER_VER_INFO); + + catalogInfo.cbStruct = sizeof(catalogInfo); + catalogInfo.pcwszCatalogFilePath = ci.wszCatalogFile; + catalogInfo.pcwszMemberFilePath = Information->FileName; + catalogInfo.pcwszMemberTag = fileHashTag->Buffer; + catalogInfo.pbCalculatedFileHash = fileHash; + catalogInfo.cbCalculatedFileHash = fileHashLength; + catalogInfo.hCatAdmin = catAdminHandle; + verifyResult = PhpVerifyFile(Information, FileHandle, WTD_CHOICE_CATALOG, &catalogInfo, &DriverActionVerify, &verInfo, &signatures, &numberOfSignatures); + + if (verInfo.pcSignerCertContext) + CertFreeCertificateContext_I(verInfo.pcSignerCertContext); + } + + CryptCATAdminReleaseCatalogContext(catAdminHandle, catInfoHandle, 0); + } + else + { + // Search any user-supplied catalogs. + + for (i = 0; i < Information->NumberOfCatalogFileNames; i++) + { + PhFreeVerifySignatures(signatures, numberOfSignatures); + + catalogInfo.cbStruct = sizeof(catalogInfo); + catalogInfo.pcwszCatalogFilePath = Information->CatalogFileNames[i]; + catalogInfo.pcwszMemberFilePath = Information->FileName; + catalogInfo.pcwszMemberTag = fileHashTag->Buffer; + catalogInfo.pbCalculatedFileHash = fileHash; + catalogInfo.cbCalculatedFileHash = fileHashLength; + catalogInfo.hCatAdmin = catAdminHandle; + verifyResult = PhpVerifyFile(Information, FileHandle, WTD_CHOICE_CATALOG, &catalogInfo, &WinTrustActionGenericVerifyV2, NULL, &signatures, &numberOfSignatures); + + if (verifyResult == VrTrusted) + break; + } + } + + PhDereferenceObject(fileHashTag); + PhFree(fileHash); + CryptCATAdminReleaseContext(catAdminHandle, 0); + } + + *Signatures = signatures; + *NumberOfSignatures = numberOfSignatures; + + return verifyResult; +} + +NTSTATUS PhVerifyFileEx( + _In_ PPH_VERIFY_FILE_INFO Information, + _Out_ VERIFY_RESULT *VerifyResult, + _Out_opt_ PCERT_CONTEXT **Signatures, + _Out_opt_ PULONG NumberOfSignatures + ) +{ + NTSTATUS status; + HANDLE fileHandle; + VERIFY_RESULT verifyResult; + PCERT_CONTEXT *signatures; + ULONG numberOfSignatures; + WINTRUST_FILE_INFO fileInfo = { 0 }; + + if (PhBeginInitOnce(&PhpVerifyInitOnce)) + { + PhpVerifyInitialization(); + PhEndInitOnce(&PhpVerifyInitOnce); + } + + // Make sure we have successfully imported the required functions. + if ( + !CryptCATAdminCalcHashFromFileHandle || + !CryptCATAdminAcquireContext || + !CryptCATAdminEnumCatalogFromHash || + !CryptCATCatalogInfoFromContext || + !CryptCATAdminReleaseCatalogContext || + !CryptCATAdminReleaseContext || + !WinVerifyTrust_I || + !WTHelperProvDataFromStateData_I || + !WTHelperGetProvSignerFromChain_I || + !CertNameToStr_I || + !CertDuplicateCertificateContext_I || + !CertFreeCertificateContext_I + ) + return STATUS_NOT_SUPPORTED; + + if (!NT_SUCCESS(status = PhCreateFileWin32( + &fileHandle, + Information->FileName, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + return status; + + fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileInfo.pcwszFilePath = Information->FileName; + fileInfo.hFile = fileHandle; + + verifyResult = PhpVerifyFile(Information, fileHandle, WTD_CHOICE_FILE, &fileInfo, &WinTrustActionGenericVerifyV2, NULL, &signatures, &numberOfSignatures); + + if (verifyResult == VrNoSignature) + { + if (CryptCATAdminAcquireContext2 && CryptCATAdminCalcHashFromFileHandle2) + { + PhFreeVerifySignatures(signatures, numberOfSignatures); + verifyResult = PhpVerifyFileFromCatalog(Information, fileHandle, BCRYPT_SHA256_ALGORITHM, &signatures, &numberOfSignatures); + } + + if (verifyResult != VrTrusted) + { + PhFreeVerifySignatures(signatures, numberOfSignatures); + verifyResult = PhpVerifyFileFromCatalog(Information, fileHandle, NULL, &signatures, &numberOfSignatures); + } + } + + *VerifyResult = verifyResult; + + if (Signatures) + *Signatures = signatures; + else + PhFreeVerifySignatures(signatures, numberOfSignatures); + + if (NumberOfSignatures) + *NumberOfSignatures = numberOfSignatures; + + NtClose(fileHandle); + + return STATUS_SUCCESS; +} + +VOID PhFreeVerifySignatures( + _In_ PCERT_CONTEXT *Signatures, + _In_ ULONG NumberOfSignatures + ) +{ + ULONG i; + + if (Signatures) + { + for (i = 0; i < NumberOfSignatures; i++) + CertFreeCertificateContext_I(Signatures[i]); + + PhFree(Signatures); + } +} + +PPH_STRING PhpGetCertNameString( + _In_ PCERT_NAME_BLOB Blob + ) +{ + PPH_STRING string; + ULONG bufferSize; + + // CertNameToStr doesn't give us the correct buffer size unless we don't provide a buffer at + // all. + bufferSize = CertNameToStr_I( + X509_ASN_ENCODING, + Blob, + CERT_X500_NAME_STR, + NULL, + 0 + ); + + string = PhCreateStringEx(NULL, bufferSize * sizeof(WCHAR)); + CertNameToStr_I( + X509_ASN_ENCODING, + Blob, + CERT_X500_NAME_STR, + string->Buffer, + bufferSize + ); + + PhTrimToNullTerminatorString(string); + + return string; +} + +PPH_STRING PhpGetX500Value( + _In_ PPH_STRINGREF String, + _In_ PPH_STRINGREF KeyName + ) +{ + WCHAR keyNamePlusEqualsBuffer[10]; + PH_STRINGREF keyNamePlusEquals; + SIZE_T keyNameLength; + PH_STRINGREF firstPart; + PH_STRINGREF remainingPart; + + keyNameLength = KeyName->Length / sizeof(WCHAR); + assert(!(keyNameLength > sizeof(keyNamePlusEquals) / sizeof(WCHAR) - 1)); + keyNamePlusEquals.Buffer = keyNamePlusEqualsBuffer; + keyNamePlusEquals.Length = (keyNameLength + 1) * sizeof(WCHAR); + + memcpy(keyNamePlusEquals.Buffer, KeyName->Buffer, KeyName->Length); + keyNamePlusEquals.Buffer[keyNameLength] = '='; + + // Find "Key=". + + if (!PhSplitStringRefAtString(String, &keyNamePlusEquals, FALSE, &firstPart, &remainingPart)) + return NULL; + if (remainingPart.Length == 0) + return NULL; + + // Is the value quoted? If so, return the part inside the quotes. + if (remainingPart.Buffer[0] == '"') + { + PhSkipStringRef(&remainingPart, sizeof(WCHAR)); + + if (!PhSplitStringRefAtChar(&remainingPart, '"', &firstPart, &remainingPart)) + return NULL; + + return PhCreateString2(&firstPart); + } + else + { + PhSplitStringRefAtChar(&remainingPart, ',', &firstPart, &remainingPart); + + return PhCreateString2(&firstPart); + } +} + +PPH_STRING PhGetSignerNameFromCertificate( + _In_ PCERT_CONTEXT Certificate + ) +{ + PCERT_INFO certInfo; + PH_STRINGREF keyName; + PPH_STRING name; + PPH_STRING value; + + // Cert context -> Cert info + + certInfo = Certificate->pCertInfo; + + if (!certInfo) + return NULL; + + // Cert info subject -> Subject X.500 string + + name = PhpGetCertNameString(&certInfo->Subject); + + // Subject X.500 string -> CN or OU value + + PhInitializeStringRef(&keyName, L"CN"); + value = PhpGetX500Value(&name->sr, &keyName); + + if (!value) + { + PhInitializeStringRef(&keyName, L"OU"); + value = PhpGetX500Value(&name->sr, &keyName); + } + + PhDereferenceObject(name); + + return value; +} + +/** + * Verifies a file's digital signature. + * + * \param FileName A file name. + * \param SignerName A variable which receives a pointer to a string containing the signer name. You + * must free the string using PhDereferenceObject() when you no longer need it. Note that the signer + * name may be NULL if it is not valid. + * + * \return A VERIFY_RESULT value. + */ +VERIFY_RESULT PhVerifyFile( + _In_ PWSTR FileName, + _Out_opt_ PPH_STRING *SignerName + ) +{ + PH_VERIFY_FILE_INFO info = { 0 }; + VERIFY_RESULT verifyResult; + PCERT_CONTEXT *signatures; + ULONG numberOfSignatures; + + info.FileName = FileName; + info.Flags = PH_VERIFY_PREVENT_NETWORK_ACCESS; + + if (NT_SUCCESS(PhVerifyFileEx(&info, &verifyResult, &signatures, &numberOfSignatures))) + { + if (SignerName) + { + *SignerName = NULL; + + if (numberOfSignatures != 0) + *SignerName = PhGetSignerNameFromCertificate(signatures[0]); + } + + PhFreeVerifySignatures(signatures, numberOfSignatures); + return verifyResult; + } + else + { + if (SignerName) + *SignerName = NULL; + + return VrNoSignature; + } +} diff --git a/phlib/workqueue.c b/phlib/workqueue.c new file mode 100644 index 0000000..2ee77e0 --- /dev/null +++ b/phlib/workqueue.c @@ -0,0 +1,504 @@ +/* + * Process Hacker - + * thread pool / work queue + * + * Copyright (C) 2009-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include + +static PH_INITONCE PhWorkQueueInitOnce = PH_INITONCE_INIT; +static PH_FREE_LIST PhWorkQueueItemFreeList; +static PH_INITONCE PhGlobalWorkQueueInitOnce = PH_INITONCE_INIT; +static PH_WORK_QUEUE PhGlobalWorkQueue; +#ifdef DEBUG +PPH_LIST PhDbgWorkQueueList; +PH_QUEUED_LOCK PhDbgWorkQueueListLock = PH_QUEUED_LOCK_INIT; +#endif + +/** + * Initializes a work queue. + * + * \param WorkQueue A work queue object. + * \param MinimumThreads The suggested minimum number of threads to keep alive, even when there is + * no work to be performed. + * \param MaximumThreads The suggested maximum number of threads to create. + * \param NoWorkTimeout The number of milliseconds after which threads without work will terminate. + */ +VOID PhInitializeWorkQueue( + _Out_ PPH_WORK_QUEUE WorkQueue, + _In_ ULONG MinimumThreads, + _In_ ULONG MaximumThreads, + _In_ ULONG NoWorkTimeout + ) +{ + if (PhBeginInitOnce(&PhWorkQueueInitOnce)) + { + PhInitializeFreeList(&PhWorkQueueItemFreeList, sizeof(PH_WORK_QUEUE_ITEM), 32); +#ifdef DEBUG + PhDbgWorkQueueList = PhCreateList(4); +#endif + + PhEndInitOnce(&PhWorkQueueInitOnce); + } + + PhInitializeRundownProtection(&WorkQueue->RundownProtect); + WorkQueue->Terminating = FALSE; + + InitializeListHead(&WorkQueue->QueueListHead); + PhInitializeQueuedLock(&WorkQueue->QueueLock); + PhInitializeCondition(&WorkQueue->QueueEmptyCondition); + + WorkQueue->MinimumThreads = MinimumThreads; + WorkQueue->MaximumThreads = MaximumThreads; + WorkQueue->NoWorkTimeout = NoWorkTimeout; + + PhInitializeQueuedLock(&WorkQueue->StateLock); + + WorkQueue->SemaphoreHandle = NULL; + WorkQueue->CurrentThreads = 0; + WorkQueue->BusyCount = 0; + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgWorkQueueListLock); + PhAddItemList(PhDbgWorkQueueList, WorkQueue); + PhReleaseQueuedLockExclusive(&PhDbgWorkQueueListLock); +#endif +} + +/** + * Frees resources used by a work queue. + * + * \param WorkQueue A work queue object. + */ +VOID PhDeleteWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ) +{ + PLIST_ENTRY listEntry; + PPH_WORK_QUEUE_ITEM workQueueItem; +#ifdef DEBUG + ULONG index; +#endif + +#ifdef DEBUG + PhAcquireQueuedLockExclusive(&PhDbgWorkQueueListLock); + if ((index = PhFindItemList(PhDbgWorkQueueList, WorkQueue)) != -1) + PhRemoveItemList(PhDbgWorkQueueList, index); + PhReleaseQueuedLockExclusive(&PhDbgWorkQueueListLock); +#endif + + // Wait for all worker threads to exit. + + WorkQueue->Terminating = TRUE; + MemoryBarrier(); + + if (WorkQueue->SemaphoreHandle) + NtReleaseSemaphore(WorkQueue->SemaphoreHandle, WorkQueue->CurrentThreads, NULL); + + PhWaitForRundownProtection(&WorkQueue->RundownProtect); + + // Free all un-executed work items. + + listEntry = WorkQueue->QueueListHead.Flink; + + while (listEntry != &WorkQueue->QueueListHead) + { + workQueueItem = CONTAINING_RECORD(listEntry, PH_WORK_QUEUE_ITEM, ListEntry); + listEntry = listEntry->Flink; + PhpDestroyWorkQueueItem(workQueueItem); + } + + if (WorkQueue->SemaphoreHandle) + NtClose(WorkQueue->SemaphoreHandle); +} + +/** + * Waits for all queued work items to be executed. + * + * \param WorkQueue A work queue object. + */ +VOID PhWaitForWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ) +{ + PhAcquireQueuedLockExclusive(&WorkQueue->QueueLock); + + while (!IsListEmpty(&WorkQueue->QueueListHead)) + PhWaitForCondition(&WorkQueue->QueueEmptyCondition, &WorkQueue->QueueLock, NULL); + + PhReleaseQueuedLockExclusive(&WorkQueue->QueueLock); +} + +/** + * Queues a work item to a work queue. + * + * \param WorkQueue A work queue object. + * \param Function A function to execute. + * \param Context A user-defined value to pass to the function. + */ +VOID PhQueueItemWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue, + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context + ) +{ + PhQueueItemWorkQueueEx(WorkQueue, Function, Context, NULL, NULL); +} + +/** + * Queues a work item to a work queue. + * + * \param WorkQueue A work queue object. + * \param Function A function to execute. + * \param Context A user-defined value to pass to the function. + * \param DeleteFunction A callback function that is executed when the work queue item is about to + * be freed. + * \param Environment Execution environment parameters (e.g. priority). + */ +VOID PhQueueItemWorkQueueEx( + _Inout_ PPH_WORK_QUEUE WorkQueue, + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context, + _In_opt_ PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION DeleteFunction, + _In_opt_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ) +{ + PPH_WORK_QUEUE_ITEM workQueueItem; + + workQueueItem = PhpCreateWorkQueueItem(Function, Context, DeleteFunction, Environment); + + // Enqueue the work item. + PhAcquireQueuedLockExclusive(&WorkQueue->QueueLock); + InsertTailList(&WorkQueue->QueueListHead, &workQueueItem->ListEntry); + _InterlockedIncrement(&WorkQueue->BusyCount); + PhReleaseQueuedLockExclusive(&WorkQueue->QueueLock); + // Signal the semaphore once to let a worker thread continue. + NtReleaseSemaphore(PhpGetSemaphoreWorkQueue(WorkQueue), 1, NULL); + + PHLIB_INC_STATISTIC(WqWorkItemsQueued); + + // Check if all worker threads are currently busy, and if we can create more threads. + if (WorkQueue->BusyCount >= WorkQueue->CurrentThreads && + WorkQueue->CurrentThreads < WorkQueue->MaximumThreads) + { + // Lock and re-check. + PhAcquireQueuedLockExclusive(&WorkQueue->StateLock); + + if (WorkQueue->CurrentThreads < WorkQueue->MaximumThreads) + PhpCreateWorkQueueThread(WorkQueue); + + PhReleaseQueuedLockExclusive(&WorkQueue->StateLock); + } +} + +VOID PhInitializeWorkQueueEnvironment( + _Out_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ) +{ + PhpGetDefaultWorkQueueEnvironment(Environment); +} + +/** Returns a pointer to the default shared work queue. */ +PPH_WORK_QUEUE PhGetGlobalWorkQueue( + VOID + ) +{ + if (PhBeginInitOnce(&PhGlobalWorkQueueInitOnce)) + { + PhInitializeWorkQueue( + &PhGlobalWorkQueue, + 0, + 3, + 1000 + ); + PhEndInitOnce(&PhGlobalWorkQueueInitOnce); + } + + return &PhGlobalWorkQueue; +} + +VOID PhpGetDefaultWorkQueueEnvironment( + _Out_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ) +{ + memset(Environment, 0, sizeof(PH_WORK_QUEUE_ENVIRONMENT)); + Environment->BasePriority = 0; + Environment->IoPriority = IoPriorityNormal; + Environment->PagePriority = MEMORY_PRIORITY_NORMAL; + Environment->ForceUpdate = FALSE; +} + +VOID PhpUpdateWorkQueueEnvironment( + _Inout_ PPH_WORK_QUEUE_ENVIRONMENT CurrentEnvironment, + _In_ PPH_WORK_QUEUE_ENVIRONMENT NewEnvironment + ) +{ + if (CurrentEnvironment->BasePriority != NewEnvironment->BasePriority || NewEnvironment->ForceUpdate) + { + LONG increment; + + increment = NewEnvironment->BasePriority; + + if (NT_SUCCESS(NtSetInformationThread(NtCurrentThread(), ThreadBasePriority, + &increment, sizeof(LONG)))) + { + CurrentEnvironment->BasePriority = NewEnvironment->BasePriority; + } + } + + if (WindowsVersion >= WINDOWS_VISTA) + { + if (CurrentEnvironment->IoPriority != NewEnvironment->IoPriority || NewEnvironment->ForceUpdate) + { + IO_PRIORITY_HINT ioPriority; + + ioPriority = NewEnvironment->IoPriority; + + if (NT_SUCCESS(NtSetInformationThread(NtCurrentThread(), ThreadIoPriority, + &ioPriority, sizeof(IO_PRIORITY_HINT)))) + { + CurrentEnvironment->IoPriority = NewEnvironment->IoPriority; + } + } + + if (CurrentEnvironment->PagePriority != NewEnvironment->PagePriority || NewEnvironment->ForceUpdate) + { + ULONG pagePriority; + + pagePriority = NewEnvironment->PagePriority; + + if (NT_SUCCESS(NtSetInformationThread(NtCurrentThread(), ThreadPagePriority, + &pagePriority, sizeof(ULONG)))) + { + CurrentEnvironment->PagePriority = NewEnvironment->PagePriority; + } + } + } +} + +PPH_WORK_QUEUE_ITEM PhpCreateWorkQueueItem( + _In_ PUSER_THREAD_START_ROUTINE Function, + _In_opt_ PVOID Context, + _In_opt_ PPH_WORK_QUEUE_ITEM_DELETE_FUNCTION DeleteFunction, + _In_opt_ PPH_WORK_QUEUE_ENVIRONMENT Environment + ) +{ + PPH_WORK_QUEUE_ITEM workQueueItem; + + workQueueItem = PhAllocateFromFreeList(&PhWorkQueueItemFreeList); + workQueueItem->Function = Function; + workQueueItem->Context = Context; + workQueueItem->DeleteFunction = DeleteFunction; + + if (Environment) + workQueueItem->Environment = *Environment; + else + PhpGetDefaultWorkQueueEnvironment(&workQueueItem->Environment); + + return workQueueItem; +} + +VOID PhpDestroyWorkQueueItem( + _In_ PPH_WORK_QUEUE_ITEM WorkQueueItem + ) +{ + if (WorkQueueItem->DeleteFunction) + WorkQueueItem->DeleteFunction(WorkQueueItem->Function, WorkQueueItem->Context); + + PhFreeToFreeList(&PhWorkQueueItemFreeList, WorkQueueItem); +} + +VOID PhpExecuteWorkQueueItem( + _Inout_ PPH_WORK_QUEUE_ITEM WorkQueueItem + ) +{ + WorkQueueItem->Function(WorkQueueItem->Context); +} + +HANDLE PhpGetSemaphoreWorkQueue( + _Inout_ PPH_WORK_QUEUE WorkQueue + ) +{ + HANDLE semaphoreHandle; + + semaphoreHandle = WorkQueue->SemaphoreHandle; + + if (!semaphoreHandle) + { + NtCreateSemaphore(&semaphoreHandle, SEMAPHORE_ALL_ACCESS, NULL, 0, MAXLONG); + assert(semaphoreHandle); + + if (_InterlockedCompareExchangePointer( + &WorkQueue->SemaphoreHandle, + semaphoreHandle, + NULL + ) != NULL) + { + // Someone else created the semaphore before we did. + NtClose(semaphoreHandle); + semaphoreHandle = WorkQueue->SemaphoreHandle; + } + } + + return semaphoreHandle; +} + +BOOLEAN PhpCreateWorkQueueThread( + _Inout_ PPH_WORK_QUEUE WorkQueue + ) +{ + HANDLE threadHandle; + + // Make sure the structure doesn't get deleted while the thread is running. + if (!PhAcquireRundownProtection(&WorkQueue->RundownProtect)) + return FALSE; + + threadHandle = PhCreateThread(0, PhpWorkQueueThreadStart, WorkQueue); + + if (threadHandle) + { + PHLIB_INC_STATISTIC(WqWorkQueueThreadsCreated); + WorkQueue->CurrentThreads++; + NtClose(threadHandle); + + return TRUE; + } + else + { + PHLIB_INC_STATISTIC(WqWorkQueueThreadsCreateFailed); + PhReleaseRundownProtection(&WorkQueue->RundownProtect); + return FALSE; + } +} + +NTSTATUS PhpWorkQueueThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + PPH_WORK_QUEUE workQueue = (PPH_WORK_QUEUE)Parameter; + PH_WORK_QUEUE_ENVIRONMENT currentEnvironment; + + PhInitializeAutoPool(&autoPool); + PhpGetDefaultWorkQueueEnvironment(¤tEnvironment); + + while (TRUE) + { + NTSTATUS status; + HANDLE semaphoreHandle; + LARGE_INTEGER timeout; + PPH_WORK_QUEUE_ITEM workQueueItem = NULL; + + // Check if we have more threads than the limit. + if (workQueue->CurrentThreads > workQueue->MaximumThreads) + { + BOOLEAN terminate = FALSE; + + // Lock and re-check. + PhAcquireQueuedLockExclusive(&workQueue->StateLock); + + // Check the minimum as well. + if (workQueue->CurrentThreads > workQueue->MaximumThreads && + workQueue->CurrentThreads > workQueue->MinimumThreads) + { + workQueue->CurrentThreads--; + terminate = TRUE; + } + + PhReleaseQueuedLockExclusive(&workQueue->StateLock); + + if (terminate) + break; + } + + semaphoreHandle = PhpGetSemaphoreWorkQueue(workQueue); + + if (!workQueue->Terminating) + { + // Wait for work. + status = NtWaitForSingleObject( + semaphoreHandle, + FALSE, + PhTimeoutFromMilliseconds(&timeout, workQueue->NoWorkTimeout) + ); + } + else + { + status = STATUS_UNSUCCESSFUL; + } + + if (status == STATUS_WAIT_0 && !workQueue->Terminating) + { + PLIST_ENTRY listEntry; + + // Dequeue the work item. + + PhAcquireQueuedLockExclusive(&workQueue->QueueLock); + + listEntry = RemoveHeadList(&workQueue->QueueListHead); + + if (IsListEmpty(&workQueue->QueueListHead)) + PhPulseCondition(&workQueue->QueueEmptyCondition); + + PhReleaseQueuedLockExclusive(&workQueue->QueueLock); + + // Make sure we got work. + if (listEntry != &workQueue->QueueListHead) + { + workQueueItem = CONTAINING_RECORD(listEntry, PH_WORK_QUEUE_ITEM, ListEntry); + + PhpUpdateWorkQueueEnvironment(¤tEnvironment, &workQueueItem->Environment); + PhpExecuteWorkQueueItem(workQueueItem); + _InterlockedDecrement(&workQueue->BusyCount); + + PhpDestroyWorkQueueItem(workQueueItem); + } + } + else + { + BOOLEAN terminate = FALSE; + + // No work arrived before the timeout passed, or we are terminating, or some error + // occurred. Terminate the thread. + + PhAcquireQueuedLockExclusive(&workQueue->StateLock); + + if (workQueue->Terminating || workQueue->CurrentThreads > workQueue->MinimumThreads) + { + workQueue->CurrentThreads--; + terminate = TRUE; + } + + PhReleaseQueuedLockExclusive(&workQueue->StateLock); + + if (terminate) + break; + } + + PhDrainAutoPool(&autoPool); + } + + PhReleaseRundownProtection(&workQueue->RundownProtect); + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} diff --git a/phnt/README.md b/phnt/README.md new file mode 100644 index 0000000..04bbca3 --- /dev/null +++ b/phnt/README.md @@ -0,0 +1,24 @@ +This collection of Native API header files has been maintained since 2009 for the Process Hacker project, and is the most up-to-date set of Native API definitions that I know of. I have gathered these definitions from official Microsoft header files and symbol files, as well as a lot of reverse engineering and guessing. See `phnt.h` for more information. + +## Usage + +First make sure that your program is using the latest Windows SDK. + +These header files are designed to be used by user-mode programs. Instead of `#include `, place + +``` +#include +#include +``` + +at the top of your program. The first line provides access to the Win32 API as well as the `NTSTATUS` values. The second line provides access to the entire Native API. By default, only definitions present in Windows XP are included into your program. To change this, use one of the following: + +``` +#define PHNT_VERSION PHNT_WINXP // Windows XP +#define PHNT_VERSION PHNT_WS03 // Windows Server 2003 +#define PHNT_VERSION PHNT_VISTA // Windows Vista +#define PHNT_VERSION PHNT_WIN7 // Windows 7 +#define PHNT_VERSION PHNT_WIN8 // Windows 8 +#define PHNT_VERSION PHNT_WINBLUE // Windows 8.1 +#define PHNT_VERSION PHNT_THRESHOLD // Windows 10 +``` diff --git a/phnt/include/ntdbg.h b/phnt/include/ntdbg.h new file mode 100644 index 0000000..2be47c2 --- /dev/null +++ b/phnt/include/ntdbg.h @@ -0,0 +1,247 @@ +#ifndef _NTDBG_H +#define _NTDBG_H + +// Definitions + +typedef struct _DBGKM_EXCEPTION +{ + EXCEPTION_RECORD ExceptionRecord; + ULONG FirstChance; +} DBGKM_EXCEPTION, *PDBGKM_EXCEPTION; + +typedef struct _DBGKM_CREATE_THREAD +{ + ULONG SubSystemKey; + PVOID StartAddress; +} DBGKM_CREATE_THREAD, *PDBGKM_CREATE_THREAD; + +typedef struct _DBGKM_CREATE_PROCESS +{ + ULONG SubSystemKey; + HANDLE FileHandle; + PVOID BaseOfImage; + ULONG DebugInfoFileOffset; + ULONG DebugInfoSize; + DBGKM_CREATE_THREAD InitialThread; +} DBGKM_CREATE_PROCESS, *PDBGKM_CREATE_PROCESS; + +typedef struct _DBGKM_EXIT_THREAD +{ + NTSTATUS ExitStatus; +} DBGKM_EXIT_THREAD, *PDBGKM_EXIT_THREAD; + +typedef struct _DBGKM_EXIT_PROCESS +{ + NTSTATUS ExitStatus; +} DBGKM_EXIT_PROCESS, *PDBGKM_EXIT_PROCESS; + +typedef struct _DBGKM_LOAD_DLL +{ + HANDLE FileHandle; + PVOID BaseOfDll; + ULONG DebugInfoFileOffset; + ULONG DebugInfoSize; + PVOID NamePointer; +} DBGKM_LOAD_DLL, *PDBGKM_LOAD_DLL; + +typedef struct _DBGKM_UNLOAD_DLL +{ + PVOID BaseAddress; +} DBGKM_UNLOAD_DLL, *PDBGKM_UNLOAD_DLL; + +typedef enum _DBG_STATE +{ + DbgIdle, + DbgReplyPending, + DbgCreateThreadStateChange, + DbgCreateProcessStateChange, + DbgExitThreadStateChange, + DbgExitProcessStateChange, + DbgExceptionStateChange, + DbgBreakpointStateChange, + DbgSingleStepStateChange, + DbgLoadDllStateChange, + DbgUnloadDllStateChange +} DBG_STATE, *PDBG_STATE; + +typedef struct _DBGUI_CREATE_THREAD +{ + HANDLE HandleToThread; + DBGKM_CREATE_THREAD NewThread; +} DBGUI_CREATE_THREAD, *PDBGUI_CREATE_THREAD; + +typedef struct _DBGUI_CREATE_PROCESS +{ + HANDLE HandleToProcess; + HANDLE HandleToThread; + DBGKM_CREATE_PROCESS NewProcess; +} DBGUI_CREATE_PROCESS, *PDBGUI_CREATE_PROCESS; + +typedef struct _DBGUI_WAIT_STATE_CHANGE +{ + DBG_STATE NewState; + CLIENT_ID AppClientId; + union + { + DBGKM_EXCEPTION Exception; + DBGUI_CREATE_THREAD CreateThread; + DBGUI_CREATE_PROCESS CreateProcessInfo; + DBGKM_EXIT_THREAD ExitThread; + DBGKM_EXIT_PROCESS ExitProcess; + DBGKM_LOAD_DLL LoadDll; + DBGKM_UNLOAD_DLL UnloadDll; + } StateInfo; +} DBGUI_WAIT_STATE_CHANGE, *PDBGUI_WAIT_STATE_CHANGE; + +// System calls + +#define DEBUG_READ_EVENT 0x0001 +#define DEBUG_PROCESS_ASSIGN 0x0002 +#define DEBUG_SET_INFORMATION 0x0004 +#define DEBUG_QUERY_INFORMATION 0x0008 +#define DEBUG_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \ + DEBUG_READ_EVENT | DEBUG_PROCESS_ASSIGN | DEBUG_SET_INFORMATION | \ + DEBUG_QUERY_INFORMATION) + +#define DEBUG_KILL_ON_CLOSE 0x1 + +typedef enum _DEBUGOBJECTINFOCLASS +{ + DebugObjectFlags = 1, + MaxDebugObjectInfoClass +} DEBUGOBJECTINFOCLASS, *PDEBUGOBJECTINFOCLASS; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateDebugObject( + _Out_ PHANDLE DebugObjectHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDebugActiveProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE DebugObjectHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDebugContinue( + _In_ HANDLE DebugObjectHandle, + _In_ PCLIENT_ID ClientId, + _In_ NTSTATUS ContinueStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRemoveProcessDebug( + _In_ HANDLE ProcessHandle, + _In_ HANDLE DebugObjectHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationDebugObject( + _In_ HANDLE DebugObjectHandle, + _In_ DEBUGOBJECTINFOCLASS DebugObjectInformationClass, + _In_ PVOID DebugInformation, + _In_ ULONG DebugInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForDebugEvent( + _In_ HANDLE DebugObjectHandle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout, + _Out_ PVOID WaitStateChange + ); + +// Debugging UI + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiConnectToDbg( + VOID + ); + +NTSYSAPI +HANDLE +NTAPI +DbgUiGetThreadDebugObject( + VOID + ); + +NTSYSAPI +VOID +NTAPI +DbgUiSetThreadDebugObject( + _In_ HANDLE DebugObject + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiWaitStateChange( + _Out_ PDBGUI_WAIT_STATE_CHANGE StateChange, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiContinue( + _In_ PCLIENT_ID AppClientId, + _In_ NTSTATUS ContinueStatus + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiStopDebugging( + _In_ HANDLE Process + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiDebugActiveProcess( + _In_ HANDLE Process + ); + +NTSYSAPI +VOID +NTAPI +DbgUiRemoteBreakin( + _In_ PVOID Context + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiIssueRemoteBreakin( + _In_ HANDLE Process + ); + +struct _DEBUG_EVENT; + +NTSYSAPI +NTSTATUS +NTAPI +DbgUiConvertStateChangeStructure( + _In_ PDBGUI_WAIT_STATE_CHANGE StateChange, + _Out_ struct _DEBUG_EVENT *DebugEvent + ); + +#endif diff --git a/phnt/include/ntexapi.h b/phnt/include/ntexapi.h new file mode 100644 index 0000000..d25b062 --- /dev/null +++ b/phnt/include/ntexapi.h @@ -0,0 +1,3187 @@ +#ifndef _NTEXAPI_H +#define _NTEXAPI_H + +#include + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +// Thread execution + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDelayExecution( + _In_ BOOLEAN Alertable, + _In_ PLARGE_INTEGER DelayInterval + ); + +// Environment values + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySystemEnvironmentValue( + _In_ PUNICODE_STRING VariableName, + _Out_writes_bytes_(ValueLength) PWSTR VariableValue, + _In_ USHORT ValueLength, + _Out_opt_ PUSHORT ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSystemEnvironmentValue( + _In_ PUNICODE_STRING VariableName, + _In_ PUNICODE_STRING VariableValue + ); + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySystemEnvironmentValueEx( + _In_ PUNICODE_STRING VariableName, + _In_ LPGUID VendorGuid, + _Out_writes_bytes_opt_(*ValueLength) PVOID Value, + _Inout_ PULONG ValueLength, + _Out_opt_ PULONG Attributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSystemEnvironmentValueEx( + _In_ PUNICODE_STRING VariableName, + _In_ LPGUID VendorGuid, + _In_reads_bytes_opt_(ValueLength) PVOID Value, + _In_ ULONG ValueLength, + _In_ ULONG Attributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateSystemEnvironmentValuesEx( + _In_ ULONG InformationClass, + _Out_ PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +#endif + +// EFI + +// private +typedef struct _BOOT_ENTRY +{ + ULONG Version; + ULONG Length; + ULONG Id; + ULONG Attributes; + ULONG FriendlyNameOffset; + ULONG BootFilePathOffset; + ULONG OsOptionsLength; + UCHAR OsOptions[1]; +} BOOT_ENTRY, *PBOOT_ENTRY; + +// private +typedef struct _BOOT_ENTRY_LIST +{ + ULONG NextEntryOffset; + BOOT_ENTRY BootEntry; +} BOOT_ENTRY_LIST, *PBOOT_ENTRY_LIST; + +// private +typedef struct _BOOT_OPTIONS +{ + ULONG Version; + ULONG Length; + ULONG Timeout; + ULONG CurrentBootEntryId; + ULONG NextBootEntryId; + WCHAR HeadlessRedirection[1]; +} BOOT_OPTIONS, *PBOOT_OPTIONS; + +// private +typedef struct _FILE_PATH +{ + ULONG Version; + ULONG Length; + ULONG Type; + UCHAR FilePath[1]; +} FILE_PATH, *PFILE_PATH; + +// private +typedef struct _EFI_DRIVER_ENTRY +{ + ULONG Version; + ULONG Length; + ULONG Id; + ULONG FriendlyNameOffset; + ULONG DriverFilePathOffset; +} EFI_DRIVER_ENTRY, *PEFI_DRIVER_ENTRY; + +// private +typedef struct _EFI_DRIVER_ENTRY_LIST +{ + ULONG NextEntryOffset; + EFI_DRIVER_ENTRY DriverEntry; +} EFI_DRIVER_ENTRY_LIST, *PEFI_DRIVER_ENTRY_LIST; + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAddBootEntry( + _In_ PBOOT_ENTRY BootEntry, + _Out_opt_ PULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteBootEntry( + _In_ ULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtModifyBootEntry( + _In_ PBOOT_ENTRY BootEntry + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateBootEntries( + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryBootEntryOrder( + _Out_writes_opt_(*Count) PULONG Ids, + _Inout_ PULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetBootEntryOrder( + _In_reads_(Count) PULONG Ids, + _In_ ULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryBootOptions( + _Out_writes_bytes_opt_(*BootOptionsLength) PBOOT_OPTIONS BootOptions, + _Inout_ PULONG BootOptionsLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetBootOptions( + _In_ PBOOT_OPTIONS BootOptions, + _In_ ULONG FieldsToChange + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTranslateFilePath( + _In_ PFILE_PATH InputFilePath, + _In_ ULONG OutputType, + _Out_writes_bytes_opt_(*OutputFilePathLength) PFILE_PATH OutputFilePath, + _Inout_opt_ PULONG OutputFilePathLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAddDriverEntry( + _In_ PEFI_DRIVER_ENTRY DriverEntry, + _Out_opt_ PULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteDriverEntry( + _In_ ULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtModifyDriverEntry( + _In_ PEFI_DRIVER_ENTRY DriverEntry + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateDriverEntries( + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDriverEntryOrder( + _Out_writes_opt_(*Count) PULONG Ids, + _Inout_ PULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetDriverEntryOrder( + _In_reads_(Count) PULONG Ids, + _In_ ULONG Count + ); + +#endif + +// Event + +#ifndef EVENT_QUERY_STATE +#define EVENT_QUERY_STATE 0x0001 +#endif + +typedef enum _EVENT_INFORMATION_CLASS +{ + EventBasicInformation +} EVENT_INFORMATION_CLASS; + +typedef struct _EVENT_BASIC_INFORMATION +{ + EVENT_TYPE EventType; + LONG EventState; +} EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateEvent( + _Out_ PHANDLE EventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ EVENT_TYPE EventType, + _In_ BOOLEAN InitialState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenEvent( + _Out_ PHANDLE EventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetEventBoostPriority( + _In_ HANDLE EventHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtClearEvent( + _In_ HANDLE EventHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtResetEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPulseEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryEvent( + _In_ HANDLE EventHandle, + _In_ EVENT_INFORMATION_CLASS EventInformationClass, + _Out_writes_bytes_(EventInformationLength) PVOID EventInformation, + _In_ ULONG EventInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// Event Pair + +#define EVENT_PAIR_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateEventPair( + _Out_ PHANDLE EventPairHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenEventPair( + _Out_ PHANDLE EventPairHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetLowEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitLowEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetLowWaitHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetHighWaitLowEventPair( + _In_ HANDLE EventPairHandle + ); + +// Mutant + +typedef enum _MUTANT_INFORMATION_CLASS +{ + MutantBasicInformation, + MutantOwnerInformation +} MUTANT_INFORMATION_CLASS; + +typedef struct _MUTANT_BASIC_INFORMATION +{ + LONG CurrentCount; + BOOLEAN OwnedByCaller; + BOOLEAN AbandonedState; +} MUTANT_BASIC_INFORMATION, *PMUTANT_BASIC_INFORMATION; + +typedef struct _MUTANT_OWNER_INFORMATION +{ + CLIENT_ID ClientId; +} MUTANT_OWNER_INFORMATION, *PMUTANT_OWNER_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateMutant( + _Out_ PHANDLE MutantHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ BOOLEAN InitialOwner + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenMutant( + _Out_ PHANDLE MutantHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReleaseMutant( + _In_ HANDLE MutantHandle, + _Out_opt_ PLONG PreviousCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryMutant( + _In_ HANDLE MutantHandle, + _In_ MUTANT_INFORMATION_CLASS MutantInformationClass, + _Out_writes_bytes_(MutantInformationLength) PVOID MutantInformation, + _In_ ULONG MutantInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// Semaphore + +#ifndef SEMAPHORE_QUERY_STATE +#define SEMAPHORE_QUERY_STATE 0x0001 +#endif + +typedef enum _SEMAPHORE_INFORMATION_CLASS +{ + SemaphoreBasicInformation +} SEMAPHORE_INFORMATION_CLASS; + +typedef struct _SEMAPHORE_BASIC_INFORMATION +{ + LONG CurrentCount; + LONG MaximumCount; +} SEMAPHORE_BASIC_INFORMATION, *PSEMAPHORE_BASIC_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateSemaphore( + _Out_ PHANDLE SemaphoreHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ LONG InitialCount, + _In_ LONG MaximumCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenSemaphore( + _Out_ PHANDLE SemaphoreHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReleaseSemaphore( + _In_ HANDLE SemaphoreHandle, + _In_ LONG ReleaseCount, + _Out_opt_ PLONG PreviousCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySemaphore( + _In_ HANDLE SemaphoreHandle, + _In_ SEMAPHORE_INFORMATION_CLASS SemaphoreInformationClass, + _Out_writes_bytes_(SemaphoreInformationLength) PVOID SemaphoreInformation, + _In_ ULONG SemaphoreInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// Timer + +typedef enum _TIMER_INFORMATION_CLASS +{ + TimerBasicInformation +} TIMER_INFORMATION_CLASS; + +typedef struct _TIMER_BASIC_INFORMATION +{ + LARGE_INTEGER RemainingTime; + BOOLEAN TimerState; +} TIMER_BASIC_INFORMATION, *PTIMER_BASIC_INFORMATION; + +typedef VOID (NTAPI *PTIMER_APC_ROUTINE)( + _In_ PVOID TimerContext, + _In_ ULONG TimerLowValue, + _In_ LONG TimerHighValue + ); + +typedef enum _TIMER_SET_INFORMATION_CLASS +{ + TimerSetCoalescableTimer, + MaxTimerInfoClass +} TIMER_SET_INFORMATION_CLASS; + +#if (PHNT_VERSION >= PHNT_WIN7) +struct _COUNTED_REASON_CONTEXT; + +typedef struct _TIMER_SET_COALESCABLE_TIMER_INFO +{ + _In_ LARGE_INTEGER DueTime; + _In_opt_ PTIMER_APC_ROUTINE TimerApcRoutine; + _In_opt_ PVOID TimerContext; + _In_opt_ struct _COUNTED_REASON_CONTEXT *WakeContext; + _In_opt_ ULONG Period; + _In_ ULONG TolerableDelay; + _Out_opt_ PBOOLEAN PreviousState; +} TIMER_SET_COALESCABLE_TIMER_INFO, *PTIMER_SET_COALESCABLE_TIMER_INFO; +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TIMER_TYPE TimerType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetTimer( + _In_ HANDLE TimerHandle, + _In_ PLARGE_INTEGER DueTime, + _In_opt_ PTIMER_APC_ROUTINE TimerApcRoutine, + _In_opt_ PVOID TimerContext, + _In_ BOOLEAN ResumeTimer, + _In_opt_ LONG Period, + _Out_opt_ PBOOLEAN PreviousState + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetTimerEx( + _In_ HANDLE TimerHandle, + _In_ TIMER_SET_INFORMATION_CLASS TimerSetInformationClass, + _Inout_updates_bytes_opt_(TimerSetInformationLength) PVOID TimerSetInformation, + _In_ ULONG TimerSetInformationLength + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelTimer( + _In_ HANDLE TimerHandle, + _Out_opt_ PBOOLEAN CurrentState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryTimer( + _In_ HANDLE TimerHandle, + _In_ TIMER_INFORMATION_CLASS TimerInformationClass, + _Out_writes_bytes_(TimerInformationLength) PVOID TimerInformation, + _In_ ULONG TimerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateIRTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetIRTimer( + _In_ HANDLE TimerHandle, + _In_opt_ PLARGE_INTEGER DueTime + ); + +#endif + +typedef struct _T2_SET_PARAMETERS_V0 +{ + ULONG Version; + ULONG Reserved; + LONGLONG NoWakeTolerance; +} T2_SET_PARAMETERS, *PT2_SET_PARAMETERS; + +typedef PVOID PT2_CANCEL_PARAMETERS; + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateTimer2( + _Out_ PHANDLE TimerHandle, + _In_opt_ PVOID Reserved1, + _In_opt_ PVOID Reserved2, + _In_ ULONG Attributes, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetTimer2( + _In_ HANDLE TimerHandle, + _In_ PLARGE_INTEGER DueTime, + _In_opt_ PLARGE_INTEGER Period, + _In_ PT2_SET_PARAMETERS Parameters + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelTimer2( + _In_ HANDLE TimerHandle, + _In_ PT2_CANCEL_PARAMETERS Parameters + ); + +#endif + +// Profile + +#define PROFILE_CONTROL 0x0001 +#define PROFILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | PROFILE_CONTROL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateProfile( + _Out_ PHANDLE ProfileHandle, + _In_opt_ HANDLE Process, + _In_ PVOID ProfileBase, + _In_ SIZE_T ProfileSize, + _In_ ULONG BucketSize, + _In_reads_bytes_(BufferSize) PULONG Buffer, + _In_ ULONG BufferSize, + _In_ KPROFILE_SOURCE ProfileSource, + _In_ KAFFINITY Affinity + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateProfileEx( + _Out_ PHANDLE ProfileHandle, + _In_opt_ HANDLE Process, + _In_ PVOID ProfileBase, + _In_ SIZE_T ProfileSize, + _In_ ULONG BucketSize, + _In_reads_bytes_(BufferSize) PULONG Buffer, + _In_ ULONG BufferSize, + _In_ KPROFILE_SOURCE ProfileSource, + _In_ USHORT GroupCount, + _In_reads_(GroupCount) PGROUP_AFFINITY GroupAffinity + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtStartProfile( + _In_ HANDLE ProfileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtStopProfile( + _In_ HANDLE ProfileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryIntervalProfile( + _In_ KPROFILE_SOURCE ProfileSource, + _Out_ PULONG Interval + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetIntervalProfile( + _In_ ULONG Interval, + _In_ KPROFILE_SOURCE Source + ); + +// Keyed Event + +#define KEYEDEVENT_WAIT 0x0001 +#define KEYEDEVENT_WAKE 0x0002 +#define KEYEDEVENT_ALL_ACCESS \ + (STANDARD_RIGHTS_REQUIRED | KEYEDEVENT_WAIT | KEYEDEVENT_WAKE) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateKeyedEvent( + _Out_ PHANDLE KeyedEventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenKeyedEvent( + _Out_ PHANDLE KeyedEventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReleaseKeyedEvent( + _In_ HANDLE KeyedEventHandle, + _In_ PVOID KeyValue, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForKeyedEvent( + _In_ HANDLE KeyedEventHandle, + _In_ PVOID KeyValue, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +// UMS + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUmsThreadYield( + _In_ PVOID SchedulerParam + ); +#endif + +// WNF + +// begin_private + +typedef struct _WNF_STATE_NAME +{ + ULONG Data[2]; +} WNF_STATE_NAME, *PWNF_STATE_NAME; + +typedef const WNF_STATE_NAME *PCWNF_STATE_NAME; + +typedef enum _WNF_STATE_NAME_LIFETIME +{ + WnfWellKnownStateName, + WnfPermanentStateName, + WnfPersistentStateName, + WnfTemporaryStateName +} WNF_STATE_NAME_LIFETIME; + +typedef enum _WNF_STATE_NAME_INFORMATION +{ + WnfInfoStateNameExist, + WnfInfoSubscribersPresent, + WnfInfoIsQuiescent +} WNF_STATE_NAME_INFORMATION; + +typedef enum _WNF_DATA_SCOPE +{ + WnfDataScopeSystem, + WnfDataScopeSession, + WnfDataScopeUser, + WnfDataScopeProcess +} WNF_DATA_SCOPE; + +typedef struct _WNF_TYPE_ID +{ + GUID TypeId; +} WNF_TYPE_ID, *PWNF_TYPE_ID; + +typedef const WNF_TYPE_ID *PCWNF_TYPE_ID; + +// rev +typedef ULONG WNF_CHANGE_STAMP, *PWNF_CHANGE_STAMP; + +typedef struct _WNF_DELIVERY_DESCRIPTOR +{ + ULONGLONG SubscriptionId; + WNF_STATE_NAME StateName; + WNF_CHANGE_STAMP ChangeStamp; + ULONG StateDataSize; + ULONG EventMask; + WNF_TYPE_ID TypeId; + ULONG StateDataOffset; +} WNF_DELIVERY_DESCRIPTOR, *PWNF_DELIVERY_DESCRIPTOR; + +// end_private + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateWnfStateName( + _Out_ PWNF_STATE_NAME StateName, + _In_ WNF_STATE_NAME_LIFETIME NameLifetime, + _In_ WNF_DATA_SCOPE DataScope, + _In_ BOOLEAN PersistData, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_ ULONG MaximumStateSize, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteWnfStateName( + _In_ PCWNF_STATE_NAME StateName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUpdateWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_reads_bytes_opt_(Length) const VOID *Buffer, + _In_opt_ ULONG Length, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_opt_ const VOID *ExplicitScope, + _In_ WNF_CHANGE_STAMP MatchingChangeStamp, + _In_ LOGICAL CheckStamp + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ const VOID *ExplicitScope + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_opt_ const VOID *ExplicitScope, + _Out_ PWNF_CHANGE_STAMP ChangeStamp, + _Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer, + _Inout_ PULONG BufferSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryWnfStateNameInformation( + _In_ PCWNF_STATE_NAME StateName, + _In_ WNF_STATE_NAME_INFORMATION NameInfoClass, + _In_opt_ const VOID *ExplicitScope, + _Out_writes_bytes_(InfoBufferSize) PVOID InfoBuffer, + _In_ ULONG InfoBufferSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSubscribeWnfStateChange( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ WNF_CHANGE_STAMP ChangeStamp, + _In_ ULONG EventMask, + _Out_opt_ PULONG64 SubscriptionId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnsubscribeWnfStateChange( + _In_ PCWNF_STATE_NAME StateName + ); + +#endif + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetCompleteWnfStateSubscription( + _In_opt_ PWNF_STATE_NAME OldDescriptorStateName, + _In_opt_ ULONG64 *OldSubscriptionId, + _In_opt_ ULONG OldDescriptorEventMask, + _In_opt_ ULONG OldDescriptorStatus, + _Out_writes_bytes_(DescriptorSize) PWNF_DELIVERY_DESCRIPTOR NewDeliveryDescriptor, + _In_ ULONG DescriptorSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetWnfProcessNotificationEvent( + _In_ HANDLE NotificationEvent + ); + +#endif + +// Worker factory + +// begin_rev + +#define WORKER_FACTORY_RELEASE_WORKER 0x0001 +#define WORKER_FACTORY_WAIT 0x0002 +#define WORKER_FACTORY_SET_INFORMATION 0x0004 +#define WORKER_FACTORY_QUERY_INFORMATION 0x0008 +#define WORKER_FACTORY_READY_WORKER 0x0010 +#define WORKER_FACTORY_SHUTDOWN 0x0020 + +#define WORKER_FACTORY_ALL_ACCESS ( \ + STANDARD_RIGHTS_REQUIRED | \ + WORKER_FACTORY_RELEASE_WORKER | \ + WORKER_FACTORY_WAIT | \ + WORKER_FACTORY_SET_INFORMATION | \ + WORKER_FACTORY_QUERY_INFORMATION | \ + WORKER_FACTORY_READY_WORKER | \ + WORKER_FACTORY_SHUTDOWN \ + ) + +// end_rev + +// begin_private + +typedef enum _WORKERFACTORYINFOCLASS +{ + WorkerFactoryTimeout, + WorkerFactoryRetryTimeout, + WorkerFactoryIdleTimeout, + WorkerFactoryBindingCount, + WorkerFactoryThreadMinimum, + WorkerFactoryThreadMaximum, + WorkerFactoryPaused, + WorkerFactoryBasicInformation, + WorkerFactoryAdjustThreadGoal, + WorkerFactoryCallbackType, + WorkerFactoryStackInformation, // 10 + WorkerFactoryThreadBasePriority, + WorkerFactoryTimeoutWaiters, // since THRESHOLD + WorkerFactoryFlags, + WorkerFactoryThreadSoftMaximum, + MaxWorkerFactoryInfoClass +} WORKERFACTORYINFOCLASS, *PWORKERFACTORYINFOCLASS; + +typedef struct _WORKER_FACTORY_BASIC_INFORMATION +{ + LARGE_INTEGER Timeout; + LARGE_INTEGER RetryTimeout; + LARGE_INTEGER IdleTimeout; + BOOLEAN Paused; + BOOLEAN TimerSet; + BOOLEAN QueuedToExWorker; + BOOLEAN MayCreate; + BOOLEAN CreateInProgress; + BOOLEAN InsertedIntoQueue; + BOOLEAN Shutdown; + ULONG BindingCount; + ULONG ThreadMinimum; + ULONG ThreadMaximum; + ULONG PendingWorkerCount; + ULONG WaitingWorkerCount; + ULONG TotalWorkerCount; + ULONG ReleaseCount; + LONGLONG InfiniteWaitGoal; + PVOID StartRoutine; + PVOID StartParameter; + HANDLE ProcessId; + SIZE_T StackReserve; + SIZE_T StackCommit; + NTSTATUS LastThreadCreationStatus; +} WORKER_FACTORY_BASIC_INFORMATION, *PWORKER_FACTORY_BASIC_INFORMATION; + +// end_private + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateWorkerFactory( + _Out_ PHANDLE WorkerFactoryHandleReturn, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE CompletionPortHandle, + _In_ HANDLE WorkerProcessHandle, + _In_ PVOID StartRoutine, + _In_opt_ PVOID StartParameter, + _In_opt_ ULONG MaxThreadCount, + _In_opt_ SIZE_T StackReserve, + _In_opt_ SIZE_T StackCommit + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _In_ WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, + _Out_writes_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, + _In_ ULONG WorkerFactoryInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _In_ WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, + _In_reads_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, + _In_ ULONG WorkerFactoryInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtShutdownWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _Inout_ volatile LONG *PendingWorkerCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReleaseWorkerFactoryWorker( + _In_ HANDLE WorkerFactoryHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWorkerFactoryWorkerReady( + _In_ HANDLE WorkerFactoryHandle + ); + +struct _FILE_IO_COMPLETION_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForWorkViaWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _Out_ struct _FILE_IO_COMPLETION_INFORMATION *MiniPacket + ); + +#endif + +// Time + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySystemTime( + _Out_ PLARGE_INTEGER SystemTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSystemTime( + _In_opt_ PLARGE_INTEGER SystemTime, + _Out_opt_ PLARGE_INTEGER PreviousTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryTimerResolution( + _Out_ PULONG MaximumTime, + _Out_ PULONG MinimumTime, + _Out_ PULONG CurrentTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetTimerResolution( + _In_ ULONG DesiredTime, + _In_ BOOLEAN SetResolution, + _Out_ PULONG ActualTime + ); + +// Performance Counter + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryPerformanceCounter( + _Out_ PLARGE_INTEGER PerformanceCounter, + _Out_opt_ PLARGE_INTEGER PerformanceFrequency + ); + +// LUIDs + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAllocateLocallyUniqueId( + _Out_ PLUID Luid + ); + +// UUIDs + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetUuidSeed( + _In_ PCHAR Seed + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAllocateUuids( + _Out_ PULARGE_INTEGER Time, + _Out_ PULONG Range, + _Out_ PULONG Sequence, + _Out_ PCHAR Seed + ); + +// System Information + +#endif // (PHNT_MODE != PHNT_MODE_KERNEL) + +// rev +// private +// source:http://www.microsoft.com/whdc/system/Sysinternals/MoreThan64proc.mspx +typedef enum _SYSTEM_INFORMATION_CLASS +{ + SystemBasicInformation, // q: SYSTEM_BASIC_INFORMATION + SystemProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION + SystemPerformanceInformation, // q: SYSTEM_PERFORMANCE_INFORMATION + SystemTimeOfDayInformation, // q: SYSTEM_TIMEOFDAY_INFORMATION + SystemPathInformation, // not implemented + SystemProcessInformation, // q: SYSTEM_PROCESS_INFORMATION + SystemCallCountInformation, // q: SYSTEM_CALL_COUNT_INFORMATION + SystemDeviceInformation, // q: SYSTEM_DEVICE_INFORMATION + SystemProcessorPerformanceInformation, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION + SystemFlagsInformation, // q: SYSTEM_FLAGS_INFORMATION + SystemCallTimeInformation, // not implemented // 10 + SystemModuleInformation, // q: RTL_PROCESS_MODULES + SystemLocksInformation, + SystemStackTraceInformation, + SystemPagedPoolInformation, // not implemented + SystemNonPagedPoolInformation, // not implemented + SystemHandleInformation, // q: SYSTEM_HANDLE_INFORMATION + SystemObjectInformation, // q: SYSTEM_OBJECTTYPE_INFORMATION mixed with SYSTEM_OBJECT_INFORMATION + SystemPageFileInformation, // q: SYSTEM_PAGEFILE_INFORMATION + SystemVdmInstemulInformation, // q + SystemVdmBopInformation, // not implemented // 20 + SystemFileCacheInformation, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemCache) + SystemPoolTagInformation, // q: SYSTEM_POOLTAG_INFORMATION + SystemInterruptInformation, // q: SYSTEM_INTERRUPT_INFORMATION + SystemDpcBehaviorInformation, // q: SYSTEM_DPC_BEHAVIOR_INFORMATION; s: SYSTEM_DPC_BEHAVIOR_INFORMATION (requires SeLoadDriverPrivilege) + SystemFullMemoryInformation, // not implemented + SystemLoadGdiDriverInformation, // s (kernel-mode only) + SystemUnloadGdiDriverInformation, // s (kernel-mode only) + SystemTimeAdjustmentInformation, // q: SYSTEM_QUERY_TIME_ADJUST_INFORMATION; s: SYSTEM_SET_TIME_ADJUST_INFORMATION (requires SeSystemtimePrivilege) + SystemSummaryMemoryInformation, // not implemented + SystemMirrorMemoryInformation, // s (requires license value "Kernel-MemoryMirroringSupported") (requires SeShutdownPrivilege) // 30 + SystemPerformanceTraceInformation, // s + SystemObsolete0, // not implemented + SystemExceptionInformation, // q: SYSTEM_EXCEPTION_INFORMATION + SystemCrashDumpStateInformation, // s (requires SeDebugPrivilege) + SystemKernelDebuggerInformation, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION + SystemContextSwitchInformation, // q: SYSTEM_CONTEXT_SWITCH_INFORMATION + SystemRegistryQuotaInformation, // q: SYSTEM_REGISTRY_QUOTA_INFORMATION; s (requires SeIncreaseQuotaPrivilege) + SystemExtendServiceTableInformation, // s (requires SeLoadDriverPrivilege) // loads win32k only + SystemPrioritySeperation, // s (requires SeTcbPrivilege) + SystemVerifierAddDriverInformation, // s (requires SeDebugPrivilege) // 40 + SystemVerifierRemoveDriverInformation, // s (requires SeDebugPrivilege) + SystemProcessorIdleInformation, // q: SYSTEM_PROCESSOR_IDLE_INFORMATION + SystemLegacyDriverInformation, // q: SYSTEM_LEGACY_DRIVER_INFORMATION + SystemCurrentTimeZoneInformation, // q + SystemLookasideInformation, // q: SYSTEM_LOOKASIDE_INFORMATION + SystemTimeSlipNotification, // s (requires SeSystemtimePrivilege) + SystemSessionCreate, // not implemented + SystemSessionDetach, // not implemented + SystemSessionInformation, // not implemented + SystemRangeStartInformation, // q // 50 + SystemVerifierInformation, // q: SYSTEM_VERIFIER_INFORMATION; s (requires SeDebugPrivilege) + SystemVerifierThunkExtend, // s (kernel-mode only) + SystemSessionProcessInformation, // q: SYSTEM_SESSION_PROCESS_INFORMATION + SystemLoadGdiDriverInSystemSpace, // s (kernel-mode only) (same as SystemLoadGdiDriverInformation) + SystemNumaProcessorMap, // q + SystemPrefetcherInformation, // q: PREFETCHER_INFORMATION; s: PREFETCHER_INFORMATION // PfSnQueryPrefetcherInformation + SystemExtendedProcessInformation, // q: SYSTEM_PROCESS_INFORMATION + SystemRecommendedSharedDataAlignment, // q + SystemComPlusPackage, // q; s + SystemNumaAvailableMemory, // 60 + SystemProcessorPowerInformation, // q: SYSTEM_PROCESSOR_POWER_INFORMATION + SystemEmulationBasicInformation, // q + SystemEmulationProcessorInformation, + SystemExtendedHandleInformation, // q: SYSTEM_HANDLE_INFORMATION_EX + SystemLostDelayedWriteInformation, // q: ULONG + SystemBigPoolInformation, // q: SYSTEM_BIGPOOL_INFORMATION + SystemSessionPoolTagInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION + SystemSessionMappedViewInformation, // q: SYSTEM_SESSION_MAPPED_VIEW_INFORMATION + SystemHotpatchInformation, // q; s + SystemObjectSecurityMode, // q // 70 + SystemWatchdogTimerHandler, // s (kernel-mode only) + SystemWatchdogTimerInformation, // q (kernel-mode only); s (kernel-mode only) + SystemLogicalProcessorInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION + SystemWow64SharedInformationObsolete, // not implemented + SystemRegisterFirmwareTableInformationHandler, // s (kernel-mode only) + SystemFirmwareTableInformation, // not implemented + SystemModuleInformationEx, // q: RTL_PROCESS_MODULE_INFORMATION_EX + SystemVerifierTriageInformation, // not implemented + SystemSuperfetchInformation, // q: SUPERFETCH_INFORMATION; s: SUPERFETCH_INFORMATION // PfQuerySuperfetchInformation + SystemMemoryListInformation, // q: SYSTEM_MEMORY_LIST_INFORMATION; s: SYSTEM_MEMORY_LIST_COMMAND (requires SeProfileSingleProcessPrivilege) // 80 + SystemFileCacheInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (same as SystemFileCacheInformation) + SystemThreadPriorityClientIdInformation, // s: SYSTEM_THREAD_CID_PRIORITY_INFORMATION (requires SeIncreaseBasePriorityPrivilege) + SystemProcessorIdleCycleTimeInformation, // q: SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION[] + SystemVerifierCancellationInformation, // not implemented // name:wow64:whNT32QuerySystemVerifierCancellationInformation + SystemProcessorPowerInformationEx, // not implemented + SystemRefTraceInformation, // q; s // ObQueryRefTraceInformation + SystemSpecialPoolInformation, // q; s (requires SeDebugPrivilege) // MmSpecialPoolTag, then MmSpecialPoolCatchOverruns != 0 + SystemProcessIdInformation, // q: SYSTEM_PROCESS_ID_INFORMATION + SystemErrorPortInformation, // s (requires SeTcbPrivilege) + SystemBootEnvironmentInformation, // q: SYSTEM_BOOT_ENVIRONMENT_INFORMATION // 90 + SystemHypervisorInformation, // q; s (kernel-mode only) + SystemVerifierInformationEx, // q; s + SystemTimeZoneInformation, // s (requires SeTimeZonePrivilege) + SystemImageFileExecutionOptionsInformation, // s: SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION (requires SeTcbPrivilege) + SystemCoverageInformation, // q; s // name:wow64:whNT32QuerySystemCoverageInformation; ExpCovQueryInformation + SystemPrefetchPatchInformation, // not implemented + SystemVerifierFaultsInformation, // s (requires SeDebugPrivilege) + SystemSystemPartitionInformation, // q: SYSTEM_SYSTEM_PARTITION_INFORMATION + SystemSystemDiskInformation, // q: SYSTEM_SYSTEM_DISK_INFORMATION + SystemProcessorPerformanceDistribution, // q: SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION // 100 + SystemNumaProximityNodeInformation, // q + SystemDynamicTimeZoneInformation, // q; s (requires SeTimeZonePrivilege) + SystemCodeIntegrityInformation, // q // SeCodeIntegrityQueryInformation + SystemProcessorMicrocodeUpdateInformation, // s + SystemProcessorBrandString, // q // HaliQuerySystemInformation -> HalpGetProcessorBrandString, info class 23 + SystemVirtualAddressInformation, // q: SYSTEM_VA_LIST_INFORMATION[]; s: SYSTEM_VA_LIST_INFORMATION[] (requires SeIncreaseQuotaPrivilege) // MmQuerySystemVaInformation + SystemLogicalProcessorAndGroupInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX // since WIN7 // KeQueryLogicalProcessorRelationship + SystemProcessorCycleTimeInformation, // q: SYSTEM_PROCESSOR_CYCLE_TIME_INFORMATION[] + SystemStoreInformation, // q; s // SmQueryStoreInformation + SystemRegistryAppendString, // s: SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS // 110 + SystemAitSamplingValue, // s: ULONG (requires SeProfileSingleProcessPrivilege) + SystemVhdBootInformation, // q: SYSTEM_VHD_BOOT_INFORMATION + SystemCpuQuotaInformation, // q; s // PsQueryCpuQuotaInformation + SystemNativeBasicInformation, // not implemented + SystemSpare1, // not implemented + SystemLowPriorityIoInformation, // q: SYSTEM_LOW_PRIORITY_IO_INFORMATION + SystemTpmBootEntropyInformation, // q: TPM_BOOT_ENTROPY_NT_RESULT // ExQueryTpmBootEntropyInformation + SystemVerifierCountersInformation, // q: SYSTEM_VERIFIER_COUNTERS_INFORMATION + SystemPagedPoolInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypePagedPool) + SystemSystemPtesInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemPtes) // 120 + SystemNodeDistanceInformation, // q + SystemAcpiAuditInformation, // q: SYSTEM_ACPI_AUDIT_INFORMATION // HaliQuerySystemInformation -> HalpAuditQueryResults, info class 26 + SystemBasicPerformanceInformation, // q: SYSTEM_BASIC_PERFORMANCE_INFORMATION // name:wow64:whNtQuerySystemInformation_SystemBasicPerformanceInformation + SystemQueryPerformanceCounterInformation, // q: SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION // since WIN7 SP1 + SystemSessionBigPoolInformation, // since WIN8 + SystemBootGraphicsInformation, + SystemScrubPhysicalMemoryInformation, + SystemBadPageInformation, + SystemProcessorProfileControlArea, + SystemCombinePhysicalMemoryInformation, // 130 + SystemEntropyInterruptTimingCallback, + SystemConsoleInformation, + SystemPlatformBinaryInformation, + SystemThrottleNotificationInformation, + SystemHypervisorProcessorCountInformation, + SystemDeviceDataInformation, + SystemDeviceDataEnumerationInformation, + SystemMemoryTopologyInformation, + SystemMemoryChannelInformation, + SystemBootLogoInformation, // 140 + SystemProcessorPerformanceInformationEx, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX // since WINBLUE + SystemSpare0, + SystemSecureBootPolicyInformation, + SystemPageFileInformationEx, // q: SYSTEM_PAGEFILE_INFORMATION_EX + SystemSecureBootInformation, + SystemEntropyInterruptTimingRawInformation, + SystemPortableWorkspaceEfiLauncherInformation, + SystemFullProcessInformation, // q: SYSTEM_PROCESS_INFORMATION with SYSTEM_PROCESS_INFORMATION_EXTENSION (requires admin) + SystemKernelDebuggerInformationEx, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX + SystemBootMetadataInformation, // 150 + SystemSoftRebootInformation, + SystemElamCertificateInformation, + SystemOfflineDumpConfigInformation, + SystemProcessorFeaturesInformation, // q: SYSTEM_PROCESSOR_FEATURES_INFORMATION + SystemRegistryReconciliationInformation, + SystemEdidInformation, + SystemManufacturingInformation, // q: SYSTEM_MANUFACTURING_INFORMATION // since THRESHOLD + SystemEnergyEstimationConfigInformation, // q: SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION + SystemHypervisorDetailInformation, // q: SYSTEM_HYPERVISOR_DETAIL_INFORMATION + SystemProcessorCycleStatsInformation, // q: SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION // 160 + SystemVmGenerationCountInformation, + SystemTrustedPlatformModuleInformation, // q: SYSTEM_TPM_INFORMATION + SystemKernelDebuggerFlags, + SystemCodeIntegrityPolicyInformation, + SystemIsolatedUserModeInformation, + SystemHardwareSecurityTestInterfaceResultsInformation, + SystemSingleModuleInformation, // q: SYSTEM_SINGLE_MODULE_INFORMATION + SystemAllowedCpuSetsInformation, + SystemDmaProtectionInformation, // q: SYSTEM_DMA_PROTECTION_INFORMATION + SystemInterruptCpuSetsInformation, + SystemSecureBootPolicyFullInformation, + SystemCodeIntegrityPolicyFullInformation, + SystemAffinitizedInterruptProcessorInformation, + SystemRootSiloInformation, // q: SYSTEM_ROOT_SILO_INFORMATION + SystemCpuSetInformation, // q: SYSTEM_CPU_SET_INFORMATION // since THRESHOLD2 + SystemCpuSetTagInformation, // q: SYSTEM_CPU_SET_TAG_INFORMATION + SystemWin32WerStartCallout, + SystemSecureKernelProfileInformation, + MaxSystemInfoClass +} SYSTEM_INFORMATION_CLASS; + +typedef struct _SYSTEM_BASIC_INFORMATION +{ + ULONG Reserved; + ULONG TimerResolution; + ULONG PageSize; + ULONG NumberOfPhysicalPages; + ULONG LowestPhysicalPageNumber; + ULONG HighestPhysicalPageNumber; + ULONG AllocationGranularity; + ULONG_PTR MinimumUserModeAddress; + ULONG_PTR MaximumUserModeAddress; + ULONG_PTR ActiveProcessorsAffinityMask; + CCHAR NumberOfProcessors; +} SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION; + +typedef struct _SYSTEM_PROCESSOR_INFORMATION +{ + USHORT ProcessorArchitecture; + USHORT ProcessorLevel; + USHORT ProcessorRevision; + USHORT Reserved; + ULONG ProcessorFeatureBits; +} SYSTEM_PROCESSOR_INFORMATION, *PSYSTEM_PROCESSOR_INFORMATION; + +typedef struct _SYSTEM_PERFORMANCE_INFORMATION +{ + LARGE_INTEGER IdleProcessTime; + LARGE_INTEGER IoReadTransferCount; + LARGE_INTEGER IoWriteTransferCount; + LARGE_INTEGER IoOtherTransferCount; + ULONG IoReadOperationCount; + ULONG IoWriteOperationCount; + ULONG IoOtherOperationCount; + ULONG AvailablePages; + ULONG CommittedPages; + ULONG CommitLimit; + ULONG PeakCommitment; + ULONG PageFaultCount; + ULONG CopyOnWriteCount; + ULONG TransitionCount; + ULONG CacheTransitionCount; + ULONG DemandZeroCount; + ULONG PageReadCount; + ULONG PageReadIoCount; + ULONG CacheReadCount; + ULONG CacheIoCount; + ULONG DirtyPagesWriteCount; + ULONG DirtyWriteIoCount; + ULONG MappedPagesWriteCount; + ULONG MappedWriteIoCount; + ULONG PagedPoolPages; + ULONG NonPagedPoolPages; + ULONG PagedPoolAllocs; + ULONG PagedPoolFrees; + ULONG NonPagedPoolAllocs; + ULONG NonPagedPoolFrees; + ULONG FreeSystemPtes; + ULONG ResidentSystemCodePage; + ULONG TotalSystemDriverPages; + ULONG TotalSystemCodePages; + ULONG NonPagedPoolLookasideHits; + ULONG PagedPoolLookasideHits; + ULONG AvailablePagedPoolPages; + ULONG ResidentSystemCachePage; + ULONG ResidentPagedPoolPage; + ULONG ResidentSystemDriverPage; + ULONG CcFastReadNoWait; + ULONG CcFastReadWait; + ULONG CcFastReadResourceMiss; + ULONG CcFastReadNotPossible; + ULONG CcFastMdlReadNoWait; + ULONG CcFastMdlReadWait; + ULONG CcFastMdlReadResourceMiss; + ULONG CcFastMdlReadNotPossible; + ULONG CcMapDataNoWait; + ULONG CcMapDataWait; + ULONG CcMapDataNoWaitMiss; + ULONG CcMapDataWaitMiss; + ULONG CcPinMappedDataCount; + ULONG CcPinReadNoWait; + ULONG CcPinReadWait; + ULONG CcPinReadNoWaitMiss; + ULONG CcPinReadWaitMiss; + ULONG CcCopyReadNoWait; + ULONG CcCopyReadWait; + ULONG CcCopyReadNoWaitMiss; + ULONG CcCopyReadWaitMiss; + ULONG CcMdlReadNoWait; + ULONG CcMdlReadWait; + ULONG CcMdlReadNoWaitMiss; + ULONG CcMdlReadWaitMiss; + ULONG CcReadAheadIos; + ULONG CcLazyWriteIos; + ULONG CcLazyWritePages; + ULONG CcDataFlushes; + ULONG CcDataPages; + ULONG ContextSwitches; + ULONG FirstLevelTbFills; + ULONG SecondLevelTbFills; + ULONG SystemCalls; + ULONGLONG CcTotalDirtyPages; // since THRESHOLD + ULONGLONG CcDirtyPageThreshold; // since THRESHOLD + LONGLONG ResidentAvailablePages; // since THRESHOLD + ULONGLONG SharedCommittedPages; // since THRESHOLD +} SYSTEM_PERFORMANCE_INFORMATION, *PSYSTEM_PERFORMANCE_INFORMATION; + +typedef struct _SYSTEM_TIMEOFDAY_INFORMATION +{ + LARGE_INTEGER BootTime; + LARGE_INTEGER CurrentTime; + LARGE_INTEGER TimeZoneBias; + ULONG TimeZoneId; + ULONG Reserved; + ULONGLONG BootTimeBias; + ULONGLONG SleepTimeBias; +} SYSTEM_TIMEOFDAY_INFORMATION, *PSYSTEM_TIMEOFDAY_INFORMATION; + +typedef struct _SYSTEM_THREAD_INFORMATION +{ + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + ULONG WaitTime; + PVOID StartAddress; + CLIENT_ID ClientId; + KPRIORITY Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + KWAIT_REASON WaitReason; +} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION; + +typedef struct _TEB *PTEB; + +// private +typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION +{ + SYSTEM_THREAD_INFORMATION ThreadInfo; + PVOID StackBase; + PVOID StackLimit; + PVOID Win32StartAddress; + PTEB TebBase; // since VISTA + ULONG_PTR Reserved2; + ULONG_PTR Reserved3; + ULONG_PTR Reserved4; +} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION; + +typedef struct _SYSTEM_PROCESS_INFORMATION +{ + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER WorkingSetPrivateSize; // since VISTA + ULONG HardFaultCount; // since WIN7 + ULONG NumberOfThreadsHighWatermark; // since WIN7 + ULONGLONG CycleTime; // since WIN7 + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + KPRIORITY BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR UniqueProcessKey; // since VISTA (requires SystemExtendedProcessInformation) + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + SYSTEM_THREAD_INFORMATION Threads[1]; +} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; + +typedef struct _SYSTEM_CALL_COUNT_INFORMATION +{ + ULONG Length; + ULONG NumberOfTables; +} SYSTEM_CALL_COUNT_INFORMATION, *PSYSTEM_CALL_COUNT_INFORMATION; + +typedef struct _SYSTEM_DEVICE_INFORMATION +{ + ULONG NumberOfDisks; + ULONG NumberOfFloppies; + ULONG NumberOfCdRoms; + ULONG NumberOfTapes; + ULONG NumberOfSerialPorts; + ULONG NumberOfParallelPorts; +} SYSTEM_DEVICE_INFORMATION, *PSYSTEM_DEVICE_INFORMATION; + +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION +{ + LARGE_INTEGER IdleTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER DpcTime; + LARGE_INTEGER InterruptTime; + ULONG InterruptCount; +} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; + +typedef struct _SYSTEM_FLAGS_INFORMATION +{ + ULONG Flags; // NtGlobalFlag +} SYSTEM_FLAGS_INFORMATION, *PSYSTEM_FLAGS_INFORMATION; + +typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO +{ + USHORT UniqueProcessId; + USHORT CreatorBackTraceIndex; + UCHAR ObjectTypeIndex; + UCHAR HandleAttributes; + USHORT HandleValue; + PVOID Object; + ULONG GrantedAccess; +} SYSTEM_HANDLE_TABLE_ENTRY_INFO, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO; + +typedef struct _SYSTEM_HANDLE_INFORMATION +{ + ULONG NumberOfHandles; + SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1]; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_OBJECTTYPE_INFORMATION +{ + ULONG NextEntryOffset; + ULONG NumberOfObjects; + ULONG NumberOfHandles; + ULONG TypeIndex; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccessMask; + ULONG PoolType; + BOOLEAN SecurityRequired; + BOOLEAN WaitableObject; + UNICODE_STRING TypeName; +} SYSTEM_OBJECTTYPE_INFORMATION, *PSYSTEM_OBJECTTYPE_INFORMATION; + +typedef struct _SYSTEM_OBJECT_INFORMATION +{ + ULONG NextEntryOffset; + PVOID Object; + HANDLE CreatorUniqueProcess; + USHORT CreatorBackTraceIndex; + USHORT Flags; + LONG PointerCount; + LONG HandleCount; + ULONG PagedPoolCharge; + ULONG NonPagedPoolCharge; + HANDLE ExclusiveProcessId; + PVOID SecurityDescriptor; + UNICODE_STRING NameInfo; +} SYSTEM_OBJECT_INFORMATION, *PSYSTEM_OBJECT_INFORMATION; + +typedef struct _SYSTEM_PAGEFILE_INFORMATION +{ + ULONG NextEntryOffset; + ULONG TotalSize; + ULONG TotalInUse; + ULONG PeakUsage; + UNICODE_STRING PageFileName; +} SYSTEM_PAGEFILE_INFORMATION, *PSYSTEM_PAGEFILE_INFORMATION; + +#define MM_WORKING_SET_MAX_HARD_ENABLE 0x1 +#define MM_WORKING_SET_MAX_HARD_DISABLE 0x2 +#define MM_WORKING_SET_MIN_HARD_ENABLE 0x4 +#define MM_WORKING_SET_MIN_HARD_DISABLE 0x8 + +typedef struct _SYSTEM_FILECACHE_INFORMATION +{ + SIZE_T CurrentSize; + SIZE_T PeakSize; + ULONG PageFaultCount; + SIZE_T MinimumWorkingSet; + SIZE_T MaximumWorkingSet; + SIZE_T CurrentSizeIncludingTransitionInPages; + SIZE_T PeakSizeIncludingTransitionInPages; + ULONG TransitionRePurposeCount; + ULONG Flags; +} SYSTEM_FILECACHE_INFORMATION, *PSYSTEM_FILECACHE_INFORMATION; + +// Can be used instead of SYSTEM_FILECACHE_INFORMATION +typedef struct _SYSTEM_BASIC_WORKING_SET_INFORMATION +{ + SIZE_T CurrentSize; + SIZE_T PeakSize; + ULONG PageFaultCount; +} SYSTEM_BASIC_WORKING_SET_INFORMATION, *PSYSTEM_BASIC_WORKING_SET_INFORMATION; + +typedef struct _SYSTEM_POOLTAG +{ + union + { + UCHAR Tag[4]; + ULONG TagUlong; + }; + ULONG PagedAllocs; + ULONG PagedFrees; + SIZE_T PagedUsed; + ULONG NonPagedAllocs; + ULONG NonPagedFrees; + SIZE_T NonPagedUsed; +} SYSTEM_POOLTAG, *PSYSTEM_POOLTAG; + +typedef struct _SYSTEM_POOLTAG_INFORMATION +{ + ULONG Count; + SYSTEM_POOLTAG TagInfo[1]; +} SYSTEM_POOLTAG_INFORMATION, *PSYSTEM_POOLTAG_INFORMATION; + +typedef struct _SYSTEM_INTERRUPT_INFORMATION +{ + ULONG ContextSwitches; + ULONG DpcCount; + ULONG DpcRate; + ULONG TimeIncrement; + ULONG DpcBypassCount; + ULONG ApcBypassCount; +} SYSTEM_INTERRUPT_INFORMATION, *PSYSTEM_INTERRUPT_INFORMATION; + +typedef struct _SYSTEM_DPC_BEHAVIOR_INFORMATION +{ + ULONG Spare; + ULONG DpcQueueDepth; + ULONG MinimumDpcRate; + ULONG AdjustDpcThreshold; + ULONG IdealDpcRate; +} SYSTEM_DPC_BEHAVIOR_INFORMATION, *PSYSTEM_DPC_BEHAVIOR_INFORMATION; + +typedef struct _SYSTEM_QUERY_TIME_ADJUST_INFORMATION +{ + ULONG TimeAdjustment; + ULONG TimeIncrement; + BOOLEAN Enable; +} SYSTEM_QUERY_TIME_ADJUST_INFORMATION, *PSYSTEM_QUERY_TIME_ADJUST_INFORMATION; + +typedef struct _SYSTEM_SET_TIME_ADJUST_INFORMATION +{ + ULONG TimeAdjustment; + BOOLEAN Enable; +} SYSTEM_SET_TIME_ADJUST_INFORMATION, *PSYSTEM_SET_TIME_ADJUST_INFORMATION; + +typedef struct _SYSTEM_EXCEPTION_INFORMATION +{ + ULONG AlignmentFixupCount; + ULONG ExceptionDispatchCount; + ULONG FloatingEmulationCount; + ULONG ByteWordEmulationCount; +} SYSTEM_EXCEPTION_INFORMATION, *PSYSTEM_EXCEPTION_INFORMATION; + +typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION +{ + BOOLEAN KernelDebuggerEnabled; + BOOLEAN KernelDebuggerNotPresent; +} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; + +typedef struct _SYSTEM_CONTEXT_SWITCH_INFORMATION +{ + ULONG ContextSwitches; + ULONG FindAny; + ULONG FindLast; + ULONG FindIdeal; + ULONG IdleAny; + ULONG IdleCurrent; + ULONG IdleLast; + ULONG IdleIdeal; + ULONG PreemptAny; + ULONG PreemptCurrent; + ULONG PreemptLast; + ULONG SwitchToIdle; +} SYSTEM_CONTEXT_SWITCH_INFORMATION, *PSYSTEM_CONTEXT_SWITCH_INFORMATION; + +typedef struct _SYSTEM_REGISTRY_QUOTA_INFORMATION +{ + ULONG RegistryQuotaAllowed; + ULONG RegistryQuotaUsed; + SIZE_T PagedPoolSize; +} SYSTEM_REGISTRY_QUOTA_INFORMATION, *PSYSTEM_REGISTRY_QUOTA_INFORMATION; + +typedef struct _SYSTEM_PROCESSOR_IDLE_INFORMATION +{ + ULONGLONG IdleTime; + ULONGLONG C1Time; + ULONGLONG C2Time; + ULONGLONG C3Time; + ULONG C1Transitions; + ULONG C2Transitions; + ULONG C3Transitions; + ULONG Padding; +} SYSTEM_PROCESSOR_IDLE_INFORMATION, *PSYSTEM_PROCESSOR_IDLE_INFORMATION; + +typedef struct _SYSTEM_LEGACY_DRIVER_INFORMATION +{ + ULONG VetoType; + UNICODE_STRING VetoList; +} SYSTEM_LEGACY_DRIVER_INFORMATION, *PSYSTEM_LEGACY_DRIVER_INFORMATION; + +typedef struct _SYSTEM_LOOKASIDE_INFORMATION +{ + USHORT CurrentDepth; + USHORT MaximumDepth; + ULONG TotalAllocates; + ULONG AllocateMisses; + ULONG TotalFrees; + ULONG FreeMisses; + ULONG Type; + ULONG Tag; + ULONG Size; +} SYSTEM_LOOKASIDE_INFORMATION, *PSYSTEM_LOOKASIDE_INFORMATION; + +typedef struct _SYSTEM_VERIFIER_INFORMATION +{ + ULONG NextEntryOffset; + ULONG Level; + UNICODE_STRING DriverName; + + ULONG RaiseIrqls; + ULONG AcquireSpinLocks; + ULONG SynchronizeExecutions; + ULONG AllocationsAttempted; + + ULONG AllocationsSucceeded; + ULONG AllocationsSucceededSpecialPool; + ULONG AllocationsWithNoTag; + ULONG TrimRequests; + + ULONG Trims; + ULONG AllocationsFailed; + ULONG AllocationsFailedDeliberately; + ULONG Loads; + + ULONG Unloads; + ULONG UnTrackedPool; + ULONG CurrentPagedPoolAllocations; + ULONG CurrentNonPagedPoolAllocations; + + ULONG PeakPagedPoolAllocations; + ULONG PeakNonPagedPoolAllocations; + + SIZE_T PagedPoolUsageInBytes; + SIZE_T NonPagedPoolUsageInBytes; + SIZE_T PeakPagedPoolUsageInBytes; + SIZE_T PeakNonPagedPoolUsageInBytes; +} SYSTEM_VERIFIER_INFORMATION, *PSYSTEM_VERIFIER_INFORMATION; + +typedef struct _SYSTEM_SESSION_PROCESS_INFORMATION +{ + ULONG SessionId; + ULONG SizeOfBuf; + PVOID Buffer; +} SYSTEM_SESSION_PROCESS_INFORMATION, *PSYSTEM_SESSION_PROCESS_INFORMATION; + +typedef struct _SYSTEM_PROCESSOR_POWER_INFORMATION +{ + UCHAR CurrentFrequency; + UCHAR ThermalLimitFrequency; + UCHAR ConstantThrottleFrequency; + UCHAR DegradedThrottleFrequency; + UCHAR LastBusyFrequency; + UCHAR LastC3Frequency; + UCHAR LastAdjustedBusyFrequency; + UCHAR ProcessorMinThrottle; + UCHAR ProcessorMaxThrottle; + ULONG NumberOfFrequencies; + ULONG PromotionCount; + ULONG DemotionCount; + ULONG ErrorCount; + ULONG RetryCount; + ULONGLONG CurrentFrequencyTime; + ULONGLONG CurrentProcessorTime; + ULONGLONG CurrentProcessorIdleTime; + ULONGLONG LastProcessorTime; + ULONGLONG LastProcessorIdleTime; +} SYSTEM_PROCESSOR_POWER_INFORMATION, *PSYSTEM_PROCESSOR_POWER_INFORMATION; + +typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +{ + PVOID Object; + ULONG_PTR UniqueProcessId; + ULONG_PTR HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX +{ + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _SYSTEM_BIGPOOL_ENTRY +{ + union + { + PVOID VirtualAddress; + ULONG_PTR NonPaged : 1; + }; + SIZE_T SizeInBytes; + union + { + UCHAR Tag[4]; + ULONG TagUlong; + }; +} SYSTEM_BIGPOOL_ENTRY, *PSYSTEM_BIGPOOL_ENTRY; + +typedef struct _SYSTEM_BIGPOOL_INFORMATION +{ + ULONG Count; + SYSTEM_BIGPOOL_ENTRY AllocatedInfo[1]; +} SYSTEM_BIGPOOL_INFORMATION, *PSYSTEM_BIGPOOL_INFORMATION; + +typedef struct _SYSTEM_POOL_ENTRY +{ + BOOLEAN Allocated; + BOOLEAN Spare0; + USHORT AllocatorBackTraceIndex; + ULONG Size; + union + { + UCHAR Tag[4]; + ULONG TagUlong; + PVOID ProcessChargedQuota; + }; +} SYSTEM_POOL_ENTRY, *PSYSTEM_POOL_ENTRY; + +typedef struct _SYSTEM_POOL_INFORMATION +{ + SIZE_T TotalSize; + PVOID FirstEntry; + USHORT EntryOverhead; + BOOLEAN PoolTagPresent; + BOOLEAN Spare0; + ULONG NumberOfEntries; + SYSTEM_POOL_ENTRY Entries[1]; +} SYSTEM_POOL_INFORMATION, *PSYSTEM_POOL_INFORMATION; + +typedef struct _SYSTEM_SESSION_POOLTAG_INFORMATION +{ + SIZE_T NextEntryOffset; + ULONG SessionId; + ULONG Count; + SYSTEM_POOLTAG TagInfo[1]; +} SYSTEM_SESSION_POOLTAG_INFORMATION, *PSYSTEM_SESSION_POOLTAG_INFORMATION; + +typedef struct _SYSTEM_SESSION_MAPPED_VIEW_INFORMATION +{ + SIZE_T NextEntryOffset; + ULONG SessionId; + ULONG ViewFailures; + SIZE_T NumberOfBytesAvailable; + SIZE_T NumberOfBytesAvailableContiguous; +} SYSTEM_SESSION_MAPPED_VIEW_INFORMATION, *PSYSTEM_SESSION_MAPPED_VIEW_INFORMATION; + +// private +typedef struct _SYSTEM_MEMORY_LIST_INFORMATION +{ + ULONG_PTR ZeroPageCount; + ULONG_PTR FreePageCount; + ULONG_PTR ModifiedPageCount; + ULONG_PTR ModifiedNoWritePageCount; + ULONG_PTR BadPageCount; + ULONG_PTR PageCountByPriority[8]; + ULONG_PTR RepurposedPagesByPriority[8]; + ULONG_PTR ModifiedPageCountPageFile; +} SYSTEM_MEMORY_LIST_INFORMATION, *PSYSTEM_MEMORY_LIST_INFORMATION; + +// private +typedef enum _SYSTEM_MEMORY_LIST_COMMAND +{ + MemoryCaptureAccessedBits, + MemoryCaptureAndResetAccessedBits, + MemoryEmptyWorkingSets, + MemoryFlushModifiedList, + MemoryPurgeStandbyList, + MemoryPurgeLowPriorityStandbyList, + MemoryCommandMax +} SYSTEM_MEMORY_LIST_COMMAND; + +// private +typedef struct _SYSTEM_THREAD_CID_PRIORITY_INFORMATION +{ + CLIENT_ID ClientId; + KPRIORITY Priority; +} SYSTEM_THREAD_CID_PRIORITY_INFORMATION, *PSYSTEM_THREAD_CID_PRIORITY_INFORMATION; + +// private +typedef struct _SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION +{ + ULONGLONG CycleTime; +} SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION, *PSYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION; + +// private +typedef struct _SYSTEM_REF_TRACE_INFORMATION +{ + BOOLEAN TraceEnable; + BOOLEAN TracePermanent; + UNICODE_STRING TraceProcessName; + UNICODE_STRING TracePoolTags; +} SYSTEM_REF_TRACE_INFORMATION, *PSYSTEM_REF_TRACE_INFORMATION; + +// private +typedef struct _SYSTEM_PROCESS_ID_INFORMATION +{ + HANDLE ProcessId; + UNICODE_STRING ImageName; +} SYSTEM_PROCESS_ID_INFORMATION, *PSYSTEM_PROCESS_ID_INFORMATION; + +#if (PHNT_MODE == PHNT_MODE_KERNEL) +typedef enum _FIRMWARE_TYPE +{ + FirmwareTypeUnknown, + FirmwareTypeBios, + FirmwareTypeUefi, + FirmwareTypeMax +} FIRMWARE_TYPE, *PFIRMWARE_TYPE; +#endif + +// private +typedef struct _SYSTEM_BOOT_ENVIRONMENT_INFORMATION +{ + GUID BootIdentifier; + FIRMWARE_TYPE FirmwareType; +} SYSTEM_BOOT_ENVIRONMENT_INFORMATION, PSYSTEM_BOOT_ENVIRONMENT_INFORMATION; + +// private +typedef struct _SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION +{ + ULONG FlagsToEnable; + ULONG FlagsToDisable; +} SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION, *PSYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION; + +// private +typedef struct _SYSTEM_SYSTEM_PARTITION_INFORMATION +{ + UNICODE_STRING SystemPartition; +} SYSTEM_SYSTEM_PARTITION_INFORMATION, *PSYSTEM_SYSTEM_PARTITION_INFORMATION; + +// private +typedef struct _SYSTEM_SYSTEM_DISK_INFORMATION +{ + UNICODE_STRING SystemDisk; +} SYSTEM_SYSTEM_DISK_INFORMATION, *PSYSTEM_SYSTEM_DISK_INFORMATION; + +// private +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT +{ + LARGE_INTEGER Hits; // ULONG in WIN8 + UCHAR PercentFrequency; +} SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT, *PSYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT; + +// private +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION +{ + ULONG ProcessorNumber; + ULONG StateCount; + SYSTEM_PROCESSOR_PERFORMANCE_HITCOUNT States[1]; +} SYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION, *PSYSTEM_PROCESSOR_PERFORMANCE_STATE_DISTRIBUTION; + +// private +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION +{ + ULONG ProcessorCount; + ULONG Offsets[1]; +} SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION, *PSYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION; + +// private +typedef enum _SYSTEM_VA_TYPE +{ + SystemVaTypeAll, + SystemVaTypeNonPagedPool, + SystemVaTypePagedPool, + SystemVaTypeSystemCache, + SystemVaTypeSystemPtes, + SystemVaTypeSessionSpace, + SystemVaTypeMax +} SYSTEM_VA_TYPE, *PSYSTEM_VA_TYPE; + +// private +typedef struct _SYSTEM_VA_LIST_INFORMATION +{ + SIZE_T VirtualSize; + SIZE_T VirtualPeak; + SIZE_T VirtualLimit; + SIZE_T AllocationFailures; +} SYSTEM_VA_LIST_INFORMATION, *PSYSTEM_VA_LIST_INFORMATION; + +// private +typedef struct _SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS +{ + HANDLE KeyHandle; + PUNICODE_STRING ValueNamePointer; + PULONG RequiredLengthPointer; + PUCHAR Buffer; + ULONG BufferLength; + ULONG Type; + PUCHAR AppendBuffer; + ULONG AppendBufferLength; + BOOLEAN CreateIfDoesntExist; + BOOLEAN TruncateExistingValue; +} SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS, *PSYSTEM_REGISTRY_APPEND_STRING_PARAMETERS; + +// msdn +typedef struct _SYSTEM_VHD_BOOT_INFORMATION +{ + BOOLEAN OsDiskIsVhd; + ULONG OsVhdFilePathOffset; + WCHAR OsVhdParentVolume[ANYSIZE_ARRAY]; +} SYSTEM_VHD_BOOT_INFORMATION, *PSYSTEM_VHD_BOOT_INFORMATION; + +// private +typedef struct _SYSTEM_LOW_PRIORITY_IO_INFORMATION +{ + ULONG LowPriReadOperations; + ULONG LowPriWriteOperations; + ULONG KernelBumpedToNormalOperations; + ULONG LowPriPagingReadOperations; + ULONG KernelPagingReadsBumpedToNormal; + ULONG LowPriPagingWriteOperations; + ULONG KernelPagingWritesBumpedToNormal; + ULONG BoostedIrpCount; + ULONG BoostedPagingIrpCount; + ULONG BlanketBoostCount; +} SYSTEM_LOW_PRIORITY_IO_INFORMATION, *PSYSTEM_LOW_PRIORITY_IO_INFORMATION; + +// symbols +typedef enum _TPM_BOOT_ENTROPY_RESULT_CODE +{ + TpmBootEntropyStructureUninitialized, + TpmBootEntropyDisabledByPolicy, + TpmBootEntropyNoTpmFound, + TpmBootEntropyTpmError, + TpmBootEntropySuccess +} TPM_BOOT_ENTROPY_RESULT_CODE; + +// Contents of KeLoaderBlock->Extension->TpmBootEntropyResult (TPM_BOOT_ENTROPY_LDR_RESULT). +// EntropyData is truncated to 40 bytes. + +// private +typedef struct _TPM_BOOT_ENTROPY_NT_RESULT +{ + ULONGLONG Policy; + TPM_BOOT_ENTROPY_RESULT_CODE ResultCode; + NTSTATUS ResultStatus; + ULONGLONG Time; + ULONG EntropyLength; + UCHAR EntropyData[40]; +} TPM_BOOT_ENTROPY_NT_RESULT, *PTPM_BOOT_ENTROPY_NT_RESULT; + +// private +typedef struct _SYSTEM_VERIFIER_COUNTERS_INFORMATION +{ + SYSTEM_VERIFIER_INFORMATION Legacy; + ULONG RaiseIrqls; + ULONG AcquireSpinLocks; + ULONG SynchronizeExecutions; + ULONG AllocationsWithNoTag; + ULONG AllocationsFailed; + ULONG AllocationsFailedDeliberately; + SIZE_T LockedBytes; + SIZE_T PeakLockedBytes; + SIZE_T MappedLockedBytes; + SIZE_T PeakMappedLockedBytes; + SIZE_T MappedIoSpaceBytes; + SIZE_T PeakMappedIoSpaceBytes; + SIZE_T PagesForMdlBytes; + SIZE_T PeakPagesForMdlBytes; + SIZE_T ContiguousMemoryBytes; + SIZE_T PeakContiguousMemoryBytes; +} SYSTEM_VERIFIER_COUNTERS_INFORMATION, *PSYSTEM_VERIFIER_COUNTERS_INFORMATION; + +// private +typedef struct _SYSTEM_ACPI_AUDIT_INFORMATION +{ + ULONG RsdpCount; + ULONG SameRsdt : 1; + ULONG SlicPresent : 1; + ULONG SlicDifferent : 1; +} SYSTEM_ACPI_AUDIT_INFORMATION, *PSYSTEM_ACPI_AUDIT_INFORMATION; + +// private +typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION +{ + SIZE_T AvailablePages; + SIZE_T CommittedPages; + SIZE_T CommitLimit; + SIZE_T PeakCommitment; +} SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION; + +// begin_msdn + +typedef struct _QUERY_PERFORMANCE_COUNTER_FLAGS +{ + union + { + struct + { + ULONG KernelTransition : 1; + ULONG Reserved : 31; + }; + ULONG ul; + }; +} QUERY_PERFORMANCE_COUNTER_FLAGS; + +typedef struct _SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION +{ + ULONG Version; + QUERY_PERFORMANCE_COUNTER_FLAGS Flags; + QUERY_PERFORMANCE_COUNTER_FLAGS ValidFlags; +} SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION, *PSYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION; + +// end_msdn + +// private +typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX +{ + LARGE_INTEGER IdleTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER DpcTime; + LARGE_INTEGER InterruptTime; + ULONG InterruptCount; + ULONG Spare0; + LARGE_INTEGER AvailableTime; + LARGE_INTEGER Spare1; + LARGE_INTEGER Spare2; +} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX; + +// private +typedef struct _SYSTEM_PAGEFILE_INFORMATION_EX +{ + SYSTEM_PAGEFILE_INFORMATION Info; + ULONG MinimumSize; + ULONG MaximumSize; +} SYSTEM_PAGEFILE_INFORMATION_EX, *PSYSTEM_PAGEFILE_INFORMATION_EX; + +// private +typedef struct _PROCESS_DISK_COUNTERS +{ + ULONGLONG BytesRead; + ULONGLONG BytesWritten; + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG FlushOperationCount; +} PROCESS_DISK_COUNTERS, *PPROCESS_DISK_COUNTERS; + +// private +typedef struct _PROCESS_ENERGY_VALUES +{ + ULONGLONG Cycles[2][4]; + ULONGLONG DiskEnergy; + ULONGLONG NetworkTailEnergy; + ULONGLONG MBBTailEnergy; + ULONGLONG NetworkTxRxBytes; + ULONGLONG MBBTxRxBytes; + union + { + struct + { + ULONG Foreground : 1; + }; + ULONG WindowInformation; + }; + ULONG PixelArea; + LONGLONG PixelReportTimestamp; + ULONGLONG PixelTime; + LONGLONG ForegroundReportTimestamp; + ULONGLONG ForegroundTime; +} PROCESS_ENERGY_VALUES, *PPROCESS_ENERGY_VALUES; + +// private +typedef struct _SYSTEM_PROCESS_INFORMATION_EXTENSION +{ + PROCESS_DISK_COUNTERS DiskCounters; + ULONGLONG ContextSwitches; + union + { + ULONG Flags; + struct + { + ULONG HasStrongId : 1; + ULONG Spare : 31; + }; + }; + ULONG UserSidOffset; + ULONG PackageFullNameOffset; // since THRESHOLD + PROCESS_ENERGY_VALUES EnergyValues; // since THRESHOLD + ULONG AppIdOffset; // since THRESHOLD + SIZE_T SharedCommitCharge; // since THRESHOLD2 +} SYSTEM_PROCESS_INFORMATION_EXTENSION, *PSYSTEM_PROCESS_INFORMATION_EXTENSION; + +// private +typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX +{ + BOOLEAN DebuggerAllowed; + BOOLEAN DebuggerEnabled; + BOOLEAN DebuggerPresent; +} SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION_EX; + +// private +typedef struct _SYSTEM_PROCESSOR_FEATURES_INFORMATION +{ + ULONGLONG ProcessorFeatureBits; + ULONGLONG Reserved[3]; +} SYSTEM_PROCESSOR_FEATURES_INFORMATION, *PSYSTEM_PROCESSOR_FEATURES_INFORMATION; + +// private +typedef struct _SYSTEM_MANUFACTURING_INFORMATION +{ + ULONG Options; + UNICODE_STRING ProfileName; +} SYSTEM_MANUFACTURING_INFORMATION, *PSYSTEM_MANUFACTURING_INFORMATION; + +// private +typedef struct _SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION +{ + BOOLEAN Enabled; +} SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION, *PSYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION; + +// private +typedef struct _HV_DETAILS +{ + ULONG Data[4]; +} HV_DETAILS, *PHV_DETAILS; + +// private +typedef struct _SYSTEM_HYPERVISOR_DETAIL_INFORMATION +{ + HV_DETAILS HvVendorAndMaxFunction; + HV_DETAILS HypervisorInterface; + HV_DETAILS HypervisorVersion; + HV_DETAILS HvFeatures; + HV_DETAILS HwFeatures; + HV_DETAILS EnlightenmentInfo; + HV_DETAILS ImplementationLimits; +} SYSTEM_HYPERVISOR_DETAIL_INFORMATION, *PSYSTEM_HYPERVISOR_DETAIL_INFORMATION; + +// private +typedef struct _SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION +{ + ULONGLONG Cycles[2][4]; +} SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION, *PSYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION; + +// private +typedef struct _SYSTEM_TPM_INFORMATION +{ + ULONG Flags; +} SYSTEM_TPM_INFORMATION, *PSYSTEM_TPM_INFORMATION; + +// private +typedef struct _SYSTEM_DMA_PROTECTION_INFORMATION +{ + BOOLEAN DmaProtectionsAvailable; + BOOLEAN DmaProtectionsInUse; +} SYSTEM_DMA_PROTECTION_INFORMATION, *PSYSTEM_DMA_PROTECTION_INFORMATION; + +// private +typedef struct _SYSTEM_SINGLE_MODULE_INFORMATION +{ + PVOID TargetModuleAddress; + RTL_PROCESS_MODULE_INFORMATION_EX ExInfo; +} SYSTEM_SINGLE_MODULE_INFORMATION, *PSYSTEM_SINGLE_MODULE_INFORMATION; + +// private +typedef struct _SYSTEM_ROOT_SILO_INFORMATION +{ + ULONG NumberOfSilos; + HANDLE SiloIdList[1]; +} SYSTEM_ROOT_SILO_INFORMATION, *PSYSTEM_ROOT_SILO_INFORMATION; + +// private +typedef struct _SYSTEM_CPU_SET_TAG_INFORMATION +{ + ULONGLONG Tag; + ULONGLONG CpuSets[1]; +} SYSTEM_CPU_SET_TAG_INFORMATION, *PSYSTEM_CPU_SET_TAG_INFORMATION; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySystemInformationEx( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _In_reads_bytes_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _In_reads_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength + ); + +// SysDbg APIs + +// private +typedef enum _SYSDBG_COMMAND +{ + SysDbgQueryModuleInformation, + SysDbgQueryTraceInformation, + SysDbgSetTracepoint, + SysDbgSetSpecialCall, + SysDbgClearSpecialCalls, + SysDbgQuerySpecialCalls, + SysDbgBreakPoint, + SysDbgQueryVersion, + SysDbgReadVirtual, + SysDbgWriteVirtual, + SysDbgReadPhysical, + SysDbgWritePhysical, + SysDbgReadControlSpace, + SysDbgWriteControlSpace, + SysDbgReadIoSpace, + SysDbgWriteIoSpace, + SysDbgReadMsr, + SysDbgWriteMsr, + SysDbgReadBusData, + SysDbgWriteBusData, + SysDbgCheckLowMemory, + SysDbgEnableKernelDebugger, + SysDbgDisableKernelDebugger, + SysDbgGetAutoKdEnable, + SysDbgSetAutoKdEnable, + SysDbgGetPrintBufferSize, + SysDbgSetPrintBufferSize, + SysDbgGetKdUmExceptionEnable, + SysDbgSetKdUmExceptionEnable, + SysDbgGetTriageDump, + SysDbgGetKdBlockEnable, + SysDbgSetKdBlockEnable, + SysDbgRegisterForUmBreakInfo, + SysDbgGetUmBreakPid, + SysDbgClearUmBreakPid, + SysDbgGetUmAttachPid, + SysDbgClearUmAttachPid +} SYSDBG_COMMAND, *PSYSDBG_COMMAND; + +typedef struct _SYSDBG_VIRTUAL +{ + PVOID Address; + PVOID Buffer; + ULONG Request; +} SYSDBG_VIRTUAL, *PSYSDBG_VIRTUAL; + +typedef struct _SYSDBG_PHYSICAL +{ + PHYSICAL_ADDRESS Address; + PVOID Buffer; + ULONG Request; +} SYSDBG_PHYSICAL, *PSYSDBG_PHYSICAL; + +typedef struct _SYSDBG_CONTROL_SPACE +{ + ULONG64 Address; + PVOID Buffer; + ULONG Request; + ULONG Processor; +} SYSDBG_CONTROL_SPACE, *PSYSDBG_CONTROL_SPACE; + +enum _INTERFACE_TYPE; + +typedef struct _SYSDBG_IO_SPACE +{ + ULONG64 Address; + PVOID Buffer; + ULONG Request; + enum _INTERFACE_TYPE InterfaceType; + ULONG BusNumber; + ULONG AddressSpace; +} SYSDBG_IO_SPACE, *PSYSDBG_IO_SPACE; + +typedef struct _SYSDBG_MSR +{ + ULONG Msr; + ULONG64 Data; +} SYSDBG_MSR, *PSYSDBG_MSR; + +enum _BUS_DATA_TYPE; + +typedef struct _SYSDBG_BUS_DATA +{ + ULONG Address; + PVOID Buffer; + ULONG Request; + enum _BUS_DATA_TYPE BusDataType; + ULONG BusNumber; + ULONG SlotNumber; +} SYSDBG_BUS_DATA, *PSYSDBG_BUS_DATA; + +// private +typedef struct _SYSDBG_TRIAGE_DUMP +{ + ULONG Flags; + ULONG BugCheckCode; + ULONG_PTR BugCheckParam1; + ULONG_PTR BugCheckParam2; + ULONG_PTR BugCheckParam3; + ULONG_PTR BugCheckParam4; + ULONG ProcessHandles; + ULONG ThreadHandles; + PHANDLE Handles; +} SYSDBG_TRIAGE_DUMP, *PSYSDBG_TRIAGE_DUMP; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSystemDebugControl( + _In_ SYSDBG_COMMAND Command, + _Inout_updates_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength, + _Out_opt_ PULONG ReturnLength + ); + +// Hard errors + +typedef enum _HARDERROR_RESPONSE_OPTION +{ + OptionAbortRetryIgnore, + OptionOk, + OptionOkCancel, + OptionRetryCancel, + OptionYesNo, + OptionYesNoCancel, + OptionShutdownSystem, + OptionOkNoWait, + OptionCancelTryContinue +} HARDERROR_RESPONSE_OPTION; + +typedef enum _HARDERROR_RESPONSE +{ + ResponseReturnToCaller, + ResponseNotHandled, + ResponseAbort, + ResponseCancel, + ResponseIgnore, + ResponseNo, + ResponseOk, + ResponseRetry, + ResponseYes, + ResponseTryAgain, + ResponseContinue +} HARDERROR_RESPONSE; + +// HARDERROR_MSG not included + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRaiseHardError( + _In_ NTSTATUS ErrorStatus, + _In_ ULONG NumberOfParameters, + _In_ ULONG UnicodeStringParameterMask, + _In_reads_(NumberOfParameters) PULONG_PTR Parameters, + _In_ ULONG ValidResponseOptions, + _Out_ PULONG Response + ); + +// Kernel-user shared data + +typedef enum _ALTERNATIVE_ARCHITECTURE_TYPE +{ + StandardDesign, + NEC98x86, + EndAlternatives +} ALTERNATIVE_ARCHITECTURE_TYPE; + +#define PROCESSOR_FEATURE_MAX 64 + +#define MAX_WOW64_SHARED_ENTRIES 16 + +#define NX_SUPPORT_POLICY_ALWAYSOFF 0 +#define NX_SUPPORT_POLICY_ALWAYSON 1 +#define NX_SUPPORT_POLICY_OPTIN 2 +#define NX_SUPPORT_POLICY_OPTOUT 3 + +#include +typedef struct _KUSER_SHARED_DATA +{ + ULONG TickCountLowDeprecated; + ULONG TickCountMultiplier; + + volatile KSYSTEM_TIME InterruptTime; + volatile KSYSTEM_TIME SystemTime; + volatile KSYSTEM_TIME TimeZoneBias; + + USHORT ImageNumberLow; + USHORT ImageNumberHigh; + + WCHAR NtSystemRoot[260]; + + ULONG MaxStackTraceDepth; + + ULONG CryptoExponent; + + ULONG TimeZoneId; + ULONG LargePageMinimum; + ULONG AitSamplingValue; + ULONG AppCompatFlag; + ULONGLONG RNGSeedVersion; + ULONG GlobalValidationRunlevel; + LONG TimeZoneBiasStamp; + ULONG Reserved2; + + ULONG NtProductType; + BOOLEAN ProductTypeIsValid; + UCHAR Reserved0[1]; + USHORT NativeProcessorArchitecture; + + ULONG NtMajorVersion; + ULONG NtMinorVersion; + + BOOLEAN ProcessorFeatures[PROCESSOR_FEATURE_MAX]; + + ULONG Reserved1; + ULONG Reserved3; + + volatile ULONG TimeSlip; + + ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture; + ULONG AltArchitecturePad[1]; + + LARGE_INTEGER SystemExpirationDate; + + ULONG SuiteMask; + + BOOLEAN KdDebuggerEnabled; + union + { + UCHAR MitigationPolicies; + struct + { + UCHAR NXSupportPolicy : 2; + UCHAR SEHValidationPolicy : 2; + UCHAR CurDirDevicesSkippedForDlls : 2; + UCHAR Reserved : 2; + }; + }; + UCHAR Reserved6[2]; + + volatile ULONG ActiveConsoleId; + + volatile ULONG DismountCount; + + ULONG ComPlusPackage; + + ULONG LastSystemRITEventTickCount; + + ULONG NumberOfPhysicalPages; + + BOOLEAN SafeBootMode; + UCHAR Reserved12[3]; + + union + { + ULONG SharedDataFlags; + struct + { + ULONG DbgErrorPortPresent : 1; + ULONG DbgElevationEnabled : 1; + ULONG DbgVirtEnabled : 1; + ULONG DbgInstallerDetectEnabled : 1; + ULONG DbgLkgEnabled : 1; + ULONG DbgDynProcessorEnabled : 1; + ULONG DbgConsoleBrokerEnabled : 1; + ULONG DbgSecureBootEnabled : 1; + ULONG SpareBits : 24; + }; + }; + ULONG DataFlagsPad[1]; + + ULONGLONG TestRetInstruction; + ULONGLONG QpcFrequency; + ULONGLONG SystemCallPad[3]; + + union + { + volatile KSYSTEM_TIME TickCount; + volatile ULONG64 TickCountQuad; + ULONG ReservedTickCountOverlay[3]; + }; + ULONG TickCountPad[1]; + + ULONG Cookie; + ULONG CookiePad[1]; + + LONGLONG ConsoleSessionForegroundProcessId; + ULONGLONG TimeUpdateLock; + ULONGLONG BaselineSystemTimeQpc; + ULONGLONG BaselineInterruptTimeQpc; + ULONGLONG QpcSystemTimeIncrement; + ULONGLONG QpcInterruptTimeIncrement; + ULONG QpcSystemTimeIncrement32; + ULONG QpcInterruptTimeIncrement32; + UCHAR QpcSystemTimeIncrementShift; + UCHAR QpcInterruptTimeIncrementShift; + UCHAR Reserved8[14]; + + USHORT UserModeGlobalLogger[16]; + ULONG ImageFileExecutionOptions; + + ULONG LangGenerationCount; + ULONGLONG Reserved4; + volatile ULONG64 InterruptTimeBias; + volatile ULONG64 QpcBias; + + volatile ULONG ActiveProcessorCount; + volatile UCHAR ActiveGroupCount; + UCHAR Reserved9; + union + { + USHORT QpcData; + struct + { + UCHAR QpcBypassEnabled : 1; + UCHAR QpcShift : 1; + }; + }; + + LARGE_INTEGER TimeZoneBiasEffectiveStart; + LARGE_INTEGER TimeZoneBiasEffectiveEnd; + XSTATE_CONFIGURATION XState; +} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA; +#include + +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TickCountMultiplier) == 0x4); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, InterruptTime) == 0x8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, SystemTime) == 0x14); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TimeZoneBias) == 0x20); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ImageNumberLow) == 0x2c); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ImageNumberHigh) == 0x2e); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, NtSystemRoot) == 0x30); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, MaxStackTraceDepth) == 0x238); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, CryptoExponent) == 0x23c); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TimeZoneId) == 0x240); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, LargePageMinimum) == 0x244); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, NtProductType) == 0x264); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ProductTypeIsValid) == 0x268); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, NtMajorVersion) == 0x26c); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, NtMinorVersion) == 0x270); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ProcessorFeatures) == 0x274); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, Reserved1) == 0x2b4); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, Reserved3) == 0x2b8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TimeSlip) == 0x2bc); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, AlternativeArchitecture) == 0x2c0); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, SystemExpirationDate) == 0x2c8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, SuiteMask) == 0x2d0); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, KdDebuggerEnabled) == 0x2d4); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ActiveConsoleId) == 0x2d8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, DismountCount) == 0x2dc); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, ComPlusPackage) == 0x2e0); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, LastSystemRITEventTickCount) == 0x2e4); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, NumberOfPhysicalPages) == 0x2e8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, SafeBootMode) == 0x2ec); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TestRetInstruction) == 0x2f8); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, SystemCallPad) == 0x308); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TickCount) == 0x320); +C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, TickCountQuad) == 0x320); +// C_ASSERT(FIELD_OFFSET(KUSER_SHARED_DATA, XState) == 0x3d8); // Visual Studio has a problem with this + +#define USER_SHARED_DATA ((KUSER_SHARED_DATA * const)0x7ffe0000) + +#if (PHNT_VERSION >= PHNT_WS03) + +FORCEINLINE ULONGLONG NtGetTickCount64() +{ + ULARGE_INTEGER tickCount; + +#ifdef _WIN64 + + tickCount.QuadPart = USER_SHARED_DATA->TickCountQuad; + +#else + + while (TRUE) + { + tickCount.HighPart = (ULONG)USER_SHARED_DATA->TickCount.High1Time; + tickCount.LowPart = USER_SHARED_DATA->TickCount.LowPart; + + if (tickCount.HighPart == (ULONG)USER_SHARED_DATA->TickCount.High2Time) + break; + + YieldProcessor(); + } + +#endif + + return (UInt32x32To64(tickCount.LowPart, USER_SHARED_DATA->TickCountMultiplier) >> 24) + + (UInt32x32To64(tickCount.HighPart, USER_SHARED_DATA->TickCountMultiplier) << 8); +} + +FORCEINLINE ULONG NtGetTickCount() +{ +#ifdef _WIN64 + + return (ULONG)((USER_SHARED_DATA->TickCountQuad * USER_SHARED_DATA->TickCountMultiplier) >> 24); + +#else + + ULARGE_INTEGER tickCount; + + while (TRUE) + { + tickCount.HighPart = (ULONG)USER_SHARED_DATA->TickCount.High1Time; + tickCount.LowPart = USER_SHARED_DATA->TickCount.LowPart; + + if (tickCount.HighPart == (ULONG)USER_SHARED_DATA->TickCount.High2Time) + break; + + YieldProcessor(); + } + + return (ULONG)((UInt32x32To64(tickCount.LowPart, USER_SHARED_DATA->TickCountMultiplier) >> 24) + + UInt32x32To64((tickCount.HighPart << 8) & 0xffffffff, USER_SHARED_DATA->TickCountMultiplier)); + +#endif +} + +#endif + +// Locale + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDefaultLocale( + _In_ BOOLEAN UserProfile, + _Out_ PLCID DefaultLocaleId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetDefaultLocale( + _In_ BOOLEAN UserProfile, + _In_ LCID DefaultLocaleId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInstallUILanguage( + _Out_ LANGID *InstallUILanguageId + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushInstallUILanguage( + _In_ LANGID InstallUILanguage, + _In_ ULONG SetComittedFlag + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDefaultUILanguage( + _Out_ LANGID *DefaultUILanguageId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetDefaultUILanguage( + _In_ LANGID DefaultUILanguageId + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtIsUILanguageComitted( + VOID + ); +#endif + +// NLS + +// begin_private + +#if (PHNT_VERSION >= PHNT_VISTA) + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtInitializeNlsFiles( + _Out_ PVOID *BaseAddress, + _Out_ PLCID DefaultLocaleId, + _Out_ PLARGE_INTEGER DefaultCasingTableSize + ); +#else +NTSYSCALLAPI +NTSTATUS +NTAPI +NtInitializeNlsFiles( + _Out_ PVOID *BaseAddress, + _Out_ PLCID DefaultLocaleId, + _Out_ PLARGE_INTEGER DefaultCasingTableSize, + _Out_opt_ PULONG CurrentNLSVersion + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetNlsSectionPtr( + _In_ ULONG SectionType, + _In_ ULONG SectionData, + _In_ PVOID ContextData, + _Out_ PVOID *SectionPointer, + _Out_ PULONG SectionSize + ); + +#if (PHNT_VERSION < PHNT_WIN7) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAcquireCMFViewOwnership( + _Out_ PULONGLONG TimeStamp, + _Out_ PBOOLEAN tokenTaken, + _In_ BOOLEAN replaceExisting + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReleaseCMFViewOwnership( + VOID + ); + +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMapCMFModule( + _In_ ULONG What, + _In_ ULONG Index, + _Out_opt_ PULONG CacheIndexOut, + _Out_opt_ PULONG CacheFlagsOut, + _Out_opt_ PULONG ViewSizeOut, + _Out_opt_ PVOID *BaseAddress + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetMUIRegistryInfo( + _In_ ULONG Flags, + _Inout_ PULONG DataSize, + _Out_ PVOID Data + ); + +#endif + +// end_private + +// Global atoms + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAddAtom( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +// rev +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAddAtomEx( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom, + _In_ ULONG Flags + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFindAtom( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteAtom( + _In_ RTL_ATOM Atom + ); + +typedef enum _ATOM_INFORMATION_CLASS +{ + AtomBasicInformation, + AtomTableInformation +} ATOM_INFORMATION_CLASS; + +typedef struct _ATOM_BASIC_INFORMATION +{ + USHORT UsageCount; + USHORT Flags; + USHORT NameLength; + WCHAR Name[1]; +} ATOM_BASIC_INFORMATION, *PATOM_BASIC_INFORMATION; + +typedef struct _ATOM_TABLE_INFORMATION +{ + ULONG NumberOfAtoms; + RTL_ATOM Atoms[1]; +} ATOM_TABLE_INFORMATION, *PATOM_TABLE_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationAtom( + _In_ RTL_ATOM Atom, + _In_ ATOM_INFORMATION_CLASS AtomInformationClass, + _Out_writes_bytes_(AtomInformationLength) PVOID AtomInformation, + _In_ ULONG AtomInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +// Global flags + +#define FLG_STOP_ON_EXCEPTION 0x00000001 // uk +#define FLG_SHOW_LDR_SNAPS 0x00000002 // uk +#define FLG_DEBUG_INITIAL_COMMAND 0x00000004 // k +#define FLG_STOP_ON_HUNG_GUI 0x00000008 // k + +#define FLG_HEAP_ENABLE_TAIL_CHECK 0x00000010 // u +#define FLG_HEAP_ENABLE_FREE_CHECK 0x00000020 // u +#define FLG_HEAP_VALIDATE_PARAMETERS 0x00000040 // u +#define FLG_HEAP_VALIDATE_ALL 0x00000080 // u + +#define FLG_APPLICATION_VERIFIER 0x00000100 // u +#define FLG_POOL_ENABLE_TAGGING 0x00000400 // k +#define FLG_HEAP_ENABLE_TAGGING 0x00000800 // u + +#define FLG_USER_STACK_TRACE_DB 0x00001000 // u,32 +#define FLG_KERNEL_STACK_TRACE_DB 0x00002000 // k,32 +#define FLG_MAINTAIN_OBJECT_TYPELIST 0x00004000 // k +#define FLG_HEAP_ENABLE_TAG_BY_DLL 0x00008000 // u + +#define FLG_DISABLE_STACK_EXTENSION 0x00010000 // u +#define FLG_ENABLE_CSRDEBUG 0x00020000 // k +#define FLG_ENABLE_KDEBUG_SYMBOL_LOAD 0x00040000 // k +#define FLG_DISABLE_PAGE_KERNEL_STACKS 0x00080000 // k + +#define FLG_ENABLE_SYSTEM_CRIT_BREAKS 0x00100000 // u +#define FLG_HEAP_DISABLE_COALESCING 0x00200000 // u +#define FLG_ENABLE_CLOSE_EXCEPTIONS 0x00400000 // k +#define FLG_ENABLE_EXCEPTION_LOGGING 0x00800000 // k + +#define FLG_ENABLE_HANDLE_TYPE_TAGGING 0x01000000 // k +#define FLG_HEAP_PAGE_ALLOCS 0x02000000 // u +#define FLG_DEBUG_INITIAL_COMMAND_EX 0x04000000 // k +#define FLG_DISABLE_DBGPRINT 0x08000000 // k + +#define FLG_CRITSEC_EVENT_CREATION 0x10000000 // u +#define FLG_LDR_TOP_DOWN 0x20000000 // u,64 +#define FLG_ENABLE_HANDLE_EXCEPTIONS 0x40000000 // k +#define FLG_DISABLE_PROTDLLS 0x80000000 // u + +#define FLG_VALID_BITS 0xfffffdff + +#define FLG_USERMODE_VALID_BITS (FLG_STOP_ON_EXCEPTION | \ + FLG_SHOW_LDR_SNAPS | \ + FLG_HEAP_ENABLE_TAIL_CHECK | \ + FLG_HEAP_ENABLE_FREE_CHECK | \ + FLG_HEAP_VALIDATE_PARAMETERS | \ + FLG_HEAP_VALIDATE_ALL | \ + FLG_APPLICATION_VERIFIER | \ + FLG_HEAP_ENABLE_TAGGING | \ + FLG_USER_STACK_TRACE_DB | \ + FLG_HEAP_ENABLE_TAG_BY_DLL | \ + FLG_DISABLE_STACK_EXTENSION | \ + FLG_ENABLE_SYSTEM_CRIT_BREAKS | \ + FLG_HEAP_DISABLE_COALESCING | \ + FLG_DISABLE_PROTDLLS | \ + FLG_HEAP_PAGE_ALLOCS | \ + FLG_CRITSEC_EVENT_CREATION | \ + FLG_LDR_TOP_DOWN) + +#define FLG_BOOTONLY_VALID_BITS (FLG_KERNEL_STACK_TRACE_DB | \ + FLG_MAINTAIN_OBJECT_TYPELIST | \ + FLG_ENABLE_CSRDEBUG | \ + FLG_DEBUG_INITIAL_COMMAND | \ + FLG_DEBUG_INITIAL_COMMAND_EX | \ + FLG_DISABLE_PAGE_KERNEL_STACKS) + +#define FLG_KERNELMODE_VALID_BITS (FLG_STOP_ON_EXCEPTION | \ + FLG_SHOW_LDR_SNAPS | \ + FLG_STOP_ON_HUNG_GUI | \ + FLG_POOL_ENABLE_TAGGING | \ + FLG_ENABLE_KDEBUG_SYMBOL_LOAD | \ + FLG_ENABLE_CLOSE_EXCEPTIONS | \ + FLG_ENABLE_EXCEPTION_LOGGING | \ + FLG_ENABLE_HANDLE_TYPE_TAGGING | \ + FLG_DISABLE_DBGPRINT | \ + FLG_ENABLE_HANDLE_EXCEPTIONS) + +// Licensing + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryLicenseValue( + _In_ PUNICODE_STRING ValueName, + _Out_opt_ PULONG Type, + _Out_writes_bytes_to_opt_(DataSize, *ResultDataSize) PVOID Data, + _In_ ULONG DataSize, + _Out_ PULONG ResultDataSize + ); + +// Misc. + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetDefaultHardErrorPort( + _In_ HANDLE DefaultHardErrorPort + ); + +typedef enum _SHUTDOWN_ACTION +{ + ShutdownNoReboot, + ShutdownReboot, + ShutdownPowerOff +} SHUTDOWN_ACTION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtShutdownSystem( + _In_ SHUTDOWN_ACTION Action + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDisplayString( + _In_ PUNICODE_STRING String + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDrawText( + _In_ PUNICODE_STRING String + ); +#endif + +#endif // (PHNT_MODE != PHNT_MODE_KERNEL) + +#endif diff --git a/phnt/include/ntgdi.h b/phnt/include/ntgdi.h new file mode 100644 index 0000000..d7b47cd --- /dev/null +++ b/phnt/include/ntgdi.h @@ -0,0 +1,121 @@ +#ifndef _NTGDI_H +#define _NTGDI_H + +#define GDI_MAX_HANDLE_COUNT 0x4000 + +#define GDI_HANDLE_INDEX_SHIFT 0 +#define GDI_HANDLE_INDEX_BITS 16 +#define GDI_HANDLE_INDEX_MASK 0xffff + +#define GDI_HANDLE_TYPE_SHIFT 16 +#define GDI_HANDLE_TYPE_BITS 5 +#define GDI_HANDLE_TYPE_MASK 0x1f + +#define GDI_HANDLE_ALTTYPE_SHIFT 21 +#define GDI_HANDLE_ALTTYPE_BITS 2 +#define GDI_HANDLE_ALTTYPE_MASK 0x3 + +#define GDI_HANDLE_STOCK_SHIFT 23 +#define GDI_HANDLE_STOCK_BITS 1 +#define GDI_HANDLE_STOCK_MASK 0x1 + +#define GDI_HANDLE_UNIQUE_SHIFT 24 +#define GDI_HANDLE_UNIQUE_BITS 8 +#define GDI_HANDLE_UNIQUE_MASK 0xff + +#define GDI_HANDLE_INDEX(Handle) ((ULONG)(Handle) & GDI_HANDLE_INDEX_MASK) +#define GDI_HANDLE_TYPE(Handle) (((ULONG)(Handle) >> GDI_HANDLE_TYPE_SHIFT) & GDI_HANDLE_TYPE_MASK) +#define GDI_HANDLE_ALTTYPE(Handle) (((ULONG)(Handle) >> GDI_HANDLE_ALTTYPE_SHIFT) & GDI_HANDLE_ALTTYPE_MASK) +#define GDI_HANDLE_STOCK(Handle) (((ULONG)(Handle) >> GDI_HANDLE_STOCK_SHIFT)) & GDI_HANDLE_STOCK_MASK) + +#define GDI_MAKE_HANDLE(Index, Unique) ((ULONG)(((ULONG)(Unique) << GDI_HANDLE_INDEX_BITS) | (ULONG)(Index))) + +// GDI server-side types + +#define GDI_DEF_TYPE 0 // invalid handle +#define GDI_DC_TYPE 1 +#define GDI_DD_DIRECTDRAW_TYPE 2 +#define GDI_DD_SURFACE_TYPE 3 +#define GDI_RGN_TYPE 4 +#define GDI_SURF_TYPE 5 +#define GDI_CLIENTOBJ_TYPE 6 +#define GDI_PATH_TYPE 7 +#define GDI_PAL_TYPE 8 +#define GDI_ICMLCS_TYPE 9 +#define GDI_LFONT_TYPE 10 +#define GDI_RFONT_TYPE 11 +#define GDI_PFE_TYPE 12 +#define GDI_PFT_TYPE 13 +#define GDI_ICMCXF_TYPE 14 +#define GDI_ICMDLL_TYPE 15 +#define GDI_BRUSH_TYPE 16 +#define GDI_PFF_TYPE 17 // unused +#define GDI_CACHE_TYPE 18 // unused +#define GDI_SPACE_TYPE 19 +#define GDI_DBRUSH_TYPE 20 // unused +#define GDI_META_TYPE 21 +#define GDI_EFSTATE_TYPE 22 +#define GDI_BMFD_TYPE 23 // unused +#define GDI_VTFD_TYPE 24 // unused +#define GDI_TTFD_TYPE 25 // unused +#define GDI_RC_TYPE 26 // unused +#define GDI_TEMP_TYPE 27 // unused +#define GDI_DRVOBJ_TYPE 28 +#define GDI_DCIOBJ_TYPE 29 // unused +#define GDI_SPOOL_TYPE 30 + +// GDI client-side types + +#define GDI_CLIENT_TYPE_FROM_HANDLE(Handle) ((ULONG)(Handle) & ((GDI_HANDLE_ALTTYPE_MASK << GDI_HANDLE_ALTTYPE_SHIFT) | \ + (GDI_HANDLE_TYPE_MASK << GDI_HANDLE_TYPE_SHIFT))) +#define GDI_CLIENT_TYPE_FROM_UNIQUE(Unique) GDI_CLIENT_TYPE_FROM_HANDLE((ULONG)(Unique) << 16) + +#define GDI_ALTTYPE_1 (1 << GDI_HANDLE_ALTTYPE_SHIFT) +#define GDI_ALTTYPE_2 (2 << GDI_HANDLE_ALTTYPE_SHIFT) +#define GDI_ALTTYPE_3 (3 << GDI_HANDLE_ALTTYPE_SHIFT) + +#define GDI_CLIENT_BITMAP_TYPE (GDI_SURF_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_BRUSH_TYPE (GDI_BRUSH_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_CLIENTOBJ_TYPE (GDI_CLIENTOBJ_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_DC_TYPE (GDI_DC_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_FONT_TYPE (GDI_LFONT_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_PALETTE_TYPE (GDI_PAL_TYPE << GDI_HANDLE_TYPE_SHIFT) +#define GDI_CLIENT_REGION_TYPE (GDI_RGN_TYPE << GDI_HANDLE_TYPE_SHIFT) + +#define GDI_CLIENT_ALTDC_TYPE (GDI_CLIENT_DC_TYPE | GDI_ALTTYPE_1) +#define GDI_CLIENT_DIBSECTION_TYPE (GDI_CLIENT_BITMAP_TYPE | GDI_ALTTYPE_1) +#define GDI_CLIENT_EXTPEN_TYPE (GDI_CLIENT_BRUSH_TYPE | GDI_ALTTYPE_2) +#define GDI_CLIENT_METADC16_TYPE (GDI_CLIENT_CLIENTOBJ_TYPE | GDI_ALTTYPE_3) +#define GDI_CLIENT_METAFILE_TYPE (GDI_CLIENT_CLIENTOBJ_TYPE | GDI_ALTTYPE_2) +#define GDI_CLIENT_METAFILE16_TYPE (GDI_CLIENT_CLIENTOBJ_TYPE | GDI_ALTTYPE_1) +#define GDI_CLIENT_PEN_TYPE (GDI_CLIENT_BRUSH_TYPE | GDI_ALTTYPE_1) + +typedef struct _GDI_HANDLE_ENTRY +{ + union + { + PVOID Object; + PVOID NextFree; + }; + union + { + struct + { + USHORT ProcessId; + USHORT Lock : 1; + USHORT Count : 15; + }; + ULONG Value; + } Owner; + USHORT Unique; + UCHAR Type; + UCHAR Flags; + PVOID UserPointer; +} GDI_HANDLE_ENTRY, *PGDI_HANDLE_ENTRY; + +typedef struct _GDI_SHARED_MEMORY +{ + GDI_HANDLE_ENTRY Handles[GDI_MAX_HANDLE_COUNT]; +} GDI_SHARED_MEMORY, *PGDI_SHARED_MEMORY; + +#endif diff --git a/phnt/include/ntioapi.h b/phnt/include/ntioapi.h new file mode 100644 index 0000000..d0a0261 --- /dev/null +++ b/phnt/include/ntioapi.h @@ -0,0 +1,1690 @@ +#ifndef _NTIOAPI_H +#define _NTIOAPI_H + +// Create disposition + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +// Create/open flags + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_FOR_RECOVERY 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 +#if (PHNT_VERSION >= PHNT_WIN7) +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#define FILE_DISALLOW_EXCLUSIVE 0x00020000 +#endif +#if (PHNT_VERSION >= PHNT_WIN8) +#define FILE_SESSION_AWARE 0x00040000 +#endif + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +#define FILE_COPY_STRUCTURED_STORAGE 0x00000041 +#define FILE_STRUCTURED_STORAGE 0x00000441 + +// I/O status information values for NtCreateFile/NtOpenFile + +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +// Special ByteOffset parameters + +#define FILE_WRITE_TO_END_OF_FILE 0xffffffff +#define FILE_USE_FILE_POINTER_POSITION 0xfffffffe + +// Alignment requirement values + +#define FILE_BYTE_ALIGNMENT 0x00000000 +#define FILE_WORD_ALIGNMENT 0x00000001 +#define FILE_LONG_ALIGNMENT 0x00000003 +#define FILE_QUAD_ALIGNMENT 0x00000007 +#define FILE_OCTA_ALIGNMENT 0x0000000f +#define FILE_32_BYTE_ALIGNMENT 0x0000001f +#define FILE_64_BYTE_ALIGNMENT 0x0000003f +#define FILE_128_BYTE_ALIGNMENT 0x0000007f +#define FILE_256_BYTE_ALIGNMENT 0x000000ff +#define FILE_512_BYTE_ALIGNMENT 0x000001ff + +// Maximum length of a filename string + +#define MAXIMUM_FILENAME_LENGTH 256 + +// Extended attributes + +#define FILE_NEED_EA 0x00000080 + +#define FILE_EA_TYPE_BINARY 0xfffe +#define FILE_EA_TYPE_ASCII 0xfffd +#define FILE_EA_TYPE_BITMAP 0xfffb +#define FILE_EA_TYPE_METAFILE 0xfffa +#define FILE_EA_TYPE_ICON 0xfff9 +#define FILE_EA_TYPE_EA 0xffee +#define FILE_EA_TYPE_MVMT 0xffdf +#define FILE_EA_TYPE_MVST 0xffde +#define FILE_EA_TYPE_ASN1 0xffdd +#define FILE_EA_TYPE_FAMILY_IDS 0xff01 + +// Device characteristics + +#define FILE_REMOVABLE_MEDIA 0x00000001 +#define FILE_READ_ONLY_DEVICE 0x00000002 +#define FILE_FLOPPY_DISKETTE 0x00000004 +#define FILE_WRITE_ONCE_MEDIA 0x00000008 +#define FILE_REMOTE_DEVICE 0x00000010 +#define FILE_DEVICE_IS_MOUNTED 0x00000020 +#define FILE_VIRTUAL_VOLUME 0x00000040 +#define FILE_AUTOGENERATED_DEVICE_NAME 0x00000080 +#define FILE_DEVICE_SECURE_OPEN 0x00000100 +#define FILE_CHARACTERISTIC_PNP_DEVICE 0x00000800 +#define FILE_CHARACTERISTIC_TS_DEVICE 0x00001000 +#define FILE_CHARACTERISTIC_WEBDAV_DEVICE 0x00002000 +#define FILE_CHARACTERISTIC_CSV 0x00010000 +#define FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL 0x00020000 +#define FILE_PORTABLE_DEVICE 0x00040000 + +// Named pipe values + +// NamedPipeType for NtCreateNamedPipeFile +#define FILE_PIPE_BYTE_STREAM_TYPE 0x00000000 +#define FILE_PIPE_MESSAGE_TYPE 0x00000001 +#define FILE_PIPE_ACCEPT_REMOTE_CLIENTS 0x00000000 +#define FILE_PIPE_REJECT_REMOTE_CLIENTS 0x00000002 +#define FILE_PIPE_TYPE_VALID_MASK 0x00000003 + +// CompletionMode for NtCreateNamedPipeFile +#define FILE_PIPE_QUEUE_OPERATION 0x00000000 +#define FILE_PIPE_COMPLETE_OPERATION 0x00000001 + +// ReadMode for NtCreateNamedPipeFile +#define FILE_PIPE_BYTE_STREAM_MODE 0x00000000 +#define FILE_PIPE_MESSAGE_MODE 0x00000001 + +// NamedPipeConfiguration for NtQueryInformationFile +#define FILE_PIPE_INBOUND 0x00000000 +#define FILE_PIPE_OUTBOUND 0x00000001 +#define FILE_PIPE_FULL_DUPLEX 0x00000002 + +// NamedPipeState for NtQueryInformationFile +#define FILE_PIPE_DISCONNECTED_STATE 0x00000001 +#define FILE_PIPE_LISTENING_STATE 0x00000002 +#define FILE_PIPE_CONNECTED_STATE 0x00000003 +#define FILE_PIPE_CLOSING_STATE 0x00000004 + +// NamedPipeEnd for NtQueryInformationFile +#define FILE_PIPE_CLIENT_END 0x00000000 +#define FILE_PIPE_SERVER_END 0x00000001 + +// Mailslot values + +#define MAILSLOT_SIZE_AUTO 0 + +typedef struct _IO_STATUS_BLOCK +{ + union + { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +typedef VOID (NTAPI *PIO_APC_ROUTINE)( + _In_ PVOID ApcContext, + _In_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG Reserved + ); + +// private +typedef struct _FILE_IO_COMPLETION_INFORMATION +{ + PVOID KeyContext; + PVOID ApcContext; + IO_STATUS_BLOCK IoStatusBlock; +} FILE_IO_COMPLETION_INFORMATION, *PFILE_IO_COMPLETION_INFORMATION; + +typedef enum _FILE_INFORMATION_CLASS +{ + FileDirectoryInformation = 1, + FileFullDirectoryInformation, + FileBothDirectoryInformation, + FileBasicInformation, + FileStandardInformation, + FileInternalInformation, + FileEaInformation, + FileAccessInformation, + FileNameInformation, + FileRenameInformation, // 10 + FileLinkInformation, + FileNamesInformation, + FileDispositionInformation, + FilePositionInformation, + FileFullEaInformation, + FileModeInformation, + FileAlignmentInformation, + FileAllInformation, + FileAllocationInformation, + FileEndOfFileInformation, // 20 + FileAlternateNameInformation, + FileStreamInformation, + FilePipeInformation, + FilePipeLocalInformation, + FilePipeRemoteInformation, + FileMailslotQueryInformation, + FileMailslotSetInformation, + FileCompressionInformation, + FileObjectIdInformation, + FileCompletionInformation, // 30 + FileMoveClusterInformation, + FileQuotaInformation, + FileReparsePointInformation, + FileNetworkOpenInformation, + FileAttributeTagInformation, + FileTrackingInformation, + FileIdBothDirectoryInformation, + FileIdFullDirectoryInformation, + FileValidDataLengthInformation, + FileShortNameInformation, // 40 + FileIoCompletionNotificationInformation, + FileIoStatusBlockRangeInformation, + FileIoPriorityHintInformation, + FileSfioReserveInformation, + FileSfioVolumeInformation, + FileHardLinkInformation, + FileProcessIdsUsingFileInformation, + FileNormalizedNameInformation, + FileNetworkPhysicalNameInformation, + FileIdGlobalTxDirectoryInformation, // 50 + FileIsRemoteDeviceInformation, + FileUnusedInformation, + FileNumaNodeInformation, + FileStandardLinkInformation, + FileRemoteProtocolInformation, + FileRenameInformationBypassAccessCheck, // (kernel-mode only) // since WIN8 + FileLinkInformationBypassAccessCheck, // (kernel-mode only) + FileIntegrityStreamInformation, + FileVolumeNameInformation, + FileIdInformation, + FileIdExtdDirectoryInformation, + FileReplaceCompletionInformation, // since WINBLUE + FileHardLinkFullIdInformation, + FileIdExtdBothDirectoryInformation, // since THRESHOLD + FileMaximumInformation +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +// NtQueryInformationFile/NtSetInformationFile types + +typedef struct _FILE_BASIC_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +typedef struct _FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; + +typedef struct _FILE_STANDARD_INFORMATION_EX +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; + BOOLEAN AlternateStream; + BOOLEAN MetadataAttribute; +} FILE_STANDARD_INFORMATION_EX, *PFILE_STANDARD_INFORMATION_EX; + +typedef struct _FILE_INTERNAL_INFORMATION +{ + LARGE_INTEGER IndexNumber; +} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; + +typedef struct _FILE_EA_INFORMATION +{ + ULONG EaSize; +} FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; + +typedef struct _FILE_ACCESS_INFORMATION +{ + ACCESS_MASK AccessFlags; +} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; + +typedef struct _FILE_POSITION_INFORMATION +{ + LARGE_INTEGER CurrentByteOffset; +} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION; + +typedef struct _FILE_MODE_INFORMATION +{ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; + +typedef struct _FILE_ALIGNMENT_INFORMATION +{ + ULONG AlignmentRequirement; +} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION; + +typedef struct _FILE_NAME_INFORMATION +{ + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; + +typedef struct _FILE_ALL_INFORMATION +{ + FILE_BASIC_INFORMATION BasicInformation; + FILE_STANDARD_INFORMATION StandardInformation; + FILE_INTERNAL_INFORMATION InternalInformation; + FILE_EA_INFORMATION EaInformation; + FILE_ACCESS_INFORMATION AccessInformation; + FILE_POSITION_INFORMATION PositionInformation; + FILE_MODE_INFORMATION ModeInformation; + FILE_ALIGNMENT_INFORMATION AlignmentInformation; + FILE_NAME_INFORMATION NameInformation; +} FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; + +typedef struct _FILE_NETWORK_OPEN_INFORMATION +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +typedef struct _FILE_ATTRIBUTE_TAG_INFORMATION +{ + ULONG FileAttributes; + ULONG ReparseTag; +} FILE_ATTRIBUTE_TAG_INFORMATION, *PFILE_ATTRIBUTE_TAG_INFORMATION; + +typedef struct _FILE_ALLOCATION_INFORMATION +{ + LARGE_INTEGER AllocationSize; +} FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION; + +typedef struct _FILE_COMPRESSION_INFORMATION +{ + LARGE_INTEGER CompressedFileSize; + USHORT CompressionFormat; + UCHAR CompressionUnitShift; + UCHAR ChunkShift; + UCHAR ClusterShift; + UCHAR Reserved[3]; +} FILE_COMPRESSION_INFORMATION, *PFILE_COMPRESSION_INFORMATION; + +typedef struct _FILE_DISPOSITION_INFORMATION +{ + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; + +typedef struct _FILE_END_OF_FILE_INFORMATION +{ + LARGE_INTEGER EndOfFile; +} FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION; + +typedef struct _FILE_VALID_DATA_LENGTH_INFORMATION +{ + LARGE_INTEGER ValidDataLength; +} FILE_VALID_DATA_LENGTH_INFORMATION, *PFILE_VALID_DATA_LENGTH_INFORMATION; + +typedef struct _FILE_LINK_INFORMATION +{ + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; + +typedef struct _FILE_MOVE_CLUSTER_INFORMATION +{ + ULONG ClusterCount; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_MOVE_CLUSTER_INFORMATION, *PFILE_MOVE_CLUSTER_INFORMATION; + +typedef struct _FILE_RENAME_INFORMATION +{ + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +typedef struct _FILE_STREAM_INFORMATION +{ + ULONG NextEntryOffset; + ULONG StreamNameLength; + LARGE_INTEGER StreamSize; + LARGE_INTEGER StreamAllocationSize; + WCHAR StreamName[1]; +} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION; + +typedef struct _FILE_TRACKING_INFORMATION +{ + HANDLE DestinationFile; + ULONG ObjectInformationLength; + CHAR ObjectInformation[1]; +} FILE_TRACKING_INFORMATION, *PFILE_TRACKING_INFORMATION; + +typedef struct _FILE_COMPLETION_INFORMATION +{ + HANDLE Port; + PVOID Key; +} FILE_COMPLETION_INFORMATION, *PFILE_COMPLETION_INFORMATION; + +typedef struct _FILE_PIPE_INFORMATION +{ + ULONG ReadMode; + ULONG CompletionMode; +} FILE_PIPE_INFORMATION, *PFILE_PIPE_INFORMATION; + +typedef struct _FILE_PIPE_LOCAL_INFORMATION +{ + ULONG NamedPipeType; + ULONG NamedPipeConfiguration; + ULONG MaximumInstances; + ULONG CurrentInstances; + ULONG InboundQuota; + ULONG ReadDataAvailable; + ULONG OutboundQuota; + ULONG WriteQuotaAvailable; + ULONG NamedPipeState; + ULONG NamedPipeEnd; +} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION; + +typedef struct _FILE_PIPE_REMOTE_INFORMATION +{ + LARGE_INTEGER CollectDataTime; + ULONG MaximumCollectionCount; +} FILE_PIPE_REMOTE_INFORMATION, *PFILE_PIPE_REMOTE_INFORMATION; + +typedef struct _FILE_MAILSLOT_QUERY_INFORMATION +{ + ULONG MaximumMessageSize; + ULONG MailslotQuota; + ULONG NextMessageSize; + ULONG MessagesAvailable; + LARGE_INTEGER ReadTimeout; +} FILE_MAILSLOT_QUERY_INFORMATION, *PFILE_MAILSLOT_QUERY_INFORMATION; + +typedef struct _FILE_MAILSLOT_SET_INFORMATION +{ + PLARGE_INTEGER ReadTimeout; +} FILE_MAILSLOT_SET_INFORMATION, *PFILE_MAILSLOT_SET_INFORMATION; + +typedef struct _FILE_REPARSE_POINT_INFORMATION +{ + LONGLONG FileReference; + ULONG Tag; +} FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION; + +typedef struct _FILE_LINK_ENTRY_INFORMATION +{ + ULONG NextEntryOffset; + LONGLONG ParentFileId; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_LINK_ENTRY_INFORMATION, *PFILE_LINK_ENTRY_INFORMATION; + +typedef struct _FILE_LINKS_INFORMATION +{ + ULONG BytesNeeded; + ULONG EntriesReturned; + FILE_LINK_ENTRY_INFORMATION Entry; +} FILE_LINKS_INFORMATION, *PFILE_LINKS_INFORMATION; + +typedef struct _FILE_NETWORK_PHYSICAL_NAME_INFORMATION +{ + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NETWORK_PHYSICAL_NAME_INFORMATION, *PFILE_NETWORK_PHYSICAL_NAME_INFORMATION; + +typedef struct _FILE_STANDARD_LINK_INFORMATION +{ + ULONG NumberOfAccessibleLinks; + ULONG TotalNumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} FILE_STANDARD_LINK_INFORMATION, *PFILE_STANDARD_LINK_INFORMATION; + +typedef struct _FILE_SFIO_RESERVE_INFORMATION +{ + ULONG RequestsPerPeriod; + ULONG Period; + BOOLEAN RetryFailures; + BOOLEAN Discardable; + ULONG RequestSize; + ULONG NumOutstandingRequests; +} FILE_SFIO_RESERVE_INFORMATION, *PFILE_SFIO_RESERVE_INFORMATION; + +typedef struct _FILE_SFIO_VOLUME_INFORMATION +{ + ULONG MaximumRequestsPerPeriod; + ULONG MinimumPeriod; + ULONG MinimumTransferSize; +} FILE_SFIO_VOLUME_INFORMATION, *PFILE_SFIO_VOLUME_INFORMATION; + +typedef enum _IO_PRIORITY_HINT +{ + IoPriorityVeryLow = 0, // Defragging, content indexing and other background I/Os. + IoPriorityLow, // Prefetching for applications. + IoPriorityNormal, // Normal I/Os. + IoPriorityHigh, // Used by filesystems for checkpoint I/O. + IoPriorityCritical, // Used by memory manager. Not available for applications. + MaxIoPriorityTypes +} IO_PRIORITY_HINT; + +typedef struct _FILE_IO_PRIORITY_HINT_INFORMATION +{ + IO_PRIORITY_HINT PriorityHint; +} FILE_IO_PRIORITY_HINT_INFORMATION, *PFILE_IO_PRIORITY_HINT_INFORMATION; + +typedef struct _FILE_IO_PRIORITY_HINT_INFORMATION_EX +{ + IO_PRIORITY_HINT PriorityHint; + BOOLEAN BoostOutstanding; +} FILE_IO_PRIORITY_HINT_INFORMATION_EX, *PFILE_IO_PRIORITY_HINT_INFORMATION_EX; + +#define FILE_SKIP_COMPLETION_PORT_ON_SUCCESS 0x1 +#define FILE_SKIP_SET_EVENT_ON_HANDLE 0x2 +#define FILE_SKIP_SET_USER_EVENT_ON_FAST_IO 0x4 + +typedef struct _FILE_IO_COMPLETION_NOTIFICATION_INFORMATION +{ + ULONG Flags; +} FILE_IO_COMPLETION_NOTIFICATION_INFORMATION, *PFILE_IO_COMPLETION_NOTIFICATION_INFORMATION; + +typedef struct _FILE_PROCESS_IDS_USING_FILE_INFORMATION +{ + ULONG NumberOfProcessIdsInList; + ULONG_PTR ProcessIdList[1]; +} FILE_PROCESS_IDS_USING_FILE_INFORMATION, *PFILE_PROCESS_IDS_USING_FILE_INFORMATION; + +typedef struct _FILE_IS_REMOTE_DEVICE_INFORMATION +{ + BOOLEAN IsRemote; +} FILE_IS_REMOTE_DEVICE_INFORMATION, *PFILE_IS_REMOTE_DEVICE_INFORMATION; + +typedef struct _FILE_NUMA_NODE_INFORMATION +{ + USHORT NodeNumber; +} FILE_NUMA_NODE_INFORMATION, *PFILE_NUMA_NODE_INFORMATION; + +typedef struct _FILE_IOSTATUSBLOCK_RANGE_INFORMATION +{ + PUCHAR IoStatusBlockRange; + ULONG Length; +} FILE_IOSTATUSBLOCK_RANGE_INFORMATION, *PFILE_IOSTATUSBLOCK_RANGE_INFORMATION; + +typedef struct _FILE_REMOTE_PROTOCOL_INFORMATION +{ + USHORT StructureVersion; // 1 + USHORT StructureSize; + + ULONG Protocol; // WNNC_NET_* + + USHORT ProtocolMajorVersion; + USHORT ProtocolMinorVersion; + USHORT ProtocolRevision; + + USHORT Reserved; + + // Generic information + + ULONG Flags; + + struct + { + ULONG Reserved[8]; + } GenericReserved; + + // Specific information + +#if (PHNT_VERSION < PHNT_WIN8) + struct + { + ULONG Reserved[16]; + } ProtocolSpecificReserved; +#else + union + { + struct + { + struct + { + ULONG Capabilities; + } Server; + struct + { + ULONG Capabilities; + ULONG CachingFlags; + } Share; + } Smb2; + ULONG Reserved[16]; + } ProtocolSpecific; +#endif +} FILE_REMOTE_PROTOCOL_INFORMATION, *PFILE_REMOTE_PROTOCOL_INFORMATION; + +#define CHECKSUM_ENFORCEMENT_OFF 0x00000001 + +typedef struct _FILE_INTEGRITY_STREAM_INFORMATION +{ + USHORT ChecksumAlgorithm; + UCHAR ChecksumChunkShift; + UCHAR ClusterShift; + ULONG Flags; +} FILE_INTEGRITY_STREAM_INFORMATION, *PFILE_INTEGRITY_STREAM_INFORMATION; + +// private +typedef struct _FILE_VOLUME_NAME_INFORMATION +{ + ULONG DeviceNameLength; + WCHAR DeviceName[1]; +} FILE_VOLUME_NAME_INFORMATION, *PFILE_VOLUME_NAME_INFORMATION; + +// NtQueryDirectoryFile types + +typedef struct _FILE_DIRECTORY_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; + +typedef struct _FILE_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + WCHAR FileName[1]; +} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION; + +typedef struct _FILE_ID_FULL_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; + +typedef struct _FILE_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; + +typedef struct _FILE_ID_BOTH_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION; + +typedef struct _FILE_NAMES_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; + +typedef struct _FILE_ID_GLOBAL_TX_DIR_INFORMATION +{ + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + LARGE_INTEGER FileId; + GUID LockingTransactionId; + ULONG TxInfoFlags; + WCHAR FileName[1]; +} FILE_ID_GLOBAL_TX_DIR_INFORMATION, *PFILE_ID_GLOBAL_TX_DIR_INFORMATION; + +#define FILE_ID_GLOBAL_TX_DIR_INFO_FLAG_WRITELOCKED 0x00000001 +#define FILE_ID_GLOBAL_TX_DIR_INFO_FLAG_VISIBLE_TO_TX 0x00000002 +#define FILE_ID_GLOBAL_TX_DIR_INFO_FLAG_VISIBLE_OUTSIDE_TX 0x00000004 + +typedef struct _FILE_OBJECTID_INFORMATION +{ + LONGLONG FileReference; + UCHAR ObjectId[16]; + union + { + struct + { + UCHAR BirthVolumeId[16]; + UCHAR BirthObjectId[16]; + UCHAR DomainId[16]; + }; + UCHAR ExtendedInfo[48]; + }; +} FILE_OBJECTID_INFORMATION, *PFILE_OBJECTID_INFORMATION; + +// NtQueryEaFile/NtSetEaFile types + +typedef struct _FILE_FULL_EA_INFORMATION +{ + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + CHAR EaName[1]; +} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; + +typedef struct _FILE_GET_EA_INFORMATION +{ + ULONG NextEntryOffset; + UCHAR EaNameLength; + CHAR EaName[1]; +} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION; + +// NtQueryQuotaInformationFile/NtSetQuotaInformationFile types + +typedef struct _FILE_GET_QUOTA_INFORMATION +{ + ULONG NextEntryOffset; + ULONG SidLength; + SID Sid; +} FILE_GET_QUOTA_INFORMATION, *PFILE_GET_QUOTA_INFORMATION; + +typedef struct _FILE_QUOTA_INFORMATION +{ + ULONG NextEntryOffset; + ULONG SidLength; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER QuotaUsed; + LARGE_INTEGER QuotaThreshold; + LARGE_INTEGER QuotaLimit; + SID Sid; +} FILE_QUOTA_INFORMATION, *PFILE_QUOTA_INFORMATION; + +typedef enum _FSINFOCLASS +{ + FileFsVolumeInformation = 1, + FileFsLabelInformation, + FileFsSizeInformation, + FileFsDeviceInformation, + FileFsAttributeInformation, + FileFsControlInformation, + FileFsFullSizeInformation, + FileFsObjectIdInformation, + FileFsDriverPathInformation, + FileFsVolumeFlagsInformation, + FileFsSectorSizeInformation, // since WIN8 + FileFsDataCopyInformation, + FileFsMetadataSizeInformation, // since THRESHOLD + FileFsMaximumInformation +} FSINFOCLASS, *PFSINFOCLASS; + +// NtQueryVolumeInformation/NtSetVolumeInformation types + +typedef struct _FILE_FS_LABEL_INFORMATION +{ + ULONG VolumeLabelLength; + WCHAR VolumeLabel[1]; +} FILE_FS_LABEL_INFORMATION, *PFILE_FS_LABEL_INFORMATION; + +typedef struct _FILE_FS_VOLUME_INFORMATION +{ + LARGE_INTEGER VolumeCreationTime; + ULONG VolumeSerialNumber; + ULONG VolumeLabelLength; + BOOLEAN SupportsObjects; + WCHAR VolumeLabel[1]; +} FILE_FS_VOLUME_INFORMATION, *PFILE_FS_VOLUME_INFORMATION; + +typedef struct _FILE_FS_SIZE_INFORMATION +{ + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER AvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION; + +typedef struct _FILE_FS_FULL_SIZE_INFORMATION +{ + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER CallerAvailableAllocationUnits; + LARGE_INTEGER ActualAvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_FULL_SIZE_INFORMATION, *PFILE_FS_FULL_SIZE_INFORMATION; + +typedef struct _FILE_FS_OBJECTID_INFORMATION +{ + UCHAR ObjectId[16]; + UCHAR ExtendedInfo[48]; +} FILE_FS_OBJECTID_INFORMATION, *PFILE_FS_OBJECTID_INFORMATION; + +typedef struct _FILE_FS_DEVICE_INFORMATION +{ + DEVICE_TYPE DeviceType; + ULONG Characteristics; +} FILE_FS_DEVICE_INFORMATION, *PFILE_FS_DEVICE_INFORMATION; + +typedef struct _FILE_FS_ATTRIBUTE_INFORMATION +{ + ULONG FileSystemAttributes; + LONG MaximumComponentNameLength; + ULONG FileSystemNameLength; + WCHAR FileSystemName[1]; +} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION; + +typedef struct _FILE_FS_DRIVER_PATH_INFORMATION +{ + BOOLEAN DriverInPath; + ULONG DriverNameLength; + WCHAR DriverName[1]; +} FILE_FS_DRIVER_PATH_INFORMATION, *PFILE_FS_DRIVER_PATH_INFORMATION; + +typedef struct _FILE_FS_VOLUME_FLAGS_INFORMATION +{ + ULONG Flags; +} FILE_FS_VOLUME_FLAGS_INFORMATION, *PFILE_FS_VOLUME_FLAGS_INFORMATION; + +#define SSINFO_FLAGS_ALIGNED_DEVICE 0x00000001 +#define SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE 0x00000002 + +// If set for Sector and Partition fields, alignment is not known. +#define SSINFO_OFFSET_UNKNOWN 0xffffffff + +typedef struct _FILE_FS_SECTOR_SIZE_INFORMATION +{ + ULONG LogicalBytesPerSector; + ULONG PhysicalBytesPerSectorForAtomicity; + ULONG PhysicalBytesPerSectorForPerformance; + ULONG FileSystemEffectivePhysicalBytesPerSectorForAtomicity; + ULONG Flags; + ULONG ByteOffsetForSectorAlignment; + ULONG ByteOffsetForPartitionAlignment; +} FILE_FS_SECTOR_SIZE_INFORMATION, *PFILE_FS_SECTOR_SIZE_INFORMATION; + +typedef struct _FILE_FS_METADATA_SIZE_INFORMATION +{ + LARGE_INTEGER TotalMetadataAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_METADATA_SIZE_INFORMATION, *PFILE_FS_METADATA_SIZE_INFORMATION; + +// System calls + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateFile( + _Out_ PHANDLE FileHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_opt_ PLARGE_INTEGER AllocationSize, + _In_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _In_reads_bytes_opt_(EaLength) PVOID EaBuffer, + _In_ ULONG EaLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateNamedPipeFile( + _Out_ PHANDLE FileHandle, + _In_ ULONG DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _In_ ULONG NamedPipeType, + _In_ ULONG ReadMode, + _In_ ULONG CompletionMode, + _In_ ULONG MaximumInstances, + _In_ ULONG InboundQuota, + _In_ ULONG OutboundQuota, + _In_opt_ PLARGE_INTEGER DefaultTimeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateMailslotFile( + _Out_ PHANDLE FileHandle, + _In_ ULONG DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CreateOptions, + _In_ ULONG MailslotQuota, + _In_ ULONG MaximumMessageSize, + _In_ PLARGE_INTEGER ReadTimeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenFile( + _Out_ PHANDLE FileHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG ShareAccess, + _In_ ULONG OpenOptions + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushBuffersFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +#define FLUSH_FLAGS_FILE_DATA_ONLY 0x00000001 +#define FLUSH_FLAGS_NO_SYNC 0x00000002 + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushBuffersFileEx( + _In_ HANDLE FileHandle, + _In_ ULONG Flags, + _In_reads_bytes_(ParametersSize) PVOID Parameters, + _In_ ULONG ParametersSize, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass, + _In_ BOOLEAN ReturnSingleEntry, + _In_opt_ PUNICODE_STRING FileName, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_reads_bytes_opt_(EaListLength) PVOID EaList, + _In_ ULONG EaListLength, + _In_opt_ PULONG EaIndex, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryQuotaInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_reads_bytes_opt_(SidListLength) PVOID SidList, + _In_ ULONG SidListLength, + _In_opt_ PSID StartSid, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetQuotaInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryVolumeInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FsInformation, + _In_ ULONG Length, + _In_ FSINFOCLASS FsInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetVolumeInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID FsInformation, + _In_ ULONG Length, + _In_ FSINFOCLASS FsInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelIoFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelIoFileEx( + _In_ HANDLE FileHandle, + _In_opt_ PIO_STATUS_BLOCK IoRequestToCancel, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelSynchronousIoFile( + _In_ HANDLE ThreadHandle, + _In_opt_ PIO_STATUS_BLOCK IoRequestToCancel, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeviceIoControlFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG IoControlCode, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFsControlFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG FsControlCode, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWriteFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadFileScatter( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PFILE_SEGMENT_ELEMENT SegmentArray, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWriteFileGather( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PFILE_SEGMENT_ELEMENT SegmentArray, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLockFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PLARGE_INTEGER ByteOffset, + _In_ PLARGE_INTEGER Length, + _In_ ULONG Key, + _In_ BOOLEAN FailImmediately, + _In_ BOOLEAN ExclusiveLock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnlockFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PLARGE_INTEGER ByteOffset, + _In_ PLARGE_INTEGER Length, + _In_ ULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryAttributesFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PFILE_BASIC_INFORMATION FileInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryFullAttributesFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PFILE_NETWORK_OPEN_INFORMATION FileInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtNotifyChangeDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, // FILE_NOTIFY_INFORMATION + _In_ ULONG Length, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLoadDriver( + _In_ PUNICODE_STRING DriverServiceName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnloadDriver( + _In_ PUNICODE_STRING DriverServiceName + ); + +// I/O completion port + +#ifndef IO_COMPLETION_QUERY_STATE +#define IO_COMPLETION_QUERY_STATE 0x0001 +#endif + +typedef enum _IO_COMPLETION_INFORMATION_CLASS +{ + IoCompletionBasicInformation +} IO_COMPLETION_INFORMATION_CLASS; + +typedef struct _IO_COMPLETION_BASIC_INFORMATION +{ + LONG Depth; +} IO_COMPLETION_BASIC_INFORMATION, *PIO_COMPLETION_BASIC_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateIoCompletion( + _Out_ PHANDLE IoCompletionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenIoCompletion( + _Out_ PHANDLE IoCompletionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryIoCompletion( + _In_ HANDLE IoCompletionHandle, + _In_ IO_COMPLETION_INFORMATION_CLASS IoCompletionInformationClass, + _Out_writes_bytes_(IoCompletionInformation) PVOID IoCompletionInformation, + _In_ ULONG IoCompletionInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetIoCompletion( + _In_ HANDLE IoCompletionHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetIoCompletionEx( + _In_ HANDLE IoCompletionHandle, + _In_ HANDLE IoCompletionPacketHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRemoveIoCompletion( + _In_ HANDLE IoCompletionHandle, + _Out_ PVOID *KeyContext, + _Out_ PVOID *ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRemoveIoCompletionEx( + _In_ HANDLE IoCompletionHandle, + _Out_writes_to_(Count, *NumEntriesRemoved) PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation, + _In_ ULONG Count, + _Out_ PULONG NumEntriesRemoved, + _In_opt_ PLARGE_INTEGER Timeout, + _In_ BOOLEAN Alertable + ); +#endif + +// Wait completion packet + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateWaitCompletionPacket( + _Out_ PHANDLE WaitCompletionPacketHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAssociateWaitCompletionPacket( + _In_ HANDLE WaitCompletionPacketHandle, + _In_ HANDLE IoCompletionHandle, + _In_ HANDLE TargetObjectHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation, + _Out_opt_ PBOOLEAN AlreadySignaled + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCancelWaitCompletionPacket( + _In_ HANDLE WaitCompletionPacketHandle, + _In_ BOOLEAN RemoveSignaledPacket + ); + +#endif + +// Sessions + +typedef enum _IO_SESSION_EVENT +{ + IoSessionEventIgnore, + IoSessionEventCreated, + IoSessionEventTerminated, + IoSessionEventConnected, + IoSessionEventDisconnected, + IoSessionEventLogon, + IoSessionEventLogoff, + IoSessionEventMax +} IO_SESSION_EVENT; + +typedef enum _IO_SESSION_STATE +{ + IoSessionStateCreated, + IoSessionStateInitialized, + IoSessionStateConnected, + IoSessionStateDisconnected, + IoSessionStateDisconnectedLoggedOn, + IoSessionStateLoggedOn, + IoSessionStateLoggedOff, + IoSessionStateTerminated, + IoSessionStateMax +} IO_SESSION_STATE; + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtNotifyChangeSession( + _In_ HANDLE SessionHandle, + _In_ ULONG ChangeSequenceNumber, + _In_ PLARGE_INTEGER ChangeTimeStamp, + _In_ IO_SESSION_EVENT Event, + _In_ IO_SESSION_STATE NewState, + _In_ IO_SESSION_STATE PreviousState, + _In_reads_bytes_opt_(PayloadSize) PVOID Payload, + _In_ ULONG PayloadSize + ); +#endif + +// Other types + +typedef enum _INTERFACE_TYPE +{ + InterfaceTypeUndefined = -1, + Internal, + Isa, + Eisa, + MicroChannel, + TurboChannel, + PCIBus, + VMEBus, + NuBus, + PCMCIABus, + CBus, + MPIBus, + MPSABus, + ProcessorInternal, + InternalPowerBus, + PNPISABus, + PNPBus, + Vmcs, + MaximumInterfaceType +} INTERFACE_TYPE, *PINTERFACE_TYPE; + +typedef enum _DMA_WIDTH +{ + Width8Bits, + Width16Bits, + Width32Bits, + MaximumDmaWidth +} DMA_WIDTH, *PDMA_WIDTH; + +typedef enum _DMA_SPEED +{ + Compatible, + TypeA, + TypeB, + TypeC, + TypeF, + MaximumDmaSpeed +} DMA_SPEED, *PDMA_SPEED; + +typedef enum _BUS_DATA_TYPE +{ + ConfigurationSpaceUndefined = -1, + Cmos, + EisaConfiguration, + Pos, + CbusConfiguration, + PCIConfiguration, + VMEConfiguration, + NuBusConfiguration, + PCMCIAConfiguration, + MPIConfiguration, + MPSAConfiguration, + PNPISAConfiguration, + SgiInternalConfiguration, + MaximumBusDataType +} BUS_DATA_TYPE, *PBUS_DATA_TYPE; + +// Control structures + +// Reparse structure for FSCTL_SET_REPARSE_POINT, FSCTL_GET_REPARSE_POINT, FSCTL_DELETE_REPARSE_POINT + +#define SYMLINK_FLAG_RELATIVE 1 + +typedef struct _REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +// Named pipe FS control definitions + +#define FSCTL_PIPE_ASSIGN_EVENT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 0, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_DISCONNECT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 1, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_LISTEN CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_PEEK CTL_CODE(FILE_DEVICE_NAMED_PIPE, 3, METHOD_BUFFERED, FILE_READ_DATA) +#define FSCTL_PIPE_QUERY_EVENT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 4, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_TRANSCEIVE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 5, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) +#define FSCTL_PIPE_WAIT CTL_CODE(FILE_DEVICE_NAMED_PIPE, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_IMPERSONATE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 7, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_SET_CLIENT_PROCESS CTL_CODE(FILE_DEVICE_NAMED_PIPE, 8, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_QUERY_CLIENT_PROCESS CTL_CODE(FILE_DEVICE_NAMED_PIPE, 9, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_GET_PIPE_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 10, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_SET_PIPE_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 11, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_GET_CONNECTION_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 12, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_SET_CONNECTION_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 13, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_GET_HANDLE_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 14, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_SET_HANDLE_ATTRIBUTE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 15, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define FSCTL_PIPE_FLUSH CTL_CODE(FILE_DEVICE_NAMED_PIPE, 16, METHOD_BUFFERED, FILE_WRITE_DATA) + +#define FSCTL_PIPE_INTERNAL_READ CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2045, METHOD_BUFFERED, FILE_READ_DATA) +#define FSCTL_PIPE_INTERNAL_WRITE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2046, METHOD_BUFFERED, FILE_WRITE_DATA) +#define FSCTL_PIPE_INTERNAL_TRANSCEIVE CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2047, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA) +#define FSCTL_PIPE_INTERNAL_READ_OVFLOW CTL_CODE(FILE_DEVICE_NAMED_PIPE, 2048, METHOD_BUFFERED, FILE_READ_DATA) + +// Flags for query event + +#define FILE_PIPE_READ_DATA 0x00000000 +#define FILE_PIPE_WRITE_SPACE 0x00000001 + +// Input for FSCTL_PIPE_ASSIGN_EVENT +typedef struct _FILE_PIPE_ASSIGN_EVENT_BUFFER +{ + HANDLE EventHandle; + ULONG KeyValue; +} FILE_PIPE_ASSIGN_EVENT_BUFFER, *PFILE_PIPE_ASSIGN_EVENT_BUFFER; + +// Output for FILE_PIPE_PEEK_BUFFER +typedef struct _FILE_PIPE_PEEK_BUFFER +{ + ULONG NamedPipeState; + ULONG ReadDataAvailable; + ULONG NumberOfMessages; + ULONG MessageLength; + CHAR Data[1]; +} FILE_PIPE_PEEK_BUFFER, *PFILE_PIPE_PEEK_BUFFER; + +// Output for FSCTL_PIPE_QUERY_EVENT +typedef struct _FILE_PIPE_EVENT_BUFFER +{ + ULONG NamedPipeState; + ULONG EntryType; + ULONG ByteCount; + ULONG KeyValue; + ULONG NumberRequests; +} FILE_PIPE_EVENT_BUFFER, *PFILE_PIPE_EVENT_BUFFER; + +// Input for FSCTL_PIPE_WAIT +typedef struct _FILE_PIPE_WAIT_FOR_BUFFER +{ + LARGE_INTEGER Timeout; + ULONG NameLength; + BOOLEAN TimeoutSpecified; + WCHAR Name[1]; +} FILE_PIPE_WAIT_FOR_BUFFER, *PFILE_PIPE_WAIT_FOR_BUFFER; + +// Input for FSCTL_PIPE_SET_CLIENT_PROCESS, Output for FSCTL_PIPE_QUERY_CLIENT_PROCESS +typedef struct _FILE_PIPE_CLIENT_PROCESS_BUFFER +{ +#if !defined(BUILD_WOW6432) + PVOID ClientSession; + PVOID ClientProcess; +#else + ULONGLONG ClientSession; + ULONGLONG ClientProcess; +#endif +} FILE_PIPE_CLIENT_PROCESS_BUFFER, *PFILE_PIPE_CLIENT_PROCESS_BUFFER; + +#define FILE_PIPE_COMPUTER_NAME_LENGTH 15 + +// Input for FSCTL_PIPE_SET_CLIENT_PROCESS, Output for FSCTL_PIPE_QUERY_CLIENT_PROCESS +typedef struct _FILE_PIPE_CLIENT_PROCESS_BUFFER_EX +{ +#if !defined(BUILD_WOW6432) + PVOID ClientSession; + PVOID ClientProcess; +#else + ULONGLONG ClientSession; + ULONGLONG ClientProcess; +#endif + USHORT ClientComputerNameLength; // in bytes + WCHAR ClientComputerBuffer[FILE_PIPE_COMPUTER_NAME_LENGTH + 1]; // null-terminated +} FILE_PIPE_CLIENT_PROCESS_BUFFER_EX, *PFILE_PIPE_CLIENT_PROCESS_BUFFER_EX; + +// Mailslot FS control definitions + +#define MAILSLOT_CLASS_FIRSTCLASS 1 +#define MAILSLOT_CLASS_SECONDCLASS 2 + +#define FSCTL_MAILSLOT_PEEK CTL_CODE(FILE_DEVICE_MAILSLOT, 0, METHOD_NEITHER, FILE_READ_DATA) + +// Output for FSCTL_MAILSLOT_PEEK +typedef struct _FILE_MAILSLOT_PEEK_BUFFER +{ + ULONG ReadDataAvailable; + ULONG NumberOfMessages; + ULONG MessageLength; +} FILE_MAILSLOT_PEEK_BUFFER, *PFILE_MAILSLOT_PEEK_BUFFER; + +#endif diff --git a/phnt/include/ntkeapi.h b/phnt/include/ntkeapi.h new file mode 100644 index 0000000..6bd07f8 --- /dev/null +++ b/phnt/include/ntkeapi.h @@ -0,0 +1,165 @@ +#ifndef _NTKEAPI_H +#define _NTKEAPI_H + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#define LOW_PRIORITY 0 // Lowest thread priority level +#define LOW_REALTIME_PRIORITY 16 // Lowest realtime priority level +#define HIGH_PRIORITY 31 // Highest thread priority level +#define MAXIMUM_PRIORITY 32 // Number of thread priority levels +#endif + +typedef enum _KTHREAD_STATE +{ + Initialized, + Ready, + Running, + Standby, + Terminated, + Waiting, + Transition, + DeferredReady, + GateWaitObsolete, + WaitingForProcessInSwap, + MaximumThreadState +} KTHREAD_STATE, *PKTHREAD_STATE; + +// private +typedef enum _KHETERO_CPU_POLICY +{ + KHeteroCpuPolicyAll, + KHeteroCpuPolicyLarge, + KHeteroCpuPolicyLargeOrIdle, + KHeteroCpuPolicySmall, + KHeteroCpuPolicySmallOrIdle, + KHeteroCpuPolicyDynamic, + KHeteroCpuPolicyStaticMax, + KHeteroCpuPolicyBiasedSmall, + KHeteroCpuPolicyBiasedLarge, + KHeteroCpuPolicyDefault, + KHeteroCpuPolicyMax +} KHETERO_CPU_POLICY, *PKHETERO_CPU_POLICY; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +typedef enum _KWAIT_REASON +{ + Executive, + FreePage, + PageIn, + PoolAllocation, + DelayExecution, + Suspended, + UserRequest, + WrExecutive, + WrFreePage, + WrPageIn, + WrPoolAllocation, + WrDelayExecution, + WrSuspended, + WrUserRequest, + WrEventPair, + WrQueue, + WrLpcReceive, + WrLpcReply, + WrVirtualMemory, + WrPageOut, + WrRendezvous, + WrKeyedEvent, + WrTerminated, + WrProcessInSwap, + WrCpuRateControl, + WrCalloutStack, + WrKernel, + WrResource, + WrPushLock, + WrMutex, + WrQuantumEnd, + WrDispatchInt, + WrPreempted, + WrYieldExecution, + WrFastMutex, + WrGuardedMutex, + WrRundown, + WrAlertByThreadId, + WrDeferredPreempt, + MaximumWaitReason +} KWAIT_REASON, *PKWAIT_REASON; + +typedef enum _KPROFILE_SOURCE +{ + ProfileTime, + ProfileAlignmentFixup, + ProfileTotalIssues, + ProfilePipelineDry, + ProfileLoadInstructions, + ProfilePipelineFrozen, + ProfileBranchInstructions, + ProfileTotalNonissues, + ProfileDcacheMisses, + ProfileIcacheMisses, + ProfileCacheMisses, + ProfileBranchMispredictions, + ProfileStoreInstructions, + ProfileFpInstructions, + ProfileIntegerInstructions, + Profile2Issue, + Profile3Issue, + Profile4Issue, + ProfileSpecialInstructions, + ProfileTotalCycles, + ProfileIcacheIssues, + ProfileDcacheAccesses, + ProfileMemoryBarrierCycles, + ProfileLoadLinkedIssues, + ProfileMaximum +} KPROFILE_SOURCE; + +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCallbackReturn( + _In_reads_bytes_opt_(OutputLength) PVOID OutputBuffer, + _In_ ULONG OutputLength, + _In_ NTSTATUS Status + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +VOID +NTAPI +NtFlushProcessWriteBuffers( + VOID + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_ BOOLEAN State + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtYieldExecution( + VOID + ); + +#endif + +#endif diff --git a/phnt/include/ntldr.h b/phnt/include/ntldr.h new file mode 100644 index 0000000..288bf9f --- /dev/null +++ b/phnt/include/ntldr.h @@ -0,0 +1,541 @@ +#ifndef _NTLDR_H +#define _NTLDR_H + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +// DLLs + +// symbols +typedef struct _LDR_SERVICE_TAG_RECORD +{ + struct _LDR_SERVICE_TAG_RECORD *Next; + ULONG ServiceTag; +} LDR_SERVICE_TAG_RECORD, *PLDR_SERVICE_TAG_RECORD; + +// symbols +typedef struct _LDRP_CSLIST +{ + PSINGLE_LIST_ENTRY Tail; +} LDRP_CSLIST, *PLDRP_CSLIST; + +// symbols +typedef enum _LDR_DDAG_STATE +{ + LdrModulesMerged = -5, + LdrModulesInitError = -4, + LdrModulesSnapError = -3, + LdrModulesUnloaded = -2, + LdrModulesUnloading = -1, + LdrModulesPlaceHolder = 0, + LdrModulesMapping = 1, + LdrModulesMapped = 2, + LdrModulesWaitingForDependencies = 3, + LdrModulesSnapping = 4, + LdrModulesSnapped = 5, + LdrModulesCondensed = 6, + LdrModulesReadyToInit = 7, + LdrModulesInitializing = 8, + LdrModulesReadyToRun = 9 +} LDR_DDAG_STATE; + +// symbols +typedef struct _LDR_DDAG_NODE +{ + LIST_ENTRY Modules; + PLDR_SERVICE_TAG_RECORD ServiceTagList; + ULONG LoadCount; + ULONG ReferenceCount; + ULONG DependencyCount; + union + { + LDRP_CSLIST Dependencies; + SINGLE_LIST_ENTRY RemovalLink; + }; + LDRP_CSLIST IncomingDependencies; + LDR_DDAG_STATE State; + SINGLE_LIST_ENTRY CondenseLink; + ULONG PreorderNumber; + ULONG LowestLink; +} LDR_DDAG_NODE, *PLDR_DDAG_NODE; + +// rev +typedef struct _LDR_DEPENDENCY_RECORD +{ + SINGLE_LIST_ENTRY DependencyLink; + PLDR_DDAG_NODE DependencyNode; + SINGLE_LIST_ENTRY IncomingDependencyLink; + PLDR_DDAG_NODE IncomingDependencyNode; +} LDR_DEPENDENCY_RECORD, *PLDR_DEPENDENCY_RECORD; + +// symbols +typedef enum _LDR_DLL_LOAD_REASON +{ + LoadReasonStaticDependency, + LoadReasonStaticForwarderDependency, + LoadReasonDynamicForwarderDependency, + LoadReasonDelayloadDependency, + LoadReasonDynamicLoad, + LoadReasonAsImageLoad, + LoadReasonAsDataLoad, + LoadReasonUnknown = -1 +} LDR_DLL_LOAD_REASON, *PLDR_DLL_LOAD_REASON; + +#define LDRP_PACKAGED_BINARY 0x00000001 +#define LDRP_IMAGE_DLL 0x00000004 +#define LDRP_LOAD_IN_PROGRESS 0x00001000 +#define LDRP_ENTRY_PROCESSED 0x00004000 +#define LDRP_DONT_CALL_FOR_THREADS 0x00040000 +#define LDRP_PROCESS_ATTACH_CALLED 0x00080000 +#define LDRP_PROCESS_ATTACH_FAILED 0x00100000 +#define LDRP_IMAGE_NOT_AT_BASE 0x00200000 // Vista and below +#define LDRP_COR_IMAGE 0x00400000 +#define LDRP_DONT_RELOCATE 0x00800000 +#define LDRP_REDIRECTED 0x10000000 +#define LDRP_COMPAT_DATABASE_PROCESSED 0x80000000 + +// Use the size of the structure as it was in Windows XP. +#define LDR_DATA_TABLE_ENTRY_SIZE_WINXP FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, DdagNode) +#define LDR_DATA_TABLE_ENTRY_SIZE_WIN7 FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, BaseNameHashValue) +#define LDR_DATA_TABLE_ENTRY_SIZE_WIN8 FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, ImplicitPathOptions) + +// symbols +typedef struct _LDR_DATA_TABLE_ENTRY +{ + LIST_ENTRY InLoadOrderLinks; + LIST_ENTRY InMemoryOrderLinks; + union + { + LIST_ENTRY InInitializationOrderLinks; + LIST_ENTRY InProgressLinks; + }; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + union + { + UCHAR FlagGroup[4]; + ULONG Flags; + struct + { + ULONG PackagedBinary : 1; + ULONG MarkedForRemoval : 1; + ULONG ImageDll : 1; + ULONG LoadNotificationsSent : 1; + ULONG TelemetryEntryProcessed : 1; + ULONG ProcessStaticImport : 1; + ULONG InLegacyLists : 1; + ULONG InIndexes : 1; + ULONG ShimDll : 1; + ULONG InExceptionTable : 1; + ULONG ReservedFlags1 : 2; + ULONG LoadInProgress : 1; + ULONG LoadConfigProcessed : 1; + ULONG EntryProcessed : 1; + ULONG ProtectDelayLoad : 1; + ULONG ReservedFlags3 : 2; + ULONG DontCallForThreads : 1; + ULONG ProcessAttachCalled : 1; + ULONG ProcessAttachFailed : 1; + ULONG CorDeferredValidate : 1; + ULONG CorImage : 1; + ULONG DontRelocate : 1; + ULONG CorILOnly : 1; + ULONG ReservedFlags5 : 3; + ULONG Redirected : 1; + ULONG ReservedFlags6 : 2; + ULONG CompatDatabaseProcessed : 1; + }; + }; + USHORT ObsoleteLoadCount; + USHORT TlsIndex; + LIST_ENTRY HashLinks; + ULONG TimeDateStamp; + struct _ACTIVATION_CONTEXT *EntryPointActivationContext; + PVOID Lock; + PLDR_DDAG_NODE DdagNode; + LIST_ENTRY NodeModuleLink; + struct _LDRP_LOAD_CONTEXT *LoadContext; + PVOID ParentDllBase; + PVOID SwitchBackContext; + RTL_BALANCED_NODE BaseAddressIndexNode; + RTL_BALANCED_NODE MappingInfoIndexNode; + ULONG_PTR OriginalBase; + LARGE_INTEGER LoadTime; + ULONG BaseNameHashValue; + LDR_DLL_LOAD_REASON LoadReason; + ULONG ImplicitPathOptions; + ULONG ReferenceCount; +} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; + +typedef BOOLEAN (NTAPI *PDLL_INIT_ROUTINE)( + _In_ PVOID DllHandle, + _In_ ULONG Reason, + _In_opt_ PCONTEXT Context + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrLoadDll( + _In_opt_ PWSTR DllPath, + _In_opt_ PULONG DllCharacteristics, + _In_ PUNICODE_STRING DllName, + _Out_ PVOID *DllHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrUnloadDll( + _In_ PVOID DllHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrGetDllHandle( + _In_opt_ PWSTR DllPath, + _In_opt_ PULONG DllCharacteristics, + _In_ PUNICODE_STRING DllName, + _Out_ PVOID *DllHandle + ); + +#define LDR_GET_DLL_HANDLE_EX_UNCHANGED_REFCOUNT 0x00000001 +#define LDR_GET_DLL_HANDLE_EX_PIN 0x00000002 + +NTSYSAPI +NTSTATUS +NTAPI +LdrGetDllHandleEx( + _In_ ULONG Flags, + _In_opt_ PCWSTR DllPath, + _In_opt_ PULONG DllCharacteristics, + _In_ PUNICODE_STRING DllName, + _Out_opt_ PVOID *DllHandle + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +LdrGetDllHandleByMapping( + _In_ PVOID Base, + _Out_ PVOID *DllHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +LdrGetDllHandleByName( + _In_opt_ PUNICODE_STRING BaseDllName, + _In_opt_ PUNICODE_STRING FullDllName, + _Out_ PVOID *DllHandle + ); +#endif + +#define LDR_ADDREF_DLL_PIN 0x00000001 + +NTSYSAPI +NTSTATUS +NTAPI +LdrAddRefDll( + _In_ ULONG Flags, + _In_ PVOID DllHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrGetProcedureAddress( + _In_ PVOID DllHandle, + _In_opt_ PANSI_STRING ProcedureName, + _In_opt_ ULONG ProcedureNumber, + _Out_ PVOID *ProcedureAddress + ); + +// rev +#define LDR_GET_PROCEDURE_ADDRESS_DONT_RECORD_FORWARDER 0x00000001 + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrGetProcedureAddressEx( + _In_ PVOID DllHandle, + _In_opt_ PANSI_STRING ProcedureName, + _In_opt_ ULONG ProcedureNumber, + _Out_ PVOID *ProcedureAddress, + _In_ ULONG Flags + ); +#endif + +#define LDR_LOCK_LOADER_LOCK_FLAG_RAISE_ON_ERRORS 0x00000001 +#define LDR_LOCK_LOADER_LOCK_FLAG_TRY_ONLY 0x00000002 + +#define LDR_LOCK_LOADER_LOCK_DISPOSITION_INVALID 0 +#define LDR_LOCK_LOADER_LOCK_DISPOSITION_LOCK_ACQUIRED 1 +#define LDR_LOCK_LOADER_LOCK_DISPOSITION_LOCK_NOT_ACQUIRED 2 + +NTSYSAPI +NTSTATUS +NTAPI +LdrLockLoaderLock( + _In_ ULONG Flags, + _Out_opt_ ULONG *Disposition, + _Out_ PVOID *Cookie + ); + +#define LDR_UNLOCK_LOADER_LOCK_FLAG_RAISE_ON_ERRORS 0x00000001 + +NTSYSAPI +NTSTATUS +NTAPI +LdrUnlockLoaderLock( + _In_ ULONG Flags, + _Inout_ PVOID Cookie + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrRelocateImage( + _In_ PVOID NewBase, + _In_ PSTR LoaderName, + _In_ NTSTATUS Success, + _In_ NTSTATUS Conflict, + _In_ NTSTATUS Invalid + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrRelocateImageWithBias( + _In_ PVOID NewBase, + _In_ LONGLONG Bias, + _In_ PSTR LoaderName, + _In_ NTSTATUS Success, + _In_ NTSTATUS Conflict, + _In_ NTSTATUS Invalid + ); + +NTSYSAPI +PIMAGE_BASE_RELOCATION +NTAPI +LdrProcessRelocationBlock( + _In_ ULONG_PTR VA, + _In_ ULONG SizeOfBlock, + _In_ PUSHORT NextOffset, + _In_ LONG_PTR Diff + ); + +NTSYSAPI +BOOLEAN +NTAPI +LdrVerifyMappedImageMatchesChecksum( + _In_ PVOID BaseAddress, + _In_ SIZE_T NumberOfBytes, + _In_ ULONG FileLength + ); + +typedef VOID (NTAPI *PLDR_IMPORT_MODULE_CALLBACK)( + _In_ PVOID Parameter, + _In_ PSTR ModuleName + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrVerifyImageMatchesChecksum( + _In_ HANDLE ImageFileHandle, + _In_opt_ PLDR_IMPORT_MODULE_CALLBACK ImportCallbackRoutine, + _In_ PVOID ImportCallbackParameter, + _Out_opt_ PUSHORT ImageCharacteristics + ); + +// private +typedef struct _LDR_IMPORT_CALLBACK_INFO +{ + PLDR_IMPORT_MODULE_CALLBACK ImportCallbackRoutine; + PVOID ImportCallbackParameter; +} LDR_IMPORT_CALLBACK_INFO, *PLDR_IMPORT_CALLBACK_INFO; + +// private +typedef struct _LDR_SECTION_INFO +{ + HANDLE SectionHandle; + ACCESS_MASK DesiredAccess; + POBJECT_ATTRIBUTES ObjA; + ULONG SectionPageProtection; + ULONG AllocationAttributes; +} LDR_SECTION_INFO, *PLDR_SECTION_INFO; + +// private +typedef struct _LDR_VERIFY_IMAGE_INFO +{ + ULONG Size; + ULONG Flags; + LDR_IMPORT_CALLBACK_INFO CallbackInfo; + LDR_SECTION_INFO SectionInfo; + USHORT ImageCharacteristics; +} LDR_VERIFY_IMAGE_INFO, *PLDR_VERIFY_IMAGE_INFO; + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrVerifyImageMatchesChecksumEx( + _In_ HANDLE ImageFileHandle, + _Inout_ PLDR_VERIFY_IMAGE_INFO VerifyInfo + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrQueryModuleServiceTags( + _In_ PVOID DllHandle, + _Out_writes_(*BufferSize) PULONG ServiceTagBuffer, + _Inout_ PULONG BufferSize + ); +#endif + +// begin_msdn:"DLL Load Notification" + +#define LDR_DLL_NOTIFICATION_REASON_LOADED 1 +#define LDR_DLL_NOTIFICATION_REASON_UNLOADED 2 + +typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA +{ + ULONG Flags; + PUNICODE_STRING FullDllName; + PUNICODE_STRING BaseDllName; + PVOID DllBase; + ULONG SizeOfImage; +} LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; + +typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA +{ + ULONG Flags; + PCUNICODE_STRING FullDllName; + PCUNICODE_STRING BaseDllName; + PVOID DllBase; + ULONG SizeOfImage; +} LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + +typedef union _LDR_DLL_NOTIFICATION_DATA +{ + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; +} LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; + +typedef VOID (NTAPI *PLDR_DLL_NOTIFICATION_FUNCTION)( + _In_ ULONG NotificationReason, + _In_ PLDR_DLL_NOTIFICATION_DATA NotificationData, + _In_opt_ PVOID Context + ); + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSAPI +NTSTATUS +NTAPI +LdrRegisterDllNotification( + _In_ ULONG Flags, + _In_ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, + _In_ PVOID Context, + _Out_ PVOID *Cookie + ); + +NTSYSAPI +NTSTATUS +NTAPI +LdrUnregisterDllNotification( + _In_ PVOID Cookie + ); + +#endif + +// end_msdn + +// Load as data table + +#if (PHNT_VERSION >= PHNT_VISTA) + +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrAddLoadAsDataTable( + _In_ PVOID Module, + _In_ PWSTR FilePath, + _In_ SIZE_T Size, + _In_ HANDLE Handle + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrRemoveLoadAsDataTable( + _In_ PVOID InitModule, + _Out_opt_ PVOID *BaseModule, + _Out_opt_ PSIZE_T Size, + _In_ ULONG Flags + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +LdrGetFileNameFromLoadAsDataTable( + _In_ PVOID Module, + _Out_ PVOID *pFileNamePrt + ); + +#endif + +#endif // (PHNT_MODE != PHNT_MODE_KERNEL) + +// Module information + +typedef struct _RTL_PROCESS_MODULE_INFORMATION +{ + HANDLE Section; + PVOID MappedBase; + PVOID ImageBase; + ULONG ImageSize; + ULONG Flags; + USHORT LoadOrderIndex; + USHORT InitOrderIndex; + USHORT LoadCount; + USHORT OffsetToFileName; + UCHAR FullPathName[256]; +} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION; + +typedef struct _RTL_PROCESS_MODULES +{ + ULONG NumberOfModules; + RTL_PROCESS_MODULE_INFORMATION Modules[1]; +} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; + +// private +typedef struct _RTL_PROCESS_MODULE_INFORMATION_EX +{ + USHORT NextOffset; + RTL_PROCESS_MODULE_INFORMATION BaseInfo; + ULONG ImageChecksum; + ULONG TimeDateStamp; + PVOID DefaultBase; +} RTL_PROCESS_MODULE_INFORMATION_EX, *PRTL_PROCESS_MODULE_INFORMATION_EX; + +#endif diff --git a/phnt/include/ntlpcapi.h b/phnt/include/ntlpcapi.h new file mode 100644 index 0000000..b5df4f3 --- /dev/null +++ b/phnt/include/ntlpcapi.h @@ -0,0 +1,959 @@ +#ifndef _NTLPCAPI_H +#define _NTLPCAPI_H + +// Local Inter-process Communication + +#define PORT_CONNECT 0x0001 +#define PORT_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1) + +typedef struct _PORT_MESSAGE +{ + union + { + struct + { + CSHORT DataLength; + CSHORT TotalLength; + } s1; + ULONG Length; + } u1; + union + { + struct + { + CSHORT Type; + CSHORT DataInfoOffset; + } s2; + ULONG ZeroInit; + } u2; + union + { + CLIENT_ID ClientId; + double DoNotUseThisField; + }; + ULONG MessageId; + union + { + SIZE_T ClientViewSize; // only valid for LPC_CONNECTION_REQUEST messages + ULONG CallbackId; // only valid for LPC_REQUEST messages + }; +} PORT_MESSAGE, *PPORT_MESSAGE; + +typedef struct _PORT_DATA_ENTRY +{ + PVOID Base; + ULONG Size; +} PORT_DATA_ENTRY, *PPORT_DATA_ENTRY; + +typedef struct _PORT_DATA_INFORMATION +{ + ULONG CountDataEntries; + PORT_DATA_ENTRY DataEntries[1]; +} PORT_DATA_INFORMATION, *PPORT_DATA_INFORMATION; + +#define LPC_REQUEST 1 +#define LPC_REPLY 2 +#define LPC_DATAGRAM 3 +#define LPC_LOST_REPLY 4 +#define LPC_PORT_CLOSED 5 +#define LPC_CLIENT_DIED 6 +#define LPC_EXCEPTION 7 +#define LPC_DEBUG_EVENT 8 +#define LPC_ERROR_EVENT 9 +#define LPC_CONNECTION_REQUEST 10 + +#define LPC_KERNELMODE_MESSAGE (CSHORT)0x8000 +#define LPC_NO_IMPERSONATE (CSHORT)0x4000 + +#define PORT_VALID_OBJECT_ATTRIBUTES OBJ_CASE_INSENSITIVE + +#ifdef _WIN64 +#define PORT_MAXIMUM_MESSAGE_LENGTH 512 +#else +#define PORT_MAXIMUM_MESSAGE_LENGTH 256 +#endif + +#define LPC_MAX_CONNECTION_INFO_SIZE (16 * sizeof(ULONG_PTR)) + +#define PORT_TOTAL_MAXIMUM_MESSAGE_LENGTH \ + ((PORT_MAXIMUM_MESSAGE_LENGTH + sizeof(PORT_MESSAGE) + LPC_MAX_CONNECTION_INFO_SIZE + 0xf) & ~0xf) + +typedef struct _LPC_CLIENT_DIED_MSG +{ + PORT_MESSAGE PortMsg; + LARGE_INTEGER CreateTime; +} LPC_CLIENT_DIED_MSG, *PLPC_CLIENT_DIED_MSG; + +typedef struct _PORT_VIEW +{ + ULONG Length; + HANDLE SectionHandle; + ULONG SectionOffset; + SIZE_T ViewSize; + PVOID ViewBase; + PVOID ViewRemoteBase; +} PORT_VIEW, *PPORT_VIEW; + +typedef struct _REMOTE_PORT_VIEW +{ + ULONG Length; + SIZE_T ViewSize; + PVOID ViewBase; +} REMOTE_PORT_VIEW, *PREMOTE_PORT_VIEW; + +// WOW64 definitions + +// Except in a small number of special cases, WOW64 programs using the LPC APIs must use the 64-bit versions of the +// PORT_MESSAGE, PORT_VIEW and REMOTE_PORT_VIEW data structures. Note that we take a different approach than the +// official NT headers, which produce 64-bit versions in a 32-bit environment when USE_LPC6432 is defined. + +typedef struct _PORT_MESSAGE64 +{ + union + { + struct + { + CSHORT DataLength; + CSHORT TotalLength; + } s1; + ULONG Length; + } u1; + union + { + struct + { + CSHORT Type; + CSHORT DataInfoOffset; + } s2; + ULONG ZeroInit; + } u2; + union + { + CLIENT_ID64 ClientId; + double DoNotUseThisField; + }; + ULONG MessageId; + union + { + ULONGLONG ClientViewSize; // only valid for LPC_CONNECTION_REQUEST messages + ULONG CallbackId; // only valid for LPC_REQUEST messages + }; +} PORT_MESSAGE64, *PPORT_MESSAGE64; + +typedef struct _LPC_CLIENT_DIED_MSG64 +{ + PORT_MESSAGE64 PortMsg; + LARGE_INTEGER CreateTime; +} LPC_CLIENT_DIED_MSG64, *PLPC_CLIENT_DIED_MSG64; + +typedef struct _PORT_VIEW64 +{ + ULONG Length; + ULONGLONG SectionHandle; + ULONG SectionOffset; + ULONGLONG ViewSize; + ULONGLONG ViewBase; + ULONGLONG ViewRemoteBase; +} PORT_VIEW64, *PPORT_VIEW64; + +typedef struct _REMOTE_PORT_VIEW64 +{ + ULONG Length; + ULONGLONG ViewSize; + ULONGLONG ViewBase; +} REMOTE_PORT_VIEW64, *PREMOTE_PORT_VIEW64; + +// Port creation + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreatePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG MaxConnectionInfoLength, + _In_ ULONG MaxMessageLength, + _In_opt_ ULONG MaxPoolUsage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateWaitablePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG MaxConnectionInfoLength, + _In_ ULONG MaxMessageLength, + _In_opt_ ULONG MaxPoolUsage + ); + +// Port connection (client) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos, + _Inout_opt_ PPORT_VIEW ClientView, + _Inout_opt_ PREMOTE_PORT_VIEW ServerView, + _Out_opt_ PULONG MaxMessageLength, + _Inout_updates_bytes_to_opt_(*ConnectionInformationLength, *ConnectionInformationLength) PVOID ConnectionInformation, + _Inout_opt_ PULONG ConnectionInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSecureConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos, + _Inout_opt_ PPORT_VIEW ClientView, + _In_opt_ PSID RequiredServerSid, + _Inout_opt_ PREMOTE_PORT_VIEW ServerView, + _Out_opt_ PULONG MaxMessageLength, + _Inout_updates_bytes_to_opt_(*ConnectionInformationLength, *ConnectionInformationLength) PVOID ConnectionInformation, + _Inout_opt_ PULONG ConnectionInformationLength + ); + +// Port connection (server) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtListenPort( + _In_ HANDLE PortHandle, + _Out_ PPORT_MESSAGE ConnectionRequest + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAcceptConnectPort( + _Out_ PHANDLE PortHandle, + _In_opt_ PVOID PortContext, + _In_ PPORT_MESSAGE ConnectionRequest, + _In_ BOOLEAN AcceptConnection, + _Inout_opt_ PPORT_VIEW ServerView, + _Out_opt_ PREMOTE_PORT_VIEW ClientView + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCompleteConnectPort( + _In_ HANDLE PortHandle + ); + +// General + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRequestPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(RequestMessage->u1.s1.TotalLength) PPORT_MESSAGE RequestMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRequestWaitReplyPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(RequestMessage->u1.s1.TotalLength) PPORT_MESSAGE RequestMessage, + _Out_ PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplyPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplyWaitReplyPort( + _In_ HANDLE PortHandle, + _Inout_ PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplyWaitReceivePort( + _In_ HANDLE PortHandle, + _Out_opt_ PVOID *PortContext, + _In_reads_bytes_opt_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage, + _Out_ PPORT_MESSAGE ReceiveMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplyWaitReceivePortEx( + _In_ HANDLE PortHandle, + _Out_opt_ PVOID *PortContext, + _In_reads_bytes_opt_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage, + _Out_ PPORT_MESSAGE ReceiveMessage, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtImpersonateClientOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadRequestData( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG DataEntryIndex, + _Out_writes_bytes_to_(BufferSize, *NumberOfBytesRead) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWriteRequestData( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG DataEntryIndex, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesWritten + ); + +typedef enum _PORT_INFORMATION_CLASS +{ + PortBasicInformation, + PortDumpInformation +} PORT_INFORMATION_CLASS; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationPort( + _In_ HANDLE PortHandle, + _In_ PORT_INFORMATION_CLASS PortInformationClass, + _Out_writes_bytes_to_(Length, *ReturnLength) PVOID PortInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +// Asynchronous Local Inter-process Communication + +// rev +typedef HANDLE ALPC_HANDLE, *PALPC_HANDLE; + +#define ALPC_PORFLG_ALLOW_LPC_REQUESTS 0x20000 // rev +#define ALPC_PORFLG_WAITABLE_PORT 0x40000 // dbg +#define ALPC_PORFLG_SYSTEM_PROCESS 0x100000 // dbg + +// symbols +typedef struct _ALPC_PORT_ATTRIBUTES +{ + ULONG Flags; + SECURITY_QUALITY_OF_SERVICE SecurityQos; + SIZE_T MaxMessageLength; + SIZE_T MemoryBandwidth; + SIZE_T MaxPoolUsage; + SIZE_T MaxSectionSize; + SIZE_T MaxViewSize; + SIZE_T MaxTotalSectionSize; + ULONG DupObjectTypes; +#ifdef _WIN64 + ULONG Reserved; +#endif +} ALPC_PORT_ATTRIBUTES, *PALPC_PORT_ATTRIBUTES; + +// begin_rev +#define ALPC_MESSAGE_SECURITY_ATTRIBUTE 0x80000000 +#define ALPC_MESSAGE_VIEW_ATTRIBUTE 0x40000000 +#define ALPC_MESSAGE_CONTEXT_ATTRIBUTE 0x20000000 +#define ALPC_MESSAGE_HANDLE_ATTRIBUTE 0x10000000 +// end_rev + +// symbols +typedef struct _ALPC_MESSAGE_ATTRIBUTES +{ + ULONG AllocatedAttributes; + ULONG ValidAttributes; +} ALPC_MESSAGE_ATTRIBUTES, *PALPC_MESSAGE_ATTRIBUTES; + +// symbols +typedef struct _ALPC_COMPLETION_LIST_STATE +{ + union + { + struct + { + ULONG64 Head : 24; + ULONG64 Tail : 24; + ULONG64 ActiveThreadCount : 16; + } s1; + ULONG64 Value; + } u1; +} ALPC_COMPLETION_LIST_STATE, *PALPC_COMPLETION_LIST_STATE; + +#define ALPC_COMPLETION_LIST_BUFFER_GRANULARITY_MASK 0x3f // dbg + +// symbols +typedef struct DECLSPEC_ALIGN(128) _ALPC_COMPLETION_LIST_HEADER +{ + ULONG64 StartMagic; + + ULONG TotalSize; + ULONG ListOffset; + ULONG ListSize; + ULONG BitmapOffset; + ULONG BitmapSize; + ULONG DataOffset; + ULONG DataSize; + ULONG AttributeFlags; + ULONG AttributeSize; + + DECLSPEC_ALIGN(128) ALPC_COMPLETION_LIST_STATE State; + ULONG LastMessageId; + ULONG LastCallbackId; + DECLSPEC_ALIGN(128) ULONG PostCount; + DECLSPEC_ALIGN(128) ULONG ReturnCount; + DECLSPEC_ALIGN(128) ULONG LogSequenceNumber; + DECLSPEC_ALIGN(128) RTL_SRWLOCK UserLock; + + ULONG64 EndMagic; +} ALPC_COMPLETION_LIST_HEADER, *PALPC_COMPLETION_LIST_HEADER; + +// private +typedef struct _ALPC_CONTEXT_ATTR +{ + PVOID PortContext; + PVOID MessageContext; + ULONG Sequence; + ULONG MessageId; + ULONG CallbackId; +} ALPC_CONTEXT_ATTR, *PALPC_CONTEXT_ATTR; + +// begin_rev +#define ALPC_HANDLEFLG_DUPLICATE_SAME_ACCESS 0x10000 +#define ALPC_HANDLEFLG_DUPLICATE_SAME_ATTRIBUTES 0x20000 +#define ALPC_HANDLEFLG_DUPLICATE_INHERIT 0x80000 +// end_rev + +// private +typedef struct _ALPC_HANDLE_ATTR +{ + ULONG Flags; + HANDLE Handle; + ULONG ObjectType; // ObjectTypeCode, not ObjectTypeIndex + ACCESS_MASK DesiredAccess; +} ALPC_HANDLE_ATTR, *PALPC_HANDLE_ATTR; + +#define ALPC_SECFLG_CREATE_HANDLE 0x20000 // dbg + +// private +typedef struct _ALPC_SECURITY_ATTR +{ + ULONG Flags; + PSECURITY_QUALITY_OF_SERVICE QoS; + ALPC_HANDLE ContextHandle; // dbg +} ALPC_SECURITY_ATTR, *PALPC_SECURITY_ATTR; + +// begin_rev +#define ALPC_VIEWFLG_NOT_SECURE 0x40000 +// end_rev + +// private +typedef struct _ALPC_DATA_VIEW_ATTR +{ + ULONG Flags; + ALPC_HANDLE SectionHandle; + PVOID ViewBase; // must be zero on input + SIZE_T ViewSize; +} ALPC_DATA_VIEW_ATTR, *PALPC_DATA_VIEW_ATTR; + +// private +typedef enum _ALPC_PORT_INFORMATION_CLASS +{ + AlpcBasicInformation, // q: out ALPC_BASIC_INFORMATION + AlpcPortInformation, // s: in ALPC_PORT_ATTRIBUTES + AlpcAssociateCompletionPortInformation, // s: in ALPC_PORT_ASSOCIATE_COMPLETION_PORT + AlpcConnectedSIDInformation, // q: in SID + AlpcServerInformation, // q: inout ALPC_SERVER_INFORMATION + AlpcMessageZoneInformation, // s: in ALPC_PORT_MESSAGE_ZONE_INFORMATION + AlpcRegisterCompletionListInformation, // s: in ALPC_PORT_COMPLETION_LIST_INFORMATION + AlpcUnregisterCompletionListInformation, // s: VOID + AlpcAdjustCompletionListConcurrencyCountInformation, // s: in ULONG + AlpcRegisterCallbackInformation, // kernel-mode only + AlpcCompletionListRundownInformation, // s: VOID + AlpcWaitForPortReferences +} ALPC_PORT_INFORMATION_CLASS; + +// private +typedef struct _ALPC_BASIC_INFORMATION +{ + ULONG Flags; + ULONG SequenceNo; + PVOID PortContext; +} ALPC_BASIC_INFORMATION, *PALPC_BASIC_INFORMATION; + +// private +typedef struct _ALPC_PORT_ASSOCIATE_COMPLETION_PORT +{ + PVOID CompletionKey; + HANDLE CompletionPort; +} ALPC_PORT_ASSOCIATE_COMPLETION_PORT, *PALPC_PORT_ASSOCIATE_COMPLETION_PORT; + +// private +typedef struct _ALPC_SERVER_INFORMATION +{ + union + { + struct + { + HANDLE ThreadHandle; + } In; + struct + { + BOOLEAN ThreadBlocked; + HANDLE ConnectedProcessId; + UNICODE_STRING ConnectionPortName; + } Out; + }; +} ALPC_SERVER_INFORMATION, *PALPC_SERVER_INFORMATION; + +// private +typedef struct _ALPC_PORT_MESSAGE_ZONE_INFORMATION +{ + PVOID Buffer; + ULONG Size; +} ALPC_PORT_MESSAGE_ZONE_INFORMATION, *PALPC_PORT_MESSAGE_ZONE_INFORMATION; + +// private +typedef struct _ALPC_PORT_COMPLETION_LIST_INFORMATION +{ + PVOID Buffer; // PALPC_COMPLETION_LIST_HEADER + ULONG Size; + ULONG ConcurrencyCount; + ULONG AttributeFlags; +} ALPC_PORT_COMPLETION_LIST_INFORMATION, *PALPC_PORT_COMPLETION_LIST_INFORMATION; + +// private +typedef enum _ALPC_MESSAGE_INFORMATION_CLASS +{ + AlpcMessageSidInformation, // q: out SID + AlpcMessageTokenModifiedIdInformation, // q: out LUID + MaxAlpcMessageInfoClass +} ALPC_MESSAGE_INFORMATION_CLASS, *PALPC_MESSAGE_INFORMATION_CLASS; + +// begin_private + +#if (PHNT_VERSION >= PHNT_VISTA) + +// System calls + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCreatePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcDisconnectPort( + _In_ HANDLE PortHandle, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcQueryInformation( + _In_opt_ HANDLE PortHandle, + _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass, + _Inout_updates_bytes_to_(Length, *ReturnLength) PVOID PortInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcSetInformation( + _In_ HANDLE PortHandle, + _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass, + _In_reads_bytes_opt_(Length) PVOID PortInformation, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCreatePortSection( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_opt_ HANDLE SectionHandle, + _In_ SIZE_T SectionSize, + _Out_ PALPC_HANDLE AlpcSectionHandle, + _Out_ PSIZE_T ActualSectionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcDeletePortSection( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE SectionHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCreateResourceReserve( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ SIZE_T MessageSize, + _Out_ PALPC_HANDLE ResourceId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcDeleteResourceReserve( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ResourceId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCreateSectionView( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _Inout_ PALPC_DATA_VIEW_ATTR ViewAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcDeleteSectionView( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ PVOID ViewBase + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCreateSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _Inout_ PALPC_SECURITY_ATTR SecurityAttribute + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcDeleteSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ContextHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcRevokeSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ContextHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcQueryInformationMessage( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ALPC_MESSAGE_INFORMATION_CLASS MessageInformationClass, + _Out_writes_bytes_to_opt_(Length, *ReturnLength) PVOID MessageInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +#define ALPC_MSGFLG_REPLY_MESSAGE 0x1 +#define ALPC_MSGFLG_LPC_MODE 0x2 // ? +#define ALPC_MSGFLG_RELEASE_MESSAGE 0x10000 // dbg +#define ALPC_MSGFLG_SYNC_REQUEST 0x20000 // dbg +#define ALPC_MSGFLG_WAIT_USER_MODE 0x100000 +#define ALPC_MSGFLG_WAIT_ALERTABLE 0x200000 +#define ALPC_MSGFLG_WOW64_CALL 0x80000000 // dbg + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_ ULONG Flags, + _In_opt_ PSID RequiredServerSid, + _Inout_updates_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ConnectionMessage, + _Inout_opt_ PULONG BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES OutMessageAttributes, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES InMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcConnectPortEx( + _Out_ PHANDLE PortHandle, + _In_ POBJECT_ATTRIBUTES ConnectionPortObjectAttributes, + _In_opt_ POBJECT_ATTRIBUTES ClientPortObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_ ULONG Flags, + _In_opt_ PSECURITY_DESCRIPTOR ServerSecurityRequirements, + _Inout_updates_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ConnectionMessage, + _Inout_opt_ PSIZE_T BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES OutMessageAttributes, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES InMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcAcceptConnectPort( + _Out_ PHANDLE PortHandle, + _In_ HANDLE ConnectionPortHandle, + _In_ ULONG Flags, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_opt_ PVOID PortContext, + _In_reads_bytes_(ConnectionRequest->u1.s1.TotalLength) PPORT_MESSAGE ConnectionRequest, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES ConnectionMessageAttributes, + _In_ BOOLEAN AcceptConnection + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcSendWaitReceivePort( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_reads_bytes_opt_(SendMessage->u1.s1.TotalLength) PPORT_MESSAGE SendMessage, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES SendMessageAttributes, + _Out_writes_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ReceiveMessage, + _Inout_opt_ PSIZE_T BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES ReceiveMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#define ALPC_CANCELFLG_TRY_CANCEL 0x1 // dbg +#define ALPC_CANCELFLG_NO_CONTEXT_CHECK 0x8 +#define ALPC_CANCELFLGP_FLUSH 0x10000 // dbg + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcCancelMessage( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_ PALPC_CONTEXT_ATTR MessageContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcImpersonateClientOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ PVOID Flags + ); + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcImpersonateClientContainerOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG Flags + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcOpenSenderProcess( + _Out_ PHANDLE ProcessHandle, + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ULONG Flags, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlpcOpenSenderThread( + _Out_ PHANDLE ThreadHandle, + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ULONG Flags, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +// Support functions + +NTSYSAPI +ULONG +NTAPI +AlpcMaxAllowedMessageLength( + VOID + ); + +NTSYSAPI +ULONG +NTAPI +AlpcGetHeaderSize( + _In_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +AlpcInitializeMessageAttribute( + _In_ ULONG AttributeFlags, + _Out_opt_ PALPC_MESSAGE_ATTRIBUTES Buffer, + _In_ ULONG BufferSize, + _Out_ PULONG RequiredBufferSize + ); + +NTSYSAPI +PVOID +NTAPI +AlpcGetMessageAttribute( + _In_ PALPC_MESSAGE_ATTRIBUTES Buffer, + _In_ ULONG AttributeFlag + ); + +NTSYSAPI +NTSTATUS +NTAPI +AlpcRegisterCompletionList( + _In_ HANDLE PortHandle, + _Out_ PALPC_COMPLETION_LIST_HEADER Buffer, + _In_ ULONG Size, + _In_ ULONG ConcurrencyCount, + _In_ ULONG AttributeFlags + ); + +NTSYSAPI +NTSTATUS +NTAPI +AlpcUnregisterCompletionList( + _In_ HANDLE PortHandle + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +AlpcRundownCompletionList( + _In_ HANDLE PortHandle + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +AlpcAdjustCompletionListConcurrencyCount( + _In_ HANDLE PortHandle, + _In_ ULONG ConcurrencyCount + ); + +NTSYSAPI +BOOLEAN +NTAPI +AlpcRegisterCompletionListWorkerThread( + _Inout_ PVOID CompletionList + ); + +NTSYSAPI +BOOLEAN +NTAPI +AlpcUnregisterCompletionListWorkerThread( + _Inout_ PVOID CompletionList + ); + +NTSYSAPI +VOID +NTAPI +AlpcGetCompletionListLastMessageInformation( + _In_ PVOID CompletionList, + _Out_ PULONG LastMessageId, + _Out_ PULONG LastCallbackId + ); + +NTSYSAPI +ULONG +NTAPI +AlpcGetOutstandingCompletionListMessageCount( + _In_ PVOID CompletionList + ); + +NTSYSAPI +PPORT_MESSAGE +NTAPI +AlpcGetMessageFromCompletionList( + _In_ PVOID CompletionList, + _Out_opt_ PALPC_MESSAGE_ATTRIBUTES *MessageAttributes + ); + +NTSYSAPI +VOID +NTAPI +AlpcFreeCompletionListMessage( + _Inout_ PVOID CompletionList, + _In_ PPORT_MESSAGE Message + ); + +NTSYSAPI +PALPC_MESSAGE_ATTRIBUTES +NTAPI +AlpcGetCompletionListMessageAttributes( + _In_ PVOID CompletionList, + _In_ PPORT_MESSAGE Message + ); + +#endif + +// end_private + +#endif diff --git a/phnt/include/ntmisc.h b/phnt/include/ntmisc.h new file mode 100644 index 0000000..7ba023d --- /dev/null +++ b/phnt/include/ntmisc.h @@ -0,0 +1,77 @@ +#ifndef _NTMISC_H +#define _NTMISC_H + +// Boot graphics + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDrawText( + _In_ PUNICODE_STRING Text + ); +#endif + +// Filter manager + +#define FLT_PORT_CONNECT 0x0001 +#define FLT_PORT_ALL_ACCESS (FLT_PORT_CONNECT | STANDARD_RIGHTS_ALL) + +// VDM + +typedef enum _VDMSERVICECLASS +{ + VdmStartExecution, + VdmQueueInterrupt, + VdmDelayInterrupt, + VdmInitialize, + VdmFeatures, + VdmSetInt21Handler, + VdmQueryDir, + VdmPrinterDirectIoOpen, + VdmPrinterDirectIoClose, + VdmPrinterInitialize, + VdmSetLdtEntries, + VdmSetProcessLdtInfo, + VdmAdlibEmulation, + VdmPMCliControl, + VdmQueryVdmProcess +} VDMSERVICECLASS, *PVDMSERVICECLASS; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtVdmControl( + _In_ VDMSERVICECLASS Service, + _Inout_ PVOID ServiceData + ); + +// WMI/ETW + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTraceEvent( + _In_ HANDLE TraceHandle, + _In_ ULONG Flags, + _In_ ULONG FieldSize, + _In_ PVOID Fields + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTraceControl( + _In_ ULONG FunctionCode, + _In_reads_bytes_opt_(InBufferLen) PVOID InBuffer, + _In_ ULONG InBufferLen, + _Out_writes_bytes_opt_(OutBufferLen) PVOID OutBuffer, + _In_ ULONG OutBufferLen, + _Out_ PULONG ReturnLength + ); +#endif + +#endif diff --git a/phnt/include/ntmmapi.h b/phnt/include/ntmmapi.h new file mode 100644 index 0000000..4e2530d --- /dev/null +++ b/phnt/include/ntmmapi.h @@ -0,0 +1,711 @@ +#ifndef _NTMMAPI_H +#define _NTMMAPI_H + +#if (PHNT_MODE == PHNT_MODE_KERNEL) + +// Protection constants + +#define PAGE_NOACCESS 0x01 +#define PAGE_READONLY 0x02 +#define PAGE_READWRITE 0x04 +#define PAGE_WRITECOPY 0x08 +#define PAGE_EXECUTE 0x10 +#define PAGE_EXECUTE_READ 0x20 +#define PAGE_EXECUTE_READWRITE 0x40 +#define PAGE_EXECUTE_WRITECOPY 0x80 +#define PAGE_GUARD 0x100 +#define PAGE_NOCACHE 0x200 +#define PAGE_WRITECOMBINE 0x400 + +#define PAGE_REVERT_TO_FILE_MAP 0x80000000 +#define PAGE_ENCLAVE_THREAD_CONTROL 0x80000000 +#define PAGE_TARGETS_NO_UPDATE 0x40000000 +#define PAGE_TARGETS_INVALID 0x40000000 +#define PAGE_ENCLAVE_UNVALIDATED 0x20000000 + +// Region and section constants + +#define MEM_COMMIT 0x1000 +#define MEM_RESERVE 0x2000 +#define MEM_DECOMMIT 0x4000 +#define MEM_RELEASE 0x8000 +#define MEM_FREE 0x10000 +#define MEM_PRIVATE 0x20000 +#define MEM_MAPPED 0x40000 +#define MEM_RESET 0x80000 +#define MEM_TOP_DOWN 0x100000 +#define MEM_WRITE_WATCH 0x200000 +#define MEM_PHYSICAL 0x400000 +#define MEM_ROTATE 0x800000 +#define MEM_DIFFERENT_IMAGE_BASE_OK 0x800000 +#define MEM_RESET_UNDO 0x1000000 +#define MEM_LARGE_PAGES 0x20000000 +#define MEM_4MB_PAGES 0x80000000 + +#define SEC_FILE 0x800000 +#define SEC_IMAGE 0x1000000 +#define SEC_PROTECTED_IMAGE 0x2000000 +#define SEC_RESERVE 0x4000000 +#define SEC_COMMIT 0x8000000 +#define SEC_NOCACHE 0x10000000 +#define SEC_WRITECOMBINE 0x40000000 +#define SEC_LARGE_PAGES 0x80000000 +#define SEC_IMAGE_NO_EXECUTE (SEC_IMAGE | SEC_NOCACHE) +#define MEM_IMAGE SEC_IMAGE + +#endif + +// private +typedef enum _MEMORY_INFORMATION_CLASS +{ + MemoryBasicInformation, // MEMORY_BASIC_INFORMATION + MemoryWorkingSetInformation, // MEMORY_WORKING_SET_INFORMATION + MemoryMappedFilenameInformation, // UNICODE_STRING + MemoryRegionInformation, // MEMORY_REGION_INFORMATION + MemoryWorkingSetExInformation, // MEMORY_WORKING_SET_EX_INFORMATION + MemorySharedCommitInformation, // MEMORY_SHARED_COMMIT_INFORMATION + MemoryImageInformation // MEMORY_IMAGE_INFORMATION +} MEMORY_INFORMATION_CLASS; + +#if (PHNT_MODE == PHNT_MODE_KERNEL) + +typedef struct _MEMORY_BASIC_INFORMATION +{ + PVOID BaseAddress; + PVOID AllocationBase; + ULONG AllocationProtect; + SIZE_T RegionSize; + ULONG State; + ULONG Protect; + ULONG Type; +} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; +#endif + +typedef struct _MEMORY_WORKING_SET_BLOCK +{ + ULONG_PTR Protection : 5; + ULONG_PTR ShareCount : 3; + ULONG_PTR Shared : 1; + ULONG_PTR Node : 3; +#ifdef _WIN64 + ULONG_PTR VirtualPage : 52; +#else + ULONG VirtualPage : 20; +#endif +} MEMORY_WORKING_SET_BLOCK, *PMEMORY_WORKING_SET_BLOCK; + +typedef struct _MEMORY_WORKING_SET_INFORMATION +{ + ULONG_PTR NumberOfEntries; + MEMORY_WORKING_SET_BLOCK WorkingSetInfo[1]; +} MEMORY_WORKING_SET_INFORMATION, *PMEMORY_WORKING_SET_INFORMATION; + +// private +typedef struct _MEMORY_REGION_INFORMATION +{ + PVOID AllocationBase; + ULONG AllocationProtect; + ULONG RegionType; + SIZE_T RegionSize; +} MEMORY_REGION_INFORMATION, *PMEMORY_REGION_INFORMATION; + +// private +typedef struct _MEMORY_WORKING_SET_EX_BLOCK +{ + union + { + struct + { + ULONG_PTR Valid : 1; + ULONG_PTR ShareCount : 3; + ULONG_PTR Win32Protection : 11; + ULONG_PTR Shared : 1; + ULONG_PTR Node : 6; + ULONG_PTR Locked : 1; + ULONG_PTR LargePage : 1; + ULONG_PTR Priority : 3; + ULONG_PTR Reserved : 3; + ULONG_PTR SharedOriginal : 1; + ULONG_PTR Bad : 1; +#ifdef _WIN64 + ULONG_PTR ReservedUlong : 32; +#endif + }; + struct + { + ULONG_PTR Valid : 1; + ULONG_PTR Reserved0 : 14; + ULONG_PTR Shared : 1; + ULONG_PTR Reserved1 : 5; + ULONG_PTR PageTable : 1; + ULONG_PTR Location : 2; + ULONG_PTR Priority : 3; + ULONG_PTR ModifiedList : 1; + ULONG_PTR Reserved2 : 2; + ULONG_PTR SharedOriginal : 1; + ULONG_PTR Bad : 1; +#ifdef _WIN64 + ULONG_PTR ReservedUlong : 32; +#endif + } Invalid; + }; +} MEMORY_WORKING_SET_EX_BLOCK, *PMEMORY_WORKING_SET_EX_BLOCK; + +// private +typedef struct _MEMORY_WORKING_SET_EX_INFORMATION +{ + PVOID VirtualAddress; + union + { + MEMORY_WORKING_SET_EX_BLOCK VirtualAttributes; + ULONG_PTR Long; + } u1; +} MEMORY_WORKING_SET_EX_INFORMATION, *PMEMORY_WORKING_SET_EX_INFORMATION; + +// private +typedef struct _MEMORY_SHARED_COMMIT_INFORMATION +{ + SIZE_T CommitSize; +} MEMORY_SHARED_COMMIT_INFORMATION, *PMEMORY_SHARED_COMMIT_INFORMATION; + +// private +typedef struct _MEMORY_IMAGE_INFORMATION +{ + PVOID ImageBase; + SIZE_T SizeOfImage; + union + { + ULONG ImageFlags; + struct + { + ULONG ImagePartialMap : 1; + ULONG ImageNotExecutable : 1; + ULONG Reserved : 30; + }; + }; +} MEMORY_IMAGE_INFORMATION, *PMEMORY_IMAGE_INFORMATION; + +#define MMPFNLIST_ZERO 0 +#define MMPFNLIST_FREE 1 +#define MMPFNLIST_STANDBY 2 +#define MMPFNLIST_MODIFIED 3 +#define MMPFNLIST_MODIFIEDNOWRITE 4 +#define MMPFNLIST_BAD 5 +#define MMPFNLIST_ACTIVE 6 +#define MMPFNLIST_TRANSITION 7 + +#define MMPFNUSE_PROCESSPRIVATE 0 +#define MMPFNUSE_FILE 1 +#define MMPFNUSE_PAGEFILEMAPPED 2 +#define MMPFNUSE_PAGETABLE 3 +#define MMPFNUSE_PAGEDPOOL 4 +#define MMPFNUSE_NONPAGEDPOOL 5 +#define MMPFNUSE_SYSTEMPTE 6 +#define MMPFNUSE_SESSIONPRIVATE 7 +#define MMPFNUSE_METAFILE 8 +#define MMPFNUSE_AWEPAGE 9 +#define MMPFNUSE_DRIVERLOCKPAGE 10 + +typedef struct _MEMORY_FRAME_INFORMATION +{ + ULONGLONG UseDescription : 4; // MMPFNUSE_* + ULONGLONG ListDescription : 3; // MMPFNLIST_* + ULONGLONG Reserved0 : 1; // reserved for future expansion + ULONGLONG Pinned : 1; // 1 - pinned, 0 - not pinned + ULONGLONG DontUse : 48; // *_INFORMATION overlay + ULONGLONG Priority : 3; // rev + ULONGLONG Reserved : 4; // reserved for future expansion +} MEMORY_FRAME_INFORMATION; + +typedef struct _FILEOFFSET_INFORMATION +{ + ULONGLONG DontUse : 9; // MEMORY_FRAME_INFORMATION overlay + ULONGLONG Offset : 48; // mapped files + ULONGLONG Reserved : 7; // reserved for future expansion +} FILEOFFSET_INFORMATION; + +typedef struct _PAGEDIR_INFORMATION +{ + ULONGLONG DontUse : 9; // MEMORY_FRAME_INFORMATION overlay + ULONGLONG PageDirectoryBase : 48; // private pages + ULONGLONG Reserved : 7; // reserved for future expansion +} PAGEDIR_INFORMATION; + +typedef struct _MMPFN_IDENTITY +{ + union + { + MEMORY_FRAME_INFORMATION e1; // all + FILEOFFSET_INFORMATION e2; // mapped files + PAGEDIR_INFORMATION e3; // private pages + } u1; + ULONG_PTR PageFrameIndex; // all + union + { + PVOID FileObject; // mapped files + PVOID VirtualAddress; // everything else + } u2; +} MMPFN_IDENTITY, *PMMPFN_IDENTITY; + +typedef struct _MMPFN_MEMSNAP_INFORMATION +{ + ULONG_PTR InitialPageFrameIndex; + ULONG_PTR Count; +} MMPFN_MEMSNAP_INFORMATION, *PMMPFN_MEMSNAP_INFORMATION; + +typedef enum _SECTION_INFORMATION_CLASS +{ + SectionBasicInformation, + SectionImageInformation, + SectionRelocationInformation, // name:wow64:whNtQuerySection_SectionRelocationInformation + MaxSectionInfoClass +} SECTION_INFORMATION_CLASS; + +typedef struct _SECTION_BASIC_INFORMATION +{ + PVOID BaseAddress; + ULONG AllocationAttributes; + LARGE_INTEGER MaximumSize; +} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; + +// symbols +typedef struct _SECTION_IMAGE_INFORMATION +{ + PVOID TransferAddress; + ULONG ZeroBits; + SIZE_T MaximumStackSize; + SIZE_T CommittedStackSize; + ULONG SubSystemType; + union + { + struct + { + USHORT SubSystemMinorVersion; + USHORT SubSystemMajorVersion; + }; + ULONG SubSystemVersion; + }; + ULONG GpValue; + USHORT ImageCharacteristics; + USHORT DllCharacteristics; + USHORT Machine; + BOOLEAN ImageContainsCode; + union + { + UCHAR ImageFlags; + struct + { + UCHAR ComPlusNativeReady : 1; + UCHAR ComPlusILOnly : 1; + UCHAR ImageDynamicallyRelocated : 1; + UCHAR ImageMappedFlat : 1; + UCHAR BaseBelow4gb : 1; + UCHAR Reserved : 3; + }; + }; + ULONG LoaderFlags; + ULONG ImageFileSize; + ULONG CheckSum; +} SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +typedef enum _SECTION_INHERIT +{ + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT; +#endif + +#define SEC_BASED 0x200000 +#define SEC_NO_CHANGE 0x400000 +#define SEC_GLOBAL 0x20000000 + +#define MEM_EXECUTE_OPTION_DISABLE 0x1 +#define MEM_EXECUTE_OPTION_ENABLE 0x2 +#define MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION 0x4 +#define MEM_EXECUTE_OPTION_PERMANENT 0x8 +#define MEM_EXECUTE_OPTION_EXECUTE_DISPATCH_ENABLE 0x10 +#define MEM_EXECUTE_OPTION_IMAGE_DISPATCH_ENABLE 0x20 +#define MEM_EXECUTE_OPTION_VALID_FLAGS 0x3f + +// Virtual memory + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAllocateVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ _At_(*BaseAddress, _Readable_bytes_(*RegionSize) _Writable_bytes_(*RegionSize) _Post_readable_byte_size_(*RegionSize)) PVOID *BaseAddress, + _In_ ULONG_PTR ZeroBits, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG AllocationType, + _In_ ULONG Protect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFreeVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG FreeType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _Out_writes_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWriteVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesWritten + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtProtectVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG NewProtect, + _Out_ PULONG OldProtect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ MEMORY_INFORMATION_CLASS MemoryInformationClass, + _Out_writes_bytes_(MemoryInformationLength) PVOID MemoryInformation, + _In_ SIZE_T MemoryInformationLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +#endif + +// begin_private + +typedef enum _VIRTUAL_MEMORY_INFORMATION_CLASS +{ + VmPrefetchInformation, + VmPagePriorityInformation, + VmCfgCallTargetInformation +} VIRTUAL_MEMORY_INFORMATION_CLASS; + +typedef struct _MEMORY_RANGE_ENTRY +{ + PVOID VirtualAddress; + SIZE_T NumberOfBytes; +} MEMORY_RANGE_ENTRY, *PMEMORY_RANGE_ENTRY; + +// end_private + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_ VIRTUAL_MEMORY_INFORMATION_CLASS VmInformationClass, + _In_ ULONG_PTR NumberOfEntries, + _In_reads_ (NumberOfEntries) PMEMORY_RANGE_ENTRY VirtualAddresses, + _In_reads_bytes_ (VmInformationLength) PVOID VmInformation, + _In_ ULONG VmInformationLength + ); + +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLockVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG MapType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnlockVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG MapType + ); + +#endif + +// Sections + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateSection( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PLARGE_INTEGER MaximumSize, + _In_ ULONG SectionPageProtection, + _In_ ULONG AllocationAttributes, + _In_opt_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenSection( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMapViewOfSection( + _In_ HANDLE SectionHandle, + _In_ HANDLE ProcessHandle, + _Inout_ _At_(*BaseAddress, _Readable_bytes_(*ViewSize) _Writable_bytes_(*ViewSize) _Post_readable_byte_size_(*ViewSize)) PVOID *BaseAddress, + _In_ ULONG_PTR ZeroBits, + _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, + _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, + _In_ ULONG AllocationType, + _In_ ULONG Win32Protect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnmapViewOfSection( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnmapViewOfSectionEx( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_ ULONG Flags + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtExtendSection( + _In_ HANDLE SectionHandle, + _Inout_ PLARGE_INTEGER NewSectionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySection( + _In_ HANDLE SectionHandle, + _In_ SECTION_INFORMATION_CLASS SectionInformationClass, + _Out_writes_bytes_(SectionInformationLength) PVOID SectionInformation, + _In_ SIZE_T SectionInformationLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAreMappedFilesTheSame( + _In_ PVOID File1MappedAsAnImage, + _In_ PVOID File2MappedAsFile + ); + +#endif + +// Partitions + +// private +typedef enum _MEMORY_PARTITION_INFORMATION_CLASS +{ + SystemMemoryPartitionInformation, + SystemMemoryPartitionMoveMemory, + SystemMemoryPartitionAddPagefile, + SystemMemoryPartitionCombineMemory +} MEMORY_PARTITION_INFORMATION_CLASS; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreatePartition( + _Out_ PHANDLE PartitionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG PreferredNode + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenPartition( + _Out_ PHANDLE PartitionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtManagePartition( + _In_ MEMORY_PARTITION_INFORMATION_CLASS PartitionInformationClass, + _In_ PVOID PartitionInformation, + _In_ ULONG PartitionInformationLength + ); + +#endif + +#endif + +// User physical pages + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMapUserPhysicalPages( + _In_ PVOID VirtualAddress, + _In_ ULONG_PTR NumberOfPages, + _In_reads_opt_(NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMapUserPhysicalPagesScatter( + _In_reads_(NumberOfPages) PVOID *VirtualAddresses, + _In_ ULONG_PTR NumberOfPages, + _In_reads_opt_(NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAllocateUserPhysicalPages( + _In_ HANDLE ProcessHandle, + _Inout_ PULONG_PTR NumberOfPages, + _Out_writes_(*NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFreeUserPhysicalPages( + _In_ HANDLE ProcessHandle, + _Inout_ PULONG_PTR NumberOfPages, + _In_reads_(*NumberOfPages) PULONG_PTR UserPfnArray + ); + +#endif + +// Sessions + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenSession( + _Out_ PHANDLE SessionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); +#endif + +#endif + +// Misc. + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetWriteWatch( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize, + _Out_writes_(*EntriesInUserAddressArray) PVOID *UserAddressArray, + _Inout_ PULONG_PTR EntriesInUserAddressArray, + _Out_ PULONG Granularity + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtResetWriteWatch( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreatePagingFile( + _In_ PUNICODE_STRING PageFileName, + _In_ PLARGE_INTEGER MinimumSize, + _In_ PLARGE_INTEGER MaximumSize, + _In_ ULONG Priority + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushInstructionCache( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_ SIZE_T Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushWriteBuffer( + VOID + ); + +#endif + +#endif diff --git a/phnt/include/ntnls.h b/phnt/include/ntnls.h new file mode 100644 index 0000000..c50b5d1 --- /dev/null +++ b/phnt/include/ntnls.h @@ -0,0 +1,30 @@ +#ifndef _NTNLS_H +#define _NTNLS_H + +#define MAXIMUM_LEADBYTES 12 + +typedef struct _CPTABLEINFO +{ + USHORT CodePage; + USHORT MaximumCharacterSize; + USHORT DefaultChar; + USHORT UniDefaultChar; + USHORT TransDefaultChar; + USHORT TransUniDefaultChar; + USHORT DBCSCodePage; + UCHAR LeadByte[MAXIMUM_LEADBYTES]; + PUSHORT MultiByteTable; + PVOID WideCharTable; + PUSHORT DBCSRanges; + PUSHORT DBCSOffsets; +} CPTABLEINFO, *PCPTABLEINFO; + +typedef struct _NLSTABLEINFO +{ + CPTABLEINFO OemTableInfo; + CPTABLEINFO AnsiTableInfo; + PUSHORT UpperCaseTable; + PUSHORT LowerCaseTable; +} NLSTABLEINFO, *PNLSTABLEINFO; + +#endif diff --git a/phnt/include/ntobapi.h b/phnt/include/ntobapi.h new file mode 100644 index 0000000..ea1b88d --- /dev/null +++ b/phnt/include/ntobapi.h @@ -0,0 +1,371 @@ +#ifndef _NTOBAPI_H +#define _NTOBAPI_H + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#define OBJECT_TYPE_CREATE 0x0001 +#define OBJECT_TYPE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1) +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#define DIRECTORY_QUERY 0x0001 +#define DIRECTORY_TRAVERSE 0x0002 +#define DIRECTORY_CREATE_OBJECT 0x0004 +#define DIRECTORY_CREATE_SUBDIRECTORY 0x0008 +#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xf) +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#define SYMBOLIC_LINK_QUERY 0x0001 +#define SYMBOLIC_LINK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1) +#endif + +#define OBJ_PROTECT_CLOSE 0x00000001 +#ifndef OBJ_INHERIT +#define OBJ_INHERIT 0x00000002 +#endif +#define OBJ_AUDIT_OBJECT_CLOSE 0x00000004 + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +typedef enum _OBJECT_INFORMATION_CLASS +{ + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectTypesInformation, + ObjectHandleFlagInformation, + ObjectSessionInformation, + MaxObjectInfoClass +} OBJECT_INFORMATION_CLASS; +#else +#define ObjectNameInformation 1 +#define ObjectTypesInformation 3 +#define ObjectHandleFlagInformation 4 +#define ObjectSessionInformation 5 +#endif + +typedef struct _OBJECT_BASIC_INFORMATION +{ + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG PagedPoolCharge; + ULONG NonPagedPoolCharge; + ULONG Reserved[3]; + ULONG NameInfoSize; + ULONG TypeInfoSize; + ULONG SecurityDescriptorSize; + LARGE_INTEGER CreationTime; +} OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +typedef struct _OBJECT_NAME_INFORMATION +{ + UNICODE_STRING Name; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; +#endif + +typedef struct _OBJECT_TYPE_INFORMATION +{ + UNICODE_STRING TypeName; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccessMask; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + UCHAR TypeIndex; // since WINBLUE + CHAR ReservedByte; + ULONG PoolType; + ULONG DefaultPagedPoolCharge; + ULONG DefaultNonPagedPoolCharge; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef struct _OBJECT_TYPES_INFORMATION +{ + ULONG NumberOfTypes; +} OBJECT_TYPES_INFORMATION, *POBJECT_TYPES_INFORMATION; + +typedef struct _OBJECT_HANDLE_FLAG_INFORMATION +{ + BOOLEAN Inherit; + BOOLEAN ProtectFromClose; +} OBJECT_HANDLE_FLAG_INFORMATION, *POBJECT_HANDLE_FLAG_INFORMATION; + +// Objects, handles + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryObject( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationObject( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _In_reads_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength + ); + +#define DUPLICATE_CLOSE_SOURCE 0x00000001 +#define DUPLICATE_SAME_ACCESS 0x00000002 +#define DUPLICATE_SAME_ATTRIBUTES 0x00000004 + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDuplicateObject( + _In_ HANDLE SourceProcessHandle, + _In_ HANDLE SourceHandle, + _In_opt_ HANDLE TargetProcessHandle, + _Out_opt_ PHANDLE TargetHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Options + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMakeTemporaryObject( + _In_ HANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtMakePermanentObject( + _In_ HANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSignalAndWaitForSingleObject( + _In_ HANDLE SignalHandle, + _In_ HANDLE WaitHandle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForSingleObject( + _In_ HANDLE Handle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForMultipleObjects( + _In_ ULONG Count, + _In_reads_(Count) HANDLE Handles[], + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForMultipleObjects32( + _In_ ULONG Count, + _In_reads_(Count) LONG Handles[], + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSecurityObject( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySecurityObject( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_writes_bytes_opt_(Length) PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ULONG Length, + _Out_ PULONG LengthNeeded + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtClose( + _In_ HANDLE Handle + ); + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCompareObjects( + _In_ HANDLE FirstObjectHandle, + _In_ HANDLE SecondObjectHandle + ); +#endif + +#endif + +// Directory objects + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateDirectoryObject( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateDirectoryObjectEx( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ShadowDirectoryHandle, + _In_ ULONG Flags + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenDirectoryObject( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +typedef struct _OBJECT_DIRECTORY_INFORMATION +{ + UNICODE_STRING Name; + UNICODE_STRING TypeName; +} OBJECT_DIRECTORY_INFORMATION, *POBJECT_DIRECTORY_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryDirectoryObject( + _In_ HANDLE DirectoryHandle, + _Out_writes_bytes_opt_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_ BOOLEAN RestartScan, + _Inout_ PULONG Context, + _Out_opt_ PULONG ReturnLength + ); + +#endif + +// Private namespaces + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreatePrivateNamespace( + _Out_ PHANDLE NamespaceHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PVOID BoundaryDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenPrivateNamespace( + _Out_ PHANDLE NamespaceHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PVOID BoundaryDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeletePrivateNamespace( + _In_ HANDLE NamespaceHandle + ); + +#endif + +#endif + +// Symbolic links + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateSymbolicLinkObject( + _Out_ PHANDLE LinkHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PUNICODE_STRING LinkTarget + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenSymbolicLinkObject( + _Out_ PHANDLE LinkHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySymbolicLinkObject( + _In_ HANDLE LinkHandle, + _Inout_ PUNICODE_STRING LinkTarget, + _Out_opt_ PULONG ReturnedLength + ); + +#endif + +#endif diff --git a/phnt/include/ntpebteb.h b/phnt/include/ntpebteb.h new file mode 100644 index 0000000..cd3d62f --- /dev/null +++ b/phnt/include/ntpebteb.h @@ -0,0 +1,310 @@ +#ifndef _NTPEBTEB_H +#define _NTPEBTEB_H + +typedef struct _RTL_USER_PROCESS_PARAMETERS *PRTL_USER_PROCESS_PARAMETERS; +typedef struct _RTL_CRITICAL_SECTION *PRTL_CRITICAL_SECTION; + +// symbols +typedef struct _PEB +{ + BOOLEAN InheritedAddressSpace; + BOOLEAN ReadImageFileExecOptions; + BOOLEAN BeingDebugged; + union + { + BOOLEAN BitField; + struct + { + BOOLEAN ImageUsesLargePages : 1; + BOOLEAN IsProtectedProcess : 1; + BOOLEAN IsImageDynamicallyRelocated : 1; + BOOLEAN SkipPatchingUser32Forwarders : 1; + BOOLEAN IsPackagedProcess : 1; + BOOLEAN IsAppContainer : 1; + BOOLEAN IsProtectedProcessLight : 1; + BOOLEAN SpareBits : 1; + }; + }; + HANDLE Mutant; + + PVOID ImageBaseAddress; + PPEB_LDR_DATA Ldr; + PRTL_USER_PROCESS_PARAMETERS ProcessParameters; + PVOID SubSystemData; + PVOID ProcessHeap; + PRTL_CRITICAL_SECTION FastPebLock; + PVOID AtlThunkSListPtr; + PVOID IFEOKey; + union + { + ULONG CrossProcessFlags; + struct + { + ULONG ProcessInJob : 1; + ULONG ProcessInitializing : 1; + ULONG ProcessUsingVEH : 1; + ULONG ProcessUsingVCH : 1; + ULONG ProcessUsingFTH : 1; + ULONG ReservedBits0 : 27; + }; + }; + union + { + PVOID KernelCallbackTable; + PVOID UserSharedInfoPtr; + }; + ULONG SystemReserved[1]; + ULONG AtlThunkSListPtr32; + PVOID ApiSetMap; + ULONG TlsExpansionCounter; + PVOID TlsBitmap; + ULONG TlsBitmapBits[2]; + PVOID ReadOnlySharedMemoryBase; + PVOID HotpatchInformation; + PVOID *ReadOnlyStaticServerData; + PVOID AnsiCodePageData; + PVOID OemCodePageData; + PVOID UnicodeCaseTableData; + + ULONG NumberOfProcessors; + ULONG NtGlobalFlag; + + LARGE_INTEGER CriticalSectionTimeout; + SIZE_T HeapSegmentReserve; + SIZE_T HeapSegmentCommit; + SIZE_T HeapDeCommitTotalFreeThreshold; + SIZE_T HeapDeCommitFreeBlockThreshold; + + ULONG NumberOfHeaps; + ULONG MaximumNumberOfHeaps; + PVOID *ProcessHeaps; + + PVOID GdiSharedHandleTable; + PVOID ProcessStarterHelper; + ULONG GdiDCAttributeList; + + PRTL_CRITICAL_SECTION LoaderLock; + + ULONG OSMajorVersion; + ULONG OSMinorVersion; + USHORT OSBuildNumber; + USHORT OSCSDVersion; + ULONG OSPlatformId; + ULONG ImageSubsystem; + ULONG ImageSubsystemMajorVersion; + ULONG ImageSubsystemMinorVersion; + ULONG_PTR ImageProcessAffinityMask; + GDI_HANDLE_BUFFER GdiHandleBuffer; + PVOID PostProcessInitRoutine; + + PVOID TlsExpansionBitmap; + ULONG TlsExpansionBitmapBits[32]; + + ULONG SessionId; + + ULARGE_INTEGER AppCompatFlags; + ULARGE_INTEGER AppCompatFlagsUser; + PVOID pShimData; + PVOID AppCompatInfo; + + UNICODE_STRING CSDVersion; + + PVOID ActivationContextData; + PVOID ProcessAssemblyStorageMap; + PVOID SystemDefaultActivationContextData; + PVOID SystemAssemblyStorageMap; + + SIZE_T MinimumStackCommit; + + PVOID *FlsCallback; + LIST_ENTRY FlsListHead; + PVOID FlsBitmap; + ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)]; + ULONG FlsHighIndex; + + PVOID WerRegistrationData; + PVOID WerShipAssertPtr; + PVOID pContextData; + PVOID pImageHeaderHash; + union + { + ULONG TracingFlags; + struct + { + ULONG HeapTracingEnabled : 1; + ULONG CritSecTracingEnabled : 1; + ULONG LibLoaderTracingEnabled : 1; + ULONG SpareTracingBits : 29; + }; + }; + ULONGLONG CsrServerReadOnlySharedMemoryBase; +} PEB, *PPEB; + +#define GDI_BATCH_BUFFER_SIZE 310 + +typedef struct _GDI_TEB_BATCH +{ + ULONG Offset; + ULONG_PTR HDC; + ULONG Buffer[GDI_BATCH_BUFFER_SIZE]; +} GDI_TEB_BATCH, *PGDI_TEB_BATCH; + +typedef struct _TEB_ACTIVE_FRAME_CONTEXT +{ + ULONG Flags; + PSTR FrameName; +} TEB_ACTIVE_FRAME_CONTEXT, *PTEB_ACTIVE_FRAME_CONTEXT; + +typedef struct _TEB_ACTIVE_FRAME +{ + ULONG Flags; + struct _TEB_ACTIVE_FRAME *Previous; + PTEB_ACTIVE_FRAME_CONTEXT Context; +} TEB_ACTIVE_FRAME, *PTEB_ACTIVE_FRAME; + +typedef struct _TEB +{ + NT_TIB NtTib; + + PVOID EnvironmentPointer; + CLIENT_ID ClientId; + PVOID ActiveRpcHandle; + PVOID ThreadLocalStoragePointer; + PPEB ProcessEnvironmentBlock; + + ULONG LastErrorValue; + ULONG CountOfOwnedCriticalSections; + PVOID CsrClientThread; + PVOID Win32ThreadInfo; + ULONG User32Reserved[26]; + ULONG UserReserved[5]; + PVOID WOW32Reserved; + LCID CurrentLocale; + ULONG FpSoftwareStatusRegister; + PVOID SystemReserved1[54]; + NTSTATUS ExceptionCode; + PVOID ActivationContextStackPointer; +#ifdef _WIN64 + UCHAR SpareBytes[24]; +#else + UCHAR SpareBytes[36]; +#endif + ULONG TxFsContext; + + GDI_TEB_BATCH GdiTebBatch; + CLIENT_ID RealClientId; + HANDLE GdiCachedProcessHandle; + ULONG GdiClientPID; + ULONG GdiClientTID; + PVOID GdiThreadLocalInfo; + ULONG_PTR Win32ClientInfo[62]; + PVOID glDispatchTable[233]; + ULONG_PTR glReserved1[29]; + PVOID glReserved2; + PVOID glSectionInfo; + PVOID glSection; + PVOID glTable; + PVOID glCurrentRC; + PVOID glContext; + + NTSTATUS LastStatusValue; + UNICODE_STRING StaticUnicodeString; + WCHAR StaticUnicodeBuffer[261]; + + PVOID DeallocationStack; + PVOID TlsSlots[64]; + LIST_ENTRY TlsLinks; + + PVOID Vdm; + PVOID ReservedForNtRpc; + PVOID DbgSsReserved[2]; + + ULONG HardErrorMode; +#ifdef _WIN64 + PVOID Instrumentation[11]; +#else + PVOID Instrumentation[9]; +#endif + GUID ActivityId; + + PVOID SubProcessTag; + PVOID EtwLocalData; + PVOID EtwTraceData; + PVOID WinSockData; + ULONG GdiBatchCount; + + union + { + PROCESSOR_NUMBER CurrentIdealProcessor; + ULONG IdealProcessorValue; + struct + { + UCHAR ReservedPad0; + UCHAR ReservedPad1; + UCHAR ReservedPad2; + UCHAR IdealProcessor; + }; + }; + + ULONG GuaranteedStackBytes; + PVOID ReservedForPerf; + PVOID ReservedForOle; + ULONG WaitingOnLoaderLock; + PVOID SavedPriorityState; + ULONG_PTR SoftPatchPtr1; + PVOID ThreadPoolData; + PVOID *TlsExpansionSlots; +#ifdef _WIN64 + PVOID DeallocationBStore; + PVOID BStoreLimit; +#endif + ULONG MuiGeneration; + ULONG IsImpersonating; + PVOID NlsCache; + PVOID pShimData; + ULONG HeapVirtualAffinity; + HANDLE CurrentTransactionHandle; + PTEB_ACTIVE_FRAME ActiveFrame; + PVOID FlsData; + + PVOID PreferredLanguages; + PVOID UserPrefLanguages; + PVOID MergedPrefLanguages; + ULONG MuiImpersonation; + + union + { + USHORT CrossTebFlags; + USHORT SpareCrossTebBits : 16; + }; + union + { + USHORT SameTebFlags; + struct + { + USHORT SafeThunkCall : 1; + USHORT InDebugPrint : 1; + USHORT HasFiberData : 1; + USHORT SkipThreadAttach : 1; + USHORT WerInShipAssertCode : 1; + USHORT RanProcessInit : 1; + USHORT ClonedThread : 1; + USHORT SuppressDebugMsg : 1; + USHORT DisableUserStackWalk : 1; + USHORT RtlExceptionAttached : 1; + USHORT InitialThread : 1; + USHORT SessionAware : 1; + USHORT SpareSameTebBits : 4; + }; + }; + + PVOID TxnScopeEnterCallback; + PVOID TxnScopeExitCallback; + PVOID TxnScopeContext; + ULONG LockCount; + ULONG SpareUlong0; + PVOID ResourceRetValue; + PVOID ReservedForWdf; +} TEB, *PTEB; + +#endif diff --git a/phnt/include/ntpfapi.h b/phnt/include/ntpfapi.h new file mode 100644 index 0000000..07603e9 --- /dev/null +++ b/phnt/include/ntpfapi.h @@ -0,0 +1,252 @@ +#ifndef _NTPFAPI_H +#define _NTPFAPI_H + +// begin_private + +// Prefetch + +typedef enum _PF_BOOT_PHASE_ID +{ + PfKernelInitPhase = 0, + PfBootDriverInitPhase = 90, + PfSystemDriverInitPhase = 120, + PfSessionManagerInitPhase = 150, + PfSMRegistryInitPhase = 180, + PfVideoInitPhase = 210, + PfPostVideoInitPhase = 240, + PfBootAcceptedRegistryInitPhase = 270, + PfUserShellReadyPhase = 300, + PfMaxBootPhaseId = 900 +} PF_BOOT_PHASE_ID; + +typedef enum _PF_ENABLE_STATUS +{ + PfSvNotSpecified, + PfSvEnabled, + PfSvDisabled, + PfSvMaxEnableStatus +} PF_ENABLE_STATUS; + +typedef struct _PF_TRACE_LIMITS +{ + ULONG MaxNumPages; + ULONG MaxNumSections; + LONGLONG TimerPeriod; +} PF_TRACE_LIMITS, *PPF_TRACE_LIMITS; + +typedef struct _PF_SYSTEM_PREFETCH_PARAMETERS +{ + PF_ENABLE_STATUS EnableStatus[2]; + PF_TRACE_LIMITS TraceLimits[2]; + ULONG MaxNumActiveTraces; + ULONG MaxNumSavedTraces; + WCHAR RootDirPath[32]; + WCHAR HostingApplicationList[128]; +} PF_SYSTEM_PREFETCH_PARAMETERS, *PPF_SYSTEM_PREFETCH_PARAMETERS; + +#define PF_BOOT_CONTROL_VERSION 1 + +typedef struct _PF_BOOT_CONTROL +{ + ULONG Version; + ULONG DisableBootPrefetching; +} PF_BOOT_CONTROL, *PPF_BOOT_CONTROL; + +typedef enum _PREFETCHER_INFORMATION_CLASS +{ + PrefetcherRetrieveTrace = 1, // q: CHAR[] + PrefetcherSystemParameters, // q: PF_SYSTEM_PREFETCH_PARAMETERS + PrefetcherBootPhase, // s: PF_BOOT_PHASE_ID + PrefetcherRetrieveBootLoaderTrace, // q: CHAR[] + PrefetcherBootControl // s: PF_BOOT_CONTROL +} PREFETCHER_INFORMATION_CLASS; + +#define PREFETCHER_INFORMATION_VERSION 23 // rev +#define PREFETCHER_INFORMATION_MAGIC ('kuhC') // rev + +typedef struct _PREFETCHER_INFORMATION +{ + ULONG Version; + ULONG Magic; + PREFETCHER_INFORMATION_CLASS PrefetcherInformationClass; + PVOID PrefetcherInformation; + ULONG PrefetcherInformationLength; +} PREFETCHER_INFORMATION, *PPREFETCHER_INFORMATION; + +// Superfetch + +typedef struct _PF_SYSTEM_SUPERFETCH_PARAMETERS +{ + ULONG EnabledComponents; + ULONG BootID; + ULONG SavedSectInfoTracesMax; + ULONG SavedPageAccessTracesMax; + ULONG ScenarioPrefetchTimeoutStandby; + ULONG ScenarioPrefetchTimeoutHibernate; +} PF_SYSTEM_SUPERFETCH_PARAMETERS, *PPF_SYSTEM_SUPERFETCH_PARAMETERS; + +#define PF_PFN_PRIO_REQUEST_VERSION 1 +#define PF_PFN_PRIO_REQUEST_QUERY_MEMORY_LIST 0x1 +#define PF_PFN_PRIO_REQUEST_VALID_FLAGS 0x1 + +typedef struct _PF_PFN_PRIO_REQUEST +{ + ULONG Version; + ULONG RequestFlags; + ULONG PfnCount; + SYSTEM_MEMORY_LIST_INFORMATION MemInfo; + MMPFN_IDENTITY PageData[256]; +} PF_PFN_PRIO_REQUEST, *PPF_PFN_PRIO_REQUEST; + +typedef enum _PFS_PRIVATE_PAGE_SOURCE_TYPE +{ + PfsPrivateSourceKernel, + PfsPrivateSourceSession, + PfsPrivateSourceProcess, + PfsPrivateSourceMax +} PFS_PRIVATE_PAGE_SOURCE_TYPE; + +typedef struct _PFS_PRIVATE_PAGE_SOURCE +{ + PFS_PRIVATE_PAGE_SOURCE_TYPE Type; + union + { + ULONG_PTR SessionId; + ULONG_PTR ProcessId; + }; + ULONG ImagePathHash; + ULONG UniqueProcessHash; +} PFS_PRIVATE_PAGE_SOURCE, *PPFS_PRIVATE_PAGE_SOURCE; + +typedef struct _PF_PRIVSOURCE_INFO +{ + PFS_PRIVATE_PAGE_SOURCE DbInfo; + union + { + ULONG_PTR EProcess; + ULONG_PTR GlobalVA; + }; + ULONG WsPrivatePages; + ULONG TotalPrivatePages; + ULONG SessionID; + CHAR ImageName[16]; +} PF_PRIVSOURCE_INFO, *PPF_PRIVSOURCE_INFO; + +#define PF_PRIVSOURCE_QUERY_REQUEST_VERSION 3 + +typedef struct _PF_PRIVSOURCE_QUERY_REQUEST +{ + ULONG Version; + ULONG InfoCount; + PF_PRIVSOURCE_INFO InfoArray[1]; +} PF_PRIVSOURCE_QUERY_REQUEST, *PPF_PRIVSOURCE_QUERY_REQUEST; + +typedef enum _PF_PHASED_SCENARIO_TYPE +{ + PfScenarioTypeNone, + PfScenarioTypeStandby, + PfScenarioTypeHibernate, + PfScenarioTypeFUS, + PfScenarioTypeMax +} PF_PHASED_SCENARIO_TYPE; + +#define PF_SCENARIO_PHASE_INFO_VERSION 4 + +typedef struct _PF_SCENARIO_PHASE_INFO +{ + ULONG Version; + PF_PHASED_SCENARIO_TYPE ScenType; + ULONG PhaseId; + ULONG SequenceNumber; + ULONG Flags; + ULONG FUSUserId; +} PF_SCENARIO_PHASE_INFO, *PPF_SCENARIO_PHASE_INFO; + +typedef struct _PF_MEMORY_LIST_NODE +{ + ULONGLONG Node : 8; + ULONGLONG Spare : 56; + ULONGLONG StandbyLowPageCount; + ULONGLONG StandbyMediumPageCount; + ULONGLONG StandbyHighPageCount; + ULONGLONG FreePageCount; + ULONGLONG ModifiedPageCount; +} PF_MEMORY_LIST_NODE, *PPF_MEMORY_LIST_NODE; + +#define PF_MEMORY_LIST_INFO_VERSION 1 + +typedef struct _PF_MEMORY_LIST_INFO +{ + ULONG Version; + ULONG Size; + ULONG NodeCount; + PF_MEMORY_LIST_NODE Nodes[1]; +} PF_MEMORY_LIST_INFO, *PPF_MEMORY_LIST_INFO; + +typedef struct _PF_PHYSICAL_MEMORY_RANGE +{ + ULONG BasePfn; + ULONG PageCount; +} PF_PHYSICAL_MEMORY_RANGE, *PPF_PHYSICAL_MEMORY_RANGE; + +#define PF_PHYSICAL_MEMORY_RANGE_INFO_VERSION 1 + +typedef struct _PF_PHYSICAL_MEMORY_RANGE_INFO +{ + ULONG Version; + ULONG RangeCount; + PF_PHYSICAL_MEMORY_RANGE Ranges[1]; +} PF_PHYSICAL_MEMORY_RANGE_INFO, *PPF_PHYSICAL_MEMORY_RANGE_INFO; + +// begin_rev + +#define PF_REPURPOSED_BY_PREFETCH_INFO_VERSION 1 + +typedef struct _PF_REPURPOSED_BY_PREFETCH_INFO +{ + ULONG Version; + ULONG RepurposedByPrefetch; +} PF_REPURPOSED_BY_PREFETCH_INFO, *PPF_REPURPOSED_BY_PREFETCH_INFO; + +// end_rev + +typedef enum _SUPERFETCH_INFORMATION_CLASS +{ + SuperfetchRetrieveTrace = 1, // q: CHAR[] + SuperfetchSystemParameters, // q: PF_SYSTEM_SUPERFETCH_PARAMETERS + SuperfetchLogEvent, + SuperfetchGenerateTrace, + SuperfetchPrefetch, + SuperfetchPfnQuery, // q: PF_PFN_PRIO_REQUEST + SuperfetchPfnSetPriority, + SuperfetchPrivSourceQuery, // q: PF_PRIVSOURCE_QUERY_REQUEST + SuperfetchSequenceNumberQuery, // q: ULONG + SuperfetchScenarioPhase, // 10 + SuperfetchWorkerPriority, + SuperfetchScenarioQuery, // q: PF_SCENARIO_PHASE_INFO + SuperfetchScenarioPrefetch, + SuperfetchRobustnessControl, + SuperfetchTimeControl, + SuperfetchMemoryListQuery, // q: PF_MEMORY_LIST_INFO + SuperfetchMemoryRangesQuery, // q: PF_PHYSICAL_MEMORY_RANGE_INFO + SuperfetchTracingControl, + SuperfetchTrimWhileAgingControl, + SuperfetchRepurposedByPrefetch, // q: PF_REPURPOSED_BY_PREFETCH_INFO // rev + SuperfetchInformationMax +} SUPERFETCH_INFORMATION_CLASS; + +#define SUPERFETCH_INFORMATION_VERSION 45 // rev +#define SUPERFETCH_INFORMATION_MAGIC ('kuhC') // rev + +typedef struct _SUPERFETCH_INFORMATION +{ + ULONG Version; + ULONG Magic; + SUPERFETCH_INFORMATION_CLASS InfoClass; + PVOID Data; + ULONG Length; +} SUPERFETCH_INFORMATION, *PSUPERFETCH_INFORMATION; + +// end_private + +#endif diff --git a/phnt/include/ntpnpapi.h b/phnt/include/ntpnpapi.h new file mode 100644 index 0000000..bc3f1a2 --- /dev/null +++ b/phnt/include/ntpnpapi.h @@ -0,0 +1,158 @@ +#ifndef _NTPNPAPI_H +#define _NTPNPAPI_H + +typedef enum _PLUGPLAY_EVENT_CATEGORY +{ + HardwareProfileChangeEvent, + TargetDeviceChangeEvent, + DeviceClassChangeEvent, + CustomDeviceEvent, + DeviceInstallEvent, + DeviceArrivalEvent, + PowerEvent, + VetoEvent, + BlockedDriverEvent, + InvalidIDEvent, + MaxPlugEventCategory +} PLUGPLAY_EVENT_CATEGORY, *PPLUGPLAY_EVENT_CATEGORY; + +typedef struct _PLUGPLAY_EVENT_BLOCK +{ + GUID EventGuid; + PLUGPLAY_EVENT_CATEGORY EventCategory; + PULONG Result; + ULONG Flags; + ULONG TotalSize; + PVOID DeviceObject; + + union + { + struct + { + GUID ClassGuid; + WCHAR SymbolicLinkName[1]; + } DeviceClass; + struct + { + WCHAR DeviceIds[1]; + } TargetDevice; + struct + { + WCHAR DeviceId[1]; + } InstallDevice; + struct + { + PVOID NotificationStructure; + WCHAR DeviceIds[1]; + } CustomNotification; + struct + { + PVOID Notification; + } ProfileNotification; + struct + { + ULONG NotificationCode; + ULONG NotificationData; + } PowerNotification; + struct + { + PNP_VETO_TYPE VetoType; + WCHAR DeviceIdVetoNameBuffer[1]; // DeviceIdVetoName + } VetoNotification; + struct + { + GUID BlockedDriverGuid; + } BlockedDriverNotification; + struct + { + WCHAR ParentId[1]; + } InvalidIDNotification; + } u; +} PLUGPLAY_EVENT_BLOCK, *PPLUGPLAY_EVENT_BLOCK; + +typedef enum _PLUGPLAY_CONTROL_CLASS +{ + PlugPlayControlEnumerateDevice, + PlugPlayControlRegisterNewDevice, + PlugPlayControlDeregisterDevice, + PlugPlayControlInitializeDevice, + PlugPlayControlStartDevice, + PlugPlayControlUnlockDevice, + PlugPlayControlQueryAndRemoveDevice, + PlugPlayControlUserResponse, + PlugPlayControlGenerateLegacyDevice, + PlugPlayControlGetInterfaceDeviceList, + PlugPlayControlProperty, + PlugPlayControlDeviceClassAssociation, + PlugPlayControlGetRelatedDevice, + PlugPlayControlGetInterfaceDeviceAlias, + PlugPlayControlDeviceStatus, + PlugPlayControlGetDeviceDepth, + PlugPlayControlQueryDeviceRelations, + PlugPlayControlTargetDeviceRelation, + PlugPlayControlQueryConflictList, + PlugPlayControlRetrieveDock, + PlugPlayControlResetDevice, + PlugPlayControlHaltDevice, + PlugPlayControlGetBlockedDriverList, + MaxPlugPlayControl +} PLUGPLAY_CONTROL_CLASS, *PPLUGPLAY_CONTROL_CLASS; + +#if (PHNT_VERSION < PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetPlugPlayEvent( + _In_ HANDLE EventHandle, + _In_opt_ PVOID Context, + _Out_writes_bytes_(EventBufferSize) PPLUGPLAY_EVENT_BLOCK EventBlock, + _In_ ULONG EventBufferSize + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPlugPlayControl( + _In_ PLUGPLAY_CONTROL_CLASS PnPControlClass, + _Inout_updates_bytes_(PnPControlDataLength) PVOID PnPControlData, + _In_ ULONG PnPControlDataLength + ); + +#if (PHNT_VERSION >= PHNT_WIN7) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSerializeBoot( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnableLastKnownGood( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDisableLastKnownGood( + VOID + ); + +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplacePartitionUnit( + _In_ PUNICODE_STRING TargetInstancePath, + _In_ PUNICODE_STRING SpareInstancePath, + _In_ ULONG Flags + ); +#endif + +#endif diff --git a/phnt/include/ntpoapi.h b/phnt/include/ntpoapi.h new file mode 100644 index 0000000..16736e1 --- /dev/null +++ b/phnt/include/ntpoapi.h @@ -0,0 +1,182 @@ +#ifndef _NTPOAPI_H +#define _NTPOAPI_H + +typedef union _POWER_STATE +{ + SYSTEM_POWER_STATE SystemState; + DEVICE_POWER_STATE DeviceState; +} POWER_STATE, *PPOWER_STATE; + +typedef enum _POWER_STATE_TYPE +{ + SystemPowerState = 0, + DevicePowerState +} POWER_STATE_TYPE, *PPOWER_STATE_TYPE; + +#if (PHNT_VERSION >= PHNT_VISTA) +// wdm +typedef struct _SYSTEM_POWER_STATE_CONTEXT +{ + union + { + struct + { + ULONG Reserved1 : 8; + ULONG TargetSystemState : 4; + ULONG EffectiveSystemState : 4; + ULONG CurrentSystemState : 4; + ULONG IgnoreHibernationPath : 1; + ULONG PseudoTransition : 1; + ULONG Reserved2 : 10; + }; + ULONG ContextAsUlong; + }; +} SYSTEM_POWER_STATE_CONTEXT, *PSYSTEM_POWER_STATE_CONTEXT; +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +/** \cond NEVER */ // disable doxygen warning +// wdm +typedef struct _COUNTED_REASON_CONTEXT +{ + ULONG Version; + ULONG Flags; + union + { + struct + { + UNICODE_STRING ResourceFileName; + USHORT ResourceReasonId; + ULONG StringCount; + PUNICODE_STRING _Field_size_(StringCount) ReasonStrings; + }; + UNICODE_STRING SimpleString; + }; +} COUNTED_REASON_CONTEXT, *PCOUNTED_REASON_CONTEXT; +/** \endcond */ +#endif + +typedef enum +{ + PowerStateSleeping1 = 0, + PowerStateSleeping2 = 1, + PowerStateSleeping3 = 2, + PowerStateSleeping4 = 3, + PowerStateSleeping4Firmware = 4, + PowerStateShutdownReset = 5, + PowerStateShutdownOff = 6, + PowerStateMaximum = 7 +} POWER_STATE_HANDLER_TYPE, *PPOWER_STATE_HANDLER_TYPE; + +typedef NTSTATUS (NTAPI *PENTER_STATE_SYSTEM_HANDLER)( + _In_ PVOID SystemContext + ); + +typedef NTSTATUS (NTAPI *PENTER_STATE_HANDLER)( + _In_ PVOID Context, + _In_opt_ PENTER_STATE_SYSTEM_HANDLER SystemHandler, + _In_ PVOID SystemContext, + _In_ LONG NumberProcessors, + _In_ volatile PLONG Number + ); + +typedef struct _POWER_STATE_HANDLER +{ + POWER_STATE_HANDLER_TYPE Type; + BOOLEAN RtcWake; + UCHAR Spare[3]; + PENTER_STATE_HANDLER Handler; + PVOID Context; +} POWER_STATE_HANDLER, *PPOWER_STATE_HANDLER; + +typedef NTSTATUS (NTAPI *PENTER_STATE_NOTIFY_HANDLER)( + _In_ POWER_STATE_HANDLER_TYPE State, + _In_ PVOID Context, + _In_ BOOLEAN Entering + ); + +typedef struct _POWER_STATE_NOTIFY_HANDLER +{ + PENTER_STATE_NOTIFY_HANDLER Handler; + PVOID Context; +} POWER_STATE_NOTIFY_HANDLER, *PPOWER_STATE_NOTIFY_HANDLER; + +typedef struct _PROCESSOR_POWER_INFORMATION +{ + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; + +typedef struct _SYSTEM_POWER_INFORMATION +{ + ULONG MaxIdlenessAllowed; + ULONG Idleness; + ULONG TimeRemaining; + UCHAR CoolingMode; +} SYSTEM_POWER_INFORMATION, *PSYSTEM_POWER_INFORMATION; + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPowerInformation( + _In_ POWER_INFORMATION_LEVEL InformationLevel, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetThreadExecutionState( + _In_ EXECUTION_STATE NewFlags, // ES_* flags + _Out_ EXECUTION_STATE *PreviousFlags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRequestWakeupLatency( + _In_ LATENCY_TIME latency + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtInitiatePowerAction( + _In_ POWER_ACTION SystemAction, + _In_ SYSTEM_POWER_STATE LightestSystemState, + _In_ ULONG Flags, // POWER_ACTION_* flags + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetSystemPowerState( + _In_ POWER_ACTION SystemAction, + _In_ SYSTEM_POWER_STATE LightestSystemState, + _In_ ULONG Flags // POWER_ACTION_* flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetDevicePowerState( + _In_ HANDLE Device, + _Out_ PDEVICE_POWER_STATE State + ); + +NTSYSCALLAPI +BOOLEAN +NTAPI +NtIsSystemResumeAutomatic( + VOID + ); + +#endif diff --git a/phnt/include/ntpsapi.h b/phnt/include/ntpsapi.h new file mode 100644 index 0000000..1a94f4a --- /dev/null +++ b/phnt/include/ntpsapi.h @@ -0,0 +1,1477 @@ +#ifndef _NTPSAPI_H +#define _NTPSAPI_H + +#if (PHNT_MODE == PHNT_MODE_KERNEL) +#define PROCESS_TERMINATE 0x0001 +#define PROCESS_CREATE_THREAD 0x0002 +#define PROCESS_SET_SESSIONID 0x0004 +#define PROCESS_VM_OPERATION 0x0008 +#define PROCESS_VM_READ 0x0010 +#define PROCESS_VM_WRITE 0x0020 +#define PROCESS_CREATE_PROCESS 0x0080 +#define PROCESS_SET_QUOTA 0x0100 +#define PROCESS_SET_INFORMATION 0x0200 +#define PROCESS_QUERY_INFORMATION 0x0400 +#define PROCESS_SET_PORT 0x0800 +#define PROCESS_SUSPEND_RESUME 0x0800 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000 +#else +#ifndef PROCESS_SET_PORT +#define PROCESS_SET_PORT 0x0800 +#endif +#endif + +#if (PHNT_MODE == PHNT_MODE_KERNEL) +#define THREAD_QUERY_INFORMATION 0x0040 +#define THREAD_SET_THREAD_TOKEN 0x0080 +#define THREAD_IMPERSONATE 0x0100 +#define THREAD_DIRECT_IMPERSONATION 0x0200 +#else +#ifndef THREAD_ALERT +#define THREAD_ALERT 0x0004 +#endif +#endif + +#if (PHNT_MODE == PHNT_MODE_KERNEL) +#define JOB_OBJECT_ASSIGN_PROCESS 0x0001 +#define JOB_OBJECT_SET_ATTRIBUTES 0x0002 +#define JOB_OBJECT_QUERY 0x0004 +#define JOB_OBJECT_TERMINATE 0x0008 +#define JOB_OBJECT_SET_SECURITY_ATTRIBUTES 0x0010 +#define JOB_OBJECT_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1f) +#endif + +#define GDI_HANDLE_BUFFER_SIZE32 34 +#define GDI_HANDLE_BUFFER_SIZE64 60 + +#ifndef WIN64 +#define GDI_HANDLE_BUFFER_SIZE GDI_HANDLE_BUFFER_SIZE32 +#else +#define GDI_HANDLE_BUFFER_SIZE GDI_HANDLE_BUFFER_SIZE64 +#endif + +typedef ULONG GDI_HANDLE_BUFFER[GDI_HANDLE_BUFFER_SIZE]; + +typedef ULONG GDI_HANDLE_BUFFER32[GDI_HANDLE_BUFFER_SIZE32]; +typedef ULONG GDI_HANDLE_BUFFER64[GDI_HANDLE_BUFFER_SIZE64]; + +#define FLS_MAXIMUM_AVAILABLE 128 +#define TLS_MINIMUM_AVAILABLE 64 +#define TLS_EXPANSION_SLOTS 1024 + +// symbols +typedef struct _PEB_LDR_DATA +{ + ULONG Length; + BOOLEAN Initialized; + HANDLE SsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID EntryInProgress; + BOOLEAN ShutdownInProgress; + HANDLE ShutdownThreadId; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct _INITIAL_TEB +{ + struct + { + PVOID OldStackBase; + PVOID OldStackLimit; + } OldInitialTeb; + PVOID StackBase; + PVOID StackLimit; + PVOID StackAllocationBase; +} INITIAL_TEB, *PINITIAL_TEB; + +typedef struct _WOW64_PROCESS +{ + PVOID Wow64; +} WOW64_PROCESS, *PWOW64_PROCESS; + +#include + +// source:http://www.microsoft.com/whdc/system/Sysinternals/MoreThan64proc.mspx + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +typedef enum _PROCESSINFOCLASS +{ + ProcessBasicInformation, // q: PROCESS_BASIC_INFORMATION, PROCESS_EXTENDED_BASIC_INFORMATION + ProcessQuotaLimits, // qs: QUOTA_LIMITS, QUOTA_LIMITS_EX + ProcessIoCounters, // q: IO_COUNTERS + ProcessVmCounters, // q: VM_COUNTERS, VM_COUNTERS_EX, VM_COUNTERS_EX2 + ProcessTimes, // q: KERNEL_USER_TIMES + ProcessBasePriority, // s: KPRIORITY + ProcessRaisePriority, // s: ULONG + ProcessDebugPort, // q: HANDLE + ProcessExceptionPort, // s: HANDLE + ProcessAccessToken, // s: PROCESS_ACCESS_TOKEN + ProcessLdtInformation, // qs: PROCESS_LDT_INFORMATION // 10 + ProcessLdtSize, // s: PROCESS_LDT_SIZE + ProcessDefaultHardErrorMode, // qs: ULONG + ProcessIoPortHandlers, // (kernel-mode only) + ProcessPooledUsageAndLimits, // q: POOLED_USAGE_AND_LIMITS + ProcessWorkingSetWatch, // q: PROCESS_WS_WATCH_INFORMATION[]; s: void + ProcessUserModeIOPL, + ProcessEnableAlignmentFaultFixup, // s: BOOLEAN + ProcessPriorityClass, // qs: PROCESS_PRIORITY_CLASS + ProcessWx86Information, + ProcessHandleCount, // q: ULONG, PROCESS_HANDLE_INFORMATION // 20 + ProcessAffinityMask, // s: KAFFINITY + ProcessPriorityBoost, // qs: ULONG + ProcessDeviceMap, // qs: PROCESS_DEVICEMAP_INFORMATION, PROCESS_DEVICEMAP_INFORMATION_EX + ProcessSessionInformation, // q: PROCESS_SESSION_INFORMATION + ProcessForegroundInformation, // s: PROCESS_FOREGROUND_BACKGROUND + ProcessWow64Information, // q: ULONG_PTR + ProcessImageFileName, // q: UNICODE_STRING + ProcessLUIDDeviceMapsEnabled, // q: ULONG + ProcessBreakOnTermination, // qs: ULONG + ProcessDebugObjectHandle, // q: HANDLE // 30 + ProcessDebugFlags, // qs: ULONG + ProcessHandleTracing, // q: PROCESS_HANDLE_TRACING_QUERY; s: size 0 disables, otherwise enables + ProcessIoPriority, // qs: IO_PRIORITY_HINT + ProcessExecuteFlags, // qs: ULONG + ProcessResourceManagement, + ProcessCookie, // q: ULONG + ProcessImageInformation, // q: SECTION_IMAGE_INFORMATION + ProcessCycleTime, // q: PROCESS_CYCLE_TIME_INFORMATION // since VISTA + ProcessPagePriority, // q: ULONG + ProcessInstrumentationCallback, // 40 + ProcessThreadStackAllocation, // s: PROCESS_STACK_ALLOCATION_INFORMATION, PROCESS_STACK_ALLOCATION_INFORMATION_EX + ProcessWorkingSetWatchEx, // q: PROCESS_WS_WATCH_INFORMATION_EX[] + ProcessImageFileNameWin32, // q: UNICODE_STRING + ProcessImageFileMapping, // q: HANDLE (input) + ProcessAffinityUpdateMode, // qs: PROCESS_AFFINITY_UPDATE_MODE + ProcessMemoryAllocationMode, // qs: PROCESS_MEMORY_ALLOCATION_MODE + ProcessGroupInformation, // q: USHORT[] + ProcessTokenVirtualizationEnabled, // s: ULONG + ProcessConsoleHostProcess, // q: ULONG_PTR + ProcessWindowInformation, // q: PROCESS_WINDOW_INFORMATION // 50 + ProcessHandleInformation, // q: PROCESS_HANDLE_SNAPSHOT_INFORMATION // since WIN8 + ProcessMitigationPolicy, // s: PROCESS_MITIGATION_POLICY_INFORMATION + ProcessDynamicFunctionTableInformation, + ProcessHandleCheckingMode, + ProcessKeepAliveCount, // q: PROCESS_KEEPALIVE_COUNT_INFORMATION + ProcessRevokeFileHandles, // s: PROCESS_REVOKE_FILE_HANDLES_INFORMATION + ProcessWorkingSetControl, // s: PROCESS_WORKING_SET_CONTROL + ProcessHandleTable, // since WINBLUE + ProcessCheckStackExtentsMode, + ProcessCommandLineInformation, // q: UNICODE_STRING // 60 + ProcessProtectionInformation, // q: PS_PROTECTION + ProcessMemoryExhaustion, // PROCESS_MEMORY_EXHAUSTION_INFO // since THRESHOLD + ProcessFaultInformation, // PROCESS_FAULT_INFORMATION + ProcessTelemetryIdInformation, // PROCESS_TELEMETRY_ID_INFORMATION + ProcessCommitReleaseInformation, // PROCESS_COMMIT_RELEASE_INFORMATION + ProcessDefaultCpuSetsInformation, + ProcessAllowedCpuSetsInformation, + ProcessReserved1Information, + ProcessReserved2Information, + ProcessSubsystemProcess, // 70 + ProcessJobMemoryInformation, // PROCESS_JOB_MEMORY_INFO + ProcessInPrivate, // since THRESHOLD2 + ProcessRaiseUMExceptionOnInvalidHandleClose, + MaxProcessInfoClass +} PROCESSINFOCLASS; +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +typedef enum _THREADINFOCLASS +{ + ThreadBasicInformation, // q: THREAD_BASIC_INFORMATION + ThreadTimes, // q: KERNEL_USER_TIMES + ThreadPriority, // s: KPRIORITY + ThreadBasePriority, // s: LONG + ThreadAffinityMask, // s: KAFFINITY + ThreadImpersonationToken, // s: HANDLE + ThreadDescriptorTableEntry, // q: DESCRIPTOR_TABLE_ENTRY (or WOW64_DESCRIPTOR_TABLE_ENTRY) + ThreadEnableAlignmentFaultFixup, // s: BOOLEAN + ThreadEventPair, + ThreadQuerySetWin32StartAddress, // q: PVOID + ThreadZeroTlsCell, // 10 + ThreadPerformanceCount, // q: LARGE_INTEGER + ThreadAmILastThread, // q: ULONG + ThreadIdealProcessor, // s: ULONG + ThreadPriorityBoost, // qs: ULONG + ThreadSetTlsArrayAddress, + ThreadIsIoPending, // q: ULONG + ThreadHideFromDebugger, // s: void + ThreadBreakOnTermination, // qs: ULONG + ThreadSwitchLegacyState, + ThreadIsTerminated, // q: ULONG // 20 + ThreadLastSystemCall, // q: THREAD_LAST_SYSCALL_INFORMATION + ThreadIoPriority, // qs: IO_PRIORITY_HINT + ThreadCycleTime, // q: THREAD_CYCLE_TIME_INFORMATION + ThreadPagePriority, // q: ULONG + ThreadActualBasePriority, + ThreadTebInformation, // q: THREAD_TEB_INFORMATION (requires THREAD_GET_CONTEXT + THREAD_SET_CONTEXT) + ThreadCSwitchMon, + ThreadCSwitchPmu, + ThreadWow64Context, // q: WOW64_CONTEXT + ThreadGroupInformation, // q: GROUP_AFFINITY // 30 + ThreadUmsInformation, + ThreadCounterProfiling, + ThreadIdealProcessorEx, // q: PROCESSOR_NUMBER + ThreadCpuAccountingInformation, // since WIN8 + ThreadSuspendCount, // since WINBLUE + ThreadHeterogeneousCpuPolicy, // q: KHETERO_CPU_POLICY // since THRESHOLD + ThreadContainerId, // q: GUID + ThreadNameInformation, + ThreadSelectedCpuSets, + ThreadSystemThreadInformation, // q: SYSTEM_THREAD_INFORMATION // 40 + ThreadActualGroupAffinity, // since THRESHOLD2 + MaxThreadInfoClass +} THREADINFOCLASS; +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +// Use with both ProcessPagePriority and ThreadPagePriority +typedef struct _PAGE_PRIORITY_INFORMATION +{ + ULONG PagePriority; +} PAGE_PRIORITY_INFORMATION, *PPAGE_PRIORITY_INFORMATION; +#endif + +// Process information structures + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +typedef struct _PROCESS_BASIC_INFORMATION +{ + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + ULONG_PTR AffinityMask; + KPRIORITY BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; +} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; + +typedef struct _PROCESS_EXTENDED_BASIC_INFORMATION +{ + SIZE_T Size; // set to sizeof structure on input + PROCESS_BASIC_INFORMATION BasicInfo; + union + { + ULONG Flags; + struct + { + ULONG IsProtectedProcess : 1; + ULONG IsWow64Process : 1; + ULONG IsProcessDeleting : 1; + ULONG IsCrossSessionCreate : 1; + ULONG IsFrozen : 1; + ULONG IsBackground : 1; + ULONG IsStronglyNamed : 1; + ULONG IsSecureProcess : 1; + ULONG SpareBits : 24; + }; + }; +} PROCESS_EXTENDED_BASIC_INFORMATION, *PPROCESS_EXTENDED_BASIC_INFORMATION; + +typedef struct _VM_COUNTERS +{ + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +} VM_COUNTERS, *PVM_COUNTERS; + +typedef struct _VM_COUNTERS_EX +{ + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivateUsage; +} VM_COUNTERS_EX, *PVM_COUNTERS_EX; + +// private +typedef struct _VM_COUNTERS_EX2 +{ + VM_COUNTERS_EX CountersEx; + SIZE_T PrivateWorkingSetSize; + SIZE_T SharedCommitUsage; +} VM_COUNTERS_EX2, *PVM_COUNTERS_EX2; + +typedef struct _KERNEL_USER_TIMES +{ + LARGE_INTEGER CreateTime; + LARGE_INTEGER ExitTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; +} KERNEL_USER_TIMES, *PKERNEL_USER_TIMES; + +typedef struct _POOLED_USAGE_AND_LIMITS +{ + SIZE_T PeakPagedPoolUsage; + SIZE_T PagedPoolUsage; + SIZE_T PagedPoolLimit; + SIZE_T PeakNonPagedPoolUsage; + SIZE_T NonPagedPoolUsage; + SIZE_T NonPagedPoolLimit; + SIZE_T PeakPagefileUsage; + SIZE_T PagefileUsage; + SIZE_T PagefileLimit; +} POOLED_USAGE_AND_LIMITS, *PPOOLED_USAGE_AND_LIMITS; + +typedef struct _PROCESS_ACCESS_TOKEN +{ + HANDLE Token; // needs TOKEN_ASSIGN_PRIMARY access + HANDLE Thread; // handle to initial/only thread; needs THREAD_QUERY_INFORMATION access +} PROCESS_ACCESS_TOKEN, *PPROCESS_ACCESS_TOKEN; + +typedef struct _PROCESS_LDT_INFORMATION +{ + ULONG Start; + ULONG Length; + LDT_ENTRY LdtEntries[1]; +} PROCESS_LDT_INFORMATION, *PPROCESS_LDT_INFORMATION; + +typedef struct _PROCESS_LDT_SIZE +{ + ULONG Length; +} PROCESS_LDT_SIZE, *PPROCESS_LDT_SIZE; + +typedef struct _PROCESS_WS_WATCH_INFORMATION +{ + PVOID FaultingPc; + PVOID FaultingVa; +} PROCESS_WS_WATCH_INFORMATION, *PPROCESS_WS_WATCH_INFORMATION; + +#endif + +// psapi:PSAPI_WS_WATCH_INFORMATION_EX +typedef struct _PROCESS_WS_WATCH_INFORMATION_EX +{ + PROCESS_WS_WATCH_INFORMATION BasicInfo; + ULONG_PTR FaultingThreadId; + ULONG_PTR Flags; +} PROCESS_WS_WATCH_INFORMATION_EX, *PPROCESS_WS_WATCH_INFORMATION_EX; + +#define PROCESS_PRIORITY_CLASS_UNKNOWN 0 +#define PROCESS_PRIORITY_CLASS_IDLE 1 +#define PROCESS_PRIORITY_CLASS_NORMAL 2 +#define PROCESS_PRIORITY_CLASS_HIGH 3 +#define PROCESS_PRIORITY_CLASS_REALTIME 4 +#define PROCESS_PRIORITY_CLASS_BELOW_NORMAL 5 +#define PROCESS_PRIORITY_CLASS_ABOVE_NORMAL 6 + +typedef struct _PROCESS_PRIORITY_CLASS +{ + BOOLEAN Foreground; + UCHAR PriorityClass; +} PROCESS_PRIORITY_CLASS, *PPROCESS_PRIORITY_CLASS; + +typedef struct _PROCESS_FOREGROUND_BACKGROUND +{ + BOOLEAN Foreground; +} PROCESS_FOREGROUND_BACKGROUND, *PPROCESS_FOREGROUND_BACKGROUND; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +typedef struct _PROCESS_DEVICEMAP_INFORMATION +{ + union + { + struct + { + HANDLE DirectoryHandle; + } Set; + struct + { + ULONG DriveMap; + UCHAR DriveType[32]; + } Query; + }; +} PROCESS_DEVICEMAP_INFORMATION, *PPROCESS_DEVICEMAP_INFORMATION; + +#define PROCESS_LUID_DOSDEVICES_ONLY 0x00000001 + +typedef struct _PROCESS_DEVICEMAP_INFORMATION_EX +{ + union + { + struct + { + HANDLE DirectoryHandle; + } Set; + struct + { + ULONG DriveMap; + UCHAR DriveType[32]; + } Query; + }; + ULONG Flags; // PROCESS_LUID_DOSDEVICES_ONLY +} PROCESS_DEVICEMAP_INFORMATION_EX, *PPROCESS_DEVICEMAP_INFORMATION_EX; + +typedef struct _PROCESS_SESSION_INFORMATION +{ + ULONG SessionId; +} PROCESS_SESSION_INFORMATION, *PPROCESS_SESSION_INFORMATION; + +typedef struct _PROCESS_HANDLE_TRACING_ENABLE +{ + ULONG Flags; // 0 to disable, 1 to enable +} PROCESS_HANDLE_TRACING_ENABLE, *PPROCESS_HANDLE_TRACING_ENABLE; + +typedef struct _PROCESS_HANDLE_TRACING_ENABLE_EX +{ + ULONG Flags; // 0 to disable, 1 to enable + ULONG TotalSlots; +} PROCESS_HANDLE_TRACING_ENABLE_EX, *PPROCESS_HANDLE_TRACING_ENABLE_EX; + +#define PROCESS_HANDLE_TRACING_MAX_STACKS 16 +#define HANDLE_TRACE_DB_OPEN 1 +#define HANDLE_TRACE_DB_CLOSE 2 +#define HANDLE_TRACE_DB_BADREF 3 + +typedef struct _PROCESS_HANDLE_TRACING_ENTRY +{ + HANDLE Handle; + CLIENT_ID ClientId; + ULONG Type; + PVOID Stacks[PROCESS_HANDLE_TRACING_MAX_STACKS]; +} PROCESS_HANDLE_TRACING_ENTRY, *PPROCESS_HANDLE_TRACING_ENTRY; + +typedef struct _PROCESS_HANDLE_TRACING_QUERY +{ + HANDLE Handle; + ULONG TotalTraces; + PROCESS_HANDLE_TRACING_ENTRY HandleTrace[1]; +} PROCESS_HANDLE_TRACING_QUERY, *PPROCESS_HANDLE_TRACING_QUERY; + +#endif + +// private +typedef struct _PROCESS_STACK_ALLOCATION_INFORMATION +{ + SIZE_T ReserveSize; + SIZE_T ZeroBits; + PVOID StackBase; +} PROCESS_STACK_ALLOCATION_INFORMATION, *PPROCESS_STACK_ALLOCATION_INFORMATION; + +// private +typedef struct _PROCESS_STACK_ALLOCATION_INFORMATION_EX +{ + ULONG PreferredNode; + ULONG Reserved0; + ULONG Reserved1; + ULONG Reserved2; + PROCESS_STACK_ALLOCATION_INFORMATION AllocInfo; +} PROCESS_STACK_ALLOCATION_INFORMATION_EX, *PPROCESS_STACK_ALLOCATION_INFORMATION_EX; + +// private +typedef union _PROCESS_AFFINITY_UPDATE_MODE +{ + ULONG Flags; + struct + { + ULONG EnableAutoUpdate : 1; + ULONG Permanent : 1; + ULONG Reserved : 30; + }; +} PROCESS_AFFINITY_UPDATE_MODE, *PPROCESS_AFFINITY_UPDATE_MODE; + +// private +typedef union _PROCESS_MEMORY_ALLOCATION_MODE +{ + ULONG Flags; + struct + { + ULONG TopDown : 1; + ULONG Reserved : 31; + }; +} PROCESS_MEMORY_ALLOCATION_MODE, *PPROCESS_MEMORY_ALLOCATION_MODE; + +// private +typedef struct _PROCESS_HANDLE_INFORMATION +{ + ULONG HandleCount; + ULONG HandleCountHighWatermark; +} PROCESS_HANDLE_INFORMATION, *PPROCESS_HANDLE_INFORMATION; + +// private +typedef struct _PROCESS_CYCLE_TIME_INFORMATION +{ + ULONGLONG AccumulatedCycles; + ULONGLONG CurrentCycleCount; +} PROCESS_CYCLE_TIME_INFORMATION, *PPROCESS_CYCLE_TIME_INFORMATION; + +// private +typedef struct _PROCESS_WINDOW_INFORMATION +{ + ULONG WindowFlags; + USHORT WindowTitleLength; + WCHAR WindowTitle[1]; +} PROCESS_WINDOW_INFORMATION, *PPROCESS_WINDOW_INFORMATION; + +// private +typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO +{ + HANDLE HandleValue; + ULONG_PTR HandleCount; + ULONG_PTR PointerCount; + ULONG GrantedAccess; + ULONG ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} PROCESS_HANDLE_TABLE_ENTRY_INFO, *PPROCESS_HANDLE_TABLE_ENTRY_INFO; + +// private +typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION +{ + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1]; +} PROCESS_HANDLE_SNAPSHOT_INFORMATION, *PPROCESS_HANDLE_SNAPSHOT_INFORMATION; + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +// private +typedef struct _PROCESS_MITIGATION_POLICY_INFORMATION +{ + PROCESS_MITIGATION_POLICY Policy; + union + { + PROCESS_MITIGATION_ASLR_POLICY ASLRPolicy; + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY StrictHandleCheckPolicy; + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY SystemCallDisablePolicy; + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY ExtensionPointDisablePolicy; + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY DynamicCodePolicy; + PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY ControlFlowGuardPolicy; + PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY SignaturePolicy; + PROCESS_MITIGATION_FONT_DISABLE_POLICY FontDisablePolicy; + PROCESS_MITIGATION_IMAGE_LOAD_POLICY ImageLoadPolicy; + }; +} PROCESS_MITIGATION_POLICY_INFORMATION, *PPROCESS_MITIGATION_POLICY_INFORMATION; + +typedef struct _PROCESS_KEEPALIVE_COUNT_INFORMATION +{ + ULONG WakeCount; + ULONG NoWakeCount; +} PROCESS_KEEPALIVE_COUNT_INFORMATION, *PPROCESS_KEEPALIVE_COUNT_INFORMATION; + +typedef struct _PROCESS_REVOKE_FILE_HANDLES_INFORMATION +{ + UNICODE_STRING TargetDevicePath; +} PROCESS_REVOKE_FILE_HANDLES_INFORMATION, *PPROCESS_REVOKE_FILE_HANDLES_INFORMATION; + +// begin_private + +typedef enum _PROCESS_WORKING_SET_OPERATION +{ + ProcessWorkingSetSwap, + ProcessWorkingSetEmpty, + ProcessWorkingSetOperationMax +} PROCESS_WORKING_SET_OPERATION; + +typedef struct _PROCESS_WORKING_SET_CONTROL +{ + ULONG Version; + PROCESS_WORKING_SET_OPERATION Operation; + ULONG Flags; +} PROCESS_WORKING_SET_CONTROL, *PPROCESS_WORKING_SET_CONTROL; + +typedef enum _PS_PROTECTED_TYPE +{ + PsProtectedTypeNone, + PsProtectedTypeProtectedLight, + PsProtectedTypeProtected, + PsProtectedTypeMax +} PS_PROTECTED_TYPE; + +typedef enum _PS_PROTECTED_SIGNER +{ + PsProtectedSignerNone, + PsProtectedSignerAuthenticode, + PsProtectedSignerCodeGen, + PsProtectedSignerAntimalware, + PsProtectedSignerLsa, + PsProtectedSignerWindows, + PsProtectedSignerWinTcb, + PsProtectedSignerMax +} PS_PROTECTED_SIGNER; + +typedef struct _PS_PROTECTION +{ + union + { + UCHAR Level; + struct + { + UCHAR Type : 3; + UCHAR Audit : 1; + UCHAR Signer : 4; + }; + }; +} PS_PROTECTION, *PPS_PROTECTION; + +typedef struct _PROCESS_FAULT_INFORMATION +{ + ULONG FaultFlags; + ULONG AdditionalInfo; +} PROCESS_FAULT_INFORMATION, *PPROCESS_FAULT_INFORMATION; + +typedef struct _PROCESS_TELEMETRY_ID_INFORMATION +{ + ULONG HeaderSize; + ULONG ProcessId; + ULONGLONG ProcessStartKey; + ULONGLONG CreateTime; + ULONGLONG CreateInterruptTime; + ULONGLONG CreateUnbiasedInterruptTime; + ULONGLONG ProcessSequenceNumber; + ULONGLONG SessionCreateTime; + ULONG SessionId; + ULONG BootId; + ULONG ImageChecksum; + ULONG ImageTimeDateStamp; + ULONG UserSidOffset; + ULONG ImagePathOffset; + ULONG PackageNameOffset; + ULONG RelativeAppNameOffset; + ULONG CommandLineOffset; +} PROCESS_TELEMETRY_ID_INFORMATION, *PPROCESS_TELEMETRY_ID_INFORMATION; + +typedef struct _PROCESS_COMMIT_RELEASE_INFORMATION +{ + ULONG Version; + struct + { + ULONG Eligible : 1; + ULONG Spare : 31; + }; + SIZE_T CommitDebt; +} PROCESS_COMMIT_RELEASE_INFORMATION, *PPROCESS_COMMIT_RELEASE_INFORMATION; + +typedef struct _PROCESS_JOB_MEMORY_INFO +{ + ULONGLONG SharedCommitUsage; + ULONGLONG PrivateCommitUsage; + ULONGLONG PeakPrivateCommitUsage; + ULONGLONG PrivateCommitLimit; + ULONGLONG TotalCommitLimit; +} PROCESS_JOB_MEMORY_INFO, *PPROCESS_JOB_MEMORY_INFO; + +// end_private + +#endif + +// Thread information structures + +typedef struct _THREAD_BASIC_INFORMATION +{ + NTSTATUS ExitStatus; + PTEB TebBaseAddress; + CLIENT_ID ClientId; + ULONG_PTR AffinityMask; + KPRIORITY Priority; + LONG BasePriority; +} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION; + +// private +typedef struct _THREAD_LAST_SYSCALL_INFORMATION +{ + PVOID FirstArgument; + USHORT SystemCallNumber; +} THREAD_LAST_SYSCALL_INFORMATION, *PTHREAD_LAST_SYSCALL_INFORMATION; + +// private +typedef struct _THREAD_CYCLE_TIME_INFORMATION +{ + ULONGLONG AccumulatedCycles; + ULONGLONG CurrentCycleCount; +} THREAD_CYCLE_TIME_INFORMATION, *PTHREAD_CYCLE_TIME_INFORMATION; + +// private +typedef struct _THREAD_TEB_INFORMATION +{ + PVOID TebInformation; // buffer to place data in + ULONG TebOffset; // offset in TEB to begin reading from + ULONG BytesToRead; // number of bytes to read +} THREAD_TEB_INFORMATION, *PTHREAD_TEB_INFORMATION; + +// symbols +typedef struct _COUNTER_READING +{ + HARDWARE_COUNTER_TYPE Type; + ULONG Index; + ULONG64 Start; + ULONG64 Total; +} COUNTER_READING, *PCOUNTER_READING; + +// symbols +typedef struct _THREAD_PERFORMANCE_DATA +{ + USHORT Size; + USHORT Version; + PROCESSOR_NUMBER ProcessorNumber; + ULONG ContextSwitches; + ULONG HwCountersCount; + ULONG64 UpdateCount; + ULONG64 WaitReasonBitMap; + ULONG64 HardwareCounters; + COUNTER_READING CycleTime; + COUNTER_READING HwCounters[MAX_HW_COUNTERS]; +} THREAD_PERFORMANCE_DATA, *PTHREAD_PERFORMANCE_DATA; + +// private +typedef struct _THREAD_PROFILING_INFORMATION +{ + ULONG64 HardwareCounters; + ULONG Flags; + ULONG Enable; + PTHREAD_PERFORMANCE_DATA PerformanceData; +} THREAD_PROFILING_INFORMATION, *PTHREAD_PROFILING_INFORMATION; + +// Processes + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ParentProcess, + _In_ BOOLEAN InheritObjectTable, + _In_opt_ HANDLE SectionHandle, + _In_opt_ HANDLE DebugPort, + _In_opt_ HANDLE ExceptionPort + ); + +#define PROCESS_CREATE_FLAGS_BREAKAWAY 0x00000001 +#define PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT 0x00000002 +#define PROCESS_CREATE_FLAGS_INHERIT_HANDLES 0x00000004 +#define PROCESS_CREATE_FLAGS_OVERRIDE_ADDRESS_SPACE 0x00000008 +#define PROCESS_CREATE_FLAGS_LARGE_PAGES 0x00000010 + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateProcessEx( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ParentProcess, + _In_ ULONG Flags, + _In_opt_ HANDLE SectionHandle, + _In_opt_ HANDLE DebugPort, + _In_opt_ HANDLE ExceptionPort, + _In_ ULONG JobMemberLevel + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PCLIENT_ID ClientId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTerminateProcess( + _In_opt_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSuspendProcess( + _In_ HANDLE ProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtResumeProcess( + _In_ HANDLE ProcessHandle + ); + +#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) +#define ZwCurrentProcess() NtCurrentProcess() +#define NtCurrentThread() ((HANDLE)(LONG_PTR)-2) +#define ZwCurrentThread() NtCurrentThread() +#define NtCurrentSession() ((HANDLE)(LONG_PTR)-3) +#define ZwCurrentSession() NtCurrentSession() +#define NtCurrentPeb() (NtCurrentTeb()->ProcessEnvironmentBlock) + +// Not NT, but useful. +#define NtCurrentProcessId() (NtCurrentTeb()->ClientId.UniqueProcess) +#define NtCurrentThreadId() (NtCurrentTeb()->ClientId.UniqueThread) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetNextProcess( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Flags, + _Out_ PHANDLE NewProcessHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetNextThread( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Flags, + _Out_ PHANDLE NewThreadHandle + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _In_reads_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryPortInformationProcess( + VOID + ); + +#endif + +// Threads + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ProcessHandle, + _Out_ PCLIENT_ID ClientId, + _In_ PCONTEXT ThreadContext, + _In_ PINITIAL_TEB InitialTeb, + _In_ BOOLEAN CreateSuspended + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PCLIENT_ID ClientId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTerminateThread( + _In_opt_ HANDLE ThreadHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSuspendThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtResumeThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +ULONG +NTAPI +NtGetCurrentProcessorNumber( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetContextThread( + _In_ HANDLE ThreadHandle, + _Inout_ PCONTEXT ThreadContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetContextThread( + _In_ HANDLE ThreadHandle, + _In_ PCONTEXT ThreadContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationThread( + _In_ HANDLE ThreadHandle, + _In_ THREADINFOCLASS ThreadInformationClass, + _Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationThread( + _In_ HANDLE ThreadHandle, + _In_ THREADINFOCLASS ThreadInformationClass, + _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlertThread( + _In_ HANDLE ThreadHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlertResumeThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTestAlert( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtImpersonateThread( + _In_ HANDLE ServerThreadHandle, + _In_ HANDLE ClientThreadHandle, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRegisterThreadTerminatePort( + _In_ HANDLE PortHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetLdtEntries( + _In_ ULONG Selector0, + _In_ ULONG Entry0Low, + _In_ ULONG Entry0Hi, + _In_ ULONG Selector1, + _In_ ULONG Entry1Low, + _In_ ULONG Entry1Hi + ); + +typedef VOID (*PPS_APC_ROUTINE)( + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueueApcThread( + _In_ HANDLE ThreadHandle, + _In_ PPS_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueueApcThreadEx( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE UserApcReserveHandle, + _In_ PPS_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN8) + +// rev +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAlertThreadByThreadId( + _In_ HANDLE ThreadId + ); + +// rev +NTSYSCALLAPI +NTSTATUS +NTAPI +NtWaitForAlertByThreadId( + _In_ PVOID Address, + _In_opt_ PLARGE_INTEGER Timeout + ); + +#endif + +#endif + +// User processes and threads + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +// Attributes + +// begin_rev +#define PS_ATTRIBUTE_NUMBER_MASK 0x0000ffff +#define PS_ATTRIBUTE_THREAD 0x00010000 // can be used with threads +#define PS_ATTRIBUTE_INPUT 0x00020000 // input only +#define PS_ATTRIBUTE_UNKNOWN 0x00040000 +// end_rev + +// private +typedef enum _PS_ATTRIBUTE_NUM +{ + PsAttributeParentProcess, // in HANDLE + PsAttributeDebugPort, // in HANDLE + PsAttributeToken, // in HANDLE + PsAttributeClientId, // out PCLIENT_ID + PsAttributeTebAddress, // out PTEB * + PsAttributeImageName, // in PWSTR + PsAttributeImageInfo, // out PSECTION_IMAGE_INFORMATION + PsAttributeMemoryReserve, // in PPS_MEMORY_RESERVE + PsAttributePriorityClass, // in UCHAR + PsAttributeErrorMode, // in ULONG + PsAttributeStdHandleInfo, // 10, in PPS_STD_HANDLE_INFO + PsAttributeHandleList, // in PHANDLE + PsAttributeGroupAffinity, // in PGROUP_AFFINITY + PsAttributePreferredNode, // in PUSHORT + PsAttributeIdealProcessor, // in PPROCESSOR_NUMBER + PsAttributeUmsThread, // ? in PUMS_CREATE_THREAD_ATTRIBUTES + PsAttributeMitigationOptions, // in UCHAR + PsAttributeProtectionLevel, + PsAttributeSecureProcess, // since THRESHOLD + PsAttributeJobList, + PsAttributeChildProcessPolicy, // since THRESHOLD2 + PsAttributeMax +} PS_ATTRIBUTE_NUM; + +// begin_rev + +#define PsAttributeValue(Number, Thread, Input, Unknown) \ + (((Number) & PS_ATTRIBUTE_NUMBER_MASK) | \ + ((Thread) ? PS_ATTRIBUTE_THREAD : 0) | \ + ((Input) ? PS_ATTRIBUTE_INPUT : 0) | \ + ((Unknown) ? PS_ATTRIBUTE_UNKNOWN : 0)) + +#define PS_ATTRIBUTE_PARENT_PROCESS \ + PsAttributeValue(PsAttributeParentProcess, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_DEBUG_PORT \ + PsAttributeValue(PsAttributeDebugPort, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_TOKEN \ + PsAttributeValue(PsAttributeToken, FALSE, TRUE, TRUE) +#define PS_ATTRIBUTE_CLIENT_ID \ + PsAttributeValue(PsAttributeClientId, TRUE, FALSE, FALSE) +#define PS_ATTRIBUTE_TEB_ADDRESS \ + PsAttributeValue(PsAttributeTebAddress, TRUE, FALSE, FALSE) +#define PS_ATTRIBUTE_IMAGE_NAME \ + PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_IMAGE_INFO \ + PsAttributeValue(PsAttributeImageInfo, FALSE, FALSE, FALSE) +#define PS_ATTRIBUTE_MEMORY_RESERVE \ + PsAttributeValue(PsAttributeMemoryReserve, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_PRIORITY_CLASS \ + PsAttributeValue(PsAttributePriorityClass, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_ERROR_MODE \ + PsAttributeValue(PsAttributeErrorMode, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_STD_HANDLE_INFO \ + PsAttributeValue(PsAttributeStdHandleInfo, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_HANDLE_LIST \ + PsAttributeValue(PsAttributeHandleList, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_GROUP_AFFINITY \ + PsAttributeValue(PsAttributeGroupAffinity, TRUE, TRUE, FALSE) +#define PS_ATTRIBUTE_PREFERRED_NODE \ + PsAttributeValue(PsAttributePreferredNode, FALSE, TRUE, FALSE) +#define PS_ATTRIBUTE_IDEAL_PROCESSOR \ + PsAttributeValue(PsAttributeIdealProcessor, TRUE, TRUE, FALSE) +#define PS_ATTRIBUTE_MITIGATION_OPTIONS \ + PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, TRUE) + +// end_rev + +// begin_private + +typedef struct _PS_ATTRIBUTE +{ + ULONG Attribute; + SIZE_T Size; + union + { + ULONG Value; + PVOID ValuePtr; + }; + PSIZE_T ReturnLength; +} PS_ATTRIBUTE, *PPS_ATTRIBUTE; + +typedef struct _PS_ATTRIBUTE_LIST +{ + SIZE_T TotalLength; + PS_ATTRIBUTE Attributes[1]; +} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST; + +typedef struct _PS_MEMORY_RESERVE +{ + PVOID ReserveAddress; + SIZE_T ReserveSize; +} PS_MEMORY_RESERVE, *PPS_MEMORY_RESERVE; + +typedef enum _PS_STD_HANDLE_STATE +{ + PsNeverDuplicate, + PsRequestDuplicate, // duplicate standard handles specified by PseudoHandleMask, and only if StdHandleSubsystemType matches the image subsystem + PsAlwaysDuplicate, // always duplicate standard handles + PsMaxStdHandleStates +} PS_STD_HANDLE_STATE; + +// begin_rev +#define PS_STD_INPUT_HANDLE 0x1 +#define PS_STD_OUTPUT_HANDLE 0x2 +#define PS_STD_ERROR_HANDLE 0x4 +// end_rev + +typedef struct _PS_STD_HANDLE_INFO +{ + union + { + ULONG Flags; + struct + { + ULONG StdHandleState : 2; // PS_STD_HANDLE_STATE + ULONG PseudoHandleMask : 3; // PS_STD_* + }; + }; + ULONG StdHandleSubsystemType; +} PS_STD_HANDLE_INFO, *PPS_STD_HANDLE_INFO; + +// windows-internals-book:"Chapter 5" +typedef enum _PS_CREATE_STATE +{ + PsCreateInitialState, + PsCreateFailOnFileOpen, + PsCreateFailOnSectionCreate, + PsCreateFailExeFormat, + PsCreateFailMachineMismatch, + PsCreateFailExeName, // Debugger specified + PsCreateSuccess, + PsCreateMaximumStates +} PS_CREATE_STATE; + +typedef struct _PS_CREATE_INFO +{ + SIZE_T Size; + PS_CREATE_STATE State; + union + { + // PsCreateInitialState + struct + { + union + { + ULONG InitFlags; + struct + { + UCHAR WriteOutputOnExit : 1; + UCHAR DetectManifest : 1; + UCHAR IFEOSkipDebugger : 1; + UCHAR IFEODoNotPropagateKeyState : 1; + UCHAR SpareBits1 : 4; + UCHAR SpareBits2 : 8; + USHORT ProhibitedImageCharacteristics : 16; + }; + }; + ACCESS_MASK AdditionalFileAccess; + } InitState; + + // PsCreateFailOnSectionCreate + struct + { + HANDLE FileHandle; + } FailSection; + + // PsCreateFailExeFormat + struct + { + USHORT DllCharacteristics; + } ExeFormat; + + // PsCreateFailExeName + struct + { + HANDLE IFEOKey; + } ExeName; + + // PsCreateSuccess + struct + { + union + { + ULONG OutputFlags; + struct + { + UCHAR ProtectedProcess : 1; + UCHAR AddressSpaceOverride : 1; + UCHAR DevOverrideEnabled : 1; // from Image File Execution Options + UCHAR ManifestDetected : 1; + UCHAR ProtectedProcessLight : 1; + UCHAR SpareBits1 : 3; + UCHAR SpareBits2 : 8; + USHORT SpareBits3 : 16; + }; + }; + HANDLE FileHandle; + HANDLE SectionHandle; + ULONGLONG UserProcessParametersNative; + ULONG UserProcessParametersWow64; + ULONG CurrentParameterFlags; + ULONGLONG PebAddressNative; + ULONG PebAddressWow64; + ULONGLONG ManifestAddress; + ULONG ManifestSize; + } SuccessState; + }; +} PS_CREATE_INFO, *PPS_CREATE_INFO; + +// end_private + +// Extended PROCESS_CREATE_FLAGS_* +// begin_rev +#define PROCESS_CREATE_FLAGS_LARGE_PAGE_SYSTEM_DLL 0x00000020 +#define PROCESS_CREATE_FLAGS_PROTECTED_PROCESS 0x00000040 +#define PROCESS_CREATE_FLAGS_CREATE_SESSION 0x00000080 // ? +#define PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT 0x00000100 +// end_rev + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateUserProcess( + _Out_ PHANDLE ProcessHandle, + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK ProcessDesiredAccess, + _In_ ACCESS_MASK ThreadDesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ProcessObjectAttributes, + _In_opt_ POBJECT_ATTRIBUTES ThreadObjectAttributes, + _In_ ULONG ProcessFlags, // PROCESS_CREATE_FLAGS_* + _In_ ULONG ThreadFlags, // THREAD_CREATE_FLAGS_* + _In_opt_ PVOID ProcessParameters, // PRTL_USER_PROCESS_PARAMETERS + _Inout_ PPS_CREATE_INFO CreateInfo, + _In_opt_ PPS_ATTRIBUTE_LIST AttributeList + ); +#endif + +// begin_rev +#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001 +#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002 // ? +#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004 +#define THREAD_CREATE_FLAGS_HAS_SECURITY_DESCRIPTOR 0x00000010 // ? +#define THREAD_CREATE_FLAGS_ACCESS_CHECK_IN_TARGET 0x00000020 // ? +#define THREAD_CREATE_FLAGS_INITIAL_THREAD 0x00000080 +// end_rev + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateThreadEx( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ProcessHandle, + _In_ PVOID StartRoutine, // PUSER_THREAD_START_ROUTINE + _In_opt_ PVOID Argument, + _In_ ULONG CreateFlags, // THREAD_CREATE_FLAGS_* + _In_ SIZE_T ZeroBits, + _In_ SIZE_T StackSize, + _In_ SIZE_T MaximumStackSize, + _In_opt_ PPS_ATTRIBUTE_LIST AttributeList + ); +#endif + +#endif + +// Job objects + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateJobObject( + _Out_ PHANDLE JobHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenJobObject( + _Out_ PHANDLE JobHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAssignProcessToJobObject( + _In_ HANDLE JobHandle, + _In_ HANDLE ProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtTerminateJobObject( + _In_ HANDLE JobHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtIsProcessInJob( + _In_ HANDLE ProcessHandle, + _In_opt_ HANDLE JobHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationJobObject( + _In_opt_ HANDLE JobHandle, + _In_ JOBOBJECTINFOCLASS JobObjectInformationClass, + _Out_writes_bytes_(JobObjectInformationLength) PVOID JobObjectInformation, + _In_ ULONG JobObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationJobObject( + _In_ HANDLE JobHandle, + _In_ JOBOBJECTINFOCLASS JobObjectInformationClass, + _In_reads_bytes_(JobObjectInformationLength) PVOID JobObjectInformation, + _In_ ULONG JobObjectInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateJobSet( + _In_ ULONG NumJob, + _In_reads_(NumJob) PJOB_SET_ARRAY UserJobSet, + _In_ ULONG Flags + ); + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRevertContainerImpersonation( + VOID + ); +#endif + +#endif + +// Reserve objects + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +// private +typedef enum _MEMORY_RESERVE_TYPE +{ + MemoryReserveUserApc, + MemoryReserveIoCompletion, + MemoryReserveTypeMax +} MEMORY_RESERVE_TYPE; + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAllocateReserveObject( + _Out_ PHANDLE MemoryReserveHandle, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ MEMORY_RESERVE_TYPE Type + ); +#endif + +#endif + +#endif diff --git a/phnt/include/ntregapi.h b/phnt/include/ntregapi.h new file mode 100644 index 0000000..a269126 --- /dev/null +++ b/phnt/include/ntregapi.h @@ -0,0 +1,618 @@ +#ifndef _NTREGAPI_H +#define _NTREGAPI_H + +// Boot condition flags (NtInitializeRegistry) + +#define REG_INIT_BOOT_SM 0x0000 +#define REG_INIT_BOOT_SETUP 0x0001 +#define REG_INIT_BOOT_ACCEPTED_BASE 0x0002 +#define REG_INIT_BOOT_ACCEPTED_MAX REG_INIT_BOOT_ACCEPTED_BASE + 999 + +#define REG_MAX_KEY_VALUE_NAME_LENGTH 32767 +#define REG_MAX_KEY_NAME_LENGTH 512 + +typedef enum _KEY_INFORMATION_CLASS +{ + KeyBasicInformation, + KeyNodeInformation, + KeyFullInformation, + KeyNameInformation, + KeyCachedInformation, + KeyFlagsInformation, + KeyVirtualizationInformation, + KeyHandleTagsInformation, + KeyTrustInformation, + MaxKeyInfoClass +} KEY_INFORMATION_CLASS; + +typedef struct _KEY_BASIC_INFORMATION +{ + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG NameLength; + WCHAR Name[1]; +} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; + +typedef struct _KEY_NODE_INFORMATION +{ + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG NameLength; + WCHAR Name[1]; + // ... + // WCHAR Class[1]; +} KEY_NODE_INFORMATION, *PKEY_NODE_INFORMATION; + +typedef struct _KEY_FULL_INFORMATION +{ + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG SubKeys; + ULONG MaxNameLen; + ULONG MaxClassLen; + ULONG Values; + ULONG MaxValueNameLen; + ULONG MaxValueDataLen; + WCHAR Class[1]; +} KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION; + +typedef struct _KEY_NAME_INFORMATION +{ + ULONG NameLength; + WCHAR Name[1]; +} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; + +typedef struct _KEY_CACHED_INFORMATION +{ + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG SubKeys; + ULONG MaxNameLen; + ULONG Values; + ULONG MaxValueNameLen; + ULONG MaxValueDataLen; + ULONG NameLength; + WCHAR Name[1]; +} KEY_CACHED_INFORMATION, *PKEY_CACHED_INFORMATION; + +typedef struct _KEY_FLAGS_INFORMATION +{ + ULONG UserFlags; +} KEY_FLAGS_INFORMATION, *PKEY_FLAGS_INFORMATION; + +typedef struct _KEY_VIRTUALIZATION_INFORMATION +{ + ULONG VirtualizationCandidate : 1; // Tells whether the key is part of the virtualization namespace scope (only HKLM\Software for now). + ULONG VirtualizationEnabled : 1; // Tells whether virtualization is enabled on this key. Can be 1 only if above flag is 1. + ULONG VirtualTarget : 1; // Tells if the key is a virtual key. Can be 1 only if above 2 are 0. Valid only on the virtual store key handles. + ULONG VirtualStore : 1; // Tells if the key is a part of the virtual store path. Valid only on the virtual store key handles. + ULONG VirtualSource : 1; // Tells if the key has ever been virtualized, can be 1 only if VirtualizationCandidate is 1. + ULONG Reserved : 27; +} KEY_VIRTUALIZATION_INFORMATION, *PKEY_VIRTUALIZATION_INFORMATION; + +// private +typedef struct _KEY_TRUST_INFORMATION +{ + ULONG TrustedKey : 1; + ULONG Reserved : 31; +} KEY_TRUST_INFORMATION, *PKEY_TRUST_INFORMATION; + +typedef enum _KEY_SET_INFORMATION_CLASS +{ + KeyWriteTimeInformation, + KeyWow64FlagsInformation, + KeyControlFlagsInformation, + KeySetVirtualizationInformation, + KeySetDebugInformation, + KeySetHandleTagsInformation, + MaxKeySetInfoClass +} KEY_SET_INFORMATION_CLASS; + +typedef struct _KEY_WRITE_TIME_INFORMATION +{ + LARGE_INTEGER LastWriteTime; +} KEY_WRITE_TIME_INFORMATION, *PKEY_WRITE_TIME_INFORMATION; + +typedef struct _KEY_WOW64_FLAGS_INFORMATION +{ + ULONG UserFlags; +} KEY_WOW64_FLAGS_INFORMATION, *PKEY_WOW64_FLAGS_INFORMATION; + +typedef struct _KEY_HANDLE_TAGS_INFORMATION +{ + ULONG HandleTags; +} KEY_HANDLE_TAGS_INFORMATION, *PKEY_HANDLE_TAGS_INFORMATION; + +typedef struct _KEY_CONTROL_FLAGS_INFORMATION +{ + ULONG ControlFlags; +} KEY_CONTROL_FLAGS_INFORMATION, *PKEY_CONTROL_FLAGS_INFORMATION; + +typedef struct _KEY_SET_VIRTUALIZATION_INFORMATION +{ + ULONG VirtualTarget : 1; + ULONG VirtualStore : 1; + ULONG VirtualSource : 1; // true if key has been virtualized at least once + ULONG Reserved : 29; +} KEY_SET_VIRTUALIZATION_INFORMATION, *PKEY_SET_VIRTUALIZATION_INFORMATION; + +typedef enum _KEY_VALUE_INFORMATION_CLASS +{ + KeyValueBasicInformation, + KeyValueFullInformation, + KeyValuePartialInformation, + KeyValueFullInformationAlign64, + KeyValuePartialInformationAlign64, + MaxKeyValueInfoClass +} KEY_VALUE_INFORMATION_CLASS; + +typedef struct _KEY_VALUE_BASIC_INFORMATION +{ + ULONG TitleIndex; + ULONG Type; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_BASIC_INFORMATION, *PKEY_VALUE_BASIC_INFORMATION; + +typedef struct _KEY_VALUE_FULL_INFORMATION +{ + ULONG TitleIndex; + ULONG Type; + ULONG DataOffset; + ULONG DataLength; + ULONG NameLength; + WCHAR Name[1]; + // ... + // UCHAR Data[1]; +} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION; + +typedef struct _KEY_VALUE_PARTIAL_INFORMATION +{ + ULONG TitleIndex; + ULONG Type; + ULONG DataLength; + UCHAR Data[1]; +} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION; + +typedef struct _KEY_VALUE_PARTIAL_INFORMATION_ALIGN64 +{ + ULONG Type; + ULONG DataLength; + UCHAR Data[1]; +} KEY_VALUE_PARTIAL_INFORMATION_ALIGN64, *PKEY_VALUE_PARTIAL_INFORMATION_ALIGN64; + +typedef struct _KEY_VALUE_ENTRY +{ + PUNICODE_STRING ValueName; + ULONG DataLength; + ULONG DataOffset; + ULONG Type; +} KEY_VALUE_ENTRY, *PKEY_VALUE_ENTRY; + +typedef enum _REG_ACTION +{ + KeyAdded, + KeyRemoved, + KeyModified +} REG_ACTION; + +typedef struct _REG_NOTIFY_INFORMATION +{ + ULONG NextEntryOffset; + REG_ACTION Action; + ULONG KeyLength; + WCHAR Key[1]; +} REG_NOTIFY_INFORMATION, *PREG_NOTIFY_INFORMATION; + +typedef struct _KEY_PID_ARRAY +{ + HANDLE PID; + UNICODE_STRING KeyName; +} KEY_PID_ARRAY, *PKEY_PID_ARRAY; + +typedef struct _KEY_OPEN_SUBKEYS_INFORMATION +{ + ULONG Count; + KEY_PID_ARRAY KeyArray[1]; +} KEY_OPEN_SUBKEYS_INFORMATION, *PKEY_OPEN_SUBKEYS_INFORMATION; + +// System calls + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Reserved_ ULONG TitleIndex, + _In_opt_ PUNICODE_STRING Class, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG Disposition + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateKeyTransacted( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Reserved_ ULONG TitleIndex, + _In_opt_ PUNICODE_STRING Class, + _In_ ULONG CreateOptions, + _In_ HANDLE TransactionHandle, + _Out_opt_ PULONG Disposition + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenKeyTransacted( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE TransactionHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenKeyEx( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG OpenOptions + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenKeyTransactedEx( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG OpenOptions, + _In_ HANDLE TransactionHandle + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRenameKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING NewName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryKey( + _In_ HANDLE KeyHandle, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationKey( + _In_ HANDLE KeyHandle, + _In_ KEY_SET_INFORMATION_CLASS KeySetInformationClass, + _In_reads_bytes_(KeySetInformationLength) PVOID KeySetInformation, + _In_ ULONG KeySetInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyValueInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName, + _In_opt_ ULONG TitleIndex, + _In_ ULONG Type, + _In_reads_bytes_opt_(DataSize) PVOID Data, + _In_ ULONG DataSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryMultipleValueKey( + _In_ HANDLE KeyHandle, + _Inout_updates_(EntryCount) PKEY_VALUE_ENTRY ValueEntries, + _In_ ULONG EntryCount, + _Out_writes_bytes_(*BufferLength) PVOID ValueBuffer, + _Inout_ PULONG BufferLength, + _Out_opt_ PULONG RequiredBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateKey( + _In_ HANDLE KeyHandle, + _In_ ULONG Index, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateValueKey( + _In_ HANDLE KeyHandle, + _In_ ULONG Index, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyValueInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFlushKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCompactKeys( + _In_ ULONG Count, + _In_reads_(Count) HANDLE KeyArray[] + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCompressKey( + _In_ HANDLE Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLoadKey( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLoadKey2( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLoadKeyEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile, + _In_ ULONG Flags, + _In_opt_ HANDLE TrustClassKey, + _In_opt_ HANDLE Event, + _In_opt_ ACCESS_MASK DesiredAccess, + _Out_opt_ PHANDLE RootHandle, + _Out_opt_ PIO_STATUS_BLOCK IoStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReplaceKey( + _In_ POBJECT_ATTRIBUTES NewFile, + _In_ HANDLE TargetHandle, + _In_ POBJECT_ATTRIBUTES OldFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSaveKey( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSaveKeyEx( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle, + _In_ ULONG Format + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSaveMergedKeys( + _In_ HANDLE HighPrecedenceKeyHandle, + _In_ HANDLE LowPrecedenceKeyHandle, + _In_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRestoreKey( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnloadKey( + _In_ POBJECT_ATTRIBUTES TargetKey + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnloadKey2( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtUnloadKeyEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_opt_ HANDLE Event + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtNotifyChangeKey( + _In_ HANDLE KeyHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtNotifyChangeMultipleKeys( + _In_ HANDLE MasterKeyHandle, + _In_opt_ ULONG Count, + _In_reads_opt_(Count) OBJECT_ATTRIBUTES SubordinateObjects[], + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryOpenSubKeys( + _In_ POBJECT_ATTRIBUTES TargetKey, + _Out_ PULONG HandleCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryOpenSubKeysEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _Out_ PULONG RequiredSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtInitializeRegistry( + _In_ USHORT BootCondition + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLockRegistryKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtLockProductActivationKeys( + _Inout_opt_ ULONG *pPrivateVer, + _Out_opt_ ULONG *pSafeMode + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFreezeRegistry( + _In_ ULONG TimeOutInSeconds + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtThawRegistry( + VOID + ); +#endif + +#endif diff --git a/phnt/include/ntrtl.h b/phnt/include/ntrtl.h new file mode 100644 index 0000000..216aaf5 --- /dev/null +++ b/phnt/include/ntrtl.h @@ -0,0 +1,6517 @@ +#ifndef _NTRTL_H +#define _NTRTL_H + +// Linked lists + +FORCEINLINE VOID InitializeListHead( + _Out_ PLIST_ENTRY ListHead + ) +{ + ListHead->Flink = ListHead->Blink = ListHead; +} + +_Check_return_ FORCEINLINE BOOLEAN IsListEmpty( + _In_ PLIST_ENTRY ListHead + ) +{ + return ListHead->Flink == ListHead; +} + +FORCEINLINE BOOLEAN RemoveEntryList( + _In_ PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Flink; + + Flink = Entry->Flink; + Blink = Entry->Blink; + Blink->Flink = Flink; + Flink->Blink = Blink; + + return Flink == Blink; +} + +FORCEINLINE PLIST_ENTRY RemoveHeadList( + _Inout_ PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Flink; + PLIST_ENTRY Entry; + + Entry = ListHead->Flink; + Flink = Entry->Flink; + ListHead->Flink = Flink; + Flink->Blink = ListHead; + + return Entry; +} + +FORCEINLINE PLIST_ENTRY RemoveTailList( + _Inout_ PLIST_ENTRY ListHead + ) +{ + PLIST_ENTRY Blink; + PLIST_ENTRY Entry; + + Entry = ListHead->Blink; + Blink = Entry->Blink; + ListHead->Blink = Blink; + Blink->Flink = ListHead; + + return Entry; +} + +FORCEINLINE VOID InsertTailList( + _Inout_ PLIST_ENTRY ListHead, + _Inout_ PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Blink; + + Blink = ListHead->Blink; + Entry->Flink = ListHead; + Entry->Blink = Blink; + Blink->Flink = Entry; + ListHead->Blink = Entry; +} + +FORCEINLINE VOID InsertHeadList( + _Inout_ PLIST_ENTRY ListHead, + _Inout_ PLIST_ENTRY Entry + ) +{ + PLIST_ENTRY Flink; + + Flink = ListHead->Flink; + Entry->Flink = Flink; + Entry->Blink = ListHead; + Flink->Blink = Entry; + ListHead->Flink = Entry; +} + +FORCEINLINE VOID AppendTailList( + _Inout_ PLIST_ENTRY ListHead, + _Inout_ PLIST_ENTRY ListToAppend + ) +{ + PLIST_ENTRY ListEnd = ListHead->Blink; + + ListHead->Blink->Flink = ListToAppend; + ListHead->Blink = ListToAppend->Blink; + ListToAppend->Blink->Flink = ListHead; + ListToAppend->Blink = ListEnd; +} + +FORCEINLINE PSINGLE_LIST_ENTRY PopEntryList( + _Inout_ PSINGLE_LIST_ENTRY ListHead + ) +{ + PSINGLE_LIST_ENTRY FirstEntry; + + FirstEntry = ListHead->Next; + + if (FirstEntry) + ListHead->Next = FirstEntry->Next; + + return FirstEntry; +} + +FORCEINLINE VOID PushEntryList( + _Inout_ PSINGLE_LIST_ENTRY ListHead, + _Inout_ PSINGLE_LIST_ENTRY Entry + ) +{ + Entry->Next = ListHead->Next; + ListHead->Next = Entry; +} + +// AVL and splay trees + +typedef enum _TABLE_SEARCH_RESULT +{ + TableEmptyTree, + TableFoundNode, + TableInsertAsLeft, + TableInsertAsRight +} TABLE_SEARCH_RESULT; + +typedef enum _RTL_GENERIC_COMPARE_RESULTS +{ + GenericLessThan, + GenericGreaterThan, + GenericEqual +} RTL_GENERIC_COMPARE_RESULTS; + +typedef RTL_GENERIC_COMPARE_RESULTS (NTAPI *PRTL_AVL_COMPARE_ROUTINE)( + _In_ struct _RTL_AVL_TABLE *Table, + _In_ PVOID FirstStruct, + _In_ PVOID SecondStruct + ); + +typedef PVOID (NTAPI *PRTL_AVL_ALLOCATE_ROUTINE)( + _In_ struct _RTL_AVL_TABLE *Table, + _In_ CLONG ByteSize + ); + +typedef VOID (NTAPI *PRTL_AVL_FREE_ROUTINE)( + _In_ struct _RTL_AVL_TABLE *Table, + _In_ _Post_invalid_ PVOID Buffer + ); + +typedef NTSTATUS (NTAPI *PRTL_AVL_MATCH_FUNCTION)( + _In_ struct _RTL_AVL_TABLE *Table, + _In_ PVOID UserData, + _In_ PVOID MatchData + ); + +typedef struct _RTL_BALANCED_LINKS +{ + struct _RTL_BALANCED_LINKS *Parent; + struct _RTL_BALANCED_LINKS *LeftChild; + struct _RTL_BALANCED_LINKS *RightChild; + CHAR Balance; + UCHAR Reserved[3]; +} RTL_BALANCED_LINKS, *PRTL_BALANCED_LINKS; + +typedef struct _RTL_AVL_TABLE +{ + RTL_BALANCED_LINKS BalancedRoot; + PVOID OrderedPointer; + ULONG WhichOrderedElement; + ULONG NumberGenericTableElements; + ULONG DepthOfTree; + PRTL_BALANCED_LINKS RestartKey; + ULONG DeleteCount; + PRTL_AVL_COMPARE_ROUTINE CompareRoutine; + PRTL_AVL_ALLOCATE_ROUTINE AllocateRoutine; + PRTL_AVL_FREE_ROUTINE FreeRoutine; + PVOID TableContext; +} RTL_AVL_TABLE, *PRTL_AVL_TABLE; + +NTSYSAPI +VOID +NTAPI +RtlInitializeGenericTableAvl( + _Out_ PRTL_AVL_TABLE Table, + _In_ PRTL_AVL_COMPARE_ROUTINE CompareRoutine, + _In_ PRTL_AVL_ALLOCATE_ROUTINE AllocateRoutine, + _In_ PRTL_AVL_FREE_ROUTINE FreeRoutine, + _In_opt_ PVOID TableContext + ); + +NTSYSAPI +PVOID +NTAPI +RtlInsertElementGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ CLONG BufferSize, + _Out_opt_ PBOOLEAN NewElement + ); + +NTSYSAPI +PVOID +NTAPI +RtlInsertElementGenericTableFullAvl( + _In_ PRTL_AVL_TABLE Table, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ CLONG BufferSize, + _Out_opt_ PBOOLEAN NewElement, + _In_ PVOID NodeOrParent, + _In_ TABLE_SEARCH_RESULT SearchResult + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlDeleteElementGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlLookupElementGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ PVOID Buffer + ); + +NTSYSAPI +PVOID +NTAPI +RtlLookupElementGenericTableFullAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ PVOID Buffer, + _Out_ PVOID *NodeOrParent, + _Out_ TABLE_SEARCH_RESULT *SearchResult + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlEnumerateGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ BOOLEAN Restart + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlEnumerateGenericTableWithoutSplayingAvl( + _In_ PRTL_AVL_TABLE Table, + _Inout_ PVOID *RestartKey + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlLookupFirstMatchingElementGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ PVOID Buffer, + _Out_ PVOID *RestartKey + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlEnumerateGenericTableLikeADirectory( + _In_ PRTL_AVL_TABLE Table, + _In_opt_ PRTL_AVL_MATCH_FUNCTION MatchFunction, + _In_opt_ PVOID MatchData, + _In_ ULONG NextFlag, + _Inout_ PVOID *RestartKey, + _Inout_ PULONG DeleteCount, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlGetElementGenericTableAvl( + _In_ PRTL_AVL_TABLE Table, + _In_ ULONG I + ); + +NTSYSAPI +ULONG +NTAPI +RtlNumberGenericTableElementsAvl( + _In_ PRTL_AVL_TABLE Table + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlIsGenericTableEmptyAvl( + _In_ PRTL_AVL_TABLE Table + ); + +typedef struct _RTL_SPLAY_LINKS +{ + struct _RTL_SPLAY_LINKS *Parent; + struct _RTL_SPLAY_LINKS *LeftChild; + struct _RTL_SPLAY_LINKS *RightChild; +} RTL_SPLAY_LINKS, *PRTL_SPLAY_LINKS; + +#define RtlInitializeSplayLinks(Links) \ + { \ + PRTL_SPLAY_LINKS _SplayLinks; \ + _SplayLinks = (PRTL_SPLAY_LINKS)(Links); \ + _SplayLinks->Parent = _SplayLinks; \ + _SplayLinks->LeftChild = NULL; \ + _SplayLinks->RightChild = NULL; \ + } + +#define RtlParent(Links) ((PRTL_SPLAY_LINKS)(Links)->Parent) +#define RtlLeftChild(Links) ((PRTL_SPLAY_LINKS)(Links)->LeftChild) +#define RtlRightChild(Links) ((PRTL_SPLAY_LINKS)(Links)->RightChild) +#define RtlIsRoot(Links) ((RtlParent(Links) == (PRTL_SPLAY_LINKS)(Links))) +#define RtlIsLeftChild(Links) ((RtlLeftChild(RtlParent(Links)) == (PRTL_SPLAY_LINKS)(Links))) +#define RtlIsRightChild(Links) ((RtlRightChild(RtlParent(Links)) == (PRTL_SPLAY_LINKS)(Links))) + +#define RtlInsertAsLeftChild(ParentLinks, ChildLinks) \ + { \ + PRTL_SPLAY_LINKS _SplayParent; \ + PRTL_SPLAY_LINKS _SplayChild; \ + _SplayParent = (PRTL_SPLAY_LINKS)(ParentLinks); \ + _SplayChild = (PRTL_SPLAY_LINKS)(ChildLinks); \ + _SplayParent->LeftChild = _SplayChild; \ + _SplayChild->Parent = _SplayParent; \ + } + +#define RtlInsertAsRightChild(ParentLinks, ChildLinks) \ + { \ + PRTL_SPLAY_LINKS _SplayParent; \ + PRTL_SPLAY_LINKS _SplayChild; \ + _SplayParent = (PRTL_SPLAY_LINKS)(ParentLinks); \ + _SplayChild = (PRTL_SPLAY_LINKS)(ChildLinks); \ + _SplayParent->RightChild = _SplayChild; \ + _SplayChild->Parent = _SplayParent; \ + } + +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlSplay( + _Inout_ PRTL_SPLAY_LINKS Links + ); + +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlDelete( + _In_ PRTL_SPLAY_LINKS Links + ); + +NTSYSAPI +VOID +NTAPI +RtlDeleteNoSplay( + _In_ PRTL_SPLAY_LINKS Links, + _Inout_ PRTL_SPLAY_LINKS *Root + ); + +_Check_return_ +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlSubtreeSuccessor( + _In_ PRTL_SPLAY_LINKS Links + ); + +_Check_return_ +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlSubtreePredecessor( + _In_ PRTL_SPLAY_LINKS Links + ); + +_Check_return_ +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlRealSuccessor( + _In_ PRTL_SPLAY_LINKS Links + ); + +_Check_return_ +NTSYSAPI +PRTL_SPLAY_LINKS +NTAPI +RtlRealPredecessor( + _In_ PRTL_SPLAY_LINKS Links + ); + +struct _RTL_GENERIC_TABLE; + +typedef RTL_GENERIC_COMPARE_RESULTS (NTAPI *PRTL_GENERIC_COMPARE_ROUTINE)( + _In_ struct _RTL_GENERIC_TABLE *Table, + _In_ PVOID FirstStruct, + _In_ PVOID SecondStruct + ); + +typedef PVOID (NTAPI *PRTL_GENERIC_ALLOCATE_ROUTINE)( + _In_ struct _RTL_GENERIC_TABLE *Table, + _In_ CLONG ByteSize + ); + +typedef VOID (NTAPI *PRTL_GENERIC_FREE_ROUTINE)( + _In_ struct _RTL_GENERIC_TABLE *Table, + _In_ _Post_invalid_ PVOID Buffer + ); + +typedef struct _RTL_GENERIC_TABLE +{ + PRTL_SPLAY_LINKS TableRoot; + LIST_ENTRY InsertOrderList; + PLIST_ENTRY OrderedPointer; + ULONG WhichOrderedElement; + ULONG NumberGenericTableElements; + PRTL_GENERIC_COMPARE_ROUTINE CompareRoutine; + PRTL_GENERIC_ALLOCATE_ROUTINE AllocateRoutine; + PRTL_GENERIC_FREE_ROUTINE FreeRoutine; + PVOID TableContext; +} RTL_GENERIC_TABLE, *PRTL_GENERIC_TABLE; + +NTSYSAPI +VOID +NTAPI +RtlInitializeGenericTable( + _Out_ PRTL_GENERIC_TABLE Table, + _In_ PRTL_GENERIC_COMPARE_ROUTINE CompareRoutine, + _In_ PRTL_GENERIC_ALLOCATE_ROUTINE AllocateRoutine, + _In_ PRTL_GENERIC_FREE_ROUTINE FreeRoutine, + _In_opt_ PVOID TableContext + ); + +NTSYSAPI +PVOID +NTAPI +RtlInsertElementGenericTable( + _In_ PRTL_GENERIC_TABLE Table, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ CLONG BufferSize, + _Out_opt_ PBOOLEAN NewElement + ); + +NTSYSAPI +PVOID +NTAPI +RtlInsertElementGenericTableFull( + _In_ PRTL_GENERIC_TABLE Table, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ CLONG BufferSize, + _Out_opt_ PBOOLEAN NewElement, + _In_ PVOID NodeOrParent, + _In_ TABLE_SEARCH_RESULT SearchResult + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlDeleteElementGenericTable( + _In_ PRTL_GENERIC_TABLE Table, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlLookupElementGenericTable( + _In_ PRTL_GENERIC_TABLE Table, + _In_ PVOID Buffer + ); + +NTSYSAPI +PVOID +NTAPI +RtlLookupElementGenericTableFull( + _In_ PRTL_GENERIC_TABLE Table, + _In_ PVOID Buffer, + _Out_ PVOID *NodeOrParent, + _Out_ TABLE_SEARCH_RESULT *SearchResult + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlEnumerateGenericTable( + _In_ PRTL_GENERIC_TABLE Table, + _In_ BOOLEAN Restart + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlEnumerateGenericTableWithoutSplaying( + _In_ PRTL_GENERIC_TABLE Table, + _Inout_ PVOID *RestartKey + ); + +_Check_return_ +NTSYSAPI +PVOID +NTAPI +RtlGetElementGenericTable( + _In_ PRTL_GENERIC_TABLE Table, + _In_ ULONG I + ); + +NTSYSAPI +ULONG +NTAPI +RtlNumberGenericTableElements( + _In_ PRTL_GENERIC_TABLE Table + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlIsGenericTableEmpty( + _In_ PRTL_GENERIC_TABLE Table + ); + +// RB trees + +typedef struct _RTL_RB_TREE +{ + PRTL_BALANCED_NODE Root; + PRTL_BALANCED_NODE Min; +} RTL_RB_TREE, *PRTL_RB_TREE; + +#if (PHNT_VERSION >= PHNT_WIN8) + +// rev +NTSYSAPI +VOID +NTAPI +RtlRbInsertNodeEx( + _In_ PRTL_RB_TREE Tree, + _In_opt_ PRTL_BALANCED_NODE Parent, + _In_ BOOLEAN Right, + _Out_ PRTL_BALANCED_NODE Node + ); + +// rev +NTSYSAPI +VOID +NTAPI +RtlRbRemoveNode( + _In_ PRTL_RB_TREE Tree, + _In_ PRTL_BALANCED_NODE Node + ); + +#endif + +// Hash tables + +// begin_ntddk + +#define RTL_HASH_ALLOCATED_HEADER 0x00000001 +#define RTL_HASH_RESERVED_SIGNATURE 0 + +typedef struct _RTL_DYNAMIC_HASH_TABLE_ENTRY +{ + LIST_ENTRY Linkage; + ULONG_PTR Signature; +} RTL_DYNAMIC_HASH_TABLE_ENTRY, *PRTL_DYNAMIC_HASH_TABLE_ENTRY; + +#define HASH_ENTRY_KEY(x) ((x)->Signature) + +typedef struct _RTL_DYNAMIC_HASH_TABLE_CONTEXT +{ + PLIST_ENTRY ChainHead; + PLIST_ENTRY PrevLinkage; + ULONG_PTR Signature; +} RTL_DYNAMIC_HASH_TABLE_CONTEXT, *PRTL_DYNAMIC_HASH_TABLE_CONTEXT; + +typedef struct _RTL_DYNAMIC_HASH_TABLE_ENUMERATOR +{ + RTL_DYNAMIC_HASH_TABLE_ENTRY HashEntry; + PLIST_ENTRY ChainHead; + ULONG BucketIndex; +} RTL_DYNAMIC_HASH_TABLE_ENUMERATOR, *PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR; + +typedef struct _RTL_DYNAMIC_HASH_TABLE +{ + // Entries initialized at creation. + ULONG Flags; + ULONG Shift; + + // Entries used in bucket computation. + ULONG TableSize; + ULONG Pivot; + ULONG DivisorMask; + + // Counters. + ULONG NumEntries; + ULONG NonEmptyBuckets; + ULONG NumEnumerators; + + // The directory. This field is for internal use only. + PVOID Directory; +} RTL_DYNAMIC_HASH_TABLE, *PRTL_DYNAMIC_HASH_TABLE; + +#if (PHNT_VERSION >= PHNT_WIN7) + +FORCEINLINE +VOID +RtlInitHashTableContext( + _Inout_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ) +{ + Context->ChainHead = NULL; + Context->PrevLinkage = NULL; +} + +FORCEINLINE +VOID +RtlInitHashTableContextFromEnumerator( + _Inout_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context, + _In_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ) +{ + Context->ChainHead = Enumerator->ChainHead; + Context->PrevLinkage = Enumerator->HashEntry.Linkage.Blink; +} + +FORCEINLINE +VOID +RtlReleaseHashTableContext( + _Inout_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ) +{ + UNREFERENCED_PARAMETER(Context); + return; +} + +FORCEINLINE +ULONG +RtlTotalBucketsHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ) +{ + return HashTable->TableSize; +} + +FORCEINLINE +ULONG +RtlNonEmptyBucketsHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ) +{ + return HashTable->NonEmptyBuckets; +} + +FORCEINLINE +ULONG +RtlEmptyBucketsHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ) +{ + return HashTable->TableSize - HashTable->NonEmptyBuckets; +} + +FORCEINLINE +ULONG +RtlTotalEntriesHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ) +{ + return HashTable->NumEntries; +} + +FORCEINLINE +ULONG +RtlActiveEnumeratorsHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ) +{ + return HashTable->NumEnumerators; +} + +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlCreateHashTable( + _Inout_ _When_(*HashTable == NULL, __drv_allocatesMem(Mem)) PRTL_DYNAMIC_HASH_TABLE *HashTable, + _In_ ULONG Shift, + _In_ _Reserved_ ULONG Flags + ); + +NTSYSAPI +VOID +NTAPI +RtlDeleteHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlInsertEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _In_ PRTL_DYNAMIC_HASH_TABLE_ENTRY Entry, + _In_ ULONG_PTR Signature, + _Inout_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlRemoveEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _In_ PRTL_DYNAMIC_HASH_TABLE_ENTRY Entry, + _Inout_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ); + +_Must_inspect_result_ +NTSYSAPI +PRTL_DYNAMIC_HASH_TABLE_ENTRY +NTAPI +RtlLookupEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _In_ ULONG_PTR Signature, + _Out_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ); + +_Must_inspect_result_ +NTSYSAPI +PRTL_DYNAMIC_HASH_TABLE_ENTRY +NTAPI +RtlGetNextEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _In_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlInitEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Out_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +_Must_inspect_result_ +NTSYSAPI +PRTL_DYNAMIC_HASH_TABLE_ENTRY +NTAPI +RtlEnumerateEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +NTSYSAPI +VOID +NTAPI +RtlEndEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlInitWeakEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Out_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +_Must_inspect_result_ +NTSYSAPI +PRTL_DYNAMIC_HASH_TABLE_ENTRY +NTAPI +RtlWeaklyEnumerateEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +NTSYSAPI +VOID +NTAPI +RtlEndWeakEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlExpandHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlContractHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable + ); + +#endif + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSAPI +BOOLEAN +NTAPI +RtlInitStrongEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Out_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +_Must_inspect_result_ +NTSYSAPI +PRTL_DYNAMIC_HASH_TABLE_ENTRY +NTAPI +RtlStronglyEnumerateEntryHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +NTSYSAPI +VOID +NTAPI +RtlEndStrongEnumerationHashTable( + _In_ PRTL_DYNAMIC_HASH_TABLE HashTable, + _Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator + ); + +#endif + +// end_ntddk + +// Critical sections + +NTSYSAPI +NTSTATUS +NTAPI +RtlInitializeCriticalSection( + _Out_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlInitializeCriticalSectionAndSpinCount( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection, + _In_ ULONG SpinCount + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteCriticalSection( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlEnterCriticalSection( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLeaveCriticalSection( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +LOGICAL +NTAPI +RtlTryEnterCriticalSection( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +LOGICAL +NTAPI +RtlIsCriticalSectionLocked( + _In_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +LOGICAL +NTAPI +RtlIsCriticalSectionLockedByThread( + _In_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +ULONG +NTAPI +RtlGetCriticalSectionRecursionCount( + _In_ PRTL_CRITICAL_SECTION CriticalSection + ); + +NTSYSAPI +ULONG +NTAPI +RtlSetCriticalSectionSpinCount( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection, + _In_ ULONG SpinCount + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +HANDLE +NTAPI +RtlQueryCriticalSectionOwner( + _In_ HANDLE EventHandle + ); +#endif + +NTSYSAPI +VOID +NTAPI +RtlCheckForOrphanedCriticalSections( + _In_ HANDLE hThread + ); + +// Resources + +typedef struct _RTL_RESOURCE +{ + RTL_CRITICAL_SECTION CriticalSection; + + HANDLE SharedSemaphore; + ULONG NumberOfWaitingShared; + HANDLE ExclusiveSemaphore; + ULONG NumberOfWaitingExclusive; + + LONG NumberOfActive; // negative: exclusive acquire; zero: not acquired; positive: shared acquire(s) + HANDLE ExclusiveOwnerThread; + + ULONG Flags; // RTL_RESOURCE_FLAG_* + + PRTL_RESOURCE_DEBUG DebugInfo; +} RTL_RESOURCE, *PRTL_RESOURCE; + +#define RTL_RESOURCE_FLAG_LONG_TERM ((ULONG)0x00000001) + +NTSYSAPI +VOID +NTAPI +RtlInitializeResource( + _Out_ PRTL_RESOURCE Resource + ); + +NTSYSAPI +VOID +NTAPI +RtlDeleteResource( + _Inout_ PRTL_RESOURCE Resource + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlAcquireResourceShared( + _Inout_ PRTL_RESOURCE Resource, + _In_ BOOLEAN Wait + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlAcquireResourceExclusive( + _Inout_ PRTL_RESOURCE Resource, + _In_ BOOLEAN Wait + ); + +NTSYSAPI +VOID +NTAPI +RtlReleaseResource( + _Inout_ PRTL_RESOURCE Resource + ); + +NTSYSAPI +VOID +NTAPI +RtlConvertSharedToExclusive( + _Inout_ PRTL_RESOURCE Resource + ); + +NTSYSAPI +VOID +NTAPI +RtlConvertExclusiveToShared( + _Inout_ PRTL_RESOURCE Resource + ); + +// Slim reader-writer locks, condition variables, and barriers + +#if (PHNT_VERSION >= PHNT_VISTA) + +// winbase:InitializeSRWLock +NTSYSAPI +VOID +NTAPI +RtlInitializeSRWLock( + _Out_ PRTL_SRWLOCK SRWLock + ); + +// winbase:AcquireSRWLockExclusive +NTSYSAPI +VOID +NTAPI +RtlAcquireSRWLockExclusive( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +// winbase:AcquireSRWLockShared +NTSYSAPI +VOID +NTAPI +RtlAcquireSRWLockShared( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +// winbase:ReleaseSRWLockExclusive +NTSYSAPI +VOID +NTAPI +RtlReleaseSRWLockExclusive( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +// winbase:ReleaseSRWLockShared +NTSYSAPI +VOID +NTAPI +RtlReleaseSRWLockShared( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +// winbase:TryAcquireSRWLockExclusive +NTSYSAPI +BOOLEAN +NTAPI +RtlTryAcquireSRWLockExclusive( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +// winbase:TryAcquireSRWLockShared +NTSYSAPI +BOOLEAN +NTAPI +RtlTryAcquireSRWLockShared( + _Inout_ PRTL_SRWLOCK SRWLock + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +VOID +NTAPI +RtlAcquireReleaseSRWLockExclusive( + _Inout_ PRTL_SRWLOCK SRWLock + ); +#endif + +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) + +// winbase:InitializeConditionVariable +NTSYSAPI +VOID +NTAPI +RtlInitializeConditionVariable( + _Out_ PRTL_CONDITION_VARIABLE ConditionVariable + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSleepConditionVariableCS( + _Inout_ PRTL_CONDITION_VARIABLE ConditionVariable, + _Inout_ PRTL_CRITICAL_SECTION CriticalSection, + _In_opt_ PLARGE_INTEGER Timeout + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSleepConditionVariableSRW( + _Inout_ PRTL_CONDITION_VARIABLE ConditionVariable, + _Inout_ PRTL_SRWLOCK SRWLock, + _In_opt_ PLARGE_INTEGER Timeout, + _In_ ULONG Flags + ); + +// winbase:WakeConditionVariable +NTSYSAPI +VOID +NTAPI +RtlWakeConditionVariable( + _Inout_ PRTL_CONDITION_VARIABLE ConditionVariable + ); + +// winbase:WakeAllConditionVariable +NTSYSAPI +VOID +NTAPI +RtlWakeAllConditionVariable( + _Inout_ PRTL_CONDITION_VARIABLE ConditionVariable + ); + +#endif + +// begin_rev +#define RTL_BARRIER_FLAGS_SPIN_ONLY 0x00000001 // never block on event - always spin +#define RTL_BARRIER_FLAGS_BLOCK_ONLY 0x00000002 // always block on event - never spin +#define RTL_BARRIER_FLAGS_NO_DELETE 0x00000004 // use if barrier will never be deleted +// end_rev + +// begin_private + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSAPI +NTSTATUS +NTAPI +RtlInitBarrier( + _Out_ PRTL_BARRIER Barrier, + _In_ ULONG TotalThreads, + _In_ ULONG SpinCount + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteBarrier( + _In_ PRTL_BARRIER Barrier + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlBarrier( + _Inout_ PRTL_BARRIER Barrier, + _In_ ULONG Flags + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlBarrierForDelete( + _Inout_ PRTL_BARRIER Barrier, + _In_ ULONG Flags + ); + +#endif + +// end_private + +// Wait on address + +// begin_rev + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSAPI +NTSTATUS +NTAPI +RtlWaitOnAddress( + _In_ volatile VOID *Address, + _In_ PVOID CompareAddress, + _In_ SIZE_T AddressSize, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSAPI +VOID +NTAPI +RtlWakeAddressAll( + _In_ PVOID Address + ); + +NTSYSAPI +VOID +NTAPI +RtlWakeAddressSingle( + _In_ PVOID Address + ); + +#endif + +// end_rev + +// Strings + +#ifndef PHNT_NO_INLINE_INIT_STRING +FORCEINLINE VOID RtlInitString( + _Out_ PSTRING DestinationString, + _In_opt_ PSTR SourceString + ) +{ + if (SourceString) + DestinationString->MaximumLength = (DestinationString->Length = (USHORT)strlen(SourceString)) + 1; + else + DestinationString->MaximumLength = DestinationString->Length = 0; + + DestinationString->Buffer = SourceString; +} +#else +NTSYSAPI +VOID +NTAPI +RtlInitString( + _Out_ PSTRING DestinationString, + _In_opt_ PSTR SourceString + ); +#endif + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSAPI +NTSTATUS +NTAPI +RtlInitStringEx( + _Out_ PSTRING DestinationString, + _In_opt_z_ PCSZ SourceString + ); +#endif + +#ifndef PHNT_NO_INLINE_INIT_STRING +FORCEINLINE VOID RtlInitAnsiString( + _Out_ PANSI_STRING DestinationString, + _In_opt_ PSTR SourceString + ) +{ + if (SourceString) + DestinationString->MaximumLength = (DestinationString->Length = (USHORT)strlen(SourceString)) + 1; + else + DestinationString->MaximumLength = DestinationString->Length = 0; + + DestinationString->Buffer = SourceString; +} +#else +NTSYSAPI +VOID +NTAPI +RtlInitAnsiString( + _Out_ PANSI_STRING DestinationString, + _In_opt_ PSTR SourceString + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +NTSTATUS +NTAPI +RtlInitAnsiStringEx( + _Out_ PANSI_STRING DestinationString, + _In_opt_z_ PCSZ SourceString + ); +#endif + +NTSYSAPI +VOID +NTAPI +RtlFreeAnsiString( + _In_ PANSI_STRING AnsiString + ); + +NTSYSAPI +VOID +NTAPI +RtlFreeOemString( + _In_ POEM_STRING OemString + ); + +NTSYSAPI +VOID +NTAPI +RtlCopyString( + _In_ PSTRING DestinationString, + _In_opt_ PSTRING SourceString + ); + +NTSYSAPI +CHAR +NTAPI +RtlUpperChar( + _In_ CHAR Character + ); + +_Must_inspect_result_ +NTSYSAPI +LONG +NTAPI +RtlCompareString( + _In_ PSTRING String1, + _In_ PSTRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlEqualString( + _In_ PSTRING String1, + _In_ PSTRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlPrefixString( + _In_ PSTRING String1, + _In_ PSTRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAppendStringToString( + _In_ PSTRING Destination, + _In_ PSTRING Source + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAppendAsciizToString( + _In_ PSTRING Destination, + _In_opt_ PSTR Source + ); + +NTSYSAPI +VOID +NTAPI +RtlUpperString( + _In_ PSTRING DestinationString, + _In_ PSTRING SourceString + ); + +#ifndef PHNT_NO_INLINE_INIT_STRING +FORCEINLINE VOID RtlInitUnicodeString( + _Out_ PUNICODE_STRING DestinationString, + _In_opt_ PWSTR SourceString + ) +{ + if (SourceString) + DestinationString->MaximumLength = (DestinationString->Length = (USHORT)(wcslen(SourceString) * sizeof(WCHAR))) + sizeof(WCHAR); + else + DestinationString->MaximumLength = DestinationString->Length = 0; + + DestinationString->Buffer = SourceString; +} +#else +NTSYSAPI +VOID +NTAPI +RtlInitUnicodeString( + _Out_ PUNICODE_STRING DestinationString, + _In_opt_ PWSTR SourceString + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlInitUnicodeStringEx( + _Out_ PUNICODE_STRING DestinationString, + _In_opt_ PWSTR SourceString + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlCreateUnicodeString( + _Out_ PUNICODE_STRING DestinationString, + _In_ PWSTR SourceString + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlCreateUnicodeStringFromAsciiz( + _Out_ PUNICODE_STRING DestinationString, + _In_ PSTR SourceString + ); + +NTSYSAPI +VOID +NTAPI +RtlFreeUnicodeString( + _In_ PUNICODE_STRING UnicodeString + ); + +#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE (0x00000001) +#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING (0x00000002) + +NTSYSAPI +NTSTATUS +NTAPI +RtlDuplicateUnicodeString( + _In_ ULONG Flags, + _In_ PUNICODE_STRING StringIn, + _Out_ PUNICODE_STRING StringOut + ); + +NTSYSAPI +VOID +NTAPI +RtlCopyUnicodeString( + _In_ PUNICODE_STRING DestinationString, + _In_ PUNICODE_STRING SourceString + ); + +NTSYSAPI +WCHAR +NTAPI +RtlUpcaseUnicodeChar( + _In_ WCHAR SourceCharacter + ); + +NTSYSAPI +WCHAR +NTAPI +RtlDowncaseUnicodeChar( + _In_ WCHAR SourceCharacter + ); + +_Must_inspect_result_ +NTSYSAPI +LONG +NTAPI +RtlCompareUnicodeString( + _In_ PUNICODE_STRING String1, + _In_ PUNICODE_STRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +_Must_inspect_result_ +NTSYSAPI +LONG +NTAPI +RtlCompareUnicodeStrings( + _In_reads_(String1Length) PWCH String1, + _In_ SIZE_T String1Length, + _In_reads_(String2Length) PWCH String2, + _In_ SIZE_T String2Length, + _In_ BOOLEAN CaseInSensitive + ); +#endif + +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlEqualUnicodeString( + _In_ PUNICODE_STRING String1, + _In_ PUNICODE_STRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +#define HASH_STRING_ALGORITHM_DEFAULT 0 +#define HASH_STRING_ALGORITHM_X65599 1 +#define HASH_STRING_ALGORITHM_INVALID 0xffffffff + +NTSYSAPI +NTSTATUS +NTAPI +RtlHashUnicodeString( + _In_ PUNICODE_STRING String, + _In_ BOOLEAN CaseInSensitive, + _In_ ULONG HashAlgorithm, + _Out_ PULONG HashValue + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlValidateUnicodeString( + _In_ ULONG Flags, + _In_ PUNICODE_STRING String + ); + +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlPrefixUnicodeString( + _In_ PCUNICODE_STRING String1, + _In_ PCUNICODE_STRING String2, + _In_ BOOLEAN CaseInSensitive + ); + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +_Must_inspect_result_ +NTSYSAPI +BOOLEAN +NTAPI +RtlSuffixUnicodeString( + _In_ PCUNICODE_STRING String1, + _In_ PCUNICODE_STRING String2, + _In_ BOOLEAN CaseInSensitive + ); +#endif + +#define RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END 0x00000001 +#define RTL_FIND_CHAR_IN_UNICODE_STRING_COMPLEMENT_CHAR_SET 0x00000002 +#define RTL_FIND_CHAR_IN_UNICODE_STRING_CASE_INSENSITIVE 0x00000004 + +NTSYSAPI +NTSTATUS +NTAPI +RtlFindCharInUnicodeString( + _In_ ULONG Flags, + _In_ PUNICODE_STRING StringToSearch, + _In_ PUNICODE_STRING CharSet, + _Out_ PUSHORT NonInclusivePrefixLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAppendUnicodeStringToString( + _In_ PUNICODE_STRING Destination, + _In_ PUNICODE_STRING Source + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAppendUnicodeToString( + _In_ PUNICODE_STRING Destination, + _In_opt_ PWSTR Source + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeString( + _Inout_ PUNICODE_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDowncaseUnicodeString( + _Inout_ PUNICODE_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +VOID +NTAPI +RtlEraseUnicodeString( + _Inout_ PUNICODE_STRING String + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAnsiStringToUnicodeString( + _Inout_ PUNICODE_STRING DestinationString, + _In_ PANSI_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeStringToAnsiString( + _Inout_ PANSI_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +WCHAR +NTAPI +RtlAnsiCharToUnicodeChar( + _Inout_ PUCHAR *SourceCharacter + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeStringToAnsiString( + _Inout_ PANSI_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlOemStringToUnicodeString( + _Inout_ PUNICODE_STRING DestinationString, + _In_ POEM_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeStringToOemString( + _Inout_ POEM_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeStringToOemString( + _Inout_ POEM_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeStringToCountedOemString( + _Inout_ POEM_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeStringToCountedOemString( + _Inout_ POEM_STRING DestinationString, + _In_ PUNICODE_STRING SourceString, + _In_ BOOLEAN AllocateDestinationString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlMultiByteToUnicodeN( + _Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG MaxBytesInUnicodeString, + _Out_opt_ PULONG BytesInUnicodeString, + _In_reads_bytes_(BytesInMultiByteString) PSTR MultiByteString, + _In_ ULONG BytesInMultiByteString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlMultiByteToUnicodeSize( + _Out_ PULONG BytesInUnicodeString, + _In_reads_bytes_(BytesInMultiByteString) PSTR MultiByteString, + _In_ ULONG BytesInMultiByteString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToMultiByteN( + _Out_writes_bytes_to_(MaxBytesInMultiByteString, *BytesInMultiByteString) PCHAR MultiByteString, + _In_ ULONG MaxBytesInMultiByteString, + _Out_opt_ PULONG BytesInMultiByteString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToMultiByteSize( + _Out_ PULONG BytesInMultiByteString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeToMultiByteN( + _Out_writes_bytes_to_(MaxBytesInMultiByteString, *BytesInMultiByteString) PCHAR MultiByteString, + _In_ ULONG MaxBytesInMultiByteString, + _Out_opt_ PULONG BytesInMultiByteString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlOemToUnicodeN( + _Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWSTR UnicodeString, + _In_ ULONG MaxBytesInUnicodeString, + _Out_opt_ PULONG BytesInUnicodeString, + _In_reads_bytes_(BytesInOemString) PCH OemString, + _In_ ULONG BytesInOemString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToOemN( + _Out_writes_bytes_to_(MaxBytesInOemString, *BytesInOemString) PCHAR OemString, + _In_ ULONG MaxBytesInOemString, + _Out_opt_ PULONG BytesInOemString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeToOemN( + _Out_writes_bytes_to_(MaxBytesInOemString, *BytesInOemString) PCHAR OemString, + _In_ ULONG MaxBytesInOemString, + _Out_opt_ PULONG BytesInOemString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlConsoleMultiByteToUnicodeN( + _Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG MaxBytesInUnicodeString, + _Out_opt_ PULONG BytesInUnicodeString, + _In_reads_bytes_(BytesInMultiByteString) PCH MultiByteString, + _In_ ULONG BytesInMultiByteString, + _Out_ PULONG pdwSpecialChar + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSAPI +NTSTATUS +NTAPI +RtlUTF8ToUnicodeN( + _Out_writes_bytes_to_(UnicodeStringMaxByteCount, *UnicodeStringActualByteCount) PWSTR UnicodeStringDestination, + _In_ ULONG UnicodeStringMaxByteCount, + _Out_ PULONG UnicodeStringActualByteCount, + _In_reads_bytes_(UTF8StringByteCount) PCH UTF8StringSource, + _In_ ULONG UTF8StringByteCount + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToUTF8N( + _Out_writes_bytes_to_(UTF8StringMaxByteCount, *UTF8StringActualByteCount) PCHAR UTF8StringDestination, + _In_ ULONG UTF8StringMaxByteCount, + _Out_ PULONG UTF8StringActualByteCount, + _In_reads_bytes_(UnicodeStringByteCount) PWCH UnicodeStringSource, + _In_ ULONG UnicodeStringByteCount + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlCustomCPToUnicodeN( + _In_ PCPTABLEINFO CustomCP, + _Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG MaxBytesInUnicodeString, + _Out_opt_ PULONG BytesInUnicodeString, + _In_reads_bytes_(BytesInCustomCPString) PCH CustomCPString, + _In_ ULONG BytesInCustomCPString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeToCustomCPN( + _In_ PCPTABLEINFO CustomCP, + _Out_writes_bytes_to_(MaxBytesInCustomCPString, *BytesInCustomCPString) PCH CustomCPString, + _In_ ULONG MaxBytesInCustomCPString, + _Out_opt_ PULONG BytesInCustomCPString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpcaseUnicodeToCustomCPN( + _In_ PCPTABLEINFO CustomCP, + _Out_writes_bytes_to_(MaxBytesInCustomCPString, *BytesInCustomCPString) PCH CustomCPString, + _In_ ULONG MaxBytesInCustomCPString, + _Out_opt_ PULONG BytesInCustomCPString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + +NTSYSAPI +VOID +NTAPI +RtlInitCodePageTable( + _In_ PUSHORT TableBase, + _Out_ PCPTABLEINFO CodePageTable + ); + +NTSYSAPI +VOID +NTAPI +RtlInitNlsTables( + _In_ PUSHORT AnsiNlsBase, + _In_ PUSHORT OemNlsBase, + _In_ PUSHORT LanguageNlsBase, + _Out_ PNLSTABLEINFO TableInfo + ); + +NTSYSAPI +VOID +NTAPI +RtlResetRtlTranslations( + _In_ PNLSTABLEINFO TableInfo + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlIsTextUnicode( + _In_ PVOID Buffer, + _In_ ULONG Size, + _Inout_opt_ PULONG Result + ); + +typedef enum _RTL_NORM_FORM +{ + NormOther = 0x0, + NormC = 0x1, + NormD = 0x2, + NormKC = 0x5, + NormKD = 0x6, + NormIdna = 0xd, + DisallowUnassigned = 0x100, + NormCDisallowUnassigned = 0x101, + NormDDisallowUnassigned = 0x102, + NormKCDisallowUnassigned = 0x105, + NormKDDisallowUnassigned = 0x106, + NormIdnaDisallowUnassigned = 0x10d +} RTL_NORM_FORM; + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +NTSTATUS +NTAPI +RtlNormalizeString( + _In_ ULONG NormForm, // RTL_NORM_FORM + _In_ PCWSTR SourceString, + _In_ LONG SourceStringLength, + _Out_writes_to_(*DestinationStringLength, *DestinationStringLength) PWSTR DestinationString, + _Inout_ PLONG DestinationStringLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +NTSTATUS +NTAPI +RtlIsNormalizedString( + _In_ ULONG NormForm, // RTL_NORM_FORM + _In_ PCWSTR SourceString, + _In_ LONG SourceStringLength, + _Out_ PBOOLEAN Normalized + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +// ntifs:FsRtlIsNameInExpression +NTSYSAPI +BOOLEAN +NTAPI +RtlIsNameInExpression( + _In_ PUNICODE_STRING Expression, + _In_ PUNICODE_STRING Name, + _In_ BOOLEAN IgnoreCase, + _In_opt_ PWCH UpcaseTable + ); +#endif + +NTSYSAPI +BOOLEAN +NTAPI +RtlEqualDomainName( + _In_ PUNICODE_STRING String1, + _In_ PUNICODE_STRING String2 + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlEqualComputerName( + _In_ PUNICODE_STRING String1, + _In_ PUNICODE_STRING String2 + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDnsHostNameToComputerName( + _Out_ PUNICODE_STRING ComputerNameString, + _In_ PCUNICODE_STRING DnsHostNameString, + _In_ BOOLEAN AllocateComputerNameString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlStringFromGUID( + _In_ PGUID Guid, + _Out_ PUNICODE_STRING GuidString + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGUIDFromString( + _In_ PUNICODE_STRING GuidString, + _Out_ PGUID Guid + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +LONG +NTAPI +RtlCompareAltitudes( + _In_ PUNICODE_STRING Altitude1, + _In_ PUNICODE_STRING Altitude2 + ); +#endif + +// Prefix + +typedef struct _PREFIX_TABLE_ENTRY +{ + CSHORT NodeTypeCode; + CSHORT NameLength; + struct _PREFIX_TABLE_ENTRY *NextPrefixTree; + RTL_SPLAY_LINKS Links; + PSTRING Prefix; +} PREFIX_TABLE_ENTRY, *PPREFIX_TABLE_ENTRY; + +typedef struct _PREFIX_TABLE +{ + CSHORT NodeTypeCode; + CSHORT NameLength; + PPREFIX_TABLE_ENTRY NextPrefixTree; +} PREFIX_TABLE, *PPREFIX_TABLE; + +NTSYSAPI +VOID +NTAPI +PfxInitialize( + _Out_ PPREFIX_TABLE PrefixTable + ); + +NTSYSAPI +BOOLEAN +NTAPI +PfxInsertPrefix( + _In_ PPREFIX_TABLE PrefixTable, + _In_ PSTRING Prefix, + _Out_ PPREFIX_TABLE_ENTRY PrefixTableEntry + ); + +NTSYSAPI +VOID +NTAPI +PfxRemovePrefix( + _In_ PPREFIX_TABLE PrefixTable, + _In_ PPREFIX_TABLE_ENTRY PrefixTableEntry + ); + +NTSYSAPI +PPREFIX_TABLE_ENTRY +NTAPI +PfxFindPrefix( + _In_ PPREFIX_TABLE PrefixTable, + _In_ PSTRING FullName + ); + +typedef struct _UNICODE_PREFIX_TABLE_ENTRY +{ + CSHORT NodeTypeCode; + CSHORT NameLength; + struct _UNICODE_PREFIX_TABLE_ENTRY *NextPrefixTree; + struct _UNICODE_PREFIX_TABLE_ENTRY *CaseMatch; + RTL_SPLAY_LINKS Links; + PUNICODE_STRING Prefix; +} UNICODE_PREFIX_TABLE_ENTRY, *PUNICODE_PREFIX_TABLE_ENTRY; + +typedef struct _UNICODE_PREFIX_TABLE +{ + CSHORT NodeTypeCode; + CSHORT NameLength; + PUNICODE_PREFIX_TABLE_ENTRY NextPrefixTree; + PUNICODE_PREFIX_TABLE_ENTRY LastNextEntry; +} UNICODE_PREFIX_TABLE, *PUNICODE_PREFIX_TABLE; + +NTSYSAPI +VOID +NTAPI +RtlInitializeUnicodePrefix( + _Out_ PUNICODE_PREFIX_TABLE PrefixTable + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlInsertUnicodePrefix( + _In_ PUNICODE_PREFIX_TABLE PrefixTable, + _In_ PUNICODE_STRING Prefix, + _Out_ PUNICODE_PREFIX_TABLE_ENTRY PrefixTableEntry + ); + +NTSYSAPI +VOID +NTAPI +RtlRemoveUnicodePrefix( + _In_ PUNICODE_PREFIX_TABLE PrefixTable, + _In_ PUNICODE_PREFIX_TABLE_ENTRY PrefixTableEntry + ); + +NTSYSAPI +PUNICODE_PREFIX_TABLE_ENTRY +NTAPI +RtlFindUnicodePrefix( + _In_ PUNICODE_PREFIX_TABLE PrefixTable, + _In_ PUNICODE_STRING FullName, + _In_ ULONG CaseInsensitiveIndex + ); + +NTSYSAPI +PUNICODE_PREFIX_TABLE_ENTRY +NTAPI +RtlNextUnicodePrefix( + _In_ PUNICODE_PREFIX_TABLE PrefixTable, + _In_ BOOLEAN Restart + ); + +// Compression + +typedef struct _COMPRESSED_DATA_INFO +{ + USHORT CompressionFormatAndEngine; // COMPRESSION_FORMAT_* and COMPRESSION_ENGINE_* + + UCHAR CompressionUnitShift; + UCHAR ChunkShift; + UCHAR ClusterShift; + UCHAR Reserved; + + USHORT NumberOfChunks; + + ULONG CompressedChunkSizes[1]; +} COMPRESSED_DATA_INFO, *PCOMPRESSED_DATA_INFO; + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetCompressionWorkSpaceSize( + _In_ USHORT CompressionFormatAndEngine, + _Out_ PULONG CompressBufferWorkSpaceSize, + _Out_ PULONG CompressFragmentWorkSpaceSize + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCompressBuffer( + _In_ USHORT CompressionFormatAndEngine, + _In_reads_bytes_(UncompressedBufferSize) PUCHAR UncompressedBuffer, + _In_ ULONG UncompressedBufferSize, + _Out_writes_bytes_to_(CompressedBufferSize, *FinalCompressedSize) PUCHAR CompressedBuffer, + _In_ ULONG CompressedBufferSize, + _In_ ULONG UncompressedChunkSize, + _Out_ PULONG FinalCompressedSize, + _In_ PVOID WorkSpace + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDecompressBuffer( + _In_ USHORT CompressionFormat, + _Out_writes_bytes_to_(UncompressedBufferSize, *FinalUncompressedSize) PUCHAR UncompressedBuffer, + _In_ ULONG UncompressedBufferSize, + _In_reads_bytes_(CompressedBufferSize) PUCHAR CompressedBuffer, + _In_ ULONG CompressedBufferSize, + _Out_ PULONG FinalUncompressedSize + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSAPI +NTSTATUS +NTAPI +RtlDecompressBufferEx( + _In_ USHORT CompressionFormat, + _Out_writes_bytes_to_(UncompressedBufferSize, *FinalUncompressedSize) PUCHAR UncompressedBuffer, + _In_ ULONG UncompressedBufferSize, + _In_reads_bytes_(CompressedBufferSize) PUCHAR CompressedBuffer, + _In_ ULONG CompressedBufferSize, + _Out_ PULONG FinalUncompressedSize, + _In_ PVOID WorkSpace + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlDecompressFragment( + _In_ USHORT CompressionFormat, + _Out_writes_bytes_to_(UncompressedFragmentSize, *FinalUncompressedSize) PUCHAR UncompressedFragment, + _In_ ULONG UncompressedFragmentSize, + _In_reads_bytes_(CompressedBufferSize) PUCHAR CompressedBuffer, + _In_ ULONG CompressedBufferSize, + _In_range_(<, CompressedBufferSize) ULONG FragmentOffset, + _Out_ PULONG FinalUncompressedSize, + _In_ PVOID WorkSpace + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDescribeChunk( + _In_ USHORT CompressionFormat, + _Inout_ PUCHAR *CompressedBuffer, + _In_ PUCHAR EndOfCompressedBufferPlus1, + _Out_ PUCHAR *ChunkBuffer, + _Out_ PULONG ChunkSize + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlReserveChunk( + _In_ USHORT CompressionFormat, + _Inout_ PUCHAR *CompressedBuffer, + _In_ PUCHAR EndOfCompressedBufferPlus1, + _Out_ PUCHAR *ChunkBuffer, + _In_ ULONG ChunkSize + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDecompressChunks( + _Out_writes_bytes_(UncompressedBufferSize) PUCHAR UncompressedBuffer, + _In_ ULONG UncompressedBufferSize, + _In_reads_bytes_(CompressedBufferSize) PUCHAR CompressedBuffer, + _In_ ULONG CompressedBufferSize, + _In_reads_bytes_(CompressedTailSize) PUCHAR CompressedTail, + _In_ ULONG CompressedTailSize, + _In_ PCOMPRESSED_DATA_INFO CompressedDataInfo + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCompressChunks( + _In_reads_bytes_(UncompressedBufferSize) PUCHAR UncompressedBuffer, + _In_ ULONG UncompressedBufferSize, + _Out_writes_bytes_(CompressedBufferSize) PUCHAR CompressedBuffer, + _In_range_(>=, (UncompressedBufferSize - (UncompressedBufferSize / 16))) ULONG CompressedBufferSize, + _Inout_updates_bytes_(CompressedDataInfoLength) PCOMPRESSED_DATA_INFO CompressedDataInfo, + _In_range_(>, sizeof(COMPRESSED_DATA_INFO)) ULONG CompressedDataInfoLength, + _In_ PVOID WorkSpace + ); + +// Locale + +#if (PHNT_VERSION >= PHNT_VISTA) + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlConvertLCIDToString( + _In_ LCID LcidValue, + _In_ ULONG Base, + _In_ ULONG Padding, // string is padded to this width + _Out_writes_(Size) PWSTR pResultBuf, + _In_ ULONG Size + ); + +// private +NTSYSAPI +BOOLEAN +NTAPI +RtlIsValidLocaleName( + _In_ PWSTR LocaleName, + _In_ ULONG Flags + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlGetParentLocaleName( + _In_ PWSTR LocaleName, + _Inout_ PUNICODE_STRING ParentLocaleName, + _In_ ULONG Flags, + _In_ BOOLEAN AllocateDestinationString + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlLcidToLocaleName( + _In_ LCID lcid, // sic + _Inout_ PUNICODE_STRING LocaleName, + _In_ ULONG Flags, + _In_ BOOLEAN AllocateDestinationString + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlLocaleNameToLcid( + _In_ PWSTR LocaleName, + _Out_ PLCID lcid, + _In_ ULONG Flags + ); + +// private +NTSYSAPI +BOOLEAN +NTAPI +RtlLCIDToCultureName( + _In_ LCID Lcid, + _Inout_ PUNICODE_STRING String + ); + +// private +NTSYSAPI +BOOLEAN +NTAPI +RtlCultureNameToLCID( + _In_ PUNICODE_STRING String, + _Out_ PLCID Lcid + ); + +// private +NTSYSAPI +VOID +NTAPI +RtlCleanUpTEBLangLists( + VOID + ); + +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) + +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlGetLocaleFileMappingAddress( + _Out_ PVOID *BaseAddress, + _Out_ PLCID DefaultLocaleId, + _Out_ PLARGE_INTEGER DefaultCasingTableSize + ); + +#endif + +// PEB + +NTSYSAPI +VOID +NTAPI +RtlAcquirePebLock( + VOID + ); + +NTSYSAPI +VOID +NTAPI +RtlReleasePebLock( + VOID + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +LOGICAL +NTAPI +RtlTryAcquirePebLock( + VOID + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlAllocateFromPeb( + _In_ ULONG Size, + _Out_ PVOID *Block + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlFreeToPeb( + _In_ PVOID Block, + _In_ ULONG Size + ); + +// Processes + +#define DOS_MAX_COMPONENT_LENGTH 255 +#define DOS_MAX_PATH_LENGTH (DOS_MAX_COMPONENT_LENGTH + 5) + +typedef struct _CURDIR +{ + UNICODE_STRING DosPath; + HANDLE Handle; +} CURDIR, *PCURDIR; + +#define RTL_USER_PROC_CURDIR_CLOSE 0x00000002 +#define RTL_USER_PROC_CURDIR_INHERIT 0x00000003 + +typedef struct _RTL_DRIVE_LETTER_CURDIR +{ + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + STRING DosPath; +} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; + +#define RTL_MAX_DRIVE_LETTERS 32 +#define RTL_DRIVE_LETTER_VALID (USHORT)0x0001 + +typedef struct _RTL_USER_PROCESS_PARAMETERS +{ + ULONG MaximumLength; + ULONG Length; + + ULONG Flags; + ULONG DebugFlags; + + HANDLE ConsoleHandle; + ULONG ConsoleFlags; + HANDLE StandardInput; + HANDLE StandardOutput; + HANDLE StandardError; + + CURDIR CurrentDirectory; + UNICODE_STRING DllPath; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + PVOID Environment; + + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + + ULONG WindowFlags; + ULONG ShowWindowFlags; + UNICODE_STRING WindowTitle; + UNICODE_STRING DesktopInfo; + UNICODE_STRING ShellInfo; + UNICODE_STRING RuntimeData; + RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS]; + + ULONG EnvironmentSize; + ULONG EnvironmentVersion; + PVOID PackageDependencyData; + ULONG ProcessGroupId; + ULONG LoaderThreads; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +#define RTL_USER_PROC_PARAMS_NORMALIZED 0x00000001 +#define RTL_USER_PROC_PROFILE_USER 0x00000002 +#define RTL_USER_PROC_PROFILE_KERNEL 0x00000004 +#define RTL_USER_PROC_PROFILE_SERVER 0x00000008 +#define RTL_USER_PROC_RESERVE_1MB 0x00000020 +#define RTL_USER_PROC_RESERVE_16MB 0x00000040 +#define RTL_USER_PROC_CASE_SENSITIVE 0x00000080 +#define RTL_USER_PROC_DISABLE_HEAP_DECOMMIT 0x00000100 +#define RTL_USER_PROC_DLL_REDIRECTION_LOCAL 0x00001000 +#define RTL_USER_PROC_APP_MANIFEST_PRESENT 0x00002000 +#define RTL_USER_PROC_IMAGE_KEY_MISSING 0x00004000 +#define RTL_USER_PROC_OPTIN_PROCESS 0x00020000 + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateProcessParameters( + _Out_ PRTL_USER_PROCESS_PARAMETERS *pProcessParameters, + _In_ PUNICODE_STRING ImagePathName, + _In_opt_ PUNICODE_STRING DllPath, + _In_opt_ PUNICODE_STRING CurrentDirectory, + _In_opt_ PUNICODE_STRING CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PUNICODE_STRING WindowTitle, + _In_opt_ PUNICODE_STRING DesktopInfo, + _In_opt_ PUNICODE_STRING ShellInfo, + _In_opt_ PUNICODE_STRING RuntimeData + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateProcessParametersEx( + _Out_ PRTL_USER_PROCESS_PARAMETERS *pProcessParameters, + _In_ PUNICODE_STRING ImagePathName, + _In_opt_ PUNICODE_STRING DllPath, + _In_opt_ PUNICODE_STRING CurrentDirectory, + _In_opt_ PUNICODE_STRING CommandLine, + _In_opt_ PVOID Environment, + _In_opt_ PUNICODE_STRING WindowTitle, + _In_opt_ PUNICODE_STRING DesktopInfo, + _In_opt_ PUNICODE_STRING ShellInfo, + _In_opt_ PUNICODE_STRING RuntimeData, + _In_ ULONG Flags // pass RTL_USER_PROC_PARAMS_NORMALIZED to keep parameters normalized + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyProcessParameters( + _In_ _Post_invalid_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters + ); + +NTSYSAPI +PRTL_USER_PROCESS_PARAMETERS +NTAPI +RtlNormalizeProcessParams( + _Inout_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters + ); + +NTSYSAPI +PRTL_USER_PROCESS_PARAMETERS +NTAPI +RtlDeNormalizeProcessParams( + _Inout_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters + ); + +typedef struct _RTL_USER_PROCESS_INFORMATION +{ + ULONG Length; + HANDLE Process; + HANDLE Thread; + CLIENT_ID ClientId; + SECTION_IMAGE_INFORMATION ImageInformation; +} RTL_USER_PROCESS_INFORMATION, *PRTL_USER_PROCESS_INFORMATION; + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateUserProcess( + _In_ PUNICODE_STRING NtImagePathName, + _In_ ULONG AttributesDeprecated, + _In_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters, + _In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + _In_opt_ HANDLE ParentProcess, + _In_ BOOLEAN InheritHandles, + _In_opt_ HANDLE DebugPort, + _In_opt_ HANDLE TokenHandle, // used to be ExceptionPort + _Out_ PRTL_USER_PROCESS_INFORMATION ProcessInformation + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +DECLSPEC_NORETURN +NTSYSAPI +VOID +NTAPI +RtlExitUserProcess( + _In_ NTSTATUS ExitStatus + ); +#else + +#define RtlExitUserProcess RtlExitUserProcess_R + +DECLSPEC_NORETURN +FORCEINLINE VOID RtlExitUserProcess_R( + _In_ NTSTATUS ExitStatus + ) +{ + ExitProcess(ExitStatus); +} + +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) + +// begin_rev +#define RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED 0x00000001 +#define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000002 +#define RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE 0x00000004 // don't update synchronization objects +// end_rev + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlCloneUserProcess( + _In_ ULONG ProcessFlags, + _In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + _In_opt_ HANDLE DebugPort, + _Out_ PRTL_USER_PROCESS_INFORMATION ProcessInformation + ); + +// private +NTSYSAPI +VOID +NTAPI +RtlUpdateClonedCriticalSection( + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +// private +NTSYSAPI +VOID +NTAPI +RtlUpdateClonedSRWLock( + _Inout_ PRTL_SRWLOCK SRWLock, + _In_ LOGICAL Shared // TRUE to set to shared acquire + ); + +// private +typedef struct _RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION +{ + HANDLE ReflectionProcessHandle; + HANDLE ReflectionThreadHandle; + CLIENT_ID ReflectionClientId; +} RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION, *PRTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION; + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateProcessReflection( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_opt_ PVOID StartRoutine, + _In_opt_ PVOID StartContext, + _In_opt_ HANDLE EventHandle, + _Out_opt_ PRTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION ReflectionInformation + ); +#endif + +#endif + +NTSYSAPI +NTSTATUS +STDAPIVCALLTYPE +RtlSetProcessIsCritical( + _In_ BOOLEAN NewValue, + _Out_opt_ PBOOLEAN OldValue, + _In_ BOOLEAN CheckFlag + ); + +NTSYSAPI +NTSTATUS +STDAPIVCALLTYPE +RtlSetThreadIsCritical( + _In_ BOOLEAN NewValue, + _Out_opt_ PBOOLEAN OldValue, + _In_ BOOLEAN CheckFlag + ); + +// Threads + +typedef NTSTATUS (NTAPI *PUSER_THREAD_START_ROUTINE)( + _In_ PVOID ThreadParameter + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateUserThread( + _In_ HANDLE Process, + _In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + _In_ BOOLEAN CreateSuspended, + _In_opt_ ULONG ZeroBits, + _In_opt_ SIZE_T MaximumStackSize, + _In_opt_ SIZE_T CommittedStackSize, + _In_ PUSER_THREAD_START_ROUTINE StartAddress, + _In_opt_ PVOID Parameter, + _Out_opt_ PHANDLE Thread, + _Out_opt_ PCLIENT_ID ClientId + ); + +#if (PHNT_VERSION >= PHNT_VISTA) // should be PHNT_WINXP, but is PHNT_VISTA for consistency with RtlExitUserProcess +DECLSPEC_NORETURN +NTSYSAPI +VOID +NTAPI +RtlExitUserThread( + _In_ NTSTATUS ExitStatus + ); +#else + +#define RtlExitUserThread RtlExitUserThread_R + +DECLSPEC_NORETURN +FORCEINLINE VOID RtlExitUserThread_R( + _In_ NTSTATUS ExitStatus + ) +{ + ExitThread(ExitStatus); +} + +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateUserStack( + _In_opt_ SIZE_T CommittedStackSize, + _In_opt_ SIZE_T MaximumStackSize, + _In_opt_ ULONG_PTR ZeroBits, + _In_ SIZE_T PageSize, + _In_ ULONG_PTR ReserveAlignment, + _Out_ PINITIAL_TEB InitialTeb + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlFreeUserStack( + _In_ PVOID AllocationBase + ); + +#endif + +NTSYSAPI +VOID +NTAPI +RtlInitializeContext( + _In_ HANDLE Process, + _Out_ PCONTEXT Context, + _In_opt_ PVOID Parameter, + _In_opt_ PVOID InitialPc, + _In_opt_ PVOID InitialSp + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlRemoteCall( + _In_ HANDLE Process, + _In_ HANDLE Thread, + _In_ PVOID CallSite, + _In_ ULONG ArgumentCount, + _In_opt_ PULONG_PTR Arguments, + _In_ BOOLEAN PassContext, + _In_ BOOLEAN AlreadySuspended + ); + +#ifdef _WIN64 +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlWow64GetThreadContext( + _In_ HANDLE ThreadHandle, + _Inout_ PWOW64_CONTEXT ThreadContext + ); +#endif + +#ifdef _WIN64 +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlWow64SetThreadContext( + _In_ HANDLE ThreadHandle, + _In_ PWOW64_CONTEXT ThreadContext + ); +#endif + +// Vectored exception handlers + +NTSYSAPI +PVOID +NTAPI +RtlAddVectoredExceptionHandler( + _In_ ULONG First, + _In_ PVECTORED_EXCEPTION_HANDLER Handler + ); + +NTSYSAPI +ULONG +NTAPI +RtlRemoveVectoredExceptionHandler( + _In_ PVOID Handle + ); + +NTSYSAPI +PVOID +NTAPI +RtlAddVectoredContinueHandler( + _In_ ULONG First, + _In_ PVECTORED_EXCEPTION_HANDLER Handler + ); + +NTSYSAPI +ULONG +NTAPI +RtlRemoveVectoredContinueHandler( + _In_ PVOID Handle + ); + +// Runtime exception handling + +#ifdef _WIN64 + +// private +typedef enum _FUNCTION_TABLE_TYPE +{ + RF_SORTED, + RF_UNSORTED, + RF_CALLBACK, + RF_KERNEL_DYNAMIC +} FUNCTION_TABLE_TYPE; + +// private +typedef struct _DYNAMIC_FUNCTION_TABLE +{ + LIST_ENTRY ListEntry; + PRUNTIME_FUNCTION FunctionTable; + LARGE_INTEGER TimeStamp; + ULONG64 MinimumAddress; + ULONG64 MaximumAddress; + ULONG64 BaseAddress; + PGET_RUNTIME_FUNCTION_CALLBACK Callback; + PVOID Context; + PWSTR OutOfProcessCallbackDll; + FUNCTION_TABLE_TYPE Type; + ULONG EntryCount; +} DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE; + +// rev +NTSYSAPI +PLIST_ENTRY +NTAPI +RtlGetFunctionTableListHead( + VOID + ); + +#endif + +// Images + +NTSYSAPI +PVOID +NTAPI +RtlPcToFileHeader( + _In_ PVOID PcValue, + _Out_ PVOID *BaseOfImage + ); + +NTSYSAPI +PIMAGE_NT_HEADERS +NTAPI +RtlImageNtHeader( + _In_ PVOID Base + ); + +#define RTL_IMAGE_NT_HEADER_EX_FLAG_NO_RANGE_CHECK 0x00000001 + +NTSYSAPI +NTSTATUS +NTAPI +RtlImageNtHeaderEx( + _In_ ULONG Flags, + _In_ PVOID Base, + _In_ ULONG64 Size, + _Out_ PIMAGE_NT_HEADERS *OutHeaders + ); + +NTSYSAPI +PVOID +NTAPI +RtlAddressInSectionTable( + _In_ PIMAGE_NT_HEADERS NtHeaders, + _In_ PVOID BaseOfImage, + _In_ ULONG VirtualAddress + ); + +NTSYSAPI +PIMAGE_SECTION_HEADER +NTAPI +RtlSectionTableFromVirtualAddress( + _In_ PIMAGE_NT_HEADERS NtHeaders, + _In_ PVOID BaseOfImage, + _In_ ULONG VirtualAddress + ); + +NTSYSAPI +PVOID +NTAPI +RtlImageDirectoryEntryToData( + _In_ PVOID BaseOfImage, + _In_ BOOLEAN MappedAsImage, + _In_ USHORT DirectoryEntry, + _Out_ PULONG Size + ); + +NTSYSAPI +PIMAGE_SECTION_HEADER +NTAPI +RtlImageRvaToSection( + _In_ PIMAGE_NT_HEADERS NtHeaders, + _In_ PVOID Base, + _In_ ULONG Rva + ); + +NTSYSAPI +PVOID +NTAPI +RtlImageRvaToVa( + _In_ PIMAGE_NT_HEADERS NtHeaders, + _In_ PVOID Base, + _In_ ULONG Rva, + _Inout_opt_ PIMAGE_SECTION_HEADER *LastRvaSection + ); + +// Memory + +NTSYSAPI +SIZE_T +NTAPI +RtlCompareMemoryUlong( + _In_ PVOID Source, + _In_ SIZE_T Length, + _In_ ULONG Pattern + ); + +NTSYSAPI +VOID +NTAPI +RtlFillMemoryUlong( + _Out_ PVOID Destination, + _In_ SIZE_T Length, + _In_ ULONG Pattern + ); + +NTSYSAPI +VOID +NTAPI +RtlFillMemoryUlonglong( + _Out_ PVOID Destination, + _In_ SIZE_T Length, + _In_ ULONGLONG Pattern + ); + +// Environment + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateEnvironment( + _In_ BOOLEAN CloneCurrentEnvironment, + _Out_ PVOID *Environment + ); + +// begin_rev +#define RTL_CREATE_ENVIRONMENT_TRANSLATE 0x1 // translate from multi-byte to Unicode +#define RTL_CREATE_ENVIRONMENT_TRANSLATE_FROM_OEM 0x2 // translate from OEM to Unicode (Translate flag must also be set) +#define RTL_CREATE_ENVIRONMENT_EMPTY 0x4 // create empty environment block +// end_rev + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateEnvironmentEx( + _In_ PVOID SourceEnv, + _Out_ PVOID *Environment, + _In_ ULONG Flags + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyEnvironment( + _In_ PVOID Environment + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetCurrentEnvironment( + _In_ PVOID Environment, + _Out_opt_ PVOID *PreviousEnvironment + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSetEnvironmentVar( + _In_opt_ PWSTR *Environment, + _In_reads_(NameLength) PWSTR Name, + _In_ SIZE_T NameLength, + _In_reads_(ValueLength) PWSTR Value, + _In_ SIZE_T ValueLength + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetEnvironmentVariable( + _In_opt_ PVOID *Environment, + _In_ PUNICODE_STRING Name, + _In_ PUNICODE_STRING Value + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryEnvironmentVariable( + _In_opt_ PVOID Environment, + _In_reads_(NameLength) PWSTR Name, + _In_ SIZE_T NameLength, + _Out_writes_(ValueLength) PWSTR Value, + _In_ SIZE_T ValueLength, + _Out_ PSIZE_T ReturnLength + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryEnvironmentVariable_U( + _In_opt_ PVOID Environment, + _In_ PUNICODE_STRING Name, + _Out_ PUNICODE_STRING Value + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlExpandEnvironmentStrings( + _In_opt_ PVOID Environment, + _In_reads_(SrcLength) PWSTR Src, + _In_ SIZE_T SrcLength, + _Out_writes_(DstLength) PWSTR Dst, + _In_ SIZE_T DstLength, + _Out_opt_ PSIZE_T ReturnLength + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlExpandEnvironmentStrings_U( + _In_opt_ PVOID Environment, + _In_ PUNICODE_STRING Source, + _Out_ PUNICODE_STRING Destination, + _Out_opt_ PULONG ReturnedLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetEnvironmentStrings( + _In_ PWCHAR NewEnvironment, + _In_ SIZE_T NewEnvironmentSize + ); + +// Current directory and paths + +typedef struct _RTLP_CURDIR_REF *PRTLP_CURDIR_REF; + +typedef struct _RTL_RELATIVE_NAME_U +{ + UNICODE_STRING RelativeName; + HANDLE ContainingDirectory; + PRTLP_CURDIR_REF CurDirRef; +} RTL_RELATIVE_NAME_U, *PRTL_RELATIVE_NAME_U; + +typedef enum _RTL_PATH_TYPE +{ + RtlPathTypeUnknown, + RtlPathTypeUncAbsolute, + RtlPathTypeDriveAbsolute, + RtlPathTypeDriveRelative, + RtlPathTypeRooted, + RtlPathTypeRelative, + RtlPathTypeLocalDevice, + RtlPathTypeRootLocalDevice +} RTL_PATH_TYPE; + +NTSYSAPI +RTL_PATH_TYPE +NTAPI +RtlDetermineDosPathNameType_U( + _In_ PWSTR DosFileName + ); + +NTSYSAPI +ULONG +NTAPI +RtlIsDosDeviceName_U( + _In_ PWSTR DosFileName + ); + +NTSYSAPI +ULONG +NTAPI +RtlGetFullPathName_U( + _In_ PWSTR FileName, + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PWSTR Buffer, + _Out_opt_ PWSTR *FilePart + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlGetFullPathName_UEx( + _In_ PWSTR FileName, + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PWSTR Buffer, + _Out_opt_ PWSTR *FilePart, + _Out_opt_ RTL_PATH_TYPE *InputPathType + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +NTSTATUS +NTAPI +RtlGetFullPathName_UstrEx( + _In_ PUNICODE_STRING FileName, + _Inout_ PUNICODE_STRING StaticString, + _Out_opt_ PUNICODE_STRING DynamicString, + _Out_opt_ PUNICODE_STRING *StringUsed, + _Out_opt_ SIZE_T *FilePartPrefixCch, + _Out_opt_ PBOOLEAN NameInvalid, + _Out_ RTL_PATH_TYPE *InputPathType, + _Out_opt_ SIZE_T *BytesRequired + ); +#endif + +NTSYSAPI +ULONG +NTAPI +RtlGetCurrentDirectory_U( + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PWSTR Buffer + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetCurrentDirectory_U( + _In_ PUNICODE_STRING PathName + ); + +NTSYSAPI +ULONG +NTAPI +RtlGetLongestNtPathLength( + VOID + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlDosPathNameToNtPathName_U( + _In_ PWSTR DosFileName, + _Out_ PUNICODE_STRING NtFileName, + _Out_opt_ PWSTR *FilePart, + _Out_opt_ PRTL_RELATIVE_NAME_U RelativeName + ); + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +NTSTATUS +NTAPI +RtlDosPathNameToNtPathName_U_WithStatus( + _In_ PWSTR DosFileName, + _Out_ PUNICODE_STRING NtFileName, + _Out_opt_ PWSTR *FilePart, + _Out_opt_ PRTL_RELATIVE_NAME_U RelativeName + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +BOOLEAN +NTAPI +RtlDosPathNameToRelativeNtPathName_U( + _In_ PWSTR DosFileName, + _Out_ PUNICODE_STRING NtFileName, + _Out_opt_ PWSTR *FilePart, + _Out_opt_ PRTL_RELATIVE_NAME_U RelativeName + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +NTSTATUS +NTAPI +RtlDosPathNameToRelativeNtPathName_U_WithStatus( + _In_ PWSTR DosFileName, + _Out_ PUNICODE_STRING NtFileName, + _Out_opt_ PWSTR *FilePart, + _Out_opt_ PRTL_RELATIVE_NAME_U RelativeName + ); +#endif + +#if (PHNT_VERSION >= PHNT_WS03) +NTSYSAPI +VOID +NTAPI +RtlReleaseRelativeName( + _Inout_ PRTL_RELATIVE_NAME_U RelativeName + ); +#endif + +NTSYSAPI +ULONG +NTAPI +RtlDosSearchPath_U( + _In_ PWSTR Path, + _In_ PWSTR FileName, + _In_opt_ PWSTR Extension, + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PWSTR Buffer, + _Out_opt_ PWSTR *FilePart + ); + +#define RTL_DOS_SEARCH_PATH_FLAG_APPLY_ISOLATION_REDIRECTION 0x00000001 +#define RTL_DOS_SEARCH_PATH_FLAG_DISALLOW_DOT_RELATIVE_PATH_SEARCH 0x00000002 +#define RTL_DOS_SEARCH_PATH_FLAG_APPLY_DEFAULT_EXTENSION_WHEN_NOT_RELATIVE_PATH_EVEN_IF_FILE_HAS_EXTENSION 0x00000004) + +NTSYSAPI +NTSTATUS +NTAPI +RtlDosSearchPath_Ustr( + _In_ ULONG Flags, + _In_ PUNICODE_STRING Path, + _In_ PUNICODE_STRING FileName, + _In_opt_ PUNICODE_STRING DefaultExtension, + _Out_opt_ PUNICODE_STRING StaticString, + _Out_opt_ PUNICODE_STRING DynamicString, + _Out_opt_ PCUNICODE_STRING *FullFileNameOut, + _Out_opt_ SIZE_T *FilePartPrefixCch, + _Out_opt_ SIZE_T *BytesRequired + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlDoesFileExists_U( + _In_ PWSTR FileName + ); + +// Heaps + +typedef struct _RTL_HEAP_ENTRY +{ + SIZE_T Size; + USHORT Flags; + USHORT AllocatorBackTraceIndex; + union + { + struct + { + SIZE_T Settable; + ULONG Tag; + } s1; + struct + { + SIZE_T CommittedSize; + PVOID FirstBlock; + } s2; + } u; +} RTL_HEAP_ENTRY, *PRTL_HEAP_ENTRY; + +#define RTL_HEAP_BUSY (USHORT)0x0001 +#define RTL_HEAP_SEGMENT (USHORT)0x0002 +#define RTL_HEAP_SETTABLE_VALUE (USHORT)0x0010 +#define RTL_HEAP_SETTABLE_FLAG1 (USHORT)0x0020 +#define RTL_HEAP_SETTABLE_FLAG2 (USHORT)0x0040 +#define RTL_HEAP_SETTABLE_FLAG3 (USHORT)0x0080 +#define RTL_HEAP_SETTABLE_FLAGS (USHORT)0x00e0 +#define RTL_HEAP_UNCOMMITTED_RANGE (USHORT)0x0100 +#define RTL_HEAP_PROTECTED_ENTRY (USHORT)0x0200 + +typedef struct _RTL_HEAP_TAG +{ + ULONG NumberOfAllocations; + ULONG NumberOfFrees; + SIZE_T BytesAllocated; + USHORT TagIndex; + USHORT CreatorBackTraceIndex; + WCHAR TagName[24]; +} RTL_HEAP_TAG, *PRTL_HEAP_TAG; + +typedef struct _RTL_HEAP_INFORMATION +{ + PVOID BaseAddress; + ULONG Flags; + USHORT EntryOverhead; + USHORT CreatorBackTraceIndex; + SIZE_T BytesAllocated; + SIZE_T BytesCommitted; + ULONG NumberOfTags; + ULONG NumberOfEntries; + ULONG NumberOfPseudoTags; + ULONG PseudoTagGranularity; + ULONG Reserved[5]; + PRTL_HEAP_TAG Tags; + PRTL_HEAP_ENTRY Entries; +} RTL_HEAP_INFORMATION, *PRTL_HEAP_INFORMATION; + +typedef struct _RTL_PROCESS_HEAPS +{ + ULONG NumberOfHeaps; + RTL_HEAP_INFORMATION Heaps[1]; +} RTL_PROCESS_HEAPS, *PRTL_PROCESS_HEAPS; + +typedef NTSTATUS (NTAPI *PRTL_HEAP_COMMIT_ROUTINE)( + _In_ PVOID Base, + _Inout_ PVOID *CommitAddress, + _Inout_ PSIZE_T CommitSize + ); + +typedef struct _RTL_HEAP_PARAMETERS +{ + ULONG Length; + SIZE_T SegmentReserve; + SIZE_T SegmentCommit; + SIZE_T DeCommitFreeBlockThreshold; + SIZE_T DeCommitTotalFreeThreshold; + SIZE_T MaximumAllocationSize; + SIZE_T VirtualMemoryThreshold; + SIZE_T InitialCommit; + SIZE_T InitialReserve; + PRTL_HEAP_COMMIT_ROUTINE CommitRoutine; + SIZE_T Reserved[2]; +} RTL_HEAP_PARAMETERS, *PRTL_HEAP_PARAMETERS; + +#define HEAP_SETTABLE_USER_VALUE 0x00000100 +#define HEAP_SETTABLE_USER_FLAG1 0x00000200 +#define HEAP_SETTABLE_USER_FLAG2 0x00000400 +#define HEAP_SETTABLE_USER_FLAG3 0x00000800 +#define HEAP_SETTABLE_USER_FLAGS 0x00000e00 + +#define HEAP_CLASS_0 0x00000000 // Process heap +#define HEAP_CLASS_1 0x00001000 // Private heap +#define HEAP_CLASS_2 0x00002000 // Kernel heap +#define HEAP_CLASS_3 0x00003000 // GDI heap +#define HEAP_CLASS_4 0x00004000 // User heap +#define HEAP_CLASS_5 0x00005000 // Console heap +#define HEAP_CLASS_6 0x00006000 // User desktop heap +#define HEAP_CLASS_7 0x00007000 // CSR shared heap +#define HEAP_CLASS_8 0x00008000 // CSR port heap +#define HEAP_CLASS_MASK 0x0000f000 + +NTSYSAPI +PVOID +NTAPI +RtlCreateHeap( + _In_ ULONG Flags, + _In_opt_ PVOID HeapBase, + _In_opt_ SIZE_T ReserveSize, + _In_opt_ SIZE_T CommitSize, + _In_opt_ PVOID Lock, + _In_opt_ PRTL_HEAP_PARAMETERS Parameters + ); + +NTSYSAPI +PVOID +NTAPI +RtlDestroyHeap( + _Frees_ptr_ PVOID HeapHandle + ); + +NTSYSAPI +PVOID +NTAPI +RtlAllocateHeap( + _In_ PVOID HeapHandle, + _In_opt_ ULONG Flags, + _In_ SIZE_T Size + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlFreeHeap( + _In_ PVOID HeapHandle, + _In_opt_ ULONG Flags, + _Frees_ptr_opt_ PVOID BaseAddress + ); + +NTSYSAPI +SIZE_T +NTAPI +RtlSizeHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlZeroHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags + ); + +NTSYSAPI +VOID +NTAPI +RtlProtectHeap( + _In_ PVOID HeapHandle, + _In_ BOOLEAN MakeReadOnly + ); + +#define RtlProcessHeap() (NtCurrentPeb()->ProcessHeap) + +NTSYSAPI +BOOLEAN +NTAPI +RtlLockHeap( + _In_ PVOID HeapHandle + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlUnlockHeap( + _In_ PVOID HeapHandle + ); + +NTSYSAPI +PVOID +NTAPI +RtlReAllocateHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _Frees_ptr_opt_ PVOID BaseAddress, + _In_ SIZE_T Size + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlGetUserInfoHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress, + _Out_opt_ PVOID *UserValue, + _Out_opt_ PULONG UserFlags + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlSetUserValueHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress, + _In_ PVOID UserValue + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlSetUserFlagsHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress, + _In_ ULONG UserFlagsReset, + _In_ ULONG UserFlagsSet + ); + +typedef struct _RTL_HEAP_TAG_INFO +{ + ULONG NumberOfAllocations; + ULONG NumberOfFrees; + SIZE_T BytesAllocated; +} RTL_HEAP_TAG_INFO, *PRTL_HEAP_TAG_INFO; + +#define RTL_HEAP_MAKE_TAG HEAP_MAKE_TAG_FLAGS + +NTSYSAPI +ULONG +NTAPI +RtlCreateTagHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_opt_ PWSTR TagPrefix, + _In_ PWSTR TagNames + ); + +NTSYSAPI +PWSTR +NTAPI +RtlQueryTagHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ USHORT TagIndex, + _In_ BOOLEAN ResetCounters, + _Out_opt_ PRTL_HEAP_TAG_INFO TagInfo + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlExtendHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID Base, + _In_ SIZE_T Size + ); + +NTSYSAPI +SIZE_T +NTAPI +RtlCompactHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlValidateHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlValidateProcessHeaps( + VOID + ); + +NTSYSAPI +ULONG +NTAPI +RtlGetProcessHeaps( + _In_ ULONG NumberOfHeaps, + _Out_ PVOID *ProcessHeaps + ); + +typedef NTSTATUS (NTAPI *PRTL_ENUM_HEAPS_ROUTINE)( + _In_ PVOID HeapHandle, + _In_ PVOID Parameter + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlEnumProcessHeaps( + _In_ PRTL_ENUM_HEAPS_ROUTINE EnumRoutine, + _In_ PVOID Parameter + ); + +typedef struct _RTL_HEAP_USAGE_ENTRY +{ + struct _RTL_HEAP_USAGE_ENTRY *Next; + PVOID Address; + SIZE_T Size; + USHORT AllocatorBackTraceIndex; + USHORT TagIndex; +} RTL_HEAP_USAGE_ENTRY, *PRTL_HEAP_USAGE_ENTRY; + +typedef struct _RTL_HEAP_USAGE +{ + ULONG Length; + SIZE_T BytesAllocated; + SIZE_T BytesCommitted; + SIZE_T BytesReserved; + SIZE_T BytesReservedMaximum; + PRTL_HEAP_USAGE_ENTRY Entries; + PRTL_HEAP_USAGE_ENTRY AddedEntries; + PRTL_HEAP_USAGE_ENTRY RemovedEntries; + ULONG_PTR Reserved[8]; +} RTL_HEAP_USAGE, *PRTL_HEAP_USAGE; + +#define HEAP_USAGE_ALLOCATED_BLOCKS HEAP_REALLOC_IN_PLACE_ONLY +#define HEAP_USAGE_FREE_BUFFER HEAP_ZERO_MEMORY + +NTSYSAPI +NTSTATUS +NTAPI +RtlUsageHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _Inout_ PRTL_HEAP_USAGE Usage + ); + +typedef struct _RTL_HEAP_WALK_ENTRY +{ + PVOID DataAddress; + SIZE_T DataSize; + UCHAR OverheadBytes; + UCHAR SegmentIndex; + USHORT Flags; + union + { + struct + { + SIZE_T Settable; + USHORT TagIndex; + USHORT AllocatorBackTraceIndex; + ULONG Reserved[2]; + } Block; + struct + { + ULONG CommittedSize; + ULONG UnCommittedSize; + PVOID FirstEntry; + PVOID LastEntry; + } Segment; + }; +} RTL_HEAP_WALK_ENTRY, *PRTL_HEAP_WALK_ENTRY; + +NTSYSAPI +NTSTATUS +NTAPI +RtlWalkHeap( + _In_ PVOID HeapHandle, + _Inout_ PRTL_HEAP_WALK_ENTRY Entry + ); + +// rev +#define HeapDebuggingInformation 0x80000002 + +// rev +typedef NTSTATUS (NTAPI *PRTL_HEAP_LEAK_ENUMERATION_ROUTINE)( + _In_ LONG Reserved, + _In_ PVOID HeapHandle, + _In_ PVOID BaseAddress, + _In_ SIZE_T BlockSize, + _In_ ULONG StackTraceDepth, + _In_ PVOID *StackTrace + ); + +// symbols +typedef struct _HEAP_DEBUGGING_INFORMATION +{ + PVOID InterceptorFunction; + USHORT InterceptorValue; + ULONG ExtendedOptions; + ULONG StackTraceDepth; + SIZE_T MinTotalBlockSize; + SIZE_T MaxTotalBlockSize; + PRTL_HEAP_LEAK_ENUMERATION_ROUTINE HeapLeakEnumerationRoutine; +} HEAP_DEBUGGING_INFORMATION, *PHEAP_DEBUGGING_INFORMATION; + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryHeapInformation( + _In_ PVOID HeapHandle, + _In_ HEAP_INFORMATION_CLASS HeapInformationClass, + _Out_opt_ PVOID HeapInformation, + _In_opt_ SIZE_T HeapInformationLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetHeapInformation( + _In_ PVOID HeapHandle, + _In_ HEAP_INFORMATION_CLASS HeapInformationClass, + _In_opt_ PVOID HeapInformation, + _In_opt_ SIZE_T HeapInformationLength + ); + +NTSYSAPI +ULONG +NTAPI +RtlMultipleAllocateHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ SIZE_T Size, + _In_ ULONG Count, + _Out_ PVOID *Array + ); + +NTSYSAPI +ULONG +NTAPI +RtlMultipleFreeHeap( + _In_ PVOID HeapHandle, + _In_ ULONG Flags, + _In_ ULONG Count, + _In_ PVOID *Array + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSAPI +VOID +NTAPI +RtlDetectHeapLeaks( + VOID + ); +#endif + +// Memory zones + +// begin_private + +typedef struct _RTL_MEMORY_ZONE_SEGMENT +{ + struct _RTL_MEMORY_ZONE_SEGMENT *NextSegment; + SIZE_T Size; + PVOID Next; + PVOID Limit; +} RTL_MEMORY_ZONE_SEGMENT, *PRTL_MEMORY_ZONE_SEGMENT; + +typedef struct _RTL_MEMORY_ZONE +{ + RTL_MEMORY_ZONE_SEGMENT Segment; + RTL_SRWLOCK Lock; + ULONG LockCount; + PRTL_MEMORY_ZONE_SEGMENT FirstSegment; +} RTL_MEMORY_ZONE, *PRTL_MEMORY_ZONE; + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateMemoryZone( + _Out_ PVOID *MemoryZone, + _In_ SIZE_T InitialSize, + _Reserved_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyMemoryZone( + _In_ _Post_invalid_ PVOID MemoryZone + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAllocateMemoryZone( + _In_ PVOID MemoryZone, + _In_ SIZE_T BlockSize, + _Out_ PVOID *Block + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlResetMemoryZone( + _In_ PVOID MemoryZone + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLockMemoryZone( + _In_ PVOID MemoryZone + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnlockMemoryZone( + _In_ PVOID MemoryZone + ); + +#endif + +// end_private + +// Memory block lookaside lists + +// begin_private + +#if (PHNT_VERSION >= PHNT_VISTA) + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateMemoryBlockLookaside( + _Out_ PVOID *MemoryBlockLookaside, + _Reserved_ ULONG Flags, + _In_ ULONG InitialSize, + _In_ ULONG MinimumBlockSize, + _In_ ULONG MaximumBlockSize + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAllocateMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside, + _In_ ULONG BlockSize, + _Out_ PVOID *Block + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlFreeMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside, + _In_ PVOID Block + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlExtendMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside, + _In_ ULONG Increment + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlResetMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLockMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnlockMemoryBlockLookaside( + _In_ PVOID MemoryBlockLookaside + ); + +#endif + +// end_private + +// Transactions + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +HANDLE +NTAPI +RtlGetCurrentTransaction( + VOID + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +LOGICAL +NTAPI +RtlSetCurrentTransaction( + _In_ HANDLE TransactionHandle + ); +#endif + +// LUIDs + +FORCEINLINE BOOLEAN RtlIsEqualLuid( + _In_ PLUID L1, + _In_ PLUID L2 + ) +{ + return L1->LowPart == L2->LowPart && + L1->HighPart == L2->HighPart; +} + +FORCEINLINE BOOLEAN RtlIsZeroLuid( + _In_ PLUID L1 + ) +{ + return (L1->LowPart | L1->HighPart) == 0; +} + +FORCEINLINE LUID RtlConvertLongToLuid( + _In_ LONG Long + ) +{ + LUID tempLuid; + LARGE_INTEGER tempLi; + + tempLi.QuadPart = Long; + tempLuid.LowPart = tempLi.LowPart; + tempLuid.HighPart = tempLi.HighPart; + + return tempLuid; +} + +FORCEINLINE LUID RtlConvertUlongToLuid( + _In_ ULONG Ulong + ) +{ + LUID tempLuid; + + tempLuid.LowPart = Ulong; + tempLuid.HighPart = 0; + + return tempLuid; +} + +NTSYSAPI +VOID +NTAPI +RtlCopyLuid( + _Out_ PLUID DestinationLuid, + _In_ PLUID SourceLuid + ); + +// Debugging + +// private +typedef struct _RTL_PROCESS_VERIFIER_OPTIONS +{ + ULONG SizeStruct; + ULONG Option; + UCHAR OptionData[1]; +} RTL_PROCESS_VERIFIER_OPTIONS, *PRTL_PROCESS_VERIFIER_OPTIONS; + +// private +typedef struct _RTL_DEBUG_INFORMATION +{ + HANDLE SectionHandleClient; + PVOID ViewBaseClient; + PVOID ViewBaseTarget; + ULONG_PTR ViewBaseDelta; + HANDLE EventPairClient; + HANDLE EventPairTarget; + HANDLE TargetProcessId; + HANDLE TargetThreadHandle; + ULONG Flags; + SIZE_T OffsetFree; + SIZE_T CommitSize; + SIZE_T ViewSize; + union + { + struct _RTL_PROCESS_MODULES *Modules; + struct _RTL_PROCESS_MODULE_INFORMATION_EX *ModulesEx; + }; + struct _RTL_PROCESS_BACKTRACES *BackTraces; + struct _RTL_PROCESS_HEAPS *Heaps; + struct _RTL_PROCESS_LOCKS *Locks; + PVOID SpecificHeap; + HANDLE TargetProcessHandle; + PRTL_PROCESS_VERIFIER_OPTIONS VerifierOptions; + PVOID ProcessHeap; + HANDLE CriticalSectionHandle; + HANDLE CriticalSectionOwnerThread; + PVOID Reserved[4]; +} RTL_DEBUG_INFORMATION, *PRTL_DEBUG_INFORMATION; + +NTSYSAPI +PRTL_DEBUG_INFORMATION +NTAPI +RtlCreateQueryDebugBuffer( + _In_opt_ ULONG MaximumCommit, + _In_ BOOLEAN UseEventPair + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyQueryDebugBuffer( + _In_ PRTL_DEBUG_INFORMATION Buffer + ); + +#if (PHNT_VERSION >= PHNT_VISTA) + +// private +NTSYSAPI +PVOID +NTAPI +RtlCommitDebugInfo( + _Inout_ PRTL_DEBUG_INFORMATION Buffer, + _In_ SIZE_T Size + ); + +// private +NTSYSAPI +VOID +NTAPI +RtlDeCommitDebugInfo( + _Inout_ PRTL_DEBUG_INFORMATION Buffer, + _In_ PVOID p, + _In_ SIZE_T Size + ); + +#endif + +#define RTL_QUERY_PROCESS_MODULES 0x00000001 +#define RTL_QUERY_PROCESS_BACKTRACES 0x00000002 +#define RTL_QUERY_PROCESS_HEAP_SUMMARY 0x00000004 +#define RTL_QUERY_PROCESS_HEAP_TAGS 0x00000008 +#define RTL_QUERY_PROCESS_HEAP_ENTRIES 0x00000010 +#define RTL_QUERY_PROCESS_LOCKS 0x00000020 +#define RTL_QUERY_PROCESS_MODULES32 0x00000040 +#define RTL_QUERY_PROCESS_VERIFIER_OPTIONS 0x00000080 // rev +#define RTL_QUERY_PROCESS_MODULESEX 0x00000100 // rev +#define RTL_QUERY_PROCESS_HEAP_ENTRIES_EX 0x00000200 // ? +#define RTL_QUERY_PROCESS_CS_OWNER 0x00000400 // rev +#define RTL_QUERY_PROCESS_NONINVASIVE 0x80000000 + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryProcessDebugInformation( + _In_ HANDLE UniqueProcessId, + _In_ ULONG Flags, + _Inout_ PRTL_DEBUG_INFORMATION Buffer + ); + +// Messages + +NTSYSAPI +NTSTATUS +NTAPI +RtlFindMessage( + _In_ PVOID DllHandle, + _In_ ULONG MessageTableId, + _In_ ULONG MessageLanguageId, + _In_ ULONG MessageId, + _Out_ PMESSAGE_RESOURCE_ENTRY *MessageEntry + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlFormatMessage( + _In_ PWSTR MessageFormat, + _In_ ULONG MaximumWidth, + _In_ BOOLEAN IgnoreInserts, + _In_ BOOLEAN ArgumentsAreAnsi, + _In_ BOOLEAN ArgumentsAreAnArray, + _In_ va_list *Arguments, + _Out_writes_bytes_to_(Length, *ReturnLength) PWSTR Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +typedef struct _PARSE_MESSAGE_CONTEXT +{ + ULONG fFlags; + ULONG cwSavColumn; + SIZE_T iwSrc; + SIZE_T iwDst; + SIZE_T iwDstSpace; + va_list lpvArgStart; +} PARSE_MESSAGE_CONTEXT, *PPARSE_MESSAGE_CONTEXT; + +#define INIT_PARSE_MESSAGE_CONTEXT(ctx) \ + { \ + (ctx)->fFlags = 0; \ + } + +#define TEST_PARSE_MESSAGE_CONTEXT_FLAG(ctx, flag) ((ctx)->fFlags & (flag)) +#define SET_PARSE_MESSAGE_CONTEXT_FLAG(ctx, flag) ((ctx)->fFlags |= (flag)) +#define CLEAR_PARSE_MESSAGE_CONTEXT_FLAG(ctx, flag) ((ctx)->fFlags &= ~(flag)) + +NTSYSAPI +NTSTATUS +NTAPI +RtlFormatMessageEx( + _In_ PWSTR MessageFormat, + _In_ ULONG MaximumWidth, + _In_ BOOLEAN IgnoreInserts, + _In_ BOOLEAN ArgumentsAreAnsi, + _In_ BOOLEAN ArgumentsAreAnArray, + _In_ va_list *Arguments, + _Out_writes_bytes_to_(Length, *ReturnLength) PWSTR Buffer, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength, + _Out_opt_ PPARSE_MESSAGE_CONTEXT ParseContext + ); + +// Errors + +NTSYSAPI +ULONG +NTAPI +RtlNtStatusToDosError( + _In_ NTSTATUS Status + ); + +NTSYSAPI +ULONG +NTAPI +RtlNtStatusToDosErrorNoTeb( + _In_ NTSTATUS Status + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetLastNtStatus( + VOID + ); + +NTSYSAPI +LONG +NTAPI +RtlGetLastWin32Error( + VOID + ); + +NTSYSAPI +VOID +NTAPI +RtlSetLastWin32ErrorAndNtStatusFromNtStatus( + _In_ NTSTATUS Status + ); + +NTSYSAPI +VOID +NTAPI +RtlSetLastWin32Error( + _In_ LONG Win32Error + ); + +NTSYSAPI +VOID +NTAPI +RtlRestoreLastWin32Error( + _In_ LONG Win32Error + ); + +#define RTL_ERRORMODE_NOGPFAULTERRORBOX 0x0020 +#define RTL_ERRORMODE_NOOPENFILEERRORBOX 0x0040 + +NTSYSAPI +ULONG +NTAPI +RtlGetThreadErrorMode( + VOID + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetThreadErrorMode( + _In_ ULONG NewMode, + _Out_opt_ PULONG OldMode + ); + +// Windows Error Reporting + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlReportException( + _In_ PEXCEPTION_RECORD ExceptionRecord, + _In_ PCONTEXT ContextRecord, + _In_ ULONG Flags + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlWerpReportException( + _In_ ULONG ProcessId, + _In_ HANDLE CrashReportSharedMem, + _In_ ULONG Flags, + _Out_ PHANDLE CrashVerticalProcessHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlReportSilentProcessExit( + _In_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); +#endif + +// Vectored Exception Handlers + +NTSYSAPI +PVOID +NTAPI +RtlAddVectoredExceptionHandler( + _In_ ULONG First, + _In_ PVECTORED_EXCEPTION_HANDLER Handler + ); + +NTSYSAPI +ULONG +NTAPI +RtlRemoveVectoredExceptionHandler( + _In_ PVOID Handle + ); + +NTSYSAPI +PVOID +NTAPI +RtlAddVectoredContinueHandler( + _In_ ULONG First, + _In_ PVECTORED_EXCEPTION_HANDLER Handler + ); + +NTSYSAPI +ULONG +NTAPI +RtlRemoveVectoredContinueHandler( + _In_ PVOID Handle + ); + +// Random + +NTSYSAPI +ULONG +NTAPI +RtlUniform( + _Inout_ PULONG Seed + ); + +NTSYSAPI +ULONG +NTAPI +RtlRandom( + _Inout_ PULONG Seed + ); + +NTSYSAPI +ULONG +NTAPI +RtlRandomEx( + _Inout_ PULONG Seed + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlComputeImportTableHash( + _In_ HANDLE hFile, + _Out_writes_bytes_(16) PCHAR Hash, + _In_ ULONG ImportTableHashRevision // must be 1 + ); + +// Integer conversion + +NTSYSAPI +NTSTATUS +NTAPI +RtlIntegerToChar( + _In_ ULONG Value, + _In_opt_ ULONG Base, + _In_ LONG OutputLength, // negative to pad to width + _Out_ PSTR String + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCharToInteger( + _In_ PSTR String, + _In_opt_ ULONG Base, + _Out_ PULONG Value + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLargeIntegerToChar( + _In_ PLARGE_INTEGER Value, + _In_opt_ ULONG Base, + _In_ LONG OutputLength, + _Out_ PSTR String + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIntegerToUnicodeString( + _In_ ULONG Value, + _In_opt_ ULONG Base, + _Inout_ PUNICODE_STRING String + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlInt64ToUnicodeString( + _In_ ULONGLONG Value, + _In_opt_ ULONG Base, + _Inout_ PUNICODE_STRING String + ); + +#ifdef _WIN64 +#define RtlIntPtrToUnicodeString(Value, Base, String) RtlInt64ToUnicodeString(Value, Base, String) +#else +#define RtlIntPtrToUnicodeString(Value, Base, String) RtlIntegerToUnicodeString(Value, Base, String) +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlUnicodeStringToInteger( + _In_ PUNICODE_STRING String, + _In_opt_ ULONG Base, + _Out_ PULONG Value + ); + +// IPv4/6 conversion + +struct in_addr; +struct in6_addr; + +NTSYSAPI +PWSTR +NTAPI +RtlIpv4AddressToStringW( + _In_ struct in_addr *Addr, + _Out_writes_(16) PWSTR S + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv4AddressToStringExW( + _In_ struct in_addr *Address, + _In_ USHORT Port, + _Out_writes_to_(*AddressStringLength, *AddressStringLength) PWSTR AddressString, + _Inout_ PULONG AddressStringLength + ); + +NTSYSAPI +PWSTR +NTAPI +RtlIpv6AddressToStringW( + _In_ struct in6_addr *Addr, + _Out_writes_(65) PWSTR S + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv6AddressToStringExW( + _In_ struct in6_addr *Address, + _In_ ULONG ScopeId, + _In_ USHORT Port, + _Out_writes_to_(*AddressStringLength, *AddressStringLength) PWSTR AddressString, + _Inout_ PULONG AddressStringLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv4StringToAddressW( + _In_ PWSTR S, + _In_ BOOLEAN Strict, + _Out_ PWSTR *Terminator, + _Out_ struct in_addr *Addr + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv4StringToAddressExW( + _In_ PWSTR AddressString, + _In_ BOOLEAN Strict, + _Out_ struct in_addr *Address, + _Out_ PUSHORT Port + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv6StringToAddressW( + _In_ PWSTR S, + _Out_ PWSTR *Terminator, + _Out_ struct in6_addr *Addr + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlIpv6StringToAddressExW( + _In_ PWSTR AddressString, + _Out_ struct in6_addr *Address, + _Out_ PULONG ScopeId, + _Out_ PUSHORT Port + ); + +#define RtlIpv4AddressToString RtlIpv4AddressToStringW +#define RtlIpv4AddressToStringEx RtlIpv4AddressToStringExW +#define RtlIpv6AddressToString RtlIpv6AddressToStringW +#define RtlIpv6AddressToStringEx RtlIpv6AddressToStringExW +#define RtlIpv4StringToAddress RtlIpv4StringToAddressW +#define RtlIpv4StringToAddressEx RtlIpv4StringToAddressExW +#define RtlIpv6StringToAddress RtlIpv6StringToAddressW +#define RtlIpv6StringToAddressEx RtlIpv6StringToAddressExW + +// Time + +typedef struct _TIME_FIELDS +{ + CSHORT Year; // 1601... + CSHORT Month; // 1..12 + CSHORT Day; // 1..31 + CSHORT Hour; // 0..23 + CSHORT Minute; // 0..59 + CSHORT Second; // 0..59 + CSHORT Milliseconds; // 0..999 + CSHORT Weekday; // 0..6 = Sunday..Saturday +} TIME_FIELDS, *PTIME_FIELDS; + +NTSYSAPI +BOOLEAN +NTAPI +RtlCutoverTimeToSystemTime( + _In_ PTIME_FIELDS CutoverTime, + _Out_ PLARGE_INTEGER SystemTime, + _In_ PLARGE_INTEGER CurrentSystemTime, + _In_ BOOLEAN ThisYear + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSystemTimeToLocalTime( + _In_ PLARGE_INTEGER SystemTime, + _Out_ PLARGE_INTEGER LocalTime + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLocalTimeToSystemTime( + _In_ PLARGE_INTEGER LocalTime, + _Out_ PLARGE_INTEGER SystemTime + ); + +NTSYSAPI +VOID +NTAPI +RtlTimeToElapsedTimeFields( + _In_ PLARGE_INTEGER Time, + _Out_ PTIME_FIELDS TimeFields + ); + +NTSYSAPI +VOID +NTAPI +RtlTimeToTimeFields( + _In_ PLARGE_INTEGER Time, + _Out_ PTIME_FIELDS TimeFields + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlTimeFieldsToTime( + _In_ PTIME_FIELDS TimeFields, // Weekday is ignored + _Out_ PLARGE_INTEGER Time + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlTimeToSecondsSince1980( + _In_ PLARGE_INTEGER Time, + _Out_ PULONG ElapsedSeconds + ); + +NTSYSAPI +VOID +NTAPI +RtlSecondsSince1980ToTime( + _In_ ULONG ElapsedSeconds, + _Out_ PLARGE_INTEGER Time + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlTimeToSecondsSince1970( + _In_ PLARGE_INTEGER Time, + _Out_ PULONG ElapsedSeconds + ); + +NTSYSAPI +VOID +NTAPI +RtlSecondsSince1970ToTime( + _In_ ULONG ElapsedSeconds, + _Out_ PLARGE_INTEGER Time + ); + +// Time zones + +typedef struct _RTL_TIME_ZONE_INFORMATION +{ + LONG Bias; + WCHAR StandardName[32]; + TIME_FIELDS StandardStart; + LONG StandardBias; + WCHAR DaylightName[32]; + TIME_FIELDS DaylightStart; + LONG DaylightBias; +} RTL_TIME_ZONE_INFORMATION, *PRTL_TIME_ZONE_INFORMATION; + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryTimeZoneInformation( + _Out_ PRTL_TIME_ZONE_INFORMATION TimeZoneInformation + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetTimeZoneInformation( + _In_ PRTL_TIME_ZONE_INFORMATION TimeZoneInformation + ); + +// Bitmaps + +typedef struct _RTL_BITMAP +{ + ULONG SizeOfBitMap; + PULONG Buffer; +} RTL_BITMAP, *PRTL_BITMAP; + +NTSYSAPI +VOID +NTAPI +RtlInitializeBitMap( + _Out_ PRTL_BITMAP BitMapHeader, + _In_ PULONG BitMapBuffer, + _In_ ULONG SizeOfBitMap + ); + +#if (PHNT_MODE == PHNT_MODE_KERNEL || PHNT_VERSION >= PHNT_WIN8) +NTSYSAPI +VOID +NTAPI +RtlClearBit( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(<, BitMapHeader->SizeOfBitMap) ULONG BitNumber + ); +#endif + +#if (PHNT_MODE == PHNT_MODE_KERNEL || PHNT_VERSION >= PHNT_WIN8) +NTSYSAPI +VOID +NTAPI +RtlSetBit( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(<, BitMapHeader->SizeOfBitMap) ULONG BitNumber + ); +#endif + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlTestBit( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(<, BitMapHeader->SizeOfBitMap) ULONG BitNumber + ); + +NTSYSAPI +VOID +NTAPI +RtlClearAllBits( + _In_ PRTL_BITMAP BitMapHeader + ); + +NTSYSAPI +VOID +NTAPI +RtlSetAllBits( + _In_ PRTL_BITMAP BitMapHeader + ); + +_Success_(return != -1) +_Check_return_ +NTSYSAPI +ULONG +NTAPI +RtlFindClearBits( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG NumberToFind, + _In_ ULONG HintIndex + ); + +_Success_(return != -1) +_Check_return_ +NTSYSAPI +ULONG +NTAPI +RtlFindSetBits( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG NumberToFind, + _In_ ULONG HintIndex + ); + +_Success_(return != -1) +NTSYSAPI +ULONG +NTAPI +RtlFindClearBitsAndSet( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG NumberToFind, + _In_ ULONG HintIndex + ); + +_Success_(return != -1) +NTSYSAPI +ULONG +NTAPI +RtlFindSetBitsAndClear( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG NumberToFind, + _In_ ULONG HintIndex + ); + +NTSYSAPI +VOID +NTAPI +RtlClearBits( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(0, BitMapHeader->SizeOfBitMap - NumberToClear) ULONG StartingIndex, + _In_range_(0, BitMapHeader->SizeOfBitMap - StartingIndex) ULONG NumberToClear + ); + +NTSYSAPI +VOID +NTAPI +RtlSetBits( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(0, BitMapHeader->SizeOfBitMap - NumberToSet) ULONG StartingIndex, + _In_range_(0, BitMapHeader->SizeOfBitMap - StartingIndex) ULONG NumberToSet + ); + +typedef struct _RTL_BITMAP_RUN +{ + ULONG StartingIndex; + ULONG NumberOfBits; +} RTL_BITMAP_RUN, *PRTL_BITMAP_RUN; + +NTSYSAPI +ULONG +NTAPI +RtlFindClearRuns( + _In_ PRTL_BITMAP BitMapHeader, + _Out_writes_to_(SizeOfRunArray, return) PRTL_BITMAP_RUN RunArray, + _In_range_(>, 0) ULONG SizeOfRunArray, + _In_ BOOLEAN LocateLongestRuns + ); + +NTSYSAPI +ULONG +NTAPI +RtlFindLongestRunClear( + _In_ PRTL_BITMAP BitMapHeader, + _Out_ PULONG StartingIndex + ); + +NTSYSAPI +ULONG +NTAPI +RtlFindFirstRunClear( + _In_ PRTL_BITMAP BitMapHeader, + _Out_ PULONG StartingIndex + ); + +_Check_return_ +FORCEINLINE +BOOLEAN +RtlCheckBit( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(<, BitMapHeader->SizeOfBitMap) ULONG BitPosition + ) +{ +#ifdef _WIN64 + return BitTest64((LONG64 const *)BitMapHeader->Buffer, (LONG64)BitPosition); +#else + return (((PLONG)BitMapHeader->Buffer)[BitPosition / 32] >> (BitPosition % 32)) & 0x1; +#endif +} + +NTSYSAPI +ULONG +NTAPI +RtlNumberOfClearBits( + _In_ PRTL_BITMAP BitMapHeader + ); + +NTSYSAPI +ULONG +NTAPI +RtlNumberOfSetBits( + _In_ PRTL_BITMAP BitMapHeader + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlAreBitsClear( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG StartingIndex, + _In_ ULONG Length + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlAreBitsSet( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG StartingIndex, + _In_ ULONG Length + ); + +NTSYSAPI +ULONG +NTAPI +RtlFindNextForwardRunClear( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG FromIndex, + _Out_ PULONG StartingRunIndex + ); + +NTSYSAPI +ULONG +NTAPI +RtlFindLastBackwardRunClear( + _In_ PRTL_BITMAP BitMapHeader, + _In_ ULONG FromIndex, + _Out_ PULONG StartingRunIndex + ); + +#if (PHNT_VERSION >= PHNT_WIN7) + +// rev +NTSYSAPI +VOID +NTAPI +RtlInterlockedClearBitRun( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(0, BitMapHeader->SizeOfBitMap - NumberToClear) ULONG StartingIndex, + _In_range_(0, BitMapHeader->SizeOfBitMap - StartingIndex) ULONG NumberToClear + ); + +// rev +NTSYSAPI +VOID +NTAPI +RtlInterlockedSetBitRun( + _In_ PRTL_BITMAP BitMapHeader, + _In_range_(0, BitMapHeader->SizeOfBitMap - NumberToSet) ULONG StartingIndex, + _In_range_(0, BitMapHeader->SizeOfBitMap - StartingIndex) ULONG NumberToSet + ); + +#endif + +// Handle tables + +typedef struct _RTL_HANDLE_TABLE_ENTRY +{ + union + { + ULONG Flags; // allocated entries have the low bit set + struct _RTL_HANDLE_TABLE_ENTRY *NextFree; + }; +} RTL_HANDLE_TABLE_ENTRY, *PRTL_HANDLE_TABLE_ENTRY; + +#define RTL_HANDLE_ALLOCATED (USHORT)0x0001 + +typedef struct _RTL_HANDLE_TABLE +{ + ULONG MaximumNumberOfHandles; + ULONG SizeOfHandleTableEntry; + ULONG Reserved[2]; + PRTL_HANDLE_TABLE_ENTRY FreeHandles; + PRTL_HANDLE_TABLE_ENTRY CommittedHandles; + PRTL_HANDLE_TABLE_ENTRY UnCommittedHandles; + PRTL_HANDLE_TABLE_ENTRY MaxReservedHandles; +} RTL_HANDLE_TABLE, *PRTL_HANDLE_TABLE; + +NTSYSAPI +VOID +NTAPI +RtlInitializeHandleTable( + _In_ ULONG MaximumNumberOfHandles, + _In_ ULONG SizeOfHandleTableEntry, + _Out_ PRTL_HANDLE_TABLE HandleTable + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyHandleTable( + _Inout_ PRTL_HANDLE_TABLE HandleTable + ); + +NTSYSAPI +PRTL_HANDLE_TABLE_ENTRY +NTAPI +RtlAllocateHandle( + _In_ PRTL_HANDLE_TABLE HandleTable, + _Out_opt_ PULONG HandleIndex + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlFreeHandle( + _In_ PRTL_HANDLE_TABLE HandleTable, + _In_ PRTL_HANDLE_TABLE_ENTRY Handle + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlIsValidHandle( + _In_ PRTL_HANDLE_TABLE HandleTable, + _In_ PRTL_HANDLE_TABLE_ENTRY Handle + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlIsValidIndexHandle( + _In_ PRTL_HANDLE_TABLE HandleTable, + _In_ ULONG HandleIndex, + _Out_ PRTL_HANDLE_TABLE_ENTRY *Handle + ); + +// Atom tables + +#define RTL_ATOM_MAXIMUM_INTEGER_ATOM (RTL_ATOM)0xc000 +#define RTL_ATOM_INVALID_ATOM (RTL_ATOM)0x0000 +#define RTL_ATOM_TABLE_DEFAULT_NUMBER_OF_BUCKETS 37 +#define RTL_ATOM_MAXIMUM_NAME_LENGTH 255 +#define RTL_ATOM_PINNED 0x01 + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateAtomTable( + _In_ ULONG NumberOfBuckets, + _Out_ PVOID *AtomTableHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDestroyAtomTable( + _In_ _Post_invalid_ PVOID AtomTableHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlEmptyAtomTable( + _In_ PVOID AtomTableHandle, + _In_ BOOLEAN IncludePinnedAtoms + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAtomToAtomTable( + _In_ PVOID AtomTableHandle, + _In_ PWSTR AtomName, + _Inout_opt_ PRTL_ATOM Atom + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlLookupAtomInAtomTable( + _In_ PVOID AtomTableHandle, + _In_ PWSTR AtomName, + _Out_opt_ PRTL_ATOM Atom + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteAtomFromAtomTable( + _In_ PVOID AtomTableHandle, + _In_ RTL_ATOM Atom + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlPinAtomInAtomTable( + _In_ PVOID AtomTableHandle, + _In_ RTL_ATOM Atom + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryAtomInAtomTable( + _In_ PVOID AtomTableHandle, + _In_ RTL_ATOM Atom, + _Out_opt_ PULONG AtomUsage, + _Out_opt_ PULONG AtomFlags, + _Inout_updates_bytes_to_opt_(*AtomNameLength, *AtomNameLength) PWSTR AtomName, + _Inout_opt_ PULONG AtomNameLength + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// rev +NTSYSAPI +BOOLEAN +NTAPI +RtlGetIntegerAtom( + _In_ PWSTR AtomName, + _Out_opt_ PUSHORT IntegerAtom + ); +#endif + +// SIDs + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlValidSid( + _In_ PSID Sid + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlEqualSid( + _In_ PSID Sid1, + _In_ PSID Sid2 + ); + +NTSYSAPI +ULONG +NTAPI +RtlLengthRequiredSid( + _In_ ULONG SubAuthorityCount + ); + +NTSYSAPI +PVOID +NTAPI +RtlFreeSid( + _In_ _Post_invalid_ PSID Sid + ); + +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +RtlAllocateAndInitializeSid( + _In_ PSID_IDENTIFIER_AUTHORITY IdentifierAuthority, + _In_ UCHAR SubAuthorityCount, + _In_ ULONG SubAuthority0, + _In_ ULONG SubAuthority1, + _In_ ULONG SubAuthority2, + _In_ ULONG SubAuthority3, + _In_ ULONG SubAuthority4, + _In_ ULONG SubAuthority5, + _In_ ULONG SubAuthority6, + _In_ ULONG SubAuthority7, + _Outptr_ PSID *Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlInitializeSid( + _Out_ PSID Sid, + _In_ PSID_IDENTIFIER_AUTHORITY IdentifierAuthority, + _In_ UCHAR SubAuthorityCount + ); + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSAPI +NTSTATUS +NTAPI +RtlInitializeSidEx( + _Out_writes_bytes_(SECURITY_SID_SIZE(SubAuthorityCount)) PSID Sid, + _In_ PSID_IDENTIFIER_AUTHORITY IdentifierAuthority, + _In_ UCHAR SubAuthorityCount, + ... + ); +#endif + +NTSYSAPI +PSID_IDENTIFIER_AUTHORITY +NTAPI +RtlIdentifierAuthoritySid( + _In_ PSID Sid + ); + +NTSYSAPI +PULONG +NTAPI +RtlSubAuthoritySid( + _In_ PSID Sid, + _In_ ULONG SubAuthority + ); + +NTSYSAPI +PUCHAR +NTAPI +RtlSubAuthorityCountSid( + _In_ PSID Sid + ); + +NTSYSAPI +ULONG +NTAPI +RtlLengthSid( + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCopySid( + _In_ ULONG DestinationSidLength, + _In_reads_bytes_(DestinationSidLength) PSID DestinationSid, + _In_ PSID SourceSid + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateServiceSid( + _In_ PUNICODE_STRING ServiceName, + _Out_writes_bytes_opt_(*ServiceSidLength) PSID ServiceSid, + _Inout_ PULONG ServiceSidLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSidDominates( + _In_ PSID Sid1, + _In_ PSID Sid2, + _Out_ PBOOLEAN pbDominate + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSidEqualLevel( + _In_ PSID Sid1, + _In_ PSID Sid2, + _Out_ PBOOLEAN pbEqual + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSidIsHigherLevel( + _In_ PSID Sid1, + _In_ PSID Sid2, + _Out_ PBOOLEAN pbHigher + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateVirtualAccountSid( + _In_ PUNICODE_STRING Name, + _In_ ULONG BaseSubAuthority, + _Out_writes_bytes_(*SidLength) PSID Sid, + _Inout_ PULONG SidLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +NTSYSAPI +NTSTATUS +NTAPI +RtlReplaceSidInSd( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ PSID OldSid, + _In_ PSID NewSid, + _Out_ ULONG *NumChanges + ); +#endif + +#define MAX_UNICODE_STACK_BUFFER_LENGTH 256 + +NTSYSAPI +NTSTATUS +NTAPI +RtlConvertSidToUnicodeString( + _Inout_ PUNICODE_STRING UnicodeString, + _In_ PSID Sid, + _In_ BOOLEAN AllocateDestinationString + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSidHashInitialize( + _In_reads_(SidCount) PSID_AND_ATTRIBUTES SidAttr, + _In_ ULONG SidCount, + _Out_ PSID_AND_ATTRIBUTES_HASH SidAttrHash + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +PSID_AND_ATTRIBUTES +NTAPI +RtlSidHashLookup( + _In_ PSID_AND_ATTRIBUTES_HASH SidAttrHash, + _In_ PSID Sid + ); +#endif + +// Security Descriptors + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateSecurityDescriptor( + _Out_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ULONG Revision + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlValidSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSYSAPI +ULONG +NTAPI +RtlLengthSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +_Check_return_ +NTSYSAPI +BOOLEAN +NTAPI +RtlValidRelativeSecurityDescriptor( + _In_reads_bytes_(SecurityDescriptorLength) PSECURITY_DESCRIPTOR SecurityDescriptorInput, + _In_ ULONG SecurityDescriptorLength, + _In_ SECURITY_INFORMATION RequiredInformation + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetControlSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PSECURITY_DESCRIPTOR_CONTROL Control, + _Out_ PULONG Revision + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetControlSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest, + _In_ SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetAttributesSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ SECURITY_DESCRIPTOR_CONTROL Control, + _Out_ PULONG Revision + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlGetSecurityDescriptorRMControl( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PUCHAR RMControl + ); + +NTSYSAPI +VOID +NTAPI +RtlSetSecurityDescriptorRMControl( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PUCHAR RMControl + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetDaclSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ BOOLEAN DaclPresent, + _In_opt_ PACL Dacl, + _In_opt_ BOOLEAN DaclDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetDaclSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PBOOLEAN DaclPresent, + _Out_ PACL *Dacl, + _Out_ PBOOLEAN DaclDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetSaclSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ BOOLEAN SaclPresent, + _In_opt_ PACL Sacl, + _In_opt_ BOOLEAN SaclDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetSaclSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PBOOLEAN SaclPresent, + _Out_ PACL *Sacl, + _Out_ PBOOLEAN SaclDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetSaclSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PBOOLEAN SaclPresent, + _Out_ PACL *Sacl, + _Out_ PBOOLEAN SaclDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetOwnerSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID Owner, + _In_opt_ BOOLEAN OwnerDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetOwnerSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PSID *Owner, + _Out_ PBOOLEAN OwnerDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetGroupSecurityDescriptor( + _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID Group, + _In_opt_ BOOLEAN GroupDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetGroupSecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _Out_ PSID *Group, + _Out_ PBOOLEAN GroupDefaulted + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlMakeSelfRelativeSD( + _In_ PSECURITY_DESCRIPTOR AbsoluteSecurityDescriptor, + _Out_writes_bytes_(*BufferLength) PSECURITY_DESCRIPTOR SelfRelativeSecurityDescriptor, + _Inout_ PULONG BufferLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAbsoluteToSelfRelativeSD( + _In_ PSECURITY_DESCRIPTOR AbsoluteSecurityDescriptor, + _Out_writes_bytes_to_opt_(*BufferLength, *BufferLength) PSECURITY_DESCRIPTOR SelfRelativeSecurityDescriptor, + _Inout_ PULONG BufferLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSelfRelativeToAbsoluteSD( + _In_ PSECURITY_DESCRIPTOR SelfRelativeSecurityDescriptor, + _Out_writes_bytes_to_opt_(*AbsoluteSecurityDescriptorSize, *AbsoluteSecurityDescriptorSize) PSECURITY_DESCRIPTOR AbsoluteSecurityDescriptor, + _Inout_ PULONG AbsoluteSecurityDescriptorSize, + _Out_writes_bytes_to_opt_(*DaclSize, *DaclSize) PACL Dacl, + _Inout_ PULONG DaclSize, + _Out_writes_bytes_to_opt_(*SaclSize, *SaclSize) PACL Sacl, + _Inout_ PULONG SaclSize, + _Out_writes_bytes_to_opt_(*OwnerSize, *OwnerSize) PSID Owner, + _Inout_ PULONG OwnerSize, + _Out_writes_bytes_to_opt_(*PrimaryGroupSize, *PrimaryGroupSize) PSID PrimaryGroup, + _Inout_ PULONG PrimaryGroupSize + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlSelfRelativeToAbsoluteSD2( + _Inout_ PSECURITY_DESCRIPTOR pSelfRelativeSecurityDescriptor, + _Inout_ PULONG pBufferSize + ); + +// Access masks + +NTSYSAPI +BOOLEAN +NTAPI +RtlAreAllAccessesGranted( + _In_ ACCESS_MASK GrantedAccess, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlAreAnyAccessesGranted( + _In_ ACCESS_MASK GrantedAccess, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSAPI +VOID +NTAPI +RtlMapGenericMask( + _Inout_ PACCESS_MASK AccessMask, + _In_ PGENERIC_MAPPING GenericMapping + ); + +// ACLs + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateAcl( + _Out_writes_bytes_(AclLength) PACL Acl, + _In_ ULONG AclLength, + _In_ ULONG AclRevision + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlValidAcl( + _In_ PACL Acl + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryInformationAcl( + _In_ PACL Acl, + _Out_writes_bytes_(AclInformationLength) PVOID AclInformation, + _In_ ULONG AclInformationLength, + _In_ ACL_INFORMATION_CLASS AclInformationClass + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetInformationAcl( + _Inout_ PACL Acl, + _In_reads_bytes_(AclInformationLength) PVOID AclInformation, + _In_ ULONG AclInformationLength, + _In_ ACL_INFORMATION_CLASS AclInformationClass + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG StartingAceIndex, + _In_reads_bytes_(AceListLength) PVOID AceList, + _In_ ULONG AceListLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteAce( + _Inout_ PACL Acl, + _In_ ULONG AceIndex + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetAce( + _In_ PACL Acl, + _In_ ULONG AceIndex, + _Outptr_ PVOID *Ace + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlFirstFreeAce( + _In_ PACL Acl, + _Out_ PVOID *FirstFree + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +PVOID +NTAPI +RtlFindAceByType( + _In_ PACL pAcl, + _In_ UCHAR AceType, + _Out_opt_ PULONG pIndex + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +BOOLEAN +NTAPI +RtlOwnerAcesPresent( + _In_ PACL pAcl + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessAllowedAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessAllowedAceEx( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessDeniedAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessDeniedAceEx( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAuditAccessAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid, + _In_ BOOLEAN AuditSuccess, + _In_ BOOLEAN AuditFailure + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAuditAccessAceEx( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_ PSID Sid, + _In_ BOOLEAN AuditSuccess, + _In_ BOOLEAN AuditFailure + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessAllowedObjectAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_opt_ PGUID ObjectTypeGuid, + _In_opt_ PGUID InheritedObjectTypeGuid, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAccessDeniedObjectAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_opt_ PGUID ObjectTypeGuid, + _In_opt_ PGUID InheritedObjectTypeGuid, + _In_ PSID Sid + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddAuditAccessObjectAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ ACCESS_MASK AccessMask, + _In_opt_ PGUID ObjectTypeGuid, + _In_opt_ PGUID InheritedObjectTypeGuid, + _In_ PSID Sid, + _In_ BOOLEAN AuditSuccess, + _In_ BOOLEAN AuditFailure + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddCompoundAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ UCHAR AceType, + _In_ ACCESS_MASK AccessMask, + _In_ PSID ServerSid, + _In_ PSID ClientSid + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlAddMandatoryAce( + _Inout_ PACL Acl, + _In_ ULONG AceRevision, + _In_ ULONG AceFlags, + _In_ PSID Sid, + _In_ UCHAR AceType, + _In_ ACCESS_MASK AccessMask + ); +#endif + +// Security objects + +NTSYSAPI +NTSTATUS +NTAPI +RtlNewSecurityObject( + _In_opt_ PSECURITY_DESCRIPTOR ParentDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR CreatorDescriptor, + _Out_ PSECURITY_DESCRIPTOR *NewDescriptor, + _In_ BOOLEAN IsDirectoryObject, + _In_opt_ HANDLE Token, + _In_ PGENERIC_MAPPING GenericMapping + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlNewSecurityObjectEx( + _In_opt_ PSECURITY_DESCRIPTOR ParentDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR CreatorDescriptor, + _Out_ PSECURITY_DESCRIPTOR *NewDescriptor, + _In_opt_ GUID *ObjectType, + _In_ BOOLEAN IsDirectoryObject, + _In_ ULONG AutoInheritFlags, // SEF_* + _In_opt_ HANDLE Token, + _In_ PGENERIC_MAPPING GenericMapping + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlNewSecurityObjectWithMultipleInheritance( + _In_opt_ PSECURITY_DESCRIPTOR ParentDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR CreatorDescriptor, + _Out_ PSECURITY_DESCRIPTOR *NewDescriptor, + _In_opt_ GUID **ObjectType, + _In_ ULONG GuidCount, + _In_ BOOLEAN IsDirectoryObject, + _In_ ULONG AutoInheritFlags, // SEF_* + _In_opt_ HANDLE Token, + _In_ PGENERIC_MAPPING GenericMapping + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteSecurityObject( + _Inout_ PSECURITY_DESCRIPTOR *ObjectDescriptor + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQuerySecurityObject( + _In_ PSECURITY_DESCRIPTOR ObjectDescriptor, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_opt_ PSECURITY_DESCRIPTOR ResultantDescriptor, + _In_ ULONG DescriptorLength, + _Out_ PULONG ReturnLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetSecurityObject( + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR ModificationDescriptor, + _Inout_ PSECURITY_DESCRIPTOR *ObjectsSecurityDescriptor, + _In_ PGENERIC_MAPPING GenericMapping, + _In_opt_ HANDLE Token + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetSecurityObjectEx( + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR ModificationDescriptor, + _Inout_ PSECURITY_DESCRIPTOR *ObjectsSecurityDescriptor, + _In_ ULONG AutoInheritFlags, // SEF_* + _In_ PGENERIC_MAPPING GenericMapping, + _In_opt_ HANDLE Token + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlConvertToAutoInheritSecurityObject( + _In_opt_ PSECURITY_DESCRIPTOR ParentDescriptor, + _In_ PSECURITY_DESCRIPTOR CurrentSecurityDescriptor, + _Out_ PSECURITY_DESCRIPTOR *NewSecurityDescriptor, + _In_opt_ GUID *ObjectType, + _In_ BOOLEAN IsDirectoryObject, + _In_ PGENERIC_MAPPING GenericMapping + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlNewInstanceSecurityObject( + _In_ BOOLEAN ParentDescriptorChanged, + _In_ BOOLEAN CreatorDescriptorChanged, + _In_ PLUID OldClientTokenModifiedId, + _Out_ PLUID NewClientTokenModifiedId, + _In_opt_ PSECURITY_DESCRIPTOR ParentDescriptor, + _In_opt_ PSECURITY_DESCRIPTOR CreatorDescriptor, + _Out_ PSECURITY_DESCRIPTOR *NewDescriptor, + _In_ BOOLEAN IsDirectoryObject, + _In_ HANDLE Token, + _In_ PGENERIC_MAPPING GenericMapping + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCopySecurityDescriptor( + _In_ PSECURITY_DESCRIPTOR InputSecurityDescriptor, + _Out_ PSECURITY_DESCRIPTOR *OutputSecurityDescriptor + ); + +// Misc. security + +NTSYSAPI +VOID +NTAPI +RtlRunEncodeUnicodeString( + _Inout_ PUCHAR Seed, + _In_ PUNICODE_STRING String + ); + +NTSYSAPI +VOID +NTAPI +RtlRunDecodeUnicodeString( + _In_ UCHAR Seed, + _In_ PUNICODE_STRING String + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlImpersonateSelf( + _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlImpersonateSelfEx( + _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, + _In_opt_ ACCESS_MASK AdditionalAccess, + _Out_opt_ PHANDLE ThreadToken + ); +#endif + +NTSYSAPI +NTSTATUS +NTAPI +RtlAdjustPrivilege( + _In_ ULONG Privilege, + _In_ BOOLEAN Enable, + _In_ BOOLEAN Client, + _Out_ PBOOLEAN WasEnabled + ); + +#define RTL_ACQUIRE_PRIVILEGE_REVERT 0x00000001 +#define RTL_ACQUIRE_PRIVILEGE_PROCESS 0x00000002 + +NTSYSAPI +NTSTATUS +NTAPI +RtlAcquirePrivilege( + _In_ PULONG Privilege, + _In_ ULONG NumPriv, + _In_ ULONG Flags, + _Out_ PVOID *ReturnedState + ); + +NTSYSAPI +VOID +NTAPI +RtlReleasePrivilege( + _In_ PVOID StatePointer + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlRemovePrivileges( + _In_ HANDLE hToken, + _In_ PULONG PrivilegesToKeep, + _In_ ULONG PrivilegeCount + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSAPI +NTSTATUS +NTAPI +RtlIsUntrustedObject( + _In_opt_ HANDLE Handle, + _In_opt_ PVOID Object, + _Out_ PBOOLEAN UntrustedObject + ); + +NTSYSAPI +ULONG +NTAPI +RtlQueryValidationRunlevel( + _In_opt_ PCUNICODE_STRING ComponentName + ); + +#endif + +// Private namespaces + +#if (PHNT_VERSION >= PHNT_VISTA) + +// begin_private + +NTSYSAPI +PVOID +NTAPI +RtlCreateBoundaryDescriptor( + _In_ PUNICODE_STRING Name, + _In_ ULONG Flags + ); + +NTSYSAPI +VOID +NTAPI +RtlDeleteBoundaryDescriptor( + _In_ PVOID BoundaryDescriptor + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlAddSIDToBoundaryDescriptor( + _Inout_ PVOID *BoundaryDescriptor, + _In_ PSID RequiredSid + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +RtlAddIntegrityLabelToBoundaryDescriptor( + _Inout_ PVOID *BoundaryDescriptor, + _In_ PSID IntegrityLabel + ); +#endif + +// end_private + +#endif + +// Version + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetVersion( + _Out_ PRTL_OSVERSIONINFOW lpVersionInformation + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlVerifyVersionInfo( + _In_ PRTL_OSVERSIONINFOEXW VersionInfo, + _In_ ULONG TypeMask, + _In_ ULONGLONG ConditionMask + ); + +// System information + +NTSYSAPI +ULONG +NTAPI +RtlGetNtGlobalFlags( + VOID + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlGetNtProductType( + _Out_ PNT_PRODUCT_TYPE NtProductType + ); + +// rev +NTSYSAPI +VOID +NTAPI +RtlGetNtVersionNumbers( + _Out_opt_ PULONG pNtMajorVersion, + _Out_opt_ PULONG pNtMinorVersion, + _Out_opt_ PULONG pNtBuildNumber + ); + +// Thread pool (old) + +NTSYSAPI +NTSTATUS +NTAPI +RtlRegisterWait( + _Out_ PHANDLE WaitHandle, + _In_ HANDLE Handle, + _In_ WAITORTIMERCALLBACKFUNC Function, + _In_ PVOID Context, + _In_ ULONG Milliseconds, + _In_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeregisterWait( + _In_ HANDLE WaitHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeregisterWaitEx( + _In_ HANDLE WaitHandle, + _In_ HANDLE Event + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueueWorkItem( + _In_ WORKERCALLBACKFUNC Function, + _In_ PVOID Context, + _In_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlSetIoCompletionCallback( + _In_ HANDLE FileHandle, + _In_ APC_CALLBACK_FUNCTION CompletionProc, + _In_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateTimerQueue( + _Out_ PHANDLE TimerQueueHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateTimer( + _In_ HANDLE TimerQueueHandle, + _Out_ PHANDLE Handle, + _In_ WAITORTIMERCALLBACKFUNC Function, + _In_ PVOID Context, + _In_ ULONG DueTime, + _In_ ULONG Period, + _In_ ULONG Flags + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlUpdateTimer( + _In_ HANDLE TimerQueueHandle, + _In_ HANDLE TimerHandle, + _In_ ULONG DueTime, + _In_ ULONG Period + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteTimer( + _In_ HANDLE TimerQueueHandle, + _In_ HANDLE TimerToCancel, + _In_ HANDLE Event + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteTimerQueue( + _In_ HANDLE TimerQueueHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteTimerQueueEx( + _In_ HANDLE TimerQueueHandle, + _In_ HANDLE Event + ); + +// Registry access + +NTSYSAPI +NTSTATUS +NTAPI +RtlFormatCurrentUserKeyPath( + _Out_ PUNICODE_STRING CurrentUserKeyPath + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlOpenCurrentUser( + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE CurrentUserKey + ); + +#define RTL_REGISTRY_ABSOLUTE 0 +#define RTL_REGISTRY_SERVICES 1 // \Registry\Machine\System\CurrentControlSet\Services +#define RTL_REGISTRY_CONTROL 2 // \Registry\Machine\System\CurrentControlSet\Control +#define RTL_REGISTRY_WINDOWS_NT 3 // \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion +#define RTL_REGISTRY_DEVICEMAP 4 // \Registry\Machine\Hardware\DeviceMap +#define RTL_REGISTRY_USER 5 // \Registry\User\CurrentUser +#define RTL_REGISTRY_MAXIMUM 6 +#define RTL_REGISTRY_HANDLE 0x40000000 +#define RTL_REGISTRY_OPTIONAL 0x80000000 + +NTSYSAPI +NTSTATUS +NTAPI +RtlCreateRegistryKey( + _In_ ULONG RelativeTo, + _In_ PWSTR Path + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlCheckRegistryKey( + _In_ ULONG RelativeTo, + _In_ PWSTR Path + ); + +typedef NTSTATUS (NTAPI *PRTL_QUERY_REGISTRY_ROUTINE)( + _In_ PWSTR ValueName, + _In_ ULONG ValueType, + _In_ PVOID ValueData, + _In_ ULONG ValueLength, + _In_ PVOID Context, + _In_ PVOID EntryContext + ); + +typedef struct _RTL_QUERY_REGISTRY_TABLE +{ + PRTL_QUERY_REGISTRY_ROUTINE QueryRoutine; + ULONG Flags; + PWSTR Name; + PVOID EntryContext; + ULONG DefaultType; + PVOID DefaultData; + ULONG DefaultLength; +} RTL_QUERY_REGISTRY_TABLE, *PRTL_QUERY_REGISTRY_TABLE; + +#define RTL_QUERY_REGISTRY_SUBKEY 0x00000001 +#define RTL_QUERY_REGISTRY_TOPKEY 0x00000002 +#define RTL_QUERY_REGISTRY_REQUIRED 0x00000004 +#define RTL_QUERY_REGISTRY_NOVALUE 0x00000008 +#define RTL_QUERY_REGISTRY_NOEXPAND 0x00000010 +#define RTL_QUERY_REGISTRY_DIRECT 0x00000020 +#define RTL_QUERY_REGISTRY_DELETE 0x00000040 + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryRegistryValues( + _In_ ULONG RelativeTo, + _In_ PWSTR Path, + _In_ PRTL_QUERY_REGISTRY_TABLE QueryTable, + _In_ PVOID Context, + _In_opt_ PVOID Environment + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlWriteRegistryValue( + _In_ ULONG RelativeTo, + _In_ PWSTR Path, + _In_ PWSTR ValueName, + _In_ ULONG ValueType, + _In_ PVOID ValueData, + _In_ ULONG ValueLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDeleteRegistryValue( + _In_ ULONG RelativeTo, + _In_ PWSTR Path, + _In_ PWSTR ValueName + ); + +// Debugging + +NTSYSAPI +VOID +NTAPI +DbgUserBreakPoint( + VOID + ); + +NTSYSAPI +VOID +NTAPI +DbgBreakPoint( + VOID + ); + +NTSYSAPI +VOID +NTAPI +DbgBreakPointWithStatus( + _In_ ULONG Status + ); + +#define DBG_STATUS_CONTROL_C 1 +#define DBG_STATUS_SYSRQ 2 +#define DBG_STATUS_BUGCHECK_FIRST 3 +#define DBG_STATUS_BUGCHECK_SECOND 4 +#define DBG_STATUS_FATAL 5 +#define DBG_STATUS_DEBUG_CONTROL 6 +#define DBG_STATUS_WORKER 7 + +NTSYSAPI +ULONG +__cdecl +DbgPrint( + _In_z_ _Printf_format_string_ PSTR Format, + ... + ); + +NTSYSAPI +ULONG +__cdecl +DbgPrintEx( + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_z_ _Printf_format_string_ PSTR Format, + ... + ); + +NTSYSAPI +ULONG +NTAPI +vDbgPrintEx( + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_z_ PCH Format, + _In_ va_list arglist + ); + +NTSYSAPI +ULONG +NTAPI +vDbgPrintExWithPrefix( + _In_z_ PCH Prefix, + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_z_ PCH Format, + _In_ va_list arglist + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgQueryDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level + ); + +NTSYSAPI +NTSTATUS +NTAPI +DbgSetDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_ BOOLEAN State + ); + +NTSYSAPI +ULONG +NTAPI +DbgPrompt( + _In_ PCH Prompt, + _Out_writes_bytes_(Length) PCH Response, + _In_ ULONG Length + ); + +// Thread profiling + +#if (PHNT_VERSION >= PHNT_WIN7) + +// begin_rev + +NTSYSAPI +NTSTATUS +NTAPI +RtlEnableThreadProfiling( + _In_ HANDLE ThreadHandle, + _In_ ULONG Flags, + _In_ ULONG64 HardwareCounters, + _Out_ PVOID *PerformanceDataHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlDisableThreadProfiling( + _In_ PVOID PerformanceDataHandle + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryThreadProfiling( + _In_ HANDLE ThreadHandle, + _Out_ PBOOLEAN Enabled + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlReadThreadProfilingData( + _In_ HANDLE PerformanceDataHandle, + _In_ ULONG Flags, + _Out_ PPERFORMANCE_DATA PerformanceData + ); + +// end_rev + +#endif + +// WOW64 + +NTSYSAPI +NTSTATUS +NTAPI +RtlGetNativeSystemInformation( + _In_ ULONG SystemInformationClass, + _In_ PVOID NativeSystemInformation, + _In_ ULONG InformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlQueueApcWow64Thread( + _In_ HANDLE ThreadHandle, + _In_ PPS_APC_ROUTINE ApcRoutine, + _In_ PVOID ApcArgument1, + _In_ PVOID ApcArgument2, + _In_ PVOID ApcArgument3 + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlWow64EnableFsRedirection( + _In_ BOOLEAN Wow64FsEnableRedirection + ); + +NTSYSAPI +NTSTATUS +NTAPI +RtlWow64EnableFsRedirectionEx( + _In_ PVOID Wow64FsEnableRedirection, + _Out_ PVOID *OldFsRedirectionLevel + ); + +// Misc. + +NTSYSAPI +ULONG +NTAPI +RtlGetCurrentProcessorNumber( + VOID + ); + +NTSYSAPI +ULONG32 +NTAPI +RtlComputeCrc32( + _In_ ULONG32 PartialCrc, + _In_ PVOID Buffer, + _In_ ULONG Length + ); + +NTSYSAPI +PVOID +NTAPI +RtlEncodePointer( + _In_ PVOID Ptr + ); + +NTSYSAPI +PVOID +NTAPI +RtlDecodePointer( + _In_ PVOID Ptr + ); + +NTSYSAPI +PVOID +NTAPI +RtlEncodeSystemPointer( + _In_ PVOID Ptr + ); + +NTSYSAPI +PVOID +NTAPI +RtlDecodeSystemPointer( + _In_ PVOID Ptr + ); + +NTSYSAPI +BOOLEAN +NTAPI +RtlIsThreadWithinLoaderCallout( + VOID + ); + +NTSYSAPI +VOID +NTAPI +RtlPushFrame( + _In_ PTEB_ACTIVE_FRAME Frame + ); + +NTSYSAPI +VOID +NTAPI +RtlPopFrame( + _In_ PTEB_ACTIVE_FRAME Frame + ); + +NTSYSAPI +PTEB_ACTIVE_FRAME +NTAPI +RtlGetFrame( + VOID + ); + +typedef ULONG (NTAPI *PRTLP_UNHANDLED_EXCEPTION_FILTER)( + struct _EXCEPTION_POINTERS *ExceptionInfo + ); + +NTSYSAPI +VOID +NTAPI +RtlSetUnhandledExceptionFilter( + _In_ PRTLP_UNHANDLED_EXCEPTION_FILTER UnhandledExceptionFilter + ); + +// begin_private + +typedef union _RTL_ELEVATION_FLAGS +{ + ULONG Flags; + struct + { + ULONG ElevationEnabled : 1; + ULONG VirtualizationEnabled : 1; + ULONG InstallerDetectEnabled : 1; + ULONG ReservedBits : 29; + }; +} RTL_ELEVATION_FLAGS, *PRTL_ELEVATION_FLAGS; + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +NTSTATUS +NTAPI +RtlQueryElevationFlags( + _Out_ PRTL_ELEVATION_FLAGS Flags + ); +#endif + +// end_private + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlRegisterThreadWithCsrss( + VOID + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlLockCurrentThread( + VOID + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlUnlockCurrentThread( + VOID + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlLockModuleSection( + _In_ PVOID Address + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSAPI +NTSTATUS +NTAPI +RtlUnlockModuleSection( + _In_ PVOID Address + ); +#endif + +// begin_msdn:"Winternl" + +#define RTL_UNLOAD_EVENT_TRACE_NUMBER 64 + +// private +typedef struct _RTL_UNLOAD_EVENT_TRACE +{ + PVOID BaseAddress; + SIZE_T SizeOfImage; + ULONG Sequence; + ULONG TimeDateStamp; + ULONG CheckSum; + WCHAR ImageName[32]; + ULONG Version[2]; +} RTL_UNLOAD_EVENT_TRACE, *PRTL_UNLOAD_EVENT_TRACE; + +NTSYSAPI +PRTL_UNLOAD_EVENT_TRACE +NTAPI +RtlGetUnloadEventTrace( + VOID + ); + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSAPI +VOID +NTAPI +RtlGetUnloadEventTraceEx( + _Out_ PULONG *ElementSize, + _Out_ PULONG *ElementCount, + _Out_ PVOID *EventTrace // works across all processes + ); +#endif + +// end_msdn + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +LOGICAL +NTAPI +RtlQueryPerformanceCounter( + _Out_ PLARGE_INTEGER PerformanceCounter + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +LOGICAL +NTAPI +RtlQueryPerformanceFrequency( + _Out_ PLARGE_INTEGER PerformanceFrequency + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN8) + +NTSYSAPI +ULONG +NTAPI +RtlCrc32( + _In_reads_bytes_(Size) const void *Buffer, + _In_ size_t Size, + _In_ ULONG InitialCrc + ); + +NTSYSAPI +ULONGLONG +NTAPI +RtlCrc64( + _In_reads_bytes_(Size) const void *Buffer, + _In_ size_t Size, + _In_ ULONGLONG InitialCrc + ); + +#endif + +#endif diff --git a/phnt/include/ntsam.h b/phnt/include/ntsam.h new file mode 100644 index 0000000..ab2544d --- /dev/null +++ b/phnt/include/ntsam.h @@ -0,0 +1,1735 @@ +#ifndef _NTSAM_H +#define _NTSAM_H + +#define SAM_MAXIMUM_LOOKUP_COUNT (1000) +#define SAM_MAXIMUM_LOOKUP_LENGTH (32000) +#define SAM_MAX_PASSWORD_LENGTH (256) +#define SAM_PASSWORD_ENCRYPTION_SALT_LEN (16) + +typedef PVOID SAM_HANDLE, *PSAM_HANDLE; +typedef ULONG SAM_ENUMERATE_HANDLE, *PSAM_ENUMERATE_HANDLE; + +typedef struct _SAM_RID_ENUMERATION +{ + ULONG RelativeId; + UNICODE_STRING Name; +} SAM_RID_ENUMERATION, *PSAM_RID_ENUMERATION; + +typedef struct _SAM_SID_ENUMERATION +{ + PSID Sid; + UNICODE_STRING Name; +} SAM_SID_ENUMERATION, *PSAM_SID_ENUMERATION; + +typedef struct _SAM_BYTE_ARRAY +{ + ULONG Size; + _Field_size_bytes_(Size) PUCHAR Data; +} SAM_BYTE_ARRAY, *PSAM_BYTE_ARRAY; + +typedef struct _SAM_BYTE_ARRAY_32K +{ + ULONG Size; + _Field_size_bytes_(Size) PUCHAR Data; +} SAM_BYTE_ARRAY_32K, *PSAM_BYTE_ARRAY_32K; + +typedef SAM_BYTE_ARRAY_32K SAM_SHELL_OBJECT_PROPERTIES, *PSAM_SHELL_OBJECT_PROPERTIES; + +// Basic + +NTSTATUS +NTAPI +SamFreeMemory( + _In_ PVOID Buffer + ); + +NTSTATUS +NTAPI +SamCloseHandle( + _In_ SAM_HANDLE SamHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetSecurityObject( + _In_ SAM_HANDLE ObjectHandle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQuerySecurityObject( + _In_ SAM_HANDLE ObjectHandle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Outptr_ PSECURITY_DESCRIPTOR *SecurityDescriptor + ); + +_Check_return_ +NTSTATUS +NTAPI +SamRidToSid( + _In_ SAM_HANDLE ObjectHandle, + _In_ ULONG Rid, + _Outptr_ PSID *Sid + ); + +// Server + +#define SAM_SERVER_CONNECT 0x0001 +#define SAM_SERVER_SHUTDOWN 0x0002 +#define SAM_SERVER_INITIALIZE 0x0004 +#define SAM_SERVER_CREATE_DOMAIN 0x0008 +#define SAM_SERVER_ENUMERATE_DOMAINS 0x0010 +#define SAM_SERVER_LOOKUP_DOMAIN 0x0020 + +#define SAM_SERVER_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \ + SAM_SERVER_CONNECT | \ + SAM_SERVER_INITIALIZE | \ + SAM_SERVER_CREATE_DOMAIN | \ + SAM_SERVER_SHUTDOWN | \ + SAM_SERVER_ENUMERATE_DOMAINS | \ + SAM_SERVER_LOOKUP_DOMAIN) + +#define SAM_SERVER_READ (STANDARD_RIGHTS_READ | \ + SAM_SERVER_ENUMERATE_DOMAINS) + +#define SAM_SERVER_WRITE (STANDARD_RIGHTS_WRITE | \ + SAM_SERVER_INITIALIZE | \ + SAM_SERVER_CREATE_DOMAIN | \ + SAM_SERVER_SHUTDOWN) + +#define SAM_SERVER_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ + SAM_SERVER_CONNECT | \ + SAM_SERVER_LOOKUP_DOMAIN) + +// Functions + +_Check_return_ +NTSTATUS +NTAPI +SamConnect( + _In_opt_ PUNICODE_STRING ServerName, + _Out_ PSAM_HANDLE ServerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +_Check_return_ +NTSTATUS +NTAPI +SamShutdownSamServer( + _In_ SAM_HANDLE ServerHandle + ); + +// Domain + +#define DOMAIN_READ_PASSWORD_PARAMETERS 0x0001 +#define DOMAIN_WRITE_PASSWORD_PARAMS 0x0002 +#define DOMAIN_READ_OTHER_PARAMETERS 0x0004 +#define DOMAIN_WRITE_OTHER_PARAMETERS 0x0008 +#define DOMAIN_CREATE_USER 0x0010 +#define DOMAIN_CREATE_GROUP 0x0020 +#define DOMAIN_CREATE_ALIAS 0x0040 +#define DOMAIN_GET_ALIAS_MEMBERSHIP 0x0080 +#define DOMAIN_LIST_ACCOUNTS 0x0100 +#define DOMAIN_LOOKUP 0x0200 +#define DOMAIN_ADMINISTER_SERVER 0x0400 + +#define DOMAIN_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \ + DOMAIN_READ_OTHER_PARAMETERS | \ + DOMAIN_WRITE_OTHER_PARAMETERS | \ + DOMAIN_WRITE_PASSWORD_PARAMS | \ + DOMAIN_CREATE_USER | \ + DOMAIN_CREATE_GROUP | \ + DOMAIN_CREATE_ALIAS | \ + DOMAIN_GET_ALIAS_MEMBERSHIP | \ + DOMAIN_LIST_ACCOUNTS | \ + DOMAIN_READ_PASSWORD_PARAMETERS | \ + DOMAIN_LOOKUP | \ + DOMAIN_ADMINISTER_SERVER) + +#define DOMAIN_READ (STANDARD_RIGHTS_READ | \ + DOMAIN_GET_ALIAS_MEMBERSHIP | \ + DOMAIN_READ_OTHER_PARAMETERS) + +#define DOMAIN_WRITE (STANDARD_RIGHTS_WRITE | \ + DOMAIN_WRITE_OTHER_PARAMETERS | \ + DOMAIN_WRITE_PASSWORD_PARAMS | \ + DOMAIN_CREATE_USER | \ + DOMAIN_CREATE_GROUP | \ + DOMAIN_CREATE_ALIAS | \ + DOMAIN_ADMINISTER_SERVER) + +#define DOMAIN_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ + DOMAIN_READ_PASSWORD_PARAMETERS | \ + DOMAIN_LIST_ACCOUNTS | \ + DOMAIN_LOOKUP) + +#define DOMAIN_PROMOTION_INCREMENT { 0x0, 0x10 } +#define DOMAIN_PROMOTION_MASK { 0x0, 0xfffffff0 } + +// SamQueryInformationDomain/SamSetInformationDomain types + +typedef enum _DOMAIN_INFORMATION_CLASS +{ + DomainPasswordInformation = 1, + DomainGeneralInformation, + DomainLogoffInformation, + DomainOemInformation, + DomainNameInformation, + DomainReplicationInformation, + DomainServerRoleInformation, + DomainModifiedInformation, + DomainStateInformation, + DomainUasInformation, + DomainGeneralInformation2, + DomainLockoutInformation, + DomainModifiedInformation2 +} DOMAIN_INFORMATION_CLASS; + +typedef enum _DOMAIN_SERVER_ENABLE_STATE +{ + DomainServerEnabled = 1, + DomainServerDisabled +} DOMAIN_SERVER_ENABLE_STATE, *PDOMAIN_SERVER_ENABLE_STATE; + +typedef enum _DOMAIN_SERVER_ROLE +{ + DomainServerRoleBackup = 2, + DomainServerRolePrimary +} DOMAIN_SERVER_ROLE, *PDOMAIN_SERVER_ROLE; + +#include +typedef struct _DOMAIN_GENERAL_INFORMATION +{ + LARGE_INTEGER ForceLogoff; + UNICODE_STRING OemInformation; + UNICODE_STRING DomainName; + UNICODE_STRING ReplicaSourceNodeName; + LARGE_INTEGER DomainModifiedCount; + DOMAIN_SERVER_ENABLE_STATE DomainServerState; + DOMAIN_SERVER_ROLE DomainServerRole; + BOOLEAN UasCompatibilityRequired; + ULONG UserCount; + ULONG GroupCount; + ULONG AliasCount; +} DOMAIN_GENERAL_INFORMATION, *PDOMAIN_GENERAL_INFORMATION; +#include + +#include +typedef struct _DOMAIN_GENERAL_INFORMATION2 +{ + DOMAIN_GENERAL_INFORMATION I1; + LARGE_INTEGER LockoutDuration; // delta time + LARGE_INTEGER LockoutObservationWindow; // delta time + USHORT LockoutThreshold; +} DOMAIN_GENERAL_INFORMATION2, *PDOMAIN_GENERAL_INFORMATION2; +#include + +typedef struct _DOMAIN_UAS_INFORMATION +{ + BOOLEAN UasCompatibilityRequired; +} DOMAIN_UAS_INFORMATION; + +#ifndef _DOMAIN_PASSWORD_INFORMATION_DEFINED // defined in ntsecapi.h +#define _DOMAIN_PASSWORD_INFORMATION_DEFINED + +typedef struct _DOMAIN_PASSWORD_INFORMATION +{ + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + ULONG PasswordProperties; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; +} DOMAIN_PASSWORD_INFORMATION, *PDOMAIN_PASSWORD_INFORMATION; + +// PasswordProperties flags + +#define DOMAIN_PASSWORD_COMPLEX 0x00000001L +#define DOMAIN_PASSWORD_NO_ANON_CHANGE 0x00000002L +#define DOMAIN_PASSWORD_NO_CLEAR_CHANGE 0x00000004L +#define DOMAIN_LOCKOUT_ADMINS 0x00000008L +#define DOMAIN_PASSWORD_STORE_CLEARTEXT 0x00000010L +#define DOMAIN_REFUSE_PASSWORD_CHANGE 0x00000020L +#define DOMAIN_NO_LM_OWF_CHANGE 0x00000040L + +#endif + +typedef enum _DOMAIN_PASSWORD_CONSTRUCTION +{ + DomainPasswordSimple = 1, + DomainPasswordComplex +} DOMAIN_PASSWORD_CONSTRUCTION; + +typedef struct _DOMAIN_LOGOFF_INFORMATION +{ + LARGE_INTEGER ForceLogoff; +} DOMAIN_LOGOFF_INFORMATION, *PDOMAIN_LOGOFF_INFORMATION; + +typedef struct _DOMAIN_OEM_INFORMATION +{ + UNICODE_STRING OemInformation; +} DOMAIN_OEM_INFORMATION, *PDOMAIN_OEM_INFORMATION; + +typedef struct _DOMAIN_NAME_INFORMATION +{ + UNICODE_STRING DomainName; +} DOMAIN_NAME_INFORMATION, *PDOMAIN_NAME_INFORMATION; + +typedef struct _DOMAIN_SERVER_ROLE_INFORMATION +{ + DOMAIN_SERVER_ROLE DomainServerRole; +} DOMAIN_SERVER_ROLE_INFORMATION, *PDOMAIN_SERVER_ROLE_INFORMATION; + +typedef struct _DOMAIN_REPLICATION_INFORMATION +{ + UNICODE_STRING ReplicaSourceNodeName; +} DOMAIN_REPLICATION_INFORMATION, *PDOMAIN_REPLICATION_INFORMATION; + +typedef struct _DOMAIN_MODIFIED_INFORMATION +{ + LARGE_INTEGER DomainModifiedCount; + LARGE_INTEGER CreationTime; +} DOMAIN_MODIFIED_INFORMATION, *PDOMAIN_MODIFIED_INFORMATION; + +typedef struct _DOMAIN_MODIFIED_INFORMATION2 +{ + LARGE_INTEGER DomainModifiedCount; + LARGE_INTEGER CreationTime; + LARGE_INTEGER ModifiedCountAtLastPromotion; +} DOMAIN_MODIFIED_INFORMATION2, *PDOMAIN_MODIFIED_INFORMATION2; + +typedef struct _DOMAIN_STATE_INFORMATION +{ + DOMAIN_SERVER_ENABLE_STATE DomainServerState; +} DOMAIN_STATE_INFORMATION, *PDOMAIN_STATE_INFORMATION; + +typedef struct _DOMAIN_LOCKOUT_INFORMATION +{ + LARGE_INTEGER LockoutDuration; // delta time + LARGE_INTEGER LockoutObservationWindow; // delta time + USHORT LockoutThreshold; // zero means no lockout +} DOMAIN_LOCKOUT_INFORMATION, *PDOMAIN_LOCKOUT_INFORMATION; + +// SamQueryDisplayInformation types + +typedef enum _DOMAIN_DISPLAY_INFORMATION +{ + DomainDisplayUser = 1, + DomainDisplayMachine, + DomainDisplayGroup, + DomainDisplayOemUser, + DomainDisplayOemGroup, + DomainDisplayServer +} DOMAIN_DISPLAY_INFORMATION, *PDOMAIN_DISPLAY_INFORMATION; + +typedef struct _DOMAIN_DISPLAY_USER +{ + ULONG Index; + ULONG Rid; + ULONG AccountControl; + UNICODE_STRING LogonName; + UNICODE_STRING AdminComment; + UNICODE_STRING FullName; +} DOMAIN_DISPLAY_USER, *PDOMAIN_DISPLAY_USER; + +typedef struct _DOMAIN_DISPLAY_MACHINE +{ + ULONG Index; + ULONG Rid; + ULONG AccountControl; + UNICODE_STRING Machine; + UNICODE_STRING Comment; +} DOMAIN_DISPLAY_MACHINE, *PDOMAIN_DISPLAY_MACHINE; + +typedef struct _DOMAIN_DISPLAY_GROUP +{ + ULONG Index; + ULONG Rid; + ULONG Attributes; + UNICODE_STRING Group; + UNICODE_STRING Comment; +} DOMAIN_DISPLAY_GROUP, *PDOMAIN_DISPLAY_GROUP; + +typedef struct _DOMAIN_DISPLAY_OEM_USER +{ + ULONG Index; + OEM_STRING User; +} DOMAIN_DISPLAY_OEM_USER, *PDOMAIN_DISPLAY_OEM_USER; + +typedef struct _DOMAIN_DISPLAY_OEM_GROUP +{ + ULONG Index; + OEM_STRING Group; +} DOMAIN_DISPLAY_OEM_GROUP, *PDOMAIN_DISPLAY_OEM_GROUP; + +// SamQueryLocalizableAccountsInDomain types + +typedef enum _DOMAIN_LOCALIZABLE_ACCOUNTS_INFORMATION +{ + DomainLocalizableAccountsBasic = 1, +} DOMAIN_LOCALIZABLE_ACCOUNTS_INFORMATION, *PDOMAIN_LOCALIZABLE_ACCOUNTS_INFORMATION; + +typedef struct _DOMAIN_LOCALIZABLE_ACCOUNTS_ENTRY +{ + ULONG Rid; + SID_NAME_USE Use; + UNICODE_STRING Name; + UNICODE_STRING AdminComment; +} DOMAIN_LOCALIZABLE_ACCOUNT_ENTRY, *PDOMAIN_LOCALIZABLE_ACCOUNT_ENTRY; + +typedef struct _DOMAIN_LOCALIZABLE_ACCOUNTS +{ + ULONG Count; + _Field_size_(Count) DOMAIN_LOCALIZABLE_ACCOUNT_ENTRY *Entries; +} DOMAIN_LOCALIZABLE_ACCOUNTS_BASIC, *PDOMAIN_LOCALIZABLE_ACCOUNTS_BASIC; + +typedef union _DOMAIN_LOCALIZABLE_INFO_BUFFER +{ + DOMAIN_LOCALIZABLE_ACCOUNTS_BASIC Basic; +} DOMAIN_LOCALIZABLE_ACCOUNTS_INFO_BUFFER, *PDOMAIN_LOCALIZABLE_ACCOUNTS_INFO_BUFFER; + +// Functions + +_Check_return_ +NTSTATUS +NTAPI +SamLookupDomainInSamServer( + _In_ SAM_HANDLE ServerHandle, + _In_ PUNICODE_STRING Name, + _Outptr_ PSID *DomainId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamEnumerateDomainsInSamServer( + _In_ SAM_HANDLE ServerHandle, + _Inout_ PSAM_ENUMERATE_HANDLE EnumerationContext, + _Outptr_ PVOID *Buffer, // PSAM_SID_ENUMERATION *Buffer + _In_ ULONG PreferedMaximumLength, + _Out_ PULONG CountReturned + ); + +_Check_return_ +NTSTATUS +NTAPI +SamOpenDomain( + _In_ SAM_HANDLE ServerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PSID DomainId, + _Out_ PSAM_HANDLE DomainHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryInformationDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ DOMAIN_INFORMATION_CLASS DomainInformationClass, + _Outptr_ PVOID *Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetInformationDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ DOMAIN_INFORMATION_CLASS DomainInformationClass, + _In_ PVOID DomainInformation + ); + +_Check_return_ +NTSTATUS +NTAPI +SamLookupNamesInDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ ULONG Count, + _In_reads_(Count) PUNICODE_STRING Names, + _Out_ _Deref_post_count_(Count) PULONG *RelativeIds, + _Out_ _Deref_post_count_(Count) PSID_NAME_USE *Use + ); + +_Check_return_ +NTSTATUS +NTAPI +SamLookupIdsInDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ ULONG Count, + _In_reads_(Count) PULONG RelativeIds, + _Out_ _Deref_post_count_(Count) PUNICODE_STRING *Names, + _Out_ _Deref_post_opt_count_(Count) PSID_NAME_USE *Use + ); + +_Check_return_ +NTSTATUS +NTAPI +SamRemoveMemberFromForeignDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ PSID MemberId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryLocalizableAccountsInDomain( + _In_ SAM_HANDLE Domain, + _In_ ULONG Flags, + _In_ ULONG LanguageId, + _In_ DOMAIN_LOCALIZABLE_ACCOUNTS_INFORMATION Class, + _Outptr_ PVOID *Buffer + ); + +// Group + +#define GROUP_READ_INFORMATION 0x0001 +#define GROUP_WRITE_ACCOUNT 0x0002 +#define GROUP_ADD_MEMBER 0x0004 +#define GROUP_REMOVE_MEMBER 0x0008 +#define GROUP_LIST_MEMBERS 0x0010 + +#define GROUP_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \ + GROUP_LIST_MEMBERS | \ + GROUP_WRITE_ACCOUNT | \ + GROUP_ADD_MEMBER | \ + GROUP_REMOVE_MEMBER | \ + GROUP_READ_INFORMATION) + +#define GROUP_READ (STANDARD_RIGHTS_READ | \ + GROUP_LIST_MEMBERS) + +#define GROUP_WRITE (STANDARD_RIGHTS_WRITE | \ + GROUP_WRITE_ACCOUNT | \ + GROUP_ADD_MEMBER | \ + GROUP_REMOVE_MEMBER) + +#define GROUP_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ + GROUP_READ_INFORMATION) + +typedef struct _GROUP_MEMBERSHIP +{ + ULONG RelativeId; + ULONG Attributes; +} GROUP_MEMBERSHIP, *PGROUP_MEMBERSHIP; + +// SamQueryInformationGroup/SamSetInformationGroup types + +typedef enum _GROUP_INFORMATION_CLASS +{ + GroupGeneralInformation = 1, + GroupNameInformation, + GroupAttributeInformation, + GroupAdminCommentInformation, + GroupReplicationInformation +} GROUP_INFORMATION_CLASS; + +typedef struct _GROUP_GENERAL_INFORMATION +{ + UNICODE_STRING Name; + ULONG Attributes; + ULONG MemberCount; + UNICODE_STRING AdminComment; +} GROUP_GENERAL_INFORMATION, *PGROUP_GENERAL_INFORMATION; + +typedef struct _GROUP_NAME_INFORMATION +{ + UNICODE_STRING Name; +} GROUP_NAME_INFORMATION, *PGROUP_NAME_INFORMATION; + +typedef struct _GROUP_ATTRIBUTE_INFORMATION +{ + ULONG Attributes; +} GROUP_ATTRIBUTE_INFORMATION, *PGROUP_ATTRIBUTE_INFORMATION; + +typedef struct _GROUP_ADM_COMMENT_INFORMATION +{ + UNICODE_STRING AdminComment; +} GROUP_ADM_COMMENT_INFORMATION, *PGROUP_ADM_COMMENT_INFORMATION; + +// Functions + +_Check_return_ +NTSTATUS +NTAPI +SamEnumerateGroupsInDomain( + _In_ SAM_HANDLE DomainHandle, + _Inout_ PSAM_ENUMERATE_HANDLE EnumerationContext, + _Outptr_ PVOID *Buffer, // PSAM_RID_ENUMERATION * + _In_ ULONG PreferedMaximumLength, + _Out_ PULONG CountReturned + ); + +_Check_return_ +NTSTATUS +NTAPI +SamCreateGroupInDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ PUNICODE_STRING AccountName, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PSAM_HANDLE GroupHandle, + _Out_ PULONG RelativeId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamOpenGroup( + _In_ SAM_HANDLE DomainHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG GroupId, + _Out_ PSAM_HANDLE GroupHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamDeleteGroup( + _In_ SAM_HANDLE GroupHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryInformationGroup( + _In_ SAM_HANDLE GroupHandle, + _In_ GROUP_INFORMATION_CLASS GroupInformationClass, + _Outptr_ PVOID *Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetInformationGroup( + _In_ SAM_HANDLE GroupHandle, + _In_ GROUP_INFORMATION_CLASS GroupInformationClass, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamAddMemberToGroup( + _In_ SAM_HANDLE GroupHandle, + _In_ ULONG MemberId, + _In_ ULONG Attributes + ); + +_Check_return_ +NTSTATUS +NTAPI +SamRemoveMemberFromGroup( + _In_ SAM_HANDLE GroupHandle, + _In_ ULONG MemberId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamGetMembersInGroup( + _In_ SAM_HANDLE GroupHandle, + _Out_ _Deref_post_count_(*MemberCount) PULONG *MemberIds, + _Out_ _Deref_post_count_(*MemberCount) PULONG *Attributes, + _Out_ PULONG MemberCount + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetMemberAttributesOfGroup( + _In_ SAM_HANDLE GroupHandle, + _In_ ULONG MemberId, + _In_ ULONG Attributes + ); + +// Alias + +#define ALIAS_ADD_MEMBER 0x0001 +#define ALIAS_REMOVE_MEMBER 0x0002 +#define ALIAS_LIST_MEMBERS 0x0004 +#define ALIAS_READ_INFORMATION 0x0008 +#define ALIAS_WRITE_ACCOUNT 0x0010 + +#define ALIAS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \ + ALIAS_READ_INFORMATION | \ + ALIAS_WRITE_ACCOUNT | \ + ALIAS_LIST_MEMBERS | \ + ALIAS_ADD_MEMBER | \ + ALIAS_REMOVE_MEMBER) + +#define ALIAS_READ (STANDARD_RIGHTS_READ | \ + ALIAS_LIST_MEMBERS) + +#define ALIAS_WRITE (STANDARD_RIGHTS_WRITE | \ + ALIAS_WRITE_ACCOUNT | \ + ALIAS_ADD_MEMBER | \ + ALIAS_REMOVE_MEMBER) + +#define ALIAS_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ + ALIAS_READ_INFORMATION) + +// SamQueryInformationAlias/SamSetInformationAlias types + +typedef enum _ALIAS_INFORMATION_CLASS +{ + AliasGeneralInformation = 1, + AliasNameInformation, + AliasAdminCommentInformation, + AliasReplicationInformation, + AliasExtendedInformation, +} ALIAS_INFORMATION_CLASS; + +typedef struct _ALIAS_GENERAL_INFORMATION +{ + UNICODE_STRING Name; + ULONG MemberCount; + UNICODE_STRING AdminComment; +} ALIAS_GENERAL_INFORMATION, *PALIAS_GENERAL_INFORMATION; + +typedef struct _ALIAS_NAME_INFORMATION +{ + UNICODE_STRING Name; +} ALIAS_NAME_INFORMATION, *PALIAS_NAME_INFORMATION; + +typedef struct _ALIAS_ADM_COMMENT_INFORMATION +{ + UNICODE_STRING AdminComment; +} ALIAS_ADM_COMMENT_INFORMATION, *PALIAS_ADM_COMMENT_INFORMATION; + +#define ALIAS_ALL_NAME (0x00000001L) +#define ALIAS_ALL_MEMBER_COUNT (0x00000002L) +#define ALIAS_ALL_ADMIN_COMMENT (0x00000004L) +#define ALIAS_ALL_SHELL_ADMIN_OBJECT_PROPERTIES (0x00000008L) + +typedef struct _ALIAS_EXTENDED_INFORMATION +{ + ULONG WhichFields; + SAM_SHELL_OBJECT_PROPERTIES ShellAdminObjectProperties; +} ALIAS_EXTENDED_INFORMATION, *PALIAS_EXTENDED_INFORMATION; + +// Functions + +_Check_return_ +NTSTATUS +NTAPI +SamEnumerateAliasesInDomain( + _In_ SAM_HANDLE DomainHandle, + _Inout_ PSAM_ENUMERATE_HANDLE EnumerationContext, + _Outptr_ PVOID *Buffer, // PSAM_RID_ENUMERATION *Buffer + _In_ ULONG PreferedMaximumLength, + _Out_ PULONG CountReturned + ); + +_Check_return_ +NTSTATUS +NTAPI +SamCreateAliasInDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ PUNICODE_STRING AccountName, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PSAM_HANDLE AliasHandle, + _Out_ PULONG RelativeId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamOpenAlias( + _In_ SAM_HANDLE DomainHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG AliasId, + _Out_ PSAM_HANDLE AliasHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamDeleteAlias( + _In_ SAM_HANDLE AliasHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryInformationAlias( + _In_ SAM_HANDLE AliasHandle, + _In_ ALIAS_INFORMATION_CLASS AliasInformationClass, + _Outptr_ PVOID *Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetInformationAlias( + _In_ SAM_HANDLE AliasHandle, + _In_ ALIAS_INFORMATION_CLASS AliasInformationClass, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamAddMemberToAlias( + _In_ SAM_HANDLE AliasHandle, + _In_ PSID MemberId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamAddMultipleMembersToAlias( + _In_ SAM_HANDLE AliasHandle, + _In_reads_(MemberCount) PSID *MemberIds, + _In_ ULONG MemberCount + ); + +_Check_return_ +NTSTATUS +NTAPI +SamRemoveMemberFromAlias( + _In_ SAM_HANDLE AliasHandle, + _In_ PSID MemberId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamRemoveMultipleMembersFromAlias( + _In_ SAM_HANDLE AliasHandle, + _In_reads_(MemberCount) PSID *MemberIds, + _In_ ULONG MemberCount + ); + +_Check_return_ +NTSTATUS +NTAPI +SamGetMembersInAlias( + _In_ SAM_HANDLE AliasHandle, + _Out_ _Deref_post_count_(*MemberCount) PSID **MemberIds, + _Out_ PULONG MemberCount + ); + +_Check_return_ +NTSTATUS +NTAPI +SamGetAliasMembership( + _In_ SAM_HANDLE DomainHandle, + _In_ ULONG PassedCount, + _In_reads_(PassedCount) PSID *Sids, + _Out_ PULONG MembershipCount, + _Out_ _Deref_post_count_(*MembershipCount) PULONG *Aliases + ); + +// Group types + +#define GROUP_TYPE_BUILTIN_LOCAL_GROUP 0x00000001 +#define GROUP_TYPE_ACCOUNT_GROUP 0x00000002 +#define GROUP_TYPE_RESOURCE_GROUP 0x00000004 +#define GROUP_TYPE_UNIVERSAL_GROUP 0x00000008 +#define GROUP_TYPE_APP_BASIC_GROUP 0x00000010 +#define GROUP_TYPE_APP_QUERY_GROUP 0x00000020 +#define GROUP_TYPE_SECURITY_ENABLED 0x80000000 + +#define GROUP_TYPE_RESOURCE_BEHAVOIR (GROUP_TYPE_RESOURCE_GROUP | \ + GROUP_TYPE_APP_BASIC_GROUP | \ + GROUP_TYPE_APP_QUERY_GROUP) + +// User + +#define USER_READ_GENERAL 0x0001 +#define USER_READ_PREFERENCES 0x0002 +#define USER_WRITE_PREFERENCES 0x0004 +#define USER_READ_LOGON 0x0008 +#define USER_READ_ACCOUNT 0x0010 +#define USER_WRITE_ACCOUNT 0x0020 +#define USER_CHANGE_PASSWORD 0x0040 +#define USER_FORCE_PASSWORD_CHANGE 0x0080 +#define USER_LIST_GROUPS 0x0100 +#define USER_READ_GROUP_INFORMATION 0x0200 +#define USER_WRITE_GROUP_INFORMATION 0x0400 + +#define USER_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | \ + USER_READ_PREFERENCES | \ + USER_READ_LOGON | \ + USER_LIST_GROUPS | \ + USER_READ_GROUP_INFORMATION | \ + USER_WRITE_PREFERENCES | \ + USER_CHANGE_PASSWORD | \ + USER_FORCE_PASSWORD_CHANGE | \ + USER_READ_GENERAL | \ + USER_READ_ACCOUNT | \ + USER_WRITE_ACCOUNT | \ + USER_WRITE_GROUP_INFORMATION) + +#define USER_READ (STANDARD_RIGHTS_READ | \ + USER_READ_PREFERENCES | \ + USER_READ_LOGON | \ + USER_READ_ACCOUNT | \ + USER_LIST_GROUPS | \ + USER_READ_GROUP_INFORMATION) + +#define USER_WRITE (STANDARD_RIGHTS_WRITE | \ + USER_WRITE_PREFERENCES | \ + USER_CHANGE_PASSWORD) + +#define USER_EXECUTE (STANDARD_RIGHTS_EXECUTE | \ + USER_READ_GENERAL | \ + USER_CHANGE_PASSWORD) + +// User account control flags + +#define USER_ACCOUNT_DISABLED (0x00000001) +#define USER_HOME_DIRECTORY_REQUIRED (0x00000002) +#define USER_PASSWORD_NOT_REQUIRED (0x00000004) +#define USER_TEMP_DUPLICATE_ACCOUNT (0x00000008) +#define USER_NORMAL_ACCOUNT (0x00000010) +#define USER_MNS_LOGON_ACCOUNT (0x00000020) +#define USER_INTERDOMAIN_TRUST_ACCOUNT (0x00000040) +#define USER_WORKSTATION_TRUST_ACCOUNT (0x00000080) +#define USER_SERVER_TRUST_ACCOUNT (0x00000100) +#define USER_DONT_EXPIRE_PASSWORD (0x00000200) +#define USER_ACCOUNT_AUTO_LOCKED (0x00000400) +#define USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED (0x00000800) +#define USER_SMARTCARD_REQUIRED (0x00001000) +#define USER_TRUSTED_FOR_DELEGATION (0x00002000) +#define USER_NOT_DELEGATED (0x00004000) +#define USER_USE_DES_KEY_ONLY (0x00008000) +#define USER_DONT_REQUIRE_PREAUTH (0x00010000) +#define USER_PASSWORD_EXPIRED (0x00020000) +#define USER_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION (0x00040000) +#define USER_NO_AUTH_DATA_REQUIRED (0x00080000) +#define USER_PARTIAL_SECRETS_ACCOUNT (0x00100000) +#define USER_USE_AES_KEYS (0x00200000) // not used + +#define NEXT_FREE_ACCOUNT_CONTROL_BIT (USER_USE_AES_KEYS << 1) + +#define USER_MACHINE_ACCOUNT_MASK ( \ + USER_INTERDOMAIN_TRUST_ACCOUNT | \ + USER_WORKSTATION_TRUST_ACCOUNT | \ + USER_SERVER_TRUST_ACCOUNT \ + ) + +#define USER_ACCOUNT_TYPE_MASK ( \ + USER_TEMP_DUPLICATE_ACCOUNT | \ + USER_NORMAL_ACCOUNT | \ + USER_MACHINE_ACCOUNT_MASK \ + ) + +#define USER_COMPUTED_ACCOUNT_CONTROL_BITS ( \ + USER_ACCOUNT_AUTO_LOCKED | \ + USER_PASSWORD_EXPIRED \ + ) + +// Logon times may be expressed in day, hour, or minute granularity. + +#define SAM_DAYS_PER_WEEK (7) +#define SAM_HOURS_PER_WEEK (24 * SAM_DAYS_PER_WEEK) +#define SAM_MINUTES_PER_WEEK (60 * SAM_HOURS_PER_WEEK) + +typedef struct _LOGON_HOURS +{ + USHORT UnitsPerWeek; + + // UnitsPerWeek is the number of equal length time units the week is + // divided into. This value is used to compute the length of the bit + // string in logon_hours. Must be less than or equal to + // SAM_UNITS_PER_WEEK (10080) for this release. + // + // LogonHours is a bit map of valid logon times. Each bit represents + // a unique division in a week. The largest bit map supported is 1260 + // bytes (10080 bits), which represents minutes per week. In this case + // the first bit (bit 0, byte 0) is Sunday, 00:00:00 - 00-00:59; bit 1, + // byte 0 is Sunday, 00:01:00 - 00:01:59, etc. A NULL pointer means + // DONT_CHANGE for SamSetInformationUser() calls. + + PUCHAR LogonHours; +} LOGON_HOURS, *PLOGON_HOURS; + +typedef struct _SR_SECURITY_DESCRIPTOR +{ + ULONG Length; + PUCHAR SecurityDescriptor; +} SR_SECURITY_DESCRIPTOR, *PSR_SECURITY_DESCRIPTOR; + +// SamQueryInformationUser/SamSetInformationUser types + +typedef enum _USER_INFORMATION_CLASS +{ + UserGeneralInformation = 1, + UserPreferencesInformation, + UserLogonInformation, + UserLogonHoursInformation, + UserAccountInformation, + UserNameInformation, + UserAccountNameInformation, + UserFullNameInformation, + UserPrimaryGroupInformation, + UserHomeInformation, + UserScriptInformation, + UserProfileInformation, + UserAdminCommentInformation, + UserWorkStationsInformation, + UserSetPasswordInformation, + UserControlInformation, + UserExpiresInformation, + UserInternal1Information, + UserInternal2Information, + UserParametersInformation, + UserAllInformation, + UserInternal3Information, + UserInternal4Information, + UserInternal5Information, + UserInternal4InformationNew, + UserInternal5InformationNew, + UserInternal6Information, + UserExtendedInformation, + UserLogonUIInformation +} USER_INFORMATION_CLASS, *PUSER_INFORMATION_CLASS; + +#include +typedef struct _USER_ALL_INFORMATION +{ + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER AccountExpires; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + UNICODE_STRING UserName; + UNICODE_STRING FullName; + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING ScriptPath; + UNICODE_STRING ProfilePath; + UNICODE_STRING AdminComment; + UNICODE_STRING WorkStations; + UNICODE_STRING UserComment; + UNICODE_STRING Parameters; + UNICODE_STRING LmPassword; + UNICODE_STRING NtPassword; + UNICODE_STRING PrivateData; + SR_SECURITY_DESCRIPTOR SecurityDescriptor; + ULONG UserId; + ULONG PrimaryGroupId; + ULONG UserAccountControl; + ULONG WhichFields; + LOGON_HOURS LogonHours; + USHORT BadPasswordCount; + USHORT LogonCount; + USHORT CountryCode; + USHORT CodePage; + BOOLEAN LmPasswordPresent; + BOOLEAN NtPasswordPresent; + BOOLEAN PasswordExpired; + BOOLEAN PrivateDataSensitive; +} USER_ALL_INFORMATION, *PUSER_ALL_INFORMATION; +#include + +// Flags for WhichFields in USER_ALL_INFORMATION + +#define USER_ALL_USERNAME 0x00000001 +#define USER_ALL_FULLNAME 0x00000002 +#define USER_ALL_USERID 0x00000004 +#define USER_ALL_PRIMARYGROUPID 0x00000008 +#define USER_ALL_ADMINCOMMENT 0x00000010 +#define USER_ALL_USERCOMMENT 0x00000020 +#define USER_ALL_HOMEDIRECTORY 0x00000040 +#define USER_ALL_HOMEDIRECTORYDRIVE 0x00000080 +#define USER_ALL_SCRIPTPATH 0x00000100 +#define USER_ALL_PROFILEPATH 0x00000200 +#define USER_ALL_WORKSTATIONS 0x00000400 +#define USER_ALL_LASTLOGON 0x00000800 +#define USER_ALL_LASTLOGOFF 0x00001000 +#define USER_ALL_LOGONHOURS 0x00002000 +#define USER_ALL_BADPASSWORDCOUNT 0x00004000 +#define USER_ALL_LOGONCOUNT 0x00008000 +#define USER_ALL_PASSWORDCANCHANGE 0x00010000 +#define USER_ALL_PASSWORDMUSTCHANGE 0x00020000 +#define USER_ALL_PASSWORDLASTSET 0x00040000 +#define USER_ALL_ACCOUNTEXPIRES 0x00080000 +#define USER_ALL_USERACCOUNTCONTROL 0x00100000 +#define USER_ALL_PARAMETERS 0x00200000 +#define USER_ALL_COUNTRYCODE 0x00400000 +#define USER_ALL_CODEPAGE 0x00800000 +#define USER_ALL_NTPASSWORDPRESENT 0x01000000 // field AND boolean +#define USER_ALL_LMPASSWORDPRESENT 0x02000000 // field AND boolean +#define USER_ALL_PRIVATEDATA 0x04000000 // field AND boolean +#define USER_ALL_PASSWORDEXPIRED 0x08000000 +#define USER_ALL_SECURITYDESCRIPTOR 0x10000000 +#define USER_ALL_OWFPASSWORD 0x20000000 // boolean + +#define USER_ALL_UNDEFINED_MASK 0xc0000000 + +// Fields that require USER_READ_GENERAL access to read. + +#define USER_ALL_READ_GENERAL_MASK (USER_ALL_USERNAME | \ + USER_ALL_FULLNAME | \ + USER_ALL_USERID | \ + USER_ALL_PRIMARYGROUPID | \ + USER_ALL_ADMINCOMMENT | \ + USER_ALL_USERCOMMENT) + +// Fields that require USER_READ_LOGON access to read. + +#define USER_ALL_READ_LOGON_MASK (USER_ALL_HOMEDIRECTORY | \ + USER_ALL_HOMEDIRECTORYDRIVE | \ + USER_ALL_SCRIPTPATH | \ + USER_ALL_PROFILEPATH | \ + USER_ALL_WORKSTATIONS | \ + USER_ALL_LASTLOGON | \ + USER_ALL_LASTLOGOFF | \ + USER_ALL_LOGONHOURS | \ + USER_ALL_BADPASSWORDCOUNT | \ + USER_ALL_LOGONCOUNT | \ + USER_ALL_PASSWORDCANCHANGE | \ + USER_ALL_PASSWORDMUSTCHANGE) + +// Fields that require USER_READ_ACCOUNT access to read. + +#define USER_ALL_READ_ACCOUNT_MASK (USER_ALL_PASSWORDLASTSET | \ + USER_ALL_ACCOUNTEXPIRES | \ + USER_ALL_USERACCOUNTCONTROL | \ + USER_ALL_PARAMETERS) + +// Fields that require USER_READ_PREFERENCES access to read. + +#define USER_ALL_READ_PREFERENCES_MASK (USER_ALL_COUNTRYCODE | \ + USER_ALL_CODEPAGE) + +// Fields that can only be read by trusted clients. + +#define USER_ALL_READ_TRUSTED_MASK (USER_ALL_NTPASSWORDPRESENT | \ + USER_ALL_LMPASSWORDPRESENT | \ + USER_ALL_PASSWORDEXPIRED | \ + USER_ALL_SECURITYDESCRIPTOR | \ + USER_ALL_PRIVATEDATA) + +// Fields that can't be read. + +#define USER_ALL_READ_CANT_MASK USER_ALL_UNDEFINED_MASK + +// Fields that require USER_WRITE_ACCOUNT access to write. + +#define USER_ALL_WRITE_ACCOUNT_MASK (USER_ALL_USERNAME | \ + USER_ALL_FULLNAME | \ + USER_ALL_PRIMARYGROUPID | \ + USER_ALL_HOMEDIRECTORY | \ + USER_ALL_HOMEDIRECTORYDRIVE | \ + USER_ALL_SCRIPTPATH | \ + USER_ALL_PROFILEPATH | \ + USER_ALL_ADMINCOMMENT | \ + USER_ALL_WORKSTATIONS | \ + USER_ALL_LOGONHOURS | \ + USER_ALL_ACCOUNTEXPIRES | \ + USER_ALL_USERACCOUNTCONTROL | \ + USER_ALL_PARAMETERS) + +// Fields that require USER_WRITE_PREFERENCES access to write. + +#define USER_ALL_WRITE_PREFERENCES_MASK (USER_ALL_USERCOMMENT | \ + USER_ALL_COUNTRYCODE | \ + USER_ALL_CODEPAGE) + +// Fields that require USER_FORCE_PASSWORD_CHANGE access to write. +// +// Note that non-trusted clients only set the NT password as a +// UNICODE string. The wrapper will convert it to an LM password, +// OWF and encrypt both versions. Trusted clients can pass in OWF +// versions of either or both. + +#define USER_ALL_WRITE_FORCE_PASSWORD_CHANGE_MASK \ + (USER_ALL_NTPASSWORDPRESENT | \ + USER_ALL_LMPASSWORDPRESENT | \ + USER_ALL_PASSWORDEXPIRED) + +// Fields that can only be written by trusted clients. + +#define USER_ALL_WRITE_TRUSTED_MASK (USER_ALL_LASTLOGON | \ + USER_ALL_LASTLOGOFF | \ + USER_ALL_BADPASSWORDCOUNT | \ + USER_ALL_LOGONCOUNT | \ + USER_ALL_PASSWORDLASTSET | \ + USER_ALL_SECURITYDESCRIPTOR | \ + USER_ALL_PRIVATEDATA) + +// Fields that can't be written. + +#define USER_ALL_WRITE_CANT_MASK (USER_ALL_USERID | \ + USER_ALL_PASSWORDCANCHANGE | \ + USER_ALL_PASSWORDMUSTCHANGE | \ + USER_ALL_UNDEFINED_MASK) + +typedef struct _USER_GENERAL_INFORMATION +{ + UNICODE_STRING UserName; + UNICODE_STRING FullName; + ULONG PrimaryGroupId; + UNICODE_STRING AdminComment; + UNICODE_STRING UserComment; +} USER_GENERAL_INFORMATION, *PUSER_GENERAL_INFORMATION; + +typedef struct _USER_PREFERENCES_INFORMATION +{ + UNICODE_STRING UserComment; + UNICODE_STRING Reserved1; + USHORT CountryCode; + USHORT CodePage; +} USER_PREFERENCES_INFORMATION, *PUSER_PREFERENCES_INFORMATION; + +typedef struct _USER_PARAMETERS_INFORMATION +{ + UNICODE_STRING Parameters; +} USER_PARAMETERS_INFORMATION, *PUSER_PARAMETERS_INFORMATION; + +#include +typedef struct _USER_LOGON_INFORMATION +{ + UNICODE_STRING UserName; + UNICODE_STRING FullName; + ULONG UserId; + ULONG PrimaryGroupId; + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING ScriptPath; + UNICODE_STRING ProfilePath; + UNICODE_STRING WorkStations; + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + LOGON_HOURS LogonHours; + USHORT BadPasswordCount; + USHORT LogonCount; + ULONG UserAccountControl; +} USER_LOGON_INFORMATION, *PUSER_LOGON_INFORMATION; +#include + +#include +typedef struct _USER_ACCOUNT_INFORMATION +{ + UNICODE_STRING UserName; + UNICODE_STRING FullName; + ULONG UserId; + ULONG PrimaryGroupId; + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING ScriptPath; + UNICODE_STRING ProfilePath; + UNICODE_STRING AdminComment; + UNICODE_STRING WorkStations; + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LOGON_HOURS LogonHours; + USHORT BadPasswordCount; + USHORT LogonCount; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER AccountExpires; + ULONG UserAccountControl; +} USER_ACCOUNT_INFORMATION, *PUSER_ACCOUNT_INFORMATION; +#include + +typedef struct _USER_ACCOUNT_NAME_INFORMATION +{ + UNICODE_STRING UserName; +} USER_ACCOUNT_NAME_INFORMATION, *PUSER_ACCOUNT_NAME_INFORMATION; + +typedef struct _USER_FULL_NAME_INFORMATION +{ + UNICODE_STRING FullName; +} USER_FULL_NAME_INFORMATION, *PUSER_FULL_NAME_INFORMATION; + +typedef struct _USER_NAME_INFORMATION +{ + UNICODE_STRING UserName; + UNICODE_STRING FullName; +} USER_NAME_INFORMATION, *PUSER_NAME_INFORMATION; + +typedef struct _USER_PRIMARY_GROUP_INFORMATION +{ + ULONG PrimaryGroupId; +} USER_PRIMARY_GROUP_INFORMATION, *PUSER_PRIMARY_GROUP_INFORMATION; + +typedef struct _USER_HOME_INFORMATION +{ + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; +} USER_HOME_INFORMATION, *PUSER_HOME_INFORMATION; + +typedef struct _USER_SCRIPT_INFORMATION +{ + UNICODE_STRING ScriptPath; +} USER_SCRIPT_INFORMATION, *PUSER_SCRIPT_INFORMATION; + +typedef struct _USER_PROFILE_INFORMATION +{ + UNICODE_STRING ProfilePath; +} USER_PROFILE_INFORMATION, *PUSER_PROFILE_INFORMATION; + +typedef struct _USER_ADMIN_COMMENT_INFORMATION +{ + UNICODE_STRING AdminComment; +} USER_ADMIN_COMMENT_INFORMATION, *PUSER_ADMIN_COMMENT_INFORMATION; + +typedef struct _USER_WORKSTATIONS_INFORMATION +{ + UNICODE_STRING WorkStations; +} USER_WORKSTATIONS_INFORMATION, *PUSER_WORKSTATIONS_INFORMATION; + +typedef struct _USER_SET_PASSWORD_INFORMATION +{ + UNICODE_STRING Password; + BOOLEAN PasswordExpired; +} USER_SET_PASSWORD_INFORMATION, *PUSER_SET_PASSWORD_INFORMATION; + +typedef struct _USER_CONTROL_INFORMATION +{ + ULONG UserAccountControl; +} USER_CONTROL_INFORMATION, *PUSER_CONTROL_INFORMATION; + +typedef struct _USER_EXPIRES_INFORMATION +{ + LARGE_INTEGER AccountExpires; +} USER_EXPIRES_INFORMATION, *PUSER_EXPIRES_INFORMATION; + +typedef struct _USER_LOGON_HOURS_INFORMATION +{ + LOGON_HOURS LogonHours; +} USER_LOGON_HOURS_INFORMATION, *PUSER_LOGON_HOURS_INFORMATION; + +typedef SAM_BYTE_ARRAY_32K SAM_USER_TILE, *PSAM_USER_TILE; + +// 0xff000fff is reserved for internal callers and implementation. + +#define USER_EXTENDED_FIELD_USER_TILE (0x00001000L) +#define USER_EXTENDED_FIELD_PASSWORD_HINT (0x00002000L) +#define USER_EXTENDED_FIELD_DONT_SHOW_IN_LOGON_UI (0x00004000L) +#define USER_EXTENDED_FIELD_SHELL_ADMIN_OBJECT_PROPERTIES (0x00008000L) + +typedef struct _USER_EXTENDED_INFORMATION +{ + ULONG ExtendedWhichFields; + SAM_USER_TILE UserTile; + UNICODE_STRING PasswordHint; + BOOLEAN DontShowInLogonUI; + SAM_SHELL_OBJECT_PROPERTIES ShellAdminObjectProperties; +} USER_EXTENDED_INFORMATION, *PUSER_EXTENDED_INFORMATION; + +// For local callers only. +typedef struct _USER_LOGON_UI_INFORMATION +{ + BOOLEAN PasswordIsBlank; + BOOLEAN AccountIsDisabled; +} USER_LOGON_UI_INFORMATION, *PUSER_LOGON_UI_INFORMATION; + +// SamChangePasswordUser3 types + +// Error values: +// * SAM_PWD_CHANGE_NO_ERROR +// * SAM_PWD_CHANGE_PASSWORD_TOO_SHORT +// * SAM_PWD_CHANGE_PWD_IN_HISTORY +// * SAM_PWD_CHANGE_USERNAME_IN_PASSWORD +// * SAM_PWD_CHANGE_FULLNAME_IN_PASSWORD +// * SAM_PWD_CHANGE_MACHINE_PASSWORD_NOT_DEFAULT +// * SAM_PWD_CHANGE_FAILED_BY_FILTER + +typedef struct _USER_PWD_CHANGE_FAILURE_INFORMATION +{ + ULONG ExtendedFailureReason; + UNICODE_STRING FilterModuleName; +} USER_PWD_CHANGE_FAILURE_INFORMATION,*PUSER_PWD_CHANGE_FAILURE_INFORMATION; + +// ExtendedFailureReason values + +#define SAM_PWD_CHANGE_NO_ERROR 0 +#define SAM_PWD_CHANGE_PASSWORD_TOO_SHORT 1 +#define SAM_PWD_CHANGE_PWD_IN_HISTORY 2 +#define SAM_PWD_CHANGE_USERNAME_IN_PASSWORD 3 +#define SAM_PWD_CHANGE_FULLNAME_IN_PASSWORD 4 +#define SAM_PWD_CHANGE_NOT_COMPLEX 5 +#define SAM_PWD_CHANGE_MACHINE_PASSWORD_NOT_DEFAULT 6 +#define SAM_PWD_CHANGE_FAILED_BY_FILTER 7 +#define SAM_PWD_CHANGE_PASSWORD_TOO_LONG 8 +#define SAM_PWD_CHANGE_FAILURE_REASON_MAX 8 + +// Functions + +_Check_return_ +NTSTATUS +NTAPI +SamEnumerateUsersInDomain( + _In_ SAM_HANDLE DomainHandle, + _Inout_ PSAM_ENUMERATE_HANDLE EnumerationContext, + _In_ ULONG UserAccountControl, + _Outptr_ PVOID *Buffer, // PSAM_RID_ENUMERATION * + _In_ ULONG PreferedMaximumLength, + _Out_ PULONG CountReturned + ); + +_Check_return_ +NTSTATUS +NTAPI +SamCreateUserInDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ PUNICODE_STRING AccountName, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PSAM_HANDLE UserHandle, + _Out_ PULONG RelativeId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamCreateUser2InDomain( + _In_ SAM_HANDLE DomainHandle, + _In_ PUNICODE_STRING AccountName, + _In_ ULONG AccountType, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PSAM_HANDLE UserHandle, + _Out_ PULONG GrantedAccess, + _Out_ PULONG RelativeId + ); + +_Check_return_ +NTSTATUS +NTAPI +SamOpenUser( + _In_ SAM_HANDLE DomainHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG UserId, + _Out_ PSAM_HANDLE UserHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamDeleteUser( + _In_ SAM_HANDLE UserHandle + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryInformationUser( + _In_ SAM_HANDLE UserHandle, + _In_ USER_INFORMATION_CLASS UserInformationClass, + _Outptr_ PVOID *Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamSetInformationUser( + _In_ SAM_HANDLE UserHandle, + _In_ USER_INFORMATION_CLASS UserInformationClass, + _In_ PVOID Buffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamGetGroupsForUser( + _In_ SAM_HANDLE UserHandle, + _Out_ _Deref_post_count_(*MembershipCount) PGROUP_MEMBERSHIP *Groups, + _Out_ PULONG MembershipCount + ); + +_Check_return_ +NTSTATUS +NTAPI +SamChangePasswordUser( + _In_ SAM_HANDLE UserHandle, + _In_ PUNICODE_STRING OldPassword, + _In_ PUNICODE_STRING NewPassword + ); + +_Check_return_ +NTSTATUS +NTAPI +SamChangePasswordUser2( + _In_ PUNICODE_STRING ServerName, + _In_ PUNICODE_STRING UserName, + _In_ PUNICODE_STRING OldPassword, + _In_ PUNICODE_STRING NewPassword + ); + +_Check_return_ +NTSTATUS +NTAPI +SamChangePasswordUser3( + _In_ PUNICODE_STRING ServerName, + _In_ PUNICODE_STRING UserName, + _In_ PUNICODE_STRING OldPassword, + _In_ PUNICODE_STRING NewPassword, + _Outptr_ PDOMAIN_PASSWORD_INFORMATION *EffectivePasswordPolicy, + _Outptr_ PUSER_PWD_CHANGE_FAILURE_INFORMATION *PasswordChangeFailureInfo + ); + +_Check_return_ +NTSTATUS +NTAPI +SamQueryDisplayInformation( + _In_ SAM_HANDLE DomainHandle, + _In_ DOMAIN_DISPLAY_INFORMATION DisplayInformation, + _In_ ULONG Index, + _In_ ULONG EntryCount, + _In_ ULONG PreferredMaximumLength, + _In_ PULONG TotalAvailable, + _Out_ PULONG TotalReturned, + _Out_ PULONG ReturnedEntryCount, + _Outptr_ PVOID *SortedBuffer + ); + +_Check_return_ +NTSTATUS +NTAPI +SamGetDisplayEnumerationIndex( + _In_ SAM_HANDLE DomainHandle, + _In_ DOMAIN_DISPLAY_INFORMATION DisplayInformation, + _In_ PUNICODE_STRING Prefix, + _Out_ PULONG Index + ); + +// Database replication + +typedef enum _SECURITY_DB_DELTA_TYPE +{ + SecurityDbNew = 1, + SecurityDbRename, + SecurityDbDelete, + SecurityDbChangeMemberAdd, + SecurityDbChangeMemberSet, + SecurityDbChangeMemberDel, + SecurityDbChange, + SecurityDbChangePassword +} SECURITY_DB_DELTA_TYPE, *PSECURITY_DB_DELTA_TYPE; + +typedef enum _SECURITY_DB_OBJECT_TYPE +{ + SecurityDbObjectSamDomain = 1, + SecurityDbObjectSamUser, + SecurityDbObjectSamGroup, + SecurityDbObjectSamAlias, + SecurityDbObjectLsaPolicy, + SecurityDbObjectLsaTDomain, + SecurityDbObjectLsaAccount, + SecurityDbObjectLsaSecret +} SECURITY_DB_OBJECT_TYPE, *PSECURITY_DB_OBJECT_TYPE; + +typedef enum _SAM_ACCOUNT_TYPE +{ + SamObjectUser = 1, + SamObjectGroup, + SamObjectAlias +} SAM_ACCOUNT_TYPE, *PSAM_ACCOUNT_TYPE; + +#define SAM_USER_ACCOUNT (0x00000001) +#define SAM_GLOBAL_GROUP_ACCOUNT (0x00000002) +#define SAM_LOCAL_GROUP_ACCOUNT (0x00000004) + +typedef struct _SAM_GROUP_MEMBER_ID +{ + ULONG MemberRid; +} SAM_GROUP_MEMBER_ID, *PSAM_GROUP_MEMBER_ID; + +typedef struct _SAM_ALIAS_MEMBER_ID +{ + PSID MemberSid; +} SAM_ALIAS_MEMBER_ID, *PSAM_ALIAS_MEMBER_ID; + +typedef union _SAM_DELTA_DATA +{ + SAM_GROUP_MEMBER_ID GroupMemberId; + SAM_ALIAS_MEMBER_ID AliasMemberId; + ULONG AccountControl; +} SAM_DELTA_DATA, *PSAM_DELTA_DATA; + +typedef NTSTATUS (NTAPI *PSAM_DELTA_NOTIFICATION_ROUTINE)( + _In_ PSID DomainSid, + _In_ SECURITY_DB_DELTA_TYPE DeltaType, + _In_ SECURITY_DB_OBJECT_TYPE ObjectType, + _In_ ULONG ObjectRid, + _In_opt_ PUNICODE_STRING ObjectName, + _In_ PLARGE_INTEGER ModifiedCount, + _In_opt_ PSAM_DELTA_DATA DeltaData + ); + +#define SAM_DELTA_NOTIFY_ROUTINE "DeltaNotify" + +_Check_return_ +NTSTATUS +NTAPI +SamRegisterObjectChangeNotification( + _In_ SECURITY_DB_OBJECT_TYPE ObjectType, + _In_ HANDLE NotificationEventHandle + ); + +NTSTATUS +NTAPI +SamUnregisterObjectChangeNotification( + _In_ SECURITY_DB_OBJECT_TYPE ObjectType, + _In_ HANDLE NotificationEventHandle + ); + +// Compatibility mode + +#define SAM_SID_COMPATIBILITY_ALL 0 +#define SAM_SID_COMPATIBILITY_LAX 1 +#define SAM_SID_COMPATIBILITY_STRICT 2 + +_Check_return_ +NTSTATUS +NTAPI +SamGetCompatibilityMode( + _In_ SAM_HANDLE ObjectHandle, + _Out_ ULONG *Mode + ); + +// Password validation + +typedef enum _PASSWORD_POLICY_VALIDATION_TYPE +{ + SamValidateAuthentication = 1, + SamValidatePasswordChange, + SamValidatePasswordReset +} PASSWORD_POLICY_VALIDATION_TYPE; + +typedef struct _SAM_VALIDATE_PASSWORD_HASH +{ + ULONG Length; + _Field_size_bytes_(Length) PUCHAR Hash; +} SAM_VALIDATE_PASSWORD_HASH, *PSAM_VALIDATE_PASSWORD_HASH; + +// Flags for PresentFields in SAM_VALIDATE_PERSISTED_FIELDS + +#define SAM_VALIDATE_PASSWORD_LAST_SET 0x00000001 +#define SAM_VALIDATE_BAD_PASSWORD_TIME 0x00000002 +#define SAM_VALIDATE_LOCKOUT_TIME 0x00000004 +#define SAM_VALIDATE_BAD_PASSWORD_COUNT 0x00000008 +#define SAM_VALIDATE_PASSWORD_HISTORY_LENGTH 0x00000010 +#define SAM_VALIDATE_PASSWORD_HISTORY 0x00000020 + +typedef struct _SAM_VALIDATE_PERSISTED_FIELDS +{ + ULONG PresentFields; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER BadPasswordTime; + LARGE_INTEGER LockoutTime; + ULONG BadPasswordCount; + ULONG PasswordHistoryLength; + _Field_size_bytes_(PasswordHistoryLength) PSAM_VALIDATE_PASSWORD_HASH PasswordHistory; +} SAM_VALIDATE_PERSISTED_FIELDS, *PSAM_VALIDATE_PERSISTED_FIELDS; + +typedef enum _SAM_VALIDATE_VALIDATION_STATUS +{ + SamValidateSuccess = 0, + SamValidatePasswordMustChange, + SamValidateAccountLockedOut, + SamValidatePasswordExpired, + SamValidatePasswordIncorrect, + SamValidatePasswordIsInHistory, + SamValidatePasswordTooShort, + SamValidatePasswordTooLong, + SamValidatePasswordNotComplexEnough, + SamValidatePasswordTooRecent, + SamValidatePasswordFilterError +} SAM_VALIDATE_VALIDATION_STATUS, *PSAM_VALIDATE_VALIDATION_STATUS; + +typedef struct _SAM_VALIDATE_STANDARD_OUTPUT_ARG +{ + SAM_VALIDATE_PERSISTED_FIELDS ChangedPersistedFields; + SAM_VALIDATE_VALIDATION_STATUS ValidationStatus; +} SAM_VALIDATE_STANDARD_OUTPUT_ARG, *PSAM_VALIDATE_STANDARD_OUTPUT_ARG; + +typedef struct _SAM_VALIDATE_AUTHENTICATION_INPUT_ARG +{ + SAM_VALIDATE_PERSISTED_FIELDS InputPersistedFields; + BOOLEAN PasswordMatched; +} SAM_VALIDATE_AUTHENTICATION_INPUT_ARG, *PSAM_VALIDATE_AUTHENTICATION_INPUT_ARG; + +typedef struct _SAM_VALIDATE_PASSWORD_CHANGE_INPUT_ARG +{ + SAM_VALIDATE_PERSISTED_FIELDS InputPersistedFields; + UNICODE_STRING ClearPassword; + UNICODE_STRING UserAccountName; + SAM_VALIDATE_PASSWORD_HASH HashedPassword; + BOOLEAN PasswordMatch; // denotes if the old password supplied by user matched or not +} SAM_VALIDATE_PASSWORD_CHANGE_INPUT_ARG, *PSAM_VALIDATE_PASSWORD_CHANGE_INPUT_ARG; + +typedef struct _SAM_VALIDATE_PASSWORD_RESET_INPUT_ARG +{ + SAM_VALIDATE_PERSISTED_FIELDS InputPersistedFields; + UNICODE_STRING ClearPassword; + UNICODE_STRING UserAccountName; + SAM_VALIDATE_PASSWORD_HASH HashedPassword; + BOOLEAN PasswordMustChangeAtNextLogon; // looked at only for password reset + BOOLEAN ClearLockout; // can be used clear user account lockout +}SAM_VALIDATE_PASSWORD_RESET_INPUT_ARG, *PSAM_VALIDATE_PASSWORD_RESET_INPUT_ARG; + +typedef union _SAM_VALIDATE_INPUT_ARG +{ + SAM_VALIDATE_AUTHENTICATION_INPUT_ARG ValidateAuthenticationInput; + SAM_VALIDATE_PASSWORD_CHANGE_INPUT_ARG ValidatePasswordChangeInput; + SAM_VALIDATE_PASSWORD_RESET_INPUT_ARG ValidatePasswordResetInput; +} SAM_VALIDATE_INPUT_ARG, *PSAM_VALIDATE_INPUT_ARG; + +typedef union _SAM_VALIDATE_OUTPUT_ARG +{ + SAM_VALIDATE_STANDARD_OUTPUT_ARG ValidateAuthenticationOutput; + SAM_VALIDATE_STANDARD_OUTPUT_ARG ValidatePasswordChangeOutput; + SAM_VALIDATE_STANDARD_OUTPUT_ARG ValidatePasswordResetOutput; +} SAM_VALIDATE_OUTPUT_ARG, *PSAM_VALIDATE_OUTPUT_ARG; + +_Check_return_ +NTSTATUS +NTAPI +SamValidatePassword( + _In_opt_ PUNICODE_STRING ServerName, + _In_ PASSWORD_POLICY_VALIDATION_TYPE ValidationType, + _In_ PSAM_VALIDATE_INPUT_ARG InputArg, + _Out_ PSAM_VALIDATE_OUTPUT_ARG *OutputArg + ); + +// Generic operation + +typedef enum _SAM_GENERIC_OPERATION_TYPE +{ + SamObjectChangeNotificationOperation +} SAM_GENERIC_OPERATION_TYPE, *PSAM_GENERIC_OPERATION_TYPE; + +typedef struct _SAM_OPERATION_OBJCHG_INPUT +{ + BOOLEAN Register; + ULONG64 EventHandle; + SECURITY_DB_OBJECT_TYPE ObjectType; + ULONG ProcessID; +} SAM_OPERATION_OBJCHG_INPUT, *PSAM_OPERATION_OBJCHG_INPUT; + +typedef struct _SAM_OPERATION_OBJCHG_OUTPUT +{ + ULONG Reserved; +} SAM_OPERATION_OBJCHG_OUTPUT, *PSAM_OPERATION_OBJCHG_OUTPUT; + +typedef union _SAM_GENERIC_OPERATION_INPUT +{ + SAM_OPERATION_OBJCHG_INPUT ObjChangeIn; +} SAM_GENERIC_OPERATION_INPUT, *PSAM_GENERIC_OPERATION_INPUT; + +typedef union _SAM_GENERIC_OPERATION_OUTPUT +{ + SAM_OPERATION_OBJCHG_OUTPUT ObjChangeOut; +} SAM_GENERIC_OPERATION_OUTPUT, *PSAM_GENERIC_OPERATION_OUTPUT; + +_Check_return_ +NTSTATUS +NTAPI +SamPerformGenericOperation( + _In_opt_ PWSTR ServerName, + _In_ SAM_GENERIC_OPERATION_TYPE OperationType, + _In_ PSAM_GENERIC_OPERATION_INPUT OperationIn, + _Out_ PSAM_GENERIC_OPERATION_OUTPUT *OperationOut + ); + +#endif diff --git a/phnt/include/ntseapi.h b/phnt/include/ntseapi.h new file mode 100644 index 0000000..c4f9d02 --- /dev/null +++ b/phnt/include/ntseapi.h @@ -0,0 +1,635 @@ +#ifndef _NTSEAPI_H +#define _NTSEAPI_H + +// Privileges + +#define SE_MIN_WELL_KNOWN_PRIVILEGE (2L) +#define SE_CREATE_TOKEN_PRIVILEGE (2L) +#define SE_ASSIGNPRIMARYTOKEN_PRIVILEGE (3L) +#define SE_LOCK_MEMORY_PRIVILEGE (4L) +#define SE_INCREASE_QUOTA_PRIVILEGE (5L) + +#define SE_MACHINE_ACCOUNT_PRIVILEGE (6L) +#define SE_TCB_PRIVILEGE (7L) +#define SE_SECURITY_PRIVILEGE (8L) +#define SE_TAKE_OWNERSHIP_PRIVILEGE (9L) +#define SE_LOAD_DRIVER_PRIVILEGE (10L) +#define SE_SYSTEM_PROFILE_PRIVILEGE (11L) +#define SE_SYSTEMTIME_PRIVILEGE (12L) +#define SE_PROF_SINGLE_PROCESS_PRIVILEGE (13L) +#define SE_INC_BASE_PRIORITY_PRIVILEGE (14L) +#define SE_CREATE_PAGEFILE_PRIVILEGE (15L) +#define SE_CREATE_PERMANENT_PRIVILEGE (16L) +#define SE_BACKUP_PRIVILEGE (17L) +#define SE_RESTORE_PRIVILEGE (18L) +#define SE_SHUTDOWN_PRIVILEGE (19L) +#define SE_DEBUG_PRIVILEGE (20L) +#define SE_AUDIT_PRIVILEGE (21L) +#define SE_SYSTEM_ENVIRONMENT_PRIVILEGE (22L) +#define SE_CHANGE_NOTIFY_PRIVILEGE (23L) +#define SE_REMOTE_SHUTDOWN_PRIVILEGE (24L) +#define SE_UNDOCK_PRIVILEGE (25L) +#define SE_SYNC_AGENT_PRIVILEGE (26L) +#define SE_ENABLE_DELEGATION_PRIVILEGE (27L) +#define SE_MANAGE_VOLUME_PRIVILEGE (28L) +#define SE_IMPERSONATE_PRIVILEGE (29L) +#define SE_CREATE_GLOBAL_PRIVILEGE (30L) +#define SE_TRUSTED_CREDMAN_ACCESS_PRIVILEGE (31L) +#define SE_RELABEL_PRIVILEGE (32L) +#define SE_INC_WORKING_SET_PRIVILEGE (33L) +#define SE_TIME_ZONE_PRIVILEGE (34L) +#define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE (35L) +#define SE_MAX_WELL_KNOWN_PRIVILEGE SE_CREATE_SYMBOLIC_LINK_PRIVILEGE + + +// Authz + +// begin_rev + +// Types + +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_INVALID 0x00 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_INT64 0x01 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_UINT64 0x02 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_STRING 0x03 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_FQBN 0x04 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_SID 0x05 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_BOOLEAN 0x06 +#define TOKEN_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING 0x10 + +// Flags + +#define TOKEN_SECURITY_ATTRIBUTE_NON_INHERITABLE 0x0001 +#define TOKEN_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE 0x0002 +#define TOKEN_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY 0x0004 +#define TOKEN_SECURITY_ATTRIBUTE_DISABLED_BY_DEFAULT 0x0008 +#define TOKEN_SECURITY_ATTRIBUTE_DISABLED 0x0010 +#define TOKEN_SECURITY_ATTRIBUTE_MANDATORY 0x0020 + +#define TOKEN_SECURITY_ATTRIBUTE_VALID_FLAGS ( \ + TOKEN_SECURITY_ATTRIBUTE_NON_INHERITABLE | \ + TOKEN_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE | \ + TOKEN_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY | \ + TOKEN_SECURITY_ATTRIBUTE_DISABLED_BY_DEFAULT | \ + TOKEN_SECURITY_ATTRIBUTE_DISABLED | \ + TOKEN_SECURITY_ATTRIBUTE_MANDATORY) + +#define TOKEN_SECURITY_ATTRIBUTE_CUSTOM_FLAGS 0xffff0000 + +// end_rev + +// private +typedef struct _TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE +{ + ULONG64 Version; + UNICODE_STRING Name; +} TOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE, *PTOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE; + +// private +typedef struct _TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE +{ + PVOID pValue; + ULONG ValueLength; +} TOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE, *PTOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE; + +// private +typedef struct _TOKEN_SECURITY_ATTRIBUTE_V1 +{ + UNICODE_STRING Name; + USHORT ValueType; + USHORT Reserved; + ULONG Flags; + ULONG ValueCount; + union + { + PLONG64 pInt64; + PULONG64 pUint64; + PUNICODE_STRING pString; + PTOKEN_SECURITY_ATTRIBUTE_FQBN_VALUE pFqbn; + PTOKEN_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE pOctetString; + } Values; +} TOKEN_SECURITY_ATTRIBUTE_V1, *PTOKEN_SECURITY_ATTRIBUTE_V1; + +// rev +#define TOKEN_SECURITY_ATTRIBUTES_INFORMATION_VERSION_V1 1 +// rev +#define TOKEN_SECURITY_ATTRIBUTES_INFORMATION_VERSION TOKEN_SECURITY_ATTRIBUTES_INFORMATION_VERSION_V1 + +// private +typedef struct _TOKEN_SECURITY_ATTRIBUTES_INFORMATION +{ + USHORT Version; + USHORT Reserved; + ULONG AttributeCount; + union + { + PTOKEN_SECURITY_ATTRIBUTE_V1 pAttributeV1; + } Attribute; +} TOKEN_SECURITY_ATTRIBUTES_INFORMATION, *PTOKEN_SECURITY_ATTRIBUTES_INFORMATION; + +// Tokens + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateToken( + _Out_ PHANDLE TokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PTOKEN_USER User, + _In_ PTOKEN_GROUPS Groups, + _In_ PTOKEN_PRIVILEGES Privileges, + _In_opt_ PTOKEN_OWNER Owner, + _In_ PTOKEN_PRIMARY_GROUP PrimaryGroup, + _In_opt_ PTOKEN_DEFAULT_DACL DefaultDacl, + _In_ PTOKEN_SOURCE TokenSource + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateLowBoxToken( + _Out_ PHANDLE TokenHandle, + _In_ HANDLE ExistingTokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PSID PackageSid, + _In_ ULONG CapabilityCount, + _In_reads_opt_(CapabilityCount) PSID_AND_ATTRIBUTES Capabilities, + _In_ ULONG HandleCount, + _In_reads_opt_(HandleCount) HANDLE *Handles + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateTokenEx( + _Out_ PHANDLE TokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PTOKEN_USER User, + _In_ PTOKEN_GROUPS Groups, + _In_ PTOKEN_PRIVILEGES Privileges, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION UserAttributes, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION DeviceAttributes, + _In_opt_ PTOKEN_GROUPS DeviceGroups, + _In_opt_ PTOKEN_MANDATORY_POLICY TokenMandatoryPolicy, + _In_opt_ PTOKEN_OWNER Owner, + _In_ PTOKEN_PRIMARY_GROUP PrimaryGroup, + _In_opt_ PTOKEN_DEFAULT_DACL DefaultDacl, + _In_ PTOKEN_SOURCE TokenSource + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenProcessTokenEx( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenThreadToken( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ BOOLEAN OpenAsSelf, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenThreadTokenEx( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ BOOLEAN OpenAsSelf, + _In_ ULONG HandleAttributes, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDuplicateToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ BOOLEAN EffectiveOnly, + _In_ TOKEN_TYPE TokenType, + _Out_ PHANDLE NewTokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationToken( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _Out_writes_bytes_(TokenInformationLength) PVOID TokenInformation, + _In_ ULONG TokenInformationLength, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationToken( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _In_reads_bytes_(TokenInformationLength) PVOID TokenInformation, + _In_ ULONG TokenInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAdjustPrivilegesToken( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN DisableAllPrivileges, + _In_opt_ PTOKEN_PRIVILEGES NewState, + _In_ ULONG BufferLength, + _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_PRIVILEGES PreviousState, + _Out_ _When_(PreviousState == NULL, _Out_opt_) PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAdjustGroupsToken( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN ResetToDefault, + _In_opt_ PTOKEN_GROUPS NewState, + _In_opt_ ULONG BufferLength, + _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_GROUPS PreviousState, + _Out_ PULONG ReturnLength + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAdjustTokenClaimsAndDeviceGroups( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN UserResetToDefault, + _In_ BOOLEAN DeviceResetToDefault, + _In_ BOOLEAN DeviceGroupsResetToDefault, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION NewUserState, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION NewDeviceState, + _In_opt_ PTOKEN_GROUPS NewDeviceGroupsState, + _In_ ULONG UserBufferLength, + _Out_writes_bytes_to_opt_(UserBufferLength, *UserReturnLength) PTOKEN_SECURITY_ATTRIBUTES_INFORMATION PreviousUserState, + _In_ ULONG DeviceBufferLength, + _Out_writes_bytes_to_opt_(DeviceBufferLength, *DeviceReturnLength) PTOKEN_SECURITY_ATTRIBUTES_INFORMATION PreviousDeviceState, + _In_ ULONG DeviceGroupsBufferLength, + _Out_writes_bytes_to_opt_(DeviceGroupsBufferLength, *DeviceGroupsReturnBufferLength) PTOKEN_GROUPS PreviousDeviceGroups, + _Out_opt_ PULONG UserReturnLength, + _Out_opt_ PULONG DeviceReturnLength, + _Out_opt_ PULONG DeviceGroupsReturnBufferLength + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFilterToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _Out_ PHANDLE NewTokenHandle + ); + +#if (PHNT_VERSION >= PHNT_WIN8) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFilterTokenEx( + _In_ HANDLE ExistingTokenHandle, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _In_ ULONG DisableUserClaimsCount, + _In_opt_ PUNICODE_STRING UserClaimsToDisable, + _In_ ULONG DisableDeviceClaimsCount, + _In_opt_ PUNICODE_STRING DeviceClaimsToDisable, + _In_opt_ PTOKEN_GROUPS DeviceGroupsToDisable, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION RestrictedUserAttributes, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION RestrictedDeviceAttributes, + _In_opt_ PTOKEN_GROUPS RestrictedDeviceGroups, + _Out_ PHANDLE NewTokenHandle + ); +#endif + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCompareTokens( + _In_ HANDLE FirstTokenHandle, + _In_ HANDLE SecondTokenHandle, + _Out_ PBOOLEAN Equal + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrivilegeCheck( + _In_ HANDLE ClientToken, + _Inout_ PPRIVILEGE_SET RequiredPrivileges, + _Out_ PBOOLEAN Result + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtImpersonateAnonymousToken( + _In_ HANDLE ThreadHandle + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQuerySecurityAttributesToken( + _In_ HANDLE TokenHandle, + _In_reads_opt_(NumberOfAttributes) PUNICODE_STRING Attributes, + _In_ ULONG NumberOfAttributes, + _Out_writes_bytes_(Length) PVOID Buffer, // PTOKEN_SECURITY_ATTRIBUTES_INFORMATION + _In_ ULONG Length, + _Out_ PULONG ReturnLength + ); +#endif + +// Access checking + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheck( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckByType( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_reads_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckByTypeResultList( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_reads_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus + ); + +// Signing + +#if (PHNT_VERSION >= PHNT_THRESHOLD) + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetCachedSigningLevel( + _In_ ULONG Flags, + _In_ SE_SIGNING_LEVEL InputSigningLevel, + _In_reads_(SourceFileCount) PHANDLE SourceFiles, + _In_ ULONG SourceFileCount, + _In_opt_ HANDLE TargetFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetCachedSigningLevel( + _In_ HANDLE File, + _Out_ PULONG Flags, + _Out_ PSE_SIGNING_LEVEL SigningLevel, + _Out_writes_bytes_to_opt_(*ThumbprintSize, *ThumbprintSize) PUCHAR Thumbprint, + _Inout_opt_ PULONG ThumbprintSize, + _Out_opt_ PULONG ThumbprintAlgorithm + ); + +#endif + +// Audit alarm + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ACCESS_MASK DesiredAccess, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckByTypeAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckByTypeResultListAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtAccessCheckByTypeResultListAndAuditAlarmByHandle( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ HANDLE ClientToken, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_opt_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ ACCESS_MASK GrantedAccess, + _In_opt_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN ObjectCreation, + _In_ BOOLEAN AccessGranted, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrivilegeObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN AccessGranted + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCloseObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ BOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtDeleteObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ BOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrivilegedServiceAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_ PUNICODE_STRING ServiceName, + _In_ HANDLE ClientToken, + _In_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN AccessGranted + ); + +// Misc. + +typedef enum _FILTER_BOOT_OPTION_OPERATION +{ + FilterBootOptionOperationOpenSystemStore, + FilterBootOptionOperationSetElement, + FilterBootOptionOperationDeleteElement, + FilterBootOptionOperationMax +} FILTER_BOOT_OPTION_OPERATION; + +#if (PHNT_VERSION >= PHNT_THRESHOLD) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFilterBootOption( + _In_ FILTER_BOOT_OPTION_OPERATION FilterOperation, + _In_ ULONG ObjectType, + _In_ ULONG ElementType, + _In_reads_bytes_opt_(DataSize) PVOID Data, + _In_ ULONG DataSize + ); +#endif + +#endif diff --git a/phnt/include/nttmapi.h b/phnt/include/nttmapi.h new file mode 100644 index 0000000..29d5333 --- /dev/null +++ b/phnt/include/nttmapi.h @@ -0,0 +1,473 @@ +#ifndef _NTTMAPI_H +#define _NTTMAPI_H + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateTransactionManager( + _Out_ PHANDLE TmHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PUNICODE_STRING LogFileName, + _In_opt_ ULONG CreateOptions, + _In_opt_ ULONG CommitStrength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenTransactionManager( + _Out_ PHANDLE TmHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PUNICODE_STRING LogFileName, + _In_opt_ LPGUID TmIdentity, + _In_opt_ ULONG OpenOptions + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRenameTransactionManager( + _In_ PUNICODE_STRING LogFileName, + _In_ LPGUID ExistingTransactionManagerGuid + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRollforwardTransactionManager( + _In_ HANDLE TransactionManagerHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRecoverTransactionManager( + _In_ HANDLE TransactionManagerHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationTransactionManager( + _In_ HANDLE TransactionManagerHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _Out_writes_bytes_(TransactionManagerInformationLength) PVOID TransactionManagerInformation, + _In_ ULONG TransactionManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationTransactionManager( + _In_opt_ HANDLE TmHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _In_reads_bytes_(TransactionManagerInformationLength) PVOID TransactionManagerInformation, + _In_ ULONG TransactionManagerInformationLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtEnumerateTransactionObject( + _In_opt_ HANDLE RootObjectHandle, + _In_ KTMOBJECT_TYPE QueryType, + _Inout_updates_bytes_(ObjectCursorLength) PKTMOBJECT_CURSOR ObjectCursor, + _In_ ULONG ObjectCursorLength, + _Out_ PULONG ReturnLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateTransaction( + _Out_ PHANDLE TransactionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ LPGUID Uow, + _In_opt_ HANDLE TmHandle, + _In_opt_ ULONG CreateOptions, + _In_opt_ ULONG IsolationLevel, + _In_opt_ ULONG IsolationFlags, + _In_opt_ PLARGE_INTEGER Timeout, + _In_opt_ PUNICODE_STRING Description + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenTransaction( + _Out_ PHANDLE TransactionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ LPGUID Uow, + _In_opt_ HANDLE TmHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationTransaction( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _Out_writes_bytes_(TransactionInformationLength) PVOID TransactionInformation, + _In_ ULONG TransactionInformationLength, + _Out_opt_ PULONG ReturnLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationTransaction( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _In_reads_bytes_(TransactionInformationLength) PVOID TransactionInformation, + _In_ ULONG TransactionInformationLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCommitTransaction( + _In_ HANDLE TransactionHandle, + _In_ BOOLEAN Wait + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRollbackTransaction( + _In_ HANDLE TransactionHandle, + _In_ BOOLEAN Wait + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateEnlistment( + _Out_ PHANDLE EnlistmentHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ResourceManagerHandle, + _In_ HANDLE TransactionHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG CreateOptions, + _In_ NOTIFICATION_MASK NotificationMask, + _In_opt_ PVOID EnlistmentKey + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenEnlistment( + _Out_ PHANDLE EnlistmentHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ResourceManagerHandle, + _In_ LPGUID EnlistmentGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_ ENLISTMENT_INFORMATION_CLASS EnlistmentInformationClass, + _Out_writes_bytes_(EnlistmentInformationLength) PVOID EnlistmentInformation, + _In_ ULONG EnlistmentInformationLength, + _Out_opt_ PULONG ReturnLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationEnlistment( + _In_opt_ HANDLE EnlistmentHandle, + _In_ ENLISTMENT_INFORMATION_CLASS EnlistmentInformationClass, + _In_reads_bytes_(EnlistmentInformationLength) PVOID EnlistmentInformation, + _In_ ULONG EnlistmentInformationLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRecoverEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PVOID EnlistmentKey + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrePrepareEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrepareEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCommitEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRollbackEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrePrepareComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPrepareComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCommitComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtReadOnlyEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRollbackComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSinglePhaseReject( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtCreateResourceManager( + _Out_ PHANDLE ResourceManagerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE TmHandle, + _In_ LPGUID RmGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG CreateOptions, + _In_opt_ PUNICODE_STRING Description + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtOpenResourceManager( + _Out_ PHANDLE ResourceManagerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE TmHandle, + _In_opt_ LPGUID ResourceManagerGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRecoverResourceManager( + _In_ HANDLE ResourceManagerHandle + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtGetNotificationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _Out_ PTRANSACTION_NOTIFICATION TransactionNotification, + _In_ ULONG NotificationLength, + _In_opt_ PLARGE_INTEGER Timeout, + _Out_opt_ PULONG ReturnLength, + _In_ ULONG Asynchronous, + _In_opt_ ULONG_PTR AsynchronousContext + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtQueryInformationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _Out_writes_bytes_(ResourceManagerInformationLength) PVOID ResourceManagerInformation, + _In_ ULONG ResourceManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtSetInformationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _In_reads_bytes_(ResourceManagerInformationLength) PVOID ResourceManagerInformation, + _In_ ULONG ResourceManagerInformationLength + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRegisterProtocolAddressInformation( + _In_ HANDLE ResourceManager, + _In_ PCRM_PROTOCOL_ID ProtocolId, + _In_ ULONG ProtocolInformationSize, + _In_ PVOID ProtocolInformation, + _In_opt_ ULONG CreateOptions + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPropagationComplete( + _In_ HANDLE ResourceManagerHandle, + _In_ ULONG RequestCookie, + _In_ ULONG BufferLength, + _In_ PVOID Buffer + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +NTSYSCALLAPI +NTSTATUS +NTAPI +NtPropagationFailed( + _In_ HANDLE ResourceManagerHandle, + _In_ ULONG RequestCookie, + _In_ NTSTATUS PropStatus + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtFreezeTransactions( + _In_ PLARGE_INTEGER FreezeTimeout, + _In_ PLARGE_INTEGER ThawTimeout + ); +#endif + +#if (PHNT_VERSION >= PHNT_VISTA) +// private +NTSYSCALLAPI +NTSTATUS +NTAPI +NtThawTransactions( + VOID + ); +#endif + +#endif diff --git a/phnt/include/nttp.h b/phnt/include/nttp.h new file mode 100644 index 0000000..09edc59 --- /dev/null +++ b/phnt/include/nttp.h @@ -0,0 +1,430 @@ +#ifndef _NTTP_H +#define _NTTP_H + +// Some types are already defined in winnt.h. + +typedef struct _TP_ALPC TP_ALPC, *PTP_ALPC; + +// private +typedef VOID (NTAPI *PTP_ALPC_CALLBACK)( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _Inout_opt_ PVOID Context, + _In_ PTP_ALPC Alpc + ); + +// rev +typedef VOID (NTAPI *PTP_ALPC_CALLBACK_EX)( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _Inout_opt_ PVOID Context, + _In_ PTP_ALPC Alpc, + _In_ PVOID ApcContext + ); + +#if (PHNT_VERSION >= PHNT_VISTA) + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocPool( + _Out_ PTP_POOL *PoolReturn, + _Reserved_ PVOID Reserved + ); + +// winbase:CloseThreadpool +NTSYSAPI +VOID +NTAPI +TpReleasePool( + _Inout_ PTP_POOL Pool + ); + +// winbase:SetThreadpoolThreadMaximum +NTSYSAPI +VOID +NTAPI +TpSetPoolMaxThreads( + _Inout_ PTP_POOL Pool, + _In_ LONG MaxThreads + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +TpSetPoolMinThreads( + _Inout_ PTP_POOL Pool, + _In_ LONG MinThreads + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +TpQueryPoolStackInformation( + _In_ PTP_POOL Pool, + _Out_ PTP_POOL_STACK_INFORMATION PoolStackInformation + ); +#endif + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +TpSetPoolStackInformation( + _Inout_ PTP_POOL Pool, + _In_ PTP_POOL_STACK_INFORMATION PoolStackInformation + ); +#endif + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocCleanupGroup( + _Out_ PTP_CLEANUP_GROUP *CleanupGroupReturn + ); + +// winbase:CloseThreadpoolCleanupGroup +NTSYSAPI +VOID +NTAPI +TpReleaseCleanupGroup( + _Inout_ PTP_CLEANUP_GROUP CleanupGroup + ); + +// winbase:CloseThreadpoolCleanupGroupMembers +NTSYSAPI +VOID +NTAPI +TpReleaseCleanupGroupMembers( + _Inout_ PTP_CLEANUP_GROUP CleanupGroup, + _In_ LOGICAL CancelPendingCallbacks, + _Inout_opt_ PVOID CleanupParameter + ); + +// winbase:SetEventWhenCallbackReturns +NTSYSAPI +VOID +NTAPI +TpCallbackSetEventOnCompletion( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _In_ HANDLE Event + ); + +// winbase:ReleaseSemaphoreWhenCallbackReturns +NTSYSAPI +VOID +NTAPI +TpCallbackReleaseSemaphoreOnCompletion( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _In_ HANDLE Semaphore, + _In_ LONG ReleaseCount + ); + +// winbase:ReleaseMutexWhenCallbackReturns +NTSYSAPI +VOID +NTAPI +TpCallbackReleaseMutexOnCompletion( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _In_ HANDLE Mutex + ); + +// winbase:LeaveCriticalSectionWhenCallbackReturns +NTSYSAPI +VOID +NTAPI +TpCallbackLeaveCriticalSectionOnCompletion( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _Inout_ PRTL_CRITICAL_SECTION CriticalSection + ); + +// winbase:FreeLibraryWhenCallbackReturns +NTSYSAPI +VOID +NTAPI +TpCallbackUnloadDllOnCompletion( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID DllHandle + ); + +// winbase:CallbackMayRunLong +NTSYSAPI +NTSTATUS +NTAPI +TpCallbackMayRunLong( + _Inout_ PTP_CALLBACK_INSTANCE Instance + ); + +// winbase:DisassociateCurrentThreadFromCallback +NTSYSAPI +VOID +NTAPI +TpDisassociateCallback( + _Inout_ PTP_CALLBACK_INSTANCE Instance + ); + +// winbase:TrySubmitThreadpoolCallback +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpSimpleTryPost( + _In_ PTP_SIMPLE_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocWork( + _Out_ PTP_WORK *WorkReturn, + _In_ PTP_WORK_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +// winbase:CloseThreadpoolWork +NTSYSAPI +VOID +NTAPI +TpReleaseWork( + _Inout_ PTP_WORK Work + ); + +// winbase:SubmitThreadpoolWork +NTSYSAPI +VOID +NTAPI +TpPostWork( + _Inout_ PTP_WORK Work + ); + +// winbase:WaitForThreadpoolWorkCallbacks +NTSYSAPI +VOID +NTAPI +TpWaitForWork( + _Inout_ PTP_WORK Work, + _In_ LOGICAL CancelPendingCallbacks + ); + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocTimer( + _Out_ PTP_TIMER *Timer, + _In_ PTP_TIMER_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +// winbase:CloseThreadpoolTimer +NTSYSAPI +VOID +NTAPI +TpReleaseTimer( + _Inout_ PTP_TIMER Timer + ); + +// winbase:SetThreadpoolTimer +NTSYSAPI +VOID +NTAPI +TpSetTimer( + _Inout_ PTP_TIMER Timer, + _In_opt_ PLARGE_INTEGER DueTime, + _In_ LONG Period, + _In_opt_ LONG WindowLength + ); + +// winbase:IsThreadpoolTimerSet +NTSYSAPI +LOGICAL +NTAPI +TpIsTimerSet( + _In_ PTP_TIMER Timer + ); + +// winbase:WaitForThreadpoolTimerCallbacks +NTSYSAPI +VOID +NTAPI +TpWaitForTimer( + _Inout_ PTP_TIMER Timer, + _In_ LOGICAL CancelPendingCallbacks + ); + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocWait( + _Out_ PTP_WAIT *WaitReturn, + _In_ PTP_WAIT_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +// winbase:CloseThreadpoolWait +NTSYSAPI +VOID +NTAPI +TpReleaseWait( + _Inout_ PTP_WAIT Wait + ); + +// winbase:SetThreadpoolWait +NTSYSAPI +VOID +NTAPI +TpSetWait( + _Inout_ PTP_WAIT Wait, + _In_opt_ HANDLE Handle, + _In_opt_ PLARGE_INTEGER Timeout + ); + +// winbase:WaitForThreadpoolWaitCallbacks +NTSYSAPI +VOID +NTAPI +TpWaitForWait( + _Inout_ PTP_WAIT Wait, + _In_ LOGICAL CancelPendingCallbacks + ); + +// private +typedef VOID (NTAPI *PTP_IO_CALLBACK)( + _Inout_ PTP_CALLBACK_INSTANCE Instance, + _Inout_opt_ PVOID Context, + _In_ PVOID ApcContext, + _In_ PIO_STATUS_BLOCK IoSB, + _In_ PTP_IO Io + ); + +// private +_Check_return_ +NTSYSAPI +NTSTATUS +NTAPI +TpAllocIoCompletion( + _Out_ PTP_IO *IoReturn, + _In_ HANDLE File, + _In_ PTP_IO_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +// winbase:CloseThreadpoolIo +NTSYSAPI +VOID +NTAPI +TpReleaseIoCompletion( + _Inout_ PTP_IO Io + ); + +// winbase:StartThreadpoolIo +NTSYSAPI +VOID +NTAPI +TpStartAsyncIoOperation( + _Inout_ PTP_IO Io + ); + +// winbase:CancelThreadpoolIo +NTSYSAPI +VOID +NTAPI +TpCancelAsyncIoOperation( + _Inout_ PTP_IO Io + ); + +// winbase:WaitForThreadpoolIoCallbacks +NTSYSAPI +VOID +NTAPI +TpWaitForIoCompletion( + _Inout_ PTP_IO Io, + _In_ LOGICAL CancelPendingCallbacks + ); + +// private +NTSYSAPI +NTSTATUS +NTAPI +TpAllocAlpcCompletion( + _Out_ PTP_ALPC *AlpcReturn, + _In_ HANDLE AlpcPort, + _In_ PTP_ALPC_CALLBACK Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); + +#if (PHNT_VERSION >= PHNT_WIN7) +// rev +NTSYSAPI +NTSTATUS +NTAPI +TpAllocAlpcCompletionEx( + _Out_ PTP_ALPC *AlpcReturn, + _In_ HANDLE AlpcPort, + _In_ PTP_ALPC_CALLBACK_EX Callback, + _Inout_opt_ PVOID Context, + _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron + ); +#endif + +// private +NTSYSAPI +VOID +NTAPI +TpReleaseAlpcCompletion( + _Inout_ PTP_ALPC Alpc + ); + +// private +NTSYSAPI +VOID +NTAPI +TpWaitForAlpcCompletion( + _Inout_ PTP_ALPC Alpc + ); + +// private +typedef enum _TP_TRACE_TYPE +{ + TpTraceThreadPriority = 1, + TpTraceThreadAffinity, + MaxTpTraceType +} TP_TRACE_TYPE; + +// private +NTSYSAPI +VOID +NTAPI +TpCaptureCaller( + _In_ TP_TRACE_TYPE Type + ); + +// private +NTSYSAPI +VOID +NTAPI +TpCheckTerminateWorker( + _In_ HANDLE Thread + ); + +#endif + +#endif diff --git a/phnt/include/ntwow64.h b/phnt/include/ntwow64.h new file mode 100644 index 0000000..3ee04cd --- /dev/null +++ b/phnt/include/ntwow64.h @@ -0,0 +1,445 @@ +#ifndef _NTWOW64_H +#define _NTWOW64_H + +#define WOW64_SYSTEM_DIRECTORY "SysWOW64" +#define WOW64_SYSTEM_DIRECTORY_U L"SysWOW64" +#define WOW64_X86_TAG " (x86)" +#define WOW64_X86_TAG_U L" (x86)" + +// In USER_SHARED_DATA +typedef enum _WOW64_SHARED_INFORMATION +{ + SharedNtdll32LdrInitializeThunk, + SharedNtdll32KiUserExceptionDispatcher, + SharedNtdll32KiUserApcDispatcher, + SharedNtdll32KiUserCallbackDispatcher, + SharedNtdll32ExpInterlockedPopEntrySListFault, + SharedNtdll32ExpInterlockedPopEntrySListResume, + SharedNtdll32ExpInterlockedPopEntrySListEnd, + SharedNtdll32RtlUserThreadStart, + SharedNtdll32pQueryProcessDebugInformationRemote, + SharedNtdll32BaseAddress, + SharedNtdll32LdrSystemDllInitBlock, + Wow64SharedPageEntriesCount +} WOW64_SHARED_INFORMATION; + +// 32-bit definitions + +#define WOW64_POINTER(Type) ULONG + +typedef struct _RTL_BALANCED_NODE32 +{ + union + { + WOW64_POINTER(struct _RTL_BALANCED_NODE *) Children[2]; + struct + { + WOW64_POINTER(struct _RTL_BALANCED_NODE *) Left; + WOW64_POINTER(struct _RTL_BALANCED_NODE *) Right; + }; + }; + union + { + WOW64_POINTER(UCHAR) Red : 1; + WOW64_POINTER(UCHAR) Balance : 2; + WOW64_POINTER(ULONG_PTR) ParentValue; + }; +} RTL_BALANCED_NODE32, *PRTL_BALANCED_NODE32; + +typedef struct _RTL_RB_TREE32 +{ + WOW64_POINTER(PRTL_BALANCED_NODE) Root; + WOW64_POINTER(PRTL_BALANCED_NODE) Min; +} RTL_RB_TREE32, *PRTL_RB_TREE32; + +typedef struct _PEB_LDR_DATA32 +{ + ULONG Length; + BOOLEAN Initialized; + WOW64_POINTER(HANDLE) SsHandle; + LIST_ENTRY32 InLoadOrderModuleList; + LIST_ENTRY32 InMemoryOrderModuleList; + LIST_ENTRY32 InInitializationOrderModuleList; + WOW64_POINTER(PVOID) EntryInProgress; + BOOLEAN ShutdownInProgress; + WOW64_POINTER(HANDLE) ShutdownThreadId; +} PEB_LDR_DATA32, *PPEB_LDR_DATA32; + +typedef struct _LDR_SERVICE_TAG_RECORD32 +{ + WOW64_POINTER(struct _LDR_SERVICE_TAG_RECORD *) Next; + ULONG ServiceTag; +} LDR_SERVICE_TAG_RECORD32, *PLDR_SERVICE_TAG_RECORD32; + +typedef struct _LDRP_CSLIST32 +{ + WOW64_POINTER(PSINGLE_LIST_ENTRY) Tail; +} LDRP_CSLIST32, *PLDRP_CSLIST32; + +typedef struct _LDR_DDAG_NODE32 +{ + LIST_ENTRY32 Modules; + WOW64_POINTER(PLDR_SERVICE_TAG_RECORD) ServiceTagList; + ULONG LoadCount; + ULONG ReferenceCount; + ULONG DependencyCount; + union + { + LDRP_CSLIST32 Dependencies; + SINGLE_LIST_ENTRY32 RemovalLink; + }; + LDRP_CSLIST32 IncomingDependencies; + LDR_DDAG_STATE State; + SINGLE_LIST_ENTRY32 CondenseLink; + ULONG PreorderNumber; + ULONG LowestLink; +} LDR_DDAG_NODE32, *PLDR_DDAG_NODE32; + +#define LDR_DATA_TABLE_ENTRY_SIZE_WINXP_32 FIELD_OFFSET(LDR_DATA_TABLE_ENTRY32, DdagNode) +#define LDR_DATA_TABLE_ENTRY_SIZE_WIN7_32 FIELD_OFFSET(LDR_DATA_TABLE_ENTRY32, BaseNameHashValue) +#define LDR_DATA_TABLE_ENTRY_SIZE_WIN8_32 FIELD_OFFSET(LDR_DATA_TABLE_ENTRY32, ImplicitPathOptions) + +typedef struct _LDR_DATA_TABLE_ENTRY32 +{ + LIST_ENTRY32 InLoadOrderLinks; + LIST_ENTRY32 InMemoryOrderLinks; + union + { + LIST_ENTRY32 InInitializationOrderLinks; + LIST_ENTRY32 InProgressLinks; + }; + WOW64_POINTER(PVOID) DllBase; + WOW64_POINTER(PVOID) EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING32 FullDllName; + UNICODE_STRING32 BaseDllName; + union + { + UCHAR FlagGroup[4]; + ULONG Flags; + struct + { + ULONG PackagedBinary : 1; + ULONG MarkedForRemoval : 1; + ULONG ImageDll : 1; + ULONG LoadNotificationsSent : 1; + ULONG TelemetryEntryProcessed : 1; + ULONG ProcessStaticImport : 1; + ULONG InLegacyLists : 1; + ULONG InIndexes : 1; + ULONG ShimDll : 1; + ULONG InExceptionTable : 1; + ULONG ReservedFlags1 : 2; + ULONG LoadInProgress : 1; + ULONG LoadConfigProcessed : 1; + ULONG EntryProcessed : 1; + ULONG ProtectDelayLoad : 1; + ULONG ReservedFlags3 : 2; + ULONG DontCallForThreads : 1; + ULONG ProcessAttachCalled : 1; + ULONG ProcessAttachFailed : 1; + ULONG CorDeferredValidate : 1; + ULONG CorImage : 1; + ULONG DontRelocate : 1; + ULONG CorILOnly : 1; + ULONG ReservedFlags5 : 3; + ULONG Redirected : 1; + ULONG ReservedFlags6 : 2; + ULONG CompatDatabaseProcessed : 1; + }; + }; + USHORT ObsoleteLoadCount; + USHORT TlsIndex; + LIST_ENTRY32 HashLinks; + ULONG TimeDateStamp; + WOW64_POINTER(struct _ACTIVATION_CONTEXT *) EntryPointActivationContext; + WOW64_POINTER(PVOID) Lock; + WOW64_POINTER(PLDR_DDAG_NODE) DdagNode; + LIST_ENTRY32 NodeModuleLink; + WOW64_POINTER(struct _LDRP_LOAD_CONTEXT *) LoadContext; + WOW64_POINTER(PVOID) ParentDllBase; + WOW64_POINTER(PVOID) SwitchBackContext; + RTL_BALANCED_NODE32 BaseAddressIndexNode; + RTL_BALANCED_NODE32 MappingInfoIndexNode; + WOW64_POINTER(ULONG_PTR) OriginalBase; + LARGE_INTEGER LoadTime; + ULONG BaseNameHashValue; + LDR_DLL_LOAD_REASON LoadReason; + ULONG ImplicitPathOptions; + ULONG ReferenceCount; +} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32; + +typedef struct _CURDIR32 +{ + UNICODE_STRING32 DosPath; + WOW64_POINTER(HANDLE) Handle; +} CURDIR32, *PCURDIR32; + +typedef struct _RTL_DRIVE_LETTER_CURDIR32 +{ + USHORT Flags; + USHORT Length; + ULONG TimeStamp; + STRING32 DosPath; +} RTL_DRIVE_LETTER_CURDIR32, *PRTL_DRIVE_LETTER_CURDIR32; + +typedef struct _RTL_USER_PROCESS_PARAMETERS32 +{ + ULONG MaximumLength; + ULONG Length; + + ULONG Flags; + ULONG DebugFlags; + + WOW64_POINTER(HANDLE) ConsoleHandle; + ULONG ConsoleFlags; + WOW64_POINTER(HANDLE) StandardInput; + WOW64_POINTER(HANDLE) StandardOutput; + WOW64_POINTER(HANDLE) StandardError; + + CURDIR32 CurrentDirectory; + UNICODE_STRING32 DllPath; + UNICODE_STRING32 ImagePathName; + UNICODE_STRING32 CommandLine; + WOW64_POINTER(PVOID) Environment; + + ULONG StartingX; + ULONG StartingY; + ULONG CountX; + ULONG CountY; + ULONG CountCharsX; + ULONG CountCharsY; + ULONG FillAttribute; + + ULONG WindowFlags; + ULONG ShowWindowFlags; + UNICODE_STRING32 WindowTitle; + UNICODE_STRING32 DesktopInfo; + UNICODE_STRING32 ShellInfo; + UNICODE_STRING32 RuntimeData; + RTL_DRIVE_LETTER_CURDIR32 CurrentDirectories[RTL_MAX_DRIVE_LETTERS]; + + ULONG EnvironmentSize; + ULONG EnvironmentVersion; + WOW64_POINTER(PVOID) PackageDependencyData; + ULONG ProcessGroupId; + ULONG LoaderThreads; +} RTL_USER_PROCESS_PARAMETERS32, *PRTL_USER_PROCESS_PARAMETERS32; + +typedef struct _PEB32 +{ + BOOLEAN InheritedAddressSpace; + BOOLEAN ReadImageFileExecOptions; + BOOLEAN BeingDebugged; + union + { + BOOLEAN BitField; + struct + { + BOOLEAN ImageUsesLargePages : 1; + BOOLEAN IsProtectedProcess : 1; + BOOLEAN IsLegacyProcess : 1; + BOOLEAN IsImageDynamicallyRelocated : 1; + BOOLEAN SkipPatchingUser32Forwarders : 1; + BOOLEAN IsPackagedProcess : 1; + BOOLEAN IsAppContainer : 1; + BOOLEAN SpareBits : 1; + }; + }; + WOW64_POINTER(HANDLE) Mutant; + + WOW64_POINTER(PVOID) ImageBaseAddress; + WOW64_POINTER(PPEB_LDR_DATA) Ldr; + WOW64_POINTER(PRTL_USER_PROCESS_PARAMETERS) ProcessParameters; + WOW64_POINTER(PVOID) SubSystemData; + WOW64_POINTER(PVOID) ProcessHeap; + WOW64_POINTER(PRTL_CRITICAL_SECTION) FastPebLock; + WOW64_POINTER(PVOID) AtlThunkSListPtr; + WOW64_POINTER(PVOID) IFEOKey; + union + { + ULONG CrossProcessFlags; + struct + { + ULONG ProcessInJob : 1; + ULONG ProcessInitializing : 1; + ULONG ProcessUsingVEH : 1; + ULONG ProcessUsingVCH : 1; + ULONG ProcessUsingFTH : 1; + ULONG ReservedBits0 : 27; + }; + }; + union + { + WOW64_POINTER(PVOID) KernelCallbackTable; + WOW64_POINTER(PVOID) UserSharedInfoPtr; + }; + ULONG SystemReserved[1]; + ULONG AtlThunkSListPtr32; + WOW64_POINTER(PVOID) ApiSetMap; + ULONG TlsExpansionCounter; + WOW64_POINTER(PVOID) TlsBitmap; + ULONG TlsBitmapBits[2]; + WOW64_POINTER(PVOID) ReadOnlySharedMemoryBase; + WOW64_POINTER(PVOID) HotpatchInformation; + WOW64_POINTER(PVOID *) ReadOnlyStaticServerData; + WOW64_POINTER(PVOID) AnsiCodePageData; + WOW64_POINTER(PVOID) OemCodePageData; + WOW64_POINTER(PVOID) UnicodeCaseTableData; + + ULONG NumberOfProcessors; + ULONG NtGlobalFlag; + + LARGE_INTEGER CriticalSectionTimeout; + WOW64_POINTER(SIZE_T) HeapSegmentReserve; + WOW64_POINTER(SIZE_T) HeapSegmentCommit; + WOW64_POINTER(SIZE_T) HeapDeCommitTotalFreeThreshold; + WOW64_POINTER(SIZE_T) HeapDeCommitFreeBlockThreshold; + + ULONG NumberOfHeaps; + ULONG MaximumNumberOfHeaps; + WOW64_POINTER(PVOID *) ProcessHeaps; + + WOW64_POINTER(PVOID) GdiSharedHandleTable; + WOW64_POINTER(PVOID) ProcessStarterHelper; + ULONG GdiDCAttributeList; + + WOW64_POINTER(PRTL_CRITICAL_SECTION) LoaderLock; + + ULONG OSMajorVersion; + ULONG OSMinorVersion; + USHORT OSBuildNumber; + USHORT OSCSDVersion; + ULONG OSPlatformId; + ULONG ImageSubsystem; + ULONG ImageSubsystemMajorVersion; + ULONG ImageSubsystemMinorVersion; + WOW64_POINTER(ULONG_PTR) ImageProcessAffinityMask; + GDI_HANDLE_BUFFER32 GdiHandleBuffer; + WOW64_POINTER(PVOID) PostProcessInitRoutine; + + WOW64_POINTER(PVOID) TlsExpansionBitmap; + ULONG TlsExpansionBitmapBits[32]; + + ULONG SessionId; + + ULARGE_INTEGER AppCompatFlags; + ULARGE_INTEGER AppCompatFlagsUser; + WOW64_POINTER(PVOID) pShimData; + WOW64_POINTER(PVOID) AppCompatInfo; + + UNICODE_STRING32 CSDVersion; + + WOW64_POINTER(PVOID) ActivationContextData; + WOW64_POINTER(PVOID) ProcessAssemblyStorageMap; + WOW64_POINTER(PVOID) SystemDefaultActivationContextData; + WOW64_POINTER(PVOID) SystemAssemblyStorageMap; + + WOW64_POINTER(SIZE_T) MinimumStackCommit; + + WOW64_POINTER(PVOID *) FlsCallback; + LIST_ENTRY32 FlsListHead; + WOW64_POINTER(PVOID) FlsBitmap; + ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)]; + ULONG FlsHighIndex; + + WOW64_POINTER(PVOID) WerRegistrationData; + WOW64_POINTER(PVOID) WerShipAssertPtr; + WOW64_POINTER(PVOID) pContextData; + WOW64_POINTER(PVOID) pImageHeaderHash; + union + { + ULONG TracingFlags; + struct + { + ULONG HeapTracingEnabled : 1; + ULONG CritSecTracingEnabled : 1; + ULONG LibLoaderTracingEnabled : 1; + ULONG SpareTracingBits : 29; + }; + }; + ULONGLONG CsrServerReadOnlySharedMemoryBase; +} PEB32, *PPEB32; + +#define GDI_BATCH_BUFFER_SIZE 310 + +typedef struct _GDI_TEB_BATCH32 +{ + ULONG Offset; + WOW64_POINTER(ULONG_PTR) HDC; + ULONG Buffer[GDI_BATCH_BUFFER_SIZE]; +} GDI_TEB_BATCH32, *PGDI_TEB_BATCH32; + +typedef struct _TEB32 +{ + NT_TIB32 NtTib; + + WOW64_POINTER(PVOID) EnvironmentPointer; + CLIENT_ID32 ClientId; + WOW64_POINTER(PVOID) ActiveRpcHandle; + WOW64_POINTER(PVOID) ThreadLocalStoragePointer; + WOW64_POINTER(PPEB) ProcessEnvironmentBlock; + + ULONG LastErrorValue; + ULONG CountOfOwnedCriticalSections; + WOW64_POINTER(PVOID) CsrClientThread; + WOW64_POINTER(PVOID) Win32ThreadInfo; + ULONG User32Reserved[26]; + ULONG UserReserved[5]; + WOW64_POINTER(PVOID) WOW32Reserved; + LCID CurrentLocale; + ULONG FpSoftwareStatusRegister; + WOW64_POINTER(PVOID) SystemReserved1[54]; + NTSTATUS ExceptionCode; + WOW64_POINTER(PVOID) ActivationContextStackPointer; + BYTE SpareBytes[36]; + ULONG TxFsContext; + + GDI_TEB_BATCH32 GdiTebBatch; + CLIENT_ID32 RealClientId; + WOW64_POINTER(HANDLE) GdiCachedProcessHandle; + ULONG GdiClientPID; + ULONG GdiClientTID; + WOW64_POINTER(PVOID) GdiThreadLocalInfo; + WOW64_POINTER(ULONG_PTR) Win32ClientInfo[62]; + WOW64_POINTER(PVOID) glDispatchTable[233]; + WOW64_POINTER(ULONG_PTR) glReserved1[29]; + WOW64_POINTER(PVOID) glReserved2; + WOW64_POINTER(PVOID) glSectionInfo; + WOW64_POINTER(PVOID) glSection; + WOW64_POINTER(PVOID) glTable; + WOW64_POINTER(PVOID) glCurrentRC; + WOW64_POINTER(PVOID) glContext; + + NTSTATUS LastStatusValue; + UNICODE_STRING32 StaticUnicodeString; + WCHAR StaticUnicodeBuffer[261]; + + WOW64_POINTER(PVOID) DeallocationStack; + WOW64_POINTER(PVOID) TlsSlots[64]; + LIST_ENTRY32 TlsLinks; +} TEB32, *PTEB32; + +// Conversion + +FORCEINLINE VOID UStr32ToUStr( + _Out_ PUNICODE_STRING Destination, + _In_ PUNICODE_STRING32 Source + ) +{ + Destination->Length = Source->Length; + Destination->MaximumLength = Source->MaximumLength; + Destination->Buffer = (PWCH)UlongToPtr(Source->Buffer); +} + +FORCEINLINE VOID UStrToUStr32( + _Out_ PUNICODE_STRING32 Destination, + _In_ PUNICODE_STRING Source + ) +{ + Destination->Length = Source->Length; + Destination->MaximumLength = Source->MaximumLength; + Destination->Buffer = PtrToUlong(Source->Buffer); +} + +#endif diff --git a/phnt/include/ntxcapi.h b/phnt/include/ntxcapi.h new file mode 100644 index 0000000..29cfc73 --- /dev/null +++ b/phnt/include/ntxcapi.h @@ -0,0 +1,44 @@ +#ifndef _NTXCAPI_H +#define _NTXCAPI_H + +NTSYSAPI +BOOLEAN +NTAPI +RtlDispatchException( + _In_ PEXCEPTION_RECORD ExceptionRecord, + _In_ PCONTEXT ContextRecord + ); + +NTSYSAPI +DECLSPEC_NORETURN +VOID +NTAPI +RtlRaiseStatus( + _In_ NTSTATUS Status + ); + +NTSYSAPI +VOID +NTAPI +RtlRaiseException( + _In_ PEXCEPTION_RECORD ExceptionRecord + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtContinue( + _In_ PCONTEXT ContextRecord, + _In_ BOOLEAN TestAlert + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +NtRaiseException( + _In_ PEXCEPTION_RECORD ExceptionRecord, + _In_ PCONTEXT ContextRecord, + _In_ BOOLEAN FirstChance + ); + +#endif diff --git a/phnt/include/ntzwapi.h b/phnt/include/ntzwapi.h new file mode 100644 index 0000000..3fa1f2d --- /dev/null +++ b/phnt/include/ntzwapi.h @@ -0,0 +1,4442 @@ +#ifndef _NTZWAPI_H +#define _NTZWAPI_H + +// This file was automatically generated. Do not edit. + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAcceptConnectPort( + _Out_ PHANDLE PortHandle, + _In_opt_ PVOID PortContext, + _In_ PPORT_MESSAGE ConnectionRequest, + _In_ BOOLEAN AcceptConnection, + _Inout_opt_ PPORT_VIEW ServerView, + _Out_opt_ PREMOTE_PORT_VIEW ClientView + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheck( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ACCESS_MASK DesiredAccess, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckByType( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_reads_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckByTypeAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_ PACCESS_MASK GrantedAccess, + _Out_ PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckByTypeResultList( + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_reads_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet, + _Inout_ PULONG PrivilegeSetLength, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckByTypeResultListAndAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAccessCheckByTypeResultListAndAuditAlarmByHandle( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ HANDLE ClientToken, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_opt_ PSID PrincipalSelfSid, + _In_ ACCESS_MASK DesiredAccess, + _In_ AUDIT_EVENT_TYPE AuditType, + _In_ ULONG Flags, + _In_reads_opt_(ObjectTypeListLength) POBJECT_TYPE_LIST ObjectTypeList, + _In_ ULONG ObjectTypeListLength, + _In_ PGENERIC_MAPPING GenericMapping, + _In_ BOOLEAN ObjectCreation, + _Out_writes_(ObjectTypeListLength) PACCESS_MASK GrantedAccess, + _Out_writes_(ObjectTypeListLength) PNTSTATUS AccessStatus, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAcquireCMFViewOwnership( + _Out_ PULONGLONG TimeStamp, + _Out_ PBOOLEAN tokenTaken, + _In_ BOOLEAN replaceExisting + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAddAtom( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAddAtomEx( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAddBootEntry( + _In_ PBOOT_ENTRY BootEntry, + _Out_opt_ PULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAddDriverEntry( + _In_ PEFI_DRIVER_ENTRY DriverEntry, + _Out_opt_ PULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAdjustGroupsToken( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN ResetToDefault, + _In_opt_ PTOKEN_GROUPS NewState, + _In_opt_ ULONG BufferLength, + _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_GROUPS PreviousState, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAdjustPrivilegesToken( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN DisableAllPrivileges, + _In_opt_ PTOKEN_PRIVILEGES NewState, + _In_ ULONG BufferLength, + _Out_writes_bytes_to_opt_(BufferLength, *ReturnLength) PTOKEN_PRIVILEGES PreviousState, + _Out_ _When_(PreviousState == NULL, _Out_opt_) PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAdjustTokenClaimsAndDeviceGroups( + _In_ HANDLE TokenHandle, + _In_ BOOLEAN UserResetToDefault, + _In_ BOOLEAN DeviceResetToDefault, + _In_ BOOLEAN DeviceGroupsResetToDefault, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION NewUserState, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION NewDeviceState, + _In_opt_ PTOKEN_GROUPS NewDeviceGroupsState, + _In_ ULONG UserBufferLength, + _Out_writes_bytes_to_opt_(UserBufferLength, *UserReturnLength) PTOKEN_SECURITY_ATTRIBUTES_INFORMATION PreviousUserState, + _In_ ULONG DeviceBufferLength, + _Out_writes_bytes_to_opt_(DeviceBufferLength, *DeviceReturnLength) PTOKEN_SECURITY_ATTRIBUTES_INFORMATION PreviousDeviceState, + _In_ ULONG DeviceGroupsBufferLength, + _Out_writes_bytes_to_opt_(DeviceGroupsBufferLength, *DeviceGroupsReturnBufferLength) PTOKEN_GROUPS PreviousDeviceGroups, + _Out_opt_ PULONG UserReturnLength, + _Out_opt_ PULONG DeviceReturnLength, + _Out_opt_ PULONG DeviceGroupsReturnBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlertResumeThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlertThread( + _In_ HANDLE ThreadHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlertThreadByThreadId( + _In_ HANDLE ThreadId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAllocateLocallyUniqueId( + _Out_ PLUID Luid + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAllocateReserveObject( + _Out_ PHANDLE MemoryReserveHandle, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ MEMORY_RESERVE_TYPE Type + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAllocateUserPhysicalPages( + _In_ HANDLE ProcessHandle, + _Inout_ PULONG_PTR NumberOfPages, + _Out_writes_(*NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAllocateUuids( + _Out_ PULARGE_INTEGER Time, + _Out_ PULONG Range, + _Out_ PULONG Sequence, + _Out_ PCHAR Seed + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAllocateVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ _At_(*BaseAddress, _Readable_bytes_(*RegionSize) _Writable_bytes_(*RegionSize) _Post_readable_byte_size_(*RegionSize)) PVOID *BaseAddress, + _In_ ULONG_PTR ZeroBits, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG AllocationType, + _In_ ULONG Protect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcAcceptConnectPort( + _Out_ PHANDLE PortHandle, + _In_ HANDLE ConnectionPortHandle, + _In_ ULONG Flags, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_opt_ PVOID PortContext, + _In_reads_bytes_(ConnectionRequest->u1.s1.TotalLength) PPORT_MESSAGE ConnectionRequest, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES ConnectionMessageAttributes, + _In_ BOOLEAN AcceptConnection + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCancelMessage( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_ PALPC_CONTEXT_ATTR MessageContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_ ULONG Flags, + _In_opt_ PSID RequiredServerSid, + _Inout_updates_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ConnectionMessage, + _Inout_opt_ PULONG BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES OutMessageAttributes, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES InMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcConnectPortEx( + _Out_ PHANDLE PortHandle, + _In_ POBJECT_ATTRIBUTES ConnectionPortObjectAttributes, + _In_opt_ POBJECT_ATTRIBUTES ClientPortObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, + _In_ ULONG Flags, + _In_opt_ PSECURITY_DESCRIPTOR ServerSecurityRequirements, + _Inout_updates_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ConnectionMessage, + _Inout_opt_ PSIZE_T BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES OutMessageAttributes, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES InMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCreatePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCreatePortSection( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_opt_ HANDLE SectionHandle, + _In_ SIZE_T SectionSize, + _Out_ PALPC_HANDLE AlpcSectionHandle, + _Out_ PSIZE_T ActualSectionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCreateResourceReserve( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ SIZE_T MessageSize, + _Out_ PALPC_HANDLE ResourceId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCreateSectionView( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _Inout_ PALPC_DATA_VIEW_ATTR ViewAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcCreateSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _Inout_ PALPC_SECURITY_ATTR SecurityAttribute + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcDeletePortSection( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE SectionHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcDeleteResourceReserve( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ResourceId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcDeleteSectionView( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ PVOID ViewBase + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcDeleteSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ContextHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcDisconnectPort( + _In_ HANDLE PortHandle, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcImpersonateClientContainerOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcImpersonateClientOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ PVOID Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcOpenSenderProcess( + _Out_ PHANDLE ProcessHandle, + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ULONG Flags, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcOpenSenderThread( + _Out_ PHANDLE ThreadHandle, + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ULONG Flags, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcQueryInformation( + _In_opt_ HANDLE PortHandle, + _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass, + _Inout_updates_bytes_to_(Length, *ReturnLength) PVOID PortInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcQueryInformationMessage( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE PortMessage, + _In_ ALPC_MESSAGE_INFORMATION_CLASS MessageInformationClass, + _Out_writes_bytes_to_opt_(Length, *ReturnLength) PVOID MessageInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcRevokeSecurityContext( + _In_ HANDLE PortHandle, + _Reserved_ ULONG Flags, + _In_ ALPC_HANDLE ContextHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcSendWaitReceivePort( + _In_ HANDLE PortHandle, + _In_ ULONG Flags, + _In_reads_bytes_opt_(SendMessage->u1.s1.TotalLength) PPORT_MESSAGE SendMessage, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES SendMessageAttributes, + _Out_writes_bytes_to_opt_(*BufferLength, *BufferLength) PPORT_MESSAGE ReceiveMessage, + _Inout_opt_ PSIZE_T BufferLength, + _Inout_opt_ PALPC_MESSAGE_ATTRIBUTES ReceiveMessageAttributes, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAlpcSetInformation( + _In_ HANDLE PortHandle, + _In_ ALPC_PORT_INFORMATION_CLASS PortInformationClass, + _In_reads_bytes_opt_(Length) PVOID PortInformation, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAreMappedFilesTheSame( + _In_ PVOID File1MappedAsAnImage, + _In_ PVOID File2MappedAsFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAssignProcessToJobObject( + _In_ HANDLE JobHandle, + _In_ HANDLE ProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwAssociateWaitCompletionPacket( + _In_ HANDLE WaitCompletionPacketHandle, + _In_ HANDLE IoCompletionHandle, + _In_ HANDLE TargetObjectHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation, + _Out_opt_ PBOOLEAN AlreadySignaled + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCallbackReturn( + _In_reads_bytes_opt_(OutputLength) PVOID OutputBuffer, + _In_ ULONG OutputLength, + _In_ NTSTATUS Status + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelIoFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelIoFileEx( + _In_ HANDLE FileHandle, + _In_opt_ PIO_STATUS_BLOCK IoRequestToCancel, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelSynchronousIoFile( + _In_ HANDLE ThreadHandle, + _In_opt_ PIO_STATUS_BLOCK IoRequestToCancel, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelTimer( + _In_ HANDLE TimerHandle, + _Out_opt_ PBOOLEAN CurrentState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelTimer2( + _In_ HANDLE TimerHandle, + _In_ PT2_CANCEL_PARAMETERS Parameters + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCancelWaitCompletionPacket( + _In_ HANDLE WaitCompletionPacketHandle, + _In_ BOOLEAN RemoveSignaledPacket + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwClearEvent( + _In_ HANDLE EventHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwClose( + _In_ HANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCloseObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ BOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCommitComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCommitEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCommitTransaction( + _In_ HANDLE TransactionHandle, + _In_ BOOLEAN Wait + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCompactKeys( + _In_ ULONG Count, + _In_reads_(Count) HANDLE KeyArray[] + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCompareObjects( + _In_ HANDLE FirstObjectHandle, + _In_ HANDLE SecondObjectHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCompareTokens( + _In_ HANDLE FirstTokenHandle, + _In_ HANDLE SecondTokenHandle, + _Out_ PBOOLEAN Equal + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCompleteConnectPort( + _In_ HANDLE PortHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCompressKey( + _In_ HANDLE Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos, + _Inout_opt_ PPORT_VIEW ClientView, + _Inout_opt_ PREMOTE_PORT_VIEW ServerView, + _Out_opt_ PULONG MaxMessageLength, + _Inout_updates_bytes_to_opt_(*ConnectionInformationLength, *ConnectionInformationLength) PVOID ConnectionInformation, + _Inout_opt_ PULONG ConnectionInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwContinue( + _In_ PCONTEXT ContextRecord, + _In_ BOOLEAN TestAlert + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateDebugObject( + _Out_ PHANDLE DebugObjectHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateDirectoryObject( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateDirectoryObjectEx( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ShadowDirectoryHandle, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateEnlistment( + _Out_ PHANDLE EnlistmentHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ResourceManagerHandle, + _In_ HANDLE TransactionHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG CreateOptions, + _In_ NOTIFICATION_MASK NotificationMask, + _In_opt_ PVOID EnlistmentKey + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateEvent( + _Out_ PHANDLE EventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ EVENT_TYPE EventType, + _In_ BOOLEAN InitialState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateEventPair( + _Out_ PHANDLE EventPairHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateFile( + _Out_ PHANDLE FileHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_opt_ PLARGE_INTEGER AllocationSize, + _In_ ULONG FileAttributes, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _In_reads_bytes_opt_(EaLength) PVOID EaBuffer, + _In_ ULONG EaLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateIRTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateIoCompletion( + _Out_ PHANDLE IoCompletionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateJobObject( + _Out_ PHANDLE JobHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateJobSet( + _In_ ULONG NumJob, + _In_reads_(NumJob) PJOB_SET_ARRAY UserJobSet, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Reserved_ ULONG TitleIndex, + _In_opt_ PUNICODE_STRING Class, + _In_ ULONG CreateOptions, + _Out_opt_ PULONG Disposition + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateKeyTransacted( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Reserved_ ULONG TitleIndex, + _In_opt_ PUNICODE_STRING Class, + _In_ ULONG CreateOptions, + _In_ HANDLE TransactionHandle, + _Out_opt_ PULONG Disposition + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateKeyedEvent( + _Out_ PHANDLE KeyedEventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateLowBoxToken( + _Out_ PHANDLE TokenHandle, + _In_ HANDLE ExistingTokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PSID PackageSid, + _In_ ULONG CapabilityCount, + _In_reads_opt_(CapabilityCount) PSID_AND_ATTRIBUTES Capabilities, + _In_ ULONG HandleCount, + _In_reads_opt_(HandleCount) HANDLE *Handles + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateMailslotFile( + _Out_ PHANDLE FileHandle, + _In_ ULONG DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CreateOptions, + _In_ ULONG MailslotQuota, + _In_ ULONG MaximumMessageSize, + _In_ PLARGE_INTEGER ReadTimeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateMutant( + _Out_ PHANDLE MutantHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ BOOLEAN InitialOwner + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateNamedPipeFile( + _Out_ PHANDLE FileHandle, + _In_ ULONG DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG ShareAccess, + _In_ ULONG CreateDisposition, + _In_ ULONG CreateOptions, + _In_ ULONG NamedPipeType, + _In_ ULONG ReadMode, + _In_ ULONG CompletionMode, + _In_ ULONG MaximumInstances, + _In_ ULONG InboundQuota, + _In_ ULONG OutboundQuota, + _In_opt_ PLARGE_INTEGER DefaultTimeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreatePagingFile( + _In_ PUNICODE_STRING PageFileName, + _In_ PLARGE_INTEGER MinimumSize, + _In_ PLARGE_INTEGER MaximumSize, + _In_ ULONG Priority + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreatePartition( + _Out_ PHANDLE PartitionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG PreferredNode + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreatePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG MaxConnectionInfoLength, + _In_ ULONG MaxMessageLength, + _In_opt_ ULONG MaxPoolUsage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreatePrivateNamespace( + _Out_ PHANDLE NamespaceHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PVOID BoundaryDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ParentProcess, + _In_ BOOLEAN InheritObjectTable, + _In_opt_ HANDLE SectionHandle, + _In_opt_ HANDLE DebugPort, + _In_opt_ HANDLE ExceptionPort + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateProcessEx( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ParentProcess, + _In_ ULONG Flags, + _In_opt_ HANDLE SectionHandle, + _In_opt_ HANDLE DebugPort, + _In_opt_ HANDLE ExceptionPort, + _In_ ULONG JobMemberLevel + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateProfile( + _Out_ PHANDLE ProfileHandle, + _In_opt_ HANDLE Process, + _In_ PVOID ProfileBase, + _In_ SIZE_T ProfileSize, + _In_ ULONG BucketSize, + _In_reads_bytes_(BufferSize) PULONG Buffer, + _In_ ULONG BufferSize, + _In_ KPROFILE_SOURCE ProfileSource, + _In_ KAFFINITY Affinity + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateProfileEx( + _Out_ PHANDLE ProfileHandle, + _In_opt_ HANDLE Process, + _In_ PVOID ProfileBase, + _In_ SIZE_T ProfileSize, + _In_ ULONG BucketSize, + _In_reads_bytes_(BufferSize) PULONG Buffer, + _In_ ULONG BufferSize, + _In_ KPROFILE_SOURCE ProfileSource, + _In_ USHORT GroupCount, + _In_reads_(GroupCount) PGROUP_AFFINITY GroupAffinity + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateResourceManager( + _Out_ PHANDLE ResourceManagerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE TmHandle, + _In_ LPGUID RmGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ ULONG CreateOptions, + _In_opt_ PUNICODE_STRING Description + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateSection( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PLARGE_INTEGER MaximumSize, + _In_ ULONG SectionPageProtection, + _In_ ULONG AllocationAttributes, + _In_opt_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateSemaphore( + _Out_ PHANDLE SemaphoreHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ LONG InitialCount, + _In_ LONG MaximumCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateSymbolicLinkObject( + _Out_ PHANDLE LinkHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PUNICODE_STRING LinkTarget + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ProcessHandle, + _Out_ PCLIENT_ID ClientId, + _In_ PCONTEXT ThreadContext, + _In_ PINITIAL_TEB InitialTeb, + _In_ BOOLEAN CreateSuspended + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateThreadEx( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE ProcessHandle, + _In_ PVOID StartRoutine, // PUSER_THREAD_START_ROUTINE + _In_opt_ PVOID Argument, + _In_ ULONG CreateFlags, // THREAD_CREATE_FLAGS_* + _In_ SIZE_T ZeroBits, + _In_ SIZE_T StackSize, + _In_ SIZE_T MaximumStackSize, + _In_opt_ PPS_ATTRIBUTE_LIST AttributeList + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TIMER_TYPE TimerType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateTimer2( + _Out_ PHANDLE TimerHandle, + _In_opt_ PVOID Reserved1, + _In_opt_ PVOID Reserved2, + _In_ ULONG Attributes, + _In_ ACCESS_MASK DesiredAccess + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateToken( + _Out_ PHANDLE TokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PTOKEN_USER User, + _In_ PTOKEN_GROUPS Groups, + _In_ PTOKEN_PRIVILEGES Privileges, + _In_opt_ PTOKEN_OWNER Owner, + _In_ PTOKEN_PRIMARY_GROUP PrimaryGroup, + _In_opt_ PTOKEN_DEFAULT_DACL DefaultDacl, + _In_ PTOKEN_SOURCE TokenSource + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateTokenEx( + _Out_ PHANDLE TokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ TOKEN_TYPE TokenType, + _In_ PLUID AuthenticationId, + _In_ PLARGE_INTEGER ExpirationTime, + _In_ PTOKEN_USER User, + _In_ PTOKEN_GROUPS Groups, + _In_ PTOKEN_PRIVILEGES Privileges, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION UserAttributes, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION DeviceAttributes, + _In_opt_ PTOKEN_GROUPS DeviceGroups, + _In_opt_ PTOKEN_MANDATORY_POLICY TokenMandatoryPolicy, + _In_opt_ PTOKEN_OWNER Owner, + _In_ PTOKEN_PRIMARY_GROUP PrimaryGroup, + _In_opt_ PTOKEN_DEFAULT_DACL DefaultDacl, + _In_ PTOKEN_SOURCE TokenSource + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateTransaction( + _Out_ PHANDLE TransactionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ LPGUID Uow, + _In_opt_ HANDLE TmHandle, + _In_opt_ ULONG CreateOptions, + _In_opt_ ULONG IsolationLevel, + _In_opt_ ULONG IsolationFlags, + _In_opt_ PLARGE_INTEGER Timeout, + _In_opt_ PUNICODE_STRING Description + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateTransactionManager( + _Out_ PHANDLE TmHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PUNICODE_STRING LogFileName, + _In_opt_ ULONG CreateOptions, + _In_opt_ ULONG CommitStrength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateUserProcess( + _Out_ PHANDLE ProcessHandle, + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK ProcessDesiredAccess, + _In_ ACCESS_MASK ThreadDesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ProcessObjectAttributes, + _In_opt_ POBJECT_ATTRIBUTES ThreadObjectAttributes, + _In_ ULONG ProcessFlags, // PROCESS_CREATE_FLAGS_* + _In_ ULONG ThreadFlags, // THREAD_CREATE_FLAGS_* + _In_opt_ PVOID ProcessParameters, // PRTL_USER_PROCESS_PARAMETERS + _Inout_ PPS_CREATE_INFO CreateInfo, + _In_opt_ PPS_ATTRIBUTE_LIST AttributeList + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateWaitCompletionPacket( + _Out_ PHANDLE WaitCompletionPacketHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateWaitablePort( + _Out_ PHANDLE PortHandle, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG MaxConnectionInfoLength, + _In_ ULONG MaxMessageLength, + _In_opt_ ULONG MaxPoolUsage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateWnfStateName( + _Out_ PWNF_STATE_NAME StateName, + _In_ WNF_STATE_NAME_LIFETIME NameLifetime, + _In_ WNF_DATA_SCOPE DataScope, + _In_ BOOLEAN PersistData, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_ ULONG MaximumStateSize, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwCreateWorkerFactory( + _Out_ PHANDLE WorkerFactoryHandleReturn, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE CompletionPortHandle, + _In_ HANDLE WorkerProcessHandle, + _In_ PVOID StartRoutine, + _In_opt_ PVOID StartParameter, + _In_opt_ ULONG MaxThreadCount, + _In_opt_ SIZE_T StackReserve, + _In_opt_ SIZE_T StackCommit + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDebugActiveProcess( + _In_ HANDLE ProcessHandle, + _In_ HANDLE DebugObjectHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDebugContinue( + _In_ HANDLE DebugObjectHandle, + _In_ PCLIENT_ID ClientId, + _In_ NTSTATUS ContinueStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDelayExecution( + _In_ BOOLEAN Alertable, + _In_ PLARGE_INTEGER DelayInterval + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteAtom( + _In_ RTL_ATOM Atom + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteBootEntry( + _In_ ULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteDriverEntry( + _In_ ULONG Id + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ BOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeletePrivateNamespace( + _In_ HANDLE NamespaceHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ const VOID *ExplicitScope + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeleteWnfStateName( + _In_ PCWNF_STATE_NAME StateName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDeviceIoControlFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG IoControlCode, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDisableLastKnownGood( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDisplayString( + _In_ PUNICODE_STRING String + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDrawText( + _In_ PUNICODE_STRING String + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDuplicateObject( + _In_ HANDLE SourceProcessHandle, + _In_ HANDLE SourceHandle, + _In_opt_ HANDLE TargetProcessHandle, + _Out_opt_ PHANDLE TargetHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Options + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwDuplicateToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ BOOLEAN EffectiveOnly, + _In_ TOKEN_TYPE TokenType, + _Out_ PHANDLE NewTokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnableLastKnownGood( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateBootEntries( + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateDriverEntries( + _Out_writes_bytes_opt_(*BufferLength) PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateKey( + _In_ HANDLE KeyHandle, + _In_ ULONG Index, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateSystemEnvironmentValuesEx( + _In_ ULONG InformationClass, + _Out_ PVOID Buffer, + _Inout_ PULONG BufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateTransactionObject( + _In_opt_ HANDLE RootObjectHandle, + _In_ KTMOBJECT_TYPE QueryType, + _Inout_updates_bytes_(ObjectCursorLength) PKTMOBJECT_CURSOR ObjectCursor, + _In_ ULONG ObjectCursorLength, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwEnumerateValueKey( + _In_ HANDLE KeyHandle, + _In_ ULONG Index, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyValueInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwExtendSection( + _In_ HANDLE SectionHandle, + _Inout_ PLARGE_INTEGER NewSectionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFilterBootOption( + _In_ FILTER_BOOT_OPTION_OPERATION FilterOperation, + _In_ ULONG ObjectType, + _In_ ULONG ElementType, + _In_reads_bytes_opt_(DataSize) PVOID Data, + _In_ ULONG DataSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFilterToken( + _In_ HANDLE ExistingTokenHandle, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _Out_ PHANDLE NewTokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFilterTokenEx( + _In_ HANDLE ExistingTokenHandle, + _In_ ULONG Flags, + _In_opt_ PTOKEN_GROUPS SidsToDisable, + _In_opt_ PTOKEN_PRIVILEGES PrivilegesToDelete, + _In_opt_ PTOKEN_GROUPS RestrictedSids, + _In_ ULONG DisableUserClaimsCount, + _In_opt_ PUNICODE_STRING UserClaimsToDisable, + _In_ ULONG DisableDeviceClaimsCount, + _In_opt_ PUNICODE_STRING DeviceClaimsToDisable, + _In_opt_ PTOKEN_GROUPS DeviceGroupsToDisable, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION RestrictedUserAttributes, + _In_opt_ PTOKEN_SECURITY_ATTRIBUTES_INFORMATION RestrictedDeviceAttributes, + _In_opt_ PTOKEN_GROUPS RestrictedDeviceGroups, + _Out_ PHANDLE NewTokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFindAtom( + _In_reads_bytes_opt_(Length) PWSTR AtomName, + _In_ ULONG Length, + _Out_opt_ PRTL_ATOM Atom + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushBuffersFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushBuffersFileEx( + _In_ HANDLE FileHandle, + _In_ ULONG Flags, + _In_reads_bytes_(ParametersSize) PVOID Parameters, + _In_ ULONG ParametersSize, + _Out_ PIO_STATUS_BLOCK IoStatusBlock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushInstallUILanguage( + _In_ LANGID InstallUILanguage, + _In_ ULONG SetComittedFlag + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushInstructionCache( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_ SIZE_T Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +VOID +NTAPI +ZwFlushProcessWriteBuffers( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFlushWriteBuffer( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFreeUserPhysicalPages( + _In_ HANDLE ProcessHandle, + _Inout_ PULONG_PTR NumberOfPages, + _In_reads_(*NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFreeVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG FreeType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFreezeRegistry( + _In_ ULONG TimeOutInSeconds + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFreezeTransactions( + _In_ PLARGE_INTEGER FreezeTimeout, + _In_ PLARGE_INTEGER ThawTimeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwFsControlFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG FsControlCode, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetCachedSigningLevel( + _In_ HANDLE File, + _Out_ PULONG Flags, + _Out_ PSE_SIGNING_LEVEL SigningLevel, + _Out_writes_bytes_to_opt_(*ThumbprintSize, *ThumbprintSize) PUCHAR Thumbprint, + _Inout_opt_ PULONG ThumbprintSize, + _Out_opt_ PULONG ThumbprintAlgorithm + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetCompleteWnfStateSubscription( + _In_opt_ PWNF_STATE_NAME OldDescriptorStateName, + _In_opt_ ULONG64 *OldSubscriptionId, + _In_opt_ ULONG OldDescriptorEventMask, + _In_opt_ ULONG OldDescriptorStatus, + _Out_writes_bytes_(DescriptorSize) PWNF_DELIVERY_DESCRIPTOR NewDeliveryDescriptor, + _In_ ULONG DescriptorSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetContextThread( + _In_ HANDLE ThreadHandle, + _Inout_ PCONTEXT ThreadContext + ); + +NTSYSCALLAPI +ULONG +NTAPI +ZwGetCurrentProcessorNumber( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetDevicePowerState( + _In_ HANDLE Device, + _Out_ PDEVICE_POWER_STATE State + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetMUIRegistryInfo( + _In_ ULONG Flags, + _Inout_ PULONG DataSize, + _Out_ PVOID Data + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetNextProcess( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Flags, + _Out_ PHANDLE NewProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetNextThread( + _In_ HANDLE ProcessHandle, + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _In_ ULONG Flags, + _Out_ PHANDLE NewThreadHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetNlsSectionPtr( + _In_ ULONG SectionType, + _In_ ULONG SectionData, + _In_ PVOID ContextData, + _Out_ PVOID *SectionPointer, + _Out_ PULONG SectionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetNotificationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _Out_ PTRANSACTION_NOTIFICATION TransactionNotification, + _In_ ULONG NotificationLength, + _In_opt_ PLARGE_INTEGER Timeout, + _Out_opt_ PULONG ReturnLength, + _In_ ULONG Asynchronous, + _In_opt_ ULONG_PTR AsynchronousContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetPlugPlayEvent( + _In_ HANDLE EventHandle, + _In_opt_ PVOID Context, + _Out_writes_bytes_(EventBufferSize) PPLUGPLAY_EVENT_BLOCK EventBlock, + _In_ ULONG EventBufferSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwGetWriteWatch( + _In_ HANDLE ProcessHandle, + _In_ ULONG Flags, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize, + _Out_writes_(*EntriesInUserAddressArray) PVOID *UserAddressArray, + _Inout_ PULONG_PTR EntriesInUserAddressArray, + _Out_ PULONG Granularity + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwImpersonateAnonymousToken( + _In_ HANDLE ThreadHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwImpersonateClientOfPort( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwImpersonateThread( + _In_ HANDLE ServerThreadHandle, + _In_ HANDLE ClientThreadHandle, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwInitializeNlsFiles( + _Out_ PVOID *BaseAddress, + _Out_ PLCID DefaultLocaleId, + _Out_ PLARGE_INTEGER DefaultCasingTableSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwInitializeRegistry( + _In_ USHORT BootCondition + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwInitiatePowerAction( + _In_ POWER_ACTION SystemAction, + _In_ SYSTEM_POWER_STATE LightestSystemState, + _In_ ULONG Flags, // POWER_ACTION_* flags + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwIsProcessInJob( + _In_ HANDLE ProcessHandle, + _In_opt_ HANDLE JobHandle + ); + +NTSYSCALLAPI +BOOLEAN +NTAPI +ZwIsSystemResumeAutomatic( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwIsUILanguageComitted( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwListenPort( + _In_ HANDLE PortHandle, + _Out_ PPORT_MESSAGE ConnectionRequest + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLoadDriver( + _In_ PUNICODE_STRING DriverServiceName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLoadKey( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLoadKey2( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLoadKeyEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ POBJECT_ATTRIBUTES SourceFile, + _In_ ULONG Flags, + _In_opt_ HANDLE TrustClassKey, + _In_opt_ HANDLE Event, + _In_opt_ ACCESS_MASK DesiredAccess, + _Out_opt_ PHANDLE RootHandle, + _Out_opt_ PIO_STATUS_BLOCK IoStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLockFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PLARGE_INTEGER ByteOffset, + _In_ PLARGE_INTEGER Length, + _In_ ULONG Key, + _In_ BOOLEAN FailImmediately, + _In_ BOOLEAN ExclusiveLock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLockProductActivationKeys( + _Inout_opt_ ULONG *pPrivateVer, + _Out_opt_ ULONG *pSafeMode + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLockRegistryKey( + _In_ HANDLE KeyHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwLockVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG MapType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMakePermanentObject( + _In_ HANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMakeTemporaryObject( + _In_ HANDLE Handle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwManagePartition( + _In_ MEMORY_PARTITION_INFORMATION_CLASS PartitionInformationClass, + _In_ PVOID PartitionInformation, + _In_ ULONG PartitionInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMapCMFModule( + _In_ ULONG What, + _In_ ULONG Index, + _Out_opt_ PULONG CacheIndexOut, + _Out_opt_ PULONG CacheFlagsOut, + _Out_opt_ PULONG ViewSizeOut, + _Out_opt_ PVOID *BaseAddress + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMapUserPhysicalPages( + _In_ PVOID VirtualAddress, + _In_ ULONG_PTR NumberOfPages, + _In_reads_opt_(NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMapUserPhysicalPagesScatter( + _In_reads_(NumberOfPages) PVOID *VirtualAddresses, + _In_ ULONG_PTR NumberOfPages, + _In_reads_opt_(NumberOfPages) PULONG_PTR UserPfnArray + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwMapViewOfSection( + _In_ HANDLE SectionHandle, + _In_ HANDLE ProcessHandle, + _Inout_ _At_(*BaseAddress, _Readable_bytes_(*ViewSize) _Writable_bytes_(*ViewSize) _Post_readable_byte_size_(*ViewSize)) PVOID *BaseAddress, + _In_ ULONG_PTR ZeroBits, + _In_ SIZE_T CommitSize, + _Inout_opt_ PLARGE_INTEGER SectionOffset, + _Inout_ PSIZE_T ViewSize, + _In_ SECTION_INHERIT InheritDisposition, + _In_ ULONG AllocationType, + _In_ ULONG Win32Protect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwModifyBootEntry( + _In_ PBOOT_ENTRY BootEntry + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwModifyDriverEntry( + _In_ PEFI_DRIVER_ENTRY DriverEntry + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwNotifyChangeDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, // FILE_NOTIFY_INFORMATION + _In_ ULONG Length, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwNotifyChangeKey( + _In_ HANDLE KeyHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwNotifyChangeMultipleKeys( + _In_ HANDLE MasterKeyHandle, + _In_opt_ ULONG Count, + _In_reads_opt_(Count) OBJECT_ATTRIBUTES SubordinateObjects[], + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwNotifyChangeSession( + _In_ HANDLE SessionHandle, + _In_ ULONG ChangeSequenceNumber, + _In_ PLARGE_INTEGER ChangeTimeStamp, + _In_ IO_SESSION_EVENT Event, + _In_ IO_SESSION_STATE NewState, + _In_ IO_SESSION_STATE PreviousState, + _In_reads_bytes_opt_(PayloadSize) PVOID Payload, + _In_ ULONG PayloadSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenDirectoryObject( + _Out_ PHANDLE DirectoryHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenEnlistment( + _Out_ PHANDLE EnlistmentHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE ResourceManagerHandle, + _In_ LPGUID EnlistmentGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenEvent( + _Out_ PHANDLE EventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenEventPair( + _Out_ PHANDLE EventPairHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenFile( + _Out_ PHANDLE FileHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG ShareAccess, + _In_ ULONG OpenOptions + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenIoCompletion( + _Out_ PHANDLE IoCompletionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenJobObject( + _Out_ PHANDLE JobHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenKey( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenKeyEx( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG OpenOptions + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenKeyTransacted( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ HANDLE TransactionHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenKeyTransactedEx( + _Out_ PHANDLE KeyHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ ULONG OpenOptions, + _In_ HANDLE TransactionHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenKeyedEvent( + _Out_ PHANDLE KeyedEventHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenMutant( + _Out_ PHANDLE MutantHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ PUNICODE_STRING ObjectTypeName, + _In_ PUNICODE_STRING ObjectName, + _In_opt_ PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ ACCESS_MASK GrantedAccess, + _In_opt_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN ObjectCreation, + _In_ BOOLEAN AccessGranted, + _Out_ PBOOLEAN GenerateOnClose + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenPartition( + _Out_ PHANDLE PartitionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenPrivateNamespace( + _Out_ PHANDLE NamespaceHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PVOID BoundaryDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenProcess( + _Out_ PHANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PCLIENT_ID ClientId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenProcessToken( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenProcessTokenEx( + _In_ HANDLE ProcessHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ ULONG HandleAttributes, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenResourceManager( + _Out_ PHANDLE ResourceManagerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ HANDLE TmHandle, + _In_opt_ LPGUID ResourceManagerGuid, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenSection( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenSemaphore( + _Out_ PHANDLE SemaphoreHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenSession( + _Out_ PHANDLE SessionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenSymbolicLinkObject( + _Out_ PHANDLE LinkHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenThread( + _Out_ PHANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PCLIENT_ID ClientId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenThreadToken( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ BOOLEAN OpenAsSelf, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenThreadTokenEx( + _In_ HANDLE ThreadHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ BOOLEAN OpenAsSelf, + _In_ ULONG HandleAttributes, + _Out_ PHANDLE TokenHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenTimer( + _Out_ PHANDLE TimerHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenTransaction( + _Out_ PHANDLE TransactionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ LPGUID Uow, + _In_opt_ HANDLE TmHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwOpenTransactionManager( + _Out_ PHANDLE TmHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_opt_ PUNICODE_STRING LogFileName, + _In_opt_ LPGUID TmIdentity, + _In_opt_ ULONG OpenOptions + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPlugPlayControl( + _In_ PLUGPLAY_CONTROL_CLASS PnPControlClass, + _Inout_updates_bytes_(PnPControlDataLength) PVOID PnPControlData, + _In_ ULONG PnPControlDataLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPowerInformation( + _In_ POWER_INFORMATION_LEVEL InformationLevel, + _In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrePrepareComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrePrepareEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrepareComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrepareEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrivilegeCheck( + _In_ HANDLE ClientToken, + _Inout_ PPRIVILEGE_SET RequiredPrivileges, + _Out_ PBOOLEAN Result + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrivilegeObjectAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_opt_ PVOID HandleId, + _In_ HANDLE ClientToken, + _In_ ACCESS_MASK DesiredAccess, + _In_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN AccessGranted + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPrivilegedServiceAuditAlarm( + _In_ PUNICODE_STRING SubsystemName, + _In_ PUNICODE_STRING ServiceName, + _In_ HANDLE ClientToken, + _In_ PPRIVILEGE_SET Privileges, + _In_ BOOLEAN AccessGranted + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPropagationComplete( + _In_ HANDLE ResourceManagerHandle, + _In_ ULONG RequestCookie, + _In_ ULONG BufferLength, + _In_ PVOID Buffer + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPropagationFailed( + _In_ HANDLE ResourceManagerHandle, + _In_ ULONG RequestCookie, + _In_ NTSTATUS PropStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwProtectVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG NewProtect, + _Out_ PULONG OldProtect + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwPulseEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryAttributesFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PFILE_BASIC_INFORMATION FileInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryBootEntryOrder( + _Out_writes_opt_(*Count) PULONG Ids, + _Inout_ PULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryBootOptions( + _Out_writes_bytes_opt_(*BootOptionsLength) PBOOT_OPTIONS BootOptions, + _Inout_ PULONG BootOptionsLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDefaultLocale( + _In_ BOOLEAN UserProfile, + _Out_ PLCID DefaultLocaleId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDefaultUILanguage( + _Out_ LANGID *DefaultUILanguageId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDirectoryFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass, + _In_ BOOLEAN ReturnSingleEntry, + _In_opt_ PUNICODE_STRING FileName, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDirectoryObject( + _In_ HANDLE DirectoryHandle, + _Out_writes_bytes_opt_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_ BOOLEAN RestartScan, + _Inout_ PULONG Context, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryDriverEntryOrder( + _Out_writes_opt_(*Count) PULONG Ids, + _Inout_ PULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_reads_bytes_opt_(EaListLength) PVOID EaList, + _In_ ULONG EaListLength, + _In_opt_ PULONG EaIndex, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryEvent( + _In_ HANDLE EventHandle, + _In_ EVENT_INFORMATION_CLASS EventInformationClass, + _Out_writes_bytes_(EventInformationLength) PVOID EventInformation, + _In_ ULONG EventInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryFullAttributesFile( + _In_ POBJECT_ATTRIBUTES ObjectAttributes, + _Out_ PFILE_NETWORK_OPEN_INFORMATION FileInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationAtom( + _In_ RTL_ATOM Atom, + _In_ ATOM_INFORMATION_CLASS AtomInformationClass, + _Out_writes_bytes_(AtomInformationLength) PVOID AtomInformation, + _In_ ULONG AtomInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_ ENLISTMENT_INFORMATION_CLASS EnlistmentInformationClass, + _Out_writes_bytes_(EnlistmentInformationLength) PVOID EnlistmentInformation, + _In_ ULONG EnlistmentInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationJobObject( + _In_opt_ HANDLE JobHandle, + _In_ JOBOBJECTINFOCLASS JobObjectInformationClass, + _Out_writes_bytes_(JobObjectInformationLength) PVOID JobObjectInformation, + _In_ ULONG JobObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationPort( + _In_ HANDLE PortHandle, + _In_ PORT_INFORMATION_CLASS PortInformationClass, + _Out_writes_bytes_to_(Length, *ReturnLength) PVOID PortInformation, + _In_ ULONG Length, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _Out_writes_bytes_(ResourceManagerInformationLength) PVOID ResourceManagerInformation, + _In_ ULONG ResourceManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationThread( + _In_ HANDLE ThreadHandle, + _In_ THREADINFOCLASS ThreadInformationClass, + _Out_writes_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationToken( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _Out_writes_bytes_(TokenInformationLength) PVOID TokenInformation, + _In_ ULONG TokenInformationLength, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationTransaction( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _Out_writes_bytes_(TransactionInformationLength) PVOID TransactionInformation, + _In_ ULONG TransactionInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationTransactionManager( + _In_ HANDLE TransactionManagerHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _Out_writes_bytes_(TransactionManagerInformationLength) PVOID TransactionManagerInformation, + _In_ ULONG TransactionManagerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInformationWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _In_ WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, + _Out_writes_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, + _In_ ULONG WorkerFactoryInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryInstallUILanguage( + _Out_ LANGID *InstallUILanguageId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryIntervalProfile( + _In_ KPROFILE_SOURCE ProfileSource, + _Out_ PULONG Interval + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryIoCompletion( + _In_ HANDLE IoCompletionHandle, + _In_ IO_COMPLETION_INFORMATION_CLASS IoCompletionInformationClass, + _Out_writes_bytes_(IoCompletionInformation) PVOID IoCompletionInformation, + _In_ ULONG IoCompletionInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryKey( + _In_ HANDLE KeyHandle, + _In_ KEY_INFORMATION_CLASS KeyInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryLicenseValue( + _In_ PUNICODE_STRING ValueName, + _Out_opt_ PULONG Type, + _Out_writes_bytes_to_opt_(DataSize, *ResultDataSize) PVOID Data, + _In_ ULONG DataSize, + _Out_ PULONG ResultDataSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryMultipleValueKey( + _In_ HANDLE KeyHandle, + _Inout_updates_(EntryCount) PKEY_VALUE_ENTRY ValueEntries, + _In_ ULONG EntryCount, + _Out_writes_bytes_(*BufferLength) PVOID ValueBuffer, + _Inout_ PULONG BufferLength, + _Out_opt_ PULONG RequiredBufferLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryMutant( + _In_ HANDLE MutantHandle, + _In_ MUTANT_INFORMATION_CLASS MutantInformationClass, + _Out_writes_bytes_(MutantInformationLength) PVOID MutantInformation, + _In_ ULONG MutantInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryObject( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryOpenSubKeys( + _In_ POBJECT_ATTRIBUTES TargetKey, + _Out_ PULONG HandleCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryOpenSubKeysEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ ULONG BufferLength, + _Out_writes_bytes_(BufferLength) PVOID Buffer, + _Out_ PULONG RequiredSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryPerformanceCounter( + _Out_ PLARGE_INTEGER PerformanceCounter, + _Out_opt_ PLARGE_INTEGER PerformanceFrequency + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryPortInformationProcess( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryQuotaInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_ BOOLEAN ReturnSingleEntry, + _In_reads_bytes_opt_(SidListLength) PVOID SidList, + _In_ ULONG SidListLength, + _In_opt_ PSID StartSid, + _In_ BOOLEAN RestartScan + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySection( + _In_ HANDLE SectionHandle, + _In_ SECTION_INFORMATION_CLASS SectionInformationClass, + _Out_writes_bytes_(SectionInformationLength) PVOID SectionInformation, + _In_ SIZE_T SectionInformationLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySecurityAttributesToken( + _In_ HANDLE TokenHandle, + _In_reads_opt_(NumberOfAttributes) PUNICODE_STRING Attributes, + _In_ ULONG NumberOfAttributes, + _Out_writes_bytes_(Length) PVOID Buffer, // PTOKEN_SECURITY_ATTRIBUTES_INFORMATION + _In_ ULONG Length, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySecurityObject( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _Out_writes_bytes_opt_(Length) PSECURITY_DESCRIPTOR SecurityDescriptor, + _In_ ULONG Length, + _Out_ PULONG LengthNeeded + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySemaphore( + _In_ HANDLE SemaphoreHandle, + _In_ SEMAPHORE_INFORMATION_CLASS SemaphoreInformationClass, + _Out_writes_bytes_(SemaphoreInformationLength) PVOID SemaphoreInformation, + _In_ ULONG SemaphoreInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySymbolicLinkObject( + _In_ HANDLE LinkHandle, + _Inout_ PUNICODE_STRING LinkTarget, + _Out_opt_ PULONG ReturnedLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemEnvironmentValue( + _In_ PUNICODE_STRING VariableName, + _Out_writes_bytes_(ValueLength) PWSTR VariableValue, + _In_ USHORT ValueLength, + _Out_opt_ PUSHORT ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemEnvironmentValueEx( + _In_ PUNICODE_STRING VariableName, + _In_ LPGUID VendorGuid, + _Out_writes_bytes_opt_(*ValueLength) PVOID Value, + _Inout_ PULONG ValueLength, + _Out_opt_ PULONG Attributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemInformationEx( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _In_reads_bytes_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQuerySystemTime( + _Out_ PLARGE_INTEGER SystemTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryTimer( + _In_ HANDLE TimerHandle, + _In_ TIMER_INFORMATION_CLASS TimerInformationClass, + _Out_writes_bytes_(TimerInformationLength) PVOID TimerInformation, + _In_ ULONG TimerInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryTimerResolution( + _Out_ PULONG MaximumTime, + _Out_ PULONG MinimumTime, + _Out_ PULONG CurrentTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName, + _In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, + _Out_writes_bytes_opt_(Length) PVOID KeyValueInformation, + _In_ ULONG Length, + _Out_ PULONG ResultLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ MEMORY_INFORMATION_CLASS MemoryInformationClass, + _Out_writes_bytes_(MemoryInformationLength) PVOID MemoryInformation, + _In_ SIZE_T MemoryInformationLength, + _Out_opt_ PSIZE_T ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryVolumeInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID FsInformation, + _In_ ULONG Length, + _In_ FSINFOCLASS FsInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_opt_ const VOID *ExplicitScope, + _Out_ PWNF_CHANGE_STAMP ChangeStamp, + _Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer, + _Inout_ PULONG BufferSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueryWnfStateNameInformation( + _In_ PCWNF_STATE_NAME StateName, + _In_ WNF_STATE_NAME_INFORMATION NameInfoClass, + _In_opt_ const VOID *ExplicitScope, + _Out_writes_bytes_(InfoBufferSize) PVOID InfoBuffer, + _In_ ULONG InfoBufferSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueueApcThread( + _In_ HANDLE ThreadHandle, + _In_ PPS_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwQueueApcThreadEx( + _In_ HANDLE ThreadHandle, + _In_opt_ HANDLE UserApcReserveHandle, + _In_ PPS_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcArgument1, + _In_opt_ PVOID ApcArgument2, + _In_opt_ PVOID ApcArgument3 + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRaiseException( + _In_ PEXCEPTION_RECORD ExceptionRecord, + _In_ PCONTEXT ContextRecord, + _In_ BOOLEAN FirstChance + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRaiseHardError( + _In_ NTSTATUS ErrorStatus, + _In_ ULONG NumberOfParameters, + _In_ ULONG UnicodeStringParameterMask, + _In_reads_(NumberOfParameters) PULONG_PTR Parameters, + _In_ ULONG ValidResponseOptions, + _Out_ PULONG Response + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReadFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _Out_writes_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReadFileScatter( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PFILE_SEGMENT_ELEMENT SegmentArray, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReadOnlyEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReadRequestData( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG DataEntryIndex, + _Out_writes_bytes_to_(BufferSize, *NumberOfBytesRead) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReadVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _Out_writes_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesRead + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRecoverEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PVOID EnlistmentKey + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRecoverResourceManager( + _In_ HANDLE ResourceManagerHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRecoverTransactionManager( + _In_ HANDLE TransactionManagerHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRegisterProtocolAddressInformation( + _In_ HANDLE ResourceManager, + _In_ PCRM_PROTOCOL_ID ProtocolId, + _In_ ULONG ProtocolInformationSize, + _In_ PVOID ProtocolInformation, + _In_opt_ ULONG CreateOptions + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRegisterThreadTerminatePort( + _In_ HANDLE PortHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReleaseCMFViewOwnership( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReleaseKeyedEvent( + _In_ HANDLE KeyedEventHandle, + _In_ PVOID KeyValue, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReleaseMutant( + _In_ HANDLE MutantHandle, + _Out_opt_ PLONG PreviousCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReleaseSemaphore( + _In_ HANDLE SemaphoreHandle, + _In_ LONG ReleaseCount, + _Out_opt_ PLONG PreviousCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReleaseWorkerFactoryWorker( + _In_ HANDLE WorkerFactoryHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRemoveIoCompletion( + _In_ HANDLE IoCompletionHandle, + _Out_ PVOID *KeyContext, + _Out_ PVOID *ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRemoveIoCompletionEx( + _In_ HANDLE IoCompletionHandle, + _Out_writes_to_(Count, *NumEntriesRemoved) PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation, + _In_ ULONG Count, + _Out_ PULONG NumEntriesRemoved, + _In_opt_ PLARGE_INTEGER Timeout, + _In_ BOOLEAN Alertable + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRemoveProcessDebug( + _In_ HANDLE ProcessHandle, + _In_ HANDLE DebugObjectHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRenameKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING NewName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRenameTransactionManager( + _In_ PUNICODE_STRING LogFileName, + _In_ LPGUID ExistingTransactionManagerGuid + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplaceKey( + _In_ POBJECT_ATTRIBUTES NewFile, + _In_ HANDLE TargetHandle, + _In_ POBJECT_ATTRIBUTES OldFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplacePartitionUnit( + _In_ PUNICODE_STRING TargetInstancePath, + _In_ PUNICODE_STRING SpareInstancePath, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplyPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplyWaitReceivePort( + _In_ HANDLE PortHandle, + _Out_opt_ PVOID *PortContext, + _In_reads_bytes_opt_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage, + _Out_ PPORT_MESSAGE ReceiveMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplyWaitReceivePortEx( + _In_ HANDLE PortHandle, + _Out_opt_ PVOID *PortContext, + _In_reads_bytes_opt_(ReplyMessage->u1.s1.TotalLength) PPORT_MESSAGE ReplyMessage, + _Out_ PPORT_MESSAGE ReceiveMessage, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwReplyWaitReplyPort( + _In_ HANDLE PortHandle, + _Inout_ PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRequestPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(RequestMessage->u1.s1.TotalLength) PPORT_MESSAGE RequestMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRequestWaitReplyPort( + _In_ HANDLE PortHandle, + _In_reads_bytes_(RequestMessage->u1.s1.TotalLength) PPORT_MESSAGE RequestMessage, + _Out_ PPORT_MESSAGE ReplyMessage + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRequestWakeupLatency( + _In_ LATENCY_TIME latency + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwResetEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwResetWriteWatch( + _In_ HANDLE ProcessHandle, + _In_ PVOID BaseAddress, + _In_ SIZE_T RegionSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRestoreKey( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwResumeProcess( + _In_ HANDLE ProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwResumeThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRevertContainerImpersonation( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRollbackComplete( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRollbackEnlistment( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRollbackTransaction( + _In_ HANDLE TransactionHandle, + _In_ BOOLEAN Wait + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwRollforwardTransactionManager( + _In_ HANDLE TransactionManagerHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSaveKey( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSaveKeyEx( + _In_ HANDLE KeyHandle, + _In_ HANDLE FileHandle, + _In_ ULONG Format + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSaveMergedKeys( + _In_ HANDLE HighPrecedenceKeyHandle, + _In_ HANDLE LowPrecedenceKeyHandle, + _In_ HANDLE FileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSecureConnectPort( + _Out_ PHANDLE PortHandle, + _In_ PUNICODE_STRING PortName, + _In_ PSECURITY_QUALITY_OF_SERVICE SecurityQos, + _Inout_opt_ PPORT_VIEW ClientView, + _In_opt_ PSID RequiredServerSid, + _Inout_opt_ PREMOTE_PORT_VIEW ServerView, + _Out_opt_ PULONG MaxMessageLength, + _Inout_updates_bytes_to_opt_(*ConnectionInformationLength, *ConnectionInformationLength) PVOID ConnectionInformation, + _Inout_opt_ PULONG ConnectionInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSerializeBoot( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetBootEntryOrder( + _In_reads_(Count) PULONG Ids, + _In_ ULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetBootOptions( + _In_ PBOOT_OPTIONS BootOptions, + _In_ ULONG FieldsToChange + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetCachedSigningLevel( + _In_ ULONG Flags, + _In_ SE_SIGNING_LEVEL InputSigningLevel, + _In_reads_(SourceFileCount) PHANDLE SourceFiles, + _In_ ULONG SourceFileCount, + _In_opt_ HANDLE TargetFile + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetContextThread( + _In_ HANDLE ThreadHandle, + _In_ PCONTEXT ThreadContext + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetDebugFilterState( + _In_ ULONG ComponentId, + _In_ ULONG Level, + _In_ BOOLEAN State + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetDefaultHardErrorPort( + _In_ HANDLE DefaultHardErrorPort + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetDefaultLocale( + _In_ BOOLEAN UserProfile, + _In_ LCID DefaultLocaleId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetDefaultUILanguage( + _In_ LANGID DefaultUILanguageId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetDriverEntryOrder( + _In_reads_(Count) PULONG Ids, + _In_ ULONG Count + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetEaFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetEvent( + _In_ HANDLE EventHandle, + _Out_opt_ PLONG PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetEventBoostPriority( + _In_ HANDLE EventHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetHighWaitLowEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetIRTimer( + _In_ HANDLE TimerHandle, + _In_opt_ PLARGE_INTEGER DueTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationDebugObject( + _In_ HANDLE DebugObjectHandle, + _In_ DEBUGOBJECTINFOCLASS DebugObjectInformationClass, + _In_ PVOID DebugInformation, + _In_ ULONG DebugInformationLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationEnlistment( + _In_opt_ HANDLE EnlistmentHandle, + _In_ ENLISTMENT_INFORMATION_CLASS EnlistmentInformationClass, + _In_reads_bytes_(EnlistmentInformationLength) PVOID EnlistmentInformation, + _In_ ULONG EnlistmentInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID FileInformation, + _In_ ULONG Length, + _In_ FILE_INFORMATION_CLASS FileInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationJobObject( + _In_ HANDLE JobHandle, + _In_ JOBOBJECTINFOCLASS JobObjectInformationClass, + _In_reads_bytes_(JobObjectInformationLength) PVOID JobObjectInformation, + _In_ ULONG JobObjectInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationKey( + _In_ HANDLE KeyHandle, + _In_ KEY_SET_INFORMATION_CLASS KeySetInformationClass, + _In_reads_bytes_(KeySetInformationLength) PVOID KeySetInformation, + _In_ ULONG KeySetInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationObject( + _In_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _In_reads_bytes_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationProcess( + _In_ HANDLE ProcessHandle, + _In_ PROCESSINFOCLASS ProcessInformationClass, + _In_reads_bytes_(ProcessInformationLength) PVOID ProcessInformation, + _In_ ULONG ProcessInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationResourceManager( + _In_ HANDLE ResourceManagerHandle, + _In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass, + _In_reads_bytes_(ResourceManagerInformationLength) PVOID ResourceManagerInformation, + _In_ ULONG ResourceManagerInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationThread( + _In_ HANDLE ThreadHandle, + _In_ THREADINFOCLASS ThreadInformationClass, + _In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation, + _In_ ULONG ThreadInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationToken( + _In_ HANDLE TokenHandle, + _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + _In_reads_bytes_(TokenInformationLength) PVOID TokenInformation, + _In_ ULONG TokenInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationTransaction( + _In_ HANDLE TransactionHandle, + _In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass, + _In_reads_bytes_(TransactionInformationLength) PVOID TransactionInformation, + _In_ ULONG TransactionInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationTransactionManager( + _In_opt_ HANDLE TmHandle, + _In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass, + _In_reads_bytes_(TransactionManagerInformationLength) PVOID TransactionManagerInformation, + _In_ ULONG TransactionManagerInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_ VIRTUAL_MEMORY_INFORMATION_CLASS VmInformationClass, + _In_ ULONG_PTR NumberOfEntries, + _In_reads_ (NumberOfEntries) PMEMORY_RANGE_ENTRY VirtualAddresses, + _In_reads_bytes_ (VmInformationLength) PVOID VmInformation, + _In_ ULONG VmInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetInformationWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _In_ WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, + _In_reads_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, + _In_ ULONG WorkerFactoryInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetIntervalProfile( + _In_ ULONG Interval, + _In_ KPROFILE_SOURCE Source + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetIoCompletion( + _In_ HANDLE IoCompletionHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetIoCompletionEx( + _In_ HANDLE IoCompletionHandle, + _In_ HANDLE IoCompletionPacketHandle, + _In_opt_ PVOID KeyContext, + _In_opt_ PVOID ApcContext, + _In_ NTSTATUS IoStatus, + _In_ ULONG_PTR IoStatusInformation + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetLdtEntries( + _In_ ULONG Selector0, + _In_ ULONG Entry0Low, + _In_ ULONG Entry0Hi, + _In_ ULONG Selector1, + _In_ ULONG Entry1Low, + _In_ ULONG Entry1Hi + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetLowEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetLowWaitHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetQuotaInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSecurityObject( + _In_ HANDLE Handle, + _In_ SECURITY_INFORMATION SecurityInformation, + _In_ PSECURITY_DESCRIPTOR SecurityDescriptor + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSystemEnvironmentValue( + _In_ PUNICODE_STRING VariableName, + _In_ PUNICODE_STRING VariableValue + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSystemEnvironmentValueEx( + _In_ PUNICODE_STRING VariableName, + _In_ LPGUID VendorGuid, + _In_reads_bytes_opt_(ValueLength) PVOID Value, + _In_ ULONG ValueLength, + _In_ ULONG Attributes + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSystemInformation( + _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass, + _In_reads_bytes_opt_(SystemInformationLength) PVOID SystemInformation, + _In_ ULONG SystemInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSystemPowerState( + _In_ POWER_ACTION SystemAction, + _In_ SYSTEM_POWER_STATE LightestSystemState, + _In_ ULONG Flags // POWER_ACTION_* flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetSystemTime( + _In_opt_ PLARGE_INTEGER SystemTime, + _Out_opt_ PLARGE_INTEGER PreviousTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetThreadExecutionState( + _In_ EXECUTION_STATE NewFlags, // ES_* flags + _Out_ EXECUTION_STATE *PreviousFlags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetTimer( + _In_ HANDLE TimerHandle, + _In_ PLARGE_INTEGER DueTime, + _In_opt_ PTIMER_APC_ROUTINE TimerApcRoutine, + _In_opt_ PVOID TimerContext, + _In_ BOOLEAN ResumeTimer, + _In_opt_ LONG Period, + _Out_opt_ PBOOLEAN PreviousState + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetTimer2( + _In_ HANDLE TimerHandle, + _In_ PLARGE_INTEGER DueTime, + _In_opt_ PLARGE_INTEGER Period, + _In_ PT2_SET_PARAMETERS Parameters + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetTimerEx( + _In_ HANDLE TimerHandle, + _In_ TIMER_SET_INFORMATION_CLASS TimerSetInformationClass, + _Inout_updates_bytes_opt_(TimerSetInformationLength) PVOID TimerSetInformation, + _In_ ULONG TimerSetInformationLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetTimerResolution( + _In_ ULONG DesiredTime, + _In_ BOOLEAN SetResolution, + _Out_ PULONG ActualTime + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetUuidSeed( + _In_ PCHAR Seed + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetValueKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING ValueName, + _In_opt_ ULONG TitleIndex, + _In_ ULONG Type, + _In_reads_bytes_opt_(DataSize) PVOID Data, + _In_ ULONG DataSize + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetVolumeInformationFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID FsInformation, + _In_ ULONG Length, + _In_ FSINFOCLASS FsInformationClass + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSetWnfProcessNotificationEvent( + _In_ HANDLE NotificationEvent + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwShutdownSystem( + _In_ SHUTDOWN_ACTION Action + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwShutdownWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _Inout_ volatile LONG *PendingWorkerCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSignalAndWaitForSingleObject( + _In_ HANDLE SignalHandle, + _In_ HANDLE WaitHandle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSinglePhaseReject( + _In_ HANDLE EnlistmentHandle, + _In_opt_ PLARGE_INTEGER TmVirtualClock + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwStartProfile( + _In_ HANDLE ProfileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwStopProfile( + _In_ HANDLE ProfileHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSubscribeWnfStateChange( + _In_ PCWNF_STATE_NAME StateName, + _In_opt_ WNF_CHANGE_STAMP ChangeStamp, + _In_ ULONG EventMask, + _Out_opt_ PULONG64 SubscriptionId + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSuspendProcess( + _In_ HANDLE ProcessHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSuspendThread( + _In_ HANDLE ThreadHandle, + _Out_opt_ PULONG PreviousSuspendCount + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwSystemDebugControl( + _In_ SYSDBG_COMMAND Command, + _Inout_updates_bytes_opt_(InputBufferLength) PVOID InputBuffer, + _In_ ULONG InputBufferLength, + _Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, + _In_ ULONG OutputBufferLength, + _Out_opt_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTerminateJobObject( + _In_ HANDLE JobHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTerminateProcess( + _In_opt_ HANDLE ProcessHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTerminateThread( + _In_opt_ HANDLE ThreadHandle, + _In_ NTSTATUS ExitStatus + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTestAlert( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwThawRegistry( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwThawTransactions( + VOID + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTraceControl( + _In_ ULONG FunctionCode, + _In_reads_bytes_opt_(InBufferLen) PVOID InBuffer, + _In_ ULONG InBufferLen, + _Out_writes_bytes_opt_(OutBufferLen) PVOID OutBuffer, + _In_ ULONG OutBufferLen, + _Out_ PULONG ReturnLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTraceEvent( + _In_ HANDLE TraceHandle, + _In_ ULONG Flags, + _In_ ULONG FieldSize, + _In_ PVOID Fields + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwTranslateFilePath( + _In_ PFILE_PATH InputFilePath, + _In_ ULONG OutputType, + _Out_writes_bytes_opt_(*OutputFilePathLength) PFILE_PATH OutputFilePath, + _Inout_opt_ PULONG OutputFilePathLength + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUmsThreadYield( + _In_ PVOID SchedulerParam + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnloadDriver( + _In_ PUNICODE_STRING DriverServiceName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnloadKey( + _In_ POBJECT_ATTRIBUTES TargetKey + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnloadKey2( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnloadKeyEx( + _In_ POBJECT_ATTRIBUTES TargetKey, + _In_opt_ HANDLE Event + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnlockFile( + _In_ HANDLE FileHandle, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PLARGE_INTEGER ByteOffset, + _In_ PLARGE_INTEGER Length, + _In_ ULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnlockVirtualMemory( + _In_ HANDLE ProcessHandle, + _Inout_ PVOID *BaseAddress, + _Inout_ PSIZE_T RegionSize, + _In_ ULONG MapType + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnmapViewOfSection( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnmapViewOfSectionEx( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_ ULONG Flags + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUnsubscribeWnfStateChange( + _In_ PCWNF_STATE_NAME StateName + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwUpdateWnfStateData( + _In_ PCWNF_STATE_NAME StateName, + _In_reads_bytes_opt_(Length) const VOID *Buffer, + _In_opt_ ULONG Length, + _In_opt_ PCWNF_TYPE_ID TypeId, + _In_opt_ const VOID *ExplicitScope, + _In_ WNF_CHANGE_STAMP MatchingChangeStamp, + _In_ LOGICAL CheckStamp + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwVdmControl( + _In_ VDMSERVICECLASS Service, + _Inout_ PVOID ServiceData + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForAlertByThreadId( + _In_ PVOID Address, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForDebugEvent( + _In_ HANDLE DebugObjectHandle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout, + _Out_ PVOID WaitStateChange + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForKeyedEvent( + _In_ HANDLE KeyedEventHandle, + _In_ PVOID KeyValue, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForMultipleObjects( + _In_ ULONG Count, + _In_reads_(Count) HANDLE Handles[], + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForMultipleObjects32( + _In_ ULONG Count, + _In_reads_(Count) LONG Handles[], + _In_ WAIT_TYPE WaitType, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForSingleObject( + _In_ HANDLE Handle, + _In_ BOOLEAN Alertable, + _In_opt_ PLARGE_INTEGER Timeout + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitForWorkViaWorkerFactory( + _In_ HANDLE WorkerFactoryHandle, + _Out_ struct _FILE_IO_COMPLETION_INFORMATION *MiniPacket + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitHighEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWaitLowEventPair( + _In_ HANDLE EventPairHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWorkerFactoryWorkerReady( + _In_ HANDLE WorkerFactoryHandle + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWriteFile( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_reads_bytes_(Length) PVOID Buffer, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWriteFileGather( + _In_ HANDLE FileHandle, + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ PFILE_SEGMENT_ELEMENT SegmentArray, + _In_ ULONG Length, + _In_opt_ PLARGE_INTEGER ByteOffset, + _In_opt_ PULONG Key + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWriteRequestData( + _In_ HANDLE PortHandle, + _In_ PPORT_MESSAGE Message, + _In_ ULONG DataEntryIndex, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesWritten + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwWriteVirtualMemory( + _In_ HANDLE ProcessHandle, + _In_opt_ PVOID BaseAddress, + _In_reads_bytes_(BufferSize) PVOID Buffer, + _In_ SIZE_T BufferSize, + _Out_opt_ PSIZE_T NumberOfBytesWritten + ); + +NTSYSCALLAPI +NTSTATUS +NTAPI +ZwYieldExecution( + VOID + ); + +#endif diff --git a/phnt/include/phnt.h b/phnt/include/phnt.h new file mode 100644 index 0000000..f725155 --- /dev/null +++ b/phnt/include/phnt.h @@ -0,0 +1,100 @@ +#ifndef _PHNT_H +#define _PHNT_H + +// This header file provides access to NT APIs. + +// Definitions are annotated to indicate their source. If a definition is not annotated, it has been +// retrieved from an official Microsoft source (NT headers, DDK headers, winnt.h). + +// * "winbase" indicates that a definition has been reconstructed from a Win32-ized NT definition in +// winbase.h. +// * "rev" indicates that a definition has been reverse-engineered. +// * "dbg" indicates that a definition has been obtained from a debug message or assertion in a +// checked build of the kernel or file. + +// Reliability: +// 1. No annotation. +// 2. dbg. +// 3. symbols, private. Types may be incorrect. +// 4. winbase. Names and types may be incorrect. +// 5. rev. + +// Mode +#define PHNT_MODE_KERNEL 0 +#define PHNT_MODE_USER 1 + +// Version +#define PHNT_WIN2K 50 +#define PHNT_WINXP 51 +#define PHNT_WS03 52 +#define PHNT_VISTA 60 +#define PHNT_WIN7 61 +#define PHNT_WIN8 62 +#define PHNT_WINBLUE 63 +#define PHNT_THRESHOLD 100 +#define PHNT_THRESHOLD2 101 + +#ifndef PHNT_MODE +#define PHNT_MODE PHNT_MODE_USER +#endif + +#ifndef PHNT_VERSION +#define PHNT_VERSION PHNT_WINXP +#endif + +// Options + +//#define PHNT_NO_INLINE_INIT_STRING + +#ifdef __cplusplus +extern "C" { +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#include +#include +#include +#endif + +#include +#include + +#include +#include +#include + +#if (PHNT_MODE != PHNT_MODE_KERNEL) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#if (PHNT_MODE != PHNT_MODE_KERNEL) + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/phnt/include/phnt_ntdef.h b/phnt/include/phnt_ntdef.h new file mode 100644 index 0000000..36cc781 --- /dev/null +++ b/phnt/include/phnt_ntdef.h @@ -0,0 +1,306 @@ +#ifndef _PHNT_NTDEF_H +#define _PHNT_NTDEF_H + +#ifndef _NTDEF_ +#define _NTDEF_ + +// This header file provides basic NT types not included in Win32. If you have included winnt.h +// (perhaps indirectly), you must use this file instead of ntdef.h. + +#ifndef NOTHING +#define NOTHING +#endif + +// Basic types + +typedef struct _QUAD +{ + union + { + __int64 UseThisFieldToCopy; + double DoNotUseThisField; + }; +} QUAD, *PQUAD; + +// This isn't in NT, but it's useful. +typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _QUAD_PTR +{ + ULONG_PTR DoNotUseThisField1; + ULONG_PTR DoNotUseThisField2; +} QUAD_PTR, *PQUAD_PTR; + +typedef ULONG LOGICAL; +typedef ULONG *PLOGICAL; + +typedef _Success_(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; + +// Cardinal types + +typedef char CCHAR; +typedef short CSHORT; +typedef ULONG CLONG; + +typedef CCHAR *PCCHAR; +typedef CSHORT *PCSHORT; +typedef CLONG *PCLONG; + +typedef PCSTR PCSZ; + +// Specific + +typedef UCHAR KIRQL, *PKIRQL; +typedef LONG KPRIORITY; +typedef USHORT RTL_ATOM, *PRTL_ATOM; + +typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; + +// NT status macros + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1) +#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2) +#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) + +#define NT_FACILITY_MASK 0xfff +#define NT_FACILITY_SHIFT 16 +#define NT_FACILITY(Status) ((((ULONG)(Status)) >> NT_FACILITY_SHIFT) & NT_FACILITY_MASK) + +#define NT_NTWIN32(Status) (NT_FACILITY(Status) == FACILITY_NTWIN32) +#define WIN32_FROM_NTSTATUS(Status) (((ULONG)(Status)) & 0xffff) + +// Functions + +#ifndef _WIN64 +#define FASTCALL __fastcall +#else +#define FASTCALL +#endif + +// Synchronization enumerations + +typedef enum _EVENT_TYPE +{ + NotificationEvent, + SynchronizationEvent +} EVENT_TYPE; + +typedef enum _TIMER_TYPE +{ + NotificationTimer, + SynchronizationTimer +} TIMER_TYPE; + +typedef enum _WAIT_TYPE +{ + WaitAll, + WaitAny, + WaitNotification +} WAIT_TYPE; + +// Strings + +typedef struct _STRING +{ + USHORT Length; + USHORT MaximumLength; + _Field_size_bytes_part_opt_(MaximumLength, Length) PCHAR Buffer; +} STRING, *PSTRING, ANSI_STRING, *PANSI_STRING, OEM_STRING, *POEM_STRING; + +typedef const STRING *PCSTRING; +typedef const ANSI_STRING *PCANSI_STRING; +typedef const OEM_STRING *PCOEM_STRING; + +typedef struct _UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; + _Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef const UNICODE_STRING *PCUNICODE_STRING; + +#define RTL_CONSTANT_STRING(s) { sizeof(s) - sizeof((s)[0]), sizeof(s), s } + +// Balanced tree node + +#define RTL_BALANCED_NODE_RESERVED_PARENT_MASK 3 + +typedef struct _RTL_BALANCED_NODE +{ + union + { + struct _RTL_BALANCED_NODE *Children[2]; + struct + { + struct _RTL_BALANCED_NODE *Left; + struct _RTL_BALANCED_NODE *Right; + }; + }; + union + { + UCHAR Red : 1; + UCHAR Balance : 2; + ULONG_PTR ParentValue; + }; +} RTL_BALANCED_NODE, *PRTL_BALANCED_NODE; + +#define RTL_BALANCED_NODE_GET_PARENT_POINTER(Node) \ + ((PRTL_BALANCED_NODE)((Node)->ParentValue & ~RTL_BALANCED_NODE_RESERVED_PARENT_MASK)) + +// Portability + +typedef struct _SINGLE_LIST_ENTRY32 +{ + ULONG Next; +} SINGLE_LIST_ENTRY32, *PSINGLE_LIST_ENTRY32; + +typedef struct _STRING32 +{ + USHORT Length; + USHORT MaximumLength; + ULONG Buffer; +} STRING32, *PSTRING32; + +typedef STRING32 UNICODE_STRING32, *PUNICODE_STRING32; +typedef STRING32 ANSI_STRING32, *PANSI_STRING32; + +typedef struct _STRING64 +{ + USHORT Length; + USHORT MaximumLength; + ULONGLONG Buffer; +} STRING64, *PSTRING64; + +typedef STRING64 UNICODE_STRING64, *PUNICODE_STRING64; +typedef STRING64 ANSI_STRING64, *PANSI_STRING64; + +// Object attributes + +#define OBJ_INHERIT 0x00000002 +#define OBJ_PERMANENT 0x00000010 +#define OBJ_EXCLUSIVE 0x00000020 +#define OBJ_CASE_INSENSITIVE 0x00000040 +#define OBJ_OPENIF 0x00000080 +#define OBJ_OPENLINK 0x00000100 +#define OBJ_KERNEL_HANDLE 0x00000200 +#define OBJ_FORCE_ACCESS_CHECK 0x00000400 +#define OBJ_IGNORE_IMPERSONATED_DEVICEMAP 0x00000800 +#define OBJ_DONT_REPARSE 0x00001000 +#define OBJ_VALID_ATTRIBUTES 0x00001ff2 + +typedef struct _OBJECT_ATTRIBUTES +{ + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR; + PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE +} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + +typedef const OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES; + +#define InitializeObjectAttributes(p, n, a, r, s) { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } + +#define RTL_CONSTANT_OBJECT_ATTRIBUTES(n, a) { sizeof(OBJECT_ATTRIBUTES), NULL, n, a, NULL, NULL } +#define RTL_INIT_OBJECT_ATTRIBUTES(n, a) RTL_CONSTANT_OBJECT_ATTRIBUTES(n, a) + +// Portability + +typedef struct _OBJECT_ATTRIBUTES64 +{ + ULONG Length; + ULONG64 RootDirectory; + ULONG64 ObjectName; + ULONG Attributes; + ULONG64 SecurityDescriptor; + ULONG64 SecurityQualityOfService; +} OBJECT_ATTRIBUTES64, *POBJECT_ATTRIBUTES64; + +typedef const OBJECT_ATTRIBUTES64 *PCOBJECT_ATTRIBUTES64; + +typedef struct _OBJECT_ATTRIBUTES32 +{ + ULONG Length; + ULONG RootDirectory; + ULONG ObjectName; + ULONG Attributes; + ULONG SecurityDescriptor; + ULONG SecurityQualityOfService; +} OBJECT_ATTRIBUTES32, *POBJECT_ATTRIBUTES32; + +typedef const OBJECT_ATTRIBUTES32 *PCOBJECT_ATTRIBUTES32; + +// Product types + +typedef enum _NT_PRODUCT_TYPE +{ + NtProductWinNt = 1, + NtProductLanManNt, + NtProductServer +} NT_PRODUCT_TYPE, *PNT_PRODUCT_TYPE; + +typedef enum _SUITE_TYPE +{ + SmallBusiness, + Enterprise, + BackOffice, + CommunicationServer, + TerminalServer, + SmallBusinessRestricted, + EmbeddedNT, + DataCenter, + SingleUserTS, + Personal, + Blade, + EmbeddedRestricted, + SecurityAppliance, + StorageServer, + ComputeServer, + WHServer, + PhoneNT, + MaxSuiteType +} SUITE_TYPE; + +// Specific + +typedef struct _CLIENT_ID +{ + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef struct _CLIENT_ID32 +{ + ULONG UniqueProcess; + ULONG UniqueThread; +} CLIENT_ID32, *PCLIENT_ID32; + +typedef struct _CLIENT_ID64 +{ + ULONGLONG UniqueProcess; + ULONGLONG UniqueThread; +} CLIENT_ID64, *PCLIENT_ID64; + +#include + +typedef struct _KSYSTEM_TIME +{ + ULONG LowPart; + LONG High1Time; + LONG High2Time; +} KSYSTEM_TIME, *PKSYSTEM_TIME; + +#include + +#endif + +#endif diff --git a/phnt/include/phnt_windows.h b/phnt/include/phnt_windows.h new file mode 100644 index 0000000..fbce925 --- /dev/null +++ b/phnt/include/phnt_windows.h @@ -0,0 +1,53 @@ +#ifndef _PHNT_WINDOWS_H +#define _PHNT_WINDOWS_H + +// This header file provides access to Win32, plus NTSTATUS values and some access mask values. + +#define WIN32_LEAN_AND_MEAN +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include + +typedef double DOUBLE; +typedef GUID *PGUID; + +// Desktop access rights +#define DESKTOP_ALL_ACCESS \ + (DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_ENUMERATE | \ + DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | \ + DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP | DESKTOP_WRITEOBJECTS | \ + STANDARD_RIGHTS_REQUIRED) +#define DESKTOP_GENERIC_READ \ + (DESKTOP_ENUMERATE | DESKTOP_READOBJECTS | STANDARD_RIGHTS_READ) +#define DESKTOP_GENERIC_WRITE \ + (DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | \ + DESKTOP_JOURNALPLAYBACK | DESKTOP_JOURNALRECORD | DESKTOP_WRITEOBJECTS | \ + STANDARD_RIGHTS_WRITE) +#define DESKTOP_GENERIC_EXECUTE \ + (DESKTOP_SWITCHDESKTOP | STANDARD_RIGHTS_EXECUTE) + +// Window station access rights +#define WINSTA_GENERIC_READ \ + (WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE | WINSTA_READATTRIBUTES | \ + WINSTA_READSCREEN | STANDARD_RIGHTS_READ) +#define WINSTA_GENERIC_WRITE \ + (WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP | WINSTA_WRITEATTRIBUTES | \ + STANDARD_RIGHTS_WRITE) +#define WINSTA_GENERIC_EXECUTE \ + (WINSTA_ACCESSGLOBALATOMS | WINSTA_EXITWINDOWS | STANDARD_RIGHTS_EXECUTE) + +// WMI access rights +#define WMIGUID_GENERIC_READ \ + (WMIGUID_QUERY | WMIGUID_NOTIFICATION | WMIGUID_READ_DESCRIPTION | \ + STANDARD_RIGHTS_READ) +#define WMIGUID_GENERIC_WRITE \ + (WMIGUID_SET | TRACELOG_CREATE_REALTIME | TRACELOG_CREATE_ONDISK | \ + STANDARD_RIGHTS_WRITE) +#define WMIGUID_GENERIC_EXECUTE \ + (WMIGUID_EXECUTE | TRACELOG_GUID_ENABLE | TRACELOG_LOG_EVENT | \ + TRACELOG_ACCESS_REALTIME | TRACELOG_REGISTER_GUIDS | \ + STANDARD_RIGHTS_EXECUTE) + +#endif diff --git a/phnt/include/subprocesstag.h b/phnt/include/subprocesstag.h new file mode 100644 index 0000000..c8ea0cc --- /dev/null +++ b/phnt/include/subprocesstag.h @@ -0,0 +1,96 @@ +#ifndef _SUBPROCESSTAG_H +#define _SUBPROCESSTAG_H + +// Subprocess tag information + +typedef enum _TAG_INFO_LEVEL +{ + eTagInfoLevelNameFromTag = 1, // TAG_INFO_NAME_FROM_TAG + eTagInfoLevelNamesReferencingModule, // TAG_INFO_NAMES_REFERENCING_MODULE + eTagInfoLevelNameTagMapping, // TAG_INFO_NAME_TAG_MAPPING + eTagInfoLevelMax +} TAG_INFO_LEVEL; + +typedef enum _TAG_TYPE +{ + eTagTypeService = 1, + eTagTypeMax +} TAG_TYPE; + +typedef struct _TAG_INFO_NAME_FROM_TAG_IN_PARAMS +{ + DWORD dwPid; + DWORD dwTag; +} TAG_INFO_NAME_FROM_TAG_IN_PARAMS, *PTAG_INFO_NAME_FROM_TAG_IN_PARAMS; + +typedef struct _TAG_INFO_NAME_FROM_TAG_OUT_PARAMS +{ + DWORD eTagType; + LPWSTR pszName; +} TAG_INFO_NAME_FROM_TAG_OUT_PARAMS, *PTAG_INFO_NAME_FROM_TAG_OUT_PARAMS; + +typedef struct _TAG_INFO_NAME_FROM_TAG +{ + TAG_INFO_NAME_FROM_TAG_IN_PARAMS InParams; + TAG_INFO_NAME_FROM_TAG_OUT_PARAMS OutParams; +} TAG_INFO_NAME_FROM_TAG, *PTAG_INFO_NAME_FROM_TAG; + +typedef struct _TAG_INFO_NAMES_REFERENCING_MODULE_IN_PARAMS +{ + DWORD dwPid; + LPWSTR pszModule; +} TAG_INFO_NAMES_REFERENCING_MODULE_IN_PARAMS, *PTAG_INFO_NAMES_REFERENCING_MODULE_IN_PARAMS; + +typedef struct _TAG_INFO_NAMES_REFERENCING_MODULE_OUT_PARAMS +{ + DWORD eTagType; + LPWSTR pmszNames; +} TAG_INFO_NAMES_REFERENCING_MODULE_OUT_PARAMS, *PTAG_INFO_NAMES_REFERENCING_MODULE_OUT_PARAMS; + +typedef struct _TAG_INFO_NAMES_REFERENCING_MODULE +{ + TAG_INFO_NAMES_REFERENCING_MODULE_IN_PARAMS InParams; + TAG_INFO_NAMES_REFERENCING_MODULE_OUT_PARAMS OutParams; +} TAG_INFO_NAMES_REFERENCING_MODULE, *PTAG_INFO_NAMES_REFERENCING_MODULE; + +typedef struct _TAG_INFO_NAME_TAG_MAPPING_IN_PARAMS +{ + DWORD dwPid; +} TAG_INFO_NAME_TAG_MAPPING_IN_PARAMS, *PTAG_INFO_NAME_TAG_MAPPING_IN_PARAMS; + +typedef struct _TAG_INFO_NAME_TAG_MAPPING_ELEMENT +{ + DWORD eTagType; + DWORD dwTag; + LPWSTR pszName; + LPWSTR pszGroupName; +} TAG_INFO_NAME_TAG_MAPPING_ELEMENT, *PTAG_INFO_NAME_TAG_MAPPING_ELEMENT; + +typedef struct _TAG_INFO_NAME_TAG_MAPPING_OUT_PARAMS +{ + DWORD cElements; + PTAG_INFO_NAME_TAG_MAPPING_ELEMENT pNameTagMappingElements; +} TAG_INFO_NAME_TAG_MAPPING_OUT_PARAMS, *PTAG_INFO_NAME_TAG_MAPPING_OUT_PARAMS; + +typedef struct _TAG_INFO_NAME_TAG_MAPPING +{ + TAG_INFO_NAME_TAG_MAPPING_IN_PARAMS InParams; + PTAG_INFO_NAME_TAG_MAPPING_OUT_PARAMS pOutParams; +} TAG_INFO_NAME_TAG_MAPPING, *PTAG_INFO_NAME_TAG_MAPPING; + +_Must_inspect_result_ +DWORD +WINAPI +I_QueryTagInformation( + _In_opt_ LPCWSTR pszMachineName, + _In_ TAG_INFO_LEVEL eInfoLevel, + _Inout_ PVOID pTagInfo + ); + +typedef DWORD (WINAPI *PQUERY_TAG_INFORMATION)( + _In_opt_ LPCWSTR pszMachineName, + _In_ TAG_INFO_LEVEL eInfoLevel, + _Inout_ PVOID pTagInfo + ); + +#endif diff --git a/phnt/include/winsta.h b/phnt/include/winsta.h new file mode 100644 index 0000000..84d7e19 --- /dev/null +++ b/phnt/include/winsta.h @@ -0,0 +1,748 @@ +#ifndef _WINSTA_H +#define _WINSTA_H + +// begin_msdn:http://msdn.microsoft.com/en-us/library/cc248779%28PROT.10%29.aspx + +// Access rights + +#define WINSTATION_QUERY 0x00000001 // WinStationQueryInformation +#define WINSTATION_SET 0x00000002 // WinStationSetInformation +#define WINSTATION_RESET 0x00000004 // WinStationReset +#define WINSTATION_VIRTUAL 0x00000008 //read/write direct data +#define WINSTATION_SHADOW 0x00000010 // WinStationShadow +#define WINSTATION_LOGON 0x00000020 // logon to WinStation +#define WINSTATION_LOGOFF 0x00000040 // WinStationLogoff +#define WINSTATION_MSG 0x00000080 // WinStationMsg +#define WINSTATION_CONNECT 0x00000100 // WinStationConnect +#define WINSTATION_DISCONNECT 0x00000200 // WinStationDisconnect +#define WINSTATION_GUEST_ACCESS WINSTATION_LOGON + +#define WINSTATION_CURRENT_GUEST_ACCESS (WINSTATION_VIRTUAL | WINSTATION_LOGOFF) +#define WINSTATION_USER_ACCESS (WINSTATION_GUEST_ACCESS | WINSTATION_QUERY | WINSTATION_CONNECT) +#define WINSTATION_CURRENT_USER_ACCESS \ + (WINSTATION_SET | WINSTATION_RESET | WINSTATION_VIRTUAL | \ + WINSTATION_LOGOFF | WINSTATION_DISCONNECT) +#define WINSTATION_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | WINSTATION_QUERY | \ + WINSTATION_SET | WINSTATION_RESET | WINSTATION_VIRTUAL | \ + WINSTATION_SHADOW | WINSTATION_LOGON | WINSTATION_MSG | \ + WINSTATION_CONNECT | WINSTATION_DISCONNECT) + +#define WDPREFIX_LENGTH 12 +#define STACK_ADDRESS_LENGTH 128 +#define MAX_BR_NAME 65 +#define DIRECTORY_LENGTH 256 +#define INITIALPROGRAM_LENGTH 256 +#define USERNAME_LENGTH 20 +#define DOMAIN_LENGTH 17 +#define PASSWORD_LENGTH 14 +#define NASISPECIFICNAME_LENGTH 14 +#define NASIUSERNAME_LENGTH 47 +#define NASIPASSWORD_LENGTH 24 +#define NASISESSIONNAME_LENGTH 16 +#define NASIFILESERVER_LENGTH 47 + +#define CLIENTDATANAME_LENGTH 7 +#define CLIENTNAME_LENGTH 20 +#define CLIENTADDRESS_LENGTH 30 +#define IMEFILENAME_LENGTH 32 +#define DIRECTORY_LENGTH 256 +#define CLIENTLICENSE_LENGTH 32 +#define CLIENTMODEM_LENGTH 40 +#define CLIENT_PRODUCT_ID_LENGTH 32 +#define MAX_COUNTER_EXTENSIONS 2 +#define WINSTATIONNAME_LENGTH 32 + +#define TERMSRV_TOTAL_SESSIONS 1 +#define TERMSRV_DISC_SESSIONS 2 +#define TERMSRV_RECON_SESSIONS 3 +#define TERMSRV_CURRENT_ACTIVE_SESSIONS 4 +#define TERMSRV_CURRENT_DISC_SESSIONS 5 +#define TERMSRV_PENDING_SESSIONS 6 +#define TERMSRV_SUCC_TOTAL_LOGONS 7 +#define TERMSRV_SUCC_LOCAL_LOGONS 8 +#define TERMSRV_SUCC_REMOTE_LOGONS 9 +#define TERMSRV_SUCC_SESSION0_LOGONS 10 +#define TERMSRV_CURRENT_TERMINATING_SESSIONS 11 +#define TERMSRV_CURRENT_LOGGEDON_SESSIONS 12 + +typedef RTL_TIME_ZONE_INFORMATION TS_TIME_ZONE_INFORMATION, *PTS_TIME_ZONE_INFORMATION; + +typedef WCHAR WINSTATIONNAME[WINSTATIONNAME_LENGTH + 1]; + +// Variable length data descriptor (not needed) +typedef struct _VARDATA_WIRE +{ + USHORT Size; + USHORT Offset; +} VARDATA_WIRE, *PVARDATA_WIRE; + +typedef enum _WINSTATIONSTATECLASS +{ + State_Active = 0, + State_Connected = 1, + State_ConnectQuery = 2, + State_Shadow = 3, + State_Disconnected = 4, + State_Idle = 5, + State_Listen = 6, + State_Reset = 7, + State_Down = 8, + State_Init = 9 +} WINSTATIONSTATECLASS; + +typedef struct _SESSIONIDW +{ + union + { + ULONG SessionId; + ULONG LogonId; + }; + WINSTATIONNAME WinStationName; + WINSTATIONSTATECLASS State; +} SESSIONIDW, *PSESSIONIDW; + +// private +typedef enum _WINSTATIONINFOCLASS +{ + WinStationCreateData, + WinStationConfiguration, + WinStationPdParams, + WinStationWd, + WinStationPd, + WinStationPrinter, + WinStationClient, + WinStationModules, + WinStationInformation, + WinStationTrace, + WinStationBeep, + WinStationEncryptionOff, + WinStationEncryptionPerm, + WinStationNtSecurity, + WinStationUserToken, + WinStationUnused1, + WinStationVideoData, + WinStationInitialProgram, + WinStationCd, + WinStationSystemTrace, + WinStationVirtualData, + WinStationClientData, + WinStationSecureDesktopEnter, + WinStationSecureDesktopExit, + WinStationLoadBalanceSessionTarget, + WinStationLoadIndicator, + WinStationShadowInfo, + WinStationDigProductId, + WinStationLockedState, + WinStationRemoteAddress, + WinStationIdleTime, + WinStationLastReconnectType, + WinStationDisallowAutoReconnect, + WinStationMprNotifyInfo, + WinStationExecSrvSystemPipe, + WinStationSmartCardAutoLogon, + WinStationIsAdminLoggedOn, + WinStationReconnectedFromId, + WinStationEffectsPolicy, + WinStationType, + WinStationInformationEx, + WinStationValidationInfo +} WINSTATIONINFOCLASS; + +// WinStationCreateData +typedef struct _WINSTATIONCREATE +{ + ULONG fEnableWinStation : 1; + ULONG MaxInstanceCount; +} WINSTATIONCREATE, *PWINSTATIONCREATE; + +// WinStationClient +typedef struct _WINSTATIONCLIENT +{ + ULONG fTextOnly : 1; + ULONG fDisableCtrlAltDel : 1; + ULONG fMouse : 1; + ULONG fDoubleClickDetect : 1; + ULONG fINetClient : 1; + ULONG fPromptForPassword : 1; + ULONG fMaximizeShell : 1; + ULONG fEnableWindowsKey : 1; + ULONG fRemoteConsoleAudio : 1; + ULONG fPasswordIsScPin : 1; + ULONG fNoAudioPlayback : 1; + ULONG fUsingSavedCreds : 1; + WCHAR ClientName[CLIENTNAME_LENGTH + 1]; + WCHAR Domain[DOMAIN_LENGTH + 1]; + WCHAR UserName[USERNAME_LENGTH + 1]; + WCHAR Password[PASSWORD_LENGTH + 1]; + WCHAR WorkDirectory[DIRECTORY_LENGTH + 1]; + WCHAR InitialProgram[INITIALPROGRAM_LENGTH + 1]; + ULONG SerialNumber; + BYTE EncryptionLevel; + ULONG ClientAddressFamily; + WCHAR ClientAddress[CLIENTADDRESS_LENGTH + 1]; + USHORT HRes; + USHORT VRes; + USHORT ColorDepth; + USHORT ProtocolType; + ULONG KeyboardLayout; + ULONG KeyboardType; + ULONG KeyboardSubType; + ULONG KeyboardFunctionKey; + WCHAR ImeFileName[IMEFILENAME_LENGTH + 1]; + WCHAR ClientDirectory[DIRECTORY_LENGTH + 1]; + WCHAR ClientLicense[CLIENTLICENSE_LENGTH + 1]; + WCHAR ClientModem[CLIENTMODEM_LENGTH + 1]; + ULONG ClientBuildNumber; + ULONG ClientHardwareId; + USHORT ClientProductId; + USHORT OutBufCountHost; + USHORT OutBufCountClient; + USHORT OutBufLength; + WCHAR AudioDriverName[9]; + TS_TIME_ZONE_INFORMATION ClientTimeZone; + ULONG ClientSessionId; + WCHAR ClientDigProductId[CLIENT_PRODUCT_ID_LENGTH]; + ULONG PerformanceFlags; + ULONG ActiveInputLocale; +} WINSTATIONCLIENT, *PWINSTATIONCLIENT; + +typedef struct _TSHARE_COUNTERS +{ + ULONG Reserved; +} TSHARE_COUNTERS, *PTSHARE_COUNTERS; + +typedef struct _PROTOCOLCOUNTERS +{ + ULONG WdBytes; + ULONG WdFrames; + ULONG WaitForOutBuf; + ULONG Frames; + ULONG Bytes; + ULONG CompressedBytes; + ULONG CompressFlushes; + ULONG Errors; + ULONG Timeouts; + ULONG AsyncFramingError; + ULONG AsyncOverrunError; + ULONG AsyncOverflowError; + ULONG AsyncParityError; + ULONG TdErrors; + USHORT ProtocolType; + USHORT Length; + union + { + TSHARE_COUNTERS TShareCounters; + ULONG Reserved[100]; + } Specific; +} PROTOCOLCOUNTERS, *PPROTOCOLCOUNTERS; + +typedef struct _THINWIRECACHE +{ + ULONG CacheReads; + ULONG CacheHits; +} THINWIRECACHE, *PTHINWIRECACHE; + +#define MAX_THINWIRECACHE 4 + +typedef struct _RESERVED_CACHE +{ + THINWIRECACHE ThinWireCache[MAX_THINWIRECACHE]; +} RESERVED_CACHE, *PRESERVED_CACHE; + +typedef struct _TSHARE_CACHE +{ + ULONG Reserved; +} TSHARE_CACHE, *PTSHARE_CACHE; + +typedef struct CACHE_STATISTICS +{ + USHORT ProtocolType; + USHORT Length; + union + { + RESERVED_CACHE ReservedCacheStats; + TSHARE_CACHE TShareCacheStats; + ULONG Reserved[20]; + } Specific; +} CACHE_STATISTICS, *PCACHE_STATISTICS; + +typedef struct _PROTOCOLSTATUS +{ + PROTOCOLCOUNTERS Output; + PROTOCOLCOUNTERS Input; + CACHE_STATISTICS Cache; + ULONG AsyncSignal; + ULONG AsyncSignalMask; +} PROTOCOLSTATUS, *PPROTOCOLSTATUS; + +// WinStationInformation +typedef struct _WINSTATIONINFORMATION +{ + WINSTATIONSTATECLASS ConnectState; + WINSTATIONNAME WinStationName; + ULONG LogonId; + LARGE_INTEGER ConnectTime; + LARGE_INTEGER DisconnectTime; + LARGE_INTEGER LastInputTime; + LARGE_INTEGER LogonTime; + PROTOCOLSTATUS Status; + WCHAR Domain[DOMAIN_LENGTH + 1]; + WCHAR UserName[USERNAME_LENGTH + 1]; + LARGE_INTEGER CurrentTime; +} WINSTATIONINFORMATION, *PWINSTATIONINFORMATION; + +// WinStationUserToken +typedef struct _WINSTATIONUSERTOKEN +{ + HANDLE ProcessId; + HANDLE ThreadId; + HANDLE UserToken; +} WINSTATIONUSERTOKEN, *PWINSTATIONUSERTOKEN; + +// WinStationVideoData +typedef struct _WINSTATIONVIDEODATA +{ + USHORT HResolution; + USHORT VResolution; + USHORT fColorDepth; +} WINSTATIONVIDEODATA, *PWINSTATIONVIDEODATA; + +// WinStationDigProductId +typedef struct _WINSTATIONPRODID +{ + WCHAR DigProductId[CLIENT_PRODUCT_ID_LENGTH]; + WCHAR ClientDigProductId[CLIENT_PRODUCT_ID_LENGTH]; + WCHAR OuterMostDigProductId[CLIENT_PRODUCT_ID_LENGTH]; + ULONG CurrentSessionId; + ULONG ClientSessionId; + ULONG OuterMostSessionId; +} WINSTATIONPRODID, *PWINSTATIONPRODID; + +// WinStationRemoteAddress +typedef struct _WINSTATIONREMOTEADDRESS +{ + USHORT sin_family; + union + { + struct + { + USHORT sin_port; + ULONG sin_addr; + UCHAR sin_zero[8]; + } ipv4; + struct + { + USHORT sin6_port; + ULONG sin6_flowinfo; + USHORT sin6_addr[8]; + ULONG sin6_scope_id; + } ipv6; + }; +} WINSTATIONREMOTEADDRESS, *PWINSTATIONREMOTEADDRESS; + +// WinStationInformationEx + +// private +typedef struct _WINSTATIONINFORMATIONEX_LEVEL1 +{ + ULONG SessionId; + WINSTATIONSTATECLASS SessionState; + LONG SessionFlags; + WINSTATIONNAME WinStationName; + WCHAR UserName[USERNAME_LENGTH + 1]; + WCHAR DomainName[DOMAIN_LENGTH + 1]; + LARGE_INTEGER LogonTime; + LARGE_INTEGER ConnectTime; + LARGE_INTEGER DisconnectTime; + LARGE_INTEGER LastInputTime; + LARGE_INTEGER CurrentTime; + PROTOCOLSTATUS ProtocolStatus; +} WINSTATIONINFORMATIONEX_LEVEL1, *PWINSTATIONINFORMATIONEX_LEVEL1; + +// private +typedef struct _WINSTATIONINFORMATIONEX_LEVEL2 +{ + ULONG SessionId; + WINSTATIONSTATECLASS SessionState; + LONG SessionFlags; + WINSTATIONNAME WinStationName; + WCHAR SamCompatibleUserName[USERNAME_LENGTH + 1]; + WCHAR SamCompatibleDomainName[DOMAIN_LENGTH + 1]; + LARGE_INTEGER LogonTime; + LARGE_INTEGER ConnectTime; + LARGE_INTEGER DisconnectTime; + LARGE_INTEGER LastInputTime; + LARGE_INTEGER CurrentTime; + PROTOCOLSTATUS ProtocolStatus; + WCHAR UserName[257]; + WCHAR DomainName[256]; +} WINSTATIONINFORMATIONEX_LEVEL2, *PWINSTATIONINFORMATIONEX_LEVEL2; + +// private +typedef union _WINSTATIONINFORMATIONEX_LEVEL +{ + WINSTATIONINFORMATIONEX_LEVEL1 WinStationInfoExLevel1; + WINSTATIONINFORMATIONEX_LEVEL2 WinStationInfoExLevel2; +} WINSTATIONINFORMATIONEX_LEVEL, *PWINSTATIONINFORMATIONEX_LEVEL; + +// private +typedef struct _WINSTATIONINFORMATIONEX +{ + ULONG Level; + WINSTATIONINFORMATIONEX_LEVEL Data; +} WINSTATIONINFORMATIONEX, *PWINSTATIONINFORMATIONEX; + +#define TS_PROCESS_INFO_MAGIC_NT4 0x23495452 + +typedef struct _TS_PROCESS_INFORMATION_NT4 +{ + ULONG MagicNumber; + ULONG LogonId; + PVOID ProcessSid; + ULONG Pad; +} TS_PROCESS_INFORMATION_NT4, *PTS_PROCESS_INFORMATION_NT4; + +#define SIZEOF_TS4_SYSTEM_THREAD_INFORMATION 64 +#define SIZEOF_TS4_SYSTEM_PROCESS_INFORMATION 136 + +typedef struct _TS_SYS_PROCESS_INFORMATION +{ + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER SpareLi1; + LARGE_INTEGER SpareLi2; + LARGE_INTEGER SpareLi3; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + LONG BasePriority; + ULONG UniqueProcessId; + ULONG InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG SpareUl3; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + ULONG PeakWorkingSetSize; + ULONG WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; +} TS_SYS_PROCESS_INFORMATION, *PTS_SYS_PROCESS_INFORMATION; + +typedef struct _TS_ALL_PROCESSES_INFO +{ + PTS_SYS_PROCESS_INFORMATION pTsProcessInfo; + ULONG SizeOfSid; + PSID pSid; +} TS_ALL_PROCESSES_INFO, *PTS_ALL_PROCESSES_INFO; + +typedef struct _TS_COUNTER_HEADER +{ + DWORD dwCounterID; + BOOLEAN bResult; +} TS_COUNTER_HEADER, *PTS_COUNTER_HEADER; + +typedef struct _TS_COUNTER +{ + TS_COUNTER_HEADER CounterHead; + DWORD dwValue; + LARGE_INTEGER StartTime; +} TS_COUNTER, *PTS_COUNTER; + +// Flags for WinStationShutdownSystem +#define WSD_LOGOFF 0x1 +#define WSD_SHUTDOWN 0x2 +#define WSD_REBOOT 0x4 +#define WSD_POWEROFF 0x8 + +// Flags for WinStationWaitSystemEvent +#define WEVENT_NONE 0x0 +#define WEVENT_CREATE 0x1 +#define WEVENT_DELETE 0x2 +#define WEVENT_RENAME 0x4 +#define WEVENT_CONNECT 0x8 +#define WEVENT_DISCONNECT 0x10 +#define WEVENT_LOGON 0x20 +#define WEVENT_LOGOFF 0x40 +#define WEVENT_STATECHANGE 0x80 +#define WEVENT_LICENSE 0x100 +#define WEVENT_ALL 0x7fffffff +#define WEVENT_FLUSH 0x80000000 + +// Hotkey modifiers for WinStationShadow +#define KBDSHIFT 0x1 +#define KBDCTRL 0x2 +#define KBDALT 0x4 + +// begin_rev +// Flags for WinStationRegisterConsoleNotification +#define WNOTIFY_ALL_SESSIONS 0x1 +// end_rev + +// In the functions below, memory returned can be freed using LocalFree. NULL can be specified for +// server handles to indicate the local server. -1 can be specified for session IDs to indicate the +// current session ID. + +#define LOGONID_CURRENT (-1) +#define SERVERNAME_CURRENT (NULL) + +// rev +BOOLEAN +WINAPI +WinStationFreeMemory( + _In_ PVOID Buffer + ); + +// rev +HANDLE +WINAPI +WinStationOpenServerW( + _In_ PWSTR ServerName + ); + +// rev +BOOLEAN +WINAPI +WinStationCloseServer( + _In_ HANDLE hServer + ); + +// rev +BOOLEAN +WINAPI +WinStationServerPing( + _In_opt_ HANDLE hServer + ); + +// rev +BOOLEAN +WINAPI +WinStationGetTermSrvCountersValue( + _In_opt_ HANDLE hServer, + _In_ ULONG Count, + _Inout_ PTS_COUNTER Counters // set counter IDs before calling + ); + +BOOLEAN +WINAPI +WinStationShutdownSystem( + _In_opt_ HANDLE hServer, + _In_ ULONG ShutdownFlags // WSD_* + ); + +// rev +BOOLEAN +WINAPI +WinStationWaitSystemEvent( + _In_opt_ HANDLE hServer, + _In_ ULONG EventMask, // WEVENT_* + _Out_ PULONG EventFlags + ); + +// rev +BOOLEAN +WINAPI +WinStationRegisterConsoleNotification( + _In_opt_ HANDLE hServer, + _In_ HWND WindowHandle, + _In_ ULONG Flags + ); + +// rev +BOOLEAN +WINAPI +WinStationUnRegisterConsoleNotification( + _In_opt_ HANDLE hServer, + _In_ HWND WindowHandle + ); + +// Sessions + +// rev +BOOLEAN +WINAPI +WinStationEnumerateW( + _In_opt_ HANDLE hServer, + _Out_ PSESSIONIDW *SessionIds, + _Out_ PULONG Count + ); + +BOOLEAN +WINAPI +WinStationQueryInformationW( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ WINSTATIONINFOCLASS WinStationInformationClass, + _Out_writes_bytes_(WinStationInformationLength) PVOID pWinStationInformation, + _In_ ULONG WinStationInformationLength, + _Out_ PULONG pReturnLength + ); + +// rev +BOOLEAN +WINAPI +WinStationSetInformationW( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ WINSTATIONINFOCLASS WinStationInformationClass, + _In_reads_bytes_(WinStationInformationLength) PVOID pWinStationInformation, + _In_ ULONG WinStationInformationLength + ); + +BOOLEAN +WINAPI +WinStationNameFromLogonIdW( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _Out_writes_(WINSTATIONNAME_LENGTH + 1) PWSTR pWinStationName + ); + +// rev +BOOLEAN +WINAPI +WinStationSendMessageW( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ PWSTR Title, + _In_ ULONG TitleLength, + _In_ PWSTR Message, + _In_ ULONG MessageLength, + _In_ ULONG Style, + _In_ ULONG Timeout, + _Out_ PULONG Response, + _In_ BOOLEAN DoNotWait + ); + +BOOLEAN +WINAPI +WinStationConnectW( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ ULONG TargetSessionId, + _In_opt_ PWSTR pPassword, + _In_ BOOLEAN bWait + ); + +BOOLEAN +WINAPI +WinStationDisconnect( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ BOOLEAN bWait + ); + +// rev +BOOLEAN +WINAPI +WinStationReset( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ BOOLEAN bWait + ); + +// rev +BOOLEAN +WINAPI +WinStationShadow( + _In_opt_ HANDLE hServer, + _In_ PWSTR TargetServerName, + _In_ ULONG TargetSessionId, + _In_ UCHAR HotKeyVk, + _In_ USHORT HotkeyModifiers // KBD* + ); + +// rev +BOOLEAN +WINAPI +WinStationShadowStop( + _In_opt_ HANDLE hServer, + _In_ ULONG SessionId, + _In_ BOOLEAN bWait // ignored + ); + +// Processes + +// rev +BOOLEAN +WINAPI +WinStationEnumerateProcesses( + _In_opt_ HANDLE hServer, + _Out_ PVOID *Processes + ); + +// rev +BOOLEAN +WINAPI +WinStationGetAllProcesses( + _In_opt_ HANDLE hServer, + _In_ ULONG Level, + _Out_ PULONG NumberOfProcesses, + _Out_ PTS_ALL_PROCESSES_INFO *Processes + ); + +// rev +BOOLEAN +WINAPI +WinStationFreeGAPMemory( + _In_ ULONG Level, + _In_ PTS_ALL_PROCESSES_INFO Processes, + _In_ ULONG NumberOfProcesses + ); + +// rev +BOOLEAN +WINAPI +WinStationTerminateProcess( + _In_opt_ HANDLE hServer, + _In_ ULONG ProcessId, + _In_ ULONG ExitCode + ); + +BOOLEAN +WINAPI +WinStationGetProcessSid( + _In_opt_ HANDLE hServer, + _In_ ULONG ProcessId, + _In_ FILETIME ProcessStartTime, + _Out_ PVOID pProcessUserSid, + _Inout_ PULONG dwSidSize + ); + +// Services isolation + +#if (PHNT_VERSION >= PHNT_VISTA) + +// rev +BOOLEAN +WINAPI +WinStationSwitchToServicesSession( + VOID + ); + +// rev +BOOLEAN +WINAPI +WinStationRevertFromServicesSession( + VOID + ); + +#endif + +// Misc. + +BOOLEAN +WINAPI +_WinStationWaitForConnect( + VOID + ); + +// end_msdn + +#endif diff --git a/phnt/zw_options.txt b/phnt/zw_options.txt new file mode 100644 index 0000000..ed0c2e6 --- /dev/null +++ b/phnt/zw_options.txt @@ -0,0 +1,5 @@ +base=include +in=ntdbg.h;ntexapi.h;ntgdi.h;ntioapi.h;ntkeapi.h;ntldr.h;ntlpcapi.h;ntmisc.h;ntmmapi.h;ntnls.h;ntobapi.h;ntpebteb.h;ntpfapi.h;ntpnpapi.h;ntpoapi.h;ntpsapi.h;ntregapi.h;ntrtl.h;ntsam.h;ntseapi.h;nttmapi.h;nttp.h;ntwow64.h;ntxcapi.h +out=ntzwapi.h +header=#ifndef _NTZWAPI_H\r\n#define _NTZWAPI_H\r\n\r\n// This file was automatically generated. Do not edit.\r\n\r\n +footer=#endif\r\n \ No newline at end of file diff --git a/plugins/DotNetTools/CHANGELOG.txt b/plugins/DotNetTools/CHANGELOG.txt new file mode 100644 index 0000000..c30876c --- /dev/null +++ b/plugins/DotNetTools/CHANGELOG.txt @@ -0,0 +1,26 @@ +1.6 + * Fixed .NET assembly tab performance issues + * Added extra .NET memory counters to the .NET performance tab + * Added "Show sizes in bytes" checkbox to the .NET performance tab + * Added right-click menu to the .NET assembly tab + * Removed all Win32 functions for querying .NET process performance + +1.5 + * Rewrite of .NET Performance statistics and AppDomain enumeration + +1.4 + * Process Hacker now displays managed stack traces for 32-bit .NET processes on 64-bit Windows + * Fixed inaccurate stack traces when clicking Refresh + * Added AppDomain column for threads in .NET programs + +1.3 + * Improved .NET assembly enumeration timeout handling + +1.2 + * Fixed inaccurate stack traces for certain .NET programs + +1.1 + * Added managed symbol resolution for thread stacks + +1.0 + * Initial release \ No newline at end of file diff --git a/plugins/DotNetTools/DotNetTools.rc b/plugins/DotNetTools/DotNetTools.rc new file mode 100644 index 0000000..05b45fc --- /dev/null +++ b/plugins/DotNetTools/DotNetTools.rc @@ -0,0 +1,182 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,6,0,0 + PRODUCTVERSION 1,6,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", ".NET tools plugin for Process Hacker" + VALUE "FileVersion", "1.6" + VALUE "InternalName", "DotNetTools" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "DotNetTools.dll" + VALUE "ProductName", ".NET tools plugin for Process Hacker" + VALUE "ProductVersion", "1.6" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROCDOTNETPERF DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION ".NET performance" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_APPDOMAINS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SORTASCENDING | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,246,59 + LTEXT "Categories:",IDC_STATIC,7,72,38,8 + COMBOBOX IDC_CATEGORIES,49,70,204,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_COUNTERS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,88,246,154 + CONTROL "Show sizes in bytes",IDC_DOTNET_PERF_SHOWBYTES,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,244,78,10 +END + +IDD_PROCDOTNETASM DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION ".NET assemblies" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL ".NET assemblies",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | 0x1a,7,7,246,246,WS_EX_CLIENTEDGE +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROCDOTNETPERF, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_PROCDOTNETASM, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_PROCDOTNETASM AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCDOTNETPERF AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_ASSEMBLY_MENU MENU +BEGIN + POPUP "CLR" + BEGIN + MENUITEM "Open &file location", ID_CLR_OPENFILELOCATION + MENUITEM "&Copy\aCtrl+C", ID_CLR_COPY + END +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/DotNetTools/DotNetTools.vcxproj b/plugins/DotNetTools/DotNetTools.vcxproj new file mode 100644 index 0000000..2dbab1d --- /dev/null +++ b/plugins/DotNetTools/DotNetTools.vcxproj @@ -0,0 +1,116 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2B3235B0-31E1-44DA-81A1-183F40517E47} + DotNetTools + Win32Proj + DotNetTools + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/DotNetTools/DotNetTools.vcxproj.filters b/plugins/DotNetTools/DotNetTools.vcxproj.filters new file mode 100644 index 0000000..8947de4 --- /dev/null +++ b/plugins/DotNetTools/DotNetTools.vcxproj.filters @@ -0,0 +1,86 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {6b9aae9c-fc27-4d95-b258-53eb88081e1b} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\clr + + + Header Files\clr + + + Header Files\clr + + + Header Files\clr + + + Header Files\clr + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/DotNetTools/asmpage.c b/plugins/DotNetTools/asmpage.c new file mode 100644 index 0000000..1833a74 --- /dev/null +++ b/plugins/DotNetTools/asmpage.c @@ -0,0 +1,1327 @@ +/* + * Process Hacker .NET Tools - + * .NET Assemblies property page + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "clretw.h" +#include +#include + +#define DNATNC_STRUCTURE 0 +#define DNATNC_ID 1 +#define DNATNC_FLAGS 2 +#define DNATNC_PATH 3 +#define DNATNC_NATIVEPATH 4 +#define DNATNC_MAXIMUM 5 + +#define DNA_TYPE_CLR 1 +#define DNA_TYPE_APPDOMAIN 2 +#define DNA_TYPE_ASSEMBLY 3 + +#define UPDATE_MSG (WM_APP + 1) + +typedef struct _DNA_NODE +{ + PH_TREENEW_NODE Node; + + struct _DNA_NODE *Parent; + PPH_LIST Children; + + PH_STRINGREF TextCache[DNATNC_MAXIMUM]; + + ULONG Type; + BOOLEAN IsFakeClr; + + union + { + struct + { + USHORT ClrInstanceID; + PPH_STRING DisplayName; + } Clr; + struct + { + ULONG64 AppDomainID; + PPH_STRING DisplayName; + } AppDomain; + struct + { + ULONG64 AssemblyID; + PPH_STRING FullyQualifiedAssemblyName; + } Assembly; + } u; + + PH_STRINGREF StructureText; + PPH_STRING IdText; + PPH_STRING FlagsText; + PPH_STRING PathText; + PPH_STRING NativePathText; +} DNA_NODE, *PDNA_NODE; + +typedef struct _ASMPAGE_CONTEXT +{ + HWND WindowHandle; + PPH_PROCESS_ITEM ProcessItem; + ULONG ClrVersions; + PDNA_NODE ClrV2Node; + + HWND TnHandle; + PPH_STRING TnErrorMessage; + PPH_LIST NodeList; + PPH_LIST NodeRootList; +} ASMPAGE_CONTEXT, *PASMPAGE_CONTEXT; + +typedef struct _ASMPAGE_QUERY_CONTEXT +{ + HANDLE WindowHandle; + + HANDLE ProcessId; + ULONG ClrVersions; + PDNA_NODE ClrV2Node; + + BOOLEAN TraceClrV2; + ULONG TraceResult; + LONG TraceHandleActive; + TRACEHANDLE TraceHandle; + + PPH_LIST NodeList; + PPH_LIST NodeRootList; +} ASMPAGE_QUERY_CONTEXT, *PASMPAGE_QUERY_CONTEXT; + +typedef struct _FLAG_DEFINITION +{ + PWSTR Name; + ULONG Flag; +} FLAG_DEFINITION, *PFLAG_DEFINITION; + +typedef ULONG (__stdcall *_EnableTraceEx)( + _In_ LPCGUID ProviderId, + _In_opt_ LPCGUID SourceId, + _In_ TRACEHANDLE TraceHandle, + _In_ ULONG IsEnabled, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_ ULONG EnableProperty, + _In_opt_ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc + ); + +VOID DestroyDotNetTraceQuery( + _In_ PASMPAGE_QUERY_CONTEXT Context + ); + +INT_PTR CALLBACK DotNetAsmPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static UNICODE_STRING DotNetLoggerName = RTL_CONSTANT_STRING(L"PhDnLogger"); +static GUID ClrRuntimeProviderGuid = { 0xe13c0d23, 0xccbc, 0x4e12, { 0x93, 0x1b, 0xd9, 0xcc, 0x2e, 0xee, 0x27, 0xe4 } }; +static GUID ClrRundownProviderGuid = { 0xa669021c, 0xc450, 0x4609, { 0xa0, 0x35, 0x5a, 0xf5, 0x9a, 0xf4, 0xdf, 0x18 } }; + +static FLAG_DEFINITION AppDomainFlagsMap[] = +{ + { L"Default", 0x1 }, + { L"Executable", 0x2 }, + { L"Shared", 0x4 } +}; + +static FLAG_DEFINITION AssemblyFlagsMap[] = +{ + { L"DomainNeutral", 0x1 }, + { L"Dynamic", 0x2 }, + { L"Native", 0x4 }, + { L"Collectible", 0x8 } +}; + +static FLAG_DEFINITION ModuleFlagsMap[] = +{ + { L"DomainNeutral", 0x1 }, + { L"Native", 0x2 }, + { L"Dynamic", 0x4 }, + { L"Manifest", 0x8 } +}; + +static FLAG_DEFINITION StartupModeMap[] = +{ + { L"ManagedExe", 0x1 }, + { L"HostedCLR", 0x2 }, + { L"IjwDll", 0x4 }, + { L"ComActivated", 0x8 }, + { L"Other", 0x10 } +}; + +static FLAG_DEFINITION StartupFlagsMap[] = +{ + { L"CONCURRENT_GC", 0x1 }, + { L"LOADER_OPTIMIZATION_SINGLE_DOMAIN", 0x2 }, + { L"LOADER_OPTIMIZATION_MULTI_DOMAIN", 0x4 }, + { L"LOADER_SAFEMODE", 0x10 }, + { L"LOADER_SETPREFERENCE", 0x100 }, + { L"SERVER_GC", 0x1000 }, + { L"HOARD_GC_VM", 0x2000 }, + { L"SINGLE_VERSION_HOSTING_INTERFACE", 0x4000 }, + { L"LEGACY_IMPERSONATION", 0x10000 }, + { L"DISABLE_COMMITTHREADSTACK", 0x20000 }, + { L"ALWAYSFLOW_IMPERSONATION", 0x40000 }, + { L"TRIM_GC_COMMIT", 0x80000 }, + { L"ETW", 0x100000 }, + { L"SERVER_BUILD", 0x200000 }, + { L"ARM", 0x400000 } +}; + +VOID AddAsmPageToPropContext( + _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext + ) +{ + PhAddProcessPropPage( + PropContext->PropContext, + PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCDOTNETASM), DotNetAsmPageDlgProc, NULL) + ); +} + +PPH_STRING FlagsToString( + _In_ ULONG Flags, + _In_ PFLAG_DEFINITION Map, + _In_ ULONG SizeOfMap + ) +{ + PH_STRING_BUILDER sb; + ULONG i; + + PhInitializeStringBuilder(&sb, 100); + + for (i = 0; i < SizeOfMap / sizeof(FLAG_DEFINITION); i++) + { + if (Flags & Map[i].Flag) + { + PhAppendStringBuilder2(&sb, Map[i].Name); + PhAppendStringBuilder2(&sb, L", "); + } + } + + if (sb.String->Length != 0) + PhRemoveEndStringBuilder(&sb, 2); + + return PhFinalStringBuilderString(&sb); +} + +PDNA_NODE AddNode( + _Inout_ PASMPAGE_QUERY_CONTEXT Context + ) +{ + PDNA_NODE node; + + node = PhAllocate(sizeof(DNA_NODE)); + memset(node, 0, sizeof(DNA_NODE)); + PhInitializeTreeNewNode(&node->Node); + + memset(node->TextCache, 0, sizeof(PH_STRINGREF) * DNATNC_MAXIMUM); + node->Node.TextCache = node->TextCache; + node->Node.TextCacheSize = DNATNC_MAXIMUM; + + node->Children = PhCreateList(1); + + PhAddItemList(Context->NodeList, node); + + return node; +} + +VOID DestroyNode( + _In_ PDNA_NODE Node + ) +{ + PhDereferenceObject(Node->Children); + + if (Node->Type == DNA_TYPE_CLR) + { + if (Node->u.Clr.DisplayName) PhDereferenceObject(Node->u.Clr.DisplayName); + } + else if (Node->Type == DNA_TYPE_APPDOMAIN) + { + if (Node->u.AppDomain.DisplayName) PhDereferenceObject(Node->u.AppDomain.DisplayName); + } + else if (Node->Type == DNA_TYPE_ASSEMBLY) + { + if (Node->u.Assembly.FullyQualifiedAssemblyName) PhDereferenceObject(Node->u.Assembly.FullyQualifiedAssemblyName); + } + + if (Node->IdText) PhDereferenceObject(Node->IdText); + if (Node->FlagsText) PhDereferenceObject(Node->FlagsText); + if (Node->PathText) PhDereferenceObject(Node->PathText); + if (Node->NativePathText) PhDereferenceObject(Node->NativePathText); + + PhFree(Node); +} + +PDNA_NODE AddFakeClrNode( + _In_ PASMPAGE_QUERY_CONTEXT Context, + _In_ PWSTR DisplayName + ) +{ + PDNA_NODE node; + + node = AddNode(Context); + node->Type = DNA_TYPE_CLR; + node->IsFakeClr = TRUE; + node->u.Clr.ClrInstanceID = 0; + node->u.Clr.DisplayName = NULL; + PhInitializeStringRef(&node->StructureText, DisplayName); + + PhAddItemList(Context->NodeRootList, node); + + return node; +} + +PDNA_NODE FindClrNode( + _In_ PASMPAGE_QUERY_CONTEXT Context, + _In_ USHORT ClrInstanceID + ) +{ + ULONG i; + + for (i = 0; i < Context->NodeRootList->Count; i++) + { + PDNA_NODE node = Context->NodeRootList->Items[i]; + + if (!node->IsFakeClr && node->u.Clr.ClrInstanceID == ClrInstanceID) + return node; + } + + return NULL; +} + +PDNA_NODE FindAppDomainNode( + _In_ PDNA_NODE ClrNode, + _In_ ULONG64 AppDomainID + ) +{ + ULONG i; + + for (i = 0; i < ClrNode->Children->Count; i++) + { + PDNA_NODE node = ClrNode->Children->Items[i]; + + if (node->u.AppDomain.AppDomainID == AppDomainID) + return node; + } + + return NULL; +} + +PDNA_NODE FindAssemblyNode( + _In_ PDNA_NODE AppDomainNode, + _In_ ULONG64 AssemblyID + ) +{ + ULONG i; + + for (i = 0; i < AppDomainNode->Children->Count; i++) + { + PDNA_NODE node = AppDomainNode->Children->Items[i]; + + if (node->u.Assembly.AssemblyID == AssemblyID) + return node; + } + + return NULL; +} + +PDNA_NODE FindAssemblyNode2( + _In_ PDNA_NODE ClrNode, + _In_ ULONG64 AssemblyID + ) +{ + ULONG i; + ULONG j; + + for (i = 0; i < ClrNode->Children->Count; i++) + { + PDNA_NODE appDomainNode = ClrNode->Children->Items[i]; + + for (j = 0; j < appDomainNode->Children->Count; j++) + { + PDNA_NODE assemblyNode = appDomainNode->Children->Items[j]; + + if (assemblyNode->u.Assembly.AssemblyID == AssemblyID) + return assemblyNode; + } + } + + return NULL; +} + +static int __cdecl AssemblyNodeNameCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PDNA_NODE node1 = *(PDNA_NODE *)elem1; + PDNA_NODE node2 = *(PDNA_NODE *)elem2; + + return PhCompareStringRef(&node1->StructureText, &node2->StructureText, TRUE); +} + +PDNA_NODE DotNetAsmGetSelectedEntry( + _In_ PASMPAGE_CONTEXT Context + ) +{ + if (Context->NodeList) + { + for (ULONG i = 0; i < Context->NodeList->Count; i++) + { + PDNA_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + { + return node; + } + } + } + + return NULL; +} + +VOID DotNetAsmShowContextMenu( + _In_ PASMPAGE_CONTEXT Context, + _In_ POINT Location + ) +{ + PDNA_NODE node; + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + + if (!(node = DotNetAsmGetSelectedEntry(Context))) + return; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_ASSEMBLY_MENU), 0); + + if (PhIsNullOrEmptyString(node->PathText) || !RtlDoesFileExists_U(node->PathText->Buffer)) + { + PhSetFlagsEMenuItem(menu, ID_CLR_OPENFILELOCATION, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + + selectedItem = PhShowEMenu( + menu, + Context->WindowHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + Location.x, + Location.y + ); + + if (selectedItem && selectedItem->Id != -1) + { + switch (selectedItem->Id) + { + case ID_CLR_OPENFILELOCATION: + { + if (!PhIsNullOrEmptyString(node->PathText) && RtlDoesFileExists_U(node->PathText->Buffer)) + { + PhShellExploreFile(Context->WindowHandle, node->PathText->Buffer); + } + } + break; + case ID_CLR_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(Context->TnHandle, 0); + PhSetClipboardString(Context->TnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + } + } + + PhDestroyEMenu(menu); +} + +BOOLEAN NTAPI DotNetAsmTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PASMPAGE_CONTEXT context; + PDNA_NODE node; + + context = Context; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + node = (PDNA_NODE)getChildren->Node; + + if (!node) + { + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeRootList->Items; + getChildren->NumberOfChildren = context->NodeRootList->Count; + } + else + { + if (node->Type == DNA_TYPE_APPDOMAIN || node == context->ClrV2Node) + { + // Sort the assemblies. + qsort(node->Children->Items, node->Children->Count, sizeof(PVOID), AssemblyNodeNameCompareFunction); + } + + getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; + getChildren->NumberOfChildren = node->Children->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + node = (PDNA_NODE)isLeaf->Node; + + isLeaf->IsLeaf = node->Children->Count == 0; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + + node = (PDNA_NODE)getCellText->Node; + + switch (getCellText->Id) + { + case DNATNC_STRUCTURE: + getCellText->Text = node->StructureText; + break; + case DNATNC_ID: + getCellText->Text = PhGetStringRef(node->IdText); + break; + case DNATNC_FLAGS: + getCellText->Text = PhGetStringRef(node->FlagsText); + break; + case DNATNC_PATH: + getCellText->Text = PhGetStringRef(node->PathText); + break; + case DNATNC_NATIVEPATH: + getCellText->Text = PhGetStringRef(node->NativePathText); + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + + node = (PDNA_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0 || node->Type != DNA_TYPE_ASSEMBLY) + return FALSE; + + if (!PhIsNullOrEmptyString(node->u.Assembly.FullyQualifiedAssemblyName)) + { + getCellTooltip->Text = node->u.Assembly.FullyQualifiedAssemblyName->sr; + getCellTooltip->Unfolding = FALSE; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->WindowHandle, WM_COMMAND, ID_COPY, 0); + break; + } + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; + + DotNetAsmShowContextMenu(context, mouseEvent->Location); + } + return TRUE; + } + + return FALSE; +} + +ULONG StartDotNetTrace( + _Out_ PTRACEHANDLE SessionHandle, + _Out_ PEVENT_TRACE_PROPERTIES *Properties + ) +{ + ULONG result; + ULONG bufferSize; + PEVENT_TRACE_PROPERTIES properties; + TRACEHANDLE sessionHandle; + + bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + DotNetLoggerName.Length + sizeof(WCHAR); + properties = PhAllocate(bufferSize); + memset(properties, 0, sizeof(EVENT_TRACE_PROPERTIES)); + + properties->Wnode.BufferSize = bufferSize; + properties->Wnode.ClientContext = 2; // System time clock resolution + properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY; + properties->LogFileNameOffset = 0; + properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + + result = StartTrace(&sessionHandle, DotNetLoggerName.Buffer, properties); + + if (result == ERROR_SUCCESS) + { + *SessionHandle = sessionHandle; + *Properties = properties; + + return ERROR_SUCCESS; + } + else if (result == ERROR_ALREADY_EXISTS) + { + // Session already exists, so use that. Get the existing session handle. + + result = ControlTrace(0, DotNetLoggerName.Buffer, properties, EVENT_TRACE_CONTROL_QUERY); + + if (result != ERROR_SUCCESS) + { + PhFree(properties); + return result; + } + + *SessionHandle = properties->Wnode.HistoricalContext; + *Properties = properties; + + return ERROR_SUCCESS; + } + else + { + PhFree(properties); + + return result; + } +} + +ULONG NTAPI DotNetBufferCallback( + _In_ PEVENT_TRACE_LOGFILE Buffer + ) +{ + return TRUE; +} + +VOID NTAPI DotNetEventCallback( + _In_ PEVENT_RECORD EventRecord + ) +{ + PASMPAGE_QUERY_CONTEXT context = EventRecord->UserContext; + PEVENT_HEADER eventHeader = &EventRecord->EventHeader; + PEVENT_DESCRIPTOR eventDescriptor = &eventHeader->EventDescriptor; + + if (UlongToHandle(eventHeader->ProcessId) == context->ProcessId) + { + // .NET 4.0+ + + switch (eventDescriptor->Id) + { + case RuntimeInformationDCStart: + { + PRuntimeInformationRundown data = EventRecord->UserData; + PDNA_NODE node; + PPH_STRING startupFlagsString; + PPH_STRING startupModeString; + + // Check for duplicates. + if (FindClrNode(context, data->ClrInstanceID)) + break; + + node = AddNode(context); + node->Type = DNA_TYPE_CLR; + node->u.Clr.ClrInstanceID = data->ClrInstanceID; + node->u.Clr.DisplayName = PhFormatString(L"CLR v%u.%u.%u.%u", data->VMMajorVersion, data->VMMinorVersion, data->VMBuildNumber, data->VMQfeNumber); + node->StructureText = node->u.Clr.DisplayName->sr; + node->IdText = PhFormatString(L"%u", data->ClrInstanceID); + + startupFlagsString = FlagsToString(data->StartupFlags, StartupFlagsMap, sizeof(StartupFlagsMap)); + startupModeString = FlagsToString(data->StartupMode, StartupModeMap, sizeof(StartupModeMap)); + + if (startupFlagsString->Length != 0 && startupModeString->Length != 0) + { + node->FlagsText = PhConcatStrings(3, startupFlagsString->Buffer, L", ", startupModeString->Buffer); + PhDereferenceObject(startupFlagsString); + PhDereferenceObject(startupModeString); + } + else if (startupFlagsString->Length != 0) + { + node->FlagsText = startupFlagsString; + PhDereferenceObject(startupModeString); + } + else if (startupModeString->Length != 0) + { + node->FlagsText = startupModeString; + PhDereferenceObject(startupFlagsString); + } + + if (data->CommandLine[0]) + node->PathText = PhCreateString(data->CommandLine); + + PhAddItemList(context->NodeRootList, node); + } + break; + case AppDomainDCStart_V1: + { + PAppDomainLoadUnloadRundown_V1 data = EventRecord->UserData; + SIZE_T appDomainNameLength; + USHORT clrInstanceID; + PDNA_NODE parentNode; + PDNA_NODE node; + + appDomainNameLength = PhCountStringZ(data->AppDomainName) * sizeof(WCHAR); + clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AppDomainLoadUnloadRundown_V1, AppDomainName) + appDomainNameLength + sizeof(WCHAR) + sizeof(ULONG)); + + // Find the CLR node to add the AppDomain node to. + parentNode = FindClrNode(context, clrInstanceID); + + if (parentNode) + { + // Check for duplicates. + if (FindAppDomainNode(parentNode, data->AppDomainID)) + break; + + node = AddNode(context); + node->Type = DNA_TYPE_APPDOMAIN; + node->u.AppDomain.AppDomainID = data->AppDomainID; + node->u.AppDomain.DisplayName = PhConcatStrings2(L"AppDomain: ", data->AppDomainName); + node->StructureText = node->u.AppDomain.DisplayName->sr; + node->IdText = PhFormatString(L"%I64u", data->AppDomainID); + node->FlagsText = FlagsToString(data->AppDomainFlags, AppDomainFlagsMap, sizeof(AppDomainFlagsMap)); + + PhAddItemList(parentNode->Children, node); + } + } + break; + case AssemblyDCStart_V1: + { + PAssemblyLoadUnloadRundown_V1 data = EventRecord->UserData; + SIZE_T fullyQualifiedAssemblyNameLength; + USHORT clrInstanceID; + PDNA_NODE parentNode; + PDNA_NODE node; + PH_STRINGREF remainingPart; + + fullyQualifiedAssemblyNameLength = PhCountStringZ(data->FullyQualifiedAssemblyName) * sizeof(WCHAR); + clrInstanceID = *(PUSHORT)((PCHAR)data + FIELD_OFFSET(AssemblyLoadUnloadRundown_V1, FullyQualifiedAssemblyName) + fullyQualifiedAssemblyNameLength + sizeof(WCHAR)); + + // Find the AppDomain node to add the Assembly node to. + + parentNode = FindClrNode(context, clrInstanceID); + + if (parentNode) + parentNode = FindAppDomainNode(parentNode, data->AppDomainID); + + if (parentNode) + { + // Check for duplicates. + if (FindAssemblyNode(parentNode, data->AssemblyID)) + break; + + node = AddNode(context); + node->Type = DNA_TYPE_ASSEMBLY; + node->u.Assembly.AssemblyID = data->AssemblyID; + node->u.Assembly.FullyQualifiedAssemblyName = PhCreateStringEx(data->FullyQualifiedAssemblyName, fullyQualifiedAssemblyNameLength); + + // Display only the assembly name, not the whole fully qualified name. + if (!PhSplitStringRefAtChar(&node->u.Assembly.FullyQualifiedAssemblyName->sr, ',', &node->StructureText, &remainingPart)) + node->StructureText = node->u.Assembly.FullyQualifiedAssemblyName->sr; + + node->IdText = PhFormatString(L"%I64u", data->AssemblyID); + node->FlagsText = FlagsToString(data->AssemblyFlags, AssemblyFlagsMap, sizeof(AssemblyFlagsMap)); + + PhAddItemList(parentNode->Children, node); + } + } + break; + case ModuleDCStart_V1: + { + PModuleLoadUnloadRundown_V1 data = EventRecord->UserData; + PWSTR moduleILPath; + SIZE_T moduleILPathLength; + PWSTR moduleNativePath; + SIZE_T moduleNativePathLength; + USHORT clrInstanceID; + PDNA_NODE node; + + moduleILPath = data->ModuleILPath; + moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR); + moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR)); + moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR); + clrInstanceID = *(PUSHORT)((PCHAR)moduleNativePath + moduleNativePathLength + sizeof(WCHAR)); + + // Find the Assembly node to set the path on. + + node = FindClrNode(context, clrInstanceID); + + if (node) + node = FindAssemblyNode2(node, data->AssemblyID); + + if (node) + { + PhMoveReference(&node->PathText, PhCreateStringEx(moduleILPath, moduleILPathLength)); + + if (moduleNativePathLength != 0) + PhMoveReference(&node->NativePathText, PhCreateStringEx(moduleNativePath, moduleNativePathLength)); + } + } + break; + case DCStartComplete_V1: + { + if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1) + { + CloseTrace(context->TraceHandle); + } + } + break; + } + + // .NET 2.0 + + if (eventDescriptor->Id == 0) + { + switch (eventDescriptor->Opcode) + { + case CLR_MODULEDCSTART_OPCODE: + { + PModuleLoadUnloadRundown_V1 data = EventRecord->UserData; + PWSTR moduleILPath; + SIZE_T moduleILPathLength; + PWSTR moduleNativePath; + SIZE_T moduleNativePathLength; + PDNA_NODE node; + ULONG_PTR indexOfBackslash; + ULONG_PTR indexOfLastDot; + + moduleILPath = data->ModuleILPath; + moduleILPathLength = PhCountStringZ(moduleILPath) * sizeof(WCHAR); + moduleNativePath = (PWSTR)((PCHAR)moduleILPath + moduleILPathLength + sizeof(WCHAR)); + moduleNativePathLength = PhCountStringZ(moduleNativePath) * sizeof(WCHAR); + + if (context->ClrV2Node && (moduleILPathLength != 0 || moduleNativePathLength != 0)) + { + node = AddNode(context); + node->Type = DNA_TYPE_ASSEMBLY; + node->FlagsText = FlagsToString(data->ModuleFlags, ModuleFlagsMap, sizeof(ModuleFlagsMap)); + node->PathText = PhCreateStringEx(moduleILPath, moduleILPathLength); + + if (moduleNativePathLength != 0) + node->NativePathText = PhCreateStringEx(moduleNativePath, moduleNativePathLength); + + // Use the name between the last backslash and the last dot for the structure column text. + // (E.g. C:\...\AcmeSoft.BigLib.dll -> AcmeSoft.BigLib) + + indexOfBackslash = PhFindLastCharInString(node->PathText, 0, '\\'); + indexOfLastDot = PhFindLastCharInString(node->PathText, 0, '.'); + + if (indexOfBackslash != -1) + { + node->StructureText.Buffer = node->PathText->Buffer + indexOfBackslash + 1; + + if (indexOfLastDot != -1 && indexOfLastDot > indexOfBackslash) + { + node->StructureText.Length = (indexOfLastDot - indexOfBackslash - 1) * sizeof(WCHAR); + } + else + { + node->StructureText.Length = node->PathText->Length - indexOfBackslash * sizeof(WCHAR) - sizeof(WCHAR); + } + } + else + { + node->StructureText = node->PathText->sr; + } + + PhAddItemList(context->ClrV2Node->Children, node); + } + } + break; + case CLR_METHODDC_DCSTARTCOMPLETE_OPCODE: + { + if (_InterlockedExchange(&context->TraceHandleActive, 0) == 1) + { + CloseTrace(context->TraceHandle); + } + } + break; + } + } + } +} + +ULONG ProcessDotNetTrace( + _In_ PASMPAGE_QUERY_CONTEXT Context + ) +{ + ULONG result; + TRACEHANDLE traceHandle; + EVENT_TRACE_LOGFILE logFile; + + memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); + logFile.LoggerName = DotNetLoggerName.Buffer; + logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + logFile.BufferCallback = DotNetBufferCallback; + logFile.EventRecordCallback = DotNetEventCallback; + logFile.Context = Context; + + traceHandle = OpenTrace(&logFile); + + if (traceHandle == INVALID_PROCESSTRACE_HANDLE) + return GetLastError(); + + Context->TraceHandleActive = 1; + Context->TraceHandle = traceHandle; + result = ProcessTrace(&traceHandle, 1, NULL, NULL); + + if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1) + { + CloseTrace(traceHandle); + } + + return result; +} + +ULONG UpdateDotNetTraceInfo( + _In_ PASMPAGE_QUERY_CONTEXT Context, + _In_ BOOLEAN ClrV2 + ) +{ + static _EnableTraceEx EnableTraceEx_I = NULL; + + ULONG result; + TRACEHANDLE sessionHandle; + PEVENT_TRACE_PROPERTIES properties; + PGUID guidToEnable; + + if (!EnableTraceEx_I) + EnableTraceEx_I = PhGetModuleProcAddress(L"advapi32.dll", "EnableTraceEx"); + if (!EnableTraceEx_I) + return ERROR_NOT_SUPPORTED; + + result = StartDotNetTrace(&sessionHandle, &properties); + + if (result != 0) + return result; + + if (!ClrV2) + guidToEnable = &ClrRundownProviderGuid; + else + guidToEnable = &ClrRuntimeProviderGuid; + + EnableTraceEx_I( + guidToEnable, + NULL, + sessionHandle, + 1, + TRACE_LEVEL_INFORMATION, + CLR_LOADER_KEYWORD | CLR_STARTENUMERATION_KEYWORD, + 0, + 0, + NULL + ); + + result = ProcessDotNetTrace(Context); + + ControlTrace(sessionHandle, NULL, properties, EVENT_TRACE_CONTROL_STOP); + PhFree(properties); + + return result; +} + +NTSTATUS UpdateDotNetTraceInfoThreadStart( + _In_ PVOID Parameter + ) +{ + PASMPAGE_QUERY_CONTEXT context = Parameter; + + context->TraceResult = UpdateDotNetTraceInfo(context, context->TraceClrV2); + + return STATUS_SUCCESS; +} + +ULONG UpdateDotNetTraceInfoWithTimeout( + _In_ PASMPAGE_QUERY_CONTEXT Context, + _In_ BOOLEAN ClrV2, + _In_opt_ PLARGE_INTEGER Timeout + ) +{ + HANDLE threadHandle; + BOOLEAN timeout = FALSE; + + // ProcessDotNetTrace is not guaranteed to complete within any period of time, because + // the target process might terminate before it writes the DCStartComplete_V1 event. + // If the timeout is reached, the trace handle is closed, forcing ProcessTrace to stop + // processing. + + Context->TraceClrV2 = ClrV2; + Context->TraceResult = 0; + Context->TraceHandleActive = 0; + Context->TraceHandle = 0; + + threadHandle = PhCreateThread(0, UpdateDotNetTraceInfoThreadStart, Context); + + if (NtWaitForSingleObject(threadHandle, FALSE, Timeout) != STATUS_WAIT_0) + { + // Timeout has expired. Stop the trace processing if it's still active. + // BUG: This assumes that the thread is in ProcessTrace. It might still be + // setting up though! + if (_InterlockedExchange(&Context->TraceHandleActive, 0) == 1) + { + CloseTrace(Context->TraceHandle); + timeout = TRUE; + } + + NtWaitForSingleObject(threadHandle, FALSE, NULL); + } + + NtClose(threadHandle); + + if (timeout) + return ERROR_TIMEOUT; + + return Context->TraceResult; +} + +NTSTATUS DotNetTraceQueryThreadStart( + _In_ PVOID Parameter + ) +{ + LARGE_INTEGER timeout; + PASMPAGE_QUERY_CONTEXT context = Parameter; + BOOLEAN timeoutReached = FALSE; + BOOLEAN nonClrNode = FALSE; + ULONG i; + ULONG result = 0; + + if (context->ClrVersions & PH_CLR_VERSION_1_0) + { + AddFakeClrNode(context, L"CLR v1.0.3705"); // what PE displays + } + + if (context->ClrVersions & PH_CLR_VERSION_1_1) + { + AddFakeClrNode(context, L"CLR v1.1.4322"); + } + + timeout.QuadPart = -10 * PH_TIMEOUT_SEC; + + if (context->ClrVersions & PH_CLR_VERSION_2_0) + { + context->ClrV2Node = AddFakeClrNode(context, L"CLR v2.0.50727"); + result = UpdateDotNetTraceInfoWithTimeout(context, TRUE, &timeout); + + if (result == ERROR_TIMEOUT) + { + timeoutReached = TRUE; + result = ERROR_SUCCESS; + } + } + + if (context->ClrVersions & PH_CLR_VERSION_4_ABOVE) + { + result = UpdateDotNetTraceInfoWithTimeout(context, FALSE, &timeout); + + if (result == ERROR_TIMEOUT) + { + timeoutReached = TRUE; + result = ERROR_SUCCESS; + } + } + // If we reached the timeout, check whether we got any data back. + if (timeoutReached) + { + for (i = 0; i < context->NodeList->Count; i++) + { + PDNA_NODE node = context->NodeList->Items[i]; + + if (node->Type != DNA_TYPE_CLR) + { + nonClrNode = TRUE; + break; + } + } + + if (!nonClrNode) + result = ERROR_TIMEOUT; + } + + // If the process properties window has been closed, bail and cleanup. + // IsWindow should be safe from being called on this thread: + // https://blogs.msdn.microsoft.com/oldnewthing/20070717-00/?p=25983 + if (IsWindow(context->WindowHandle)) + { + PostMessage(context->WindowHandle, UPDATE_MSG, result, (LPARAM)context); + } + else + { + DestroyDotNetTraceQuery(context); + } + + return STATUS_SUCCESS; +} + +VOID CreateDotNetTraceQueryThread( + _In_ HWND WindowHandle, + _In_ ULONG ClrVersions, + _In_ HANDLE ProcessId + ) +{ + HANDLE threadHandle; + PASMPAGE_QUERY_CONTEXT context; + + context = PhAllocate(sizeof(ASMPAGE_QUERY_CONTEXT)); + memset(context, 0, sizeof(ASMPAGE_QUERY_CONTEXT)); + + context->WindowHandle = WindowHandle; + context->ClrVersions = ClrVersions; + context->ProcessId = ProcessId; + context->NodeList = PhCreateList(64); + context->NodeRootList = PhCreateList(2); + + if (threadHandle = PhCreateThread(0, DotNetTraceQueryThreadStart, context)) + { + NtClose(threadHandle); + } + else + { + DestroyDotNetTraceQuery(context); + } +} + +VOID DestroyDotNetTraceQuery( + _In_ PASMPAGE_QUERY_CONTEXT Context + ) +{ + if (Context->NodeList) + { + PhClearReference(&Context->NodeList); + } + + if (Context->NodeRootList) + { + PhClearReference(&Context->NodeRootList); + } + + PhFree(Context); +} + +BOOLEAN IsProcessSuspended( + _In_ HANDLE ProcessId + ) +{ + PVOID processes; + PSYSTEM_PROCESS_INFORMATION process; + + if (NT_SUCCESS(PhEnumProcesses(&processes))) + { + if (process = PhFindProcessInformation(processes, ProcessId)) + return PhGetProcessIsSuspended(process); + + PhFree(processes); + } + + return FALSE; +} + +INT_PTR CALLBACK DotNetAsmPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PASMPAGE_CONTEXT context; + + if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) + { + context = propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_STRING settings; + HWND tnHandle; + + context = PhAllocate(sizeof(ASMPAGE_CONTEXT)); + memset(context, 0, sizeof(ASMPAGE_CONTEXT)); + propPageContext->Context = context; + context->WindowHandle = hwndDlg; + context->ProcessItem = processItem; + + context->ClrVersions = 0; + PhGetProcessIsDotNetEx(processItem->ProcessId, NULL, 0, NULL, &context->ClrVersions); + + tnHandle = GetDlgItem(hwndDlg, IDC_LIST); + context->TnHandle = tnHandle; + + TreeNew_SetCallback(tnHandle, DotNetAsmTreeNewCallback, context); + TreeNew_SetExtendedFlags(tnHandle, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT); + PhSetControlTheme(tnHandle, L"explorer"); + SendMessage(TreeNew_GetTooltips(tnHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT); + PhAddTreeNewColumn(tnHandle, DNATNC_STRUCTURE, TRUE, L"Structure", 240, PH_ALIGN_LEFT, -2, 0); + PhAddTreeNewColumn(tnHandle, DNATNC_ID, TRUE, L"ID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT); + PhAddTreeNewColumn(tnHandle, DNATNC_FLAGS, TRUE, L"Flags", 120, PH_ALIGN_LEFT, 1, 0); + PhAddTreeNewColumn(tnHandle, DNATNC_PATH, TRUE, L"Path", 600, PH_ALIGN_LEFT, 2, 0); // don't use path ellipsis - the user already has the base file name + PhAddTreeNewColumn(tnHandle, DNATNC_NATIVEPATH, TRUE, L"Native image path", 600, PH_ALIGN_LEFT, 3, 0); + + settings = PhGetStringSetting(SETTING_NAME_ASM_TREE_LIST_COLUMNS); + PhCmLoadSettings(tnHandle, &settings->sr); + PhDereferenceObject(settings); + + PhSwapReference(&context->TnErrorMessage, PhCreateString(L"Loading .NET assemblies...")); + TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0); + + if ( + !IsProcessSuspended(processItem->ProcessId) || + PhShowMessage(hwndDlg, MB_ICONWARNING | MB_YESNO, L".NET assembly enumeration may not work properly because the process is currently suspended. Do you want to continue?") == IDYES + ) + { + CreateDotNetTraceQueryThread( + hwndDlg, + context->ClrVersions, + processItem->ProcessId + ); + } + else + { + PhSwapReference(&context->TnErrorMessage, + PhCreateString(L"Unable to start the event tracing session because the process is suspended.") + ); + TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0); + InvalidateRect(tnHandle, NULL, FALSE); + } + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PPH_STRING settings; + ULONG i; + + settings = PhCmSaveSettings(context->TnHandle); + PhSetStringSetting2(SETTING_NAME_ASM_TREE_LIST_COLUMNS, &settings->sr); + PhDereferenceObject(settings); + + if (context->NodeList) + { + for (i = 0; i < context->NodeList->Count; i++) + DestroyNode(context->NodeList->Items[i]); + + PhDereferenceObject(context->NodeList); + } + + if (context->NodeRootList) + PhDereferenceObject(context->NodeRootList); + + PhClearReference(&context->TnErrorMessage); + PhFree(context); + + PhPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + PPH_LAYOUT_ITEM dialogItem; + + if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext)) + { + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), dialogItem, PH_ANCHOR_ALL); + PhEndPropPageLayout(hwndDlg, propPageContext); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case ID_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(context->TnHandle, 0); + PhSetClipboardString(context->TnHandle, &text->sr); + PhDereferenceObject(text); + } + break; + } + } + break; + case UPDATE_MSG: + { + ULONG result = (ULONG)wParam; + PASMPAGE_QUERY_CONTEXT queryContext = (PASMPAGE_QUERY_CONTEXT)lParam; + + if (result == 0) + { + PhSwapReference(&context->NodeList, queryContext->NodeList); + PhSwapReference(&context->NodeRootList, queryContext->NodeRootList); + + DestroyDotNetTraceQuery(queryContext); + + TreeNew_NodesStructured(context->TnHandle); + } + else + { + PhSwapReference(&context->TnErrorMessage, + PhConcatStrings2(L"Unable to start the event tracing session: ", PhGetStringOrDefault(PhGetWin32Message(result), L"Unknown error")) + ); + TreeNew_SetEmptyText(context->TnHandle, &context->TnErrorMessage->sr, 0); + InvalidateRect(context->TnHandle, NULL, FALSE); + } + } + break; + } + + return FALSE; +} diff --git a/plugins/DotNetTools/clr/LICENSE.TXT b/plugins/DotNetTools/clr/LICENSE.TXT new file mode 100644 index 0000000..99ae10b --- /dev/null +++ b/plugins/DotNetTools/clr/LICENSE.TXT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/DotNetTools/clr/dbgappdomain.h b/plugins/DotNetTools/clr/dbgappdomain.h new file mode 100644 index 0000000..4e8c9e0 --- /dev/null +++ b/plugins/DotNetTools/clr/dbgappdomain.h @@ -0,0 +1,139 @@ +/* + * Process Hacker .NET Tools - + * .NET Process IPC definitions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the current folder for more information. +//----------------------------------------------------------------------------- +// +// dmex: This header has been highly modified. +// Original: https://github.com/dotnet/coreclr/blob/master/src/debug/inc/dbgappdomain.h + +#ifndef _DBG_APPDOMAIN_H_ +#define _DBG_APPDOMAIN_H_ + +#ifndef _WIN64 +#include +#endif +typedef struct _AppDomainInfo +{ + ULONG Id; // unique identifier + INT NameLengthInBytes; + PWSTR AppDomainName; + PVOID AppDomains; +} AppDomainInfo; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _AppDomainInfo_Wow64 +{ + ULONG Id; // unique identifier + INT NameLengthInBytes; + ULONG AppDomainName; + ULONG AppDomains; +} AppDomainInfo_Wow64; +#include + +// AppDomain publishing server support: +// Information about all appdomains in the process will be maintained +// in the shared memory block for use by the debugger, etc. +#ifndef _WIN64 +#include +#endif +typedef struct _AppDomainEnumerationIPCBlock +{ + HANDLE Mutex; // lock for serialization while manipulating AppDomain list. + + INT TotalSlots; // Number of slots in AppDomainListElement array + INT NumOfUsedSlots; + INT LastFreedSlot; + INT SizeInBytes; // Size of AppDomainInfo in bytes + + INT ProcessNameLengthInBytes; // We can use psapi!GetModuleFileNameEx to get the module name. + PVOID ProcessName; // This provides an alternative. + + PVOID ListOfAppDomains; + BOOL LockInvalid; +} AppDomainEnumerationIPCBlock; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _AppDomainEnumerationIPCBlock_Wow64 +{ + ULONG Mutex; // lock for serialization while manipulating AppDomain list. + + INT TotalSlots; // Number of slots in AppDomainListElement array + INT NumOfUsedSlots; + INT LastFreedSlot; + INT SizeInBytes; // Size of AppDomainInfo in bytes + + INT ProcessNameLengthInBytes; // We can use psapi!GetModuleFileNameEx to get the module name. + ULONG ProcessName; // This provides an alternative. + + ULONG ListOfAppDomains; + BOOL LockInvalid; +} AppDomainEnumerationIPCBlock_Wow64; +#include + + +// Enforce the AppDomain IPC block binary layout. +C_ASSERT(FIELD_OFFSET(AppDomainInfo_Wow64, Id) == 0x0); +C_ASSERT(FIELD_OFFSET(AppDomainInfo_Wow64, NameLengthInBytes) == 0x4); +C_ASSERT(FIELD_OFFSET(AppDomainInfo_Wow64, AppDomainName) == 0x8); +C_ASSERT(FIELD_OFFSET(AppDomainInfo_Wow64, AppDomains) == 0xC); + +#if defined(_WIN64) +C_ASSERT(FIELD_OFFSET(AppDomainInfo, Id) == 0x0); +C_ASSERT(FIELD_OFFSET(AppDomainInfo, NameLengthInBytes) == 0x4); +C_ASSERT(FIELD_OFFSET(AppDomainInfo, AppDomainName) == 0x8); +C_ASSERT(FIELD_OFFSET(AppDomainInfo, AppDomains) == 0x10); +#endif + +// Enforce the AppDomain IPC block binary layout. +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, Mutex) == 0x0); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, TotalSlots) == 0x4); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, NumOfUsedSlots) == 0x8); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, LastFreedSlot) == 0xC); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, SizeInBytes) == 0x10); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, ProcessNameLengthInBytes) == 0x14); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, ProcessName) == 0x18); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, ListOfAppDomains) == 0x1C); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock_Wow64, LockInvalid) == 0x20); + +#if defined(_WIN64) +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, Mutex) == 0x0); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, TotalSlots) == 0x8); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, NumOfUsedSlots) == 0xC); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, LastFreedSlot) == 0x10); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, SizeInBytes) == 0x14); +C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, ProcessNameLengthInBytes) == 0x18); +// TODO: Why does Visual Studio have issues with these entries... +//C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, ProcessName) == 0x20); +//C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, ListOfAppDomains) == 0x28); +//C_ASSERT(FIELD_OFFSET(AppDomainEnumerationIPCBlock, LockInvalid) == 0x30); +#endif + +#endif _DBG_APPDOMAIN_H_ \ No newline at end of file diff --git a/plugins/DotNetTools/clr/ipcenums.h b/plugins/DotNetTools/clr/ipcenums.h new file mode 100644 index 0000000..179135d --- /dev/null +++ b/plugins/DotNetTools/clr/ipcenums.h @@ -0,0 +1,77 @@ +/* + * Process Hacker .NET Tools - + * .NET Process IPC definitions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the current folder for more information. +//----------------------------------------------------------------------------- +// IPCEnums.h +// +// Define various enums used by IPCMan. +//----------------------------------------------------------------------------- +// +// dmex: This header has been highly modified. +// Original: https://github.com/dotnet/coreclr/blob/master/src/ipcman/ipcenums.h + +#ifndef _IPC_ENUMS_H_ +#define _IPC_ENUMS_H_ + +//----------------------------------------------------------------------------- +// Each IPC client for an IPC block has one entry. +//----------------------------------------------------------------------------- +typedef enum _EIPCClient +{ + eIPC_PerfCounters = 0, + + // MAX used for arrays, insert above this. + eIPC_MAX +} EIPCClient; + +//----------------------------------------------------------------------------- +// Each IPC client for a LegacyPrivate block (debugging, perf counters, etc) has one entry. +//----------------------------------------------------------------------------- +typedef enum _ELegacyPrivateIPCClient +{ + eLegacyPrivateIPC_PerfCounters = 0, + eLegacyPrivateIPC_Obsolete_Debugger = 1, + eLegacyPrivateIPC_AppDomain = 2, + eLegacyPrivateIPC_Obsolete_Service = 3, + eLegacyPrivateIPC_Obsolete_ClassDump = 4, + eLegacyPrivateIPC_Obsolete_MiniDump = 5, + eLegacyPrivateIPC_InstancePath = 6, + + // MAX used for arrays, insert above this. + eLegacyPrivateIPC_MAX +} ELegacyPrivateIPCClient; + +//----------------------------------------------------------------------------- +// Each IPC client for a LegacyPublic block has one entry. +//----------------------------------------------------------------------------- +typedef enum _ELegacyPublicIPCClient +{ + eLegacyPublicIPC_PerfCounters = 0, + + // MAX used for arrays, insert above this. + eLegacyPublicIPC_MAX +} ELegacyPublicIPCClient; + +#endif _IPC_ENUMS_H_ \ No newline at end of file diff --git a/plugins/DotNetTools/clr/ipcheader.h b/plugins/DotNetTools/clr/ipcheader.h new file mode 100644 index 0000000..f4c1d6a --- /dev/null +++ b/plugins/DotNetTools/clr/ipcheader.h @@ -0,0 +1,545 @@ +/* + * Process Hacker .NET Tools - + * .NET Process IPC definitions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the current folder for more information. +//----------------------------------------------------------------------------- +// IPCHeader.h +// +// Define the LegacyPrivate header format for IPC memory mapped files. +//----------------------------------------------------------------------------- +// +// dmex: This header has been highly modified. +// Original: https://github.com/dotnet/coreclr/blob/master/src/ipcman/ipcheader.h + +#ifndef _IPC_HEADER_H_ +#define _IPC_HEADER_H_ + +#include "perfcounterdefs.h" +#include "ipcenums.h" + +// Current version of the IPC Block +const USHORT VER_IPC_BLOCK = 4; +// Legacy version of the IPC Block +const USHORT VER_LEGACYPRIVATE_IPC_BLOCK = 2; +const USHORT VER_LEGACYPUBLIC_IPC_BLOCK = 3; + +//----------------------------------------------------------------------------- +// Entry in the IPC Directory. Ensure binary compatibility across versions +// if we add (or remove) entries. If we remove an block, the entry should +// be EMPTY_ENTRY_OFFSET +//----------------------------------------------------------------------------- + +const ULONG EMPTY_ENTRY_OFFSET = 0xFFFFFFFF; +const ULONG EMPTY_ENTRY_SIZE = 0; + +typedef struct IPCEntry +{ + ULONG Offset; // offset of the IPC Block from the end of the Full IPC Header + ULONG Size; // size (in bytes) of the block +} IPCEntry; + +// Newer versions of the CLR use Flags field +const USHORT IPC_FLAG_USES_FLAGS = 0x1; +const USHORT IPC_FLAG_INITIALIZED = 0x2; +const USHORT IPC_FLAG_X86 = 0x4; + +// In hindsight, we should have made the offsets be absolute, but we made them +// relative to the end of the FullIPCHeader. +// The problem is that as future versions added new Entries to the directory, +// the header size grew. +// Thus we make IPCEntry::Offset is relative to IPC_ENTRY_OFFSET_BASE, which +// corresponds to sizeof(PrivateIPCHeader) for an v1.0 /v1.1 build. +const ULONG IPC_ENTRY_OFFSET_BASE_X86 = 0x14; +const ULONG IPC_ENTRY_OFFSET_BASE_X64 = 0x0; + + +/****************************************************************************** + * The CLR opens memory mapped files to expose perfcounter values and other + * information to other processes. Historically there have been three memory + * mapped files: the BlockTable, the LegacyPrivateBlock, and the + * LegacyPublicBlock. + * + * BlockTable - The block table was designed to work with multiple runtimes + * running side by side in the same process (SxS in-proc). We have defined + * semantics using interlocked operations that allow a runtime to allocate + * a block from the block table in a thread-safe manner. + * + * LegacyPrivateBlock - The legacy private block was used by older versions + * of the runtime to expose various information to the debugger. The + * legacy private block is not compatible with in-proc SxS, and thus it + * must be removed in the near future. Currently it is being used to expose + * information about AppDomains to the debugger. We will need to keep the + * code that knows how to read the legacy private block as long as we + * continue to support .NET 3.5 SP1. + * + * LegacyPublicBlock - The legacy public block was used by older versions + * of the runtime to expose perfcounter values. The legacy public block is + * not compatible with in-proc SxS, and thus it has been removed. We will + * need to keep the code that knows how to read the legacy public block as + * long as we continue to support .NET 3.5 SP1. + ******************************************************************************/ + +/**************************************** BLOCK TABLE ****************************************/ + +#define IPC_BLOCK_TABLE_SIZE 65536 +#define IPC_BLOCK_SIZE 2048 +#define IPC_NUM_BLOCKS_IN_TABLE 32 + +C_ASSERT(IPC_BLOCK_TABLE_SIZE == IPC_NUM_BLOCKS_IN_TABLE * IPC_BLOCK_SIZE); + +typedef struct _IPCHeader +{ + // Chunk header + volatile LONG Counter; // *** Volatile *** value of 0 is special; means that this block has never been touched before by a writer + ULONG RuntimeId; // value of 0 is special; means that chunk is currently free (runtime ids are always greater than 0) + ULONG Reserved1; + ULONG Reserved2; + + // Standard header + USHORT Version; // version of the IPC Block + USHORT Flags; // flags field + ULONG blockSize; // Size of the entire shared memory block + USHORT BuildYear; // stamp for year built + USHORT BuildNumber; // stamp for Month/Day built + ULONG NumEntries; // Number of entries in the table + + // Directory + IPCEntry EntryTable[eIPC_MAX]; // entry describing each client's block +} IPCHeader; + +#define SXSPUBLIC_IPC_SIZE_NO_PADDING (sizeof(IPCHeader) + sizeof(PerfCounterIPCControlBlock)) +#define SXSPUBLIC_WOW64_IPC_SIZE_NO_PADDING (sizeof(IPCHeader) + sizeof(PerfCounterIPCControlBlock_Wow64)) + +#define SXSPUBLIC_IPC_PAD_SIZE (IPC_BLOCK_SIZE - SXSPUBLIC_IPC_SIZE_NO_PADDING) +#define SXSPUBLIC_WOW64_IPC_PAD_SIZE (IPC_BLOCK_SIZE - SXSPUBLIC_WOW64_IPC_SIZE_NO_PADDING) + +#ifndef _WIN64 +#include +#endif +typedef struct _IPCControlBlock +{ + // Header + IPCHeader Header; + + // Client blocks + PerfCounterIPCControlBlock PerfIpcBlock; + + // Padding + BYTE Padding[SXSPUBLIC_IPC_PAD_SIZE]; +} IPCControlBlock; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _IPCControlBlock_Wow64 +{ + // Header + IPCHeader Header; + + // Client blocks + PerfCounterIPCControlBlock_Wow64 PerfIpcBlock; + + // Padding + BYTE Padding[SXSPUBLIC_WOW64_IPC_PAD_SIZE]; +} IPCControlBlock_Wow64; +#include + +C_ASSERT(sizeof(IPCControlBlock) == IPC_BLOCK_SIZE); +C_ASSERT(sizeof(IPCControlBlock_Wow64) == IPC_BLOCK_SIZE); + +#ifndef _WIN64 +#include +#endif +typedef struct IPCControlBlockTable +{ + IPCControlBlock Blocks[IPC_NUM_BLOCKS_IN_TABLE]; +} IPCControlBlockTable; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct IPCControlBlockTable_Wow64 +{ + IPCControlBlock_Wow64 Blocks[IPC_NUM_BLOCKS_IN_TABLE]; +} IPCControlBlockTable_Wow64; +#include + +C_ASSERT(sizeof(IPCControlBlockTable) == IPC_BLOCK_TABLE_SIZE); +C_ASSERT(sizeof(IPCControlBlockTable_Wow64) == IPC_BLOCK_TABLE_SIZE); + + +#ifndef _WIN64 +#include +#endif +typedef struct LegacyPrivateIPCHeader +{ + USHORT Version; // version of the IPC Block + USHORT Flags; // flags field + ULONG BlockSize; // Size of the entire shared memory block + HINSTANCE hInstance; // Instance of module that created this header + USHORT BuildYear; // stamp for year built + USHORT BuildNumber; // stamp for Month/Day built + ULONG NumEntries; // Number of entries in the table +} LegacyPrivateIPCHeader; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct LegacyPrivateIPCHeader_Wow64 +{ + USHORT Version; // version of the IPC Block + USHORT Flags; // flags field + ULONG BlockSize; // Size of the entire shared memory block + ULONG hInstance; // Instance of module that created this header + USHORT BuildYear; // stamp for year built + USHORT BuildNumber; // stamp for Month/Day built + ULONG NumEntries; // Number of entries in the table +} LegacyPrivateIPCHeader_Wow64; +#include + +#ifndef _WIN64 +#include +#endif +typedef struct FullIPCHeaderLegacyPrivate +{ + // Header + LegacyPrivateIPCHeader Header; + + // Directory + IPCEntry EntryTable[eLegacyPrivateIPC_MAX]; // entry describing each client's block +} FullIPCHeaderLegacyPrivate; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct FullIPCHeaderLegacyPrivate_Wow64 +{ + // Header + LegacyPrivateIPCHeader_Wow64 Header; + + // Directory + IPCEntry EntryTable[eLegacyPrivateIPC_MAX]; // entry describing each client's block +} FullIPCHeaderLegacyPrivate_Wow64; +#include + +#ifndef _WIN64 +#include +#endif +typedef struct FullIPCHeaderLegacyPublic +{ + // Header + LegacyPrivateIPCHeader Header; + + // Directory + IPCEntry EntryTable[eLegacyPublicIPC_MAX]; // entry describing each client's block +} FullIPCHeaderLegacyPublic; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct FullIPCHeaderLegacyPublic_Wow64 +{ + // Header + LegacyPrivateIPCHeader_Wow64 Header; + + // Directory + IPCEntry EntryTable[eLegacyPublicIPC_MAX]; // entry describing each client's block +} FullIPCHeaderLegacyPublic_Wow64; +#include + + +//----------------------------------------------------------------------------- +// LegacyPrivate (per process) IPC Block for COM+ apps +//----------------------------------------------------------------------------- +#ifndef _WIN64 +#include +#endif +typedef struct _LegacyPrivateIPCControlBlock +{ + FullIPCHeaderLegacyPrivate FullIPCHeader; + + // Client blocks + PerfCounterIPCControlBlock PerfIpcBlock; // no longer used but kept for compat + AppDomainEnumerationIPCBlock AppDomainBlock; + WCHAR InstancePath[MAX_PATH]; +} LegacyPrivateIPCControlBlock; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _LegacyPrivateIPCControlBlock_Wow64 +{ + FullIPCHeaderLegacyPrivate_Wow64 FullIPCHeader; + + // Client blocks + PerfCounterIPCControlBlock_Wow64 PerfIpcBlock; // no longer used but kept for compat + AppDomainEnumerationIPCBlock_Wow64 AppDomainBlock; + WCHAR InstancePath[MAX_PATH]; +} LegacyPrivateIPCControlBlock_Wow64; +#include + + +//----------------------------------------------------------------------------- +// LegacyPublic (per process) IPC Block for CLR apps +//----------------------------------------------------------------------------- +#ifndef _WIN64 +#include +#endif +typedef struct LegacyPublicIPCControlBlock +{ + FullIPCHeaderLegacyPublic FullIPCHeaderLegacyPublic; + + // Client blocks + PerfCounterIPCControlBlock PerfIpcBlock; +} LegacyPublicIPCControlBlock; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _LegacyPublicIPCControlBlock_Wow64 +{ + FullIPCHeaderLegacyPublic_Wow64 FullIPCHeaderLegacyPublic; + + // Client blocks + PerfCounterIPCControlBlock_Wow64 PerfIpcBlock; +} LegacyPublicIPCControlBlock_Wow64; +#include + + +//class IPCHeaderLockHolder +//{ +// LONG Counter; +// BOOL HaveLock; +// IPCHeader & Header; +// +// public: +// +// IPCHeaderLockHolder(IPCHeader & header) : HaveLock(FALSE), Header(header) {} +// +// BOOL TryGetLock() +// { +// _ASSERTE(!HaveLock); +// LONG oldCounter = Header.Counter; +// if ((oldCounter & 1) != 0) +// return FALSE; +// Counter = oldCounter + 1; +// if (InterlockedCompareExchange((LONG *)(&(Header.Counter)), Counter, oldCounter) != oldCounter) +// return FALSE; +// HaveLock = TRUE; +// +// return TRUE; +// } +// +// BOOL TryGetLock(ULONG numRetries) +// { +// ULONG dwSwitchCount = 0; +// +// for (;;) +// { +// if (TryGetLock()) +// return TRUE; +// +// if (numRetries == 0) +// return FALSE; +// +// --numRetries; +// __SwitchToThread(0, ++dwSwitchCount); +// } +// } +// +// void FreeLock() +// { +// _ASSERTE(HaveLock); +// _ASSERTE(Header.Counter == Counter); +// ++Counter; +// Counter = (Counter == 0) ? 2 : Counter; +// Header.Counter = Counter; +// HaveLock = FALSE; +// } +// +// ~IPCHeaderLockHolder() +// { +// if (HaveLock) +// FreeLock(); +// } +//}; +// +//class IPCHeaderReadHelper +//{ +// IPCHeader CachedHeader; +// IPCHeader * pUnreliableHeader; +// BOOL IsOpen; +// +// public: +// +// IPCHeaderReadHelper() : pUnreliableHeader(NULL), IsOpen(FALSE) {} +// +// BOOL TryOpenHeader(IPCHeader * header) +// { +// _ASSERTE(!IsOpen); +// +// pUnreliableHeader = header; +// +// // Read the counter and the runtime ID from the header +// CachedHeader.Counter = pUnreliableHeader->Counter; +// if ((CachedHeader.Counter & 1) != 0) +// return FALSE; +// CachedHeader.RuntimeId = pUnreliableHeader->RuntimeId; +// +// // If runtime ID is 0, then this block is not allocated by +// // a runtime, and thus there is no further work to do +// if (CachedHeader.RuntimeId == 0) +// { +// IsOpen = TRUE; +// return TRUE; +// } +// +// // Read the rest of the values from the header +// CachedHeader.Reserved1 = pUnreliableHeader->Reserved1; +// CachedHeader.Reserved2 = pUnreliableHeader->Reserved2; +// CachedHeader.Version = pUnreliableHeader->Version; +// CachedHeader.Flags = pUnreliableHeader->Flags; +// CachedHeader.blockSize = pUnreliableHeader->blockSize; +// CachedHeader.BuildYear = pUnreliableHeader->BuildYear; +// CachedHeader.BuildNumber = pUnreliableHeader->BuildNumber; +// CachedHeader.numEntries = pUnreliableHeader->numEntries; +// +// // Verify that the header did not change during the read +// LONG counter = pUnreliableHeader->Counter; +// if (CachedHeader.Counter != counter) +// return FALSE; +// +// // Since we know we got a clean read of numEntries, we +// // should be able to assert this with confidence +// if (CachedHeader.numEntries == 0) +// { +// _ASSERTE(!"numEntries from IPCBlock is zero"); +// return FALSE; +// } +// else if (CachedHeader.numEntries > eIPC_MAX) +// { +// _ASSERTE(!"numEntries from IPCBlock is too big"); +// return FALSE; +// } +// +// if (CachedHeader.blockSize == 0) +// { +// _ASSERTE(!"blockSize from IPCBlock is zero"); +// return FALSE; +// } +// else if (CachedHeader.blockSize > IPC_BLOCK_SIZE) +// { +// _ASSERTE(!"blockSize from IPCBlock is too big"); +// return FALSE; +// } +// +// // Copy the table +// for (ULONG i = 0; i < CachedHeader.numEntries; ++i) +// { +// CachedHeader.EntryTable[i].Offset = pUnreliableHeader->EntryTable[i].Offset; +// CachedHeader.EntryTable[i].Size = pUnreliableHeader->EntryTable[i].Size; +// if (i == eIPC_PerfCounters) +// { +// if(!((SIZE_T)CachedHeader.EntryTable[i].Offset < IPC_BLOCK_SIZE) && ((SIZE_T)CachedHeader.EntryTable[i].Offset + CachedHeader.EntryTable[i].Size <= IPC_BLOCK_SIZE)) +// { +// _ASSERTE(!"PerfCounter section offset + size is too large"); +// return FALSE; +// } +// } +// } +// +// // If eIPC_MAX > numEntries, then mark the left over +// // slots in EntryTable as "empty". +// for (ULONG i = CachedHeader.numEntries; i < eIPC_MAX; ++i) +// { +// CachedHeader.EntryTable[i].Offset = EMPTY_ENTRY_OFFSET; +// CachedHeader.EntryTable[i].Size = EMPTY_ENTRY_SIZE; +// } +// +// // Verify again that the header did not change during the read +// counter = pUnreliableHeader->Counter; +// if (CachedHeader.Counter != counter) +// return FALSE; +// +// IsOpen = TRUE; +// return TRUE; +// } +// +// +// BOOL HeaderHasChanged() +// { +// _ASSERTE(IsOpen); +// LONG counter = pUnreliableHeader->Counter; +// return (CachedHeader.Counter != counter) ? TRUE : FALSE; +// } +// +// BOOL IsSentinal() +// { +// _ASSERTE(IsOpen); +// return (CachedHeader.Counter == 0); +// } +// +// +// BOOL UseWow64Structs() +// { +// _ASSERTE(IsOpen); +//#if !defined(_TARGET_X86_) +// return ((CachedHeader.Flags & IPC_FLAG_X86) != 0) ? TRUE : FALSE; +//#else +// return FALSE; +//#endif +// } +// +// void * GetUnreliableSection(EIPCClient eClient) +// { +// if (!IsOpen) +// { +// _ASSERTE(!"IPCHeaderReadHelper is not open"); +// return NULL; +// } +// +// if (eClient < 0 || eClient >= eIPC_MAX) +// { +// _ASSERTE(!"eClient is out of bounds"); +// return NULL; +// } +// +// if (CachedHeader.EntryTable[eClient].Offset == EMPTY_ENTRY_OFFSET) +// { +// _ASSERTE(!"Section is empty"); +// return NULL; +// } +// +// return (BYTE*)pUnreliableHeader + (SIZE_T)CachedHeader.EntryTable[eClient].Offset; +// } +//}; + +#endif // _IPC_HEADER_H_ \ No newline at end of file diff --git a/plugins/DotNetTools/clr/ipcshared.h b/plugins/DotNetTools/clr/ipcshared.h new file mode 100644 index 0000000..5c14462 --- /dev/null +++ b/plugins/DotNetTools/clr/ipcshared.h @@ -0,0 +1,57 @@ +/* + * Process Hacker .NET Tools - + * .NET Process IPC definitions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the current folder for more information. +//----------------------------------------------------------------------------- +// IPCShared.h +// +// Shared LegacyPrivate utility functions for IPC operations. +//----------------------------------------------------------------------------- +// +// dmex: This header has been highly modified. +// Original: https://github.com/dotnet/coreclr/blob/master/src/ipcman/ipcshared.h + +#ifndef _IPC_SHARED_H_ +#define _IPC_SHARED_H_ + +// This is the name of the file backed session's name on the LS (debuggee) +// Name of the LegacyPrivate (per-process) block. %lu resolved to a PID +#define CorLegacyPrivateIPCBlock L"Cor_Private_IPCBlock_%lu" +#define CorLegacyPrivateIPCBlockTempV4 L"Cor_Private_IPCBlock_v4_%lu" +#define CorLegacyPublicIPCBlock L"Cor_Public_IPCBlock_%lu" +#define CorSxSPublicIPCBlock L"Cor_SxSPublic_IPCBlock_%lu" +#define CorSxSBoundaryDescriptor L"Cor_CLR_IPCBlock_%lu" +#define CorSxSWriterPrivateNamespacePrefix L"Cor_CLR_WRITER" +#define CorSxSReaderPrivateNamespacePrefix L"Cor_CLR_READER" +#define CorSxSVistaPublicIPCBlock L"Cor_SxSPublic_IPCBlock" + +#define CorLegacyPrivateIPCBlock_RS L"CLR_PRIVATE_RS_IPCBlock_%lu" +#define CorLegacyPrivateIPCBlock_RSTempV4 L"CLR_PRIVATE_RS_IPCBlock_v4_%lu" +#define CorLegacyPublicIPCBlock_RS L"CLR_PUBLIC_IPCBlock_%lu" +#define CorSxSPublicIPCBlock_RS L"CLR_SXSPUBLIC_IPCBlock_%lu" + +#define CorSxSPublicInstanceName L"%s_p%lu_r%lu" +#define CorSxSPublicInstanceNameWhidbey L"%s_p%lu" + +#endif _IPC_SHARED_H_ \ No newline at end of file diff --git a/plugins/DotNetTools/clr/perfcounterdefs.h b/plugins/DotNetTools/clr/perfcounterdefs.h new file mode 100644 index 0000000..284aaa2 --- /dev/null +++ b/plugins/DotNetTools/clr/perfcounterdefs.h @@ -0,0 +1,333 @@ +/* + * Process Hacker .NET Tools - + * .NET Process IPC definitions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the current folder for more information. +//----------------------------------------------------------------------------- +// PerfCounterDefs.h +// +// Internal Interface for CLR to use Performance counters. +//----------------------------------------------------------------------------- +// +// dmex: This header has been highly modified. +// Original: https://github.com/dotnet/coreclr/blob/master/src/inc/perfcounterdefs.h + +#ifndef _PERF_COUNTERS_H_ +#define _PERF_COUNTERS_H_ + +//----------------------------------------------------------------------------- +// Name of global IPC block +#define SHARED_PERF_IPC_NAME "SharedPerfIPCBlock" + +//----------------------------------------------------------------------------- +// Attributes for the IPC block +#define PERF_ATTR_ON 0x0001 // Are we even updating any counters? +#define PERF_ATTR_GLOBAL 0x0002 // Is this a global or private block? + +//............................................................................. +// Tri Counter. Support for the common trio of counters (Total, Current, and Instantaneous). +typedef struct _TRICOUNT +{ + ULONG Current; // Current, has +, - + ULONG Total; // Total, has only + +} TRICOUNT; + +//............................................................................. +// Interlocked Tri Counter. Support for the common trio of counters (Total, Current, and Instantaneous). +typedef struct _TRICOUNT_IL +{ + ULONG Current; // Current, has +, - + ULONG Total; // Total, has only + +} TRICOUNT_IL; + + +//............................................................................. +// Dual Counter. Support for the (Total and Instantaneous (rate)). Helpful in cases +// where the current value is always same as the total value. ie. the counter is never +// decremented. +//............................................................................. +typedef struct _DUALCOUNT +{ + ULONG Total; +} DUALCOUNT; + +//----------------------------------------------------------------------------- +// Format for the Perf Counter IPC Block +// IPC block is broken up into sections. This marks it easier to marshall +// into different perfmon objects +// +//............................................................................. +// Naming convention (by prefix): +// c - Raw count of something. +// cb- count of bytes +// time - time value. +// depth - stack depth +//----------------------------------------------------------------------------- + +#define MAX_TRACKED_GENS 3 // number of generations we track + + +#ifndef _WIN64 +#include +#endif +typedef struct _Perf_GC +{ + size_t cGenCollections[MAX_TRACKED_GENS]; // count of collects per gen + size_t cbPromotedMem[MAX_TRACKED_GENS - 1]; // count of promoted memory + size_t cbPromotedFinalizationMem; // count of memory promoted due to finalization + size_t cProcessID; // process ID + size_t cGenHeapSize[MAX_TRACKED_GENS]; // size of heaps per gen + size_t cTotalCommittedBytes; // total number of committed bytes. + size_t cTotalReservedBytes; // bytes reserved via VirtualAlloc + size_t cLrgObjSize; // size of Large Object Heap + size_t cSurviveFinalize; // count of instances surviving from finalizing + size_t cHandles; // count of GC handles + size_t cbAlloc; // bytes allocated + size_t cbLargeAlloc; // bytes allocated for Large Objects + size_t cInducedGCs; // number of explicit GCs + ULONG timeInGC; // Time in GC + ULONG timeInGCBase; // must follow time in GC counter + size_t cPinnedObj; // # of Pinned Objects + size_t cSinkBlocks; // # of sink blocks +} Perf_GC; +#ifndef _WIN64 +#include +#endif + +// Perf_GC_Wow64 mimics in a 64 bit process, the layout of Perf_GC in a 32 bit process +// It does this by replacing all size_t by ULONG +#include +typedef struct _Perf_GC_Wow64 +{ + ULONG cGenCollections[MAX_TRACKED_GENS]; // count of collects per gen + ULONG cbPromotedMem[MAX_TRACKED_GENS - 1]; // count of promoted memory + ULONG cbPromotedFinalizationMem; // count of memory promoted due to finalization + ULONG cProcessID; // process ID + ULONG cGenHeapSize[MAX_TRACKED_GENS]; // size of heaps per gen + ULONG cTotalCommittedBytes; // total number of committed bytes. + ULONG cTotalReservedBytes; // bytes reserved via VirtualAlloc + ULONG cLrgObjSize; // size of Large Object Heap + ULONG cSurviveFinalize; // count of instances surviving from finalizing + ULONG cHandles; // count of GC handles + ULONG cbAlloc; // bytes allocated + ULONG cbLargeAlloc; // bytes allocated for Large Objects + ULONG cInducedGCs; // number of explicit GCs + ULONG timeInGC; // Time in GC + ULONG timeInGCBase; // must follow time in GC counter + ULONG cPinnedObj; // # of Pinned Objects + ULONG cSinkBlocks; // # of sink blocks +} Perf_GC_Wow64; +#include + + + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Loading +{ + TRICOUNT cClassesLoaded; + TRICOUNT_IL cAppDomains; // Current # of AppDomains + TRICOUNT cAssemblies; // Current # of Assemblies + UNALIGNED LONGLONG timeLoading; // % time loading + ULONG cAsmSearchLen; // Avg search length for assemblies + DUALCOUNT cLoadFailures; // Classes Failed to load + size_t cbLoaderHeapSize; // Total size of heap used by the loader + DUALCOUNT cAppDomainsUnloaded; // Rate at which app domains are unloaded +} Perf_Loading; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct Perf_Loading_Wow64 +{ + TRICOUNT cClassesLoaded; + TRICOUNT_IL cAppDomains; // Current # of AppDomains + TRICOUNT cAssemblies; // Current # of Assemblies + UNALIGNED LONGLONG timeLoading; // % time loading + ULONG cAsmSearchLen; // Avg search length for assemblies + DUALCOUNT cLoadFailures; // Classes Failed to load + ULONG cbLoaderHeapSize; // Total size of heap used by the loader + DUALCOUNT cAppDomainsUnloaded; // Rate at which app domains are unloaded +} Perf_Loading_Wow64; +#include + + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Jit +{ + ULONG cMethodsJitted; // number of methods jitted + TRICOUNT cbILJitted; // IL jitted stats + //DUALCOUNT cbPitched; // Total bytes pitched + ULONG cJitFailures; // # of standard Jit failures + ULONG timeInJit; // Time in JIT since last sample + ULONG timeInJitBase; // Time in JIT base counter +} Perf_Jit; +#ifndef _WIN64 +#include +#endif + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Excep +{ + DUALCOUNT cThrown; // Number of Exceptions thrown + ULONG cFiltersExecuted; // Number of Filters executed + ULONG cFinallysExecuted; // Number of Finallys executed + ULONG cThrowToCatchStackDepth; // Delta from throw to catch site on stack +} Perf_Excep; +#ifndef _WIN64 +#include +#endif + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Interop +{ + ULONG cCCW; // Number of CCWs + ULONG cStubs; // Number of stubs + ULONG cMarshalling; // # of time marshalling args and return values. + ULONG cTLBImports; // Number of tlbs we import + ULONG cTLBExports; // Number of tlbs we export +} Perf_Interop; +#ifndef _WIN64 +#include +#endif + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_LocksAndThreads +{ + // Locks + DUALCOUNT cContention; // # of times in AwareLock::EnterEpilogue() + TRICOUNT cQueueLength; // Lenght of queue + // Threads + ULONG cCurrentThreadsLogical; // Number (created - destroyed) of logical threads + ULONG cCurrentThreadsPhysical; // Number (created - destroyed) of OS threads + TRICOUNT cRecognizedThreads; // # of Threads execute in runtime's control +} Perf_LocksAndThreads; +#ifndef _WIN64 +#include +#endif + + +// IMPORTANT!!!!!!!: The first two fields in the struct have to be together +// and be the first two fields in the struct. The managed code in ChannelServices.cs +// depends on this. +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Contexts +{ + // Contexts & Remoting + DUALCOUNT cRemoteCalls; // # of remote calls + ULONG cChannels; // Number of current channels + ULONG cProxies; // Number of context proxies. + ULONG cClasses; // # of Context-bound classes + ULONG cObjAlloc; // # of context bound objects allocated + ULONG cContexts; // The current number of contexts. +} Perf_Contexts; +#ifndef _WIN64 +#include +#endif + + +#ifndef _WIN64 +#include +#endif +typedef struct Perf_Security +{ + ULONG cTotalRTChecks; // Total runtime checks + UNALIGNED LONGLONG timeAuthorize; // % time authenticating + ULONG cLinkChecks; // link time checks + ULONG timeRTchecks; // % time in Runtime checks + ULONG timeRTchecksBase; // % time in Runtime checks base counter + ULONG stackWalkDepth; // depth of stack for security checks +} Perf_Security; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct Perf_Security_Wow64 +{ + ULONG cTotalRTChecks; // Total runtime checks + UNALIGNED LONGLONG timeAuthorize; // % time authenticating + ULONG cLinkChecks; // link time checks + ULONG timeRTchecks; // % time in Runtime checks + ULONG timeRTchecksBase; // % time in Runtime checks base counter + ULONG stackWalkDepth; // depth of stack for security checks +} Perf_Security_Wow64; +#include + + +#ifndef _WIN64 +#include +#endif +typedef struct _PerfCounterIPCControlBlock +{ + // Versioning info + USHORT Bytes; // size of this entire block + USHORT Attributes; // attributes for this block + + // Counter Sections + Perf_GC GC; + Perf_Contexts Context; + Perf_Interop Interop; + Perf_Loading Loading; + Perf_Excep Excep; + Perf_LocksAndThreads LocksAndThreads; + Perf_Jit Jit; + Perf_Security Security; +} PerfCounterIPCControlBlock; +#ifndef _WIN64 +#include +#endif + +#include +typedef struct _PerfCounterIPCControlBlock_Wow64 +{ + // Versioning info + USHORT Bytes; // size of this entire block + USHORT Attributes; // attributes for this block + + // Counter Sections + Perf_GC_Wow64 GC; + Perf_Contexts Context; + Perf_Interop Interop; + Perf_Loading_Wow64 Loading; + Perf_Excep Excep; + Perf_LocksAndThreads LocksAndThreads; + Perf_Jit Jit; + Perf_Security_Wow64 Security; +} PerfCounterIPCControlBlock_Wow64; +#include + + +#endif _PERF_COUNTERS_H_ \ No newline at end of file diff --git a/plugins/DotNetTools/clretw.h b/plugins/DotNetTools/clretw.h new file mode 100644 index 0000000..289802e --- /dev/null +++ b/plugins/DotNetTools/clretw.h @@ -0,0 +1,146 @@ +/* + * Process Hacker .NET Tools + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef CLRETW_H +#define CLRETW_H + +// Keywords + +#define CLR_LOADER_KEYWORD 0x8 +#define CLR_STARTENUMERATION_KEYWORD 0x40 + +// Event IDs + +#define DCStartComplete_V1 145 +#define ModuleDCStart_V1 153 +#define AssemblyDCStart_V1 155 +#define AppDomainDCStart_V1 157 +#define RuntimeInformationDCStart 187 + +// Opcodes + +#define CLR_METHODDC_DCSTARTCOMPLETE_OPCODE 14 +#define CLR_MODULEDCSTART_OPCODE 35 + +// Bit maps + +// AppDomainFlags +#define AppDomainFlags_Default 0x1 +#define AppDomainFlags_Executable 0x2 +#define AppDomainFlags_Shared 0x4 + +// AssemblyFlags +#define AssemblyFlags_DomainNeutral 0x1 +#define AssemblyFlags_Dynamic 0x2 +#define AssemblyFlags_Native 0x4 +#define AssemblyFlags_Collectible 0x8 + +// ModuleFlags +#define ModuleFlags_DomainNeutral 0x1 +#define ModuleFlags_Native 0x2 +#define ModuleFlags_Dynamic 0x4 +#define ModuleFlags_Manifest 0x8 + +// StartupMode +#define StartupMode_ManagedExe 0x1 +#define StartupMode_HostedCLR 0x2 +#define StartupMode_IjwDll 0x4 +#define StartupMode_ComActivated 0x8 +#define StartupMode_Other 0x10 + +// StartupFlags +#define StartupFlags_CONCURRENT_GC 0x1 +#define StartupFlags_LOADER_OPTIMIZATION_SINGLE_DOMAIN 0x2 +#define StartupFlags_LOADER_OPTIMIZATION_MULTI_DOMAIN 0x4 +#define StartupFlags_LOADER_SAFEMODE 0x10 +#define StartupFlags_LOADER_SETPREFERENCE 0x100 +#define StartupFlags_SERVER_GC 0x1000 +#define StartupFlags_HOARD_GC_VM 0x2000 +#define StartupFlags_SINGLE_VERSION_HOSTING_INTERFACE 0x4000 +#define StartupFlags_LEGACY_IMPERSONATION 0x10000 +#define StartupFlags_DISABLE_COMMITTHREADSTACK 0x20000 +#define StartupFlags_ALWAYSFLOW_IMPERSONATION 0x40000 +#define StartupFlags_TRIM_GC_COMMIT 0x80000 +#define StartupFlags_ETW 0x100000 +#define StartupFlags_SERVER_BUILD 0x200000 +#define StartupFlags_ARM 0x400000 + +// Templates + +#include + +typedef struct _DCStartEnd +{ + USHORT ClrInstanceID; +} DCStartEnd, *PDCStartEnd; + +typedef struct _ModuleLoadUnloadRundown_V1 +{ + ULONG64 ModuleID; + ULONG64 AssemblyID; + ULONG ModuleFlags; // ModuleFlags + ULONG Reserved1; + WCHAR ModuleILPath[1]; + // WCHAR ModuleNativePath[1]; + // USHORT ClrInstanceID; +} ModuleLoadUnloadRundown_V1, *PModuleLoadUnloadRundown_V1; + +typedef struct _AssemblyLoadUnloadRundown_V1 +{ + ULONG64 AssemblyID; + ULONG64 AppDomainID; + ULONG64 BindingID; + ULONG AssemblyFlags; // AssemblyFlags + WCHAR FullyQualifiedAssemblyName[1]; + // USHORT ClrInstanceID; +} AssemblyLoadUnloadRundown_V1, *PAssemblyLoadUnloadRundown_V1; + +typedef struct _AppDomainLoadUnloadRundown_V1 +{ + ULONG64 AppDomainID; + ULONG AppDomainFlags; // AppDomainFlags + WCHAR AppDomainName[1]; + // ULONG AppDomainIndex; + // USHORT ClrInstanceID; +} AppDomainLoadUnloadRundown_V1, *PAppDomainLoadUnloadRundown_V1; + +typedef struct _RuntimeInformationRundown +{ + USHORT ClrInstanceID; + USHORT Sku; + USHORT BclMajorVersion; + USHORT BclMinorVersion; + USHORT BclBuildNumber; + USHORT BclQfeNumber; + USHORT VMMajorVersion; + USHORT VMMinorVersion; + USHORT VMBuildNumber; + USHORT VMQfeNumber; + ULONG StartupFlags; // StartupFlags + UCHAR StartupMode; // StartupMode + WCHAR CommandLine[1]; + // GUID ComObjectGuid; + // WCHAR RuntimeDllPath[1]; +} RuntimeInformationRundown, *PRuntimeInformationRundown; + +#include + +#endif diff --git a/plugins/DotNetTools/clrsup.c b/plugins/DotNetTools/clrsup.c new file mode 100644 index 0000000..3fca660 --- /dev/null +++ b/plugins/DotNetTools/clrsup.c @@ -0,0 +1,575 @@ +/* + * Process Hacker .NET Tools - + * CLR data access functions + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "clrsup.h" + +static GUID IID_ICLRDataTarget_I = { 0x3e11ccee, 0xd08b, 0x43e5, { 0xaf, 0x01, 0x32, 0x71, 0x7a, 0x64, 0xda, 0x03 } }; +static GUID IID_IXCLRDataProcess = { 0x5c552ab6, 0xfc09, 0x4cb3, { 0x8e, 0x36, 0x22, 0xfa, 0x03, 0xc7, 0x98, 0xb7 } }; + +static ICLRDataTargetVtbl DnCLRDataTarget_VTable = +{ + DnCLRDataTarget_QueryInterface, + DnCLRDataTarget_AddRef, + DnCLRDataTarget_Release, + DnCLRDataTarget_GetMachineType, + DnCLRDataTarget_GetPointerSize, + DnCLRDataTarget_GetImageBase, + DnCLRDataTarget_ReadVirtual, + DnCLRDataTarget_WriteVirtual, + DnCLRDataTarget_GetTLSValue, + DnCLRDataTarget_SetTLSValue, + DnCLRDataTarget_GetCurrentThreadID, + DnCLRDataTarget_GetThreadContext, + DnCLRDataTarget_SetThreadContext, + DnCLRDataTarget_Request +}; + +PCLR_PROCESS_SUPPORT CreateClrProcessSupport( + _In_ HANDLE ProcessId + ) +{ + PCLR_PROCESS_SUPPORT support; + ICLRDataTarget *dataTarget; + IXCLRDataProcess *dataProcess; + + dataTarget = DnCLRDataTarget_Create(ProcessId); + + if (!dataTarget) + return NULL; + + dataProcess = NULL; + CreateXCLRDataProcess(ProcessId, dataTarget, &dataProcess); + ICLRDataTarget_Release(dataTarget); + + if (!dataProcess) + return NULL; + + support = PhAllocate(sizeof(CLR_PROCESS_SUPPORT)); + support->DataProcess = dataProcess; + + return support; +} + +VOID FreeClrProcessSupport( + _In_ PCLR_PROCESS_SUPPORT Support + ) +{ + IXCLRDataProcess_Release(Support->DataProcess); + PhFree(Support); +} + +PPH_STRING GetRuntimeNameByAddressClrProcess( + _In_ PCLR_PROCESS_SUPPORT Support, + _In_ ULONG64 Address, + _Out_opt_ PULONG64 Displacement + ) +{ + PPH_STRING buffer; + ULONG bufferLength; + ULONG returnLength; + ULONG64 displacement; + + bufferLength = 33; + buffer = PhCreateStringEx(NULL, (bufferLength - 1) * 2); + + returnLength = 0; + + if (!SUCCEEDED(IXCLRDataProcess_GetRuntimeNameByAddress( + Support->DataProcess, + Address, + 0, + bufferLength, + &returnLength, + buffer->Buffer, + &displacement + ))) + { + PhDereferenceObject(buffer); + return NULL; + } + + // Try again if our buffer was too small. + if (returnLength > bufferLength) + { + PhDereferenceObject(buffer); + bufferLength = returnLength; + buffer = PhCreateStringEx(NULL, (bufferLength - 1) * 2); + + if (!SUCCEEDED(IXCLRDataProcess_GetRuntimeNameByAddress( + Support->DataProcess, + Address, + 0, + bufferLength, + &returnLength, + buffer->Buffer, + &displacement + ))) + { + PhDereferenceObject(buffer); + return NULL; + } + } + + if (Displacement) + *Displacement = displacement; + + buffer->Length = (returnLength - 1) * 2; + + return buffer; +} + +PPH_STRING GetNameXClrDataAppDomain( + _In_ PVOID AppDomain + ) +{ + IXCLRDataAppDomain *appDomain; + PPH_STRING buffer; + ULONG bufferLength; + ULONG returnLength; + + appDomain = AppDomain; + + bufferLength = 33; + buffer = PhCreateStringEx(NULL, (bufferLength - 1) * 2); + + returnLength = 0; + + if (!SUCCEEDED(IXCLRDataAppDomain_GetName(appDomain, bufferLength, &returnLength, buffer->Buffer))) + { + PhDereferenceObject(buffer); + return NULL; + } + + // Try again if our buffer was too small. + if (returnLength > bufferLength) + { + PhDereferenceObject(buffer); + bufferLength = returnLength; + buffer = PhCreateStringEx(NULL, (bufferLength - 1) * 2); + + if (!SUCCEEDED(IXCLRDataAppDomain_GetName(appDomain, bufferLength, &returnLength, buffer->Buffer))) + { + PhDereferenceObject(buffer); + return NULL; + } + } + + buffer->Length = (returnLength - 1) * 2; + + return buffer; +} + +PVOID LoadMscordacwks( + _In_ BOOLEAN IsClrV4 + ) +{ + PVOID dllBase; + PH_STRINGREF systemRootString; + PH_STRINGREF mscordacwksPathString; + PPH_STRING mscordacwksFileName; + + LoadLibrary(L"mscoree.dll"); + + PhGetSystemRoot(&systemRootString); + + if (IsClrV4) + { +#ifdef _WIN64 + PhInitializeStringRef(&mscordacwksPathString, L"\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordacwks.dll"); +#else + PhInitializeStringRef(&mscordacwksPathString, L"\\Microsoft.NET\\Framework\\v4.0.30319\\mscordacwks.dll"); +#endif + } + else + { +#ifdef _WIN64 + PhInitializeStringRef(&mscordacwksPathString, L"\\Microsoft.NET\\Framework64\\v2.0.50727\\mscordacwks.dll"); +#else + PhInitializeStringRef(&mscordacwksPathString, L"\\Microsoft.NET\\Framework\\v2.0.50727\\mscordacwks.dll"); +#endif + } + + mscordacwksFileName = PhConcatStringRef2(&systemRootString, &mscordacwksPathString); + dllBase = LoadLibrary(mscordacwksFileName->Buffer); + PhDereferenceObject(mscordacwksFileName); + + return dllBase; +} + +HRESULT CreateXCLRDataProcess( + _In_ HANDLE ProcessId, + _In_ ICLRDataTarget *Target, + _Out_ struct IXCLRDataProcess **DataProcess + ) +{ + ULONG flags; + BOOLEAN clrV4; + HMODULE dllBase; + HRESULT (__stdcall *clrDataCreateInstance)(REFIID, ICLRDataTarget *, void **); + + clrV4 = FALSE; + + if (NT_SUCCESS(PhGetProcessIsDotNetEx(ProcessId, NULL, 0, NULL, &flags))) + { + if (flags & PH_CLR_VERSION_4_ABOVE) + clrV4 = TRUE; + } + + // Load the correct version of mscordacwks.dll. + + if (clrV4) + { + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static HMODULE mscordacwksDllBase; + + if (PhBeginInitOnce(&initOnce)) + { + mscordacwksDllBase = LoadMscordacwks(TRUE); + PhEndInitOnce(&initOnce); + } + + dllBase = mscordacwksDllBase; + } + else + { + static PH_INITONCE initOnce = PH_INITONCE_INIT; + static HMODULE mscordacwksDllBase; + + if (PhBeginInitOnce(&initOnce)) + { + mscordacwksDllBase = LoadMscordacwks(FALSE); + PhEndInitOnce(&initOnce); + } + + dllBase = mscordacwksDllBase; + } + + if (!dllBase) + return E_FAIL; + + clrDataCreateInstance = PhGetProcedureAddress(dllBase, "CLRDataCreateInstance", 0); + + if (!clrDataCreateInstance) + return E_FAIL; + + return clrDataCreateInstance(&IID_IXCLRDataProcess, Target, DataProcess); +} + +ICLRDataTarget *DnCLRDataTarget_Create( + _In_ HANDLE ProcessId + ) +{ + DnCLRDataTarget *dataTarget; + HANDLE processHandle; + BOOLEAN isWow64; + + if (!NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessId))) + return NULL; + +#ifdef _WIN64 + if (!NT_SUCCESS(PhGetProcessIsWow64(processHandle, &isWow64))) + { + NtClose(processHandle); + return NULL; + } +#else + isWow64 = FALSE; +#endif + + dataTarget = PhAllocate(sizeof(DnCLRDataTarget)); + dataTarget->VTable = &DnCLRDataTarget_VTable; + dataTarget->RefCount = 1; + + dataTarget->ProcessId = ProcessId; + dataTarget->ProcessHandle = processHandle; + dataTarget->IsWow64 = isWow64; + + return (ICLRDataTarget *)dataTarget; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_QueryInterface( + _In_ ICLRDataTarget *This, + _In_ REFIID Riid, + _Out_ PVOID *Object + ) +{ + if ( + IsEqualIID(Riid, &IID_IUnknown) || + IsEqualIID(Riid, &IID_ICLRDataTarget_I) + ) + { + DnCLRDataTarget_AddRef(This); + *Object = This; + return S_OK; + } + + *Object = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE DnCLRDataTarget_AddRef( + _In_ ICLRDataTarget *This + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + + this->RefCount++; + + return this->RefCount; +} + +ULONG STDMETHODCALLTYPE DnCLRDataTarget_Release( + _In_ ICLRDataTarget *This + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + + this->RefCount--; + + if (this->RefCount == 0) + { + NtClose(this->ProcessHandle); + + PhFree(this); + + return 0; + } + + return this->RefCount; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetMachineType( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *machineType + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + +#ifdef _WIN64 + if (!this->IsWow64) + *machineType = IMAGE_FILE_MACHINE_AMD64; + else + *machineType = IMAGE_FILE_MACHINE_I386; +#else + *machineType = IMAGE_FILE_MACHINE_I386; +#endif + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetPointerSize( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *pointerSize + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + +#ifdef _WIN64 + if (!this->IsWow64) +#endif + *pointerSize = sizeof(PVOID); +#ifdef _WIN64 + else + *pointerSize = sizeof(ULONG); +#endif + + return S_OK; +} + +BOOLEAN NTAPI PhpGetImageBaseCallback( + _In_ PLDR_DATA_TABLE_ENTRY Module, + _In_opt_ PVOID Context + ) +{ + PPHP_GET_IMAGE_BASE_CONTEXT context = Context; + + if (RtlEqualUnicodeString(&Module->FullDllName, &context->ImagePath, TRUE) || + RtlEqualUnicodeString(&Module->BaseDllName, &context->ImagePath, TRUE)) + { + context->BaseAddress = Module->DllBase; + return FALSE; + } + + return TRUE; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetImageBase( + _In_ ICLRDataTarget *This, + _In_ LPCWSTR imagePath, + _Out_ CLRDATA_ADDRESS *baseAddress + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + PHP_GET_IMAGE_BASE_CONTEXT context; + + RtlInitUnicodeString(&context.ImagePath, (PWSTR)imagePath); + context.BaseAddress = NULL; + PhEnumProcessModules(this->ProcessHandle, PhpGetImageBaseCallback, &context); + +#ifdef _WIN64 + if (this->IsWow64) + PhEnumProcessModules32(this->ProcessHandle, PhpGetImageBaseCallback, &context); +#endif + + if (context.BaseAddress) + { + *baseAddress = (CLRDATA_ADDRESS)context.BaseAddress; + + return S_OK; + } + else + { + return E_FAIL; + } +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_ReadVirtual( + _In_ ICLRDataTarget *This, + _In_ CLRDATA_ADDRESS address, + _Out_ BYTE *buffer, + _In_ ULONG32 bytesRequested, + _Out_ ULONG32 *bytesRead + ) +{ + DnCLRDataTarget *this = (DnCLRDataTarget *)This; + NTSTATUS status; + SIZE_T numberOfBytesRead; + + if (NT_SUCCESS(status = NtReadVirtualMemory( + this->ProcessHandle, + (PVOID)address, + buffer, + bytesRequested, + &numberOfBytesRead + ))) + { + *bytesRead = (ULONG32)numberOfBytesRead; + + return S_OK; + } + else + { + ULONG result; + + result = RtlNtStatusToDosError(status); + + return HRESULT_FROM_WIN32(result); + } +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_WriteVirtual( + _In_ ICLRDataTarget *This, + _In_ CLRDATA_ADDRESS address, + _In_ BYTE *buffer, + _In_ ULONG32 bytesRequested, + _Out_ ULONG32 *bytesWritten + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetTLSValue( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 index, + _Out_ CLRDATA_ADDRESS *value + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_SetTLSValue( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 index, + _In_ CLRDATA_ADDRESS value + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetCurrentThreadID( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *threadID + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetThreadContext( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 contextFlags, + _In_ ULONG32 contextSize, + _Out_ BYTE *context + ) +{ + NTSTATUS status; + HANDLE threadHandle; + CONTEXT buffer; + + if (contextSize < sizeof(CONTEXT)) + return E_INVALIDARG; + + memset(&buffer, 0, sizeof(CONTEXT)); + buffer.ContextFlags = contextFlags; + + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_GET_CONTEXT, UlongToHandle(threadID)))) + { + status = NtGetContextThread(threadHandle, &buffer); + NtClose(threadHandle); + } + + if (NT_SUCCESS(status)) + { + memcpy(context, &buffer, sizeof(CONTEXT)); + + return S_OK; + } + else + { + return HRESULT_FROM_WIN32(RtlNtStatusToDosError(status)); + } +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_SetThreadContext( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 contextSize, + _In_ BYTE *context + ) +{ + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_Request( + _In_ ICLRDataTarget *This, + _In_ ULONG32 reqCode, + _In_ ULONG32 inBufferSize, + _In_ BYTE *inBuffer, + _In_ ULONG32 outBufferSize, + _Out_ BYTE *outBuffer + ) +{ + return E_NOTIMPL; +} diff --git a/plugins/DotNetTools/clrsup.h b/plugins/DotNetTools/clrsup.h new file mode 100644 index 0000000..c89ccc7 --- /dev/null +++ b/plugins/DotNetTools/clrsup.h @@ -0,0 +1,645 @@ +/* + * Process Hacker .NET Tools - + * CLR data access functions + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef CLRSUP_H +#define CLRSUP_H + +#define CINTERFACE +#define COBJMACROS +#include +#undef CINTERFACE +#undef COBJMACROS + +// General interfaces + +typedef struct _CLR_PROCESS_SUPPORT +{ + struct IXCLRDataProcess *DataProcess; +} CLR_PROCESS_SUPPORT, *PCLR_PROCESS_SUPPORT; + +PCLR_PROCESS_SUPPORT CreateClrProcessSupport( + _In_ HANDLE ProcessId + ); + +VOID FreeClrProcessSupport( + _In_ PCLR_PROCESS_SUPPORT Support + ); + +PPH_STRING GetRuntimeNameByAddressClrProcess( + _In_ PCLR_PROCESS_SUPPORT Support, + _In_ ULONG64 Address, + _Out_opt_ PULONG64 Displacement + ); + +PPH_STRING GetNameXClrDataAppDomain( + _In_ PVOID AppDomain + ); + +PVOID LoadMscordacwks( + _In_ BOOLEAN IsClrV4 + ); + +HRESULT CreateXCLRDataProcess( + _In_ HANDLE ProcessId, + _In_ ICLRDataTarget *Target, + _Out_ struct IXCLRDataProcess **DataProcess + ); + +// xclrdata + +typedef ULONG64 CLRDATA_ENUM; + +typedef struct IXCLRDataProcess IXCLRDataProcess; +typedef struct IXCLRDataAppDomain IXCLRDataAppDomain; +typedef struct IXCLRDataTask IXCLRDataTask; +typedef struct IXCLRDataStackWalk IXCLRDataStackWalk; +typedef struct IXCLRDataFrame IXCLRDataFrame; + +typedef struct IXCLRDataProcessVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + _In_ IXCLRDataProcess *This, + _In_ REFIID riid, + _Outptr_ void **ppvObject + ); + + ULONG (STDMETHODCALLTYPE *AddRef)( + _In_ IXCLRDataProcess *This + ); + + ULONG (STDMETHODCALLTYPE *Release)( + _In_ IXCLRDataProcess *This + ); + + HRESULT (STDMETHODCALLTYPE *Flush)( + _In_ IXCLRDataProcess *This + ); + + HRESULT (STDMETHODCALLTYPE *StartEnumTasks)( + _In_ IXCLRDataProcess *This, + _Out_ CLRDATA_ENUM *handle + ); + + HRESULT (STDMETHODCALLTYPE *EnumTask)( + _In_ IXCLRDataProcess *This, + _Inout_ CLRDATA_ENUM *handle, + _Out_ IXCLRDataTask **task + ); + + HRESULT (STDMETHODCALLTYPE *EndEnumTasks)( + _In_ IXCLRDataProcess *This, + _In_ CLRDATA_ENUM handle + ); + + HRESULT (STDMETHODCALLTYPE *GetTaskByOSThreadID)( + _In_ IXCLRDataProcess *This, + _In_ ULONG32 osThreadID, + _Out_ IXCLRDataTask **task + ); + + PVOID GetTaskByUniqueID; + PVOID GetFlags; + PVOID IsSameObject; + PVOID GetManagedObject; + PVOID GetDesiredExecutionState; + PVOID SetDesiredExecutionState; + PVOID GetAddressType; + + HRESULT (STDMETHODCALLTYPE *GetRuntimeNameByAddress)( + _In_ IXCLRDataProcess *This, + _In_ CLRDATA_ADDRESS address, + _In_ ULONG32 flags, + _In_ ULONG32 bufLen, + _Out_ ULONG32 *nameLen, + _Out_ WCHAR *nameBuf, + _Out_ CLRDATA_ADDRESS *displacement + ); + + // ... +} IXCLRDataProcessVtbl; + +typedef struct IXCLRDataProcess +{ + struct IXCLRDataProcessVtbl *lpVtbl; +} IXCLRDataProcess; + +#define IXCLRDataProcess_QueryInterface(This, riid, ppvObject) \ + ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) + +#define IXCLRDataProcess_AddRef(This) \ + ((This)->lpVtbl->AddRef(This)) + +#define IXCLRDataProcess_Release(This) \ + ((This)->lpVtbl->Release(This)) + +#define IXCLRDataProcess_GetRuntimeNameByAddress(This, address, flags, bufLen, nameLen, nameBuf, displacement) \ + ((This)->lpVtbl->GetRuntimeNameByAddress(This, address, flags, bufLen, nameLen, nameBuf, displacement)) + +#define IXCLRDataProcess_Flush(This) \ + ((This)->lpVtbl->Flush(This)) + +#define IXCLRDataProcess_StartEnumTasks(This, handle) \ + ((This)->lpVtbl->StartEnumTasks(This, handle)) + +#define IXCLRDataProcess_EnumTask(This, handle, task) \ + ((This)->lpVtbl->EnumTask(This, handle, task)) + +#define IXCLRDataProcess_EndEnumTasks(This, handle) \ + ((This)->lpVtbl->EndEnumTasks(This, handle)) + +#define IXCLRDataProcess_GetTaskByOSThreadID(This, osThreadID, task) \ + ((This)->lpVtbl->GetTaskByOSThreadID(This, osThreadID, task)) + +typedef struct IXCLRDataAppDomainVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + _In_ IXCLRDataAppDomain *This, + _In_ REFIID riid, + _Outptr_ void **ppvObject + ); + + ULONG (STDMETHODCALLTYPE *AddRef)( + _In_ IXCLRDataAppDomain *This + ); + + ULONG (STDMETHODCALLTYPE *Release)( + _In_ IXCLRDataAppDomain *This + ); + + HRESULT (STDMETHODCALLTYPE *GetProcess)( + _In_ IXCLRDataAppDomain *This, + _Out_ IXCLRDataProcess **process + ); + + HRESULT (STDMETHODCALLTYPE *GetName)( + _In_ IXCLRDataAppDomain *This, + _In_ ULONG32 bufLen, + _Out_ ULONG32 *nameLen, + _Out_ WCHAR *name + ); + + HRESULT (STDMETHODCALLTYPE *GetUniqueID)( + _In_ IXCLRDataAppDomain *This, + _Out_ ULONG64 *id + ); + + // ... +} IXCLRDataAppDomainVtbl; + +typedef struct IXCLRDataAppDomain +{ + struct IXCLRDataAppDomainVtbl *lpVtbl; +} IXCLRDataAppDomain; + +#define IXCLRDataAppDomain_QueryInterface(This, riid, ppvObject) \ + ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) + +#define IXCLRDataAppDomain_AddRef(This) \ + ((This)->lpVtbl->AddRef(This)) + +#define IXCLRDataAppDomain_Release(This) \ + ((This)->lpVtbl->Release(This)) + +#define IXCLRDataAppDomain_GetProcess(This, process) \ + ((This)->lpVtbl->GetProcess(This, process)) + +#define IXCLRDataAppDomain_GetName(This, bufLen, nameLen, name) \ + ((This)->lpVtbl->GetName(This, bufLen, nameLen, name)) + +#define IXCLRDataAppDomain_GetUniqueID(This, id) \ + ((This)->lpVtbl->GetUniqueID(This, id)) + +typedef struct IXCLRDataTaskVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + _In_ IXCLRDataTask *This, + _In_ REFIID riid, + _Outptr_ void **ppvObject + ); + + ULONG (STDMETHODCALLTYPE *AddRef)( + _In_ IXCLRDataTask *This + ); + + ULONG (STDMETHODCALLTYPE *Release)( + _In_ IXCLRDataTask *This + ); + + HRESULT (STDMETHODCALLTYPE *GetProcess)( + _In_ IXCLRDataTask *This, + _Out_ IXCLRDataProcess **process + ); + + HRESULT (STDMETHODCALLTYPE *GetCurrentAppDomain)( + _In_ IXCLRDataTask *This, + _Out_ IXCLRDataAppDomain **appDomain + ); + + HRESULT (STDMETHODCALLTYPE *GetUniqueID)( + _In_ IXCLRDataTask *This, + _Out_ ULONG64 *id + ); + + HRESULT (STDMETHODCALLTYPE *GetFlags)( + _In_ IXCLRDataTask *This, + _Out_ ULONG32 *flags + ); + + PVOID IsSameObject; + PVOID GetManagedObject; + PVOID GetDesiredExecutionState; + PVOID SetDesiredExecutionState; + + HRESULT (STDMETHODCALLTYPE *CreateStackWalk)( + _In_ IXCLRDataTask *This, + _In_ ULONG32 flags, + _Out_ IXCLRDataStackWalk **stackWalk + ); + + HRESULT (STDMETHODCALLTYPE *GetOSThreadID)( + _In_ IXCLRDataTask *This, + _Out_ ULONG32 *id + ); + + PVOID GetContext; + PVOID SetContext; + PVOID GetCurrentExceptionState; + PVOID Request; + + HRESULT (STDMETHODCALLTYPE *GetName)( + _In_ IXCLRDataTask *This, + _In_ ULONG32 bufLen, + _Out_ ULONG32 *nameLen, + _Out_ WCHAR *name + ); + + PVOID GetLastExceptionState; +} IXCLRDataTaskVtbl; + +typedef struct IXCLRDataTask +{ + struct IXCLRDataTaskVtbl *lpVtbl; +} IXCLRDataTask; + +#define IXCLRDataTask_QueryInterface(This, riid, ppvObject) \ + ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) + +#define IXCLRDataTask_AddRef(This) \ + ((This)->lpVtbl->AddRef(This)) + +#define IXCLRDataTask_Release(This) \ + ((This)->lpVtbl->Release(This)) + +#define IXCLRDataTask_GetProcess(This, process) \ + ((This)->lpVtbl->GetProcess(This, process)) + +#define IXCLRDataTask_GetCurrentAppDomain(This, appDomain) \ + ((This)->lpVtbl->GetCurrentAppDomain(This, appDomain)) + +#define IXCLRDataTask_GetUniqueID(This, id) \ + ((This)->lpVtbl->GetUniqueID(This, id)) + +#define IXCLRDataTask_GetFlags(This, flags) \ + ((This)->lpVtbl->GetFlags(This, flags)) + +#define IXCLRDataTask_CreateStackWalk(This, flags, stackWalk) \ + ((This)->lpVtbl->CreateStackWalk(This, flags, stackWalk)) + +#define IXCLRDataTask_GetOSThreadID(This, id) \ + ((This)->lpVtbl->GetOSThreadID(This, id)) + +#define IXCLRDataTask_GetName(This, bufLen, nameLen, name) \ + ((This)->lpVtbl->GetName(This, bufLen, nameLen, name)) + +typedef enum +{ + CLRDATA_SIMPFRAME_UNRECOGNIZED = 0x1, + CLRDATA_SIMPFRAME_MANAGED_METHOD = 0x2, + CLRDATA_SIMPFRAME_RUNTIME_MANAGED_CODE = 0x4, + CLRDATA_SIMPFRAME_RUNTIME_UNMANAGED_CODE = 0x8 +} CLRDataSimpleFrameType; + +typedef enum +{ + CLRDATA_DETFRAME_UNRECOGNIZED, + CLRDATA_DETFRAME_UNKNOWN_STUB, + CLRDATA_DETFRAME_CLASS_INIT, + CLRDATA_DETFRAME_EXCEPTION_FILTER, + CLRDATA_DETFRAME_SECURITY, + CLRDATA_DETFRAME_CONTEXT_POLICY, + CLRDATA_DETFRAME_INTERCEPTION, + CLRDATA_DETFRAME_PROCESS_START, + CLRDATA_DETFRAME_THREAD_START, + CLRDATA_DETFRAME_TRANSITION_TO_MANAGED, + CLRDATA_DETFRAME_TRANSITION_TO_UNMANAGED, + CLRDATA_DETFRAME_COM_INTEROP_STUB, + CLRDATA_DETFRAME_DEBUGGER_EVAL, + CLRDATA_DETFRAME_CONTEXT_SWITCH, + CLRDATA_DETFRAME_FUNC_EVAL, + CLRDATA_DETFRAME_FINALLY +} CLRDataDetailedFrameType; + +typedef enum +{ + CLRDATA_STACK_SET_UNWIND_CONTEXT = 0x00000000, + CLRDATA_STACK_SET_CURRENT_CONTEXT = 0x00000001 +} CLRDataStackSetContextFlag; + +typedef struct IXCLRDataStackWalkVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + _In_ IXCLRDataStackWalk *This, + _In_ REFIID riid, + _Outptr_ void **ppvObject + ); + + ULONG (STDMETHODCALLTYPE *AddRef)( + _In_ IXCLRDataStackWalk *This + ); + + ULONG (STDMETHODCALLTYPE *Release)( + _In_ IXCLRDataStackWalk *This + ); + + HRESULT (STDMETHODCALLTYPE *GetContext)( + _In_ IXCLRDataStackWalk *This, + _In_ ULONG32 contextFlags, + _In_ ULONG32 contextBufSize, + _Out_ ULONG32 *contextSize, + _Out_ BYTE *contextBuf + ); + + PVOID SetContext; + + HRESULT (STDMETHODCALLTYPE *Next)( + _In_ IXCLRDataStackWalk *This + ); + + HRESULT (STDMETHODCALLTYPE *GetStackSizeSkipped)( + _In_ IXCLRDataStackWalk *This, + _Out_ ULONG64 *stackSizeSkipped + ); + + HRESULT (STDMETHODCALLTYPE *GetFrameType)( + _In_ IXCLRDataStackWalk *This, + _Out_ CLRDataSimpleFrameType *simpleType, + _Out_ CLRDataDetailedFrameType *detailedType + ); + + HRESULT (STDMETHODCALLTYPE *GetFrame)( + _In_ IXCLRDataStackWalk *This, + _Out_ PVOID *frame + ); + + HRESULT (STDMETHODCALLTYPE *Request)( + _In_ IXCLRDataStackWalk *This, + _In_ ULONG32 reqCode, + _In_ ULONG32 inBufferSize, + _In_ BYTE *inBuffer, + _In_ ULONG32 outBufferSize, + _Out_ BYTE *outBuffer + ); + + HRESULT (STDMETHODCALLTYPE *SetContext2)( + _In_ IXCLRDataStackWalk *This, + _In_ ULONG32 flags, + _In_ ULONG32 contextSize, + _In_ BYTE *context + ); +} IXCLRDataStackWalkVtbl; + +typedef struct IXCLRDataStackWalk +{ + struct IXCLRDataStackWalkVtbl *lpVtbl; +} IXCLRDataStackWalk; + +#define IXCLRDataStackWalk_QueryInterface(This, riid, ppvObject) \ + ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) + +#define IXCLRDataStackWalk_AddRef(This) \ + ((This)->lpVtbl->AddRef(This)) + +#define IXCLRDataStackWalk_Release(This) \ + ((This)->lpVtbl->Release(This)) + +#define IXCLRDataStackWalk_GetContext(This, contextFlags, contextBufSize, contextSize, contextBuf) \ + ((This)->lpVtbl->GetContext(This, contextFlags, contextBufSize, contextSize, contextBuf)) + +#define IXCLRDataStackWalk_Next(This) \ + ((This)->lpVtbl->Next(This)) + +#define IXCLRDataStackWalk_GetStackSizeSkipped(This, stackSizeSkipped) \ + ((This)->lpVtbl->GetStackSizeSkipped(This, stackSizeSkipped)) + +#define IXCLRDataStackWalk_GetFrameType(This, simpleType, detailedType) \ + ((This)->lpVtbl->GetFrameType(This, simpleType, detailedType)) + +#define IXCLRDataStackWalk_GetFrame(This, frame) \ + ((This)->lpVtbl->GetFrame(This, frame)) + +#define IXCLRDataStackWalk_Request(This, reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) \ + ((This)->lpVtbl->SetContext2(This, reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer)) + +#define IXCLRDataStackWalk_SetContext2(This, flags, contextSize, context) \ + ((This)->lpVtbl->SetContext2(This, flags, contextSize, context)) + +typedef struct IXCLRDataFrameVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + _In_ IXCLRDataFrame *This, + _In_ REFIID riid, + _Outptr_ void **ppvObject + ); + + ULONG (STDMETHODCALLTYPE *AddRef)( + _In_ IXCLRDataFrame *This + ); + + ULONG (STDMETHODCALLTYPE *Release)( + _In_ IXCLRDataFrame *This + ); + + HRESULT (STDMETHODCALLTYPE *GetFrameType)( + _In_ IXCLRDataFrame *This, + _Out_ CLRDataSimpleFrameType *simpleType, + _Out_ CLRDataDetailedFrameType *detailedType + ); + + HRESULT (STDMETHODCALLTYPE *GetContext)( + _In_ IXCLRDataFrame *This, + _In_ ULONG32 contextFlags, + _In_ ULONG32 contextBufSize, + _Out_ ULONG32 *contextSize, + _Out_ BYTE *contextBuf + ); + + PVOID GetAppDomain; + PVOID GetNumArguments; + PVOID GetArgumentByIndex; + PVOID GetNumLocalVariables; + PVOID GetLocalVariableByIndex; + + HRESULT (STDMETHODCALLTYPE *GetCodeName)( + _In_ IXCLRDataFrame *This, + _In_ ULONG32 *flags, + _In_ ULONG32 *bufLen, + _Out_ ULONG32 *nameLen, + _Out_ WCHAR *nameBuf + ); +} IXCLRDataFrameVtbl; + +typedef struct IXCLRDataFrame +{ + IXCLRDataFrameVtbl *lpVtbl; +} IXCLRDataFrame; + +#define IXCLRDataFrame_QueryInterface(This, riid, ppvObject) \ + ((This)->lpVtbl->QueryInterface(This, riid, ppvObject)) + +#define IXCLRDataFrame_AddRef(This) \ + ((This)->lpVtbl->AddRef(This)) + +#define IXCLRDataFrame_Release(This) \ + ((This)->lpVtbl->Release(This)) + +#define IXCLRDataFrame_GetFrameType(This, simpleType, detailedType) \ + ((This)->lpVtbl->GetFrameType(This, simpleType, detailedType)) + +#define IXCLRDataFrame_GetContext(This, contextFlags, contextBufSize, contextSize, contextBuf) \ + ((This)->lpVtbl->GetContext(This, contextFlags, contextBufSize, contextSize, contextBuf)) + +#define IXCLRDataFrame_GetCodeName(This, flags, bufLen, nameLen, nameBuf) \ + ((This)->lpVtbl->GetCodeName(This, flags, bufLen, nameLen, nameBuf)) + +// DnCLRDataTarget + +typedef struct +{ + ICLRDataTargetVtbl *VTable; + + ULONG RefCount; + + HANDLE ProcessId; + HANDLE ProcessHandle; + BOOLEAN IsWow64; +} DnCLRDataTarget; + +ICLRDataTarget *DnCLRDataTarget_Create( + _In_ HANDLE ProcessId + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_QueryInterface( + _In_ ICLRDataTarget *This, + _In_ REFIID Riid, + _Out_ PVOID *Object + ); + +ULONG STDMETHODCALLTYPE DnCLRDataTarget_AddRef( + _In_ ICLRDataTarget *This + ); + +ULONG STDMETHODCALLTYPE DnCLRDataTarget_Release( + _In_ ICLRDataTarget *This + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetMachineType( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *machineType + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetPointerSize( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *pointerSize + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetImageBase( + _In_ ICLRDataTarget *This, + _In_ LPCWSTR imagePath, + _Out_ CLRDATA_ADDRESS *baseAddress + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_ReadVirtual( + _In_ ICLRDataTarget *This, + _In_ CLRDATA_ADDRESS address, + _Out_ BYTE *buffer, + _In_ ULONG32 bytesRequested, + _Out_ ULONG32 *bytesRead + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_WriteVirtual( + _In_ ICLRDataTarget *This, + _In_ CLRDATA_ADDRESS address, + _In_ BYTE *buffer, + _In_ ULONG32 bytesRequested, + _Out_ ULONG32 *bytesWritten + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetTLSValue( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 index, + _Out_ CLRDATA_ADDRESS *value + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_SetTLSValue( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 index, + _In_ CLRDATA_ADDRESS value + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetCurrentThreadID( + _In_ ICLRDataTarget *This, + _Out_ ULONG32 *threadID + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_GetThreadContext( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 contextFlags, + _In_ ULONG32 contextSize, + _Out_ BYTE *context + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_SetThreadContext( + _In_ ICLRDataTarget *This, + _In_ ULONG32 threadID, + _In_ ULONG32 contextSize, + _In_ BYTE *context + ); + +HRESULT STDMETHODCALLTYPE DnCLRDataTarget_Request( + _In_ ICLRDataTarget *This, + _In_ ULONG32 reqCode, + _In_ ULONG32 inBufferSize, + _In_ BYTE *inBuffer, + _In_ ULONG32 outBufferSize, + _Out_ BYTE *outBuffer + ); + +typedef struct _PHP_GET_IMAGE_BASE_CONTEXT +{ + UNICODE_STRING ImagePath; + PVOID BaseAddress; +} PHP_GET_IMAGE_BASE_CONTEXT, *PPHP_GET_IMAGE_BASE_CONTEXT; + +#endif diff --git a/plugins/DotNetTools/counters.c b/plugins/DotNetTools/counters.c new file mode 100644 index 0000000..7e3316d --- /dev/null +++ b/plugins/DotNetTools/counters.c @@ -0,0 +1,1044 @@ +/* + * Process Hacker .NET Tools - + * IPC support functions + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "clr/dbgappdomain.h" +#include "clr/ipcheader.h" +#include "clr/ipcshared.h" + +typedef PVOID (NTAPI* _RtlCreateBoundaryDescriptor)( + _In_ PUNICODE_STRING Name, + _In_ ULONG Flags + ); + +typedef NTSTATUS (NTAPI* _RtlAddSIDToBoundaryDescriptor)( + _Inout_ PVOID* BoundaryDescriptor, + _In_ PSID RequiredSid + ); + +typedef VOID (NTAPI* _RtlDeleteBoundaryDescriptor)( + _In_ PVOID BoundaryDescriptor + ); + +typedef NTSTATUS (WINAPI* _NtOpenPrivateNamespace)( + _Out_ PHANDLE NamespaceHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, + _In_ PVOID BoundaryDescriptor + ); + +static _NtOpenPrivateNamespace NtOpenPrivateNamespace_I = NULL; +static _RtlCreateBoundaryDescriptor RtlCreateBoundaryDescriptor_I = NULL; +static _RtlDeleteBoundaryDescriptor RtlDeleteBoundaryDescriptor_I = NULL; +static _RtlAddSIDToBoundaryDescriptor RtlAddSIDToBoundaryDescriptor_I = NULL; + +PPH_STRING GeneratePrivateName(_In_ HANDLE ProcessId) +{ + return PhaFormatString(L"\\BaseNamedObjects\\" CorLegacyPrivateIPCBlock, HandleToUlong(ProcessId)); +} + +PPH_STRING GeneratePrivateNameV4(_In_ HANDLE ProcessId) +{ + return PhaFormatString(L"\\BaseNamedObjects\\" CorLegacyPrivateIPCBlockTempV4, HandleToUlong(ProcessId)); +} + +PPH_STRING GenerateLegacyPublicName(_In_ HANDLE ProcessId) +{ + return PhaFormatString(L"\\BaseNamedObjects\\" CorLegacyPublicIPCBlock, HandleToUlong(ProcessId)); +} + +PPH_STRING GenerateSxSPublicNameV4(_In_ HANDLE ProcessId) +{ + return PhaFormatString(L"\\BaseNamedObjects\\" CorSxSPublicIPCBlock, HandleToUlong(ProcessId)); +} + +PPH_STRING GenerateBoundaryDescriptorName(_In_ HANDLE ProcessId) +{ + return PhaFormatString(CorSxSBoundaryDescriptor, HandleToUlong(ProcessId)); +} + +PVOID GetLegacyBlockTableEntry( + _In_ BOOLEAN Wow64, + _In_ PVOID IpcBlockAddress, + _In_ ULONG EntryId + ) +{ + if (Wow64) + { + LegacyPrivateIPCControlBlock_Wow64* IpcBlock = IpcBlockAddress; + + // skip over directory (variable size) + ULONG offsetBase = IPC_ENTRY_OFFSET_BASE_X86 + IpcBlock->FullIPCHeader.Header.NumEntries * sizeof(IPCEntry); + // Directory has offset in bytes of block + ULONG offsetEntry = IpcBlock->FullIPCHeader.EntryTable[EntryId].Offset; + + return ((PBYTE)IpcBlock) + offsetBase + offsetEntry; + } + else + { + LegacyPrivateIPCControlBlock* IpcBlock = IpcBlockAddress; + + // skip over directory (variable size) + ULONG offsetBase = IPC_ENTRY_OFFSET_BASE_X64 + IpcBlock->FullIPCHeader.Header.NumEntries * sizeof(IPCEntry); + // Directory has offset in bytes of block + ULONG offsetEntry = IpcBlock->FullIPCHeader.EntryTable[EntryId].Offset; + + return ((PBYTE)IpcBlock) + offsetBase + offsetEntry; + } +} + +PVOID GetPerfIpcBlock_V2( + _In_ BOOLEAN Wow64, + _In_ PVOID BlockTableAddress + ) +{ + if (Wow64) + { + LegacyPublicIPCControlBlock_Wow64* ipcLegacyBlockTable_Wow64 = BlockTableAddress; + + if (ipcLegacyBlockTable_Wow64->FullIPCHeaderLegacyPublic.Header.Version > VER_LEGACYPUBLIC_IPC_BLOCK) + { + return NULL; + } + + return &ipcLegacyBlockTable_Wow64->PerfIpcBlock; + } + else + { + LegacyPublicIPCControlBlock* ipcLegacyBlockTable = BlockTableAddress; + + if (ipcLegacyBlockTable->FullIPCHeaderLegacyPublic.Header.Version > VER_IPC_BLOCK) + { + return NULL; + } + + return &ipcLegacyBlockTable->PerfIpcBlock; + } +} + +PVOID GetPerfIpcBlock_V4( + _In_ BOOLEAN Wow64, + _In_ PVOID BlockTableAddress + ) +{ + if (Wow64) + { + IPCControlBlockTable_Wow64* ipcBlockTable_Wow64 = BlockTableAddress; + + if (ipcBlockTable_Wow64->Blocks->Header.Version > VER_IPC_BLOCK) + { + return NULL; + } + + return &ipcBlockTable_Wow64->Blocks->PerfIpcBlock; + } + else + { + IPCControlBlockTable_Wow64* ipcBlockTable = BlockTableAddress; + + if (ipcBlockTable->Blocks->Header.Version > VER_IPC_BLOCK) + { + return NULL; + } + + return &ipcBlockTable->Blocks->PerfIpcBlock; + } +} + +PPH_LIST EnumAppDomainIpcBlock( + _In_ HANDLE ProcessHandle, + _In_ AppDomainEnumerationIPCBlock* AppDomainIpcBlock + ) +{ + LARGE_INTEGER timeout; + SIZE_T appDomainInfoBlockLength; + HANDLE legacyPrivateBlockMutexHandle = NULL; + AppDomainEnumerationIPCBlock tempBlock; + AppDomainInfo* appDomainInfoBlock = NULL; + PPH_LIST appDomainsList = PhCreateList(1); + + __try + { + // If the mutex isn't filled in, the CLR is either starting up or shutting down + if (!AppDomainIpcBlock->Mutex) + { + __leave; + } + + // Dup the valid mutex handle into this process. + if (!NT_SUCCESS(NtDuplicateObject( + ProcessHandle, + AppDomainIpcBlock->Mutex, + NtCurrentProcess(), + &legacyPrivateBlockMutexHandle, + GENERIC_ALL, + 0, + DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES + ))) + { + __leave; + } + + // Acquire the mutex, only waiting two seconds. + // We can't actually gaurantee that the target put a mutex object in here. + if (NtWaitForSingleObject( + legacyPrivateBlockMutexHandle, + FALSE, + PhTimeoutFromMilliseconds(&timeout, 2000) + ) == STATUS_WAIT_0) + { + // Make sure the mutex handle is still valid. If its not, then we lost a shutdown race. + if (!AppDomainIpcBlock->Mutex) + { + __leave; + } + } + else + { + // Again, landing here is most probably a shutdown race. + __leave; + } + + // Beware: If the target pid is not properly honoring the mutex, the data in the IPC block may still shift underneath us. + // If we get here, then hMutex is held by this process. + + // Make a copy of the IPC block so that we can gaurantee that it's not changing on us. + memcpy(&tempBlock, AppDomainIpcBlock, sizeof(AppDomainEnumerationIPCBlock)); + + // It's possible the process will not have any appdomains. + if ((tempBlock.ListOfAppDomains == NULL) != (tempBlock.SizeInBytes == 0)) + { + __leave; + } + + // All the data in the IPC block is signed integers. They should never be negative, + // so check that now. + if ((tempBlock.TotalSlots < 0) || + (tempBlock.NumOfUsedSlots < 0) || + (tempBlock.LastFreedSlot < 0) || + (tempBlock.SizeInBytes < 0) || + (tempBlock.ProcessNameLengthInBytes < 0)) + { + __leave; + } + + // Allocate memory to read the remote process' memory into + appDomainInfoBlockLength = tempBlock.SizeInBytes; + + // Check other invariants. + if (appDomainInfoBlockLength != tempBlock.TotalSlots * sizeof(AppDomainInfo)) + { + __leave; + } + + appDomainInfoBlock = (AppDomainInfo*)PhAllocate(appDomainInfoBlockLength); + memset(appDomainInfoBlock, 0, appDomainInfoBlockLength); + + if (!NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + tempBlock.ListOfAppDomains, + appDomainInfoBlock, + appDomainInfoBlockLength, + NULL + ))) + { + PhFree(appDomainInfoBlock); + __leave; + } + + // Collect all the AppDomain names into a list of strings. + for (INT i = 0; i < tempBlock.NumOfUsedSlots; i++) + { + SIZE_T appDomainNameLength; + PVOID appDomainName; + + if (!appDomainInfoBlock[i].AppDomainName) + continue; + + // Should be positive, and at least have a null-terminator character. + if (appDomainInfoBlock[i].NameLengthInBytes <= 1) + continue; + + // Make sure buffer has right geometry. + if (appDomainInfoBlock[i].NameLengthInBytes < 0) + continue; + + // If it's not on a WCHAR boundary, then we may have a 1-byte buffer-overflow. + appDomainNameLength = appDomainInfoBlock[i].NameLengthInBytes / sizeof(WCHAR); + + if ((appDomainNameLength * sizeof(WCHAR)) != appDomainInfoBlock[i].NameLengthInBytes) + continue; + + // It should at least have 1 char for the null terminator. + if (appDomainNameLength < 1) + continue; + + // We know the string is a well-formed null-terminated string, + // but beyond that, we can't verify that the data is actually truthful. + appDomainName = PhAllocate(appDomainInfoBlock[i].NameLengthInBytes + 1); + memset(appDomainName, 0, appDomainInfoBlock[i].NameLengthInBytes + 1); + + if (!NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + appDomainInfoBlock[i].AppDomainName, + appDomainName, + appDomainInfoBlock[i].NameLengthInBytes, + NULL + ))) + { + PhFree(appDomainName); + continue; + } + + PhAddItemList(appDomainsList, appDomainName); + } + } + __finally + { + if (appDomainInfoBlock) + { + PhFree(appDomainInfoBlock); + } + + if (legacyPrivateBlockMutexHandle) + { + NtReleaseMutant(legacyPrivateBlockMutexHandle, NULL); + NtClose(legacyPrivateBlockMutexHandle); + } + } + + return appDomainsList; +} + +PPH_LIST EnumAppDomainIpcBlockWow64( + _In_ HANDLE ProcessHandle, + _In_ AppDomainEnumerationIPCBlock_Wow64* AppDomainIpcBlock + ) +{ + LARGE_INTEGER timeout; + SIZE_T appDomainInfoBlockLength; + HANDLE legacyPrivateBlockMutexHandle = NULL; + AppDomainEnumerationIPCBlock_Wow64 tempBlock; + AppDomainInfo_Wow64* appDomainInfoBlock = NULL; + PPH_LIST appDomainsList = PhCreateList(1); + + __try + { + // If the mutex isn't filled in, the CLR is either starting up or shutting down + if (!AppDomainIpcBlock->Mutex) + { + __leave; + } + + // Dup the valid mutex handle into this process. + if (!NT_SUCCESS(NtDuplicateObject( + ProcessHandle, + UlongToHandle(AppDomainIpcBlock->Mutex), + NtCurrentProcess(), + &legacyPrivateBlockMutexHandle, + GENERIC_ALL, + 0, + DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES + ))) + { + __leave; + } + + // Acquire the mutex, only waiting two seconds. + // We can't actually gaurantee that the target put a mutex object in here. + if (NtWaitForSingleObject( + legacyPrivateBlockMutexHandle, + FALSE, + PhTimeoutFromMilliseconds(&timeout, 2000) + ) == STATUS_WAIT_0) + { + // Make sure the mutex handle is still valid. If its not, then we lost a shutdown race. + if (!AppDomainIpcBlock->Mutex) + { + __leave; + } + } + else + { + // Again, landing here is most probably a shutdown race. + __leave; + } + + // Beware: If the target pid is not properly honoring the mutex, the data in the IPC block may still shift underneath us. + // If we get here, then hMutex is held by this process. + + // Make a copy of the IPC block so that we can gaurantee that it's not changing on us. + memcpy(&tempBlock, AppDomainIpcBlock, sizeof(AppDomainEnumerationIPCBlock_Wow64)); + + // It's possible the process will not have any appdomains. + if ((tempBlock.ListOfAppDomains == 0) != (tempBlock.SizeInBytes == 0)) + { + __leave; + } + + // All the data in the IPC block is signed integers. They should never be negative, + // so check that now. + if ((tempBlock.TotalSlots < 0) || + (tempBlock.NumOfUsedSlots < 0) || + (tempBlock.LastFreedSlot < 0) || + (tempBlock.SizeInBytes < 0) || + (tempBlock.ProcessNameLengthInBytes < 0)) + { + __leave; + } + + // Allocate memory to read the remote process' memory into + appDomainInfoBlockLength = tempBlock.SizeInBytes; + + // Check other invariants. + if (appDomainInfoBlockLength != tempBlock.TotalSlots * sizeof(AppDomainInfo_Wow64)) + { + __leave; + } + + appDomainInfoBlock = (AppDomainInfo_Wow64*)PhAllocate(appDomainInfoBlockLength); + memset(appDomainInfoBlock, 0, appDomainInfoBlockLength); + + if (!NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + UlongToPtr(tempBlock.ListOfAppDomains), + appDomainInfoBlock, + appDomainInfoBlockLength, + NULL + ))) + { + PhFree(appDomainInfoBlock); + __leave; + } + + // Collect all the AppDomain names into a list of strings. + for (INT i = 0; i < tempBlock.NumOfUsedSlots; i++) + { + SIZE_T appDomainNameLength; + PVOID appDomainName; + + if (!appDomainInfoBlock[i].AppDomainName) + continue; + + // Should be positive, and at least have a null-terminator character. + if (appDomainInfoBlock[i].NameLengthInBytes <= 1) + continue; + + // Make sure buffer has right geometry. + if (appDomainInfoBlock[i].NameLengthInBytes < 0) + continue; + + // If it's not on a WCHAR boundary, then we may have a 1-byte buffer-overflow. + appDomainNameLength = appDomainInfoBlock[i].NameLengthInBytes / sizeof(WCHAR); + + if ((appDomainNameLength * sizeof(WCHAR)) != appDomainInfoBlock[i].NameLengthInBytes) + continue; + + // It should at least have 1 char for the null terminator. + if (appDomainNameLength < 1) + continue; + + // We know the string is a well-formed null-terminated string, + // but beyond that, we can't verify that the data is actually truthful. + appDomainName = PhAllocate(appDomainInfoBlock[i].NameLengthInBytes + 1); + memset(appDomainName, 0, appDomainInfoBlock[i].NameLengthInBytes + 1); + + if (!NT_SUCCESS(NtReadVirtualMemory( + ProcessHandle, + UlongToPtr(appDomainInfoBlock[i].AppDomainName), + appDomainName, + appDomainInfoBlock[i].NameLengthInBytes, + NULL + ))) + { + PhFree(appDomainName); + continue; + } + + PhAddItemList(appDomainsList, appDomainName); + } + } + __finally + { + if (appDomainInfoBlock) + { + PhFree(appDomainInfoBlock); + } + + if (legacyPrivateBlockMutexHandle) + { + NtReleaseMutant(legacyPrivateBlockMutexHandle, NULL); + NtClose(legacyPrivateBlockMutexHandle); + } + } + + return appDomainsList; +} + +BOOLEAN OpenDotNetPublicControlBlock_V2( + _In_ HANDLE ProcessId, + _Out_ HANDLE* BlockTableHandle, + _Out_ PVOID* BlockTableAddress + ) +{ + BOOLEAN result = FALSE; + HANDLE blockTableHandle = NULL; + PVOID blockTableAddress = NULL; + UNICODE_STRING sectionNameUs; + OBJECT_ATTRIBUTES objectAttributes; + LARGE_INTEGER sectionOffset = { 0 }; + SIZE_T viewSize = 0; + + __try + { + if (!PhStringRefToUnicodeString(&GenerateLegacyPublicName(ProcessId)->sr, §ionNameUs)) + __leave; + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + 0, + NULL, + NULL + ); + + if (!NT_SUCCESS(NtOpenSection( + &blockTableHandle, + SECTION_MAP_READ, + &objectAttributes + ))) + { + __leave; + } + + if (!NT_SUCCESS(NtMapViewOfSection( + blockTableHandle, + NtCurrentProcess(), + &blockTableAddress, + 0, + viewSize, + §ionOffset, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ))) + { + __leave; + } + + *BlockTableHandle = blockTableHandle; + *BlockTableAddress = blockTableAddress; + + result = TRUE; + } + __finally + { + if (!result) + { + if (blockTableHandle) + { + NtClose(blockTableHandle); + } + + if (blockTableAddress) + { + NtUnmapViewOfSection(NtCurrentProcess(), blockTableAddress); + } + + *BlockTableHandle = NULL; + *BlockTableAddress = NULL; + } + } + + return result; +} + +BOOLEAN OpenDotNetPublicControlBlock_V4( + _In_ BOOLEAN IsImmersive, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _Out_ HANDLE* BlockTableHandle, + _Out_ PVOID* BlockTableAddress + ) +{ + BOOLEAN result = FALSE; + PVOID boundaryDescriptorHandle = NULL; + HANDLE privateNamespaceHandle = NULL; + HANDLE blockTableHandle = NULL; + HANDLE tokenHandle = NULL; + PSID everyoneSIDHandle = NULL; + PVOID blockTableAddress = NULL; + LARGE_INTEGER sectionOffset = { 0 }; + SIZE_T viewSize = 0; + PTOKEN_APPCONTAINER_INFORMATION appContainerInfo = NULL; + SID_IDENTIFIER_AUTHORITY SIDWorldAuth = SECURITY_WORLD_SID_AUTHORITY; + + __try + { + if (WindowsVersion < WINDOWS_VISTA) + { + UNICODE_STRING sectionNameUs; + OBJECT_ATTRIBUTES objectAttributes; + + if (!PhStringRefToUnicodeString(&GenerateSxSPublicNameV4(ProcessId)->sr, §ionNameUs)) + __leave; + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + 0, + NULL, + NULL + ); + + if (!NT_SUCCESS(NtOpenSection( + &blockTableHandle, + SECTION_MAP_READ, + &objectAttributes + ))) + { + __leave; + } + } + else + { + static PH_INITONCE initOnce = PH_INITONCE_INIT; + UNICODE_STRING prefixNameUs; + UNICODE_STRING sectionNameUs; + UNICODE_STRING boundaryNameUs; + OBJECT_ATTRIBUTES namespaceObjectAttributes; + OBJECT_ATTRIBUTES sectionObjectAttributes; + + if (PhBeginInitOnce(&initOnce)) + { + PVOID ntdll; + + ntdll = PhGetDllHandle(L"ntdll.dll"); + NtOpenPrivateNamespace_I = PhGetProcedureAddress(ntdll, "NtOpenPrivateNamespace", 0); + RtlCreateBoundaryDescriptor_I = PhGetProcedureAddress(ntdll, "RtlCreateBoundaryDescriptor", 0); + RtlDeleteBoundaryDescriptor_I = PhGetProcedureAddress(ntdll, "RtlDeleteBoundaryDescriptor", 0); + RtlAddSIDToBoundaryDescriptor_I = PhGetProcedureAddress(ntdll, "RtlAddSIDToBoundaryDescriptor", 0); + + PhEndInitOnce(&initOnce); + } + + if (!PhStringRefToUnicodeString(&GenerateBoundaryDescriptorName(ProcessId)->sr, &boundaryNameUs)) + __leave; + + if (!(boundaryDescriptorHandle = RtlCreateBoundaryDescriptor_I(&boundaryNameUs, 0))) + __leave; + + if (!NT_SUCCESS(RtlAllocateAndInitializeSid(&SIDWorldAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSIDHandle))) + __leave; + + if (!NT_SUCCESS(RtlAddSIDToBoundaryDescriptor_I(&boundaryDescriptorHandle, everyoneSIDHandle))) + __leave; + + if (WINDOWS_HAS_IMMERSIVE && IsImmersive) + { + if (NT_SUCCESS(NtOpenProcessToken(&tokenHandle, TOKEN_QUERY, ProcessHandle))) + { + ULONG returnLength = 0; + + if (NtQueryInformationToken( + tokenHandle, + TokenAppContainerSid, + NULL, + 0, + &returnLength + ) != STATUS_BUFFER_TOO_SMALL) + { + __leave; + } + + if (returnLength < 1) + __leave; + + appContainerInfo = PhAllocate(returnLength); + + if (!NT_SUCCESS(NtQueryInformationToken( + tokenHandle, + TokenAppContainerSid, + appContainerInfo, + returnLength, + &returnLength + ))) + { + __leave; + } + + if (!appContainerInfo->TokenAppContainer) + __leave; + + if (!NT_SUCCESS(RtlAddSIDToBoundaryDescriptor_I(&boundaryDescriptorHandle, appContainerInfo->TokenAppContainer))) + __leave; + } + } + + RtlInitUnicodeString(&prefixNameUs, CorSxSReaderPrivateNamespacePrefix); + + InitializeObjectAttributes( + &namespaceObjectAttributes, + &prefixNameUs, + OBJ_CASE_INSENSITIVE, + boundaryDescriptorHandle, + NULL + ); + + if (!NT_SUCCESS(NtOpenPrivateNamespace_I( + &privateNamespaceHandle, + MAXIMUM_ALLOWED, + &namespaceObjectAttributes, + boundaryDescriptorHandle + ))) + { + __leave; + } + + RtlInitUnicodeString(§ionNameUs, CorSxSVistaPublicIPCBlock); + + InitializeObjectAttributes( + §ionObjectAttributes, + §ionNameUs, + OBJ_CASE_INSENSITIVE, + privateNamespaceHandle, + NULL + ); + + if (!NT_SUCCESS(NtOpenSection( + &blockTableHandle, + SECTION_MAP_READ, + §ionObjectAttributes + ))) + { + __leave; + } + } + + if (!NT_SUCCESS(NtMapViewOfSection( + blockTableHandle, + NtCurrentProcess(), + &blockTableAddress, + 0, + viewSize, + §ionOffset, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ))) + { + __leave; + } + + *BlockTableHandle = blockTableHandle; + *BlockTableAddress = blockTableAddress; + + result = TRUE; + } + __finally + { + if (!result) + { + if (blockTableHandle) + { + NtClose(blockTableHandle); + } + + if (blockTableAddress) + { + NtUnmapViewOfSection(NtCurrentProcess(), blockTableAddress); + } + + *BlockTableHandle = NULL; + *BlockTableAddress = NULL; + } + + if (tokenHandle) + { + NtClose(tokenHandle); + } + + if (appContainerInfo) + { + PhFree(appContainerInfo); + } + + if (privateNamespaceHandle) + { + NtClose(privateNamespaceHandle); + } + + if (everyoneSIDHandle) + { + RtlFreeSid(everyoneSIDHandle); + } + + if (RtlDeleteBoundaryDescriptor_I && boundaryDescriptorHandle) + { + RtlDeleteBoundaryDescriptor_I(boundaryDescriptorHandle); + } + } + + return result; +} + +PPH_LIST QueryDotNetAppDomainsForPid_V2( + _In_ BOOLEAN Wow64, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId + ) +{ + LARGE_INTEGER sectionOffset = { 0 }; + SIZE_T viewSize = 0; + OBJECT_ATTRIBUTES objectAttributes; + UNICODE_STRING sectionNameUs; + HANDLE legacyPrivateBlockHandle = NULL; + PVOID ipcControlBlockTable = NULL; + PPH_LIST appDomainsList = NULL; + + __try + { + if (!PhStringRefToUnicodeString(&GeneratePrivateName(ProcessId)->sr, §ionNameUs)) + __leave; + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + 0, + NULL, + NULL + ); + + if (!NT_SUCCESS(NtOpenSection( + &legacyPrivateBlockHandle, + SECTION_MAP_READ, + &objectAttributes + ))) + { + __leave; + } + + if (!NT_SUCCESS(NtMapViewOfSection( + legacyPrivateBlockHandle, + NtCurrentProcess(), + &ipcControlBlockTable, + 0, + viewSize, + §ionOffset, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ))) + { + __leave; + } + + if (Wow64) + { + LegacyPrivateIPCControlBlock_Wow64* legacyPrivateBlock; + AppDomainEnumerationIPCBlock_Wow64* appDomainEnumBlock; + + legacyPrivateBlock = (LegacyPrivateIPCControlBlock_Wow64*)ipcControlBlockTable; + + // NOTE: .NET 2.0 processes do not have the IPC_FLAG_INITIALIZED flag. + + // Check the IPCControlBlock version is valid. + if (legacyPrivateBlock->FullIPCHeader.Header.Version > VER_LEGACYPRIVATE_IPC_BLOCK) + { + __leave; + } + + appDomainEnumBlock = GetLegacyBlockTableEntry( + Wow64, + ipcControlBlockTable, + eLegacyPrivateIPC_AppDomain + ); + + appDomainsList = EnumAppDomainIpcBlockWow64( + ProcessHandle, + appDomainEnumBlock + ); + } + else + { + LegacyPrivateIPCControlBlock* legacyPrivateBlock; + AppDomainEnumerationIPCBlock* appDomainEnumBlock; + + legacyPrivateBlock = (LegacyPrivateIPCControlBlock*)ipcControlBlockTable; + + // NOTE: .NET 2.0 processes do not have the IPC_FLAG_INITIALIZED flag. + + // Check the IPCControlBlock version is valid. + if (legacyPrivateBlock->FullIPCHeader.Header.Version > VER_LEGACYPRIVATE_IPC_BLOCK) + { + __leave; + } + + appDomainEnumBlock = GetLegacyBlockTableEntry( + Wow64, + ipcControlBlockTable, + eLegacyPrivateIPC_AppDomain + ); + + appDomainsList = EnumAppDomainIpcBlock( + ProcessHandle, + appDomainEnumBlock + ); + } + } + __finally + { + if (ipcControlBlockTable) + { + NtUnmapViewOfSection(NtCurrentProcess(), ipcControlBlockTable); + } + + if (legacyPrivateBlockHandle) + { + NtClose(legacyPrivateBlockHandle); + } + } + + return appDomainsList; +} + +PPH_LIST QueryDotNetAppDomainsForPid_V4( + _In_ BOOLEAN Wow64, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId + ) +{ + HANDLE legacyPrivateBlockHandle = NULL; + PVOID ipcControlBlockTable = NULL; + LARGE_INTEGER sectionOffset = { 0 }; + SIZE_T viewSize = 0; + OBJECT_ATTRIBUTES objectAttributes; + UNICODE_STRING sectionNameUs; + PPH_LIST appDomainsList = NULL; + + __try + { + if (!PhStringRefToUnicodeString(&GeneratePrivateNameV4(ProcessId)->sr, §ionNameUs)) + __leave; + + InitializeObjectAttributes( + &objectAttributes, + §ionNameUs, + 0, + NULL, + NULL + ); + + if (!NT_SUCCESS(NtOpenSection( + &legacyPrivateBlockHandle, + SECTION_MAP_READ, + &objectAttributes + ))) + { + __leave; + } + + if (!NT_SUCCESS(NtMapViewOfSection( + legacyPrivateBlockHandle, + NtCurrentProcess(), + &ipcControlBlockTable, + 0, + viewSize, + §ionOffset, + &viewSize, + ViewShare, + 0, + PAGE_READONLY + ))) + { + __leave; + } + + if (Wow64) + { + LegacyPrivateIPCControlBlock_Wow64* legacyPrivateBlock; + AppDomainEnumerationIPCBlock_Wow64* appDomainEnumBlock; + + legacyPrivateBlock = (LegacyPrivateIPCControlBlock_Wow64*)ipcControlBlockTable; + appDomainEnumBlock = &legacyPrivateBlock->AppDomainBlock; + + // Check the IPCControlBlock is initialized. + if ((legacyPrivateBlock->FullIPCHeader.Header.Flags & IPC_FLAG_INITIALIZED) != IPC_FLAG_INITIALIZED) + { + __leave; + } + + // Check the IPCControlBlock version is valid. + if (legacyPrivateBlock->FullIPCHeader.Header.Version > VER_LEGACYPRIVATE_IPC_BLOCK) + { + __leave; + } + + appDomainsList = EnumAppDomainIpcBlockWow64( + ProcessHandle, + appDomainEnumBlock + ); + } + else + { + LegacyPrivateIPCControlBlock* legacyPrivateBlock; + AppDomainEnumerationIPCBlock* appDomainEnumBlock; + + legacyPrivateBlock = (LegacyPrivateIPCControlBlock*)ipcControlBlockTable; + appDomainEnumBlock = &legacyPrivateBlock->AppDomainBlock; + + // Check the IPCControlBlock is initialized. + if ((legacyPrivateBlock->FullIPCHeader.Header.Flags & IPC_FLAG_INITIALIZED) != IPC_FLAG_INITIALIZED) + { + __leave; + } + + // Check the IPCControlBlock version is valid. + if (legacyPrivateBlock->FullIPCHeader.Header.Version > VER_LEGACYPRIVATE_IPC_BLOCK) + { + __leave; + } + + appDomainsList = EnumAppDomainIpcBlock( + ProcessHandle, + appDomainEnumBlock + ); + } + } + __finally + { + if (ipcControlBlockTable) + { + NtUnmapViewOfSection(NtCurrentProcess(), ipcControlBlockTable); + } + + if (legacyPrivateBlockHandle) + { + NtClose(legacyPrivateBlockHandle); + } + } + + return appDomainsList; +} \ No newline at end of file diff --git a/plugins/DotNetTools/dn.h b/plugins/DotNetTools/dn.h new file mode 100644 index 0000000..537e155 --- /dev/null +++ b/plugins/DotNetTools/dn.h @@ -0,0 +1,144 @@ +/* + * Process Hacker .NET Tools + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef DN_H +#define DN_H + +#define CINTERFACE +#define COBJMACROS +#include +#include + +#include "resource.h" + +#define PLUGIN_NAME L"ProcessHacker.DotNetTools" +#define SETTING_NAME_ASM_TREE_LIST_COLUMNS (PLUGIN_NAME L".AsmTreeListColumns") +#define SETTING_NAME_DOT_NET_CATEGORY_INDEX (PLUGIN_NAME L".DotNetCategoryIndex") +#define SETTING_NAME_DOT_NET_COUNTERS_COLUMNS (PLUGIN_NAME L".DotNetListColumns") +#define SETTING_NAME_DOT_NET_SHOW_BYTE_SIZE (PLUGIN_NAME L".DotNetShowByteSizes") + +#define MSG_UPDATE (WM_APP + 1) + +extern PPH_PLUGIN PluginInstance; + +typedef struct _DN_THREAD_ITEM +{ + PPH_THREAD_ITEM ThreadItem; + + BOOLEAN ClrDataValid; + PPH_STRING AppDomainText; +} DN_THREAD_ITEM, *PDN_THREAD_ITEM; + +// counters + +PVOID GetPerfIpcBlock_V2( + _In_ BOOLEAN Wow64, + _In_ PVOID BlockTableAddress + ); + +PVOID GetPerfIpcBlock_V4( + _In_ BOOLEAN Wow64, + _In_ PVOID BlockTableAddress + ); + +BOOLEAN OpenDotNetPublicControlBlock_V2( + _In_ HANDLE ProcessId, + _Out_ HANDLE* BlockTableHandle, + _Out_ PVOID* BlockTableAddress + ); + +BOOLEAN OpenDotNetPublicControlBlock_V4( + _In_ BOOLEAN IsImmersive, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId, + _Out_ HANDLE* BlockTableHandle, + _Out_ PVOID* BlockTableAddress + ); + +PPH_LIST QueryDotNetAppDomainsForPid_V2( + _In_ BOOLEAN Wow64, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId + ); + +PPH_LIST QueryDotNetAppDomainsForPid_V4( + _In_ BOOLEAN Wow64, + _In_ HANDLE ProcessHandle, + _In_ HANDLE ProcessId + ); + +// asmpage + +VOID AddAsmPageToPropContext( + _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext + ); + +// perfpage + +VOID AddPerfPageToPropContext( + _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext + ); + +// stackext + +VOID ProcessThreadStackControl( + _In_ PPH_PLUGIN_THREAD_STACK_CONTROL Control + ); + +VOID PredictAddressesFromClrData( + _In_ struct _CLR_PROCESS_SUPPORT *Support, + _In_ HANDLE ThreadId, + _In_ PVOID PcAddress, + _In_ PVOID FrameAddress, + _In_ PVOID StackAddress, + _Out_ PVOID *PredictedEip, + _Out_ PVOID *PredictedEbp, + _Out_ PVOID *PredictedEsp + ); + +// svcext + +VOID DispatchPhSvcRequest( + _In_ PVOID Parameter + ); + +// treeext + +VOID InitializeTreeNewObjectExtensions( + VOID + ); + +VOID DispatchTreeNewMessage( + __in PVOID Parameter + ); + +#define DNTHTNC_APPDOMAIN 1 + +VOID ThreadTreeNewInitializing( + __in PVOID Parameter + ); + +VOID ThreadTreeNewUninitializing( + __in PVOID Parameter + ); + +#endif diff --git a/plugins/DotNetTools/main.c b/plugins/DotNetTools/main.c new file mode 100644 index 0000000..ed4261a --- /dev/null +++ b/plugins/DotNetTools/main.c @@ -0,0 +1,336 @@ +/* + * Process Hacker .NET Tools - + * main program + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" + +PPH_PLUGIN PluginInstance; +static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginUnloadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginTreeNewMessageCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginPhSvcRequestCallbackRegistration; +static PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessPropertiesInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ThreadMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ModuleMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ThreadTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ThreadTreeNewUninitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ThreadStackControlCallbackRegistration; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + default: + NOTHING; + break; + } +} + +VOID NTAPI TreeNewMessageCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DispatchTreeNewMessage(Parameter); +} + +VOID NTAPI PhSvcRequestCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DispatchPhSvcRequest(Parameter); +} + +VOID NTAPI ThreadTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ThreadTreeNewInitializing(Parameter); +} + +VOID NTAPI ThreadTreeNewUninitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ThreadTreeNewUninitializing(Parameter); +} + +VOID NTAPI ProcessPropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_PROCESS_PROPCONTEXT propContext = Parameter; + BOOLEAN isDotNet; + + if (NT_SUCCESS(PhGetProcessIsDotNet(propContext->ProcessItem->ProcessId, &isDotNet))) + { + if (isDotNet) + { + if (WindowsVersion >= WINDOWS_VISTA) + AddAsmPageToPropContext(propContext); + AddPerfPageToPropContext(propContext); + } + + if (propContext->ProcessItem->IsDotNet != isDotNet) + propContext->ProcessItem->UpdateIsDotNet = TRUE; // force a refresh + } +} + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ThreadMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ModuleMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ProcessTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ThreadStackControlCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ProcessThreadStackControl(Parameter); +} + +VOID NTAPI ThreadItemCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PDN_THREAD_ITEM dnThread = Extension; + + memset(dnThread, 0, sizeof(DN_THREAD_ITEM)); + dnThread->ThreadItem = Object; +} + +VOID NTAPI ThreadItemDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PDN_THREAD_ITEM dnThread = Extension; + + PhClearReference(&dnThread->AppDomainText); +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { StringSettingType, SETTING_NAME_ASM_TREE_LIST_COLUMNS, L"" }, + { IntegerSettingType, SETTING_NAME_DOT_NET_CATEGORY_INDEX, L"5" }, + { StringSettingType, SETTING_NAME_DOT_NET_COUNTERS_COLUMNS, L"" }, + { IntegerSettingType, SETTING_NAME_DOT_NET_SHOW_BYTE_SIZE, L"1" } + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L".NET Tools"; + info->Author = L"dmex, wj32"; + info->Description = L"Adds .NET performance counters, assembly information, thread stack support, and more."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1111"; + info->HasOptions = FALSE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackUnload), + UnloadCallback, + NULL, + &PluginUnloadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + //PhRegisterCallback( + // PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + // MenuItemCallback, + // NULL, + // &PluginMenuItemCallbackRegistration + // ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackTreeNewMessage), + TreeNewMessageCallback, + NULL, + &PluginTreeNewMessageCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackPhSvcRequest), + PhSvcRequestCallback, + NULL, + &PluginPhSvcRequestCallbackRegistration + ); + + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + // MainWindowShowingCallback, + // NULL, + // &MainWindowShowingCallbackRegistration + // ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessPropertiesInitializing), + ProcessPropertiesInitializingCallback, + NULL, + &ProcessPropertiesInitializingCallbackRegistration + ); + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + // ProcessMenuInitializingCallback, + // NULL, + // &ProcessMenuInitializingCallbackRegistration + // ); + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackThreadMenuInitializing), + // ThreadMenuInitializingCallback, + // NULL, + // &ThreadMenuInitializingCallbackRegistration + // ); + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackModuleMenuInitializing), + // ModuleMenuInitializingCallback, + // NULL, + // &ModuleMenuInitializingCallbackRegistration + // ); + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), + // ProcessTreeNewInitializingCallback, + // NULL, + // &ProcessTreeNewInitializingCallbackRegistration + // ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackThreadTreeNewInitializing), + ThreadTreeNewInitializingCallback, + NULL, + &ThreadTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackThreadTreeNewUninitializing), + ThreadTreeNewUninitializingCallback, + NULL, + &ThreadTreeNewUninitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackThreadStackControl), + ThreadStackControlCallback, + NULL, + &ThreadStackControlCallbackRegistration + ); + + PhPluginSetObjectExtension( + PluginInstance, + EmThreadItemType, + sizeof(DN_THREAD_ITEM), + ThreadItemCreateCallback, + ThreadItemDeleteCallback + ); + InitializeTreeNewObjectExtensions(); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + break; + } + + return TRUE; +} diff --git a/plugins/DotNetTools/perfpage.c b/plugins/DotNetTools/perfpage.c new file mode 100644 index 0000000..d474eac --- /dev/null +++ b/plugins/DotNetTools/perfpage.c @@ -0,0 +1,944 @@ +/* + * Process Hacker .NET Tools - + * .NET Performance property page + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + + +#include "dn.h" +#include "clr\perfcounterdefs.h" + +typedef enum _DOTNET_CATEGORY +{ + // .NET CLR Exceptions (Runtime statistics on CLR exception handling) + DOTNET_CATEGORY_EXCEPTIONS, + + // .NET CLR Interop (Stats for CLR interop) + DOTNET_CATEGORY_INTEROP, + + // .NET CLR Jit (Stats for CLR Jit) + DOTNET_CATEGORY_JIT, + + // .NET CLR Loading (Statistics for CLR Class Loader) + DOTNET_CATEGORY_LOADING, + + // .NET CLR LocksAndThreads (Stats for CLR Locks and Threads) + DOTNET_CATEGORY_LOCKSANDTHREADS, + + // .NET CLR Memory (Counters for CLR Garbage Collected heap) + DOTNET_CATEGORY_MEMORY, + + // .NET CLR Remoting (Stats for CLR Remoting) + DOTNET_CATEGORY_REMOTING, + + // .NET CLR Security (Stats for CLR Security) + DOTNET_CATEGORY_SECURITY +} DOTNET_CATEGORY; + +typedef struct _PERFPAGE_CONTEXT +{ + HWND WindowHandle; + PPH_PROCESS_ITEM ProcessItem; + BOOLEAN Enabled; + + HWND AppDomainsLv; + HWND CountersLv; + HWND CategoriesCb; + + BOOLEAN ControlBlockValid; + BOOLEAN ClrV4; + BOOLEAN IsWow64; + BOOLEAN ShowByteSize; + DOTNET_CATEGORY CategoryIndex; + HANDLE ProcessHandle; + HANDLE BlockTableHandle; + PVOID BlockTableAddress; + + PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +} PERFPAGE_CONTEXT, *PPERFPAGE_CONTEXT; + +static PWSTR DotNetCategoryStrings[] = +{ + L".NET CLR Exceptions", + L".NET CLR Interop", + L".NET CLR Jit", + L".NET CLR Loading", + L".NET CLR LocksAndThreads", + L".NET CLR Memory", + L".NET CLR Remoting", + L".NET CLR Security" +}; + +PPH_STRING FormatByteValue( + _In_ PPERFPAGE_CONTEXT Context, + _In_ ULONG64 Value + ) +{ + if (Context->ShowByteSize) + return PhaFormatUInt64(Value, TRUE); + + return PhaFormatSize(Value, -1); +} + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPERFPAGE_CONTEXT context = Context; + + if (context->WindowHandle) + { + PostMessage(context->WindowHandle, MSG_UPDATE, 0, 0); + } +} + +VOID UpdateCategoryValues( + _In_ HWND hwndDlg, + _In_ PPERFPAGE_CONTEXT Context + ) +{ + ListView_DeleteAllItems(Context->CountersLv); + + switch (Context->CategoryIndex) + { + case DOTNET_CATEGORY_EXCEPTIONS: + { + // This counter displays the total number of exceptions thrown since the start of the application. These include both .NET exceptions and unmanaged exceptions that get converted into .NET exceptions e.g. null pointer reference exception in unmanaged code would get re-thrown in managed code as a .NET System.NullReferenceException; this counter includes both handled and unhandled exceptions.Exceptions that are re-thrown would get counted again. Exceptions should only occur in rare situations and not in the normal control flow of the program. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Exceps Thrown", NULL); + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Filters Executed", NULL); + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Finallys Executed", NULL); + + // Reserved for future use. + //PhAddListViewItem(Context->CountersLv, MAXINT, L" Delta from throw to catch site on stack", NULL); + + // # of Exceps Thrown / sec + // This counter displays the number of exceptions thrown per second.These include both.NET exceptions and unmanaged exceptions that get converted into.NET exceptions e.g.null pointer reference exception in unmanaged code would get re - thrown in managed code as a.NET System.NullReferenceException; this counter includes both handled and unhandled exceptions.Exceptions should only occur in rare situations and not in the normal control flow of the program; this counter was designed as an indicator of potential performance problems due to large(> 100s) rate of exceptions thrown.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // # of Filters / sec + // This counter displays the number of.NET exception filters executed per second.An exception filter evaluates whether an exception should be handled or not.This counter tracks the rate of exception filters evaluated; irrespective of whether the exception was handled or not.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // # of Finallys / sec + // This counter displays the number of finally blocks executed per second.A finally block is guaranteed to be executed regardless of how the try block was exited.Only the finally blocks that are executed for an exception are counted; finally blocks on normal code paths are not counted by this counter.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Throw To Catch Depth / sec + // This counter displays the number of stack frames traversed from the frame that threw the.NET exception to the frame that handled the exception per second.This counter resets to 0 when an exception handler is entered; so nested exceptions would show the handler to handler stack depth.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval.* / + } + break; + case DOTNET_CATEGORY_INTEROP: + { + // This counter displays the current number of Com-Callable-Wrappers (CCWs). A CCW is a proxy for the .NET managed object being referenced from unmanaged COM client(s). This counter was designed to indicate the number of managed objects being referenced by unmanaged COM code. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of CCWs", NULL); + + // This counter displays the current number of stubs created by the CLR. Stubs are responsible for marshalling arguments and return values from managed to unmanaged code and vice versa; during a COM Interop call or PInvoke call. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Stubs", NULL); + + // This counter displays the total number of times arguments and return values have been marshaled from managed to unmanaged code and vice versa since the start of the application. This counter is not incremented if the stubs are inlined. (Stubs are responsible for marshalling arguments and return values). Stubs usually get inlined if the marshalling overhead is small. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Marshalling", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of TLB imports / sec", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of TLB exports / sec", NULL); + } + break; + case DOTNET_CATEGORY_JIT: + { + // This counter displays the total number of methods compiled Just-In-Time (JIT) by the CLR JIT compiler since the start of the application. This counter does not include the pre-jitted methods. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Methods Jitted", NULL); + + // This counter displays the total IL bytes jitted since the start of the application. This counter is exactly equivalent to the "Total # of IL Bytes Jitted" counter. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of IL Bytes Jitted", NULL); + + // This counter displays the total IL bytes jitted since the start of the application. This counter is exactly equivalent to the "# of IL Bytes Jitted" counter. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total # of IL Bytes Jitted", NULL); + + // This counter displays the peak number of methods the JIT compiler has failed to JIT since the start of the application. This failure can occur if the IL cannot be verified or if there was an internal error in the JIT compiler. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Jit Failures", NULL); + + // This counter displays the percentage of elapsed time spent in JIT compilation since the last JIT compilation phase. This counter is updated at the end of every JIT compilation phase. A JIT compilation phase is the phase when a method and its dependencies are being compiled. + PhAddListViewItem(Context->CountersLv, MAXINT, L"% Time in Jit", NULL); + + // IL Bytes Jitted / sec + // This counter displays the rate at which IL bytes are jitted per second. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + } + break; + case DOTNET_CATEGORY_LOADING: + { + // This counter displays the current number of classes loaded in all Assemblies. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Current Classes Loaded", NULL); + + // This counter displays the cumulative number of classes loaded in all Assemblies since the start of this application. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Classes Loaded", NULL); + + // This counter displays the current number of AppDomains loaded in this application. AppDomains (application domains) provide a secure and versatile unit of processing that the CLR can use to provide isolation between applications running in the same process. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Current Appdomains", NULL); + + // This counter displays the peak number of AppDomains loaded since the start of this application. AppDomains (application domains) provide a secure and versatile unit of processing that the CLR can use to provide isolation between applications running in the same process. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Appdomains", NULL); + + // This counter displays the current number of Assemblies loaded across all AppDomains in this application. If the Assembly is loaded as domain - neutral from multiple AppDomains then this counter is incremented once only. Assemblies can be loaded as domain - neutral when their code can be shared by all AppDomains or they can be loaded as domain - specific when their code is private to the AppDomain. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Current Assemblies", NULL); + + // This counter displays the total number of Assemblies loaded since the start of this application. If the Assembly is loaded as domain - neutral from multiple AppDomains then this counter is incremented once only. Assemblies can be loaded as domain - neutral when their code can be shared by all AppDomains or they can be loaded as domain - specific when their code is private to the AppDomain. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Assemblies", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Assembly Search Length", NULL); + + // This counter displays the peak number of classes that have failed to load since the start of the application.These load failures could be due to many reasons like inadequate security or illegal format.Full details can be found in the profiling services help. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total # of Load Failures", NULL); + + // This counter displays the current size(in bytes) of the memory committed by the class loader across all AppDomains. (Committed memory is the physical memory for which space has been reserved on the disk paging file.) + PhAddListViewItem(Context->CountersLv, MAXINT, L"Bytes in Loader Heap", NULL); + + // This counter displays the total number of AppDomains unloaded since the start of the application. If an AppDomain is loaded and unloaded multiple times this counter would count each of those unloads as separate. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Appdomains Unloaded", NULL); + + // Reserved for future use. + //PhAddListViewItem(Context->CountersLv, MAXINT, L"% Time Loading", NULL); + + // Rate of Load Failures + // This counter displays the number of classes that failed to load per second. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. These load failures could be due to many reasons like inadequate security or illegal format. Full details can be found in the profiling services help. + + // Rate of appdomains unloaded + // This counter displays the number of AppDomains unloaded per second.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Rate of Classes Loaded + // This counter displays the number of classes loaded per second in all Assemblies.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Rate of appdomains + // This counter displays the number of AppDomains loaded per second.AppDomains(application domains) provide a secure and versatile unit of processing that the CLR can use to provide isolation between applications running in the same process.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Rate of Assemblies + // This counter displays the number of Assemblies loaded across all AppDomains per second.If the Assembly is loaded as domain - neutral from multiple AppDomains then this counter is incremented once only.Assemblies can be loaded as domain - neutral when their code can be shared by all AppDomains or they can be loaded as domain - specific when their code is private to the AppDomain.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + } + break; + case DOTNET_CATEGORY_LOCKSANDTHREADS: + { + // This counter displays the total number of times threads in the CLR have attempted to acquire a managed lock unsuccessfully. Managed locks can be acquired in many ways; by the "lock" statement in C# or by calling System.Monitor.Enter or by using MethodImplOptions.Synchronized custom attribute. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total # of Contentions", NULL); + + // This counter displays the total number of threads currently waiting to acquire some managed lock in the application. This counter is not an average over time; it displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Current Queue Length", NULL); + + // This counter displays the total number of threads that waited to acquire some managed lock since the start of the application. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Queue Length Peak", NULL); + + // This counter displays the number of current.NET thread objects in the application.A.NET thread object is created either by new System.Threading.Thread or when an unmanaged thread enters the managed environment. This counters maintains the count of both running and stopped threads. This counter is not an average over time; it just displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Current Logical Threads", NULL); + + // This counter displays the number of native OS threads created and owned by the CLR to act as underlying threads for .NET thread objects. This counters value does not include the threads used by the CLR in its internal operations; it is a subset of the threads in the OS process. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Current Physical Threads", NULL); + + // This counter displays the number of threads that are currently recognized by the CLR; they have a corresponding .NET thread object associated with them. These threads are not created by the CLR; they are created outside the CLR but have since run inside the CLR at least once. Only unique threads are tracked; threads with same thread ID re-entering the CLR or recreated after thread exit are not counted twice. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Current Recognized Threads", NULL); + + // This counter displays the total number of threads that have been recognized by the CLR since the start of this application; these threads have a corresponding .NET thread object associated with them. These threads are not created by the CLR; they are created outside the CLR but have since run inside the CLR at least once. Only unique threads are tracked; threads with same thread ID re-entering the CLR or recreated after thread exit are not counted twice. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Total Recognized Threads", NULL); + + // Contention Rate / sec + // Rate at which threads in the runtime attempt to acquire a managed lock unsuccessfully.Managed locks can be acquired in many ways; by the "lock" statement in C# or by calling System.Monitor.Enter or by using MethodImplOptions.Synchronized custom attribute. + + // Queue Length / sec + // This counter displays the number of threads per second waiting to acquire some lock in the application. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // rate of recognized threads / sec + // This counter displays the number of threads per second that have been recognized by the CLR; these threads have a corresponding .NET thread object associated with them. These threads are not created by the CLR; they are created outside the CLR but have since run inside the CLR at least once. Only unique threads are tracked; threads with same thread ID re-entering the CLR or recreated after thread exit are not counted twice. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + } + break; + case DOTNET_CATEGORY_MEMORY: + { + // This counter displays the number of times the generation 0 objects (youngest; most recently allocated) are garbage collected (Gen 0 GC) since the start of the application. Gen 0 GC occurs when the available memory in generation 0 is not sufficient to satisfy an allocation request. This counter is incremented at the end of a Gen 0 GC. Higher generation GCs include all lower generation GCs.This counter is explicitly incremented when a higher generation (Gen 1 or Gen 2) GC occurs. _Global_ counter value is not accurate and should be ignored. This counter displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Gen 0 Collections", NULL); + + // This counter displays the number of times the generation 1 objects are garbage collected since the start of the application. The counter is incremented at the end of a Gen 1 GC. Higher generation GCs include all lower generation GCs. This counter is explicitly incremented when a higher generation (Gen 2) GC occurs. _Global_ counter value is not accurate and should be ignored. This counter displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Gen 1 Collections", NULL); + + // This counter displays the number of times the generation 2 objects(older) are garbage collected since the start of the application.The counter is incremented at the end of a Gen 2 GC(also called full GC)._Global_ counter value is not accurate and should be ignored.This counter displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Gen 2 Collections", NULL); + + // This counter displays the bytes of memory that survive garbage collection(GC) and are promoted from generation 0 to generation 1; objects that are promoted just because they are waiting to be finalized are not included in this counter.This counter displays the value observed at the end of the last GC; its not a cumulative counter. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Promoted Memory from Gen 0", NULL); + + // This counter displays the bytes of memory that survive garbage collection(GC) and are promoted from generation 1 to generation 2; objects that are promoted just because they are waiting to be finalized are not included in this counter.This counter displays the value observed at the end of the last GC; its not a cumulative counter.This counter is reset to 0 if the last GC was a Gen 0 GC only. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Promoted Memory from Gen 1", NULL); + + // This counter displays the bytes of memory that are promoted from generation 0 to generation 1 just because they are waiting to be finalized.This counter displays the value observed at the end of the last GC; its not a cumulative counter. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Promoted Finalization-Memory from Gen 0", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Process ID", NULL); + + // This counter displays the maximum bytes that can be allocated in generation 0 (Gen 0); its does not indicate the current number of bytes allocated in Gen 0. A Gen 0 GC is triggered when the allocations since the last GC exceed this size.The Gen 0 size is tuned by the Garbage Collector and can change during the execution of the application.At the end of a Gen 0 collection the size of the Gen 0 heap is infact 0 bytes; this counter displays the size(in bytes) of allocations that would trigger the next Gen 0 GC.This counter is updated at the end of a GC; its not updated on every allocation. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Gen 0 Heap Size", NULL); + + // This counter displays the current number of bytes in generation 1 (Gen 1); this counter does not display the maximum size of Gen 1. Objects are not directly allocated in this generation; they are promoted from previous Gen 0 GCs.This counter is updated at the end of a GC; its not updated on every allocation. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Gen 1 Heap Size", NULL); + + // This counter displays the current number of bytes in generation 2 (Gen 2).Objects are not directly allocated in this generation; they are promoted from Gen 1 during previous Gen 1 GCs.This counter is updated at the end of a GC; its not updated on every allocation. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Gen 2 Heap Size", NULL); + + // This counter displays the current size of the Large Object Heap in bytes.Objects greater than 20 KBytes are treated as large objects by the Garbage Collector and are directly allocated in a special heap; they are not promoted through the generations.This counter is updated at the end of a GC; its not updated on every allocation. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Large Object Heap Size", NULL); + + // This counter displays the number of garbage collected objects that survive a collection because they are waiting to be finalized.If these objects hold references to other objects then those objects also survive but are not counted by this counter; the "Promoted Finalization-Memory from Gen 0" and "Promoted Finalization-Memory from Gen 1" counters represent all the memory that survived due to finalization.This counter is not a cumulative counter; its updated at the end of every GC with count of the survivors during that particular GC only.This counter was designed to indicate the extra overhead that the application might incur because of finalization. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Finalization Survivors", NULL); + + // This counter displays the current number of GC Handles in use.GCHandles are handles to resources external to the CLR and the managed environment.Handles occupy small amounts of memory in the GCHeap but potentially expensive unmanaged resources. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# GC Handles", NULL); + + // This counter displays the peak number of times a garbage collection was performed because of an explicit call to GC.Collect. Its a good practice to let the GC tune the frequency of its collections. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Induced GC", NULL); + + // % Time in GC is the percentage of elapsed time that was spent in performing a garbage collection(GC) since the last GC cycle. This counter is usually an indicator of the work done by the Garbage Collector on behalf of the application to collect and compact memory.This counter is updated only at the end of every GC and the counter value reflects the last observed value; its not an average. + PhAddListViewItem(Context->CountersLv, MAXINT, L"% Time in GC", NULL); + + // This counter is the sum of four other counters; Gen 0 Heap Size; Gen 1 Heap Size; Gen 2 Heap Size and the Large Object Heap Size. This counter indicates the current memory allocated in bytes on the GC Heaps. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Bytes in all Heaps", NULL); + + // This counter displays the amount of virtual memory(in bytes) currently committed by the Garbage Collector. (Committed memory is the physical memory for which space has been reserved on the disk paging file). + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Total Committed Bytes", NULL); + + // This counter displays the amount of virtual memory(in bytes) currently reserved by the Garbage Collector. (Reserved memory is the virtual memory space reserved for the application but no disk or main memory pages have been used.) + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Total Reserved Bytes", NULL); + + // This counter displays the number of pinned objects encountered in the last GC. This counter tracks the pinned objects only in the heaps that were garbage collected e.g. A Gen 0 GC would cause enumeration of pinned objects in the generation 0 heap only. A pinned object is one that the Garbage Collector cannot move in memory. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Pinned Objects", NULL); + + // This counter displays the current number of sync blocks in use. Sync blocks are per-object data structures allocated for storing synchronization information. Sync blocks hold weak references to managed objects and need to be scanned by the Garbage Collector. Sync blocks are not limited to storing synchronization information and can also store COM interop metadata. This counter was designed to indicate performance problems with heavy use of synchronization primitives. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of Sink Blocks in use", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Bytes Allocated (since start)", NULL); + + // Reserved for future use. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Bytes Allocated for Large Objects (since start)", NULL); + + // Gen 0 Promoted Bytes / Sec + // This counter displays the bytes per second that are promoted from generation 0 (youngest)to generation 1; objects that are promoted just because they are waiting to be finalized are not included in this counter.Memory is promoted when it survives a garbage collection.This counter was designed as an indicator of relatively long - lived objects being created per sec.This counter displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Gen 1 Promoted Bytes / Sec + // This counter displays the bytes per second that are promoted from generation 1 to generation 2 (oldest); objects that are promoted just because they are waiting to be finalized are not included in this counter.Memory is promoted when it survives a garbage collection.Nothing is promoted from generation 2 since it is the oldest.This counter was designed as an indicator of very long - lived objects being created per sec.This counter displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Promoted Finalization - Memory from Gen 1 + // This counter displays the bytes of memory that are promoted from generation 1 to generation 2 just because they are waiting to be finalized.This counter displays the value observed at the end of the last GC; its not a cumulative counter.This counter is reset to 0 if the last GC was a Gen 0 GC only. + + // Allocated Bytes / sec + // This counter displays the rate of bytes per second allocated on the GC Heap.This counter is updated at the end of every GC; not at each allocation.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + } + break; + case DOTNET_CATEGORY_REMOTING: + { + // This counter displays the total number of remote procedure calls invoked since the start of this application. A remote procedure call is a call on any object outside the callers AppDomain. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Remote Calls", NULL); + + // This counter displays the total number of remoting channels registered across all AppDomains since the start of the application. Channels are used to transport messages to and from remote objects. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Channels", NULL); + + // This counter displays the total number of remoting proxy objects created in this process since the start of the process. Proxy object acts as a representative of the remote objects and ensures that all calls made on the proxy are forwarded to the correct remote object instance. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Context Proxies", NULL); + + // This counter displays the current number of context-bound classes loaded. Classes that can be bound to a context are called context-bound classes; context-bound classes are marked with Context Attributes which provide usage rules for synchronization; thread affinity; transactions etc. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Context-Bound Classes Loaded", NULL); + + // This counter displays the current number of remoting contexts in the application. A context is a boundary containing a collection of objects with the same usage rules like synchronization; thread affinity; transactions etc. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Contexts", NULL); + + PhAddListViewItem(Context->CountersLv, MAXINT, L"# of context bound objects allocated", NULL); + + // Remote Calls / sec + // This counter displays the number of remote procedure calls invoked per second. A remote procedure call is a call on any object outside the callers AppDomain. This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + + // Context - Bound Objects Alloc / sec + // This counter displays the number of context - bound objects allocated per second. Instances of classes that can be bound to a context are called context - bound objects; context - bound classes are marked with Context Attributes which provide usage rules for synchronization; thread affinity; transactions etc.This counter is not an average over time; it displays the difference between the values observed in the last two samples divided by the duration of the sample interval. + } + break; + case DOTNET_CATEGORY_SECURITY: + { + // This counter displays the total number of runtime Code Access Security(CAS) checks performed since the start of the application. Runtime CAS checks are performed when a caller makes a call to a callee demanding a particular permission; the runtime check is made on every call by the caller; the check is done by examining the current thread stack of the caller. This counter used together with "Stack Walk Depth" is indicative of performance penalty for security checks. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Total Runtime Checks", NULL); + + // This counter displays the total number of linktime Code Access Security(CAS) checks since the start of the application. Linktime CAS checks are performed when a caller makes a call to a callee demanding a particular permission at JIT compile time; linktime check is performed once per caller. This count is not indicative of serious performance issues; its indicative of the security system activity. + PhAddListViewItem(Context->CountersLv, MAXINT, L"# Link Time Checks", NULL); + + // This counter displays the percentage of elapsed time spent in performing runtime Code Access Security(CAS) checks since the last such check. CAS allows code to be trusted to varying degrees and enforces these varying levels of trust depending on code identity. This counter is updated at the end of a runtime security check; it represents the last observed value; its not an average. + PhAddListViewItem(Context->CountersLv, MAXINT, L"% Time in RT checks", NULL); + + // This counter displays the depth of the stack during that last runtime Code Access Security check.Runtime Code Access Security check is performed by crawling the stack. This counter is not an average; it just displays the last observed value. + PhAddListViewItem(Context->CountersLv, MAXINT, L"Stack Walk Depth", NULL); + + // % Time Sig.Authenticating + // Reserved for future use. + } + break; + } +} + +VOID AddProcessAppDomains( + _In_ HWND hwndDlg, + _In_ PPERFPAGE_CONTEXT Context + ) +{ + SendMessage(Context->AppDomainsLv, WM_SETREDRAW, FALSE, 0); + ListView_DeleteAllItems(Context->AppDomainsLv); + + if (Context->ClrV4) + { + PPH_LIST processAppDomains = QueryDotNetAppDomainsForPid_V4( + Context->IsWow64, + Context->ProcessHandle, + Context->ProcessItem->ProcessId + ); + + for (ULONG i = 0; i < processAppDomains->Count; i++) + { + PhAddListViewItem(Context->AppDomainsLv, MAXINT, processAppDomains->Items[i], NULL); + PhFree(processAppDomains->Items[i]); + } + + PhDereferenceObject(processAppDomains); + } + else + { + PPH_LIST processAppDomains = QueryDotNetAppDomainsForPid_V2( + Context->IsWow64, + Context->ProcessHandle, + Context->ProcessItem->ProcessId + ); + + for (ULONG i = 0; i < processAppDomains->Count; i++) + { + PhAddListViewItem(Context->AppDomainsLv, MAXINT, processAppDomains->Items[i], NULL); + PhFree(processAppDomains->Items[i]); + } + + PhDereferenceObject(processAppDomains); + } + + SendMessage(Context->AppDomainsLv, WM_SETREDRAW, TRUE, 0); +} + +VOID UpdateCounterData( + _In_ HWND hwndDlg, + _In_ PPERFPAGE_CONTEXT Context + ) +{ + PVOID perfStatBlock = NULL; + Perf_GC dotNetPerfGC; + Perf_Contexts dotNetPerfContext; + Perf_Interop dotNetPerfInterop; + Perf_Loading dotNetPerfLoading; + Perf_Excep dotNetPerfExcep; + Perf_LocksAndThreads dotNetPerfLocksAndThreads; + Perf_Jit dotNetPerfJit; + Perf_Security dotNetPerfSecurity; + + if (Context->ClrV4) + { + perfStatBlock = GetPerfIpcBlock_V4( + Context->IsWow64, + Context->BlockTableAddress + ); + } + else + { + perfStatBlock = GetPerfIpcBlock_V2( + Context->IsWow64, + Context->BlockTableAddress + ); + } + + if (!perfStatBlock) + return; + + if (Context->IsWow64) + { + PerfCounterIPCControlBlock_Wow64* perfBlock = perfStatBlock; + Perf_GC_Wow64 dotNetPerfGC_Wow64 = perfBlock->GC; + Perf_Loading_Wow64 dotNetPerfLoading_Wow64 = perfBlock->Loading; + Perf_Security_Wow64 dotNetPerfSecurity_Wow64 = perfBlock->Security; + + // Thunk the Wow64 structures into their 64bit versions (or 32bit version on x86). + + dotNetPerfGC.cGenCollections[0] = dotNetPerfGC_Wow64.cGenCollections[0]; + dotNetPerfGC.cGenCollections[1] = dotNetPerfGC_Wow64.cGenCollections[1]; + dotNetPerfGC.cGenCollections[2] = dotNetPerfGC_Wow64.cGenCollections[2]; + dotNetPerfGC.cbPromotedMem[0] = dotNetPerfGC_Wow64.cbPromotedMem[0]; + dotNetPerfGC.cbPromotedMem[1] = dotNetPerfGC_Wow64.cbPromotedMem[1]; + dotNetPerfGC.cbPromotedFinalizationMem = dotNetPerfGC_Wow64.cbPromotedFinalizationMem; + dotNetPerfGC.cProcessID = dotNetPerfGC_Wow64.cProcessID; + dotNetPerfGC.cGenHeapSize[0] = dotNetPerfGC_Wow64.cGenHeapSize[0]; + dotNetPerfGC.cGenHeapSize[1] = dotNetPerfGC_Wow64.cGenHeapSize[1]; + dotNetPerfGC.cGenHeapSize[2] = dotNetPerfGC_Wow64.cGenHeapSize[2]; + dotNetPerfGC.cTotalCommittedBytes = dotNetPerfGC_Wow64.cTotalCommittedBytes; + dotNetPerfGC.cTotalReservedBytes = dotNetPerfGC_Wow64.cTotalReservedBytes; + dotNetPerfGC.cLrgObjSize = dotNetPerfGC_Wow64.cLrgObjSize; + dotNetPerfGC.cSurviveFinalize = dotNetPerfGC_Wow64.cSurviveFinalize; + dotNetPerfGC.cHandles = dotNetPerfGC_Wow64.cHandles; + dotNetPerfGC.cbAlloc = dotNetPerfGC_Wow64.cbAlloc; + dotNetPerfGC.cbLargeAlloc = dotNetPerfGC_Wow64.cbLargeAlloc; + dotNetPerfGC.cInducedGCs = dotNetPerfGC_Wow64.cInducedGCs; + dotNetPerfGC.timeInGC = dotNetPerfGC_Wow64.timeInGC; + dotNetPerfGC.timeInGCBase = dotNetPerfGC_Wow64.timeInGCBase; + dotNetPerfGC.cPinnedObj = dotNetPerfGC_Wow64.cPinnedObj; + dotNetPerfGC.cSinkBlocks = dotNetPerfGC_Wow64.cSinkBlocks; + + dotNetPerfContext = perfBlock->Context; + dotNetPerfInterop = perfBlock->Interop; + + dotNetPerfLoading.cClassesLoaded.Current = dotNetPerfLoading_Wow64.cClassesLoaded.Current; + dotNetPerfLoading.cClassesLoaded.Total = dotNetPerfLoading_Wow64.cClassesLoaded.Total; + dotNetPerfLoading.cAppDomains.Current = dotNetPerfLoading_Wow64.cAppDomains.Current; + dotNetPerfLoading.cAppDomains.Total = dotNetPerfLoading_Wow64.cAppDomains.Total; + dotNetPerfLoading.cAssemblies.Current = dotNetPerfLoading_Wow64.cAssemblies.Current; + dotNetPerfLoading.cAssemblies.Total = dotNetPerfLoading_Wow64.cAssemblies.Total; + dotNetPerfLoading.timeLoading = dotNetPerfLoading_Wow64.timeLoading; + dotNetPerfLoading.cAsmSearchLen = dotNetPerfLoading_Wow64.cAsmSearchLen; + dotNetPerfLoading.cLoadFailures.Total = dotNetPerfLoading_Wow64.cLoadFailures.Total; + dotNetPerfLoading.cbLoaderHeapSize = dotNetPerfLoading_Wow64.cbLoaderHeapSize; + dotNetPerfLoading.cAppDomainsUnloaded = dotNetPerfLoading_Wow64.cAppDomainsUnloaded; + + dotNetPerfExcep = perfBlock->Excep; + dotNetPerfLocksAndThreads = perfBlock->LocksAndThreads; + dotNetPerfJit = perfBlock->Jit; + + dotNetPerfSecurity.cTotalRTChecks = dotNetPerfSecurity_Wow64.cTotalRTChecks; + dotNetPerfSecurity.timeAuthorize = dotNetPerfSecurity_Wow64.timeAuthorize; + dotNetPerfSecurity.cLinkChecks = dotNetPerfSecurity_Wow64.cLinkChecks; + dotNetPerfSecurity.timeRTchecks = dotNetPerfSecurity_Wow64.timeRTchecks; + dotNetPerfSecurity.timeRTchecksBase = dotNetPerfSecurity_Wow64.timeRTchecksBase; + dotNetPerfSecurity.stackWalkDepth = dotNetPerfSecurity_Wow64.stackWalkDepth; + } + else + { + PerfCounterIPCControlBlock* perfBlock = perfStatBlock; + + dotNetPerfGC = perfBlock->GC; + dotNetPerfContext = perfBlock->Context; + dotNetPerfInterop = perfBlock->Interop; + dotNetPerfLoading = perfBlock->Loading; + dotNetPerfExcep = perfBlock->Excep; + dotNetPerfLocksAndThreads = perfBlock->LocksAndThreads; + dotNetPerfJit = perfBlock->Jit; + dotNetPerfSecurity = perfBlock->Security; + } + + switch (Context->CategoryIndex) + { + case DOTNET_CATEGORY_EXCEPTIONS: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfExcep.cThrown.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfExcep.cFiltersExecuted, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfExcep.cFinallysExecuted, TRUE)->Buffer); + //PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfExcep.cThrowToCatchStackDepth, TRUE)->Buffer); + } + break; + case DOTNET_CATEGORY_INTEROP: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfInterop.cCCW, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfInterop.cStubs, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfInterop.cMarshalling, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfInterop.cTLBImports, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 4, 1, PhaFormatUInt64(dotNetPerfInterop.cTLBExports, TRUE)->Buffer); + } + break; + case DOTNET_CATEGORY_JIT: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfJit.cMethodsJitted, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, FormatByteValue(Context, dotNetPerfJit.cbILJitted.Current)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, FormatByteValue(Context, dotNetPerfJit.cbILJitted.Total)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfJit.cJitFailures, TRUE)->Buffer); + + if (dotNetPerfJit.timeInJitBase != 0) + { + PH_FORMAT format; + WCHAR formatBuffer[10]; + + // TODO: TimeInJit is always above 100% for some processes ?? + // SeeAlso: https://github.com/dotnet/coreclr/blob/master/src/gc/gcee.cpp#L324 + PhInitFormatF(&format, (dotNetPerfJit.timeInJit << 8) * 100 / (FLOAT)(dotNetPerfJit.timeInJitBase << 8), 2); + + if (PhFormatToBuffer(&format, 1, formatBuffer, sizeof(formatBuffer), NULL)) + PhSetListViewSubItem(Context->CountersLv, 4, 1, formatBuffer); + } + else + { + PhSetListViewSubItem(Context->CountersLv, 4, 1, L"0.00"); + } + } + break; + case DOTNET_CATEGORY_LOADING: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfLoading.cClassesLoaded.Current, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfLoading.cClassesLoaded.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfLoading.cAppDomains.Current, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfLoading.cAppDomains.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 4, 1, PhaFormatUInt64(dotNetPerfLoading.cAssemblies.Current, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 5, 1, PhaFormatUInt64(dotNetPerfLoading.cAssemblies.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 6, 1, PhaFormatUInt64(dotNetPerfLoading.cAsmSearchLen, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 7, 1, PhaFormatUInt64(dotNetPerfLoading.cLoadFailures.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 8, 1, FormatByteValue(Context, dotNetPerfLoading.cbLoaderHeapSize)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 9, 1, PhaFormatUInt64(dotNetPerfLoading.cAppDomainsUnloaded.Total, TRUE)->Buffer); + //PhSetListViewSubItem(Context->CountersLv, 10, 1, PhaFormatUInt64(dotNetPerfLoading.timeLoading, TRUE)->Buffer); + } + break; + case DOTNET_CATEGORY_LOCKSANDTHREADS: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cContention.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cQueueLength.Current, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cQueueLength.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cCurrentThreadsLogical, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 4, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cCurrentThreadsPhysical, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 5, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cRecognizedThreads.Current, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 6, 1, PhaFormatUInt64(dotNetPerfLocksAndThreads.cRecognizedThreads.Total, TRUE)->Buffer); + } + break; + case DOTNET_CATEGORY_MEMORY: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfGC.cGenCollections[0], TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfGC.cGenCollections[1], TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfGC.cGenCollections[2], TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, FormatByteValue(Context, dotNetPerfGC.cbPromotedMem[0])->Buffer); + PhSetListViewSubItem(Context->CountersLv, 4, 1, FormatByteValue(Context, dotNetPerfGC.cbPromotedMem[1])->Buffer); + PhSetListViewSubItem(Context->CountersLv, 5, 1, FormatByteValue(Context, dotNetPerfGC.cbPromotedFinalizationMem)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 6, 1, PhaFormatUInt64(dotNetPerfGC.cProcessID, FALSE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 7, 1, FormatByteValue(Context, dotNetPerfGC.cGenHeapSize[0])->Buffer); + PhSetListViewSubItem(Context->CountersLv, 8, 1, FormatByteValue(Context, dotNetPerfGC.cGenHeapSize[1])->Buffer); + PhSetListViewSubItem(Context->CountersLv, 9, 1, FormatByteValue(Context, dotNetPerfGC.cGenHeapSize[2])->Buffer); + PhSetListViewSubItem(Context->CountersLv, 10, 1, FormatByteValue(Context, dotNetPerfGC.cLrgObjSize)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 11, 1, PhaFormatUInt64(dotNetPerfGC.cSurviveFinalize, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 12, 1, PhaFormatUInt64(dotNetPerfGC.cHandles, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 13, 1, PhaFormatUInt64(dotNetPerfGC.cInducedGCs, TRUE)->Buffer); + + if (dotNetPerfGC.timeInGCBase != 0) + { + PH_FORMAT format; + WCHAR formatBuffer[10]; + + PhInitFormatF(&format, (FLOAT)dotNetPerfGC.timeInGC * 100 / (FLOAT)dotNetPerfGC.timeInGCBase, 2); + + if (PhFormatToBuffer(&format, 1, formatBuffer, sizeof(formatBuffer), NULL)) + PhSetListViewSubItem(Context->CountersLv, 14, 1, formatBuffer); + } + else + { + PhSetListViewSubItem(Context->CountersLv, 14, 1, L"0.00"); + } + + // The CLR source says this value should be "Gen 0 Heap Size; Gen 1 Heap Size; Gen 2 Heap Size and the Large Object Heap Size", + // but Perflib only counts Gen 1, Gen 2 and cLrgObjSize... For now, just do what perflib does. + PhSetListViewSubItem(Context->CountersLv, 15, 1, FormatByteValue(Context, dotNetPerfGC.cGenHeapSize[1] + dotNetPerfGC.cGenHeapSize[2] + dotNetPerfGC.cLrgObjSize)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 16, 1, FormatByteValue(Context, dotNetPerfGC.cTotalCommittedBytes)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 17, 1, FormatByteValue(Context, dotNetPerfGC.cTotalReservedBytes)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 18, 1, PhaFormatUInt64(dotNetPerfGC.cPinnedObj, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 19, 1, PhaFormatUInt64(dotNetPerfGC.cSinkBlocks, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 20, 1, FormatByteValue(Context, dotNetPerfGC.cbAlloc)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 21, 1, FormatByteValue(Context, dotNetPerfGC.cbLargeAlloc)->Buffer); + } + break; + case DOTNET_CATEGORY_REMOTING: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfContext.cRemoteCalls.Total, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfContext.cChannels, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 2, 1, PhaFormatUInt64(dotNetPerfContext.cProxies, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfContext.cClasses, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 4, 1, PhaFormatUInt64(dotNetPerfContext.cContexts, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 5, 1, PhaFormatUInt64(dotNetPerfContext.cObjAlloc, TRUE)->Buffer); + } + break; + case DOTNET_CATEGORY_SECURITY: + { + PhSetListViewSubItem(Context->CountersLv, 0, 1, PhaFormatUInt64(dotNetPerfSecurity.cTotalRTChecks, TRUE)->Buffer); + PhSetListViewSubItem(Context->CountersLv, 1, 1, PhaFormatUInt64(dotNetPerfSecurity.cLinkChecks, TRUE)->Buffer); + + if (dotNetPerfSecurity.timeRTchecksBase != 0) + { + PH_FORMAT format; + WCHAR formatBuffer[10]; + + PhInitFormatF(&format, (FLOAT)dotNetPerfSecurity.timeRTchecks * 100 / (FLOAT)dotNetPerfSecurity.timeRTchecksBase, 2); + + if (PhFormatToBuffer(&format, 1, formatBuffer, sizeof(formatBuffer), NULL)) + PhSetListViewSubItem(Context->CountersLv, 2, 1, formatBuffer); + } + else + { + PhSetListViewSubItem(Context->CountersLv, 2, 1, L"0.00"); + } + + PhSetListViewSubItem(Context->CountersLv, 3, 1, PhaFormatUInt64(dotNetPerfSecurity.stackWalkDepth, TRUE)->Buffer); + } + break; + } +} + +INT_PTR CALLBACK DotNetPerfPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPERFPAGE_CONTEXT context; + + if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) + { + context = propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + context = PhAllocate(sizeof(PERFPAGE_CONTEXT)); + memset(context, 0, sizeof(PERFPAGE_CONTEXT)); + + propPageContext->Context = context; + context->WindowHandle = hwndDlg; + context->ProcessItem = processItem; + context->Enabled = TRUE; + + context->AppDomainsLv = GetDlgItem(hwndDlg, IDC_APPDOMAINS); + context->CountersLv = GetDlgItem(hwndDlg, IDC_COUNTERS); + context->CategoriesCb = GetDlgItem(hwndDlg, IDC_CATEGORIES); + + PhSetListViewStyle(context->AppDomainsLv, FALSE, TRUE); + PhSetControlTheme(context->AppDomainsLv, L"explorer"); + PhAddListViewColumn(context->AppDomainsLv, 0, 0, 0, LVCFMT_LEFT, 300, L"Application domain"); + + PhSetListViewStyle(context->CountersLv, FALSE, TRUE); + PhSetControlTheme(context->CountersLv, L"explorer"); + PhAddListViewColumn(context->CountersLv, 0, 0, 0, LVCFMT_LEFT, 250, L"Counter"); + PhAddListViewColumn(context->CountersLv, 1, 1, 1, LVCFMT_RIGHT, 140, L"Value"); + PhLoadListViewColumnsFromSetting(SETTING_NAME_DOT_NET_COUNTERS_COLUMNS, context->CountersLv); + + if (PhGetIntegerSetting(SETTING_NAME_DOT_NET_SHOW_BYTE_SIZE)) + { + context->ShowByteSize = TRUE; + Button_SetCheck(GetDlgItem(hwndDlg, IDC_DOTNET_PERF_SHOWBYTES), TRUE); + } + + PhAddComboBoxStrings(context->CategoriesCb, DotNetCategoryStrings, ARRAYSIZE(DotNetCategoryStrings)); + context->CategoryIndex = PhGetIntegerSetting(SETTING_NAME_DOT_NET_CATEGORY_INDEX); + ComboBox_SetCurSel(context->CategoriesCb, context->CategoryIndex); + +#ifdef _WIN64 + context->IsWow64 = !!context->ProcessItem->IsWow64; +#else + // HACK: Work-around for Appdomain enumeration on 32bit. + context->IsWow64 = TRUE; +#endif + + if (NT_SUCCESS(PhOpenProcess( + &context->ProcessHandle, + PROCESS_VM_READ | ProcessQueryAccess | PROCESS_DUP_HANDLE | SYNCHRONIZE, + context->ProcessItem->ProcessId + ))) + { + ULONG flags = 0; + + if (NT_SUCCESS(PhGetProcessIsDotNetEx( + context->ProcessItem->ProcessId, + context->ProcessHandle, + context->ProcessItem->IsImmersive == 1 ? 0 : PH_CLR_USE_SECTION_CHECK, + NULL, + &flags + ))) + { + if (flags & PH_CLR_VERSION_4_ABOVE) + { + context->ClrV4 = TRUE; + } + } + + // Skip AppDomain enumeration of 'Modern' .NET applications as they don't expose the CLR 'Private IPC' block. + if (!context->ProcessItem->IsImmersive) + { + AddProcessAppDomains(hwndDlg, context); + } + } + + if (context->ClrV4) + { + if (OpenDotNetPublicControlBlock_V4( + !!context->ProcessItem->IsImmersive, + context->ProcessHandle, + context->ProcessItem->ProcessId, + &context->BlockTableHandle, + &context->BlockTableAddress + )) + { + context->ControlBlockValid = TRUE; + } + } + else + { + if (OpenDotNetPublicControlBlock_V2( + context->ProcessItem->ProcessId, + &context->BlockTableHandle, + &context->BlockTableAddress + )) + { + context->ControlBlockValid = TRUE; + } + } + + if (context->ControlBlockValid) + { + UpdateCategoryValues(hwndDlg, context); + UpdateCounterData(hwndDlg, context); + } + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessesUpdated), + ProcessesUpdatedCallback, + context, + &context->ProcessesUpdatedCallbackRegistration + ); + } + break; + case WM_DESTROY: + { + PhUnregisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessesUpdated), + &context->ProcessesUpdatedCallbackRegistration + ); + + if (context->BlockTableAddress) + { + NtUnmapViewOfSection(NtCurrentProcess(), context->BlockTableAddress); + } + + if (context->BlockTableHandle) + { + NtClose(context->BlockTableHandle); + } + + if (context->ProcessHandle) + { + NtClose(context->ProcessHandle); + } + + PhSaveListViewColumnsToSetting(SETTING_NAME_DOT_NET_COUNTERS_COLUMNS, context->CountersLv); + + PhFree(context); + PhPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + PPH_LAYOUT_ITEM dialogItem; + + if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext)) + { + PhAddPropPageLayoutItem(hwndDlg, context->AppDomainsLv, dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, context->CategoriesCb, dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT); + PhAddPropPageLayoutItem(hwndDlg, context->CountersLv, dialogItem, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_DOTNET_PERF_SHOWBYTES), dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhEndPropPageLayout(hwndDlg, propPageContext); + } + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_CATEGORIES: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE) + { + context->CategoryIndex = ComboBox_GetCurSel(context->CategoriesCb); + PhSetIntegerSetting(SETTING_NAME_DOT_NET_CATEGORY_INDEX, context->CategoryIndex); + + if (context->ControlBlockValid) + { + UpdateCategoryValues(hwndDlg, context); + UpdateCounterData(hwndDlg, context); + } + } + } + break; + case IDC_DOTNET_PERF_SHOWBYTES: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED) + { + context->ShowByteSize = !context->ShowByteSize; + PhSetIntegerSetting(SETTING_NAME_DOT_NET_SHOW_BYTE_SIZE, context->ShowByteSize); + + if (context->ControlBlockValid) + { + UpdateCategoryValues(hwndDlg, context); + UpdateCounterData(hwndDlg, context); + } + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + context->Enabled = TRUE; + break; + case PSN_KILLACTIVE: + context->Enabled = FALSE; + break; + } + + PhHandleListViewNotifyForCopy(lParam, context->AppDomainsLv); + PhHandleListViewNotifyForCopy(lParam, context->CountersLv); + } + break; + case MSG_UPDATE: + { + if (context->Enabled && context->ControlBlockValid) + { + UpdateCounterData(hwndDlg, context); + } + } + break; + } + + return FALSE; +} + +VOID AddPerfPageToPropContext( + _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext + ) +{ + PhAddProcessPropPage( + PropContext->PropContext, + PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCDOTNETPERF), DotNetPerfPageDlgProc, NULL) + ); +} \ No newline at end of file diff --git a/plugins/DotNetTools/resource.h b/plugins/DotNetTools/resource.h new file mode 100644 index 0000000..62ce099 --- /dev/null +++ b/plugins/DotNetTools/resource.h @@ -0,0 +1,26 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DotNetTools.rc +// +#define IDD_PROCDOTNETPERF 101 +#define ID_COPY 101 +#define IDD_PROCDOTNETASM 102 +#define IDR_ASSEMBLY_MENU 104 +#define IDC_APPDOMAINS 1001 +#define IDC_CATEGORIES 1002 +#define IDC_COUNTERS 1003 +#define IDC_LIST 1005 +#define IDC_DOTNET_PERF_SHOWBYTES 1006 +#define ID_CLR_OPENFILELOCATION 40001 +#define ID_CLR_COPY 40002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40003 +#define _APS_NEXT_CONTROL_VALUE 1007 +#define _APS_NEXT_SYMED_VALUE 102 +#endif +#endif diff --git a/plugins/DotNetTools/stackext.c b/plugins/DotNetTools/stackext.c new file mode 100644 index 0000000..13ef6a3 --- /dev/null +++ b/plugins/DotNetTools/stackext.c @@ -0,0 +1,321 @@ +/* + * Process Hacker .NET Tools - + * thread stack extensions + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "clrsup.h" +#include "svcext.h" +#include + +typedef struct _THREAD_STACK_CONTEXT +{ + PCLR_PROCESS_SUPPORT Support; + HANDLE ProcessId; + HANDLE ThreadId; + HANDLE ThreadHandle; + + PVOID PredictedEip; + PVOID PredictedEbp; + PVOID PredictedEsp; + +#ifdef _WIN64 + BOOLEAN IsWow64; + BOOLEAN ConnectedToPhSvc; +#endif +} THREAD_STACK_CONTEXT, *PTHREAD_STACK_CONTEXT; + +static PPH_HASHTABLE ContextHashtable; +static PH_QUEUED_LOCK ContextHashtableLock = PH_QUEUED_LOCK_INIT; +static PH_INITONCE ContextHashtableInitOnce = PH_INITONCE_INIT; + +PTHREAD_STACK_CONTEXT FindThreadStackContext( + _In_ PVOID UniqueKey + ) +{ + PTHREAD_STACK_CONTEXT context; + PVOID *item; + + PhAcquireQueuedLockExclusive(&ContextHashtableLock); + + item = PhFindItemSimpleHashtable(ContextHashtable, UniqueKey); + + if (item) + context = *item; + else + context = NULL; + + PhReleaseQueuedLockExclusive(&ContextHashtableLock); + + return context; +} + +VOID ProcessThreadStackControl( + _In_ PPH_PLUGIN_THREAD_STACK_CONTROL Control + ) +{ + if (PhBeginInitOnce(&ContextHashtableInitOnce)) + { + ContextHashtable = PhCreateSimpleHashtable(4); + PhEndInitOnce(&ContextHashtableInitOnce); + } + + switch (Control->Type) + { + case PluginThreadStackInitializing: + { + PTHREAD_STACK_CONTEXT context; +#if _WIN64 + HANDLE processHandle; +#endif + + context = PhAllocate(sizeof(THREAD_STACK_CONTEXT)); + memset(context, 0, sizeof(THREAD_STACK_CONTEXT)); + context->ProcessId = Control->u.Initializing.ProcessId; + context->ThreadId = Control->u.Initializing.ThreadId; + context->ThreadHandle = Control->u.Initializing.ThreadHandle; + +#if _WIN64 + if (NT_SUCCESS(PhOpenProcess(&processHandle, ProcessQueryAccess, Control->u.Initializing.ProcessId))) + { + PhGetProcessIsWow64(processHandle, &context->IsWow64); + NtClose(processHandle); + } +#endif + + PhAcquireQueuedLockExclusive(&ContextHashtableLock); + PhAddItemSimpleHashtable(ContextHashtable, Control->UniqueKey, context); + PhReleaseQueuedLockExclusive(&ContextHashtableLock); + } + break; + case PluginThreadStackUninitializing: + { + PTHREAD_STACK_CONTEXT context = FindThreadStackContext(Control->UniqueKey); + + if (!context) + return; + + PhFree(context); + + PhAcquireQueuedLockExclusive(&ContextHashtableLock); + PhRemoveItemSimpleHashtable(ContextHashtable, Control->UniqueKey); + PhReleaseQueuedLockExclusive(&ContextHashtableLock); + } + break; + case PluginThreadStackResolveSymbol: + { + PTHREAD_STACK_CONTEXT context = FindThreadStackContext(Control->UniqueKey); + PPH_STRING managedSymbol = NULL; + ULONG64 displacement; + + if (!context) + return; + + if (context->Support) + { +#ifndef _WIN64 + PVOID predictedEip; + PVOID predictedEbp; + PVOID predictedEsp; + + predictedEip = context->PredictedEip; + predictedEbp = context->PredictedEbp; + predictedEsp = context->PredictedEsp; + + PredictAddressesFromClrData( + context->Support, + context->ThreadId, + Control->u.ResolveSymbol.StackFrame->PcAddress, + Control->u.ResolveSymbol.StackFrame->FrameAddress, + Control->u.ResolveSymbol.StackFrame->StackAddress, + &context->PredictedEip, + &context->PredictedEbp, + &context->PredictedEsp + ); + + // Fix up dbghelp EBP with real EBP given by the CLR data routines. + if (Control->u.ResolveSymbol.StackFrame->PcAddress == predictedEip) + { + Control->u.ResolveSymbol.StackFrame->FrameAddress = predictedEbp; + Control->u.ResolveSymbol.StackFrame->StackAddress = predictedEsp; + } +#endif + + managedSymbol = GetRuntimeNameByAddressClrProcess( + context->Support, + (ULONG64)Control->u.ResolveSymbol.StackFrame->PcAddress, + &displacement + ); + } +#ifdef _WIN64 + else if (context->IsWow64 && context->ConnectedToPhSvc) + { + PVOID predictedEip; + PVOID predictedEbp; + PVOID predictedEsp; + + predictedEip = context->PredictedEip; + predictedEbp = context->PredictedEbp; + predictedEsp = context->PredictedEsp; + + CallPredictAddressesFromClrData( + context->ProcessId, + context->ThreadId, + Control->u.ResolveSymbol.StackFrame->PcAddress, + Control->u.ResolveSymbol.StackFrame->FrameAddress, + Control->u.ResolveSymbol.StackFrame->StackAddress, + &context->PredictedEip, + &context->PredictedEbp, + &context->PredictedEsp + ); + + // Fix up dbghelp EBP with real EBP given by the CLR data routines. + if (Control->u.ResolveSymbol.StackFrame->PcAddress == predictedEip) + { + Control->u.ResolveSymbol.StackFrame->FrameAddress = predictedEbp; + Control->u.ResolveSymbol.StackFrame->StackAddress = predictedEsp; + } + + managedSymbol = CallGetRuntimeNameByAddress( + context->ProcessId, + (ULONG64)Control->u.ResolveSymbol.StackFrame->PcAddress, + &displacement + ); + } +#endif + + if (managedSymbol) + { + if (displacement != 0) + PhMoveReference(&managedSymbol, PhFormatString(L"%s + 0x%I64x", managedSymbol->Buffer, displacement)); + + if (Control->u.ResolveSymbol.Symbol) + PhMoveReference(&managedSymbol, PhFormatString(L"%s <-- %s", managedSymbol->Buffer, Control->u.ResolveSymbol.Symbol->Buffer)); + + PhMoveReference(&Control->u.ResolveSymbol.Symbol, managedSymbol); + } + } + break; + case PluginThreadStackBeginDefaultWalkStack: + { + PTHREAD_STACK_CONTEXT context = FindThreadStackContext(Control->UniqueKey); + BOOLEAN isDotNet; + + if (!context) + return; + + if (!NT_SUCCESS(PhGetProcessIsDotNet(context->ProcessId, &isDotNet)) || !isDotNet) + return; + + context->Support = CreateClrProcessSupport(context->ProcessId); + +#ifdef _WIN64 + if (context->IsWow64) + context->ConnectedToPhSvc = PhUiConnectToPhSvcEx(NULL, Wow64PhSvcMode, FALSE); +#endif + } + break; + case PluginThreadStackEndDefaultWalkStack: + { + PTHREAD_STACK_CONTEXT context = FindThreadStackContext(Control->UniqueKey); + + if (!context) + return; + + if (context->Support) + { + FreeClrProcessSupport(context->Support); + context->Support = NULL; + } + +#ifdef _WIN64 + if (context->ConnectedToPhSvc) + { + PhUiDisconnectFromPhSvc(); + context->ConnectedToPhSvc = FALSE; + } +#endif + } + break; + } +} + +VOID PredictAddressesFromClrData( + _In_ PCLR_PROCESS_SUPPORT Support, + _In_ HANDLE ThreadId, + _In_ PVOID PcAddress, + _In_ PVOID FrameAddress, + _In_ PVOID StackAddress, + _Out_ PVOID *PredictedEip, + _Out_ PVOID *PredictedEbp, + _Out_ PVOID *PredictedEsp + ) +{ +#ifdef _WIN64 + *PredictedEip = NULL; + *PredictedEbp = NULL; + *PredictedEsp = NULL; +#else + IXCLRDataTask *task; + + *PredictedEip = NULL; + *PredictedEbp = NULL; + *PredictedEsp = NULL; + + if (SUCCEEDED(IXCLRDataProcess_GetTaskByOSThreadID( + Support->DataProcess, + HandleToUlong(ThreadId), + &task + ))) + { + IXCLRDataStackWalk *stackWalk; + + if (SUCCEEDED(IXCLRDataTask_CreateStackWalk(task, 0xf, &stackWalk))) + { + HRESULT result; + BOOLEAN firstTime = TRUE; + CONTEXT context; + ULONG contextSize; + + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_CONTROL; + context.Eip = PtrToUlong(PcAddress); + context.Ebp = PtrToUlong(FrameAddress); + context.Esp = PtrToUlong(StackAddress); + + result = IXCLRDataStackWalk_SetContext2(stackWalk, CLRDATA_STACK_SET_CURRENT_CONTEXT, sizeof(CONTEXT), (BYTE *)&context); + + if (SUCCEEDED(result = IXCLRDataStackWalk_Next(stackWalk)) && result != S_FALSE) + { + if (SUCCEEDED(IXCLRDataStackWalk_GetContext(stackWalk, CONTEXT_CONTROL, sizeof(CONTEXT), &contextSize, (BYTE *)&context))) + { + *PredictedEip = UlongToPtr(context.Eip); + *PredictedEbp = UlongToPtr(context.Ebp); + *PredictedEsp = UlongToPtr(context.Esp); + } + } + + IXCLRDataStackWalk_Release(stackWalk); + } + + IXCLRDataTask_Release(task); + } +#endif +} diff --git a/plugins/DotNetTools/svcext.c b/plugins/DotNetTools/svcext.c new file mode 100644 index 0000000..9606c84 --- /dev/null +++ b/plugins/DotNetTools/svcext.c @@ -0,0 +1,209 @@ +/* + * Process Hacker .NET Tools - + * phsvc extensions + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "svcext.h" +#include "clrsup.h" + +PPH_STRING CallGetRuntimeNameByAddress( + _In_ HANDLE ProcessId, + _In_ ULONG64 Address, + _Out_opt_ PULONG64 Displacement + ) +{ + NTSTATUS status; + PH_PLUGIN_PHSVC_CLIENT client; + DN_API_GETRUNTIMENAMEBYADDRESS in; + DN_API_GETRUNTIMENAMEBYADDRESS out; + ULONG bufferSize; + PVOID buffer; + PPH_STRING name = NULL; + + if (!PhPluginQueryPhSvc(&client)) + return NULL; + + in.i.ProcessId = HandleToUlong(ProcessId); + in.i.Address = Address; + + bufferSize = 0x1000; + + if (!(buffer = client.CreateString(NULL, bufferSize, &in.i.Name))) + return NULL; + + status = PhPluginCallPhSvc(PluginInstance, DnGetRuntimeNameByAddressApiNumber, &in, sizeof(in), &out, sizeof(out)); + + if (status == STATUS_BUFFER_OVERFLOW) + { + client.FreeHeap(buffer); + bufferSize = out.o.NameLength; + + if (!(buffer = client.CreateString(NULL, bufferSize, &in.i.Name))) + return NULL; + + status = PhPluginCallPhSvc(PluginInstance, DnGetRuntimeNameByAddressApiNumber, &in, sizeof(in), &out, sizeof(out)); + } + + if (NT_SUCCESS(status)) + { + name = PhCreateStringEx(buffer, out.o.NameLength); + + if (Displacement) + *Displacement = out.o.Displacement; + } + + client.FreeHeap(buffer); + + return name; +} + +NTSTATUS DispatchGetRuntimeNameByAddress( + _In_ PPH_PLUGIN_PHSVC_REQUEST Request, + _In_ PDN_API_GETRUNTIMENAMEBYADDRESS In, + _Out_ PDN_API_GETRUNTIMENAMEBYADDRESS Out + ) +{ + NTSTATUS status = STATUS_SUCCESS; + PVOID nameBuffer; + PCLR_PROCESS_SUPPORT support; + PPH_STRING name; + + if (!NT_SUCCESS(status = Request->ProbeBuffer(&In->i.Name, sizeof(WCHAR), FALSE, &nameBuffer))) + return status; + + support = CreateClrProcessSupport(UlongToHandle(In->i.ProcessId)); + + if (!support) + return STATUS_UNSUCCESSFUL; + + name = GetRuntimeNameByAddressClrProcess(support, In->i.Address, &Out->o.Displacement); + + if (!name) + { + status = STATUS_UNSUCCESSFUL; + goto CleanupExit; + } + + memcpy(nameBuffer, name->Buffer, min(name->Length, In->i.Name.Length)); + Out->o.NameLength = (ULONG)name->Length; + + if (In->i.Name.Length < name->Length) + status = STATUS_BUFFER_OVERFLOW; + +CleanupExit: + FreeClrProcessSupport(support); + + return status; +} + +VOID CallPredictAddressesFromClrData( + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PVOID PcAddress, + _In_ PVOID FrameAddress, + _In_ PVOID StackAddress, + _Out_ PVOID *PredictedEip, + _Out_ PVOID *PredictedEbp, + _Out_ PVOID *PredictedEsp + ) +{ + PH_PLUGIN_PHSVC_CLIENT client; + DN_API_PREDICTADDRESSESFROMCLRDATA in; + DN_API_PREDICTADDRESSESFROMCLRDATA out; + + *PredictedEip = NULL; + *PredictedEbp = NULL; + *PredictedEsp = NULL; + + if (!PhPluginQueryPhSvc(&client)) + return; + + in.i.ProcessId = HandleToUlong(ProcessId); + in.i.ThreadId = HandleToUlong(ThreadId); + in.i.PcAddress = PtrToUlong(PcAddress); + in.i.FrameAddress = PtrToUlong(FrameAddress); + in.i.StackAddress = PtrToUlong(StackAddress); + + if (NT_SUCCESS(PhPluginCallPhSvc(PluginInstance, DnPredictAddressesFromClrDataApiNumber, &in, sizeof(in), &out, sizeof(out)))) + { + *PredictedEip = UlongToPtr(out.o.PredictedEip); + *PredictedEbp = UlongToPtr(out.o.PredictedEbp); + *PredictedEsp = UlongToPtr(out.o.PredictedEsp); + } +} + +NTSTATUS DispatchPredictAddressesFromClrData( + _In_ PPH_PLUGIN_PHSVC_REQUEST Request, + _In_ PDN_API_PREDICTADDRESSESFROMCLRDATA In, + _Out_ PDN_API_PREDICTADDRESSESFROMCLRDATA Out + ) +{ + PCLR_PROCESS_SUPPORT support; + PVOID predictedEip; + PVOID predictedEbp; + PVOID predictedEsp; + + support = CreateClrProcessSupport(UlongToHandle(In->i.ProcessId)); + + if (!support) + return STATUS_UNSUCCESSFUL; + + PredictAddressesFromClrData( + support, + UlongToHandle(In->i.ThreadId), + UlongToPtr(In->i.PcAddress), + UlongToPtr(In->i.FrameAddress), + UlongToPtr(In->i.StackAddress), + &predictedEip, + &predictedEbp, + &predictedEsp + ); + FreeClrProcessSupport(support); + + Out->o.PredictedEip = PtrToUlong(predictedEip); + Out->o.PredictedEbp = PtrToUlong(predictedEbp); + Out->o.PredictedEsp = PtrToUlong(predictedEsp); + + return STATUS_SUCCESS; +} + +VOID DispatchPhSvcRequest( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_PHSVC_REQUEST request = Parameter; + PVOID inBuffer; + + // InBuffer can alias OutBuffer, so make a copy of InBuffer. + inBuffer = PhAllocateCopy(request->InBuffer, request->InLength); + + switch (request->SubId) + { + case DnGetRuntimeNameByAddressApiNumber: + request->ReturnStatus = DispatchGetRuntimeNameByAddress(request, inBuffer, request->OutBuffer); + break; + case DnPredictAddressesFromClrDataApiNumber: + request->ReturnStatus = DispatchPredictAddressesFromClrData(request, inBuffer, request->OutBuffer); + break; + } + + PhFree(inBuffer); +} diff --git a/plugins/DotNetTools/svcext.h b/plugins/DotNetTools/svcext.h new file mode 100644 index 0000000..1600336 --- /dev/null +++ b/plugins/DotNetTools/svcext.h @@ -0,0 +1,87 @@ +/* + * Process Hacker .NET Tools - + * phsvc extensions + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef SVCEXT_H +#define SVCEXT_H + +// API + +typedef enum _DN_API_NUMBER +{ + DnGetRuntimeNameByAddressApiNumber = 1, + DnPredictAddressesFromClrDataApiNumber = 2 +} DN_API_NUMBER; + +typedef union _DN_API_GETRUNTIMENAMEBYADDRESS +{ + struct + { + ULONG ProcessId; + ULONG Reserved; + ULONG64 Address; + PH_RELATIVE_STRINGREF Name; // out + } i; + struct + { + ULONG64 Displacement; + ULONG NameLength; + } o; +} DN_API_GETRUNTIMENAMEBYADDRESS, *PDN_API_GETRUNTIMENAMEBYADDRESS; + +typedef union _DN_API_PREDICTADDRESSESFROMCLRDATA +{ + struct + { + ULONG ProcessId; + ULONG ThreadId; + ULONG PcAddress; + ULONG FrameAddress; + ULONG StackAddress; + } i; + struct + { + ULONG PredictedEip; + ULONG PredictedEbp; + ULONG PredictedEsp; + } o; +} DN_API_PREDICTADDRESSESFROMCLRDATA, *PDN_API_PREDICTADDRESSESFROMCLRDATA; + +// Calls + +PPH_STRING CallGetRuntimeNameByAddress( + _In_ HANDLE ProcessId, + _In_ ULONG64 Address, + _Out_opt_ PULONG64 Displacement + ); + +VOID CallPredictAddressesFromClrData( + _In_ HANDLE ProcessId, + _In_ HANDLE ThreadId, + _In_ PVOID PcAddress, + _In_ PVOID FrameAddress, + _In_ PVOID StackAddress, + _Out_ PVOID *PredictedEip, + _Out_ PVOID *PredictedEbp, + _Out_ PVOID *PredictedEsp + ); + +#endif \ No newline at end of file diff --git a/plugins/DotNetTools/treeext.c b/plugins/DotNetTools/treeext.c new file mode 100644 index 0000000..74b0f21 --- /dev/null +++ b/plugins/DotNetTools/treeext.c @@ -0,0 +1,296 @@ +/* + * Process Hacker .NET Tools - + * thread list extensions + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "dn.h" +#include "clrsup.h" + +VOID NTAPI ThreadsContextCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ); + +VOID NTAPI ThreadsContextDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ); + +VOID ThreadTreeNewMessage( + _In_ PVOID Parameter + ); + +LONG ThreadTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ); + +#define THREAD_TREE_CONTEXT_TYPE 1 + +typedef struct _THREAD_TREE_CONTEXT +{ + ULONG Type; + HANDLE ProcessId; + PH_CALLBACK_REGISTRATION AddedCallbackRegistration; + PCLR_PROCESS_SUPPORT Support; +} THREAD_TREE_CONTEXT, *PTHREAD_TREE_CONTEXT; + +static PPH_HASHTABLE ContextHashtable; +static PH_QUEUED_LOCK ContextHashtableLock = PH_QUEUED_LOCK_INIT; +static PH_INITONCE ContextHashtableInitOnce = PH_INITONCE_INIT; + +VOID InitializeTreeNewObjectExtensions( + VOID + ) +{ + PhPluginSetObjectExtension( + PluginInstance, + EmThreadsContextType, + sizeof(THREAD_TREE_CONTEXT), + ThreadsContextCreateCallback, + ThreadsContextDeleteCallback + ); +} + +VOID AddTreeNewColumn( + _In_ PPH_PLUGIN_TREENEW_INFORMATION TreeNewInfo, + _In_ PVOID Context, + _In_ ULONG SubId, + _In_ BOOLEAN Visible, + _In_ PWSTR Text, + _In_ ULONG Width, + _In_ ULONG Alignment, + _In_ ULONG TextFlags, + _In_ BOOLEAN SortDescending, + _In_ PPH_PLUGIN_TREENEW_SORT_FUNCTION SortFunction + ) +{ + PH_TREENEW_COLUMN column; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Visible = Visible; + column.SortDescending = SortDescending; + column.Text = Text; + column.Width = Width; + column.Alignment = Alignment; + column.DisplayIndex = TreeNew_GetVisibleColumnCount(TreeNewInfo->TreeNewHandle); + column.TextFlags = TextFlags; + + PhPluginAddTreeNewColumn( + PluginInstance, + TreeNewInfo->CmData, + &column, + SubId, + Context, + SortFunction + ); +} + +VOID DispatchTreeNewMessage( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + + switch (*(PULONG)message->Context) + { + case THREAD_TREE_CONTEXT_TYPE: + ThreadTreeNewMessage(Parameter); + break; + } +} + +static VOID ThreadAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_THREAD_ITEM threadItem = Parameter; + PDN_THREAD_ITEM dnThread; + PTHREAD_TREE_CONTEXT context = Context; + + dnThread = PhPluginGetObjectExtension(PluginInstance, threadItem, EmThreadItemType); + memset(dnThread, 0, sizeof(DN_THREAD_ITEM)); + dnThread->ThreadItem = threadItem; +} + +VOID NTAPI ThreadsContextCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_THREADS_CONTEXT threadsContext = Object; + PTHREAD_TREE_CONTEXT context = Extension; + + memset(context, 0, sizeof(THREAD_TREE_CONTEXT)); + context->Type = THREAD_TREE_CONTEXT_TYPE; + context->ProcessId = threadsContext->Provider->ProcessId; + + PhRegisterCallback( + &threadsContext->Provider->ThreadAddedEvent, + ThreadAddedHandler, + context, + &context->AddedCallbackRegistration + ); +} + +VOID NTAPI ThreadsContextDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_THREADS_CONTEXT threadsContext = Object; + PTHREAD_TREE_CONTEXT context = Extension; + + PhUnregisterCallback( + &threadsContext->Provider->ThreadAddedEvent, + &context->AddedCallbackRegistration + ); + + if (context->Support) + FreeClrProcessSupport(context->Support); +} + +VOID ThreadTreeNewInitializing( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_TREENEW_INFORMATION info = Parameter; + PPH_THREADS_CONTEXT threadsContext; + PTHREAD_TREE_CONTEXT context; + BOOLEAN isDotNet; + + threadsContext = info->SystemContext; + context = PhPluginGetObjectExtension(PluginInstance, threadsContext, EmThreadsContextType); + + if (NT_SUCCESS(PhGetProcessIsDotNet(threadsContext->Provider->ProcessId, &isDotNet)) && isDotNet) + { + PCLR_PROCESS_SUPPORT support; + + support = CreateClrProcessSupport(threadsContext->Provider->ProcessId); + + if (!support) + return; + + context->Support = support; + + AddTreeNewColumn(info, context, DNTHTNC_APPDOMAIN, TRUE, L"AppDomain", 120, PH_ALIGN_LEFT, 0, FALSE, ThreadTreeNewSortFunction); + } +} + +VOID ThreadTreeNewUninitializing( + _In_ PVOID Parameter + ) +{ + NOTHING; +} + +VOID UpdateThreadClrData( + _In_ PTHREAD_TREE_CONTEXT Context, + _Inout_ PDN_THREAD_ITEM DnThread + ) +{ + if (!DnThread->ClrDataValid) + { + IXCLRDataProcess *process; + IXCLRDataTask *task; + IXCLRDataAppDomain *appDomain; + + if (Context->Support) + process = Context->Support->DataProcess; + else + return; + + if (SUCCEEDED(IXCLRDataProcess_GetTaskByOSThreadID(process, HandleToUlong(DnThread->ThreadItem->ThreadId), &task))) + { + if (SUCCEEDED(IXCLRDataTask_GetCurrentAppDomain(task, &appDomain))) + { + DnThread->AppDomainText = GetNameXClrDataAppDomain(appDomain); + IXCLRDataAppDomain_Release(appDomain); + } + + IXCLRDataTask_Release(task); + } + + DnThread->ClrDataValid = TRUE; + } +} + +VOID ThreadTreeNewMessage( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + PTHREAD_TREE_CONTEXT context = message->Context; + + if (message->Message == TreeNewGetCellText) + { + PPH_TREENEW_GET_CELL_TEXT getCellText = message->Parameter1; + PPH_THREAD_NODE threadNode = (PPH_THREAD_NODE)getCellText->Node; + PDN_THREAD_ITEM dnThread; + + dnThread = PhPluginGetObjectExtension(PluginInstance, threadNode->ThreadItem, EmThreadItemType); + + switch (message->SubId) + { + case DNTHTNC_APPDOMAIN: + UpdateThreadClrData(context, dnThread); + getCellText->Text = PhGetStringRef(dnThread->AppDomainText); + break; + } + } +} + +LONG ThreadTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ) +{ + PTHREAD_TREE_CONTEXT context = Context; + LONG result; + PPH_THREAD_NODE node1 = Node1; + PPH_THREAD_NODE node2 = Node2; + PDN_THREAD_ITEM dnThread1; + PDN_THREAD_ITEM dnThread2; + + dnThread1 = PhPluginGetObjectExtension(PluginInstance, node1->ThreadItem, EmThreadItemType); + dnThread2 = PhPluginGetObjectExtension(PluginInstance, node2->ThreadItem, EmThreadItemType); + + result = 0; + + switch (SubId) + { + case DNTHTNC_APPDOMAIN: + UpdateThreadClrData(context, dnThread1); + UpdateThreadClrData(context, dnThread2); + result = PhCompareStringWithNull(dnThread1->AppDomainText, dnThread2->AppDomainText, TRUE); + break; + } + + return result; +} diff --git a/plugins/ExtendedNotifications/CHANGELOG.txt b/plugins/ExtendedNotifications/CHANGELOG.txt new file mode 100644 index 0000000..6ac5c90 --- /dev/null +++ b/plugins/ExtendedNotifications/CHANGELOG.txt @@ -0,0 +1,11 @@ +1.3 + * Added Growl support + +1.2 + * Added ability to log events to a file + +1.1 + * Fixed memory leak + +1.0 + * Initial release diff --git a/plugins/ExtendedNotifications/ExtendedNotifications.rc b/plugins/ExtendedNotifications/ExtendedNotifications.rc new file mode 100644 index 0000000..1e28a0f --- /dev/null +++ b/plugins/ExtendedNotifications/ExtendedNotifications.rc @@ -0,0 +1,214 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,3,0,0 + PRODUCTVERSION 1,3,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Extended Notifications for Process Hacker" + VALUE "FileVersion", "1.3" + VALUE "InternalName", "ExtendedNotifications" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "ExtendedNotifications.dll" + VALUE "ProductName", "Extended Notifications for Process Hacker" + VALUE "ProductVersion", "1.3" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROCESSES DIALOGEX 0, 0, 255, 229 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Processes" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "You can configure processes for which notifications are displayed. Wildcards can be used, and ordering is considered.",IDC_STATIC,7,7,241,19 + LISTBOX IDC_LIST,7,30,187,118,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Move up",IDC_MOVEUP,198,30,50,14 + PUSHBUTTON "Move down",IDC_MOVEDOWN,198,47,50,14 + EDITTEXT IDC_TEXT,7,152,147,12,ES_AUTOHSCROLL + CONTROL "Include",IDC_INCLUDE,"Button",BS_AUTORADIOBUTTON,163,153,39,10 + CONTROL "Exclude",IDC_EXCLUDE,"Button",BS_AUTORADIOBUTTON,207,153,41,10 + PUSHBUTTON "Add/Update",IDC_ADD,145,168,50,14 + PUSHBUTTON "Remove",IDC_REMOVE,198,168,50,14 + LTEXT "Examples:\nnote*.exe\nC:\\Windows\\system32\\cmd.exe\nC:\\Windows\\*",IDC_STATIC,7,186,241,36 +END + +IDD_SERVICES DIALOGEX 0, 0, 255, 229 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Services" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "You can configure services for which notifications are displayed. Wildcards can be used, and ordering is considered.",IDC_STATIC,7,7,241,19 + LISTBOX IDC_LIST,7,30,187,118,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Move up",IDC_MOVEUP,198,30,50,14 + PUSHBUTTON "Move down",IDC_MOVEDOWN,198,47,50,14 + EDITTEXT IDC_TEXT,7,152,147,12,ES_AUTOHSCROLL + CONTROL "Include",IDC_INCLUDE,"Button",BS_AUTORADIOBUTTON,163,153,39,10 + CONTROL "Exclude",IDC_EXCLUDE,"Button",BS_AUTORADIOBUTTON,207,153,41,10 + PUSHBUTTON "Add/Update",IDC_ADD,145,168,50,14 + PUSHBUTTON "Remove",IDC_REMOVE,198,168,50,14 + LTEXT "Examples:\nWdi*\nseclogon",IDC_STATIC,7,186,241,36 +END + +IDD_LOGGING DIALOGEX 0, 0, 255, 229 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Logging" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "File",IDC_STATIC,7,7,241,53 + LTEXT "Log all events to this file (leave blank to disable this feature):",IDC_STATIC,13,18,196,8 + EDITTEXT IDC_LOGFILENAME,13,29,178,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,194,28,50,14 + LTEXT "Changes will require a restart of Process Hacker.",IDC_STATIC,14,45,157,8 +END + +IDD_GROWL DIALOGEX 0, 0, 255, 229 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Growl" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Send notifications to Growl",IDC_ENABLEGROWL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,101,10 + LTEXT "gntp-send license:",IDC_STATIC,7,23,60,8 + EDITTEXT IDC_LICENSE,7,34,241,188,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROCESSES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 222 + END + + IDD_SERVICES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 222 + END + + IDD_LOGGING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 222 + END + + IDD_GROWL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 248 + TOPMARGIN, 7 + BOTTOMMARGIN, 222 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_GROWL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj b/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj new file mode 100644 index 0000000..2ce47eb --- /dev/null +++ b/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {80E791B8-AC98-407E-8FF9-5154AF50E887} + ExtendedNotifications + Win32Proj + ExtendedNotifications + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + ws2_32.lib;%(AdditionalDependencies) + ws2_32.dll;%(DelayLoadDLLs) + + + + + ws2_32.lib;%(AdditionalDependencies) + ws2_32.dll;%(DelayLoadDLLs) + + + + + ws2_32.lib;%(AdditionalDependencies) + ws2_32.dll;%(DelayLoadDLLs) + + + + + ws2_32.lib;%(AdditionalDependencies) + ws2_32.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj.filters b/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj.filters new file mode 100644 index 0000000..85a5e41 --- /dev/null +++ b/plugins/ExtendedNotifications/ExtendedNotifications.vcxproj.filters @@ -0,0 +1,57 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + + \ No newline at end of file diff --git a/plugins/ExtendedNotifications/extnoti.h b/plugins/ExtendedNotifications/extnoti.h new file mode 100644 index 0000000..1f72045 --- /dev/null +++ b/plugins/ExtendedNotifications/extnoti.h @@ -0,0 +1,30 @@ +#ifndef EXTNOTI_H +#define EXTNOTI_H + +#define PLUGIN_NAME L"ProcessHacker.ExtendedNotifications" +#define SETTING_NAME_ENABLE_GROWL (PLUGIN_NAME L".EnableGrowl") +#define SETTING_NAME_LOG_FILENAME (PLUGIN_NAME L".LogFileName") +#define SETTING_NAME_PROCESS_LIST (PLUGIN_NAME L".ProcessList") +#define SETTING_NAME_SERVICE_LIST (PLUGIN_NAME L".ServiceList") + +// main + +typedef enum _FILTER_TYPE +{ + FilterInclude, + FilterExclude +} FILTER_TYPE; + +typedef struct _FILTER_ENTRY +{ + FILTER_TYPE Type; + PPH_STRING Filter; +} FILTER_ENTRY, *PFILTER_ENTRY; + +// filelog + +VOID FileLogInitialization( + VOID + ); + +#endif diff --git a/plugins/ExtendedNotifications/filelog.c b/plugins/ExtendedNotifications/filelog.c new file mode 100644 index 0000000..372dc97 --- /dev/null +++ b/plugins/ExtendedNotifications/filelog.c @@ -0,0 +1,79 @@ +/* + * Process Hacker Extended Notifications - + * file logging + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include "extnoti.h" + +VOID NTAPI LoggedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +PPH_FILE_STREAM LogFileStream = NULL; +PH_CALLBACK_REGISTRATION LoggedCallbackRegistration; + +VOID FileLogInitialization( + VOID + ) +{ + NTSTATUS status; + PPH_STRING fileName; + + fileName = PhaGetStringSetting(SETTING_NAME_LOG_FILENAME); + + if (fileName->Length != 0) + { + status = PhCreateFileStream( + &LogFileStream, + fileName->Buffer, + FILE_GENERIC_WRITE, + FILE_SHARE_READ, + FILE_OPEN_IF, + PH_FILE_STREAM_APPEND | PH_FILE_STREAM_UNBUFFERED + ); + + if (NT_SUCCESS(status)) + { + PhRegisterCallback( + &PhLoggedCallback, + LoggedCallback, + NULL, + &LoggedCallbackRegistration + ); + } + } +} + +VOID NTAPI LoggedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_LOG_ENTRY logEntry = Parameter; + + PhWriteStringFormatAsUtf8FileStream( + LogFileStream, + L"%s: %s\r\n", + PhaFormatDateTime(NULL)->Buffer, + PH_AUTO_T(PH_STRING, PhFormatLogEntry(logEntry))->Buffer + ); +} diff --git a/plugins/ExtendedNotifications/gntp-send/LICENSE.txt b/plugins/ExtendedNotifications/gntp-send/LICENSE.txt new file mode 100644 index 0000000..a7e6a12 --- /dev/null +++ b/plugins/ExtendedNotifications/gntp-send/LICENSE.txt @@ -0,0 +1,26 @@ +[The "BSD licence"] +Copyright (c) 2009-2010 Yasuhiro Matsumoto +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/plugins/ExtendedNotifications/gntp-send/growl.c b/plugins/ExtendedNotifications/gntp-send/growl.c new file mode 100644 index 0000000..77e7507 --- /dev/null +++ b/plugins/ExtendedNotifications/gntp-send/growl.c @@ -0,0 +1,544 @@ +#define _CRT_RAND_S +#include +#include +#include +#include +#include +#include + +#include "md5.h" +#include "tcp.h" +#include "growl.h" + +static const char hex_table[] = "0123456789ABCDEF"; +static char* string_to_hex_alloc(const char* str, int len) { + int n, l; + char* tmp = (char*)PhAllocateSafe(len * 2 + 1); + if (tmp) { + memset(tmp, 0, len * 2 + 1); + for (l = 0, n = 0; l < len; l++) { + tmp[n++] = hex_table[(str[l] & 0xF0) >> 4]; + tmp[n++] = hex_table[str[l] & 0x0F]; + } + } + return tmp; +} + +int growl_init_ = 0; + +/* dmex: modified to use latest Winsock version */ +int growl_init() +{ + if (growl_init_ == 0) + { +#ifdef _WIN32 + WSADATA wsaData; + if (WSAStartup(WINSOCK_VERSION, &wsaData) != 0) + { + return -1; + } +#endif + + growl_init_ = 1; + } + return 1; +} + + +void growl_shutdown() +{ + if (growl_init_ == 1) + { +#ifdef _WIN32 + WSACleanup(); +#endif + } +} + +/* dmex: modified to use a version of rand with security enhancements */ +char* gen_salt_alloc(int count) +{ + char* salt = (char*)PhAllocateSafe(count + 1); + + if (salt) + { + int n; + int randSeed = 0; + + rand_s(&randSeed); + + for (n = 0; n < count; n++) + salt[n] = (randSeed % 255) + 1; + + salt[n] = 0; + } + + return salt; +} + +char* gen_password_hash_alloc(const char* password, const char* salt) { + md5_context md5ctx; + char md5tmp[20]; + char* md5digest; + + memset(md5tmp, 0, sizeof(md5tmp)); + md5_starts(&md5ctx); + md5_update(&md5ctx, (uint8_t*)password, (uint32_t)strlen(password)); + md5_update(&md5ctx, (uint8_t*)salt, (uint32_t)strlen(salt)); + md5_finish(&md5ctx, (uint8_t*)md5tmp); + + md5_starts(&md5ctx); + md5_update(&md5ctx, (uint8_t*)md5tmp, 16); + md5_finish(&md5ctx, (uint8_t*)md5tmp); + md5digest = string_to_hex_alloc(md5tmp, 16); + + return md5digest; +} + +char *growl_generate_authheader_alloc(const char*const password) +{ + char* salt; + char* salthash; + char* keyhash; + char* authheader = NULL; + + if (password) { + salt = gen_salt_alloc(8); + if (salt) { + keyhash = gen_password_hash_alloc(password, salt); + if (keyhash) { + salthash = string_to_hex_alloc(salt, 8); + if (salthash) { + authheader = (char*)PhAllocateSafe(strlen(keyhash) + strlen(salthash) + 7); + if (authheader) { + sprintf(authheader, " MD5:%s.%s", keyhash, salthash); + } + PhFree(salthash); + } + PhFree(keyhash); + } + PhFree(salt); + } + } + + return authheader; +} + +/* dmex: modified to use INVALID_SOCKET */ +int growl_tcp_register( const char *const server , const char *const appname , const char **const notifications , const int notifications_count , + const char *const password, const char* const icon ) +{ + SOCKET sock = INVALID_SOCKET; + int i=0; + char *authheader; + char *iconid = NULL; + FILE *iconfile = NULL; + size_t iconsize; + uint8_t buffer[1024]; + + growl_init(); + authheader = growl_generate_authheader_alloc(password); + sock = growl_tcp_open(server); + if (sock == INVALID_SOCKET) goto leave; + if (icon) { + size_t bytes_read; + md5_context md5ctx; + char md5tmp[20]; + iconfile = fopen(icon, "rb"); + if (iconfile) { + fseek(iconfile, 0, SEEK_END); + iconsize = ftell(iconfile); + fseek(iconfile, 0, SEEK_SET); + memset(md5tmp, 0, sizeof(md5tmp)); + md5_starts(&md5ctx); + while (!feof(iconfile)) { + bytes_read = fread(buffer, 1, 1024, iconfile); + if (bytes_read) md5_update(&md5ctx, buffer, (uint32_t)bytes_read); + } + fseek(iconfile, 0, SEEK_SET); + md5_finish(&md5ctx, md5tmp); + iconid = string_to_hex_alloc(md5tmp, 16); + } + } + + growl_tcp_write(sock, "GNTP/1.0 REGISTER NONE %s", authheader ? authheader : ""); + growl_tcp_write(sock, "Application-Name: %s", appname); + if(iconid) + { + growl_tcp_write(sock, "Application-Icon: x-growl-resource://%s", iconid); + } + else if(icon) + { + growl_tcp_write(sock, "Application-Icon: %s", icon ); + } + growl_tcp_write(sock, "Notifications-Count: %d", notifications_count); + growl_tcp_write(sock, "" ); + + for(i=0;i + +typedef struct { + PH_HASH_CONTEXT hc; +} md5_context; + +__forceinline void md5_starts(md5_context *ctx) +{ + PhInitializeHash(&ctx->hc, Md5HashAlgorithm); +} + +__forceinline void md5_update(md5_context *ctx, const uint8_t *input, uint32_t length) +{ + PhUpdateHash(&ctx->hc, (PVOID)input, length); +} + +__forceinline void md5_finish(md5_context *ctx, uint8_t digest[16]) +{ + if (!PhFinalHash(&ctx->hc, digest, 16, NULL)) + PhRaiseStatus(STATUS_INTERNAL_ERROR); +} + +#endif /* _MD5_H_ */ diff --git a/plugins/ExtendedNotifications/gntp-send/tcp.c b/plugins/ExtendedNotifications/gntp-send/tcp.c new file mode 100644 index 0000000..f47fa7f --- /dev/null +++ b/plugins/ExtendedNotifications/gntp-send/tcp.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "tcp.h" + +int growl_tcp_parse_hostname( const char *const server , int default_port , struct sockaddr_in *const sockaddr ); + +void growl_tcp_write_raw( SOCKET sock, const unsigned char * data, const int data_length ) +{ + send(sock, data, data_length, 0); +} + +void growl_tcp_write( SOCKET sock , const char *const format , ... ) +{ + int length; + char *output; + char *stop; + + va_list ap; + + va_start( ap , format ); + length = vsnprintf( NULL , 0 , format , ap ); + va_end(ap); + + va_start(ap,format); + output = (char*)PhAllocateSafe(length+1); + if (!output) { + va_end(ap); + return; + } + vsnprintf( output , length+1 , format , ap ); + va_end(ap); + + while ((stop = strstr(output, "\r\n"))) strcpy(stop, stop + 1); + + send( sock , output , length , 0 ); + send( sock , "\r\n" , 2 , 0 ); + + PhFree(output); +} + +char *growl_tcp_read(SOCKET sock) { + const int growsize = 80; + char c = 0; + char* line = (char*) PhAllocateSafe(growsize); + if (line) { + int len = growsize, pos = 0; + char* newline; + while (line) { + if (recv(sock, &c, 1, 0) <= 0) break; + if (c == '\r') continue; + if (c == '\n') break; + line[pos++] = c; + if (pos >= len) { + len += growsize; + newline = (char*) realloc(line, len); + if (!newline) { + PhFree(line); + return NULL; + } + line = newline; + } + } + line[pos] = 0; + } + return line; +} + +/* dmex: modified to use INVALID_SOCKET and SOCKET_ERROR */ +SOCKET growl_tcp_open(const char* server) { + SOCKET sock = INVALID_SOCKET; +#ifdef _WIN32 + char on; +#else + int on; +#endif + struct sockaddr_in serv_addr; + + if( growl_tcp_parse_hostname( server , 23053 , &serv_addr ) == -1 ) { + return INVALID_SOCKET; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { + perror("create socket"); + return INVALID_SOCKET; + } + + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) { + perror("connect"); + closesocket(sock); // dmex: fixed handle leaking on error + return INVALID_SOCKET; + } + + on = 1; + if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { + perror("setsockopt"); + closesocket(sock); // dmex: fixed handle leaking on error + return INVALID_SOCKET; + } + + return sock; +} + +/* dmex: modified to use INVALID_SOCKET */ +void growl_tcp_close(SOCKET sock) { +#ifdef _WIN32 + if (sock != INVALID_SOCKET) closesocket(sock); +#else + if (sock > 0) close(sock); +#endif +} + +int growl_tcp_parse_hostname( const char *const server , int default_port , struct sockaddr_in *const sockaddr ) +{ + char *hostname = PhDuplicateBytesZSafe((PSTR)server); + char *port = strchr( hostname, ':' ); + struct hostent* host_ent; + if( port != NULL ) + { + *port = '\0'; + port++; + default_port = atoi(port); + } + + host_ent = gethostbyname(hostname); + if( host_ent == NULL ) + { + perror("gethostbyname"); + PhFree(hostname); + return -1; + } + + // dmex: fixed wrong sizeof argument + memset( sockaddr , 0 , sizeof(struct sockaddr_in) ); + sockaddr->sin_family = AF_INET; + memcpy( &sockaddr->sin_addr , host_ent->h_addr , host_ent->h_length ); + sockaddr->sin_port = htons(default_port); + + PhFree(hostname); + return 0; +} + +int growl_tcp_datagram( const char *server , const unsigned char *data , const int data_length ) +{ + int result; + struct sockaddr_in serv_addr; + SOCKET sock = 0; + + if( growl_tcp_parse_hostname( server , 9887 , &serv_addr ) == -1 ) + { + return -1; + } + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if( sock == INVALID_SOCKET ) + { + return -1; + } + + if( sendto(sock, (char*)data , data_length , 0 , (struct sockaddr*)&serv_addr , sizeof(serv_addr) ) > 0 ) + { + result = 0; + } + else + { + result = 1; + } + + closesocket(sock); + return result; +} diff --git a/plugins/ExtendedNotifications/gntp-send/tcp.h b/plugins/ExtendedNotifications/gntp-send/tcp.h new file mode 100644 index 0000000..4e7e799 --- /dev/null +++ b/plugins/ExtendedNotifications/gntp-send/tcp.h @@ -0,0 +1,14 @@ +#ifndef _TCP_H_ +#define _TCP_H_ + +#ifdef _MSC_VER +#define __attribute__(x) +#endif +void growl_tcp_write_raw( SOCKET sock, const unsigned char * data, const int data_length ); +void growl_tcp_write( SOCKET sock , const char *const format , ... ) __attribute__ ((format (printf, 2, 3))); +char* growl_tcp_read(SOCKET sock); +SOCKET growl_tcp_open(const char* server); +void growl_tcp_close(SOCKET sock); +int growl_tcp_datagram( const char *server , const unsigned char *data , const int data_length ); + +#endif /* _TCP_H_ */ diff --git a/plugins/ExtendedNotifications/main.c b/plugins/ExtendedNotifications/main.c new file mode 100644 index 0000000..24c87e7 --- /dev/null +++ b/plugins/ExtendedNotifications/main.c @@ -0,0 +1,1110 @@ +/* + * Process Hacker Extended Notifications - + * main program + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include "extnoti.h" +#include "resource.h" +#include "gntp-send/growl.h" + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI NotifyEventCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID RegisterGrowl( + _In_ BOOLEAN Force + ); + +VOID NotifyGrowl( + _In_ PPH_PLUGIN_NOTIFY_EVENT NotifyEvent + ); + +NTSTATUS NTAPI RegisterGrowlCallback( + _In_ PVOID Parameter + ); + +INT_PTR CALLBACK ProcessesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK ServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK LoggingDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK GrowlDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PPH_PLUGIN PluginInstance; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION NotifyEventCallbackRegistration; + +PPH_LIST ProcessFilterList; +PPH_LIST ServiceFilterList; + +PSTR GrowlNotifications[] = +{ + "Process Created", + "Process Terminated", + "Service Created", + "Service Deleted", + "Service Started", + "Service Stopped" +}; + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Extended Notifications"; + info->Author = L"wj32"; + info->Description = L"Filters notifications."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1112"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackNotifyEvent), + NotifyEventCallback, + NULL, + &NotifyEventCallbackRegistration + ); + + { + static PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_ENABLE_GROWL, L"0" }, + { StringSettingType, SETTING_NAME_LOG_FILENAME, L"" }, + { StringSettingType, SETTING_NAME_PROCESS_LIST, L"\\i*" }, + { StringSettingType, SETTING_NAME_SERVICE_LIST, L"\\i*" } + }; + + PhAddSettings(settings, sizeof(settings) / sizeof(PH_SETTING_CREATE)); + } + + ProcessFilterList = PhCreateList(10); + ServiceFilterList = PhCreateList(10); + } + break; + } + + return TRUE; +} + +VOID FreeFilterEntry( + _In_ PFILTER_ENTRY Entry + ) +{ + PhDereferenceObject(Entry->Filter); + PhFree(Entry); +} + +VOID ClearFilterList( + _Inout_ PPH_LIST FilterList + ) +{ + ULONG i; + + for (i = 0; i < FilterList->Count; i++) + FreeFilterEntry(FilterList->Items[i]); + + PhClearList(FilterList); +} + +VOID CopyFilterList( + _Inout_ PPH_LIST Destination, + _In_ PPH_LIST Source + ) +{ + ULONG i; + + for (i = 0; i < Source->Count; i++) + { + PFILTER_ENTRY entry = Source->Items[i]; + PFILTER_ENTRY newEntry; + + newEntry = PhAllocate(sizeof(FILTER_ENTRY)); + newEntry->Type = entry->Type; + newEntry->Filter = entry->Filter; + PhReferenceObject(newEntry->Filter); + + PhAddItemList(Destination, newEntry); + } +} + +VOID LoadFilterList( + _Inout_ PPH_LIST FilterList, + _In_ PPH_STRING String + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T length; + SIZE_T i; + PFILTER_ENTRY entry; + + length = String->Length / 2; + PhInitializeStringBuilder(&stringBuilder, 20); + + entry = NULL; + + for (i = 0; i < length; i++) + { + if (String->Buffer[i] == '\\') + { + if (i != length - 1) + { + i++; + + switch (String->Buffer[i]) + { + case 'i': + case 'e': + if (entry) + { + entry->Filter = PhFinalStringBuilderString(&stringBuilder); + PhAddItemList(FilterList, entry); + PhInitializeStringBuilder(&stringBuilder, 20); + } + + entry = PhAllocate(sizeof(FILTER_ENTRY)); + entry->Type = String->Buffer[i] == 'i' ? FilterInclude : FilterExclude; + + break; + + default: + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); + break; + } + } + else + { + // Trailing backslash. Just ignore it. + break; + } + } + else + { + PhAppendCharStringBuilder(&stringBuilder, String->Buffer[i]); + } + } + + if (entry) + { + entry->Filter = PhFinalStringBuilderString(&stringBuilder); + PhAddItemList(FilterList, entry); + } + else + { + PhDeleteStringBuilder(&stringBuilder); + } +} + +PPH_STRING SaveFilterList( + _Inout_ PPH_LIST FilterList + ) +{ + PH_STRING_BUILDER stringBuilder; + SIZE_T i; + SIZE_T j; + WCHAR temp[2]; + + PhInitializeStringBuilder(&stringBuilder, 100); + + temp[0] = '\\'; + + for (i = 0; i < FilterList->Count; i++) + { + PFILTER_ENTRY entry = FilterList->Items[i]; + SIZE_T length; + + // Write the entry type. + + temp[1] = entry->Type == FilterInclude ? 'i' : 'e'; + + PhAppendStringBuilderEx(&stringBuilder, temp, 4); + + // Write the filter string. + + length = entry->Filter->Length / 2; + + for (j = 0; j < length; j++) + { + if (entry->Filter->Buffer[j] == '\\') // escape backslashes + { + temp[1] = entry->Filter->Buffer[j]; + PhAppendStringBuilderEx(&stringBuilder, temp, 4); + } + else + { + PhAppendCharStringBuilder(&stringBuilder, entry->Filter->Buffer[j]); + } + } + } + + return PhFinalStringBuilderString(&stringBuilder); +} + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + LoadFilterList(ProcessFilterList, PhaGetStringSetting(SETTING_NAME_PROCESS_LIST)); + LoadFilterList(ServiceFilterList, PhaGetStringSetting(SETTING_NAME_SERVICE_LIST)); + + FileLogInitialization(); + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_GROWL)) + { + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), RegisterGrowlCallback, NULL); + } +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[4]; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = (HWND)Parameter; + propSheetHeader.pszCaption = L"Extended Notifications"; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // Processes + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PROCESSES); + propSheetPage.pfnDlgProc = ProcessesDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Services + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SERVICES); + propSheetPage.pfnDlgProc = ServicesDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Logging + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_LOGGING); + propSheetPage.pfnDlgProc = LoggingDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Growl + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_GROWL); + propSheetPage.pfnDlgProc = GrowlDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + PhModalPropertySheet(&propSheetHeader); +} + +BOOLEAN MatchFilterList( + _In_ PPH_LIST FilterList, + _In_ PPH_STRING String, + _Out_ FILTER_TYPE *FilterType + ) +{ + ULONG i; + BOOLEAN isFileName; + + isFileName = PhFindCharInString(String, 0, '\\') != -1; + + for (i = 0; i < FilterList->Count; i++) + { + PFILTER_ENTRY entry = FilterList->Items[i]; + + if (isFileName && PhFindCharInString(entry->Filter, 0, '\\') == -1) + continue; // ignore filters without backslashes if we're matching a file name + + if (entry->Filter->Length == 2 && entry->Filter->Buffer[0] == '*') // shortcut + { + *FilterType = entry->Type; + return TRUE; + } + + if (PhMatchWildcards(entry->Filter->Buffer, String->Buffer, TRUE)) + { + *FilterType = entry->Type; + return TRUE; + } + } + + return FALSE; +} + +VOID NTAPI NotifyEventCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_NOTIFY_EVENT notifyEvent = Parameter; + PPH_PROCESS_ITEM processItem; + PPH_SERVICE_ITEM serviceItem; + FILTER_TYPE filterType; + BOOLEAN found = FALSE; + + filterType = FilterExclude; + + switch (notifyEvent->Type) + { + case PH_NOTIFY_PROCESS_CREATE: + case PH_NOTIFY_PROCESS_DELETE: + processItem = notifyEvent->Parameter; + + if (processItem->FileName) + found = MatchFilterList(ProcessFilterList, processItem->FileName, &filterType); + + if (!found) + MatchFilterList(ProcessFilterList, processItem->ProcessName, &filterType); + + break; + + case PH_NOTIFY_SERVICE_CREATE: + case PH_NOTIFY_SERVICE_DELETE: + case PH_NOTIFY_SERVICE_START: + case PH_NOTIFY_SERVICE_STOP: + serviceItem = notifyEvent->Parameter; + + MatchFilterList(ServiceFilterList, serviceItem->Name, &filterType); + + break; + } + + if (filterType == FilterExclude) + notifyEvent->Handled = TRUE; // pretend we handled the notification to prevent it from displaying + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_GROWL)) + NotifyGrowl(notifyEvent); +} + +VOID RegisterGrowl( + _In_ BOOLEAN Force + ) +{ + static BOOLEAN registered = FALSE; + + if (!Force && registered) + return; + + growl_tcp_register("127.0.0.1", "Process Hacker", GrowlNotifications, sizeof(GrowlNotifications) / sizeof(PSTR), NULL, NULL); + + registered = TRUE; +} + +VOID NotifyGrowl( + _In_ PPH_PLUGIN_NOTIFY_EVENT NotifyEvent + ) +{ + PSTR notification; + PPH_STRING title; + PPH_BYTES titleUtf8; + PPH_STRING message; + PPH_BYTES messageUtf8; + PPH_PROCESS_ITEM processItem; + PPH_SERVICE_ITEM serviceItem; + PPH_PROCESS_ITEM parentProcessItem; + + if (NotifyEvent->Handled) + return; + + switch (NotifyEvent->Type) + { + case PH_NOTIFY_PROCESS_CREATE: + processItem = NotifyEvent->Parameter; + notification = GrowlNotifications[0]; + title = processItem->ProcessName; + + parentProcessItem = PhReferenceProcessItemForParent( + processItem->ParentProcessId, + processItem->ProcessId, + &processItem->CreateTime + ); + + message = PhaFormatString( + L"The process %s (%lu) was started by %s.", + processItem->ProcessName->Buffer, + HandleToUlong(processItem->ProcessId), + parentProcessItem ? parentProcessItem->ProcessName->Buffer : L"an unknown process" + ); + + if (parentProcessItem) + PhDereferenceObject(parentProcessItem); + + break; + case PH_NOTIFY_PROCESS_DELETE: + processItem = NotifyEvent->Parameter; + notification = GrowlNotifications[1]; + title = processItem->ProcessName; + + message = PhaFormatString(L"The process %s (%lu) was terminated.", + processItem->ProcessName->Buffer, + HandleToUlong(processItem->ProcessId) + ); + + break; + case PH_NOTIFY_SERVICE_CREATE: + serviceItem = NotifyEvent->Parameter; + notification = GrowlNotifications[2]; + title = serviceItem->DisplayName; + + message = PhaFormatString(L"The service %s (%s) has been created.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + ); + + break; + case PH_NOTIFY_SERVICE_DELETE: + serviceItem = NotifyEvent->Parameter; + notification = GrowlNotifications[3]; + title = serviceItem->DisplayName; + + message = PhaFormatString(L"The service %s (%s) has been deleted.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + ); + + break; + case PH_NOTIFY_SERVICE_START: + serviceItem = NotifyEvent->Parameter; + notification = GrowlNotifications[4]; + title = serviceItem->DisplayName; + + message = PhaFormatString(L"The service %s (%s) has been started.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + ); + + break; + case PH_NOTIFY_SERVICE_STOP: + serviceItem = NotifyEvent->Parameter; + notification = GrowlNotifications[5]; + title = serviceItem->DisplayName; + + message = PhaFormatString(L"The service %s (%s) has been stopped.", + serviceItem->Name->Buffer, + serviceItem->DisplayName->Buffer + ); + + break; + default: + return; + } + + titleUtf8 = PH_AUTO(PhConvertUtf16ToUtf8Ex(title->Buffer, title->Length)); + messageUtf8 = PH_AUTO(PhConvertUtf16ToUtf8Ex(message->Buffer, message->Length)); + + RegisterGrowl(TRUE); + + if (growl_tcp_notify("127.0.0.1", "Process Hacker", notification, titleUtf8->Buffer, messageUtf8->Buffer, NULL, NULL, NULL) == 0) + NotifyEvent->Handled = TRUE; +} + +NTSTATUS NTAPI RegisterGrowlCallback( + _In_ PVOID Parameter + ) +{ + RegisterGrowl(FALSE); + + return STATUS_SUCCESS; +} + +PPH_STRING FormatFilterEntry( + _In_ PFILTER_ENTRY Entry + ) +{ + return PhConcatStrings2(Entry->Type == FilterInclude ? L"[Include] " : L"[Exclude] ", Entry->Filter->Buffer); +} + +VOID AddEntriesToListBox( + _In_ HWND ListBox, + _In_ PPH_LIST FilterList + ) +{ + ULONG i; + + for (i = 0; i < FilterList->Count; i++) + { + PFILTER_ENTRY entry = FilterList->Items[i]; + + ListBox_AddString(ListBox, PH_AUTO_T(PH_STRING, FormatFilterEntry(entry))->Buffer); + } +} + +PPH_LIST EditingProcessFilterList; +PPH_LIST EditingServiceFilterList; + +LRESULT CALLBACK TextBoxSubclassProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ ULONG_PTR dwRefData + ) +{ + switch (uMsg) + { + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, TextBoxSubclassProc, uIdSubclass); + break; + case WM_GETDLGCODE: + { + if (wParam == VK_RETURN) + return DLGC_WANTALLKEYS; + } + break; + case WM_CHAR: + { + if (wParam == VK_RETURN) + { + SendMessage(GetParent(hWnd), WM_COMMAND, IDC_TEXT_RETURN, 0); + return 0; + } + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +VOID FixControlStates( + _In_ HWND hwndDlg, + _In_ HWND ListBox + ) +{ + ULONG i; + ULONG count; + + i = ListBox_GetCurSel(ListBox); + count = ListBox_GetCount(ListBox); + + EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE), i != LB_ERR); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEUP), i != LB_ERR && i != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_MOVEDOWN), i != LB_ERR && i != count - 1); +} + +INT_PTR HandleCommonMessages( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ HWND ListBox, + _In_ PPH_LIST FilterList + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetWindowSubclass(GetDlgItem(hwndDlg, IDC_TEXT), TextBoxSubclassProc, 0, 0); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_INCLUDE), BST_CHECKED); + + FixControlStates(hwndDlg, ListBox); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_LIST: + { + if (HIWORD(wParam) == LBN_SELCHANGE) + { + ULONG i; + + i = ListBox_GetCurSel(ListBox); + + if (i != LB_ERR) + { + PFILTER_ENTRY entry; + + entry = FilterList->Items[i]; + SetDlgItemText(hwndDlg, IDC_TEXT, entry->Filter->Buffer); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_INCLUDE), + entry->Type == FilterInclude ? BST_CHECKED : BST_UNCHECKED); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_EXCLUDE), + entry->Type == FilterExclude ? BST_CHECKED : BST_UNCHECKED); + } + + FixControlStates(hwndDlg, ListBox); + } + } + break; + case IDC_ADD: + case IDC_TEXT_RETURN: + { + ULONG i; + PPH_STRING string; + PFILTER_ENTRY entry = NULL; + FILTER_TYPE type; + PPH_STRING entryString; + + string = PhGetWindowText(GetDlgItem(hwndDlg, IDC_TEXT)); + + if (string->Length == 0) + { + PhDereferenceObject(string); + return FALSE; + } + + for (i = 0; i < FilterList->Count; i++) + { + entry = FilterList->Items[i]; + + if (PhEqualString(entry->Filter, string, TRUE)) + break; + } + + type = Button_GetCheck(GetDlgItem(hwndDlg, IDC_INCLUDE)) == BST_CHECKED ? FilterInclude : FilterExclude; + + if (i == FilterList->Count) + { + // No existing entry, so add a new one. + + entry = PhAllocate(sizeof(FILTER_ENTRY)); + entry->Type = type; + entry->Filter = string; + PhInsertItemList(FilterList, 0, entry); + + entryString = FormatFilterEntry(entry); + ListBox_InsertString(ListBox, 0, entryString->Buffer); + PhDereferenceObject(entryString); + + ListBox_SetCurSel(ListBox, 0); + } + else + { + entry->Type = type; + PhDereferenceObject(entry->Filter); + entry->Filter = string; + + ListBox_DeleteString(ListBox, i); + entryString = FormatFilterEntry(entry); + ListBox_InsertString(ListBox, i, entryString->Buffer); + PhDereferenceObject(entryString); + + ListBox_SetCurSel(ListBox, i); + } + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_TEXT), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_TEXT), 0, -1); + + FixControlStates(hwndDlg, ListBox); + } + break; + case IDC_REMOVE: + { + ULONG i; + PFILTER_ENTRY entry; + + i = ListBox_GetCurSel(ListBox); + + if (i != LB_ERR) + { + entry = FilterList->Items[i]; + FreeFilterEntry(entry); + PhRemoveItemList(FilterList, i); + ListBox_DeleteString(ListBox, i); + + if (i >= FilterList->Count) + i = FilterList->Count - 1; + + ListBox_SetCurSel(ListBox, i); + + FixControlStates(hwndDlg, ListBox); + } + } + break; + case IDC_MOVEUP: + { + ULONG i; + PFILTER_ENTRY entry; + PPH_STRING entryString; + + i = ListBox_GetCurSel(ListBox); + + if (i != LB_ERR && i != 0) + { + entry = FilterList->Items[i]; + + PhRemoveItemList(FilterList, i); + PhInsertItemList(FilterList, i - 1, entry); + + ListBox_DeleteString(ListBox, i); + entryString = FormatFilterEntry(entry); + ListBox_InsertString(ListBox, i - 1, entryString->Buffer); + PhDereferenceObject(entryString); + + i--; + ListBox_SetCurSel(ListBox, i); + + FixControlStates(hwndDlg, ListBox); + } + } + break; + case IDC_MOVEDOWN: + { + ULONG i; + PFILTER_ENTRY entry; + PPH_STRING entryString; + + i = ListBox_GetCurSel(ListBox); + + if (i != LB_ERR && i != FilterList->Count - 1) + { + entry = FilterList->Items[i]; + + PhRemoveItemList(FilterList, i); + PhInsertItemList(FilterList, i + 1, entry); + + ListBox_DeleteString(ListBox, i); + entryString = FormatFilterEntry(entry); + ListBox_InsertString(ListBox, i + 1, entryString->Buffer); + PhDereferenceObject(entryString); + + i++; + ListBox_SetCurSel(ListBox, i); + + FixControlStates(hwndDlg, ListBox); + } + } + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK ProcessesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + INT_PTR result; + + if (result = HandleCommonMessages(hwndDlg, uMsg, wParam, lParam, + GetDlgItem(hwndDlg, IDC_LIST), EditingProcessFilterList)) + return result; + + switch (uMsg) + { + case WM_INITDIALOG: + { + EditingProcessFilterList = PhCreateList(ProcessFilterList->Count + 10); + CopyFilterList(EditingProcessFilterList, ProcessFilterList); + + AddEntriesToListBox(GetDlgItem(hwndDlg, IDC_LIST), EditingProcessFilterList); + } + break; + case WM_DESTROY: + { + ClearFilterList(EditingProcessFilterList); + PhDereferenceObject(EditingProcessFilterList); + EditingProcessFilterList = NULL; + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + NOTHING; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + PPH_STRING string; + + ClearFilterList(ProcessFilterList); + CopyFilterList(ProcessFilterList, EditingProcessFilterList); + + string = SaveFilterList(ProcessFilterList); + PhSetStringSetting2(SETTING_NAME_PROCESS_LIST, &string->sr); + PhDereferenceObject(string); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK ServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + if (HandleCommonMessages(hwndDlg, uMsg, wParam, lParam, + GetDlgItem(hwndDlg, IDC_LIST), EditingServiceFilterList)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + EditingServiceFilterList = PhCreateList(ServiceFilterList->Count + 10); + CopyFilterList(EditingServiceFilterList, ServiceFilterList); + + AddEntriesToListBox(GetDlgItem(hwndDlg, IDC_LIST), EditingServiceFilterList); + } + break; + case WM_DESTROY: + { + ClearFilterList(EditingServiceFilterList); + PhDereferenceObject(EditingServiceFilterList); + EditingServiceFilterList = NULL; + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + NOTHING; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + PPH_STRING string; + + ClearFilterList(ServiceFilterList); + CopyFilterList(ServiceFilterList, EditingServiceFilterList); + + string = SaveFilterList(ServiceFilterList); + PhSetStringSetting2(SETTING_NAME_SERVICE_LIST, &string->sr); + PhDereferenceObject(string); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK LoggingDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetDlgItemText(hwndDlg, IDC_LOGFILENAME, ((PPH_STRING)PH_AUTO(PhGetStringSetting(SETTING_NAME_LOG_FILENAME)))->Buffer); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Log files (*.txt;*.log)", L"*.txt;*.log" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateSaveFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PH_AUTO(PhGetFileName(PhaGetDlgItemText(hwndDlg, IDC_LOGFILENAME))); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + SetDlgItemText(hwndDlg, IDC_LOGFILENAME, fileName->Buffer); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_APPLY: + { + PhSetStringSetting2(SETTING_NAME_LOG_FILENAME, &PhaGetDlgItemText(hwndDlg, IDC_LOGFILENAME)->sr); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK GrowlDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetDlgItemText(hwndDlg, IDC_LICENSE, PH_AUTO_T(PH_STRING, PhConvertUtf8ToUtf16(gntp_send_license_text))->Buffer); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEGROWL), PhGetIntegerSetting(SETTING_NAME_ENABLE_GROWL) ? BST_CHECKED : BST_UNCHECKED); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_ENABLEGROWL)); + } + return TRUE; + case PSN_APPLY: + { + PhSetIntegerSetting(SETTING_NAME_ENABLE_GROWL, Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEGROWL)) == BST_CHECKED); + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_GROWL)) + RegisterGrowl(FALSE); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedNotifications/resource.h b/plugins/ExtendedNotifications/resource.h new file mode 100644 index 0000000..7222e3a --- /dev/null +++ b/plugins/ExtendedNotifications/resource.h @@ -0,0 +1,33 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ExtendedNotifications.rc +// +#define IDD_PROCESSES 101 +#define IDC_TEXT_RETURN 101 +#define IDD_SERVICES 102 +#define IDD_LOGGING 103 +#define IDD_GROWL 104 +#define IDC_INCLUDE 1006 +#define IDC_EXCLUDE 1007 +#define IDC_REMOVE 1008 +#define IDC_ADD 1009 +#define IDC_MOVEUP 1010 +#define IDC_MOVEDOWN 1011 +#define IDC_LIST 1012 +#define IDC_TEXT 1013 +#define IDC_EDIT1 1014 +#define IDC_LOGFILENAME 1014 +#define IDC_LICENSE 1014 +#define IDC_BROWSE 1015 +#define IDC_ENABLEGROWL 1016 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1017 +#define _APS_NEXT_SYMED_VALUE 102 +#endif +#endif diff --git a/plugins/ExtendedServices/CHANGELOG.txt b/plugins/ExtendedServices/CHANGELOG.txt new file mode 100644 index 0000000..680ed4c --- /dev/null +++ b/plugins/ExtendedServices/CHANGELOG.txt @@ -0,0 +1,38 @@ +1.10 + * Added service protection and SID information + * Added auto-elevation when saving recovery information, triggers and other service settings + +1.9 + * Added new trigger data types + +1.8 + * Fixed some bugs relating to Windows 8 + +1.7 + * Added new triggers for Windows 8 + * Fixed bug when restarting services + +1.6 + * Disabled editing of required privileges for drivers + +1.5 + * Added support for editing triggers + * Added support for editing preshutdown time-out + * Added support for editing required privileges + * Added elevation support for restarting services + +1.4 + * Improved Services submenu when there is only one service + +1.3 + * Added Services submenu for processes + +1.2 + * Added Other tab + +1.1 + * Added Restart menu item for services + * Fixed service handle leak + +1.0 + * Initial release diff --git a/plugins/ExtendedServices/ExtendedServices.rc b/plugins/ExtendedServices/ExtendedServices.rc new file mode 100644 index 0000000..c473be7 --- /dev/null +++ b/plugins/ExtendedServices/ExtendedServices.rc @@ -0,0 +1,350 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,10,0,0 + PRODUCTVERSION 1,10,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Extended Services for Process Hacker" + VALUE "FileVersion", "1.10" + VALUE "InternalName", "ExtendedServices" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "ExtendedServices.dll" + VALUE "ProductName", "Extended Services for Process Hacker" + VALUE "ProductVersion", "1.10" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SRVLIST DIALOGEX 0, 0, 282, 183 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "List" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Static",IDC_SERVICES_LAYOUT,7,19,268,157,NOT WS_VISIBLE + LTEXT "Static",IDC_MESSAGE,7,7,268,8 +END + +IDD_SRVRECOVERY DIALOGEX 0, 0, 282, 183 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Recovery" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "First failure:",IDC_STATIC,7,8,40,8 + COMBOBOX IDC_FIRSTFAILURE,85,7,103,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Second failure:",IDC_STATIC,7,24,49,8 + COMBOBOX IDC_SECONDFAILURE,85,23,103,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Subsequent failures:",IDC_STATIC,7,40,67,8 + COMBOBOX IDC_SUBSEQUENTFAILURES,85,39,103,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Reset fail count after:",IDC_STATIC,7,56,72,8 + EDITTEXT IDC_RESETFAILCOUNT,85,55,40,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "days",IDC_STATIC,130,56,16,8 + LTEXT "Restart service after:",IDC_RESTARTSERVICEAFTER_LABEL,7,72,70,8 + EDITTEXT IDC_RESTARTSERVICEAFTER,85,71,40,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "minutes",IDC_RESTARTSERVICEAFTER_MINUTES,130,72,26,8 + CONTROL "Enable actions for stops with errors",IDC_ENABLEFORERRORSTOPS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,87,129,10 + PUSHBUTTON "Restart computer options...",IDC_RESTARTCOMPUTEROPTIONS,7,100,114,14 + GROUPBOX "Run program",IDC_RUNPROGRAM_GROUP,7,118,268,45 + LTEXT "Program:",IDC_RUNPROGRAM_LABEL,15,132,30,8 + EDITTEXT IDC_RUNPROGRAM,50,131,165,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,220,130,50,14 + LTEXT "Append /fail=%1% to pass the fail count to the program.",IDC_RUNPROGRAM_INFO,15,148,186,8 +END + +IDD_RESTARTCOMP DIALOGEX 0, 0, 245, 137 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Restart the computer after:",IDC_STATIC,7,8,90,8 + EDITTEXT IDC_RESTARTCOMPAFTER,102,7,40,12,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "minutes",IDC_STATIC,147,8,26,8 + CONTROL "Before restarting, send this message to computers on the network:",IDC_ENABLERESTARTMESSAGE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,23,231,10 + EDITTEXT IDC_RESTARTMESSAGE,7,36,231,76,ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Use default message",IDC_USEDEFAULTMESSAGE,7,116,79,14 + DEFPUSHBUTTON "OK",IDOK,134,116,50,14 + PUSHBUTTON "Cancel",IDCANCEL,188,116,50,14 +END + +IDD_SRVRECOVERY2 DIALOGEX 0, 0, 282, 183 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Recovery" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CTEXT "If this service fails, the computer will restart.\nRecovery actions are not supported for this service.",IDC_STATIC,51,80,179,21 +END + +IDD_SRVPROGRESS DIALOGEX 0, 0, 204, 61 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Progress" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Cancel",IDCANCEL,147,40,50,14 + LTEXT "Progress",IDC_MESSAGE,7,7,190,8,SS_ENDELLIPSIS + CONTROL "",IDC_PROGRESS,"msctls_progress32",WS_BORDER,7,21,190,14 +END + +IDD_SRVOTHER DIALOGEX 0, 0, 282, 218 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Other" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Preshutdown timeout:",IDC_STATIC,7,8,72,8 + EDITTEXT IDC_PRESHUTDOWNTIMEOUT,85,7,75,12,ES_AUTOHSCROLL + LTEXT "milliseconds",IDC_STATIC,166,8,38,8 + LTEXT "Service SID:",IDC_STATIC,7,24,40,8 + EDITTEXT IDC_SERVICESID,64,23,211,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "SID type:",IDC_STATIC,7,40,32,8 + COMBOBOX IDC_SIDTYPE,64,39,122,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Protection:",IDC_STATIC,7,57,36,8 + COMBOBOX IDC_PROTECTION,64,56,122,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + LTEXT "Required privileges:",IDC_STATIC,7,77,64,8 + CONTROL "",IDC_PRIVILEGES,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,88,268,79 + PUSHBUTTON "Add...",IDC_ADD,171,171,50,14 + PUSHBUTTON "Remove",IDC_REMOVE,225,171,50,14 +END + +IDD_OPTIONS DIALOGEX 0, 0, 215, 54 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Enable Services submenu for processes",IDC_ENABLESERVICESMENU, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,142,10 + DEFPUSHBUTTON "OK",IDOK,105,33,50,14 + PUSHBUTTON "Cancel",IDCANCEL,158,33,50,14 +END + +IDD_SRVTRIGGER DIALOGEX 0, 0, 330, 196 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Trigger" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Type:",IDC_STATIC,7,9,20,8 + COMBOBOX IDC_TYPE,52,7,271,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Subtype:",IDC_STATIC,7,26,30,8 + COMBOBOX IDC_SUBTYPE,52,24,271,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_SUBTYPECUSTOM,52,41,271,12,ES_AUTOHSCROLL + LTEXT "Action:",IDC_STATIC,7,59,24,8 + COMBOBOX IDC_ACTION,52,58,72,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Data items:",IDC_STATIC,7,76,38,8 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,52,79,217,90 + PUSHBUTTON "New...",IDC_NEW,273,79,50,14 + PUSHBUTTON "Edit...",IDC_EDIT,273,96,50,14 + PUSHBUTTON "Delete",IDC_DELETE,273,113,50,14 + DEFPUSHBUTTON "OK",IDOK,219,175,50,14 + PUSHBUTTON "Cancel",IDCANCEL,273,175,50,14 +END + +IDD_VALUE DIALOGEX 0, 0, 267, 167 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Edit Data" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Values (one per line):",IDC_STATIC,7,7,69,8 + EDITTEXT IDC_VALUES,7,18,253,124,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + DEFPUSHBUTTON "OK",IDOK,156,146,50,14 + PUSHBUTTON "Cancel",IDCANCEL,210,146,50,14 +END + +IDD_SRVTRIGGERS DIALOGEX 0, 0, 282, 218 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Triggers" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_TRIGGERS,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,268,186 + PUSHBUTTON "New...",IDC_NEW,117,197,50,14 + PUSHBUTTON "Edit...",IDC_EDIT,171,197,50,14 + PUSHBUTTON "Delete",IDC_DELETE,225,197,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SRVLIST, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_SRVRECOVERY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_RESTARTCOMP, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 238 + TOPMARGIN, 7 + BOTTOMMARGIN, 130 + END + + IDD_SRVRECOVERY2, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_SRVPROGRESS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 197 + TOPMARGIN, 7 + BOTTOMMARGIN, 54 + END + + IDD_SRVOTHER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 211 + END + + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 208 + TOPMARGIN, 7 + BOTTOMMARGIN, 47 + END + + IDD_SRVTRIGGER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 323 + TOPMARGIN, 7 + BOTTOMMARGIN, 189 + END + + IDD_VALUE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 260 + TOPMARGIN, 7 + BOTTOMMARGIN, 160 + END + + IDD_SRVTRIGGERS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 275 + TOPMARGIN, 7 + BOTTOMMARGIN, 211 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/ExtendedServices/ExtendedServices.vcxproj b/plugins/ExtendedServices/ExtendedServices.vcxproj new file mode 100644 index 0000000..da20f58 --- /dev/null +++ b/plugins/ExtendedServices/ExtendedServices.vcxproj @@ -0,0 +1,87 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD} + ExtendedServices + Win32Proj + ExtendedServices + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/ExtendedServices/ExtendedServices.vcxproj.filters b/plugins/ExtendedServices/ExtendedServices.vcxproj.filters new file mode 100644 index 0000000..c3a0176 --- /dev/null +++ b/plugins/ExtendedServices/ExtendedServices.vcxproj.filters @@ -0,0 +1,59 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/plugins/ExtendedServices/depend.c b/plugins/ExtendedServices/depend.c new file mode 100644 index 0000000..dc15aa5 --- /dev/null +++ b/plugins/ExtendedServices/depend.c @@ -0,0 +1,344 @@ +/* + * Process Hacker Extended Services - + * dependencies and dependents + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _SERVICE_LIST_CONTEXT +{ + HWND ServiceListHandle; + PH_LAYOUT_MANAGER LayoutManager; +} SERVICE_LIST_CONTEXT, *PSERVICE_LIST_CONTEXT; + +LPENUM_SERVICE_STATUS EsEnumDependentServices( + _In_ SC_HANDLE ServiceHandle, + _In_opt_ ULONG State, + _Out_ PULONG Count + ) +{ + LOGICAL result; + PVOID buffer; + ULONG bufferSize; + ULONG returnLength; + ULONG servicesReturned; + + if (!State) + State = SERVICE_STATE_ALL; + + bufferSize = 0x800; + buffer = PhAllocate(bufferSize); + + if (!(result = EnumDependentServices( + ServiceHandle, + State, + buffer, + bufferSize, + &returnLength, + &servicesReturned + ))) + { + if (GetLastError() == ERROR_MORE_DATA) + { + PhFree(buffer); + bufferSize = returnLength; + buffer = PhAllocate(bufferSize); + + result = EnumDependentServices( + ServiceHandle, + State, + buffer, + bufferSize, + &returnLength, + &servicesReturned + ); + } + + if (!result) + { + PhFree(buffer); + return NULL; + } + } + + *Count = servicesReturned; + + return buffer; +} + +VOID EspLayoutServiceListControl( + _In_ HWND hwndDlg, + _In_ HWND ServiceListHandle + ) +{ + RECT rect; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), &rect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&rect, 2); + + MoveWindow( + ServiceListHandle, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + TRUE + ); +} + +INT_PTR CALLBACK EspServiceDependenciesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_LIST_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_LIST_CONTEXT)); + memset(context, 0, sizeof(SERVICE_LIST_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_LIST_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + HWND serviceListHandle; + PPH_LIST serviceList; + SC_HANDLE serviceHandle; + ULONG win32Result = 0; + BOOLEAN success = FALSE; + PPH_SERVICE_ITEM *services; + + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"This service depends on the following services:"); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), NULL, PH_ANCHOR_ALL); + + if (serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_CONFIG)) + { + LPQUERY_SERVICE_CONFIG serviceConfig; + + if (serviceConfig = PhGetServiceConfig(serviceHandle)) + { + PWSTR dependency; + PPH_SERVICE_ITEM dependencyService; + + dependency = serviceConfig->lpDependencies; + serviceList = PH_AUTO(PhCreateList(8)); + success = TRUE; + + if (dependency) + { + ULONG dependencyLength; + + while (TRUE) + { + dependencyLength = (ULONG)PhCountStringZ(dependency); + + if (dependencyLength == 0) + break; + + if (dependency[0] == SC_GROUP_IDENTIFIER) + goto ContinueLoop; + + if (dependencyService = PhReferenceServiceItem(dependency)) + PhAddItemList(serviceList, dependencyService); + +ContinueLoop: + dependency += dependencyLength + 1; + } + } + + services = PhAllocateCopy(serviceList->Items, sizeof(PPH_SERVICE_ITEM) * serviceList->Count); + + serviceListHandle = PhCreateServiceListControl(hwndDlg, services, serviceList->Count); + context->ServiceListHandle = serviceListHandle; + EspLayoutServiceListControl(hwndDlg, serviceListHandle); + ShowWindow(serviceListHandle, SW_SHOW); + + PhFree(serviceConfig); + } + else + { + win32Result = GetLastError(); + } + + CloseServiceHandle(serviceHandle); + } + else + { + win32Result = GetLastError(); + } + + if (!success) + { + SetDlgItemText(hwndDlg, IDC_SERVICES_LAYOUT, PhaConcatStrings2(L"Unable to enumerate dependencies: ", + ((PPH_STRING)PH_AUTO(PhGetWin32Message(win32Result)))->Buffer)->Buffer); + ShowWindow(GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), SW_SHOW); + } + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + PhFree(context); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + + if (context->ServiceListHandle) + EspLayoutServiceListControl(hwndDlg, context->ServiceListHandle); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK EspServiceDependentsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_LIST_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_LIST_CONTEXT)); + memset(context, 0, sizeof(SERVICE_LIST_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_LIST_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + HWND serviceListHandle; + PPH_LIST serviceList; + SC_HANDLE serviceHandle; + ULONG win32Result = 0; + BOOLEAN success = FALSE; + PPH_SERVICE_ITEM *services; + + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"The following services depend on this service:"); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), NULL, PH_ANCHOR_ALL); + + if (serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_ENUMERATE_DEPENDENTS)) + { + LPENUM_SERVICE_STATUS dependentServices; + ULONG numberOfDependentServices; + + if (dependentServices = EsEnumDependentServices(serviceHandle, 0, &numberOfDependentServices)) + { + ULONG i; + PPH_SERVICE_ITEM dependentService; + + serviceList = PH_AUTO(PhCreateList(8)); + success = TRUE; + + for (i = 0; i < numberOfDependentServices; i++) + { + if (dependentService = PhReferenceServiceItem(dependentServices[i].lpServiceName)) + PhAddItemList(serviceList, dependentService); + } + + services = PhAllocateCopy(serviceList->Items, sizeof(PPH_SERVICE_ITEM) * serviceList->Count); + + serviceListHandle = PhCreateServiceListControl(hwndDlg, services, serviceList->Count); + context->ServiceListHandle = serviceListHandle; + EspLayoutServiceListControl(hwndDlg, serviceListHandle); + ShowWindow(serviceListHandle, SW_SHOW); + + PhFree(dependentServices); + } + else + { + win32Result = GetLastError(); + } + + CloseServiceHandle(serviceHandle); + } + else + { + win32Result = GetLastError(); + } + + if (!success) + { + SetDlgItemText(hwndDlg, IDC_SERVICES_LAYOUT, PhaConcatStrings2(L"Unable to enumerate dependents: ", + ((PPH_STRING)PH_AUTO(PhGetWin32Message(win32Result)))->Buffer)->Buffer); + ShowWindow(GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), SW_SHOW); + } + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + PhFree(context); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + + if (context->ServiceListHandle) + EspLayoutServiceListControl(hwndDlg, context->ServiceListHandle); + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedServices/extsrv.h b/plugins/ExtendedServices/extsrv.h new file mode 100644 index 0000000..ff6ee0b --- /dev/null +++ b/plugins/ExtendedServices/extsrv.h @@ -0,0 +1,128 @@ +#ifndef ES_EXTSRV_H +#define ES_EXTSRV_H + +#include +#include + +#include "resource.h" + +// main + +extern PPH_PLUGIN PluginInstance; + +#define PLUGIN_NAME L"ProcessHacker.ExtendedServices" +#define SETTING_NAME_ENABLE_SERVICES_MENU (PLUGIN_NAME L".EnableServicesMenu") + +#define SIP(String, Integer) { (String), (PVOID)(Integer) } + +// depend + +LPENUM_SERVICE_STATUS EsEnumDependentServices( + _In_ SC_HANDLE ServiceHandle, + _In_opt_ ULONG State, + _Out_ PULONG Count + ); + +INT_PTR CALLBACK EspServiceDependenciesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EspServiceDependentsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// options + +VOID EsShowOptionsDialog( + _In_ HWND ParentWindowHandle + ); + +// other + +typedef NTSTATUS (NTAPI *_RtlCreateServiceSid)( + _In_ PUNICODE_STRING ServiceName, + _Out_writes_bytes_opt_(*ServiceSidLength) PSID ServiceSid, + _Inout_ PULONG ServiceSidLength + ); + +INT_PTR CALLBACK EspServiceOtherDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// recovery + +INT_PTR CALLBACK EspServiceRecoveryDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EspServiceRecovery2DlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// srvprgrs + +VOID EsRestartServiceWithProgress( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ SC_HANDLE ServiceHandle + ); + +// trigger + +struct _ES_TRIGGER_CONTEXT; + +struct _ES_TRIGGER_CONTEXT *EsCreateServiceTriggerContext( + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ HWND WindowHandle, + _In_ HWND TriggersLv + ); + +VOID EsDestroyServiceTriggerContext( + _In_ struct _ES_TRIGGER_CONTEXT *Context + ); + +VOID EsLoadServiceTriggerInfo( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _In_ SC_HANDLE ServiceHandle + ); + +BOOLEAN EsSaveServiceTriggerInfo( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _Out_ PULONG Win32Result + ); + +#define ES_TRIGGER_EVENT_NEW 1 +#define ES_TRIGGER_EVENT_EDIT 2 +#define ES_TRIGGER_EVENT_DELETE 3 +#define ES_TRIGGER_EVENT_SELECTIONCHANGED 4 + +VOID EsHandleEventServiceTrigger( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _In_ ULONG Event + ); + +// triggpg + +INT_PTR CALLBACK EspServiceTriggersDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#endif diff --git a/plugins/ExtendedServices/main.c b/plugins/ExtendedServices/main.c new file mode 100644 index 0000000..c36e3b8 --- /dev/null +++ b/plugins/ExtendedServices/main.c @@ -0,0 +1,481 @@ +/* + * Process Hacker Extended Services - + * main program + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +PPH_PLUGIN PluginInstance; +static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ServicePropertiesInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ServiceMenuInitializingCallbackRegistration; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + // Nothing +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EsShowOptionsDialog((HWND)Parameter); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + case ID_SERVICE_GOTOSERVICE: + { + ProcessHacker_SelectTabPage(PhMainWndHandle, 1); + ProcessHacker_SelectServiceItem(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context); + } + break; + case ID_SERVICE_START: + { + PhUiStartService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context); + } + break; + case ID_SERVICE_CONTINUE: + { + PhUiContinueService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context); + } + break; + case ID_SERVICE_PAUSE: + { + PhUiPauseService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context); + } + break; + case ID_SERVICE_STOP: + { + PhUiStopService(PhMainWndHandle, (PPH_SERVICE_ITEM)menuItem->Context); + } + break; + case ID_SERVICE_RESTART: + { + PPH_SERVICE_ITEM serviceItem = menuItem->Context; + SC_HANDLE serviceHandle; + ULONG win32Result = 0; + + if (serviceHandle = PhOpenService(serviceItem->Name->Buffer, SERVICE_QUERY_STATUS)) + { + EsRestartServiceWithProgress(PhMainWndHandle, serviceItem, serviceHandle); + CloseServiceHandle(serviceHandle); + } + else + { + win32Result = GetLastError(); + } + + if (win32Result != 0) + { + PhShowStatus( + PhMainWndHandle, + PhaFormatString(L"Unable to restart %s", serviceItem->Name->Buffer)->Buffer, + 0, + win32Result + ); + } + } + break; + } +} + +static int __cdecl ServiceForServicesMenuCompare( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_SERVICE_ITEM serviceItem1 = *(PPH_SERVICE_ITEM *)elem1; + PPH_SERVICE_ITEM serviceItem2 = *(PPH_SERVICE_ITEM *)elem2; + + return PhCompareString(serviceItem1->Name, serviceItem2->Name, TRUE); +} + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + + if ( + PhGetIntegerSetting(SETTING_NAME_ENABLE_SERVICES_MENU) && + menuInfo->u.Process.NumberOfProcesses == 1 && + menuInfo->u.Process.Processes[0]->ServiceList && + menuInfo->u.Process.Processes[0]->ServiceList->Count != 0 + ) + { + PPH_PROCESS_ITEM processItem; + PPH_EMENU_ITEM servicesMenuItem = NULL; + ULONG enumerationKey; + PPH_SERVICE_ITEM serviceItem; + PPH_LIST serviceList; + ULONG i; + PPH_EMENU_ITEM priorityMenuItem; + ULONG insertIndex; + + processItem = menuInfo->u.Process.Processes[0]; + + // Create a service list so we can sort it. + + serviceList = PH_AUTO(PhCreateList(processItem->ServiceList->Count)); + enumerationKey = 0; + + PhAcquireQueuedLockShared(&processItem->ServiceListLock); + + while (PhEnumPointerList(processItem->ServiceList, &enumerationKey, &serviceItem)) + { + PhReferenceObject(serviceItem); + // We need to use the service item when the user chooses a menu item. + PH_AUTO(serviceItem); + PhAddItemList(serviceList, serviceItem); + } + + PhReleaseQueuedLockShared(&processItem->ServiceListLock); + + // Sort the service list. + qsort(serviceList->Items, serviceList->Count, sizeof(PPH_SERVICE_ITEM), ServiceForServicesMenuCompare); + + // If there is only one service: + // * We use the text "Service (Xxx)". + // * There are no extra submenus. + if (serviceList->Count != 1) + { + servicesMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, 0, L"Services", NULL); + } + + // Create and add a menu item for each service. + + for (i = 0; i < serviceList->Count; i++) + { + PPH_STRING escapedName; + PPH_EMENU_ITEM serviceMenuItem; + PPH_EMENU_ITEM startMenuItem; + PPH_EMENU_ITEM continueMenuItem; + PPH_EMENU_ITEM pauseMenuItem; + PPH_EMENU_ITEM stopMenuItem; + + serviceItem = serviceList->Items[i]; + escapedName = PH_AUTO(PhEscapeStringForMenuPrefix(&serviceItem->Name->sr)); + + if (serviceList->Count == 1) + { + // "Service (Xxx)" + escapedName = PhaFormatString(L"Service (%s)", escapedName->Buffer); + } + + serviceMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, 0, escapedName->Buffer, NULL); + + if (serviceList->Count == 1) + { + // Make this the root submenu that we will insert. + servicesMenuItem = serviceMenuItem; + } + + PhInsertEMenuItem(serviceMenuItem, PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_GOTOSERVICE, L"Go to service", serviceItem), -1); + PhInsertEMenuItem(serviceMenuItem, startMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_START, L"Start", serviceItem), -1); + PhInsertEMenuItem(serviceMenuItem, continueMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_CONTINUE, L"Continue", serviceItem), -1); + PhInsertEMenuItem(serviceMenuItem, pauseMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_PAUSE, L"Pause", serviceItem), -1); + PhInsertEMenuItem(serviceMenuItem, stopMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_STOP, L"Stop", serviceItem), -1); + + // Massive copy and paste from mainwnd.c. + // == START == + +#define SET_MENU_ITEM_ENABLED(MenuItem, Enabled) if (!(Enabled)) (MenuItem)->Flags |= PH_EMENU_DISABLED; + + switch (serviceItem->State) + { + case SERVICE_RUNNING: + { + SET_MENU_ITEM_ENABLED(startMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(pauseMenuItem, + serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + SET_MENU_ITEM_ENABLED(stopMenuItem, + serviceItem->ControlsAccepted & SERVICE_ACCEPT_STOP); + } + break; + case SERVICE_PAUSED: + { + SET_MENU_ITEM_ENABLED(startMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(continueMenuItem, + serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE); + SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(stopMenuItem, + serviceItem->ControlsAccepted & SERVICE_ACCEPT_STOP); + } + break; + case SERVICE_STOPPED: + { + SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(stopMenuItem, FALSE); + } + break; + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_STOP_PENDING: + { + SET_MENU_ITEM_ENABLED(startMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(continueMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(pauseMenuItem, FALSE); + SET_MENU_ITEM_ENABLED(stopMenuItem, FALSE); + } + break; + } + + if (!(serviceItem->ControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE)) + { + PhDestroyEMenuItem(continueMenuItem); + PhDestroyEMenuItem(pauseMenuItem); + } + + // == END == + + if (serviceList->Count != 1) + PhInsertEMenuItem(servicesMenuItem, serviceMenuItem, -1); + } + + // Insert our Services menu after the I/O Priority menu. + + priorityMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"I/O Priority", 0); + + if (!priorityMenuItem) + priorityMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Priority", 0); + + if (priorityMenuItem) + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, priorityMenuItem) + 1; + else + insertIndex = 0; + + PhInsertEMenuItem(menuInfo->Menu, servicesMenuItem, insertIndex); + } +} + +NTAPI ServicePropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_OBJECT_PROPERTIES objectProperties = Parameter; + PROPSHEETPAGE propSheetPage; + PPH_SERVICE_ITEM serviceItem; + + serviceItem = objectProperties->Parameter; + + // Recovery + if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.lParam = (LPARAM)serviceItem; + + if (!(serviceItem->Flags & SERVICE_RUNS_IN_SYSTEM_PROCESS)) + { + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVRECOVERY); + propSheetPage.pfnDlgProc = EspServiceRecoveryDlgProc; + } + else + { + // Services which run in system processes don't support failure actions. + // Create a different page with a message saying this. + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVRECOVERY2); + propSheetPage.pfnDlgProc = EspServiceRecovery2DlgProc; + } + + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Dependencies + if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVLIST); + propSheetPage.pszTitle = L"Dependencies"; + propSheetPage.pfnDlgProc = EspServiceDependenciesDlgProc; + propSheetPage.lParam = (LPARAM)serviceItem; + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Dependents + if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVLIST); + propSheetPage.pszTitle = L"Dependents"; + propSheetPage.pfnDlgProc = EspServiceDependentsDlgProc; + propSheetPage.lParam = (LPARAM)serviceItem; + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Other + if (WindowsVersion >= WINDOWS_7 && objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVTRIGGERS); + propSheetPage.pszTitle = L"Triggers"; + propSheetPage.pfnDlgProc = EspServiceTriggersDlgProc; + propSheetPage.lParam = (LPARAM)serviceItem; + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Other + if (WindowsVersion >= WINDOWS_VISTA && objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVOTHER); + propSheetPage.pszTitle = L"Other"; + propSheetPage.pfnDlgProc = EspServiceOtherDlgProc; + propSheetPage.lParam = (LPARAM)serviceItem; + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } +} + +VOID NTAPI ServiceMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_EMENU_ITEM menuItem; + ULONG indexOfMenuItem; + + if ( + menuInfo->u.Service.NumberOfServices == 1 && + (menuInfo->u.Service.Services[0]->State == SERVICE_RUNNING || menuInfo->u.Service.Services[0]->State == SERVICE_PAUSED) + ) + { + // Insert our Restart menu item after the Stop menu item. + + menuItem = PhFindEMenuItem(menuInfo->Menu, PH_EMENU_FIND_STARTSWITH, L"Stop", 0); + + if (menuItem) + indexOfMenuItem = PhIndexOfEMenuItem(menuInfo->Menu, menuItem); + else + indexOfMenuItem = -1; + + PhInsertEMenuItem( + menuInfo->Menu, + PhPluginCreateEMenuItem(PluginInstance, 0, ID_SERVICE_RESTART, L"Restart", menuInfo->u.Service.Services[0]), + indexOfMenuItem + 1 + ); + } +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_ENABLE_SERVICES_MENU, L"1" } + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Extended Services"; + info->Author = L"wj32"; + info->Description = L"Extends service management capabilities."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1113"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + ProcessMenuInitializingCallback, + NULL, + &ProcessMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackServicePropertiesInitializing), + ServicePropertiesInitializingCallback, + NULL, + &ServicePropertiesInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackServiceMenuInitializing), + ServiceMenuInitializingCallback, + NULL, + &ServiceMenuInitializingCallbackRegistration + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/ExtendedServices/options.c b/plugins/ExtendedServices/options.c new file mode 100644 index 0000000..75ae889 --- /dev/null +++ b/plugins/ExtendedServices/options.c @@ -0,0 +1,72 @@ +/* + * Process Hacker Extended Services - + * options dialog + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLESERVICESMENU), PhGetIntegerSetting(SETTING_NAME_ENABLE_SERVICES_MENU) ? BST_CHECKED : BST_UNCHECKED); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PhSetIntegerSetting(SETTING_NAME_ENABLE_SERVICES_MENU, + Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLESERVICESMENU)) == BST_CHECKED); + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} + +VOID EsShowOptionsDialog( + _In_ HWND ParentWindowHandle + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + ParentWindowHandle, + OptionsDlgProc + ); +} \ No newline at end of file diff --git a/plugins/ExtendedServices/other.c b/plugins/ExtendedServices/other.c new file mode 100644 index 0000000..5b7957a --- /dev/null +++ b/plugins/ExtendedServices/other.c @@ -0,0 +1,732 @@ +/* + * Process Hacker Extended Services - + * other information + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _SERVICE_OTHER_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + + struct + { + ULONG Ready : 1; + ULONG Dirty : 1; + ULONG PreshutdownTimeoutValid : 1; + ULONG RequiredPrivilegesValid : 1; + ULONG SidTypeValid : 1; + ULONG LaunchProtectedValid : 1; + }; + HWND PrivilegesLv; + PPH_LIST PrivilegeList; + + ULONG OriginalLaunchProtected; +} SERVICE_OTHER_CONTEXT, *PSERVICE_OTHER_CONTEXT; + +static _RtlCreateServiceSid RtlCreateServiceSid_I = NULL; + +static PH_KEY_VALUE_PAIR EspServiceSidTypePairs[] = +{ + SIP(L"None", SERVICE_SID_TYPE_NONE), + SIP(L"Restricted", SERVICE_SID_TYPE_RESTRICTED), + SIP(L"Unrestricted", SERVICE_SID_TYPE_UNRESTRICTED) +}; + +static PH_KEY_VALUE_PAIR EspServiceLaunchProtectedPairs[] = +{ + SIP(L"None", SERVICE_LAUNCH_PROTECTED_NONE), + SIP(L"Full (Windows)", SERVICE_LAUNCH_PROTECTED_WINDOWS), + SIP(L"Light (Windows)", SERVICE_LAUNCH_PROTECTED_WINDOWS_LIGHT), + SIP(L"Light (Antimalware)", SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT) +}; + +static WCHAR *EspServiceSidTypeStrings[3] = { L"None", L"Restricted", L"Unrestricted" }; +static WCHAR *EspServiceLaunchProtectedStrings[4] = { L"None", L"Full (Windows)", L"Light (Windows)", L"Light (Antimalware)" }; + +PWSTR EspGetServiceSidTypeString( + _In_ ULONG SidType + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + EspServiceSidTypePairs, + sizeof(EspServiceSidTypePairs), + SidType, + &string + )) + return string; + else + return L"Unknown"; +} + +ULONG EspGetServiceSidTypeInteger( + _In_ PWSTR SidType + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs( + EspServiceSidTypePairs, + sizeof(EspServiceSidTypePairs), + SidType, + &integer + )) + return integer; + else + return -1; +} + +PWSTR EspGetServiceLaunchProtectedString( + _In_ ULONG LaunchProtected + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs( + EspServiceLaunchProtectedPairs, + sizeof(EspServiceLaunchProtectedPairs), + LaunchProtected, + &string + )) + return string; + else + return L"Unknown"; +} + +ULONG EspGetServiceLaunchProtectedInteger( + _In_ PWSTR LaunchProtected + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs( + EspServiceLaunchProtectedPairs, + sizeof(EspServiceLaunchProtectedPairs), + LaunchProtected, + &integer + )) + return integer; + else + return -1; +} + +NTSTATUS EspLoadOtherInfo( + _In_ HWND hwndDlg, + _In_ PSERVICE_OTHER_CONTEXT Context + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SC_HANDLE serviceHandle; + ULONG returnLength; + SERVICE_PRESHUTDOWN_INFO preshutdownInfo; + LPSERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo; + SERVICE_SID_INFO sidInfo; + SERVICE_LAUNCH_PROTECTED_INFO launchProtectedInfo; + + if (!(serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG))) + return NTSTATUS_FROM_WIN32(GetLastError()); + + // Preshutdown timeout + + if (QueryServiceConfig2(serviceHandle, + SERVICE_CONFIG_PRESHUTDOWN_INFO, + (PBYTE)&preshutdownInfo, + sizeof(SERVICE_PRESHUTDOWN_INFO), + &returnLength + )) + { + SetDlgItemInt(hwndDlg, IDC_PRESHUTDOWNTIMEOUT, preshutdownInfo.dwPreshutdownTimeout, FALSE); + Context->PreshutdownTimeoutValid = TRUE; + } + + // Required privileges + + if (requiredPrivilegesInfo = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO)) + { + PWSTR privilege; + ULONG privilegeLength; + INT lvItemIndex; + PH_STRINGREF privilegeSr; + PPH_STRING privilegeString; + PPH_STRING displayName; + + privilege = requiredPrivilegesInfo->pmszRequiredPrivileges; + + if (privilege) + { + while (TRUE) + { + privilegeLength = (ULONG)PhCountStringZ(privilege); + + if (privilegeLength == 0) + break; + + privilegeString = PhCreateStringEx(privilege, privilegeLength * sizeof(WCHAR)); + PhAddItemList(Context->PrivilegeList, privilegeString); + + lvItemIndex = PhAddListViewItem(Context->PrivilegesLv, MAXINT, privilege, privilegeString); + privilegeSr.Buffer = privilege; + privilegeSr.Length = privilegeLength * sizeof(WCHAR); + + if (PhLookupPrivilegeDisplayName(&privilegeSr, &displayName)) + { + PhSetListViewSubItem(Context->PrivilegesLv, lvItemIndex, 1, displayName->Buffer); + PhDereferenceObject(displayName); + } + + privilege += privilegeLength + 1; + } + } + + ExtendedListView_SortItems(Context->PrivilegesLv); + + PhFree(requiredPrivilegesInfo); + Context->RequiredPrivilegesValid = TRUE; + } + + // SID type + + if (QueryServiceConfig2(serviceHandle, + SERVICE_CONFIG_SERVICE_SID_INFO, + (PBYTE)&sidInfo, + sizeof(SERVICE_SID_INFO), + &returnLength + )) + { + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_SIDTYPE), + EspGetServiceSidTypeString(sidInfo.dwServiceSidType), FALSE); + Context->SidTypeValid = TRUE; + } + + // Launch protected + + if (QueryServiceConfig2(serviceHandle, + SERVICE_CONFIG_LAUNCH_PROTECTED, + (PBYTE)&launchProtectedInfo, + sizeof(SERVICE_LAUNCH_PROTECTED_INFO), + &returnLength + )) + { + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_PROTECTION), + EspGetServiceLaunchProtectedString(launchProtectedInfo.dwLaunchProtected), FALSE); + Context->LaunchProtectedValid = TRUE; + Context->OriginalLaunchProtected = launchProtectedInfo.dwLaunchProtected; + } + + CloseServiceHandle(serviceHandle); + + return status; +} + +PPH_STRING EspGetServiceSidString( + _In_ PPH_STRINGREF ServiceName + ) +{ + PSID serviceSid = NULL; + UNICODE_STRING serviceNameUs; + ULONG serviceSidLength = 0; + PPH_STRING sidString = NULL; + + if (!RtlCreateServiceSid_I) + { + if (!(RtlCreateServiceSid_I = PhGetModuleProcAddress(L"ntdll.dll", "RtlCreateServiceSid"))) + return NULL; + } + + PhStringRefToUnicodeString(ServiceName, &serviceNameUs); + + if (RtlCreateServiceSid_I(&serviceNameUs, serviceSid, &serviceSidLength) == STATUS_BUFFER_TOO_SMALL) + { + serviceSid = PhAllocate(serviceSidLength); + + if (NT_SUCCESS(RtlCreateServiceSid_I(&serviceNameUs, serviceSid, &serviceSidLength))) + sidString = PhSidToStringSid(serviceSid); + + PhFree(serviceSid); + } + + return sidString; +} + +BOOLEAN EspChangeServiceConfig2( + _In_ PWSTR ServiceName, + _In_opt_ SC_HANDLE ServiceHandle, + _In_ ULONG InfoLevel, + _In_ PVOID Info + ) +{ + if (ServiceHandle) + { + return !!ChangeServiceConfig2(ServiceHandle, InfoLevel, Info); + } + else + { + NTSTATUS status; + + if (NT_SUCCESS(status = PhSvcCallChangeServiceConfig2(ServiceName, InfoLevel, Info))) + { + return TRUE; + } + else + { + SetLastError(PhNtStatusToDosError(status)); + return FALSE; + } + } +} + +static int __cdecl PrivilegeNameCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PWSTR string1 = *(PWSTR *)elem1; + PWSTR string2 = *(PWSTR *)elem2; + + return PhCompareStringZ(string1, string2, TRUE); +} + +INT_PTR CALLBACK EspServiceOtherDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_OTHER_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_OTHER_CONTEXT)); + memset(context, 0, sizeof(SERVICE_OTHER_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_OTHER_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + HWND privilegesLv; + + context->ServiceItem = serviceItem; + + context->PrivilegesLv = privilegesLv = GetDlgItem(hwndDlg, IDC_PRIVILEGES); + PhSetListViewStyle(privilegesLv, FALSE, TRUE); + PhSetControlTheme(privilegesLv, L"explorer"); + PhAddListViewColumn(privilegesLv, 0, 0, 0, LVCFMT_LEFT, 140, L"Name"); + PhAddListViewColumn(privilegesLv, 1, 1, 1, LVCFMT_LEFT, 220, L"Display name"); + PhSetExtendedListView(privilegesLv); + + context->PrivilegeList = PhCreateList(32); + + if (context->ServiceItem->Type == SERVICE_KERNEL_DRIVER || context->ServiceItem->Type == SERVICE_FILE_SYSTEM_DRIVER) + { + // Drivers don't support required privileges. + EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE); + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE), FALSE); + + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_SIDTYPE), + EspServiceSidTypeStrings, sizeof(EspServiceSidTypeStrings) / sizeof(PWSTR)); + PhAddComboBoxStrings(GetDlgItem(hwndDlg, IDC_PROTECTION), + EspServiceLaunchProtectedStrings, sizeof(EspServiceLaunchProtectedStrings) / sizeof(PWSTR)); + + if (WindowsVersion < WINDOWS_8_1) + EnableWindow(GetDlgItem(hwndDlg, IDC_PROTECTION), FALSE); + + SetDlgItemText(hwndDlg, IDC_SERVICESID, + PhGetStringOrDefault(PH_AUTO(EspGetServiceSidString(&serviceItem->Name->sr)), L"N/A")); + + status = EspLoadOtherInfo(hwndDlg, context); + + if (!NT_SUCCESS(status)) + { + PhShowWarning(hwndDlg, L"Unable to query service information: %s", + ((PPH_STRING)PH_AUTO(PhGetNtMessage(status)))->Buffer); + } + + context->Ready = TRUE; + } + break; + case WM_DESTROY: + { + if (context->PrivilegeList) + { + PhDereferenceObjects(context->PrivilegeList->Items, context->PrivilegeList->Count); + PhDereferenceObject(context->PrivilegeList); + } + + PhFree(context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_ADD: + { + NTSTATUS status; + LSA_HANDLE policyHandle; + LSA_ENUMERATION_HANDLE enumContext; + PPOLICY_PRIVILEGE_DEFINITION buffer; + ULONG count; + ULONG i; + PPH_LIST choices; + PPH_STRING selectedChoice = NULL; + + choices = PH_AUTO(PhCreateList(100)); + + if (!NT_SUCCESS(status = PhOpenLsaPolicy(&policyHandle, POLICY_VIEW_LOCAL_INFORMATION, NULL))) + { + PhShowStatus(hwndDlg, L"Unable to open LSA policy", status, 0); + break; + } + + enumContext = 0; + + while (TRUE) + { + status = LsaEnumeratePrivileges( + policyHandle, + &enumContext, + &buffer, + 0x100, + &count + ); + + if (status == STATUS_NO_MORE_ENTRIES) + break; + if (!NT_SUCCESS(status)) + break; + + for (i = 0; i < count; i++) + { + PhAddItemList(choices, PhaCreateStringEx(buffer[i].Name.Buffer, buffer[i].Name.Length)->Buffer); + } + + LsaFreeMemory(buffer); + } + + LsaClose(policyHandle); + + qsort(choices->Items, choices->Count, sizeof(PWSTR), PrivilegeNameCompareFunction); + + while (PhaChoiceDialog( + hwndDlg, + L"Add privilege", + L"Select a privilege to add:", + (PWSTR *)choices->Items, + choices->Count, + NULL, + PH_CHOICE_DIALOG_CHOICE, + &selectedChoice, + NULL, + NULL + )) + { + BOOLEAN found = FALSE; + PPH_STRING privilegeString; + INT lvItemIndex; + PPH_STRING displayName; + + // Check for duplicates. + for (i = 0; i < context->PrivilegeList->Count; i++) + { + if (PhEqualString(context->PrivilegeList->Items[i], selectedChoice, FALSE)) + { + found = TRUE; + break; + } + } + + if (found) + { + if (PhShowMessage( + hwndDlg, + MB_OKCANCEL | MB_ICONERROR, + L"The selected privilege has already been added." + ) == IDOK) + { + continue; + } + else + { + break; + } + } + + PhSetReference(&privilegeString, selectedChoice); + PhAddItemList(context->PrivilegeList, privilegeString); + + lvItemIndex = PhAddListViewItem(context->PrivilegesLv, MAXINT, privilegeString->Buffer, privilegeString); + + if (PhLookupPrivilegeDisplayName(&privilegeString->sr, &displayName)) + { + PhSetListViewSubItem(context->PrivilegesLv, lvItemIndex, 1, displayName->Buffer); + PhDereferenceObject(displayName); + } + + ExtendedListView_SortItems(context->PrivilegesLv); + + context->Dirty = TRUE; + context->RequiredPrivilegesValid = TRUE; + + break; + } + } + break; + case IDC_REMOVE: + { + INT lvItemIndex; + PPH_STRING privilegeString; + ULONG index; + + lvItemIndex = ListView_GetNextItem(context->PrivilegesLv, -1, LVNI_SELECTED); + + if (lvItemIndex != -1 && PhGetListViewItemParam(context->PrivilegesLv, lvItemIndex, (PVOID *)&privilegeString)) + { + index = PhFindItemList(context->PrivilegeList, privilegeString); + + if (index != -1) + { + PhDereferenceObject(privilegeString); + PhRemoveItemList(context->PrivilegeList, index); + PhRemoveListViewItem(context->PrivilegesLv, lvItemIndex); + + context->Dirty = TRUE; + context->RequiredPrivilegesValid = TRUE; + } + } + } + break; + } + + switch (HIWORD(wParam)) + { + case EN_CHANGE: + case CBN_SELCHANGE: + { + if (context->Ready) + { + context->Dirty = TRUE; + + switch (LOWORD(wParam)) + { + case IDC_PRESHUTDOWNTIMEOUT: + context->PreshutdownTimeoutValid = TRUE; + break; + case IDC_SIDTYPE: + context->SidTypeValid = TRUE; + break; + case IDC_PROTECTION: + context->LaunchProtectedValid = TRUE; + break; + } + } + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_KILLACTIVE: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); + } + return TRUE; + case PSN_APPLY: + { + SC_HANDLE serviceHandle = NULL; + ULONG win32Result = 0; + BOOLEAN connectedToPhSvc = FALSE; + PPH_STRING launchProtectedString; + ULONG launchProtected; + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + + launchProtectedString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_PROTECTION))); + launchProtected = EspGetServiceLaunchProtectedInteger(launchProtectedString->Buffer); + + if (context->LaunchProtectedValid && launchProtected != 0 && launchProtected != context->OriginalLaunchProtected) + { + if (PhShowMessage( + hwndDlg, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2, + L"Setting service protection will prevent the service from being controlled, modified, or deleted. Do you want to continue?" + ) == IDNO) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + return TRUE; + } + } + + if (context->Dirty) + { + SERVICE_PRESHUTDOWN_INFO preshutdownInfo; + SERVICE_REQUIRED_PRIVILEGES_INFO requiredPrivilegesInfo; + SERVICE_SID_INFO sidInfo; + SERVICE_LAUNCH_PROTECTED_INFO launchProtectedInfo; + + if (!(serviceHandle = PhOpenService(context->ServiceItem->Name->Buffer, SERVICE_CHANGE_CONFIG))) + { + win32Result = GetLastError(); + + if (win32Result == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated) + { + // Elevate using phsvc. + if (PhUiConnectToPhSvc(hwndDlg, FALSE)) + { + win32Result = 0; + connectedToPhSvc = TRUE; + } + else + { + // User cancelled elevation. + win32Result = ERROR_CANCELLED; + goto Done; + } + } + else + { + goto Done; + } + } + + if (context->PreshutdownTimeoutValid) + { + preshutdownInfo.dwPreshutdownTimeout = GetDlgItemInt(hwndDlg, IDC_PRESHUTDOWNTIMEOUT, NULL, FALSE); + + if (!EspChangeServiceConfig2(context->ServiceItem->Name->Buffer, serviceHandle, + SERVICE_CONFIG_PRESHUTDOWN_INFO, &preshutdownInfo)) + { + win32Result = GetLastError(); + } + } + + if (context->RequiredPrivilegesValid) + { + PH_STRING_BUILDER sb; + ULONG i; + + PhInitializeStringBuilder(&sb, 100); + + for (i = 0; i < context->PrivilegeList->Count; i++) + { + PhAppendStringBuilder(&sb, &((PPH_STRING)context->PrivilegeList->Items[i])->sr); + PhAppendCharStringBuilder(&sb, 0); + } + + requiredPrivilegesInfo.pmszRequiredPrivileges = sb.String->Buffer; + + if (win32Result == 0 && !EspChangeServiceConfig2(context->ServiceItem->Name->Buffer, serviceHandle, + SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, &requiredPrivilegesInfo)) + { + win32Result = GetLastError(); + } + + PhDeleteStringBuilder(&sb); + } + + if (context->SidTypeValid) + { + PPH_STRING sidTypeString; + + sidTypeString = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_SIDTYPE))); + sidInfo.dwServiceSidType = EspGetServiceSidTypeInteger(sidTypeString->Buffer); + + if (win32Result == 0 && !EspChangeServiceConfig2(context->ServiceItem->Name->Buffer, serviceHandle, + SERVICE_CONFIG_SERVICE_SID_INFO, &sidInfo)) + { + win32Result = GetLastError(); + } + } + + if (context->LaunchProtectedValid) + { + launchProtectedInfo.dwLaunchProtected = launchProtected; + + if (!EspChangeServiceConfig2(context->ServiceItem->Name->Buffer, serviceHandle, + SERVICE_CONFIG_LAUNCH_PROTECTED, &launchProtectedInfo)) + { + // For now, ignore errors here. + // win32Result = GetLastError(); + } + } + +Done: + if (connectedToPhSvc) + PhUiDisconnectFromPhSvc(); + if (serviceHandle) + CloseServiceHandle(serviceHandle); + + if (win32Result != 0) + { + if (win32Result == ERROR_CANCELLED || PhShowMessage( + hwndDlg, + MB_ICONERROR | MB_RETRYCANCEL, + L"Unable to change service information: %s", + ((PPH_STRING)PH_AUTO(PhGetWin32Message(win32Result)))->Buffer + ) == IDRETRY) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + } + } + + return TRUE; + } + break; + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == context->PrivilegesLv) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_REMOVE), ListView_GetSelectedCount(context->PrivilegesLv) == 1); + } + } + break; + } + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/ExtendedServices/recovery.c b/plugins/ExtendedServices/recovery.c new file mode 100644 index 0000000..b917b03 --- /dev/null +++ b/plugins/ExtendedServices/recovery.c @@ -0,0 +1,709 @@ +/* + * Process Hacker Extended Services - + * recovery information + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _SERVICE_RECOVERY_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + + ULONG NumberOfActions; + BOOLEAN EnableFlagCheckBox; + ULONG RebootAfter; // in ms + PPH_STRING RebootMessage; + + BOOLEAN Ready; + BOOLEAN Dirty; +} SERVICE_RECOVERY_CONTEXT, *PSERVICE_RECOVERY_CONTEXT; + +static PH_KEY_VALUE_PAIR ServiceActionPairs[] = +{ + SIP(L"Take no action", SC_ACTION_NONE), + SIP(L"Restart the service", SC_ACTION_RESTART), + SIP(L"Run a program", SC_ACTION_RUN_COMMAND), + SIP(L"Restart the computer", SC_ACTION_REBOOT) +}; + +INT_PTR CALLBACK RestartComputerDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EspAddServiceActionStrings( + _In_ HWND ComboBoxHandle + ) +{ + ULONG i; + + for (i = 0; i < sizeof(ServiceActionPairs) / sizeof(PH_KEY_VALUE_PAIR); i++) + ComboBox_AddString(ComboBoxHandle, (PWSTR)ServiceActionPairs[i].Key); + + PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE); +} + +SC_ACTION_TYPE EspStringToServiceAction( + _In_ PWSTR String + ) +{ + ULONG integer; + + if (PhFindIntegerSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), String, &integer)) + return integer; + else + return 0; +} + +PWSTR EspServiceActionToString( + _In_ SC_ACTION_TYPE ActionType + ) +{ + PWSTR string; + + if (PhFindStringSiKeyValuePairs(ServiceActionPairs, sizeof(ServiceActionPairs), ActionType, &string)) + return string; + else + return NULL; +} + +SC_ACTION_TYPE ComboBoxToServiceAction( + _In_ HWND ComboBoxHandle + ) +{ + PPH_STRING string; + + string = PH_AUTO(PhGetComboBoxString(ComboBoxHandle, ComboBox_GetCurSel(ComboBoxHandle))); + + if (!string) + return SC_ACTION_NONE; + + return EspStringToServiceAction(string->Buffer); +} + +VOID ServiceActionToComboBox( + _In_ HWND ComboBoxHandle, + _In_ SC_ACTION_TYPE ActionType + ) +{ + PWSTR string; + + if (string = EspServiceActionToString(ActionType)) + PhSelectComboBoxString(ComboBoxHandle, string, FALSE); + else + PhSelectComboBoxString(ComboBoxHandle, (PWSTR)ServiceActionPairs[0].Key, FALSE); +} + +VOID EspFixControls( + _In_ HWND hwndDlg, + _In_ PSERVICE_RECOVERY_CONTEXT Context + ) +{ + SC_ACTION_TYPE action1; + SC_ACTION_TYPE action2; + SC_ACTION_TYPE actionS; + BOOLEAN enableRestart; + BOOLEAN enableReboot; + BOOLEAN enableCommand; + + action1 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); + action2 = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); + actionS = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); + + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), Context->EnableFlagCheckBox); + + enableRestart = action1 == SC_ACTION_RESTART || action2 == SC_ACTION_RESTART || actionS == SC_ACTION_RESTART; + enableReboot = action1 == SC_ACTION_REBOOT || action2 == SC_ACTION_REBOOT || actionS == SC_ACTION_REBOOT; + enableCommand = action1 == SC_ACTION_RUN_COMMAND || action2 == SC_ACTION_RUN_COMMAND || actionS == SC_ACTION_RUN_COMMAND; + + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_LABEL), enableRestart); + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER), enableRestart); + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTSERVICEAFTER_MINUTES), enableRestart); + + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTCOMPUTEROPTIONS), enableReboot); + + EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_GROUP), enableCommand); + EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_LABEL), enableCommand); + EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM), enableCommand); + EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE), enableCommand); + EnableWindow(GetDlgItem(hwndDlg, IDC_RUNPROGRAM_INFO), enableCommand); +} + +NTSTATUS EspLoadRecoveryInfo( + _In_ HWND hwndDlg, + _In_ PSERVICE_RECOVERY_CONTEXT Context + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SC_HANDLE serviceHandle; + LPSERVICE_FAILURE_ACTIONS failureActions; + SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; + SC_ACTION_TYPE lastType; + ULONG returnLength; + ULONG i; + + if (!(serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG))) + return NTSTATUS_FROM_WIN32(GetLastError()); + + if (!(failureActions = PhQueryServiceVariableSize(serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS))) + { + CloseServiceHandle(serviceHandle); + return NTSTATUS_FROM_WIN32(GetLastError()); + } + + // Failure action types + + Context->NumberOfActions = failureActions->cActions; + + if (failureActions->cActions != 0 && failureActions->cActions != 3) + status = STATUS_SOME_NOT_MAPPED; + + // If failure actions are not defined for a particular fail count, the + // last failure action is used. Here we duplicate this behaviour when there + // are fewer than 3 failure actions. + lastType = SC_ACTION_NONE; + + ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE), + failureActions->cActions >= 1 ? (lastType = failureActions->lpsaActions[0].Type) : lastType); + ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SECONDFAILURE), + failureActions->cActions >= 2 ? (lastType = failureActions->lpsaActions[1].Type) : lastType); + ServiceActionToComboBox(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES), + failureActions->cActions >= 3 ? (lastType = failureActions->lpsaActions[2].Type) : lastType); + + // Reset fail count after + + SetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, failureActions->dwResetPeriod / (60 * 60 * 24), FALSE); // s to days + + // Restart service after + + SetDlgItemText(hwndDlg, IDC_RESTARTSERVICEAFTER, L"1"); + + for (i = 0; i < failureActions->cActions; i++) + { + if (failureActions->lpsaActions[i].Type == SC_ACTION_RESTART) + { + if (failureActions->lpsaActions[i].Delay != 0) + { + SetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER, + failureActions->lpsaActions[i].Delay / (1000 * 60), FALSE); // ms to min + } + + break; + } + } + + // Enable actions for stops with errors + + // This is Vista and above only. + if (WindowsVersion >= WINDOWS_VISTA && QueryServiceConfig2( + serviceHandle, + SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, + (BYTE *)&failureActionsFlag, + sizeof(SERVICE_FAILURE_ACTIONS_FLAG), + &returnLength + )) + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), + failureActionsFlag.fFailureActionsOnNonCrashFailures ? BST_CHECKED : BST_UNCHECKED); + Context->EnableFlagCheckBox = TRUE; + } + else + { + Context->EnableFlagCheckBox = FALSE; + } + + // Restart computer options + + Context->RebootAfter = 1 * 1000 * 60; + + for (i = 0; i < failureActions->cActions; i++) + { + if (failureActions->lpsaActions[i].Type == SC_ACTION_REBOOT) + { + if (failureActions->lpsaActions[i].Delay != 0) + Context->RebootAfter = failureActions->lpsaActions[i].Delay; + + break; + } + } + + if (failureActions->lpRebootMsg && failureActions->lpRebootMsg[0] != 0) + PhMoveReference(&Context->RebootMessage, PhCreateString(failureActions->lpRebootMsg)); + else + PhClearReference(&Context->RebootMessage); + + // Run program + + SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, failureActions->lpCommand); + + PhFree(failureActions); + CloseServiceHandle(serviceHandle); + + return status; +} + +INT_PTR CALLBACK EspServiceRecoveryDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_RECOVERY_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_RECOVERY_CONTEXT)); + memset(context, 0, sizeof(SERVICE_RECOVERY_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + + context->ServiceItem = serviceItem; + + EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); + EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); + EspAddServiceActionStrings(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); + + status = EspLoadRecoveryInfo(hwndDlg, context); + + if (status == STATUS_SOME_NOT_MAPPED) + { + if (context->NumberOfActions > 3) + { + PhShowWarning( + hwndDlg, + L"The service has %lu failure actions configured, but this program only supports editing 3. " + L"If you save the recovery information using this program, the additional failure actions will be lost.", + context->NumberOfActions + ); + } + } + else if (!NT_SUCCESS(status)) + { + SetDlgItemText(hwndDlg, IDC_RESETFAILCOUNT, L"0"); + + if (WindowsVersion >= WINDOWS_VISTA) + { + context->EnableFlagCheckBox = TRUE; + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS), TRUE); + } + + PhShowWarning(hwndDlg, L"Unable to query service recovery information: %s", + ((PPH_STRING)PH_AUTO(PhGetNtMessage(status)))->Buffer); + } + + EspFixControls(hwndDlg, context); + + context->Ready = TRUE; + } + break; + case WM_DESTROY: + { + PhClearReference(&context->RebootMessage); + PhFree(context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_FIRSTFAILURE: + case IDC_SECONDFAILURE: + case IDC_SUBSEQUENTFAILURES: + { + if (HIWORD(wParam) == CBN_SELCHANGE) + { + EspFixControls(hwndDlg, context); + } + } + break; + case IDC_RESTARTCOMPUTEROPTIONS: + { + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_RESTARTCOMP), + hwndDlg, + RestartComputerDlgProc, + (LPARAM)context + ); + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Executable files (*.exe;*.cmd;*.bat)", L"*.exe;*.cmd;*.bat" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + SetDlgItemText(hwndDlg, IDC_RUNPROGRAM, fileName->Buffer); + } + + PhFreeFileDialog(fileDialog); + } + break; + case IDC_ENABLEFORERRORSTOPS: + { + context->Dirty = TRUE; + } + break; + } + + switch (HIWORD(wParam)) + { + case EN_CHANGE: + case CBN_SELCHANGE: + { + if (context->Ready) + context->Dirty = TRUE; + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_KILLACTIVE: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); + } + return TRUE; + case PSN_APPLY: + { + NTSTATUS status; + PPH_SERVICE_ITEM serviceItem = context->ServiceItem; + SC_HANDLE serviceHandle; + ULONG restartServiceAfter; + SERVICE_FAILURE_ACTIONS failureActions; + SC_ACTION actions[3]; + ULONG i; + BOOLEAN enableRestart = FALSE; + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + + if (!context->Dirty) + { + return TRUE; + } + + // Build the failure actions structure. + + failureActions.dwResetPeriod = GetDlgItemInt(hwndDlg, IDC_RESETFAILCOUNT, NULL, FALSE) * 60 * 60 * 24; + failureActions.lpRebootMsg = PhGetStringOrEmpty(context->RebootMessage); + failureActions.lpCommand = PhaGetDlgItemText(hwndDlg, IDC_RUNPROGRAM)->Buffer; + failureActions.cActions = 3; + failureActions.lpsaActions = actions; + + actions[0].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_FIRSTFAILURE)); + actions[1].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SECONDFAILURE)); + actions[2].Type = ComboBoxToServiceAction(GetDlgItem(hwndDlg, IDC_SUBSEQUENTFAILURES)); + + restartServiceAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTSERVICEAFTER, NULL, FALSE) * 1000 * 60; + + for (i = 0; i < 3; i++) + { + switch (actions[i].Type) + { + case SC_ACTION_RESTART: + actions[i].Delay = restartServiceAfter; + enableRestart = TRUE; + break; + case SC_ACTION_REBOOT: + actions[i].Delay = context->RebootAfter; + break; + case SC_ACTION_RUN_COMMAND: + actions[i].Delay = 0; + break; + } + } + + // Try to save the changes. + + serviceHandle = PhOpenService( + serviceItem->Name->Buffer, + SERVICE_CHANGE_CONFIG | (enableRestart ? SERVICE_START : 0) // SC_ACTION_RESTART requires SERVICE_START + ); + + if (serviceHandle) + { + if (ChangeServiceConfig2( + serviceHandle, + SERVICE_CONFIG_FAILURE_ACTIONS, + &failureActions + )) + { + if (context->EnableFlagCheckBox) + { + SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; + + failureActionsFlag.fFailureActionsOnNonCrashFailures = + Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED; + + ChangeServiceConfig2( + serviceHandle, + SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, + &failureActionsFlag + ); + } + + CloseServiceHandle(serviceHandle); + } + else + { + CloseServiceHandle(serviceHandle); + goto ErrorCase; + } + } + else + { + if (GetLastError() == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated) + { + // Elevate using phsvc. + if (PhUiConnectToPhSvc(hwndDlg, FALSE)) + { + if (NT_SUCCESS(status = PhSvcCallChangeServiceConfig2( + serviceItem->Name->Buffer, + SERVICE_CONFIG_FAILURE_ACTIONS, + &failureActions + ))) + { + if (context->EnableFlagCheckBox) + { + SERVICE_FAILURE_ACTIONS_FLAG failureActionsFlag; + + failureActionsFlag.fFailureActionsOnNonCrashFailures = + Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEFORERRORSTOPS)) == BST_CHECKED; + + PhSvcCallChangeServiceConfig2( + serviceItem->Name->Buffer, + SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, + &failureActionsFlag + ); + } + } + + PhUiDisconnectFromPhSvc(); + + if (!NT_SUCCESS(status)) + { + SetLastError(PhNtStatusToDosError(status)); + goto ErrorCase; + } + } + else + { + // User cancelled elevation. + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + } + else + { + goto ErrorCase; + } + } + + return TRUE; +ErrorCase: + if (PhShowMessage( + hwndDlg, + MB_ICONERROR | MB_RETRYCANCEL, + L"Unable to change service recovery information: %s", + ((PPH_STRING)PH_AUTO(PhGetWin32Message(GetLastError())))->Buffer + ) == IDRETRY) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK EspServiceRecovery2DlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +INT_PTR CALLBACK RestartComputerDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_RECOVERY_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PSERVICE_RECOVERY_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_RECOVERY_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + SetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, context->RebootAfter / (1000 * 60), FALSE); // ms to min + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), context->RebootMessage ? BST_CHECKED : BST_UNCHECKED); + SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, PhGetString(context->RebootMessage)); + + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_RESTARTCOMPAFTER), 0, -1); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + context->RebootAfter = GetDlgItemInt(hwndDlg, IDC_RESTARTCOMPAFTER, NULL, FALSE) * 1000 * 60; + + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE)) == BST_CHECKED) + PhMoveReference(&context->RebootMessage, PhGetWindowText(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE))); + else + PhClearReference(&context->RebootMessage); + + context->Dirty = TRUE; + + EndDialog(hwndDlg, IDOK); + } + break; + case IDC_USEDEFAULTMESSAGE: + { + PPH_STRING message; + PWSTR computerName; + ULONG bufferSize; + BOOLEAN allocated = TRUE; + + // Get the computer name. + + bufferSize = 64; + computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR)); + + if (!GetComputerName(computerName, &bufferSize)) + { + PhFree(computerName); + computerName = PhAllocate((bufferSize + 1) * sizeof(WCHAR)); + + if (!GetComputerName(computerName, &bufferSize)) + { + PhFree(computerName); + computerName = L"(unknown)"; + allocated = FALSE; + } + } + + // This message is exactly the same as the one in the Services console, + // except the double spaces are replaced by single spaces. + message = PhaFormatString( + L"Your computer is connected to the computer named %s. " + L"The %s service on %s has ended unexpectedly. " + L"%s will restart automatically, and then you can reestablish the connection.", + computerName, + context->ServiceItem->Name->Buffer, + computerName, + computerName + ); + SetDlgItemText(hwndDlg, IDC_RESTARTMESSAGE, message->Buffer); + + if (allocated) + PhFree(computerName); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), BST_CHECKED); + } + break; + case IDC_RESTARTMESSAGE: + { + if (HIWORD(wParam) == EN_CHANGE) + { + // A zero length restart message disables it, so we might as well uncheck the box. + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLERESTARTMESSAGE), + GetWindowTextLength(GetDlgItem(hwndDlg, IDC_RESTARTMESSAGE)) != 0 ? BST_CHECKED : BST_UNCHECKED); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedServices/resource.h b/plugins/ExtendedServices/resource.h new file mode 100644 index 0000000..b54d8cd --- /dev/null +++ b/plugins/ExtendedServices/resource.h @@ -0,0 +1,73 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ExtendedServices.rc +// +#define IDD_SVCLIST 101 +#define IDD_SRVLIST 101 +#define ID_SERVICE_RESTART 101 +#define IDD_SRVRECOVERY 102 +#define ID_SERVICE_START 102 +#define IDD_RESTARTCOMP 103 +#define ID_SERVICE_STOP 103 +#define IDD_SRVRECOVERY2 104 +#define ID_SERVICE_GOTOSERVICE 104 +#define IDD_SRVPROGRESS 105 +#define ID_SERVICE_CONTINUE 105 +#define IDD_SRVOTHER 106 +#define ID_SERVICE_PAUSE 106 +#define IDD_OPTIONS 107 +#define IDD_SRVTRIGGER 108 +#define IDD_VALUE 109 +#define IDD_SRVTRIGGERS 110 +#define IDC_SERVICES_LAYOUT 1001 +#define IDC_MESSAGE 1002 +#define IDC_FIRSTFAILURE 1003 +#define IDC_SECONDFAILURE 1004 +#define IDC_SUBSEQUENTFAILURES 1005 +#define IDC_RESETFAILCOUNT 1006 +#define IDC_RESTARTSERVICEAFTER 1007 +#define IDC_RESTARTSERVICEAFTER_LABEL 1008 +#define IDC_RESTARTSERVICEAFTER_MINUTES 1009 +#define IDC_ENABLEFORERRORSTOPS 1011 +#define IDC_RESTARTCOMPUTEROPTIONS 1012 +#define IDC_RUNPROGRAM 1014 +#define IDC_BROWSE 1015 +#define IDC_RESTARTCOMPAFTER 1016 +#define IDC_ENABLERESTARTMESSAGE 1017 +#define IDC_RESTARTMESSAGE 1018 +#define IDC_RUNPROGRAM_GROUP 1019 +#define IDC_RUNPROGRAM_LABEL 1020 +#define IDC_RUNPROGRAM_INFO 1021 +#define IDC_USEDEFAULTMESSAGE 1022 +#define IDC_PROGRESS 1023 +#define IDC_PRESHUTDOWNTIMEOUT 1024 +#define IDC_PRIVILEGES 1025 +#define IDC_TRIGGERS 1026 +#define IDC_TRIGGERS_LABEL 1027 +#define IDC_ENABLESERVICESMENU 1028 +#define IDC_TYPE 1029 +#define IDC_SUBTYPE 1030 +#define IDC_SUBTYPECUSTOM 1031 +#define IDC_ACTION 1032 +#define IDC_LIST 1033 +#define IDC_NEW 1034 +#define IDC_EDIT 1035 +#define IDC_DELETE 1037 +#define IDC_VALUES 1038 +#define IDC_REMOVE 1042 +#define IDC_ADD 1043 +#define IDC_SERVICESID 1044 +#define IDC_SIDTYPE 1045 +#define IDC_COMBO2 1046 +#define IDC_PROTECTION 1046 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 111 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1047 +#define _APS_NEXT_SYMED_VALUE 107 +#endif +#endif diff --git a/plugins/ExtendedServices/srvprgrs.c b/plugins/ExtendedServices/srvprgrs.c new file mode 100644 index 0000000..0cde803 --- /dev/null +++ b/plugins/ExtendedServices/srvprgrs.c @@ -0,0 +1,151 @@ +/* + * Process Hacker Extended Services - + * progress dialog + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _RESTART_SERVICE_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + SC_HANDLE ServiceHandle; + BOOLEAN Starting; + BOOLEAN DisableTimer; +} RESTART_SERVICE_CONTEXT, *PRESTART_SERVICE_CONTEXT; + +INT_PTR CALLBACK EspRestartServiceDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PRESTART_SERVICE_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PRESTART_SERVICE_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PRESTART_SERVICE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + // TODO: Use the progress information. + PhSetWindowStyle(GetDlgItem(hwndDlg, IDC_PROGRESS), PBS_MARQUEE, PBS_MARQUEE); + SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_SETMARQUEE, TRUE, 75); + + SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString(L"Attempting to stop %s...", context->ServiceItem->Name->Buffer)->Buffer); + + if (PhUiStopService(hwndDlg, context->ServiceItem)) + { + SetTimer(hwndDlg, 1, 250, NULL); + } + else + { + EndDialog(hwndDlg, IDCANCEL); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + { + EndDialog(hwndDlg, IDCANCEL); + } + break; + } + } + break; + case WM_TIMER: + { + if (wParam == 1 && !context->DisableTimer) + { + SERVICE_STATUS serviceStatus; + + if (QueryServiceStatus(context->ServiceHandle, &serviceStatus)) + { + if (!context->Starting && serviceStatus.dwCurrentState == SERVICE_STOPPED) + { + // The service is stopped, so start the service now. + + SetDlgItemText(hwndDlg, IDC_MESSAGE, + PhaFormatString(L"Attempting to start %s...", context->ServiceItem->Name->Buffer)->Buffer); + context->DisableTimer = TRUE; + + if (PhUiStartService(hwndDlg, context->ServiceItem)) + { + context->DisableTimer = FALSE; + context->Starting = TRUE; + } + else + { + EndDialog(hwndDlg, IDCANCEL); + } + } + else if (context->Starting && serviceStatus.dwCurrentState == SERVICE_RUNNING) + { + EndDialog(hwndDlg, IDOK); + } + } + } + } + break; + } + + return FALSE; +} + +VOID EsRestartServiceWithProgress( + _In_ HWND hWnd, + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ SC_HANDLE ServiceHandle + ) +{ + RESTART_SERVICE_CONTEXT context; + + context.ServiceItem = ServiceItem; + context.ServiceHandle = ServiceHandle; + context.Starting = FALSE; + context.DisableTimer = FALSE; + + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_SRVPROGRESS), + hWnd, + EspRestartServiceDlgProc, + (LPARAM)&context + ); +} \ No newline at end of file diff --git a/plugins/ExtendedServices/trigger.c b/plugins/ExtendedServices/trigger.c new file mode 100644 index 0000000..e0aa2b6 --- /dev/null +++ b/plugins/ExtendedServices/trigger.c @@ -0,0 +1,1720 @@ +/* + * Process Hacker Extended Services - + * trigger editor + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _ES_TRIGGER_DATA +{ + ULONG Type; + union + { + PPH_STRING String; + struct + { + PVOID Binary; + ULONG BinaryLength; + }; + UCHAR Byte; + ULONG64 UInt64; + }; +} ES_TRIGGER_DATA, *PES_TRIGGER_DATA; + +typedef struct _ES_TRIGGER_INFO +{ + ULONG Type; + PGUID Subtype; + ULONG Action; + PPH_LIST DataList; + GUID SubtypeBuffer; +} ES_TRIGGER_INFO, *PES_TRIGGER_INFO; + +typedef struct _ES_TRIGGER_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + HWND WindowHandle; + HWND TriggersLv; + BOOLEAN Dirty; + ULONG InitialNumberOfTriggers; + PPH_LIST InfoList; + + // Trigger dialog box + PES_TRIGGER_INFO EditingInfo; + ULONG LastSelectedType; + PPH_STRING LastCustomSubType; + + // Value dialog box + PPH_STRING EditingValue; +} ES_TRIGGER_CONTEXT, *PES_TRIGGER_CONTEXT; + +typedef struct _TYPE_ENTRY +{ + ULONG Type; + PWSTR Name; +} TYPE_ENTRY, PTYPE_ENTRY; + +typedef struct _SUBTYPE_ENTRY +{ + ULONG Type; + PGUID Guid; + PWSTR Name; +} SUBTYPE_ENTRY, PSUBTYPE_ENTRY; + +typedef struct _ETW_PUBLISHER_ENTRY +{ + PPH_STRING PublisherName; + GUID Guid; +} ETW_PUBLISHER_ENTRY, *PETW_PUBLISHER_ENTRY; + +INT_PTR CALLBACK EspServiceTriggerDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK ValueDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static GUID NetworkManagerFirstIpAddressArrivalGuid = { 0x4f27f2de, 0x14e2, 0x430b, { 0xa5, 0x49, 0x7c, 0xd4, 0x8c, 0xbc, 0x82, 0x45 } }; +static GUID NetworkManagerLastIpAddressRemovalGuid = { 0xcc4ba62a, 0x162e, 0x4648, { 0x84, 0x7a, 0xb6, 0xbd, 0xf9, 0x93, 0xe3, 0x35 } }; +static GUID DomainJoinGuid = { 0x1ce20aba, 0x9851, 0x4421, { 0x94, 0x30, 0x1d, 0xde, 0xb7, 0x66, 0xe8, 0x09 } }; +static GUID DomainLeaveGuid = { 0xddaf516e, 0x58c2, 0x4866, { 0x95, 0x74, 0xc3, 0xb6, 0x15, 0xd4, 0x2e, 0xa1 } }; +static GUID FirewallPortOpenGuid = { 0xb7569e07, 0x8421, 0x4ee0, { 0xad, 0x10, 0x86, 0x91, 0x5a, 0xfd, 0xad, 0x09 } }; +static GUID FirewallPortCloseGuid = { 0xa144ed38, 0x8e12, 0x4de4, { 0x9d, 0x96, 0xe6, 0x47, 0x40, 0xb1, 0xa5, 0x24 } }; +static GUID MachinePolicyPresentGuid = { 0x659fcae6, 0x5bdb, 0x4da9, { 0xb1, 0xff, 0xca, 0x2a, 0x17, 0x8d, 0x46, 0xe0 } }; +static GUID UserPolicyPresentGuid = { 0x54fb46c8, 0xf089, 0x464c, { 0xb1, 0xfd, 0x59, 0xd1, 0xb6, 0x2c, 0x3b, 0x50 } }; +static GUID RpcInterfaceEventGuid = { 0xbc90d167, 0x9470, 0x4139, { 0xa9, 0xba, 0xbe, 0x0b, 0xbb, 0xf5, 0xb7, 0x4d } }; +static GUID NamedPipeEventGuid = { 0x1f81d131, 0x3fac, 0x4537, { 0x9e, 0x0c, 0x7e, 0x7b, 0x0c, 0x2f, 0x4b, 0x55 } }; +static GUID SubTypeUnknownGuid; // dummy + +static TYPE_ENTRY TypeEntries[] = +{ + { SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL, L"Device interface arrival" }, + { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, L"IP address availability" }, + { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, L"Domain join" }, + { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, L"Firewall port event" }, + { SERVICE_TRIGGER_TYPE_GROUP_POLICY, L"Group policy" }, + { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, L"Network endpoint" }, + { SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE, L"Custom system state change" }, + { SERVICE_TRIGGER_TYPE_CUSTOM, L"Custom" } +}; + +static SUBTYPE_ENTRY SubTypeEntries[] = +{ + { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, NULL, L"IP address" }, + { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &NetworkManagerFirstIpAddressArrivalGuid, L"IP address: First arrival" }, + { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &NetworkManagerLastIpAddressRemovalGuid, L"IP address: Last removal" }, + { SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY, &SubTypeUnknownGuid, L"IP address: Unknown" }, + { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, NULL, L"Domain" }, + { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &DomainJoinGuid, L"Domain: Join" }, + { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &DomainLeaveGuid, L"Domain: Leave" }, + { SERVICE_TRIGGER_TYPE_DOMAIN_JOIN, &SubTypeUnknownGuid, L"Domain: Unknown" }, + { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, NULL, L"Firewall port" }, + { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &FirewallPortOpenGuid, L"Firewall port: Open" }, + { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &FirewallPortCloseGuid, L"Firewall port: Close" }, + { SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT, &SubTypeUnknownGuid, L"Firewall port: Unknown" }, + { SERVICE_TRIGGER_TYPE_GROUP_POLICY, NULL, L"Group policy change" }, + { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &MachinePolicyPresentGuid, L"Group policy change: Machine" }, + { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &UserPolicyPresentGuid, L"Group policy change: User" }, + { SERVICE_TRIGGER_TYPE_GROUP_POLICY, &SubTypeUnknownGuid, L"Group policy change: Unknown" }, + { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, NULL, L"Network endpoint" }, + { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &RpcInterfaceEventGuid, L"Network endpoint: RPC interface" }, + { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &NamedPipeEventGuid, L"Network endpoint: Named pipe" }, + { SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT, &SubTypeUnknownGuid, L"Network endpoint: Unknown" } +}; + +static PH_STRINGREF PublishersKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\WINEVT\\Publishers\\"); + +PES_TRIGGER_DATA EspCreateTriggerData( + _In_opt_ PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM DataItem + ) +{ + PES_TRIGGER_DATA data; + + data = PhAllocate(sizeof(ES_TRIGGER_DATA)); + memset(data, 0, sizeof(ES_TRIGGER_DATA)); + + if (DataItem) + { + data->Type = DataItem->dwDataType; + + if (data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING) + { + if (DataItem->pData && DataItem->cbData >= 2) + data->String = PhCreateStringEx((PWSTR)DataItem->pData, DataItem->cbData - 2); // exclude final null terminator + else + data->String = PhReferenceEmptyString(); + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY) + { + data->BinaryLength = DataItem->cbData; + data->Binary = PhAllocateCopy(DataItem->pData, DataItem->cbData); + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL) + { + if (DataItem->cbData == sizeof(UCHAR)) + data->Byte = *(PUCHAR)DataItem->pData; + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY || data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL) + { + if (DataItem->cbData == sizeof(ULONG64)) + data->UInt64 = *(PULONG64)DataItem->pData; + } + } + + return data; +} + +PES_TRIGGER_DATA EspCloneTriggerData( + _In_ PES_TRIGGER_DATA Data + ) +{ + PES_TRIGGER_DATA newData; + + newData = PhAllocateCopy(Data, sizeof(ES_TRIGGER_DATA)); + + if (newData->Type == SERVICE_TRIGGER_DATA_TYPE_STRING) + { + if (newData->String) + newData->String = PhDuplicateString(newData->String); + } + else if (newData->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY) + { + if (newData->Binary) + newData->Binary = PhAllocateCopy(newData->Binary, newData->BinaryLength); + } + + return newData; +} + +VOID EspDestroyTriggerData( + _In_ PES_TRIGGER_DATA Data + ) +{ + if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING) + { + if (Data->String) + PhDereferenceObject(Data->String); + } + else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY) + { + if (Data->Binary) + PhFree(Data->Binary); + } + + PhFree(Data); +} + +PES_TRIGGER_INFO EspCreateTriggerInfo( + _In_opt_ PSERVICE_TRIGGER Trigger + ) +{ + PES_TRIGGER_INFO info; + + info = PhAllocate(sizeof(ES_TRIGGER_INFO)); + memset(info, 0, sizeof(ES_TRIGGER_INFO)); + + if (Trigger) + { + info->Type = Trigger->dwTriggerType; + + if (Trigger->pTriggerSubtype) + { + info->SubtypeBuffer = *Trigger->pTriggerSubtype; + info->Subtype = &info->SubtypeBuffer; + } + + info->Action = Trigger->dwAction; + + if ( + info->Type == SERVICE_TRIGGER_TYPE_CUSTOM || + info->Type == SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL || + info->Type == SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT || + info->Type == SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT + ) + { + ULONG i; + + info->DataList = PhCreateList(Trigger->cDataItems); + + for (i = 0; i < Trigger->cDataItems; i++) + { + PhAddItemList(info->DataList, EspCreateTriggerData(&Trigger->pDataItems[i])); + } + } + } + + return info; +} + +PES_TRIGGER_INFO EspCloneTriggerInfo( + _In_ PES_TRIGGER_INFO Info + ) +{ + PES_TRIGGER_INFO newInfo; + + newInfo = PhAllocateCopy(Info, sizeof(ES_TRIGGER_INFO)); + + if (newInfo->Subtype == &Info->SubtypeBuffer) + newInfo->Subtype = &newInfo->SubtypeBuffer; + + if (newInfo->DataList) + { + ULONG i; + + newInfo->DataList = PhCreateList(Info->DataList->AllocatedCount); + newInfo->DataList->Count = Info->DataList->Count; + + for (i = 0; i < Info->DataList->Count; i++) + newInfo->DataList->Items[i] = EspCloneTriggerData(Info->DataList->Items[i]); + } + + return newInfo; +} + +VOID EspDestroyTriggerInfo( + _In_ PES_TRIGGER_INFO Info + ) +{ + if (Info->DataList) + { + ULONG i; + + for (i = 0; i < Info->DataList->Count; i++) + { + EspDestroyTriggerData(Info->DataList->Items[i]); + } + + PhDereferenceObject(Info->DataList); + } + + PhFree(Info); +} + +VOID EspClearTriggerInfoList( + _In_ PPH_LIST List + ) +{ + ULONG i; + + for (i = 0; i < List->Count; i++) + { + EspDestroyTriggerInfo(List->Items[i]); + } + + PhClearList(List); +} + +struct _ES_TRIGGER_CONTEXT *EsCreateServiceTriggerContext( + _In_ PPH_SERVICE_ITEM ServiceItem, + _In_ HWND WindowHandle, + _In_ HWND TriggersLv + ) +{ + PES_TRIGGER_CONTEXT context; + + context = PhAllocate(sizeof(ES_TRIGGER_CONTEXT)); + memset(context, 0, sizeof(ES_TRIGGER_CONTEXT)); + context->ServiceItem = ServiceItem; + context->WindowHandle = WindowHandle; + context->TriggersLv = TriggersLv; + context->InfoList = PhCreateList(4); + + PhSetListViewStyle(TriggersLv, FALSE, TRUE); + PhSetControlTheme(TriggersLv, L"explorer"); + PhAddListViewColumn(TriggersLv, 0, 0, 0, LVCFMT_LEFT, 300, L"Trigger"); + PhAddListViewColumn(TriggersLv, 1, 1, 1, LVCFMT_LEFT, 60, L"Action"); + PhSetExtendedListView(TriggersLv); + + EnableWindow(GetDlgItem(WindowHandle, IDC_EDIT), FALSE); + EnableWindow(GetDlgItem(WindowHandle, IDC_DELETE), FALSE); + + return context; +} + +VOID EsDestroyServiceTriggerContext( + _In_ struct _ES_TRIGGER_CONTEXT *Context + ) +{ + ULONG i; + + for (i = 0; i < Context->InfoList->Count; i++) + { + EspDestroyTriggerInfo(Context->InfoList->Items[i]); + } + + PhDereferenceObject(Context->InfoList); + PhFree(Context); +} + +PPH_STRING EspLookupEtwPublisherName( + _In_ PGUID Guid + ) +{ + PPH_STRING guidString; + PPH_STRING keyName; + HANDLE keyHandle; + PPH_STRING publisherName = NULL; + + // Copied from ProcessHacker\hndlinfo.c. + + guidString = PhFormatGuid(Guid); + + keyName = PhConcatStringRef2(&PublishersKeyName, &guidString->sr); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName->sr, + 0 + ))) + { + publisherName = PhQueryRegistryString(keyHandle, NULL); + + if (publisherName && publisherName->Length == 0) + { + PhDereferenceObject(publisherName); + publisherName = NULL; + } + + NtClose(keyHandle); + } + + PhDereferenceObject(keyName); + + if (publisherName) + { + PhDereferenceObject(guidString); + return publisherName; + } + else + { + return guidString; + } +} + +BOOLEAN EspEnumerateEtwPublishers( + _Out_ PETW_PUBLISHER_ENTRY *Entries, + _Out_ PULONG NumberOfEntries + ) +{ + NTSTATUS status; + HANDLE publishersKeyHandle; + ULONG index; + PKEY_BASIC_INFORMATION buffer; + ULONG bufferSize; + PETW_PUBLISHER_ENTRY entries; + ULONG numberOfEntries; + ULONG allocatedEntries; + + if (!NT_SUCCESS(PhOpenKey( + &publishersKeyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &PublishersKeyName, + 0 + ))) + { + return FALSE; + } + + numberOfEntries = 0; + allocatedEntries = 256; + entries = PhAllocate(allocatedEntries * sizeof(ETW_PUBLISHER_ENTRY)); + + index = 0; + bufferSize = 0x100; + buffer = PhAllocate(0x100); + + while (TRUE) + { + status = NtEnumerateKey( + publishersKeyHandle, + index, + KeyBasicInformation, + buffer, + bufferSize, + &bufferSize + ); + + if (NT_SUCCESS(status)) + { + UNICODE_STRING nameUs; + PH_STRINGREF name; + HANDLE keyHandle; + GUID guid; + PPH_STRING publisherName; + + nameUs.Buffer = buffer->Name; + nameUs.Length = (USHORT)buffer->NameLength; + name.Buffer = buffer->Name; + name.Length = buffer->NameLength; + + // Make sure this is a valid publisher key. + if (NT_SUCCESS(RtlGUIDFromString(&nameUs, &guid))) + { + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + publishersKeyHandle, + &name, + 0 + ))) + { + publisherName = PhQueryRegistryString(keyHandle, NULL); + + if (publisherName) + { + if (publisherName->Length != 0) + { + PETW_PUBLISHER_ENTRY entry; + + if (numberOfEntries == allocatedEntries) + { + allocatedEntries *= 2; + entries = PhReAllocate(entries, allocatedEntries * sizeof(ETW_PUBLISHER_ENTRY)); + } + + entry = &entries[numberOfEntries++]; + entry->PublisherName = publisherName; + entry->Guid = guid; + } + else + { + PhDereferenceObject(publisherName); + } + } + + NtClose(keyHandle); + } + } + + index++; + } + else if (status == STATUS_NO_MORE_ENTRIES) + { + break; + } + else if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL) + { + PhFree(buffer); + buffer = PhAllocate(bufferSize); + } + else + { + break; + } + } + + PhFree(buffer); + NtClose(publishersKeyHandle); + + *Entries = entries; + *NumberOfEntries = numberOfEntries; + + return TRUE; +} + +BOOLEAN EspLookupEtwPublisherGuid( + _In_ PPH_STRINGREF PublisherName, + _Out_ PGUID Guid + ) +{ + BOOLEAN result; + PETW_PUBLISHER_ENTRY entries; + ULONG numberOfEntries; + ULONG i; + + if (!EspEnumerateEtwPublishers(&entries, &numberOfEntries)) + return FALSE; + + result = FALSE; + + for (i = 0; i < numberOfEntries; i++) + { + if (!result && PhEqualStringRef(&entries[i].PublisherName->sr, PublisherName, TRUE)) + { + *Guid = entries[i].Guid; + result = TRUE; + } + + PhDereferenceObject(entries[i].PublisherName); + } + + PhFree(entries); + + return result; +} + +VOID EspFormatTriggerInfo( + _In_ PES_TRIGGER_INFO Info, + _Out_ PWSTR *TriggerString, + _Out_ PWSTR *ActionString, + _Out_ PPH_STRING *StringUsed + ) +{ + PPH_STRING stringUsed = NULL; + PWSTR triggerString = NULL; + PWSTR actionString; + ULONG i; + BOOLEAN typeFound; + BOOLEAN subTypeFound; + + switch (Info->Type) + { + case SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL: + { + PPH_STRING guidString; + + if (!Info->Subtype) + { + triggerString = L"Device interface arrival"; + } + else + { + guidString = PhFormatGuid(Info->Subtype); + stringUsed = PhConcatStrings2(L"Device interface arrival: ", guidString->Buffer); + triggerString = stringUsed->Buffer; + } + } + break; + case SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE: + { + PPH_STRING guidString; + + if (!Info->Subtype) + { + triggerString = L"Custom system state change"; + } + else + { + guidString = PhFormatGuid(Info->Subtype); + stringUsed = PhConcatStrings2(L"Custom system state change: ", guidString->Buffer); + triggerString = stringUsed->Buffer; + } + } + break; + case SERVICE_TRIGGER_TYPE_CUSTOM: + { + if (Info->Subtype) + { + PPH_STRING publisherName; + + // Try to lookup the publisher name from the GUID. + publisherName = EspLookupEtwPublisherName(Info->Subtype); + stringUsed = PhConcatStrings2(L"Custom: ", publisherName->Buffer); + PhDereferenceObject(publisherName); + triggerString = stringUsed->Buffer; + } + else + { + triggerString = L"Custom"; + } + } + break; + default: + { + typeFound = FALSE; + subTypeFound = FALSE; + + for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++) + { + if (SubTypeEntries[i].Type == Info->Type) + { + typeFound = TRUE; + + if (!Info->Subtype && !SubTypeEntries[i].Guid) + { + subTypeFound = TRUE; + triggerString = SubTypeEntries[i].Name; + break; + } + else if (Info->Subtype && SubTypeEntries[i].Guid && memcmp(Info->Subtype, SubTypeEntries[i].Guid, sizeof(GUID)) == 0) + { + subTypeFound = TRUE; + triggerString = SubTypeEntries[i].Name; + break; + } + else if (!subTypeFound && SubTypeEntries[i].Guid == &SubTypeUnknownGuid) + { + triggerString = SubTypeEntries[i].Name; + break; + } + } + } + + if (!typeFound) + { + triggerString = L"Unknown"; + } + } + break; + } + + switch (Info->Action) + { + case SERVICE_TRIGGER_ACTION_SERVICE_START: + actionString = L"Start"; + break; + case SERVICE_TRIGGER_ACTION_SERVICE_STOP: + actionString = L"Stop"; + break; + default: + actionString = L"Unknown"; + break; + } + + *TriggerString = triggerString; + *ActionString = actionString; + *StringUsed = stringUsed; +} + +VOID EsLoadServiceTriggerInfo( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _In_ SC_HANDLE ServiceHandle + ) +{ + PSERVICE_TRIGGER_INFO triggerInfo; + ULONG i; + + EspClearTriggerInfoList(Context->InfoList); + + if (triggerInfo = PhQueryServiceVariableSize(ServiceHandle, SERVICE_CONFIG_TRIGGER_INFO)) + { + for (i = 0; i < triggerInfo->cTriggers; i++) + { + PSERVICE_TRIGGER trigger = &triggerInfo->pTriggers[i]; + PES_TRIGGER_INFO info; + PWSTR triggerString; + PWSTR actionString; + PPH_STRING stringUsed; + INT lvItemIndex; + + info = EspCreateTriggerInfo(trigger); + PhAddItemList(Context->InfoList, info); + + EspFormatTriggerInfo(info, &triggerString, &actionString, &stringUsed); + + lvItemIndex = PhAddListViewItem(Context->TriggersLv, MAXINT, triggerString, info); + PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString); + + if (stringUsed) + PhDereferenceObject(stringUsed); + } + + Context->InitialNumberOfTriggers = triggerInfo->cTriggers; + + ExtendedListView_SortItems(Context->TriggersLv); + + PhFree(triggerInfo); + } +} + +BOOLEAN EsSaveServiceTriggerInfo( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _Out_ PULONG Win32Result + ) +{ + BOOLEAN result = TRUE; + PH_AUTO_POOL autoPool; + SC_HANDLE serviceHandle; + SERVICE_TRIGGER_INFO triggerInfo; + ULONG i; + ULONG j; + + if (!Context->Dirty) + return TRUE; + + // Do not try to change trigger information if we didn't have any triggers before and we don't + // have any now. ChangeServiceConfig2 returns an error in this situation. + if (Context->InitialNumberOfTriggers == 0 && Context->InfoList->Count == 0) + return TRUE; + + PhInitializeAutoPool(&autoPool); + + memset(&triggerInfo, 0, sizeof(SERVICE_TRIGGER_INFO)); + triggerInfo.cTriggers = Context->InfoList->Count; + + // pTriggers needs to be NULL when there are no triggers. + if (Context->InfoList->Count != 0) + { + triggerInfo.pTriggers = PH_AUTO(PhCreateAlloc(Context->InfoList->Count * sizeof(SERVICE_TRIGGER))); + memset(triggerInfo.pTriggers, 0, Context->InfoList->Count * sizeof(SERVICE_TRIGGER)); + + for (i = 0; i < Context->InfoList->Count; i++) + { + PSERVICE_TRIGGER trigger = &triggerInfo.pTriggers[i]; + PES_TRIGGER_INFO info = Context->InfoList->Items[i]; + + trigger->dwTriggerType = info->Type; + trigger->dwAction = info->Action; + trigger->pTriggerSubtype = info->Subtype; + + if (info->DataList && info->DataList->Count != 0) + { + trigger->cDataItems = info->DataList->Count; + trigger->pDataItems = PH_AUTO(PhCreateAlloc(info->DataList->Count * sizeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM))); + + for (j = 0; j < info->DataList->Count; j++) + { + PSERVICE_TRIGGER_SPECIFIC_DATA_ITEM dataItem = &trigger->pDataItems[j]; + PES_TRIGGER_DATA data = info->DataList->Items[j]; + + dataItem->dwDataType = data->Type; + + if (data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING) + { + dataItem->cbData = (ULONG)data->String->Length + 2; // include null terminator + dataItem->pData = (PBYTE)data->String->Buffer; + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY) + { + dataItem->cbData = data->BinaryLength; + dataItem->pData = data->Binary; + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL) + { + dataItem->cbData = sizeof(UCHAR); + dataItem->pData = (PBYTE)&data->Byte; + } + else if (data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY || data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL) + { + dataItem->cbData = sizeof(ULONG64); + dataItem->pData = (PBYTE)&data->UInt64; + } + } + } + } + } + + if (serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_CHANGE_CONFIG)) + { + if (!ChangeServiceConfig2(serviceHandle, SERVICE_CONFIG_TRIGGER_INFO, &triggerInfo)) + { + result = FALSE; + *Win32Result = GetLastError(); + } + + CloseServiceHandle(serviceHandle); + } + else + { + result = FALSE; + *Win32Result = GetLastError(); + + if (*Win32Result == ERROR_ACCESS_DENIED && !PhGetOwnTokenAttributes().Elevated) + { + // Elevate using phsvc. + if (PhUiConnectToPhSvc(Context->WindowHandle, FALSE)) + { + NTSTATUS status; + + result = TRUE; + + if (!NT_SUCCESS(status = PhSvcCallChangeServiceConfig2(Context->ServiceItem->Name->Buffer, + SERVICE_CONFIG_TRIGGER_INFO, &triggerInfo))) + { + result = FALSE; + *Win32Result = PhNtStatusToDosError(status); + } + + PhUiDisconnectFromPhSvc(); + } + else + { + // User cancelled elevation. + *Win32Result = ERROR_CANCELLED; + } + } + } + + PhDeleteAutoPool(&autoPool); + + return result; +} + +LOGICAL EspSetListViewItemParam( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ PVOID Param + ) +{ + LVITEM item; + + item.mask = LVIF_PARAM; + item.iItem = Index; + item.iSubItem = 0; + item.lParam = (LPARAM)Param; + + return ListView_SetItem(ListViewHandle, &item); +} + +VOID EsHandleEventServiceTrigger( + _In_ struct _ES_TRIGGER_CONTEXT *Context, + _In_ ULONG Event + ) +{ + switch (Event) + { + case ES_TRIGGER_EVENT_NEW: + { + Context->EditingInfo = EspCreateTriggerInfo(NULL); + Context->EditingInfo->Type = SERVICE_TRIGGER_TYPE_IP_ADDRESS_AVAILABILITY; + Context->EditingInfo->SubtypeBuffer = NetworkManagerFirstIpAddressArrivalGuid; + Context->EditingInfo->Subtype = &Context->EditingInfo->SubtypeBuffer; + Context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_START; + + if (DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_SRVTRIGGER), + Context->WindowHandle, + EspServiceTriggerDlgProc, + (LPARAM)Context + ) == IDOK) + { + PWSTR triggerString; + PWSTR actionString; + PPH_STRING stringUsed; + INT lvItemIndex; + + Context->Dirty = TRUE; + PhAddItemList(Context->InfoList, Context->EditingInfo); + + EspFormatTriggerInfo(Context->EditingInfo, &triggerString, &actionString, &stringUsed); + + lvItemIndex = PhAddListViewItem(Context->TriggersLv, MAXINT, triggerString, Context->EditingInfo); + PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString); + + if (stringUsed) + PhDereferenceObject(stringUsed); + } + else + { + EspDestroyTriggerInfo(Context->EditingInfo); + } + + Context->EditingInfo = NULL; + } + break; + case ES_TRIGGER_EVENT_EDIT: + { + INT lvItemIndex; + PES_TRIGGER_INFO info; + ULONG index; + + lvItemIndex = ListView_GetNextItem(Context->TriggersLv, -1, LVNI_SELECTED); + + if (lvItemIndex != -1 && PhGetListViewItemParam(Context->TriggersLv, lvItemIndex, (PVOID *)&info)) + { + index = PhFindItemList(Context->InfoList, info); + + if (index != -1) + { + Context->EditingInfo = EspCloneTriggerInfo(info); + + if (DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_SRVTRIGGER), + Context->WindowHandle, + EspServiceTriggerDlgProc, + (LPARAM)Context + ) == IDOK) + { + PWSTR triggerString; + PWSTR actionString; + PPH_STRING stringUsed; + + Context->Dirty = TRUE; + EspDestroyTriggerInfo(Context->InfoList->Items[index]); + Context->InfoList->Items[index] = Context->EditingInfo; + + EspFormatTriggerInfo(Context->EditingInfo, &triggerString, &actionString, &stringUsed); + PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 0, triggerString); + PhSetListViewSubItem(Context->TriggersLv, lvItemIndex, 1, actionString); + + if (stringUsed) + PhDereferenceObject(stringUsed); + + EspSetListViewItemParam(Context->TriggersLv, lvItemIndex, Context->EditingInfo); + } + else + { + EspDestroyTriggerInfo(Context->EditingInfo); + } + + Context->EditingInfo = NULL; + } + } + } + break; + case ES_TRIGGER_EVENT_DELETE: + { + INT lvItemIndex; + PES_TRIGGER_INFO info; + ULONG index; + + lvItemIndex = ListView_GetNextItem(Context->TriggersLv, -1, LVNI_SELECTED); + + if (lvItemIndex != -1 && PhGetListViewItemParam(Context->TriggersLv, lvItemIndex, (PVOID *)&info)) + { + index = PhFindItemList(Context->InfoList, info); + + if (index != -1) + { + EspDestroyTriggerInfo(info); + PhRemoveItemList(Context->InfoList, index); + PhRemoveListViewItem(Context->TriggersLv, lvItemIndex); + } + } + + Context->Dirty = TRUE; + } + break; + case ES_TRIGGER_EVENT_SELECTIONCHANGED: + { + ULONG selectedCount; + + selectedCount = ListView_GetSelectedCount(Context->TriggersLv); + + EnableWindow(GetDlgItem(Context->WindowHandle, IDC_EDIT), selectedCount == 1); + EnableWindow(GetDlgItem(Context->WindowHandle, IDC_DELETE), selectedCount == 1); + } + break; + } +} + +ULONG EspTriggerTypeStringToInteger( + _In_ PWSTR String + ) +{ + ULONG i; + + for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++) + { + if (PhEqualStringZ(TypeEntries[i].Name, String, FALSE)) + return TypeEntries[i].Type; + } + + return 0; +} + +static int __cdecl EtwPublisherByNameCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PETW_PUBLISHER_ENTRY entry1 = (PETW_PUBLISHER_ENTRY)elem1; + PETW_PUBLISHER_ENTRY entry2 = (PETW_PUBLISHER_ENTRY)elem2; + + return PhCompareString(entry1->PublisherName, entry2->PublisherName, TRUE); +} + +VOID EspFixServiceTriggerControls( + _In_ HWND hwndDlg, + _In_ PES_TRIGGER_CONTEXT Context + ) +{ + HWND typeComboBox; + HWND subTypeComboBox; + ULONG i; + PPH_STRING selectedTypeString; + ULONG type; + PPH_STRING selectedSubTypeString; + + typeComboBox = GetDlgItem(hwndDlg, IDC_TYPE); + subTypeComboBox = GetDlgItem(hwndDlg, IDC_SUBTYPE); + + selectedTypeString = PhGetWindowText(typeComboBox); + type = EspTriggerTypeStringToInteger(selectedTypeString->Buffer); + PhDereferenceObject(selectedTypeString); + + if (Context->LastSelectedType != type) + { + // Change the contents of the subtype combo box based on the type. + + ComboBox_ResetContent(subTypeComboBox); + + switch (type) + { + case SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL: + { + ComboBox_AddString(subTypeComboBox, L"Custom"); + } + break; + case SERVICE_TRIGGER_TYPE_CUSTOM_SYSTEM_STATE_CHANGE: + { + ComboBox_AddString(subTypeComboBox, L"Custom"); + } + break; + case SERVICE_TRIGGER_TYPE_CUSTOM: + { + PETW_PUBLISHER_ENTRY entries; + ULONG numberOfEntries; + ULONG i; + + ComboBox_AddString(subTypeComboBox, L"Custom"); + + // Display a list of publishers. + if (EspEnumerateEtwPublishers(&entries, &numberOfEntries)) + { + // Sort the list by name. + qsort(entries, numberOfEntries, sizeof(ETW_PUBLISHER_ENTRY), EtwPublisherByNameCompareFunction); + + for (i = 0; i < numberOfEntries; i++) + { + ComboBox_AddString(subTypeComboBox, entries[i].PublisherName->Buffer); + PhDereferenceObject(entries[i].PublisherName); + } + + PhFree(entries); + } + } + break; + default: + for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++) + { + if (SubTypeEntries[i].Type == type && SubTypeEntries[i].Guid && SubTypeEntries[i].Guid != &SubTypeUnknownGuid) + { + ComboBox_AddString(subTypeComboBox, SubTypeEntries[i].Name); + } + } + break; + } + + ComboBox_SetCurSel(subTypeComboBox, 0); + + Context->LastSelectedType = type; + } + + selectedSubTypeString = PhGetWindowText(subTypeComboBox); + + if (PhEqualString2(selectedSubTypeString, L"Custom", FALSE)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM), TRUE); + SetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM, Context->LastCustomSubType->Buffer); + } + else + { + if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM))) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM), FALSE); + PhMoveReference(&Context->LastCustomSubType, PhGetWindowText(GetDlgItem(hwndDlg, IDC_SUBTYPECUSTOM))); + SetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM, L""); + } + } + + PhDereferenceObject(selectedSubTypeString); +} + +PPH_STRING EspConvertNullsToNewLines( + _In_ PPH_STRING String + ) +{ + PH_STRING_BUILDER sb; + ULONG i; + + PhInitializeStringBuilder(&sb, String->Length); + + for (i = 0; i < (ULONG)String->Length / 2; i++) + { + if (String->Buffer[i] == 0) + { + PhAppendStringBuilderEx(&sb, L"\r\n", 4); + continue; + } + + PhAppendCharStringBuilder(&sb, String->Buffer[i]); + } + + return PhFinalStringBuilderString(&sb); +} + +PPH_STRING EspConvertNewLinesToNulls( + _In_ PPH_STRING String + ) +{ + PPH_STRING text; + SIZE_T count; + SIZE_T i; + + text = PhCreateStringEx(NULL, String->Length + 2); // plus one character for an extra null terminator (see below) + text->Length = 0; + count = 0; + + for (i = 0; i < String->Length / 2; i++) + { + // Lines are terminated by "\r\n". + if (String->Buffer[i] == '\r') + { + continue; + } + + if (String->Buffer[i] == '\n') + { + text->Buffer[count++] = 0; + continue; + } + + text->Buffer[count++] = String->Buffer[i]; + } + + if (count != 0) + { + // Make sure we have an extra null terminator at the end, as required of multistrings. + if (text->Buffer[count - 1] != 0) + text->Buffer[count++] = 0; + } + + text->Length = count * 2; + + return text; +} + +PPH_STRING EspConvertNullsToSpaces( + _In_ PPH_STRING String + ) +{ + PPH_STRING text; + SIZE_T j; + + text = PhDuplicateString(String); + + for (j = 0; j < text->Length / 2; j++) + { + if (text->Buffer[j] == 0) + text->Buffer[j] = ' '; + } + + return text; +} + +VOID EspFormatTriggerData( + _In_ PES_TRIGGER_DATA Data, + _Out_ PPH_STRING *Text + ) +{ + if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING) + { + // This check works for both normal strings and multistrings. + if (Data->String->Length != 0) + { + // Prepare the text for display by replacing null characters with spaces. + *Text = EspConvertNullsToSpaces(Data->String); + } + else + { + *Text = PhCreateString(L"(empty string)"); + } + } + else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_BINARY) + { + *Text = PhCreateString(L"(binary data)"); + } + else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_LEVEL) + { + *Text = PhFormatString(L"(level) %u", Data->Byte); + } + else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ANY) + { + *Text = PhFormatString(L"(keyword any) 0x%I64x", Data->UInt64); + } + else if (Data->Type == SERVICE_TRIGGER_DATA_TYPE_KEYWORD_ALL) + { + *Text = PhFormatString(L"(keyword all) 0x%I64x", Data->UInt64); + } + else + { + *Text = PhCreateString(L"(unknown type)"); + } +} + +INT_PTR CALLBACK EspServiceTriggerDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PES_TRIGGER_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PES_TRIGGER_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PES_TRIGGER_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND typeComboBox; + HWND actionComboBox; + HWND lvHandle; + ULONG i; + + context->LastSelectedType = 0; + + if (context->EditingInfo->Subtype) + context->LastCustomSubType = PhFormatGuid(context->EditingInfo->Subtype); + else + context->LastCustomSubType = PhReferenceEmptyString(); + + typeComboBox = GetDlgItem(hwndDlg, IDC_TYPE); + actionComboBox = GetDlgItem(hwndDlg, IDC_ACTION); + + for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++) + { + ComboBox_AddString(typeComboBox, TypeEntries[i].Name); + + if (TypeEntries[i].Type == context->EditingInfo->Type) + { + PhSelectComboBoxString(typeComboBox, TypeEntries[i].Name, FALSE); + } + } + + ComboBox_AddString(actionComboBox, L"Start"); + ComboBox_AddString(actionComboBox, L"Stop"); + ComboBox_SetCurSel(actionComboBox, context->EditingInfo->Action == SERVICE_TRIGGER_ACTION_SERVICE_START ? 0 : 1); + + EspFixServiceTriggerControls(hwndDlg, context); + + if (context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM) + { + for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++) + { + if ( + SubTypeEntries[i].Type == context->EditingInfo->Type && + SubTypeEntries[i].Guid && + context->EditingInfo->Subtype && + memcmp(SubTypeEntries[i].Guid, context->EditingInfo->Subtype, sizeof(GUID)) == 0 + ) + { + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_SUBTYPE), SubTypeEntries[i].Name, FALSE); + break; + } + } + } + else + { + if (context->EditingInfo->Subtype) + { + PPH_STRING publisherName; + + // Try to select the publisher name in the subtype list. + publisherName = EspLookupEtwPublisherName(context->EditingInfo->Subtype); + PhSelectComboBoxString(GetDlgItem(hwndDlg, IDC_SUBTYPE), publisherName->Buffer, FALSE); + PhDereferenceObject(publisherName); + } + } + + // Call a second time since the state of the custom subtype text box may have changed. + EspFixServiceTriggerControls(hwndDlg, context); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 280, L"Data"); + + if (context->EditingInfo->DataList) + { + for (i = 0; i < context->EditingInfo->DataList->Count; i++) + { + PES_TRIGGER_DATA data; + PPH_STRING text; + INT lvItemIndex; + + data = context->EditingInfo->DataList->Items[i]; + + EspFormatTriggerData(data, &text); + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, text->Buffer, data); + PhDereferenceObject(text); + } + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + } + break; + case WM_DESTROY: + { + PhClearReference(&context->LastCustomSubType); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_TYPE: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + EspFixServiceTriggerControls(hwndDlg, context); + } + break; + case IDC_SUBTYPE: + if (HIWORD(wParam) == CBN_SELCHANGE) + { + EspFixServiceTriggerControls(hwndDlg, context); + } + break; + case IDC_NEW: + { + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + context->EditingValue = PhReferenceEmptyString(); + + if (DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_VALUE), + hwndDlg, + ValueDlgProc, + (LPARAM)context + ) == IDOK) + { + PES_TRIGGER_DATA data; + PPH_STRING text; + INT lvItemIndex; + + data = EspCreateTriggerData(NULL); + data->Type = SERVICE_TRIGGER_DATA_TYPE_STRING; + data->String = EspConvertNewLinesToNulls(context->EditingValue); + + if (!context->EditingInfo->DataList) + context->EditingInfo->DataList = PhCreateList(4); + + PhAddItemList(context->EditingInfo->DataList, data); + + EspFormatTriggerData(data, &text); + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, text->Buffer, data); + PhDereferenceObject(text); + } + + PhClearReference(&context->EditingValue); + } + break; + case IDC_EDIT: + { + HWND lvHandle; + INT lvItemIndex; + PES_TRIGGER_DATA data; + ULONG index; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + lvItemIndex = ListView_GetNextItem(lvHandle, -1, LVNI_SELECTED); + + if ( + lvItemIndex != -1 && PhGetListViewItemParam(lvHandle, lvItemIndex, (PVOID *)&data) && + data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING // editing binary values is not supported + ) + { + index = PhFindItemList(context->EditingInfo->DataList, data); + + if (index != -1) + { + context->EditingValue = EspConvertNullsToNewLines(data->String); + + if (DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_VALUE), + hwndDlg, + ValueDlgProc, + (LPARAM)context + ) == IDOK) + { + PPH_STRING text; + + PhMoveReference(&data->String, EspConvertNewLinesToNulls(context->EditingValue)); + + EspFormatTriggerData(data, &text); + PhSetListViewSubItem(lvHandle, lvItemIndex, 0, text->Buffer); + PhDereferenceObject(text); + } + + PhClearReference(&context->EditingValue); + } + } + } + break; + case IDC_DELETE: + { + HWND lvHandle; + INT lvItemIndex; + PES_TRIGGER_DATA data; + ULONG index; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + lvItemIndex = ListView_GetNextItem(lvHandle, -1, LVNI_SELECTED); + + if (lvItemIndex != -1 && PhGetListViewItemParam(lvHandle, lvItemIndex, (PVOID *)&data)) + { + index = PhFindItemList(context->EditingInfo->DataList, data); + + if (index != -1) + { + EspDestroyTriggerData(data); + PhRemoveItemList(context->EditingInfo->DataList, index); + PhRemoveListViewItem(lvHandle, lvItemIndex); + } + } + } + break; + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PH_AUTO_POOL autoPool; + PPH_STRING typeString; + PPH_STRING subTypeString; + PPH_STRING customSubTypeString; + PPH_STRING actionString; + ULONG i; + + PhInitializeAutoPool(&autoPool); + + typeString = PhaGetDlgItemText(hwndDlg, IDC_TYPE); + subTypeString = PhaGetDlgItemText(hwndDlg, IDC_SUBTYPE); + customSubTypeString = PhaGetDlgItemText(hwndDlg, IDC_SUBTYPECUSTOM); + actionString = PhaGetDlgItemText(hwndDlg, IDC_ACTION); + + for (i = 0; i < sizeof(TypeEntries) / sizeof(TYPE_ENTRY); i++) + { + if (PhEqualStringZ(TypeEntries[i].Name, typeString->Buffer, FALSE)) + { + context->EditingInfo->Type = TypeEntries[i].Type; + break; + } + } + + if (!PhEqualString2(subTypeString, L"Custom", FALSE)) + { + if (context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM) + { + for (i = 0; i < sizeof(SubTypeEntries) / sizeof(SUBTYPE_ENTRY); i++) + { + if ( + SubTypeEntries[i].Type == context->EditingInfo->Type && + PhEqualString2(subTypeString, SubTypeEntries[i].Name, FALSE) + ) + { + context->EditingInfo->SubtypeBuffer = *SubTypeEntries[i].Guid; + context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer; + break; + } + } + } + else + { + if (!EspLookupEtwPublisherGuid(&subTypeString->sr, &context->EditingInfo->SubtypeBuffer)) + { + PhShowError(hwndDlg, L"Unable to find the ETW publisher GUID."); + goto DoNotClose; + } + + context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer; + } + } + else + { + UNICODE_STRING guidString; + + PhStringRefToUnicodeString(&customSubTypeString->sr, &guidString); + + // Trim whitespace. + + while (guidString.Length != 0 && *guidString.Buffer == ' ') + { + guidString.Buffer++; + guidString.Length -= 2; + } + + while (guidString.Length != 0 && guidString.Buffer[guidString.Length / 2 - 1] == ' ') + { + guidString.Length -= 2; + } + + if (NT_SUCCESS(RtlGUIDFromString(&guidString, &context->EditingInfo->SubtypeBuffer))) + { + context->EditingInfo->Subtype = &context->EditingInfo->SubtypeBuffer; + } + else + { + PhShowError(hwndDlg, L"The custom subtype is invalid. Please ensure that the string is a valid GUID: \"{x-x-x-x-x}\"."); + goto DoNotClose; + } + } + + if (PhEqualString2(actionString, L"Start", FALSE)) + context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_START; + else + context->EditingInfo->Action = SERVICE_TRIGGER_ACTION_SERVICE_STOP; + + if ( + context->EditingInfo->DataList && + context->EditingInfo->DataList->Count != 0 && + context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_DEVICE_INTERFACE_ARRIVAL && + context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT && + context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT && + context->EditingInfo->Type != SERVICE_TRIGGER_TYPE_CUSTOM + ) + { + // This trigger has data items, but the trigger type doesn't allow them. + if (PhShowMessage( + hwndDlg, + MB_OKCANCEL | MB_ICONWARNING, + L"The trigger type \"%s\" does not allow data items to be configured. " + L"If you continue, they will be removed.", + typeString->Buffer + ) != IDOK) + { + goto DoNotClose; + } + + for (i = 0; i < context->EditingInfo->DataList->Count; i++) + { + EspDestroyTriggerData(context->EditingInfo->DataList->Items[i]); + } + + PhClearReference(&context->EditingInfo->DataList); + } + + EndDialog(hwndDlg, IDOK); + +DoNotClose: + PhDeleteAutoPool(&autoPool); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == lvHandle) + { + if (ListView_GetSelectedCount(lvHandle) == 1) + { + PES_TRIGGER_DATA data = PhGetSelectedListViewItemParam(GetDlgItem(hwndDlg, IDC_LIST)); + + // Editing binary data is not supported. + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), data && data->Type == SERVICE_TRIGGER_DATA_TYPE_STRING); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), TRUE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_EDIT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); + } + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == lvHandle) + { + SendMessage(hwndDlg, WM_COMMAND, IDC_EDIT, 0); + } + } + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK ValueDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PES_TRIGGER_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PES_TRIGGER_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PES_TRIGGER_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + SetDlgItemText(hwndDlg, IDC_VALUES, context->EditingValue->Buffer); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hwndDlg, IDC_VALUES), TRUE); + Edit_SetSel(GetDlgItem(hwndDlg, IDC_VALUES), 0, -1); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + PhMoveReference(&context->EditingValue, PhGetWindowText(GetDlgItem(hwndDlg, IDC_VALUES))); + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedServices/triggpg.c b/plugins/ExtendedServices/triggpg.c new file mode 100644 index 0000000..0188dda --- /dev/null +++ b/plugins/ExtendedServices/triggpg.c @@ -0,0 +1,182 @@ +/* + * Process Hacker Extended Services - + * triggers page + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "extsrv.h" + +typedef struct _SERVICE_TRIGGERS_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + HWND TriggersLv; + struct _ES_TRIGGER_CONTEXT *TriggerContext; +} SERVICE_TRIGGERS_CONTEXT, *PSERVICE_TRIGGERS_CONTEXT; + +NTSTATUS EspLoadTriggerInfo( + _In_ HWND hwndDlg, + _In_ PSERVICE_TRIGGERS_CONTEXT Context + ) +{ + NTSTATUS status = STATUS_SUCCESS; + SC_HANDLE serviceHandle; + + if (!(serviceHandle = PhOpenService(Context->ServiceItem->Name->Buffer, SERVICE_QUERY_CONFIG))) + return NTSTATUS_FROM_WIN32(GetLastError()); + + EsLoadServiceTriggerInfo(Context->TriggerContext, serviceHandle); + CloseServiceHandle(serviceHandle); + + return status; +} + +INT_PTR CALLBACK EspServiceTriggersDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_TRIGGERS_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_TRIGGERS_CONTEXT)); + memset(context, 0, sizeof(SERVICE_TRIGGERS_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_TRIGGERS_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + NTSTATUS status; + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + HWND triggersLv; + + context->ServiceItem = serviceItem; + context->TriggersLv = triggersLv = GetDlgItem(hwndDlg, IDC_TRIGGERS); + context->TriggerContext = EsCreateServiceTriggerContext( + context->ServiceItem, + hwndDlg, + triggersLv + ); + + status = EspLoadTriggerInfo(hwndDlg, context); + + if (!NT_SUCCESS(status)) + { + PhShowWarning(hwndDlg, L"Unable to query service trigger information: %s", + ((PPH_STRING)PH_AUTO(PhGetNtMessage(status)))->Buffer); + } + } + break; + case WM_DESTROY: + { + EsDestroyServiceTriggerContext(context->TriggerContext); + PhFree(context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_NEW: + if (context->TriggerContext) + EsHandleEventServiceTrigger(context->TriggerContext, ES_TRIGGER_EVENT_NEW); + break; + case IDC_EDIT: + if (context->TriggerContext) + EsHandleEventServiceTrigger(context->TriggerContext, ES_TRIGGER_EVENT_EDIT); + break; + case IDC_DELETE: + if (context->TriggerContext) + EsHandleEventServiceTrigger(context->TriggerContext, ES_TRIGGER_EVENT_DELETE); + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_KILLACTIVE: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); + } + return TRUE; + case PSN_APPLY: + { + ULONG win32Result = 0; + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + + if (!EsSaveServiceTriggerInfo(context->TriggerContext, &win32Result)) + { + if (win32Result == ERROR_CANCELLED || (PhShowMessage( + hwndDlg, + MB_ICONERROR | MB_RETRYCANCEL, + L"Unable to change service trigger information: %s", + ((PPH_STRING)PH_AUTO(PhGetWin32Message(win32Result)))->Buffer + ) == IDRETRY)) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID); + } + } + + return TRUE; + } + break; + case LVN_ITEMCHANGED: + { + if (header->hwndFrom == context->TriggersLv && context->TriggerContext) + { + EsHandleEventServiceTrigger(context->TriggerContext, ES_TRIGGER_EVENT_SELECTIONCHANGED); + } + } + break; + case NM_DBLCLK: + { + if (header->hwndFrom == context->TriggersLv && context->TriggerContext) + { + EsHandleEventServiceTrigger(context->TriggerContext, ES_TRIGGER_EVENT_EDIT); + } + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/CHANGELOG.txt b/plugins/ExtendedTools/CHANGELOG.txt new file mode 100644 index 0000000..07591bd --- /dev/null +++ b/plugins/ExtendedTools/CHANGELOG.txt @@ -0,0 +1,65 @@ +1.16 + * Fixed "No process" disk event bug + +1.15 + * Added tray icon mini info window support + * Improved automatic GPU node selection + +1.14 + * Added Disk and Network graphs for all processes + +1.13 + * Added GPU graphs for all processes + * Can now use the search box in the Disk tab + * Improved kernel logger handling on Windows 8 + +1.12 + * Improved support for multiple GPUs (again) + * GPU column now respects "Include CPU usage of children" option + +1.11 + * Fixed network graph scaling + +1.10 + * Improved support for multiple GPUs + +1.9 + * Added GPU node selection + * Fixed incorrect GPU usage calculation + +1.8 + * Added support for new system information window + * Added Disk, Network and GPU tray icons + * Added support for custom fonts in the Disk tab + +1.7 + * Added GPU monitoring + * Added rate columns for disk and network I/O + +1.6 + * Updated disk monitoring for Windows 8 + * Updated memory list information for Windows 8 + +1.5 + * Added Disk tab + * Added Hard Faults, Hard Faults Delta and Peak Threads columns + to process tree list + * Added Firewall Status column to network list + +1.4 + * Added ETW columns for processes and network connections + +1.3 + * Fixed WS Watch refresh bug + +1.2 + * Added WS Watch + * Added display of standby repurposed pages + * Added Empty commands for memory lists + * Fixed Disk and Network page crash + +1.1 + * Fixed ETW crash + +1.0 + * Initial release \ No newline at end of file diff --git a/plugins/ExtendedTools/ExtendedTools.rc b/plugins/ExtendedTools/ExtendedTools.rc new file mode 100644 index 0000000..1103438 --- /dev/null +++ b/plugins/ExtendedTools/ExtendedTools.rc @@ -0,0 +1,537 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,16,0,0 + PRODUCTVERSION 1,16,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Extended Tools plugin for Process Hacker" + VALUE "FileVersion", "1.16" + VALUE "InternalName", "ExtendedTools" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "ExtendedTools.dll" + VALUE "ProductName", "Extended Tools plugin for Process Hacker" + VALUE "ProductVersion", "1.16" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_UNLOADEDDLLS DIALOGEX 0, 0, 342, 265 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Unloaded Modules" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,328,233 + PUSHBUTTON "Refresh",IDC_REFRESH,7,244,50,14 + DEFPUSHBUTTON "Close",IDOK,285,244,50,14 +END + +IDD_OBJALPCPORT DIALOGEX 0, 0, 160, 101 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "ALPC Port" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Sequence number: Unknown",IDC_SEQUENCENUMBER,7,7,146,8 + LTEXT "Port context: Unknown",IDC_PORTCONTEXT,7,19,146,8 +END + +IDD_OBJTPWORKERFACTORY DIALOGEX 0, 0, 186, 101 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Worker Factory" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Worker thread start: Unknown",IDC_WORKERTHREADSTART,7,7,172,8,SS_ENDELLIPSIS + LTEXT "Worker thread context: Unknown",IDC_WORKERTHREADCONTEXT,7,18,172,8,SS_ENDELLIPSIS +END + +IDD_MODSERVICES DIALOGEX 0, 0, 276, 209 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Services" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Static",IDC_MESSAGE,7,7,262,8 + LTEXT "Static",IDC_SERVICES_LAYOUT,7,22,262,162,NOT WS_VISIBLE + DEFPUSHBUTTON "Close",IDOK,219,188,50,14 +END + +IDD_PROCDISKNET DIALOGEX 0, 0, 265, 158 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Disk and Network" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Disk",IDC_GROUPDISK,7,7,251,69 + GROUPBOX "Network",IDC_GROUPNETWORK,7,81,251,69 +END + +IDD_SYSINFO_DISKPANEL DIALOGEX 0, 0, 146, 58 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Disk I/O",IDC_STATIC,0,0,145,58 + LTEXT "Reads delta",IDC_STATIC,8,11,40,8 + LTEXT "Read bytes delta",IDC_STATIC,8,22,56,8 + LTEXT "Writes delta",IDC_STATIC,8,33,40,8 + LTEXT "Write bytes delta",IDC_STATIC,8,44,57,8 + RTEXT "Static",IDC_ZREADSDELTA_V,84,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZREADBYTESDELTA_V,78,22,60,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITESDELTA_V,84,33,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITEBYTESDELTA_V,82,44,56,8,SS_ENDELLIPSIS +END + +IDD_OPTIONS DIALOGEX 0, 0, 215, 77 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Changes to settings will require a restart of Process Hacker.",IDC_STATIC,7,7,193,8 + CONTROL "Enable Disk and Network monitoring",IDC_ENABLEETWMONITOR, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,20,130,10 + CONTROL "Enable GPU monitoring",IDC_ENABLEGPUMONITOR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,32,88,10 + DEFPUSHBUTTON "OK",IDOK,104,56,50,14 + PUSHBUTTON "Cancel",IDCANCEL,158,56,50,14 +END + +IDD_WSWATCH DIALOGEX 0, 0, 325, 266 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "WS Watch" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Working set watch allows you to monitor page faults that occur in a process. You must enable WS watch for the process to start the monitoring. Once WS watch is enabled, it cannot be disabled.",IDC_STATIC,7,7,311,27 + PUSHBUTTON "Enable",IDC_ENABLE,7,37,50,14 + LTEXT "WS watch is enabled.",IDC_WSWATCHENABLED,63,40,119,8,NOT WS_VISIBLE + LTEXT "Page faults:",IDC_STATIC,7,54,40,8 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,65,311,175 + DEFPUSHBUTTON "Close",IDOK,268,245,50,14 +END + +IDD_SYSINFO_GPU DIALOGEX 0, 0, 315, 186 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "GPU" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "GPU",IDC_TITLE,0,0,72,21 + RTEXT "GPU name",IDC_GPUNAME,83,4,232,16,SS_WORDELLIPSIS + LTEXT "Panel layout",IDC_PANEL_LAYOUT,0,130,315,55,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,102,NOT WS_VISIBLE + LTEXT "GPU:",IDC_GPU_L,73,0,17,8 + LTEXT "Dedicated memory:",IDC_DEDICATED_L,73,5,63,8 + LTEXT "Shared memory:",IDC_SHARED_L,74,11,54,8 +END + +IDD_SYSINFO_GPUPANEL DIALOGEX 0, 0, 246, 54 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Dedicated memory",IDC_STATIC,0,0,118,36 + LTEXT "Current",IDC_STATIC,8,11,26,8 + LTEXT "Limit",IDC_STATIC,8,22,15,8 + RTEXT "Static",IDC_ZDEDICATEDCURRENT_V,55,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZDEDICATEDLIMIT_V,55,22,54,8,SS_ENDELLIPSIS + GROUPBOX "Shared memory",IDC_STATIC,122,0,124,36 + LTEXT "Current",IDC_STATIC,130,11,26,8 + LTEXT "Limit",IDC_STATIC,130,23,15,8 + RTEXT "Static",IDC_ZSHAREDCURRENT_V,182,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSHAREDLIMIT_V,182,23,54,8,SS_ENDELLIPSIS + PUSHBUTTON "Nodes",IDC_NODES,152,40,50,14 + LTEXT "Select nodes used for GPU usage calculations:",IDC_STATIC,0,43,148,8 +END + +IDD_PROCGPU DIALOGEX 0, 0, 369, 237 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GPU" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "GPU",IDC_GROUPGPU,7,7,355,69 + GROUPBOX "Dedicated memory",IDC_GROUPMEM,7,81,355,69 + GROUPBOX "Shared memory",IDC_GROUPSHARED,7,160,355,69 +END + +IDD_SYSINFO_DISK DIALOGEX 0, 0, 315, 186 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Disk" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Disk",IDC_TITLE,0,0,72,21 + LTEXT "Panel layout",IDC_PANEL_LAYOUT,0,124,315,61,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,99,NOT WS_VISIBLE +END + +IDD_SYSINFO_NETPANEL DIALOGEX 0, 0, 145, 58 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Network I/O",IDC_STATIC,0,0,145,58 + LTEXT "Receives delta",IDC_STATIC,8,11,48,8 + LTEXT "Receive bytes delta",IDC_STATIC,8,22,65,8 + LTEXT "Sends delta",IDC_STATIC,8,33,39,8 + LTEXT "Send bytes delta",IDC_STATIC,8,44,56,8 + RTEXT "Static",IDC_ZRECEIVESDELTA_V,84,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZRECEIVEBYTESDELTA_V,78,22,60,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSENDSDELTA_V,84,33,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSENDBYTESDELTA_V,82,44,56,8,SS_ENDELLIPSIS +END + +IDD_SYSINFO_NET DIALOGEX 0, 0, 315, 186 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Network" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Network",IDC_TITLE,0,0,72,21 + LTEXT "Panel layout",IDC_PANEL_LAYOUT,0,124,315,61,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,99,NOT WS_VISIBLE +END + +IDD_GPUNODES DIALOGEX 0, 0, 317, 183 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "GPU Nodes" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Select the nodes that will be included in GPU usage calculations.",IDC_INSTRUCTION,7,7,204,8 + LTEXT "Graph layout",IDC_LAYOUT,7,21,303,137,NOT WS_VISIBLE + CONTROL "Example checkbox",IDC_EXAMPLE,"Button",BS_AUTOCHECKBOX | NOT WS_VISIBLE | WS_TABSTOP,7,166,75,10 + DEFPUSHBUTTON "OK",IDOK,260,162,50,14 +END + +IDD_PROCGPU_PANEL DIALOGEX 0, 0, 248, 46 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Memory",IDC_STATIC,2,0,118,46 + GROUPBOX "Nodes",IDC_STATIC,124,0,124,46 + LTEXT "Running time",IDC_STATIC,132,11,44,8 + LTEXT "Context switches",IDC_STATIC,132,22,57,8 + RTEXT "Static",IDC_ZRUNNINGTIME_V,184,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCONTEXTSWITCHES_V,200,22,38,8,SS_ENDELLIPSIS + LTEXT "Total segments",IDC_STATIC,10,11,50,8 + RTEXT "Static",IDC_ZTOTALSEGMENTS_V,73,11,38,8,SS_ENDELLIPSIS + LTEXT "Total nodes",IDC_STATIC,132,33,39,8 + RTEXT "Static",IDC_ZTOTALNODES_V,200,33,38,8,SS_ENDELLIPSIS + PUSHBUTTON "More",IDC_GPUDETAILS,7,27,50,14 +END + +IDD_DISKTABERROR DIALOGEX 0, 0, 309, 176 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +EXSTYLE WS_EX_TRANSPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Disk monitoring requires Process Hacker to be restarted with administrative privileges.",IDC_ERROR,16,14,275,8 + PUSHBUTTON "Restart",IDC_RESTART,20,28,50,14 +END + +IDD_PROCDISKNET_PANEL DIALOGEX 0, 0, 248, 82 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Disk I/O",IDC_STATIC,2,0,118,82 + LTEXT "Reads",IDC_STATIC,10,11,21,8 + LTEXT "Read bytes",IDC_STATIC,10,22,38,8 + LTEXT "Read bytes delta",IDC_STATIC,10,33,56,8 + LTEXT "Writes",IDC_STATIC,10,44,22,8 + LTEXT "Write bytes",IDC_STATIC,10,55,38,8 + LTEXT "Write bytes delta",IDC_STATIC,10,66,57,8 + RTEXT "Static",IDC_ZREADS_V,57,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZREADBYTES_V,73,22,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZREADBYTESDELTA_V,73,33,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITES_V,57,44,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITEBYTES_V,73,55,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITEBYTESDELTA_V,73,66,38,8,SS_ENDELLIPSIS + GROUPBOX "Network I/O",IDC_STATIC,123,0,124,82 + LTEXT "Receives",IDC_STATIC,132,11,30,8 + LTEXT "Receive bytes",IDC_STATIC,132,23,46,8 + LTEXT "Receive bytes delta",IDC_STATIC,132,34,65,8 + LTEXT "Sends",IDC_STATIC,132,46,20,8 + LTEXT "Send bytes",IDC_STATIC,132,56,37,8 + LTEXT "Send bytes delta",IDC_STATIC,132,67,56,8 + RTEXT "Static",IDC_ZRECEIVES_V,184,11,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZRECEIVEBYTES_V,200,23,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZRECEIVEBYTESDELTA_V,200,34,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSENDS_V,184,46,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSENDBYTES_V,200,56,38,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSENDBYTESDELTA_V,200,67,38,8,SS_ENDELLIPSIS +END + +IDD_PROCGPU_DETAILS DIALOGEX 0, 0, 181, 155 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "GPU Details" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDCANCEL,124,134,50,14 + GROUPBOX "System memory",IDC_STATIC,7,7,167,125 + LTEXT "Dedicated",IDC_STATIC,15,19,33,8 + LTEXT "Shared",IDC_STATIC,15,30,24,8 + RTEXT "Static",IDC_ZDEDICATEDCOMMITTED_V,111,19,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSHAREDCOMMITTED_V,127,30,38,8,SS_ENDELLIPSIS + LTEXT "Total allocated",IDC_STATIC,15,41,48,8 + LTEXT "Total reserved",IDC_STATIC,15,51,50,8 + LTEXT "Write combined allocated",IDC_STATIC,15,62,83,8 + LTEXT "Write combined reserved",IDC_STATIC,15,73,84,8 + RTEXT "Static",IDC_ZTOTALALLOCATED_V,111,41,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZTOTALRESERVED_V,111,51,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITECOMBINEDALLOCATED_V,111,62,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZWRITECOMBINEDRESERVED_V,111,73,54,8,SS_ENDELLIPSIS + LTEXT "Cached allocated",IDC_STATIC,15,84,56,8 + LTEXT "Cached reserved",IDC_STATIC,15,95,58,8 + LTEXT "Section allocated",IDC_STATIC,15,106,56,8 + LTEXT "Section reserved",IDC_STATIC,15,117,57,8 + RTEXT "Static",IDC_ZCACHEDALLOCATED_V,111,84,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZCACHEDRESERVED_V,111,95,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSECTIONALLOCATED_V,111,106,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_ZSECTIONRESERVED_V,111,117,54,8,SS_ENDELLIPSIS +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_UNLOADEDDLLS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 335 + TOPMARGIN, 7 + BOTTOMMARGIN, 258 + END + + IDD_OBJALPCPORT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 153 + TOPMARGIN, 7 + BOTTOMMARGIN, 94 + END + + IDD_OBJTPWORKERFACTORY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 94 + END + + IDD_MODSERVICES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 269 + TOPMARGIN, 7 + BOTTOMMARGIN, 202 + END + + IDD_PROCDISKNET, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 258 + TOPMARGIN, 7 + BOTTOMMARGIN, 151 + END + + IDD_SYSINFO_DISKPANEL, DIALOG + BEGIN + END + + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 208 + TOPMARGIN, 7 + BOTTOMMARGIN, 70 + END + + IDD_WSWATCH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 318 + TOPMARGIN, 7 + BOTTOMMARGIN, 259 + END + + IDD_SYSINFO_GPU, DIALOG + BEGIN + END + + IDD_SYSINFO_GPUPANEL, DIALOG + BEGIN + END + + IDD_PROCGPU, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 362 + TOPMARGIN, 7 + BOTTOMMARGIN, 230 + END + + IDD_SYSINFO_DISK, DIALOG + BEGIN + END + + IDD_SYSINFO_NETPANEL, DIALOG + BEGIN + END + + IDD_SYSINFO_NET, DIALOG + BEGIN + END + + IDD_GPUNODES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 310 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_PROCGPU_PANEL, DIALOG + BEGIN + END + + IDD_DISKTABERROR, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 169 + END + + IDD_PROCDISKNET_PANEL, DIALOG + BEGIN + END + + IDD_PROCGPU_DETAILS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 174 + TOPMARGIN, 7 + BOTTOMMARGIN, 148 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_DISK MENU +BEGIN + POPUP "Disk" + BEGIN + MENUITEM "&Go to process", ID_DISK_GOTOPROCESS + MENUITEM SEPARATOR + MENUITEM "Open &file location\aEnter", ID_DISK_OPENFILELOCATION + MENUITEM "P&roperties", ID_DISK_PROPERTIES + MENUITEM "&Copy\aCtrl+C", ID_DISK_COPY + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OBJTPWORKERFACTORY AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCGPU_DETAILS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROCGPU_PANEL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/ExtendedTools/ExtendedTools.vcxproj b/plugins/ExtendedTools/ExtendedTools.vcxproj new file mode 100644 index 0000000..aeee4e7 --- /dev/null +++ b/plugins/ExtendedTools/ExtendedTools.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3437FD16-A3BF-4938-883A-EB0DF1584A2A} + ExtendedTools + Win32Proj + ExtendedTools + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + cfgmgr32.lib;%(AdditionalDependencies) + cfgmgr32.dll;%(DelayLoadDLLs) + + + + + cfgmgr32.lib;%(AdditionalDependencies) + cfgmgr32.dll;%(DelayLoadDLLs) + + + + + cfgmgr32.lib;%(AdditionalDependencies) + cfgmgr32.dll;%(DelayLoadDLLs) + + + + + cfgmgr32.lib;%(AdditionalDependencies) + cfgmgr32.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/ExtendedTools/ExtendedTools.vcxproj.filters b/plugins/ExtendedTools/ExtendedTools.vcxproj.filters new file mode 100644 index 0000000..802aa74 --- /dev/null +++ b/plugins/ExtendedTools/ExtendedTools.vcxproj.filters @@ -0,0 +1,128 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/ExtendedTools/d3dkmt.h b/plugins/ExtendedTools/d3dkmt.h new file mode 100644 index 0000000..9904733 --- /dev/null +++ b/plugins/ExtendedTools/d3dkmt.h @@ -0,0 +1,482 @@ +#ifndef _D3DKMT_H +#define _D3DKMT_H + +// D3D definitions + +typedef ULONG D3DKMT_HANDLE; + +typedef struct _D3DKMT_OPENADAPTERFROMDEVICENAME +{ + _In_ PCWSTR pDeviceName; + _Out_ D3DKMT_HANDLE hAdapter; + _Out_ LUID AdapterLuid; +} D3DKMT_OPENADAPTERFROMDEVICENAME; + +typedef struct _D3DKMT_CLOSEADAPTER +{ + _In_ D3DKMT_HANDLE hAdapter; +} D3DKMT_CLOSEADAPTER; + +typedef enum _D3DKMT_QUERYRESULT_PREEMPTION_ATTEMPT_RESULT +{ + D3DKMT_PreemptionAttempt = 0, + D3DKMT_PreemptionAttemptSuccess = 1, + D3DKMT_PreemptionAttemptMissNoCommand = 2, + D3DKMT_PreemptionAttemptMissNotEnabled = 3, + D3DKMT_PreemptionAttemptMissNextFence = 4, + D3DKMT_PreemptionAttemptMissPagingCommand = 5, + D3DKMT_PreemptionAttemptMissSplittedCommand = 6, + D3DKMT_PreemptionAttemptMissFenceCommand= 7, + D3DKMT_PreemptionAttemptMissRenderPendingFlip = 8, + D3DKMT_PreemptionAttemptMissNotMakingProgress = 9, + D3DKMT_PreemptionAttemptMissLessPriority = 10, + D3DKMT_PreemptionAttemptMissRemainingQuantum = 11, + D3DKMT_PreemptionAttemptMissRemainingPreemptionQuantum = 12, + D3DKMT_PreemptionAttemptMissAlreadyPreempting = 13, + D3DKMT_PreemptionAttemptMissGlobalBlock = 14, + D3DKMT_PreemptionAttemptMissAlreadyRunning = 15, + D3DKMT_PreemptionAttemptStatisticsMax +} D3DKMT_QUERYRESULT_PREEMPTION_ATTEMPT_RESULT; + +typedef enum _D3DKMT_QUERYSTATISTICS_DMA_PACKET_TYPE +{ + D3DKMT_ClientRenderBuffer = 0, + D3DKMT_ClientPagingBuffer = 1, + D3DKMT_SystemPagingBuffer = 2, + D3DKMT_SystemPreemptionBuffer = 3, + D3DKMT_DmaPacketTypeMax +} D3DKMT_QUERYSTATISTICS_DMA_PACKET_TYPE; + +typedef enum _D3DKMT_QUERYSTATISTICS_QUEUE_PACKET_TYPE +{ + D3DKMT_RenderCommandBuffer = 0, + D3DKMT_DeferredCommandBuffer = 1, + D3DKMT_SystemCommandBuffer = 2, + D3DKMT_MmIoFlipCommandBuffer = 3, + D3DKMT_WaitCommandBuffer = 4, + D3DKMT_SignalCommandBuffer = 5, + D3DKMT_DeviceCommandBuffer = 6, + D3DKMT_SoftwareCommandBuffer = 7, + D3DKMT_QueuePacketTypeMax +} D3DKMT_QUERYSTATISTICS_QUEUE_PACKET_TYPE; + +typedef enum _D3DKMT_QUERYSTATISTICS_ALLOCATION_PRIORITY_CLASS +{ + D3DKMT_AllocationPriorityClassMinimum = 0, + D3DKMT_AllocationPriorityClassLow = 1, + D3DKMT_AllocationPriorityClassNormal = 2, + D3DKMT_AllocationPriorityClassHigh = 3, + D3DKMT_AllocationPriorityClassMaximum = 4, + D3DKMT_MaxAllocationPriorityClass +} D3DKMT_QUERYSTATISTICS_ALLOCATION_PRIORITY_CLASS; + +#define D3DKMT_QUERYSTATISTICS_SEGMENT_PREFERENCE_MAX 5 + +typedef struct _D3DKMT_QUERYSTATISTICS_COUNTER +{ + ULONG Count; + ULONGLONG Bytes; +} D3DKMT_QUERYSTATISTICS_COUNTER; + +typedef struct _D3DKMT_QUERYSTATISTICS_DMA_PACKET_TYPE_INFORMATION +{ + ULONG PacketSubmited; + ULONG PacketCompleted; + ULONG PacketPreempted; + ULONG PacketFaulted; +} D3DKMT_QUERYSTATISTICS_DMA_PACKET_TYPE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_QUEUE_PACKET_TYPE_INFORMATION +{ + ULONG PacketSubmited; + ULONG PacketCompleted; +} D3DKMT_QUERYSTATISTICS_QUEUE_PACKET_TYPE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_PACKET_INFORMATION +{ + D3DKMT_QUERYSTATISTICS_QUEUE_PACKET_TYPE_INFORMATION QueuePacket[D3DKMT_QueuePacketTypeMax]; + D3DKMT_QUERYSTATISTICS_DMA_PACKET_TYPE_INFORMATION DmaPacket[D3DKMT_DmaPacketTypeMax]; +} D3DKMT_QUERYSTATISTICS_PACKET_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_PREEMPTION_INFORMATION +{ + ULONG PreemptionCounter[D3DKMT_PreemptionAttemptStatisticsMax]; +} D3DKMT_QUERYSTATISTICS_PREEMPTION_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION +{ + LARGE_INTEGER RunningTime; // 100ns + ULONG ContextSwitch; + D3DKMT_QUERYSTATISTICS_PREEMPTION_INFORMATION PreemptionStatistics; + D3DKMT_QUERYSTATISTICS_PACKET_INFORMATION PacketStatistics; + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_NODE_INFORMATION +{ + D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION GlobalInformation; // global + D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION SystemInformation; // system thread + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_NODE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE_INFORMATION +{ + ULONG Frame; + ULONG CancelledFrame; + ULONG QueuedPresent; + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_VIDPNSOURCE_INFORMATION +{ + D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE_INFORMATION GlobalInformation; // global + D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE_INFORMATION SystemInformation; // system thread + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_VIDPNSOURCE_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATSTICS_REFERENCE_DMA_BUFFER +{ + ULONG NbCall; + ULONG NbAllocationsReferenced; + ULONG MaxNbAllocationsReferenced; + ULONG NbNULLReference; + ULONG NbWriteReference; + ULONG NbRenamedAllocationsReferenced; + ULONG NbIterationSearchingRenamedAllocation; + ULONG NbLockedAllocationReferenced; + ULONG NbAllocationWithValidPrepatchingInfoReferenced; + ULONG NbAllocationWithInvalidPrepatchingInfoReferenced; + ULONG NbDMABufferSuccessfullyPrePatched; + ULONG NbPrimariesReferencesOverflow; + ULONG NbAllocationWithNonPreferredResources; + ULONG NbAllocationInsertedInMigrationTable; +} D3DKMT_QUERYSTATSTICS_REFERENCE_DMA_BUFFER; + +typedef struct _D3DKMT_QUERYSTATSTICS_RENAMING +{ + ULONG NbAllocationsRenamed; + ULONG NbAllocationsShrinked; + ULONG NbRenamedBuffer; + ULONG MaxRenamingListLength; + ULONG NbFailuresDueToRenamingLimit; + ULONG NbFailuresDueToCreateAllocation; + ULONG NbFailuresDueToOpenAllocation; + ULONG NbFailuresDueToLowResource; + ULONG NbFailuresDueToNonRetiredLimit; +} D3DKMT_QUERYSTATSTICS_RENAMING; + +typedef struct _D3DKMT_QUERYSTATSTICS_PREPRATION +{ + ULONG BroadcastStall; + ULONG NbDMAPrepared; + ULONG NbDMAPreparedLongPath; + ULONG ImmediateHighestPreparationPass; + D3DKMT_QUERYSTATISTICS_COUNTER AllocationsTrimmed; +} D3DKMT_QUERYSTATSTICS_PREPRATION; + +typedef struct _D3DKMT_QUERYSTATSTICS_PAGING_FAULT +{ + D3DKMT_QUERYSTATISTICS_COUNTER Faults; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsFirstTimeAccess; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsReclaimed; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsMigration; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsIncorrectResource; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsLostContent; + D3DKMT_QUERYSTATISTICS_COUNTER FaultsEvicted; + D3DKMT_QUERYSTATISTICS_COUNTER AllocationsMEM_RESET; + D3DKMT_QUERYSTATISTICS_COUNTER AllocationsUnresetSuccess; + D3DKMT_QUERYSTATISTICS_COUNTER AllocationsUnresetFail; + ULONG AllocationsUnresetSuccessRead; + ULONG AllocationsUnresetFailRead; + + D3DKMT_QUERYSTATISTICS_COUNTER Evictions; + D3DKMT_QUERYSTATISTICS_COUNTER EvictionsDueToPreparation; + D3DKMT_QUERYSTATISTICS_COUNTER EvictionsDueToLock; + D3DKMT_QUERYSTATISTICS_COUNTER EvictionsDueToClose; + D3DKMT_QUERYSTATISTICS_COUNTER EvictionsDueToPurge; + D3DKMT_QUERYSTATISTICS_COUNTER EvictionsDueToSuspendCPUAccess; +} D3DKMT_QUERYSTATSTICS_PAGING_FAULT; + +typedef struct _D3DKMT_QUERYSTATSTICS_PAGING_TRANSFER +{ + ULONGLONG BytesFilled; + ULONGLONG BytesDiscarded; + ULONGLONG BytesMappedIntoAperture; + ULONGLONG BytesUnmappedFromAperture; + ULONGLONG BytesTransferredFromMdlToMemory; + ULONGLONG BytesTransferredFromMemoryToMdl; + ULONGLONG BytesTransferredFromApertureToMemory; + ULONGLONG BytesTransferredFromMemoryToAperture; +} D3DKMT_QUERYSTATSTICS_PAGING_TRANSFER; + +typedef struct _D3DKMT_QUERYSTATSTICS_SWIZZLING_RANGE +{ + ULONG NbRangesAcquired; + ULONG NbRangesReleased; +} D3DKMT_QUERYSTATSTICS_SWIZZLING_RANGE; + +typedef struct _D3DKMT_QUERYSTATSTICS_LOCKS +{ + ULONG NbLocks; + ULONG NbLocksWaitFlag; + ULONG NbLocksDiscardFlag; + ULONG NbLocksNoOverwrite; + ULONG NbLocksNoReadSync; + ULONG NbLocksLinearization; + ULONG NbComplexLocks; +} D3DKMT_QUERYSTATSTICS_LOCKS; + +typedef struct _D3DKMT_QUERYSTATSTICS_ALLOCATIONS +{ + D3DKMT_QUERYSTATISTICS_COUNTER Created; + D3DKMT_QUERYSTATISTICS_COUNTER Destroyed; + D3DKMT_QUERYSTATISTICS_COUNTER Opened; + D3DKMT_QUERYSTATISTICS_COUNTER Closed; + D3DKMT_QUERYSTATISTICS_COUNTER MigratedSuccess; + D3DKMT_QUERYSTATISTICS_COUNTER MigratedFail; + D3DKMT_QUERYSTATISTICS_COUNTER MigratedAbandoned; +} D3DKMT_QUERYSTATSTICS_ALLOCATIONS; + +typedef struct _D3DKMT_QUERYSTATSTICS_TERMINATIONS +{ + D3DKMT_QUERYSTATISTICS_COUNTER TerminatedShared; + D3DKMT_QUERYSTATISTICS_COUNTER TerminatedNonShared; + D3DKMT_QUERYSTATISTICS_COUNTER DestroyedShared; + D3DKMT_QUERYSTATISTICS_COUNTER DestroyedNonShared; +} D3DKMT_QUERYSTATSTICS_TERMINATIONS; + +typedef struct _D3DKMT_QUERYSTATISTICS_ADAPTER_INFORMATION +{ + ULONG NbSegments; + ULONG NodeCount; + ULONG VidPnSourceCount; + + ULONG VSyncEnabled; + ULONG TdrDetectedCount; + + LONGLONG ZeroLengthDmaBuffers; + ULONGLONG RestartedPeriod; + + D3DKMT_QUERYSTATSTICS_REFERENCE_DMA_BUFFER ReferenceDmaBuffer; + D3DKMT_QUERYSTATSTICS_RENAMING Renaming; + D3DKMT_QUERYSTATSTICS_PREPRATION Preparation; + D3DKMT_QUERYSTATSTICS_PAGING_FAULT PagingFault; + D3DKMT_QUERYSTATSTICS_PAGING_TRANSFER PagingTransfer; + D3DKMT_QUERYSTATSTICS_SWIZZLING_RANGE SwizzlingRange; + D3DKMT_QUERYSTATSTICS_LOCKS Locks; + D3DKMT_QUERYSTATSTICS_ALLOCATIONS Allocations; + D3DKMT_QUERYSTATSTICS_TERMINATIONS Terminations; + + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_ADAPTER_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_SYSTEM_MEMORY +{ + ULONGLONG BytesAllocated; + ULONGLONG BytesReserved; + ULONG SmallAllocationBlocks; + ULONG LargeAllocationBlocks; + ULONGLONG WriteCombinedBytesAllocated; + ULONGLONG WriteCombinedBytesReserved; + ULONGLONG CachedBytesAllocated; + ULONGLONG CachedBytesReserved; + ULONGLONG SectionBytesAllocated; + ULONGLONG SectionBytesReserved; +} D3DKMT_QUERYSTATISTICS_SYSTEM_MEMORY; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_INFORMATION +{ + ULONG NodeCount; + ULONG VidPnSourceCount; + + D3DKMT_QUERYSTATISTICS_SYSTEM_MEMORY SystemMemory; + + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_PROCESS_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_DMA_BUFFER +{ + D3DKMT_QUERYSTATISTICS_COUNTER Size; + ULONG AllocationListBytes; + ULONG PatchLocationListBytes; +} D3DKMT_QUERYSTATISTICS_DMA_BUFFER; + +typedef struct _D3DKMT_QUERYSTATISTICS_COMMITMENT_DATA +{ + ULONG64 TotalBytesEvictedFromProcess; + ULONG64 BytesBySegmentPreference[D3DKMT_QUERYSTATISTICS_SEGMENT_PREFERENCE_MAX]; +} D3DKMT_QUERYSTATISTICS_COMMITMENT_DATA; + +typedef struct _D3DKMT_QUERYSTATISTICS_POLICY +{ + ULONGLONG PreferApertureForRead[D3DKMT_MaxAllocationPriorityClass]; + ULONGLONG PreferAperture[D3DKMT_MaxAllocationPriorityClass]; + ULONGLONG MemResetOnPaging; + ULONGLONG RemovePagesFromWorkingSetOnPaging; + ULONGLONG MigrationEnabled; +} D3DKMT_QUERYSTATISTICS_POLICY; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER_INFORMATION +{ + ULONG NbSegments; + ULONG NodeCount; + ULONG VidPnSourceCount; + + ULONG VirtualMemoryUsage; + + D3DKMT_QUERYSTATISTICS_DMA_BUFFER DmaBuffer; + D3DKMT_QUERYSTATISTICS_COMMITMENT_DATA CommitmentData; + D3DKMT_QUERYSTATISTICS_POLICY _Policy; + + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_MEMORY +{ + ULONGLONG TotalBytesEvicted; + ULONG AllocsCommitted; + ULONG AllocsResident; +} D3DKMT_QUERYSTATISTICS_MEMORY; + +typedef struct _D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION_V1 +{ + ULONG CommitLimit; + ULONG BytesCommitted; + ULONG BytesResident; + + D3DKMT_QUERYSTATISTICS_MEMORY Memory; + + ULONG Aperture; // boolean + + ULONGLONG TotalBytesEvictedByPriority[D3DKMT_MaxAllocationPriorityClass]; + + ULONG64 SystemMemoryEndAddress; + struct + { + ULONG64 PreservedDuringStandby : 1; + ULONG64 PreservedDuringHibernate : 1; + ULONG64 PartiallyPreservedDuringHibernate : 1; + ULONG64 Reserved : 61; + } PowerFlags; + + ULONG64 Reserved[7]; +} D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION_V1; + +typedef struct _D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION +{ + ULONGLONG CommitLimit; + ULONGLONG BytesCommitted; + ULONGLONG BytesResident; + + D3DKMT_QUERYSTATISTICS_MEMORY Memory; + + ULONG Aperture; // boolean + + ULONGLONG TotalBytesEvictedByPriority[D3DKMT_MaxAllocationPriorityClass]; + + ULONG64 SystemMemoryEndAddress; + struct + { + ULONG64 PreservedDuringStandby : 1; + ULONG64 PreservedDuringHibernate : 1; + ULONG64 PartiallyPreservedDuringHibernate : 1; + ULONG64 Reserved : 61; + } PowerFlags; + + ULONG64 Reserved[6]; +} D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION; + +typedef struct _D3DKMT_QUERYSTATISTICS_VIDEO_MEMORY +{ + ULONG AllocsCommitted; + D3DKMT_QUERYSTATISTICS_COUNTER AllocsResidentInP[D3DKMT_QUERYSTATISTICS_SEGMENT_PREFERENCE_MAX]; + D3DKMT_QUERYSTATISTICS_COUNTER AllocsResidentInNonPreferred; + ULONGLONG TotalBytesEvictedDueToPreparation; +} D3DKMT_QUERYSTATISTICS_VIDEO_MEMORY; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_POLICY +{ + ULONGLONG UseMRU; +} D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_POLICY; + +typedef struct _D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_INFORMATION +{ + ULONGLONG BytesCommitted; + ULONGLONG MaximumWorkingSet; + ULONGLONG MinimumWorkingSet; + + ULONG NbReferencedAllocationEvictedInPeriod; + + D3DKMT_QUERYSTATISTICS_VIDEO_MEMORY VideoMemory; + D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_POLICY _Policy; + + ULONG64 Reserved[8]; +} D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_INFORMATION; + +typedef enum _D3DKMT_QUERYSTATISTICS_TYPE +{ + D3DKMT_QUERYSTATISTICS_ADAPTER = 0, + D3DKMT_QUERYSTATISTICS_PROCESS = 1, + D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER = 2, + D3DKMT_QUERYSTATISTICS_SEGMENT = 3, + D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT = 4, + D3DKMT_QUERYSTATISTICS_NODE = 5, + D3DKMT_QUERYSTATISTICS_PROCESS_NODE = 6, + D3DKMT_QUERYSTATISTICS_VIDPNSOURCE = 7, + D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE = 8 +} D3DKMT_QUERYSTATISTICS_TYPE; + +typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT +{ + ULONG SegmentId; +} D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT; + +typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_NODE +{ + ULONG NodeId; +} D3DKMT_QUERYSTATISTICS_QUERY_NODE; + +typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_VIDPNSOURCE +{ + ULONG VidPnSourceId; +} D3DKMT_QUERYSTATISTICS_QUERY_VIDPNSOURCE; + +typedef union _D3DKMT_QUERYSTATISTICS_RESULT +{ + D3DKMT_QUERYSTATISTICS_ADAPTER_INFORMATION AdapterInformation; + D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION_V1 SegmentInformationV1; // WIN7 + D3DKMT_QUERYSTATISTICS_SEGMENT_INFORMATION SegmentInformation; // WIN8 + D3DKMT_QUERYSTATISTICS_NODE_INFORMATION NodeInformation; + D3DKMT_QUERYSTATISTICS_VIDPNSOURCE_INFORMATION VidPnSourceInformation; + D3DKMT_QUERYSTATISTICS_PROCESS_INFORMATION ProcessInformation; + D3DKMT_QUERYSTATISTICS_PROCESS_ADAPTER_INFORMATION ProcessAdapterInformation; + D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT_INFORMATION ProcessSegmentInformation; + D3DKMT_QUERYSTATISTICS_PROCESS_NODE_INFORMATION ProcessNodeInformation; + D3DKMT_QUERYSTATISTICS_PROCESS_VIDPNSOURCE_INFORMATION ProcessVidPnSourceInformation; +} D3DKMT_QUERYSTATISTICS_RESULT; + +typedef struct _D3DKMT_QUERYSTATISTICS +{ + _In_ D3DKMT_QUERYSTATISTICS_TYPE Type; + _In_ LUID AdapterLuid; + _In_opt_ HANDLE hProcess; + _Out_ D3DKMT_QUERYSTATISTICS_RESULT QueryResult; + + union + { + _In_ D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT QuerySegment; + _In_ D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT QueryProcessSegment; + _In_ D3DKMT_QUERYSTATISTICS_QUERY_NODE QueryNode; + _In_ D3DKMT_QUERYSTATISTICS_QUERY_NODE QueryProcessNode; + _In_ D3DKMT_QUERYSTATISTICS_QUERY_VIDPNSOURCE QueryVidPnSource; + _In_ D3DKMT_QUERYSTATISTICS_QUERY_VIDPNSOURCE QueryProcessVidPnSource; + }; +} D3DKMT_QUERYSTATISTICS; + +// Function pointers + +typedef _Check_return_ NTSTATUS (APIENTRY *PFND3DKMT_OPENADAPTERFROMDEVICENAME)(_Inout_ D3DKMT_OPENADAPTERFROMDEVICENAME *); +typedef _Check_return_ NTSTATUS (APIENTRY *PFND3DKMT_CLOSEADAPTER)(_In_ const D3DKMT_CLOSEADAPTER *); +typedef _Check_return_ NTSTATUS (APIENTRY *PFND3DKMT_QUERYSTATISTICS)(_In_ const D3DKMT_QUERYSTATISTICS *); + +#endif diff --git a/plugins/ExtendedTools/disktab.c b/plugins/ExtendedTools/disktab.c new file mode 100644 index 0000000..d560e99 --- /dev/null +++ b/plugins/ExtendedTools/disktab.c @@ -0,0 +1,1176 @@ +/* + * Process Hacker Extended Tools - + * ETW disk monitoring + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwmon.h" +#include +#include "disktabp.h" + +static BOOLEAN DiskTreeNewCreated = FALSE; +static HWND DiskTreeNewHandle; +static ULONG DiskTreeNewSortColumn; +static PH_SORT_ORDER DiskTreeNewSortOrder; + +static PPH_HASHTABLE DiskNodeHashtable; // hashtable of all nodes +static PPH_LIST DiskNodeList; // list of all nodes + +static PH_CALLBACK_REGISTRATION DiskItemAddedRegistration; +static PH_CALLBACK_REGISTRATION DiskItemModifiedRegistration; +static PH_CALLBACK_REGISTRATION DiskItemRemovedRegistration; +static PH_CALLBACK_REGISTRATION DiskItemsUpdatedRegistration; +static BOOLEAN DiskNeedsRedraw = FALSE; + +static PH_TN_FILTER_SUPPORT FilterSupport; +static PTOOLSTATUS_INTERFACE ToolStatusInterface; +static PH_CALLBACK_REGISTRATION SearchChangedRegistration; + +VOID EtInitializeDiskTab( + VOID + ) +{ + PH_ADDITIONAL_TAB_PAGE tabPage; + PPH_ADDITIONAL_TAB_PAGE addedTabPage; + PPH_PLUGIN toolStatusPlugin; + + if (toolStatusPlugin = PhFindPlugin(TOOLSTATUS_PLUGIN_NAME)) + { + ToolStatusInterface = PhGetPluginInformation(toolStatusPlugin)->Interface; + + if (ToolStatusInterface->Version < TOOLSTATUS_INTERFACE_VERSION) + ToolStatusInterface = NULL; + } + + memset(&tabPage, 0, sizeof(PH_ADDITIONAL_TAB_PAGE)); + tabPage.Text = L"Disk"; + tabPage.CreateFunction = EtpDiskTabCreateFunction; + tabPage.Index = MAXINT; + tabPage.SelectionChangedCallback = EtpDiskTabSelectionChangedCallback; + tabPage.SaveContentCallback = EtpDiskTabSaveContentCallback; + tabPage.FontChangedCallback = EtpDiskTabFontChangedCallback; + addedTabPage = ProcessHacker_AddTabPage(PhMainWndHandle, &tabPage); + + if (ToolStatusInterface) + { + PTOOLSTATUS_TAB_INFO tabInfo; + + tabInfo = ToolStatusInterface->RegisterTabInfo(addedTabPage->Index); + tabInfo->BannerText = L"Search Disk"; + tabInfo->ActivateContent = EtpToolStatusActivateContent; + tabInfo->GetTreeNewHandle = EtpToolStatusGetTreeNewHandle; + } +} + +HWND NTAPI EtpDiskTabCreateFunction( + _In_ PVOID Context + ) +{ + HWND hwnd; + + if (EtEtwEnabled) + { + ULONG thinRows; + + thinRows = PhGetIntegerSetting(L"ThinRows") ? TN_STYLE_THIN_ROWS : 0; + hwnd = CreateWindow( + PH_TREENEW_CLASSNAME, + NULL, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_BORDER | TN_STYLE_ICONS | TN_STYLE_DOUBLE_BUFFERED | thinRows, + 0, + 0, + 3, + 3, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + + if (!hwnd) + return NULL; + } + else + { + return CreateDialog( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_DISKTABERROR), + PhMainWndHandle, + EtpDiskTabErrorDialogProc + ); + } + + DiskTreeNewCreated = TRUE; + + DiskNodeHashtable = PhCreateHashtable( + sizeof(PET_DISK_NODE), + EtpDiskNodeHashtableEqualFunction, + EtpDiskNodeHashtableHashFunction, + 100 + ); + DiskNodeList = PhCreateList(100); + + EtInitializeDiskTreeList(hwnd); + + PhRegisterCallback( + &EtDiskItemAddedEvent, + EtpDiskItemAddedHandler, + NULL, + &DiskItemAddedRegistration + ); + PhRegisterCallback( + &EtDiskItemModifiedEvent, + EtpDiskItemModifiedHandler, + NULL, + &DiskItemModifiedRegistration + ); + PhRegisterCallback( + &EtDiskItemRemovedEvent, + EtpDiskItemRemovedHandler, + NULL, + &DiskItemRemovedRegistration + ); + PhRegisterCallback( + &EtDiskItemsUpdatedEvent, + EtpDiskItemsUpdatedHandler, + NULL, + &DiskItemsUpdatedRegistration + ); + + SetCursor(LoadCursor(NULL, IDC_WAIT)); + EtInitializeDiskInformation(); + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + return hwnd; +} + +VOID NTAPI EtpDiskTabSelectionChangedCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ) +{ + if ((BOOLEAN)Parameter1) + { + if (DiskTreeNewHandle) + SetFocus(DiskTreeNewHandle); + } +} + +VOID NTAPI EtpDiskTabSaveContentCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ) +{ + PPH_FILE_STREAM fileStream = Parameter1; + ULONG mode = PtrToUlong(Parameter2); + + if (!EtEtwEnabled) + return; + + EtWriteDiskList(fileStream, mode); +} + +VOID NTAPI EtpDiskTabFontChangedCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ) +{ + if (DiskTreeNewHandle) + SendMessage(DiskTreeNewHandle, WM_SETFONT, (WPARAM)Parameter1, TRUE); +} + +BOOLEAN EtpDiskNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PET_DISK_NODE diskNode1 = *(PET_DISK_NODE *)Entry1; + PET_DISK_NODE diskNode2 = *(PET_DISK_NODE *)Entry2; + + return diskNode1->DiskItem == diskNode2->DiskItem; +} + +ULONG EtpDiskNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)(*(PET_DISK_NODE *)Entry)->DiskItem); +} + +VOID EtInitializeDiskTreeList( + _In_ HWND hwnd + ) +{ + DiskTreeNewHandle = hwnd; + PhSetControlTheme(DiskTreeNewHandle, L"explorer"); + SendMessage(TreeNew_GetTooltips(DiskTreeNewHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, 0x7fff); + + TreeNew_SetCallback(hwnd, EtpDiskTreeNewCallback, NULL); + + TreeNew_SetRedraw(hwnd, FALSE); + + // Default columns + PhAddTreeNewColumn(hwnd, ETDSTNC_NAME, TRUE, L"Name", 100, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumn(hwnd, ETDSTNC_FILE, TRUE, L"File", 400, PH_ALIGN_LEFT, 1, DT_PATH_ELLIPSIS); + PhAddTreeNewColumnEx(hwnd, ETDSTNC_READRATEAVERAGE, TRUE, L"Read rate average", 70, PH_ALIGN_RIGHT, 2, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, ETDSTNC_WRITERATEAVERAGE, TRUE, L"Write rate average", 70, PH_ALIGN_RIGHT, 3, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, ETDSTNC_TOTALRATEAVERAGE, TRUE, L"Total rate average", 70, PH_ALIGN_RIGHT, 4, DT_RIGHT, TRUE); + PhAddTreeNewColumnEx(hwnd, ETDSTNC_IOPRIORITY, TRUE, L"I/O priority", 70, PH_ALIGN_LEFT, 5, 0, TRUE); + PhAddTreeNewColumnEx(hwnd, ETDSTNC_RESPONSETIME, TRUE, L"Response time (ms)", 70, PH_ALIGN_RIGHT, 6, 0, TRUE); + + TreeNew_SetRedraw(hwnd, TRUE); + + TreeNew_SetSort(hwnd, ETDSTNC_TOTALRATEAVERAGE, DescendingSortOrder); + + EtLoadSettingsDiskTreeList(); + + PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, DiskNodeList); + + if (ToolStatusInterface) + { + PhRegisterCallback(ToolStatusInterface->SearchChangedEvent, EtpSearchChangedHandler, NULL, &SearchChangedRegistration); + PhAddTreeNewFilter(&FilterSupport, EtpSearchDiskListFilterCallback, NULL); + } +} + +VOID EtLoadSettingsDiskTreeList( + VOID + ) +{ + PH_INTEGER_PAIR sortSettings; + + PhCmLoadSettings(DiskTreeNewHandle, &PhaGetStringSetting(SETTING_NAME_DISK_TREE_LIST_COLUMNS)->sr); + + sortSettings = PhGetIntegerPairSetting(SETTING_NAME_DISK_TREE_LIST_SORT); + TreeNew_SetSort(DiskTreeNewHandle, (ULONG)sortSettings.X, (PH_SORT_ORDER)sortSettings.Y); +} + +VOID EtSaveSettingsDiskTreeList( + VOID + ) +{ + PPH_STRING settings; + PH_INTEGER_PAIR sortSettings; + ULONG sortColumn; + PH_SORT_ORDER sortOrder; + + if (!DiskTreeNewCreated) + return; + + settings = PH_AUTO(PhCmSaveSettings(DiskTreeNewHandle)); + PhSetStringSetting2(SETTING_NAME_DISK_TREE_LIST_COLUMNS, &settings->sr); + + TreeNew_GetSort(DiskTreeNewHandle, &sortColumn, &sortOrder); + sortSettings.X = sortColumn; + sortSettings.Y = sortOrder; + PhSetIntegerPairSetting(SETTING_NAME_DISK_TREE_LIST_SORT, sortSettings); +} + +PET_DISK_NODE EtAddDiskNode( + _In_ PET_DISK_ITEM DiskItem + ) +{ + PET_DISK_NODE diskNode; + + diskNode = PhAllocate(sizeof(ET_DISK_NODE)); + memset(diskNode, 0, sizeof(ET_DISK_NODE)); + PhInitializeTreeNewNode(&diskNode->Node); + + PhSetReference(&diskNode->DiskItem, DiskItem); + + memset(diskNode->TextCache, 0, sizeof(PH_STRINGREF) * ETDSTNC_MAXIMUM); + diskNode->Node.TextCache = diskNode->TextCache; + diskNode->Node.TextCacheSize = ETDSTNC_MAXIMUM; + + diskNode->ProcessNameText = EtpGetDiskItemProcessName(DiskItem); + + PhAddEntryHashtable(DiskNodeHashtable, &diskNode); + PhAddItemList(DiskNodeList, diskNode); + + if (FilterSupport.NodeList) + diskNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &diskNode->Node); + + TreeNew_NodesStructured(DiskTreeNewHandle); + + return diskNode; +} + +PET_DISK_NODE EtFindDiskNode( + _In_ PET_DISK_ITEM DiskItem + ) +{ + ET_DISK_NODE lookupDiskNode; + PET_DISK_NODE lookupDiskNodePtr = &lookupDiskNode; + PET_DISK_NODE *diskNode; + + lookupDiskNode.DiskItem = DiskItem; + + diskNode = (PET_DISK_NODE *)PhFindEntryHashtable( + DiskNodeHashtable, + &lookupDiskNodePtr + ); + + if (diskNode) + return *diskNode; + else + return NULL; +} + +VOID EtRemoveDiskNode( + _In_ PET_DISK_NODE DiskNode + ) +{ + ULONG index; + + // Remove from the hashtable/list and cleanup. + + PhRemoveEntryHashtable(DiskNodeHashtable, &DiskNode); + + if ((index = PhFindItemList(DiskNodeList, DiskNode)) != -1) + PhRemoveItemList(DiskNodeList, index); + + if (DiskNode->ProcessNameText) PhDereferenceObject(DiskNode->ProcessNameText); + if (DiskNode->ReadRateAverageText) PhDereferenceObject(DiskNode->ReadRateAverageText); + if (DiskNode->WriteRateAverageText) PhDereferenceObject(DiskNode->WriteRateAverageText); + if (DiskNode->TotalRateAverageText) PhDereferenceObject(DiskNode->TotalRateAverageText); + if (DiskNode->ResponseTimeText) PhDereferenceObject(DiskNode->ResponseTimeText); + if (DiskNode->TooltipText) PhDereferenceObject(DiskNode->TooltipText); + + PhDereferenceObject(DiskNode->DiskItem); + + PhFree(DiskNode); + + TreeNew_NodesStructured(DiskTreeNewHandle); +} + +VOID EtUpdateDiskNode( + _In_ PET_DISK_NODE DiskNode + ) +{ + memset(DiskNode->TextCache, 0, sizeof(PH_STRINGREF) * ETDSTNC_MAXIMUM); + + PhInvalidateTreeNewNode(&DiskNode->Node, TN_CACHE_ICON); + TreeNew_NodesStructured(DiskTreeNewHandle); +} + +#define SORT_FUNCTION(Column) EtpDiskTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl EtpDiskTreeNewCompare##Column( \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PET_DISK_NODE node1 = *(PET_DISK_NODE *)_elem1; \ + PET_DISK_NODE node2 = *(PET_DISK_NODE *)_elem2; \ + PET_DISK_ITEM diskItem1 = node1->DiskItem; \ + PET_DISK_ITEM diskItem2 = node2->DiskItem; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + if (sortResult == 0) \ + sortResult = PhCompareString(diskItem1->FileNameWin32, diskItem2->FileNameWin32, TRUE); \ + \ + return PhModifySort(sortResult, DiskTreeNewSortOrder); \ +} + +BEGIN_SORT_FUNCTION(Process) +{ + sortResult = PhCompareString(node1->ProcessNameText, node2->ProcessNameText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(File) +{ + sortResult = PhCompareString(diskItem1->FileNameWin32, diskItem2->FileNameWin32, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ReadRateAverage) +{ + sortResult = uint64cmp(diskItem1->ReadAverage, diskItem2->ReadAverage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(WriteRateAverage) +{ + sortResult = uint64cmp(diskItem1->WriteAverage, diskItem2->WriteAverage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(TotalRateAverage) +{ + sortResult = uint64cmp(diskItem1->ReadAverage + diskItem1->WriteAverage, diskItem2->ReadAverage + diskItem2->WriteAverage); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(IoPriority) +{ + sortResult = uintcmp(diskItem1->IoPriority, diskItem2->IoPriority); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(ResponseTime) +{ + sortResult = singlecmp(diskItem1->ResponseTimeAverage, diskItem2->ResponseTimeAverage); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI EtpDiskTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PET_DISK_NODE node; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + if (!getChildren->Node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Process), + SORT_FUNCTION(File), + SORT_FUNCTION(ReadRateAverage), + SORT_FUNCTION(WriteRateAverage), + SORT_FUNCTION(TotalRateAverage), + SORT_FUNCTION(IoPriority), + SORT_FUNCTION(ResponseTime) + }; + int (__cdecl *sortFunction)(const void *, const void *); + + if (DiskTreeNewSortColumn < ETDSTNC_MAXIMUM) + sortFunction = sortFunctions[DiskTreeNewSortColumn]; + else + sortFunction = NULL; + + if (sortFunction) + { + qsort(DiskNodeList->Items, DiskNodeList->Count, sizeof(PVOID), sortFunction); + } + + getChildren->Children = (PPH_TREENEW_NODE *)DiskNodeList->Items; + getChildren->NumberOfChildren = DiskNodeList->Count; + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + PET_DISK_ITEM diskItem; + + node = (PET_DISK_NODE)getCellText->Node; + diskItem = node->DiskItem; + + switch (getCellText->Id) + { + case ETDSTNC_NAME: + getCellText->Text = node->ProcessNameText->sr; + break; + case ETDSTNC_FILE: + getCellText->Text = diskItem->FileNameWin32->sr; + break; + case ETDSTNC_READRATEAVERAGE: + EtFormatRate(diskItem->ReadAverage, &node->ReadRateAverageText, &getCellText->Text); + break; + case ETDSTNC_WRITERATEAVERAGE: + EtFormatRate(diskItem->WriteAverage, &node->WriteRateAverageText, &getCellText->Text); + break; + case ETDSTNC_TOTALRATEAVERAGE: + EtFormatRate(diskItem->ReadAverage + diskItem->WriteAverage, &node->TotalRateAverageText, &getCellText->Text); + break; + case ETDSTNC_IOPRIORITY: + switch (diskItem->IoPriority) + { + case IoPriorityVeryLow: + PhInitializeStringRef(&getCellText->Text, L"Very Low"); + break; + case IoPriorityLow: + PhInitializeStringRef(&getCellText->Text, L"Low"); + break; + case IoPriorityNormal: + PhInitializeStringRef(&getCellText->Text, L"Normal"); + break; + case IoPriorityHigh: + PhInitializeStringRef(&getCellText->Text, L"High"); + break; + case IoPriorityCritical: + PhInitializeStringRef(&getCellText->Text, L"Critical"); + break; + default: + PhInitializeStringRef(&getCellText->Text, L"Unknown"); + break; + } + break; + case ETDSTNC_RESPONSETIME: + { + PH_FORMAT format; + + PhInitFormatF(&format, diskItem->ResponseTimeAverage, 0); + PhMoveReference(&node->ResponseTimeText, PhFormat(&format, 1, 0)); + getCellText->Text = node->ResponseTimeText->sr; + } + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeIcon: + { + PPH_TREENEW_GET_NODE_ICON getNodeIcon = Parameter1; + + node = (PET_DISK_NODE)getNodeIcon->Node; + + if (node->DiskItem->ProcessIcon) + { + getNodeIcon->Icon = node->DiskItem->ProcessIcon->Icon; + } + else + { + PhGetStockApplicationIcon(&getNodeIcon->Icon, NULL); + } + + getNodeIcon->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetCellTooltip: + { + PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = Parameter1; + PPH_PROCESS_NODE processNode; + + node = (PET_DISK_NODE)getCellTooltip->Node; + + if (getCellTooltip->Column->Id != 0) + return FALSE; + + if (!node->TooltipText) + { + if (processNode = PhFindProcessNode(node->DiskItem->ProcessId)) + { + PPH_TREENEW_CALLBACK callback; + PVOID callbackContext; + PPH_TREENEW_COLUMN fixedColumn; + PH_TREENEW_GET_CELL_TOOLTIP fakeGetCellTooltip; + + // HACK: Get the tooltip text by using the treenew callback of the process tree. + if (TreeNew_GetCallback(ProcessTreeNewHandle, &callback, &callbackContext) && + (fixedColumn = TreeNew_GetFixedColumn(ProcessTreeNewHandle))) + { + fakeGetCellTooltip.Flags = 0; + fakeGetCellTooltip.Node = &processNode->Node; + fakeGetCellTooltip.Column = fixedColumn; + fakeGetCellTooltip.Unfolding = FALSE; + PhInitializeEmptyStringRef(&fakeGetCellTooltip.Text); + fakeGetCellTooltip.Font = getCellTooltip->Font; + fakeGetCellTooltip.MaximumWidth = getCellTooltip->MaximumWidth; + + if (callback(ProcessTreeNewHandle, TreeNewGetCellTooltip, &fakeGetCellTooltip, NULL, callbackContext)) + { + node->TooltipText = PhCreateString2(&fakeGetCellTooltip.Text); + } + } + } + } + + if (!PhIsNullOrEmptyString(node->TooltipText)) + { + getCellTooltip->Text = node->TooltipText->sr; + getCellTooltip->Unfolding = FALSE; + getCellTooltip->MaximumWidth = -1; + } + else + { + return FALSE; + } + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &DiskTreeNewSortColumn, &DiskTreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + EtHandleDiskCommand(ID_DISK_COPY); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + TreeNew_SelectRange(DiskTreeNewHandle, 0, -1); + break; + case VK_RETURN: + EtHandleDiskCommand(ID_DISK_OPENFILELOCATION); + break; + } + } + return TRUE; + case TreeNewHeaderRightClick: + { + PH_TN_COLUMN_MENU_DATA data; + + data.TreeNewHandle = hwnd; + data.MouseEvent = Parameter1; + data.DefaultSortColumn = 0; + data.DefaultSortOrder = AscendingSortOrder; + PhInitializeTreeNewColumnMenu(&data); + + data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y); + PhHandleTreeNewColumnMenu(&data); + PhDeleteTreeNewColumnMenu(&data); + } + return TRUE; + case TreeNewLeftDoubleClick: + { + EtHandleDiskCommand(ID_DISK_OPENFILELOCATION); + } + return TRUE; + case TreeNewContextMenu: + { + PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; + + EtShowDiskContextMenu(mouseEvent->Location); + } + return TRUE; + case TreeNewDestroying: + { + EtSaveSettingsDiskTreeList(); + } + return TRUE; + } + + return FALSE; +} + +PPH_STRING EtpGetDiskItemProcessName( + _In_ PET_DISK_ITEM DiskItem + ) +{ + PH_FORMAT format[4]; + + if (!DiskItem->ProcessId) + return PhCreateString(L"No process"); + + PhInitFormatS(&format[1], L" ("); + PhInitFormatU(&format[2], HandleToUlong(DiskItem->ProcessId)); + PhInitFormatC(&format[3], ')'); + + if (DiskItem->ProcessName) + PhInitFormatSR(&format[0], DiskItem->ProcessName->sr); + else + PhInitFormatS(&format[0], L"Unknown process"); + + return PhFormat(format, 4, 96); +} + +PET_DISK_ITEM EtGetSelectedDiskItem( + VOID + ) +{ + PET_DISK_ITEM diskItem = NULL; + ULONG i; + + for (i = 0; i < DiskNodeList->Count; i++) + { + PET_DISK_NODE node = DiskNodeList->Items[i]; + + if (node->Node.Selected) + { + diskItem = node->DiskItem; + break; + } + } + + return diskItem; +} + +VOID EtGetSelectedDiskItems( + _Out_ PET_DISK_ITEM **DiskItems, + _Out_ PULONG NumberOfDiskItems + ) +{ + PPH_LIST list; + ULONG i; + + list = PhCreateList(2); + + for (i = 0; i < DiskNodeList->Count; i++) + { + PET_DISK_NODE node = DiskNodeList->Items[i]; + + if (node->Node.Selected) + { + PhAddItemList(list, node->DiskItem); + } + } + + *DiskItems = PhAllocateCopy(list->Items, sizeof(PVOID) * list->Count); + *NumberOfDiskItems = list->Count; + + PhDereferenceObject(list); +} + +VOID EtDeselectAllDiskNodes( + VOID + ) +{ + TreeNew_DeselectRange(DiskTreeNewHandle, 0, -1); +} + +VOID EtSelectAndEnsureVisibleDiskNode( + _In_ PET_DISK_NODE DiskNode + ) +{ + EtDeselectAllDiskNodes(); + + if (!DiskNode->Node.Visible) + return; + + TreeNew_SetFocusNode(DiskTreeNewHandle, &DiskNode->Node); + TreeNew_SetMarkNode(DiskTreeNewHandle, &DiskNode->Node); + TreeNew_SelectRange(DiskTreeNewHandle, DiskNode->Node.Index, DiskNode->Node.Index); + TreeNew_EnsureVisible(DiskTreeNewHandle, &DiskNode->Node); +} + +VOID EtCopyDiskList( + VOID + ) +{ + PPH_STRING text; + + text = PhGetTreeNewText(DiskTreeNewHandle, 0); + PhSetClipboardString(DiskTreeNewHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID EtWriteDiskList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ) +{ + PPH_LIST lines; + ULONG i; + + lines = PhGetGenericTreeNewLines(DiskTreeNewHandle, Mode); + + for (i = 0; i < lines->Count; i++) + { + PPH_STRING line; + + line = lines->Items[i]; + PhWriteStringAsUtf8FileStream(FileStream, &line->sr); + PhDereferenceObject(line); + PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n"); + } + + PhDereferenceObject(lines); +} + +VOID EtHandleDiskCommand( + _In_ ULONG Id + ) +{ + switch (Id) + { + case ID_DISK_GOTOPROCESS: + { + PET_DISK_ITEM diskItem = EtGetSelectedDiskItem(); + PPH_PROCESS_NODE processNode; + + if (diskItem) + { + PhReferenceObject(diskItem); + + if (diskItem->ProcessRecord) + { + // Check if this is really the process that we want, or if it's just a case of PID re-use. + if ((processNode = PhFindProcessNode(diskItem->ProcessId)) && + processNode->ProcessItem->CreateTime.QuadPart == diskItem->ProcessRecord->CreateTime.QuadPart) + { + ProcessHacker_SelectTabPage(PhMainWndHandle, 0); + PhSelectAndEnsureVisibleProcessNode(processNode); + } + else + { + PhShowProcessRecordDialog(PhMainWndHandle, diskItem->ProcessRecord); + } + } + else + { + PhShowError(PhMainWndHandle, L"The process does not exist."); + } + + PhDereferenceObject(diskItem); + } + } + break; + case ID_DISK_OPENFILELOCATION: + { + PET_DISK_ITEM diskItem = EtGetSelectedDiskItem(); + + if (diskItem) + { + PhShellExploreFile(PhMainWndHandle, diskItem->FileNameWin32->Buffer); + } + } + break; + case ID_DISK_COPY: + { + EtCopyDiskList(); + } + break; + case ID_DISK_PROPERTIES: + { + PET_DISK_ITEM diskItem = EtGetSelectedDiskItem(); + + if (diskItem) + { + PhShellProperties(PhMainWndHandle, diskItem->FileNameWin32->Buffer); + } + } + break; + } +} + +VOID EtpInitializeDiskMenu( + _In_ PPH_EMENU Menu, + _In_ PET_DISK_ITEM *DiskItems, + _In_ ULONG NumberOfDiskItems + ) +{ + PPH_EMENU_ITEM item; + + if (NumberOfDiskItems == 0) + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + } + else if (NumberOfDiskItems == 1) + { + PPH_PROCESS_ITEM processItem; + + // If we have a process record and the process has terminated, we can only show + // process properties. + if (DiskItems[0]->ProcessRecord) + { + if (processItem = PhReferenceProcessItemForRecord(DiskItems[0]->ProcessRecord)) + { + PhDereferenceObject(processItem); + } + else + { + if (item = PhFindEMenuItem(Menu, 0, NULL, ID_DISK_GOTOPROCESS)) + { + item->Text = L"Process Properties"; + item->Flags &= ~PH_EMENU_TEXT_OWNED; + } + } + } + } + else + { + PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhEnableEMenuItem(Menu, ID_DISK_COPY, TRUE); + } +} + +VOID EtShowDiskContextMenu( + _In_ POINT Location + ) +{ + PET_DISK_ITEM *diskItems; + ULONG numberOfDiskItems; + + EtGetSelectedDiskItems(&diskItems, &numberOfDiskItems); + + if (numberOfDiskItems != 0) + { + PPH_EMENU menu; + PPH_EMENU_ITEM item; + + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_DISK), 0); + PhSetFlagsEMenuItem(menu, ID_DISK_OPENFILELOCATION, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + EtpInitializeDiskMenu(menu, diskItems, numberOfDiskItems); + + item = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + Location.x, + Location.y + ); + + if (item) + { + EtHandleDiskCommand(item->Id); + } + + PhDestroyEMenu(menu); + } + + PhFree(diskItems); +} + +VOID NTAPI EtpDiskItemAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PET_DISK_ITEM diskItem = (PET_DISK_ITEM)Parameter; + + PhReferenceObject(diskItem); + ProcessHacker_Invoke(PhMainWndHandle, EtpOnDiskItemAdded, diskItem); +} + +VOID NTAPI EtpDiskItemModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ProcessHacker_Invoke(PhMainWndHandle, EtpOnDiskItemModified, (PET_DISK_ITEM)Parameter); +} + +VOID NTAPI EtpDiskItemRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ProcessHacker_Invoke(PhMainWndHandle, EtpOnDiskItemRemoved, (PET_DISK_ITEM)Parameter); +} + +VOID NTAPI EtpDiskItemsUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ProcessHacker_Invoke(PhMainWndHandle, EtpOnDiskItemsUpdated, NULL); +} + +VOID NTAPI EtpOnDiskItemAdded( + _In_ PVOID Parameter + ) +{ + PET_DISK_ITEM diskItem = Parameter; + PET_DISK_NODE diskNode; + + if (!DiskNeedsRedraw) + { + TreeNew_SetRedraw(DiskTreeNewHandle, FALSE); + DiskNeedsRedraw = TRUE; + } + + diskNode = EtAddDiskNode(diskItem); + PhDereferenceObject(diskItem); +} + +VOID NTAPI EtpOnDiskItemModified( + _In_ PVOID Parameter + ) +{ + PET_DISK_ITEM diskItem = Parameter; + + EtUpdateDiskNode(EtFindDiskNode(diskItem)); +} + +VOID NTAPI EtpOnDiskItemRemoved( + _In_ PVOID Parameter + ) +{ + PET_DISK_ITEM diskItem = Parameter; + + if (!DiskNeedsRedraw) + { + TreeNew_SetRedraw(DiskTreeNewHandle, FALSE); + DiskNeedsRedraw = TRUE; + } + + EtRemoveDiskNode(EtFindDiskNode(diskItem)); +} + +VOID NTAPI EtpOnDiskItemsUpdated( + _In_ PVOID Parameter + ) +{ + ULONG i; + + if (DiskNeedsRedraw) + { + TreeNew_SetRedraw(DiskTreeNewHandle, TRUE); + DiskNeedsRedraw = FALSE; + } + + // Text invalidation + + for (i = 0; i < DiskNodeList->Count; i++) + { + PET_DISK_NODE node = DiskNodeList->Items[i]; + + // The name and file name never change, so we don't invalidate that. + memset(&node->TextCache[2], 0, sizeof(PH_STRINGREF) * (ETDSTNC_MAXIMUM - 2)); + // Always get the newest tooltip text from the process tree. + PhClearReference(&node->TooltipText); + } + + InvalidateRect(DiskTreeNewHandle, NULL, FALSE); +} + +VOID NTAPI EtpSearchChangedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (!EtEtwEnabled) + return; + + PhApplyTreeNewFilters(&FilterSupport); +} + +BOOLEAN NTAPI EtpSearchDiskListFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PET_DISK_NODE diskNode = (PET_DISK_NODE)Node; + PTOOLSTATUS_WORD_MATCH wordMatch = ToolStatusInterface->WordMatch; + + if (PhIsNullOrEmptyString(ToolStatusInterface->GetSearchboxText())) + return TRUE; + + if (wordMatch(&diskNode->ProcessNameText->sr)) + return TRUE; + + if (wordMatch(&diskNode->DiskItem->FileNameWin32->sr)) + return TRUE; + + return FALSE; +} + +VOID NTAPI EtpToolStatusActivateContent( + _In_ BOOLEAN Select + ) +{ + SetFocus(DiskTreeNewHandle); + + if (Select) + { + if (TreeNew_GetFlatNodeCount(DiskTreeNewHandle) > 0) + EtSelectAndEnsureVisibleDiskNode((PET_DISK_NODE)TreeNew_GetFlatNode(DiskTreeNewHandle, 0)); + } +} + +HWND NTAPI EtpToolStatusGetTreeNewHandle( + VOID + ) +{ + return DiskTreeNewHandle; +} + +INT_PTR CALLBACK EtpDiskTabErrorDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + if (!PhGetOwnTokenAttributes().Elevated) + { + SendMessage(GetDlgItem(hwndDlg, IDC_RESTART), BCM_SETSHIELD, 0, TRUE); + } + else + { + SetDlgItemText(hwndDlg, IDC_ERROR, L"Unable to start the kernel event tracing session."); + ShowWindow(GetDlgItem(hwndDlg, IDC_RESTART), SW_HIDE); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_RESTART: + ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); + + if (PhShellProcessHacker( + PhMainWndHandle, + L"-v -selecttab Disk", + SW_SHOW, + PH_SHELL_EXECUTE_ADMIN, + PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY, + 0, + NULL + )) + { + ProcessHacker_Destroy(PhMainWndHandle); + } + else + { + ProcessHacker_CancelEarlyShutdown(PhMainWndHandle); + } + + break; + } + } + break; + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: + { + SetBkMode((HDC)wParam, TRANSPARENT); + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/disktabp.h b/plugins/ExtendedTools/disktabp.h new file mode 100644 index 0000000..d9eb9be --- /dev/null +++ b/plugins/ExtendedTools/disktabp.h @@ -0,0 +1,171 @@ +#ifndef DISKTABP_H +#define DISKTABP_H + +HWND NTAPI EtpDiskTabCreateFunction( + _In_ PVOID Context + ); + +VOID NTAPI EtpDiskTabSelectionChangedCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ); + +VOID NTAPI EtpDiskTabSaveContentCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ); + +VOID NTAPI EtpDiskTabFontChangedCallback( + _In_ PVOID Parameter1, + _In_ PVOID Parameter2, + _In_ PVOID Parameter3, + _In_ PVOID Context + ); + +BOOLEAN EtpDiskNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG EtpDiskNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID EtInitializeDiskTreeList( + _In_ HWND hwnd + ); + +PET_DISK_NODE EtAddDiskNode( + _In_ PET_DISK_ITEM DiskItem + ); + +PET_DISK_NODE EtFindDiskNode( + _In_ PET_DISK_ITEM DiskItem + ); + +VOID EtRemoveDiskNode( + _In_ PET_DISK_NODE DiskNode + ); + +VOID EtUpdateDiskNode( + _In_ PET_DISK_NODE DiskNode + ); + +BOOLEAN NTAPI EtpDiskTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +PPH_STRING EtpGetDiskItemProcessName( + _In_ PET_DISK_ITEM DiskItem + ); + +PET_DISK_ITEM EtGetSelectedDiskItem( + VOID + ); + +VOID EtGetSelectedDiskItems( + _Out_ PET_DISK_ITEM **DiskItems, + _Out_ PULONG NumberOfDiskItems + ); + +VOID EtDeselectAllDiskNodes( + VOID + ); + +VOID EtSelectAndEnsureVisibleDiskNode( + _In_ PET_DISK_NODE DiskNode + ); + +VOID EtCopyDiskList( + VOID + ); + +VOID EtWriteDiskList( + _Inout_ PPH_FILE_STREAM FileStream, + _In_ ULONG Mode + ); + +VOID EtHandleDiskCommand( + _In_ ULONG Id + ); + +VOID EtpInitializeDiskMenu( + _In_ PPH_EMENU Menu, + _In_ PET_DISK_ITEM *DiskItems, + _In_ ULONG NumberOfDiskItems + ); + +VOID EtShowDiskContextMenu( + _In_ POINT Location + ); + +VOID NTAPI EtpDiskItemAddedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtpDiskItemModifiedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtpDiskItemRemovedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtpDiskItemsUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtpOnDiskItemAdded( + _In_ PVOID Parameter + ); + +VOID NTAPI EtpOnDiskItemModified( + _In_ PVOID Parameter + ); + +VOID NTAPI EtpOnDiskItemRemoved( + _In_ PVOID Parameter + ); + +VOID NTAPI EtpOnDiskItemsUpdated( + _In_ PVOID Parameter + ); + +VOID NTAPI EtpSearchChangedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +BOOLEAN NTAPI EtpSearchDiskListFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtpToolStatusActivateContent( + _In_ BOOLEAN Select + ); + +HWND NTAPI EtpToolStatusGetTreeNewHandle( + VOID + ); + +INT_PTR CALLBACK EtpDiskTabErrorDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#endif diff --git a/plugins/ExtendedTools/etwdisk.c b/plugins/ExtendedTools/etwdisk.c new file mode 100644 index 0000000..c8512f2 --- /dev/null +++ b/plugins/ExtendedTools/etwdisk.c @@ -0,0 +1,536 @@ +/* + * Process Hacker Extended Tools - + * ETW disk monitoring + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwmon.h" + +typedef struct _ETP_DISK_PACKET +{ + SLIST_ENTRY ListEntry; + ET_ETW_DISK_EVENT Event; + PPH_STRING FileName; +} ETP_DISK_PACKET, *PETP_DISK_PACKET; + +VOID NTAPI EtpDiskItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ); + +BOOLEAN NTAPI EtpDiskHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI EtpDiskHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID NTAPI EtpDiskProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +BOOLEAN EtDiskEnabled = FALSE; + +PPH_OBJECT_TYPE EtDiskItemType; +PPH_HASHTABLE EtDiskHashtable; +PH_QUEUED_LOCK EtDiskHashtableLock = PH_QUEUED_LOCK_INIT; +LIST_ENTRY EtDiskAgeListHead; +PH_CALLBACK_DECLARE(EtDiskItemAddedEvent); +PH_CALLBACK_DECLARE(EtDiskItemModifiedEvent); +PH_CALLBACK_DECLARE(EtDiskItemRemovedEvent); +PH_CALLBACK_DECLARE(EtDiskItemsUpdatedEvent); + +PH_FREE_LIST EtDiskPacketFreeList; +SLIST_HEADER EtDiskPacketListHead; +PPH_HASHTABLE EtFileNameHashtable; +PH_QUEUED_LOCK EtFileNameHashtableLock = PH_QUEUED_LOCK_INIT; + +static LARGE_INTEGER EtpPerformanceFrequency; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; + +VOID EtInitializeDiskInformation( + VOID + ) +{ + LARGE_INTEGER performanceCounter; + + EtDiskItemType = PhCreateObjectType(L"DiskItem", 0, EtpDiskItemDeleteProcedure); + EtDiskHashtable = PhCreateHashtable( + sizeof(PET_DISK_ITEM), + EtpDiskHashtableEqualFunction, + EtpDiskHashtableHashFunction, + 128 + ); + InitializeListHead(&EtDiskAgeListHead); + + PhInitializeFreeList(&EtDiskPacketFreeList, sizeof(ETP_DISK_PACKET), 64); + RtlInitializeSListHead(&EtDiskPacketListHead); + EtFileNameHashtable = PhCreateSimpleHashtable(128); + + NtQueryPerformanceCounter(&performanceCounter, &EtpPerformanceFrequency); + + EtDiskEnabled = TRUE; + + // Collect all existing file names. + EtStartEtwRundown(); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + EtpDiskProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); +} + +PET_DISK_ITEM EtCreateDiskItem( + VOID + ) +{ + PET_DISK_ITEM diskItem; + + diskItem = PhCreateObject(sizeof(ET_DISK_ITEM), EtDiskItemType); + memset(diskItem, 0, sizeof(ET_DISK_ITEM)); + + return diskItem; +} + +VOID NTAPI EtpDiskItemDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PET_DISK_ITEM diskItem = Object; + + if (diskItem->FileName) PhDereferenceObject(diskItem->FileName); + if (diskItem->FileNameWin32) PhDereferenceObject(diskItem->FileNameWin32); + if (diskItem->ProcessName) PhDereferenceObject(diskItem->ProcessName); + if (diskItem->ProcessIcon) EtProcIconDereferenceProcessIcon(diskItem->ProcessIcon); + if (diskItem->ProcessRecord) PhDereferenceProcessRecord(diskItem->ProcessRecord); +} + +BOOLEAN NTAPI EtpDiskHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PET_DISK_ITEM diskItem1 = *(PET_DISK_ITEM *)Entry1; + PET_DISK_ITEM diskItem2 = *(PET_DISK_ITEM *)Entry2; + + return diskItem1->ProcessId == diskItem2->ProcessId && PhEqualString(diskItem1->FileName, diskItem2->FileName, TRUE); +} + +ULONG NTAPI EtpDiskHashtableHashFunction( + _In_ PVOID Entry + ) +{ + PET_DISK_ITEM diskItem = *(PET_DISK_ITEM *)Entry; + + return (HandleToUlong(diskItem->ProcessId) / 4) ^ PhHashStringRef(&diskItem->FileName->sr, TRUE); +} + +PET_DISK_ITEM EtReferenceDiskItem( + _In_ HANDLE ProcessId, + _In_ PPH_STRING FileName + ) +{ + ET_DISK_ITEM lookupDiskItem; + PET_DISK_ITEM lookupDiskItemPtr = &lookupDiskItem; + PET_DISK_ITEM *diskItemPtr; + PET_DISK_ITEM diskItem; + + lookupDiskItem.ProcessId = ProcessId; + lookupDiskItem.FileName = FileName; + + PhAcquireQueuedLockShared(&EtDiskHashtableLock); + + diskItemPtr = (PET_DISK_ITEM *)PhFindEntryHashtable( + EtDiskHashtable, + &lookupDiskItemPtr + ); + + if (diskItemPtr) + PhSetReference(&diskItem, *diskItemPtr); + else + diskItem = NULL; + + PhReleaseQueuedLockShared(&EtDiskHashtableLock); + + return diskItem; +} + +VOID EtpRemoveDiskItem( + _In_ PET_DISK_ITEM DiskItem + ) +{ + RemoveEntryList(&DiskItem->AgeListEntry); + PhRemoveEntryHashtable(EtDiskHashtable, &DiskItem); + PhDereferenceObject(DiskItem); +} + +VOID EtDiskProcessDiskEvent( + _In_ PET_ETW_DISK_EVENT Event + ) +{ + PETP_DISK_PACKET packet; + + if (!EtDiskEnabled) + return; + + packet = PhAllocateFromFreeList(&EtDiskPacketFreeList); + memcpy(&packet->Event, Event, sizeof(ET_ETW_DISK_EVENT)); + packet->FileName = EtFileObjectToFileName(Event->FileObject); + RtlInterlockedPushEntrySList(&EtDiskPacketListHead, &packet->ListEntry); +} + +VOID EtDiskProcessFileEvent( + _In_ PET_ETW_FILE_EVENT Event + ) +{ + PH_KEY_VALUE_PAIR pair; + PPH_KEY_VALUE_PAIR realPair; + + if (!EtDiskEnabled) + return; + + if (Event->Type == EtEtwFileCreateType || Event->Type == EtEtwFileRundownType) + { + pair.Key = Event->FileObject; + pair.Value = NULL; + + PhAcquireQueuedLockExclusive(&EtFileNameHashtableLock); + + realPair = PhAddEntryHashtableEx(EtFileNameHashtable, &pair, NULL); + PhMoveReference(&realPair->Value, PhCreateString2(&Event->FileName)); + + PhReleaseQueuedLockExclusive(&EtFileNameHashtableLock); + } + else if (Event->Type == EtEtwFileDeleteType) + { + pair.Key = Event->FileObject; + + PhAcquireQueuedLockExclusive(&EtFileNameHashtableLock); + + realPair = PhFindEntryHashtable(EtFileNameHashtable, &pair); + + if (realPair) + { + PhDereferenceObject(realPair->Value); + PhRemoveEntryHashtable(EtFileNameHashtable, &pair); + } + + PhReleaseQueuedLockExclusive(&EtFileNameHashtableLock); + } +} + +PPH_STRING EtFileObjectToFileName( + _In_ PVOID FileObject + ) +{ + PH_KEY_VALUE_PAIR pair; + PPH_KEY_VALUE_PAIR realPair; + PPH_STRING fileName; + + pair.Key = FileObject; + fileName = NULL; + + PhAcquireQueuedLockShared(&EtFileNameHashtableLock); + + realPair = PhFindEntryHashtable(EtFileNameHashtable, &pair); + + if (realPair) + PhSetReference(&fileName, realPair->Value); + + PhReleaseQueuedLockShared(&EtFileNameHashtableLock); + + return fileName; +} + +VOID EtpProcessDiskPacket( + _In_ PETP_DISK_PACKET Packet, + _In_ ULONG RunId + ) +{ + PET_ETW_DISK_EVENT diskEvent; + PET_DISK_ITEM diskItem; + BOOLEAN added = FALSE; + + diskEvent = &Packet->Event; + + // We only process non-zero read/write events. + if (diskEvent->Type != EtEtwDiskReadType && diskEvent->Type != EtEtwDiskWriteType) + return; + if (diskEvent->TransferSize == 0) + return; + + // Ignore packets with no file name - this is useless to the user. + if (!Packet->FileName) + return; + + diskItem = EtReferenceDiskItem(diskEvent->ClientId.UniqueProcess, Packet->FileName); + + if (!diskItem) + { + PPH_PROCESS_ITEM processItem; + + // Disk item not found (or the address was re-used), create it. + + diskItem = EtCreateDiskItem(); + + diskItem->ProcessId = diskEvent->ClientId.UniqueProcess; + PhSetReference(&diskItem->FileName, Packet->FileName); + diskItem->FileNameWin32 = PhGetFileName(diskItem->FileName); + + if (processItem = PhReferenceProcessItem(diskItem->ProcessId)) + { + PhSetReference(&diskItem->ProcessName, processItem->ProcessName); + diskItem->ProcessIcon = EtProcIconReferenceSmallProcessIcon(EtGetProcessBlock(processItem)); + diskItem->ProcessRecord = processItem->Record; + PhReferenceProcessRecord(diskItem->ProcessRecord); + + PhDereferenceObject(processItem); + } + + // Add the disk item to the age list. + diskItem->AddTime = RunId; + diskItem->FreshTime = RunId; + InsertHeadList(&EtDiskAgeListHead, &diskItem->AgeListEntry); + + // Add the disk item to the hashtable. + PhAcquireQueuedLockExclusive(&EtDiskHashtableLock); + PhAddEntryHashtable(EtDiskHashtable, &diskItem); + PhReleaseQueuedLockExclusive(&EtDiskHashtableLock); + + // Raise the disk item added event. + PhInvokeCallback(&EtDiskItemAddedEvent, diskItem); + added = TRUE; + } + + // The I/O priority number needs to be decoded. + + diskItem->IoPriority = (diskEvent->IrpFlags >> 17) & 7; + + if (diskItem->IoPriority == 0) + diskItem->IoPriority = IoPriorityNormal; + else + diskItem->IoPriority--; + + // Accumulate statistics for this update period. + + if (diskEvent->Type == EtEtwDiskReadType) + diskItem->ReadDelta += diskEvent->TransferSize; + else + diskItem->WriteDelta += diskEvent->TransferSize; + + if (EtpPerformanceFrequency.QuadPart != 0) + { + // Convert the response time to milliseconds. + diskItem->ResponseTimeTotal += (FLOAT)diskEvent->HighResResponseTime * 1000 / EtpPerformanceFrequency.QuadPart; + diskItem->ResponseTimeCount++; + } + + if (!added) + { + if (diskItem->FreshTime != RunId) + { + diskItem->FreshTime = RunId; + RemoveEntryList(&diskItem->AgeListEntry); + InsertHeadList(&EtDiskAgeListHead, &diskItem->AgeListEntry); + } + + PhDereferenceObject(diskItem); + } +} + +ULONG64 EtpCalculateAverage( + _In_ PULONG64 Buffer, + _In_ ULONG BufferSize, + _In_ ULONG BufferPosition, + _In_ ULONG BufferCount, + _In_ ULONG NumberToConsider + ) +{ + ULONG64 sum; + ULONG i; + ULONG count; + + sum = 0; + i = BufferPosition; + + if (NumberToConsider > BufferCount) + NumberToConsider = BufferCount; + + if (NumberToConsider == 0) + return 0; + + count = NumberToConsider; + + do + { + sum += Buffer[i]; + i++; + + if (i == BufferSize) + i = 0; + } while (--count != 0); + + return sum / NumberToConsider; +} + +VOID NTAPI EtpDiskProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + static ULONG runCount = 0; + + PSLIST_ENTRY listEntry; + PLIST_ENTRY ageListEntry; + + // Process incoming disk event packets. + + listEntry = RtlInterlockedFlushSList(&EtDiskPacketListHead); + + while (listEntry) + { + PETP_DISK_PACKET packet; + + packet = CONTAINING_RECORD(listEntry, ETP_DISK_PACKET, ListEntry); + listEntry = listEntry->Next; + + EtpProcessDiskPacket(packet, runCount); + + if (packet->FileName) + PhDereferenceObject(packet->FileName); + + PhFreeToFreeList(&EtDiskPacketFreeList, packet); + } + + // Remove old entries. + + ageListEntry = EtDiskAgeListHead.Blink; + + while (ageListEntry != &EtDiskAgeListHead) + { + PET_DISK_ITEM diskItem; + + diskItem = CONTAINING_RECORD(ageListEntry, ET_DISK_ITEM, AgeListEntry); + ageListEntry = ageListEntry->Blink; + + if (runCount - diskItem->FreshTime < HISTORY_SIZE) // must compare like this to avoid overflow/underflow problems + break; + + PhInvokeCallback(&EtDiskItemRemovedEvent, diskItem); + + PhAcquireQueuedLockExclusive(&EtDiskHashtableLock); + EtpRemoveDiskItem(diskItem); + PhReleaseQueuedLockExclusive(&EtDiskHashtableLock); + } + + // Update existing items. + + ageListEntry = EtDiskAgeListHead.Flink; + + while (ageListEntry != &EtDiskAgeListHead) + { + PET_DISK_ITEM diskItem; + + diskItem = CONTAINING_RECORD(ageListEntry, ET_DISK_ITEM, AgeListEntry); + + // Update statistics. + + if (diskItem->HistoryPosition != 0) + diskItem->HistoryPosition--; + else + diskItem->HistoryPosition = HISTORY_SIZE - 1; + + diskItem->ReadHistory[diskItem->HistoryPosition] = diskItem->ReadDelta; + diskItem->WriteHistory[diskItem->HistoryPosition] = diskItem->WriteDelta; + + if (diskItem->HistoryCount < HISTORY_SIZE) + diskItem->HistoryCount++; + + if (diskItem->ResponseTimeCount != 0) + { + diskItem->ResponseTimeAverage = (FLOAT)diskItem->ResponseTimeTotal / diskItem->ResponseTimeCount; + + // Reset the total once in a while to avoid the number getting too large (and thus losing precision). + if (diskItem->ResponseTimeCount >= 1000) + { + diskItem->ResponseTimeTotal = diskItem->ResponseTimeAverage; + diskItem->ResponseTimeCount = 1; + } + } + + diskItem->ReadTotal += diskItem->ReadDelta; + diskItem->WriteTotal += diskItem->WriteDelta; + diskItem->ReadDelta = 0; + diskItem->WriteDelta = 0; + diskItem->ReadAverage = EtpCalculateAverage(diskItem->ReadHistory, HISTORY_SIZE, diskItem->HistoryPosition, diskItem->HistoryCount, HISTORY_SIZE); + diskItem->WriteAverage = EtpCalculateAverage(diskItem->WriteHistory, HISTORY_SIZE, diskItem->HistoryPosition, diskItem->HistoryCount, HISTORY_SIZE); + + if (diskItem->AddTime != runCount) + { + BOOLEAN modified = FALSE; + PPH_PROCESS_ITEM processItem; + + if (!diskItem->ProcessName || !diskItem->ProcessIcon || !diskItem->ProcessRecord) + { + if (processItem = PhReferenceProcessItem(diskItem->ProcessId)) + { + if (!diskItem->ProcessName) + { + PhSetReference(&diskItem->ProcessName, processItem->ProcessName); + modified = TRUE; + } + + if (!diskItem->ProcessIcon) + { + diskItem->ProcessIcon = EtProcIconReferenceSmallProcessIcon(EtGetProcessBlock(processItem)); + + if (diskItem->ProcessIcon) + modified = TRUE; + } + + if (!diskItem->ProcessRecord) + { + diskItem->ProcessRecord = processItem->Record; + PhReferenceProcessRecord(diskItem->ProcessRecord); + } + + PhDereferenceObject(processItem); + } + } + + if (modified) + { + // Raise the disk item modified event. + PhInvokeCallback(&EtDiskItemModifiedEvent, diskItem); + } + } + + ageListEntry = ageListEntry->Flink; + } + + PhInvokeCallback(&EtDiskItemsUpdatedEvent, NULL); + runCount++; +} diff --git a/plugins/ExtendedTools/etwmini.c b/plugins/ExtendedTools/etwmini.c new file mode 100644 index 0000000..367c8dc --- /dev/null +++ b/plugins/ExtendedTools/etwmini.c @@ -0,0 +1,264 @@ +/* + * Process Hacker Extended Tools - + * ETW mini information section + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwmini.h" + +VOID EtEtwMiniInformationInitializing( + _In_ PPH_PLUGIN_MINIINFO_POINTERS Pointers + ) +{ + PH_MINIINFO_LIST_SECTION section; + + memset(§ion, 0, sizeof(PH_MINIINFO_LIST_SECTION)); + section.Callback = EtpDiskListSectionCallback; + Pointers->CreateListSection(L"Disk", 0, §ion); + + memset(§ion, 0, sizeof(PH_MINIINFO_LIST_SECTION)); + section.Callback = EtpNetworkListSectionCallback; + + Pointers->CreateListSection(L"Network", 0, §ion); +} + +BOOLEAN EtpDiskListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + PH_FORMAT format[4]; + + PhInitFormatS(&format[0], L"Disk R: "); + PhInitFormatSize(&format[1], EtDiskReadDelta.Delta); + format[1].Type |= FormatUsePrecision; + format[1].Precision = 0; + PhInitFormatS(&format[2], L" W: "); + PhInitFormatSize(&format[3], EtDiskWriteDelta.Delta); + format[3].Type |= FormatUsePrecision; + format[3].Precision = 0; + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PH_AUTO(PhFormat(format, 4, 50))); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), EtpDiskListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + ULONG64 diskReadDelta = 0; + ULONG64 diskWriteDelta = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + { + PPH_PROCESS_ITEM processItem = processes->Items[i]; + PET_PROCESS_BLOCK block = EtGetProcessBlock(processItem); + diskReadDelta += block->DiskReadRawDelta.Delta; + diskWriteDelta += block->DiskWriteRawDelta.Delta; + } + + assignSortData->SortData->UserData[0] = diskReadDelta; + assignSortData->SortData->UserData[1] = diskWriteDelta; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), EtpDiskListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + ULONG64 diskReadDelta = getUsageText->SortData->UserData[0]; + ULONG64 diskWriteDelta = getUsageText->SortData->UserData[1]; + PH_FORMAT format[1]; + + PhInitFormatSize(&format[0], diskReadDelta + diskWriteDelta); + PhMoveReference(&getUsageText->Line1, PhFormat(format, 1, 16)); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl EtpDiskListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + int result; + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + PET_PROCESS_BLOCK block1 = EtGetProcessBlock(node1->ProcessItem); + PET_PROCESS_BLOCK block2 = EtGetProcessBlock(node2->ProcessItem); + ULONG64 total1 = block1->DiskReadRawDelta.Delta + block1->DiskWriteRawDelta.Delta; + ULONG64 total2 = block2->DiskReadRawDelta.Delta + block2->DiskWriteRawDelta.Delta; + + result = uint64cmp(total2, total1); + + if (result == 0) + result = uint64cmp(block2->DiskReadRaw + block2->DiskWriteRaw, block1->DiskReadRaw + block1->DiskWriteRaw); + if (result == 0) + result = singlecmp(node2->ProcessItem->CpuUsage, node1->ProcessItem->CpuUsage); + + return result; +} + +int __cdecl EtpDiskListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return uint64cmp(data2->UserData[0] + data2->UserData[1], data1->UserData[0] + data1->UserData[1]); +} + +BOOLEAN EtpNetworkListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + PH_FORMAT format[4]; + + PhInitFormatS(&format[0], L"Network R: "); + PhInitFormatSize(&format[1], EtNetworkReceiveDelta.Delta); + format[1].Type |= FormatUsePrecision; + format[1].Precision = 0; + PhInitFormatS(&format[2], L" S: "); + PhInitFormatSize(&format[3], EtNetworkSendDelta.Delta); + format[3].Type |= FormatUsePrecision; + format[3].Precision = 0; + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PH_AUTO(PhFormat(format, 4, 50))); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), EtpNetworkListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + ULONG64 networkReceiveDelta = 0; + ULONG64 networkSendDelta = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + { + PPH_PROCESS_ITEM processItem = processes->Items[i]; + PET_PROCESS_BLOCK block = EtGetProcessBlock(processItem); + networkReceiveDelta += block->NetworkReceiveRawDelta.Delta; + networkSendDelta += block->NetworkSendRawDelta.Delta; + } + + assignSortData->SortData->UserData[0] = networkReceiveDelta; + assignSortData->SortData->UserData[1] = networkSendDelta; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), EtpNetworkListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + ULONG64 networkReceiveDelta = getUsageText->SortData->UserData[0]; + ULONG64 networkSendDelta = getUsageText->SortData->UserData[1]; + PH_FORMAT format[1]; + + PhInitFormatSize(&format[0], networkReceiveDelta + networkSendDelta); + PhMoveReference(&getUsageText->Line1, PhFormat(format, 1, 16)); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl EtpNetworkListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + int result; + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + PET_PROCESS_BLOCK block1 = EtGetProcessBlock(node1->ProcessItem); + PET_PROCESS_BLOCK block2 = EtGetProcessBlock(node2->ProcessItem); + ULONG64 total1 = block1->NetworkReceiveRawDelta.Delta + block1->NetworkSendRawDelta.Delta; + ULONG64 total2 = block2->NetworkReceiveRawDelta.Delta + block2->NetworkSendRawDelta.Delta; + + result = uint64cmp(total2, total1); + + if (result == 0) + result = uint64cmp(block2->NetworkReceiveRaw + block2->NetworkSendRaw, block1->NetworkReceiveRaw + block1->NetworkSendRaw); + if (result == 0) + result = singlecmp(node2->ProcessItem->CpuUsage, node1->ProcessItem->CpuUsage); + + return result; +} + +int __cdecl EtpNetworkListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return uint64cmp(data2->UserData[0] + data2->UserData[1], data1->UserData[0] + data1->UserData[1]); +} diff --git a/plugins/ExtendedTools/etwmini.h b/plugins/ExtendedTools/etwmini.h new file mode 100644 index 0000000..0c394a4 --- /dev/null +++ b/plugins/ExtendedTools/etwmini.h @@ -0,0 +1,38 @@ +#ifndef ETWMINI_H +#define ETWMINI_H + +BOOLEAN EtpDiskListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl EtpDiskListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl EtpDiskListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +BOOLEAN EtpNetworkListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl EtpNetworkListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl EtpNetworkListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +#endif \ No newline at end of file diff --git a/plugins/ExtendedTools/etwmon.c b/plugins/ExtendedTools/etwmon.c new file mode 100644 index 0000000..7c4823a --- /dev/null +++ b/plugins/ExtendedTools/etwmon.c @@ -0,0 +1,562 @@ +/* + * Process Hacker Extended Tools - + * ETW monitoring + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwmon.h" + +ULONG NTAPI EtpEtwBufferCallback( + _In_ PEVENT_TRACE_LOGFILE Buffer + ); + +VOID NTAPI EtpEtwEventCallback( + _In_ PEVENT_RECORD EventRecord + ); + +NTSTATUS EtpEtwMonitorThreadStart( + _In_ PVOID Parameter + ); + +ULONG EtpStopEtwRundownSession( + VOID + ); + +ULONG NTAPI EtpRundownEtwBufferCallback( + _In_ PEVENT_TRACE_LOGFILE Buffer + ); + +VOID NTAPI EtpRundownEtwEventCallback( + _In_ PEVENT_RECORD EventRecord + ); + +NTSTATUS EtpRundownEtwMonitorThreadStart( + _In_ PVOID Parameter + ); + +static GUID ProcessHackerGuid = { 0x1288c53b, 0xaf35, 0x481b, { 0xb6, 0xb5, 0xa0, 0x5c, 0x39, 0x87, 0x2e, 0xd } }; +static GUID SystemTraceControlGuid_I = { 0x9e814aad, 0x3204, 0x11d2, { 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 } }; +static GUID KernelRundownGuid_I = { 0x3b9c9951, 0x3480, 0x4220, { 0x93, 0x77, 0x9c, 0x8e, 0x51, 0x84, 0xf5, 0xcd } }; +static GUID DiskIoGuid_I = { 0x3d6fa8d4, 0xfe05, 0x11d0, { 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c } }; +static GUID FileIoGuid_I = { 0x90cbdc39, 0x4a3e, 0x11d1, { 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3 } }; +static GUID TcpIpGuid_I = { 0x9a280ac0, 0xc8e0, 0x11d1, { 0x84, 0xe2, 0x00, 0xc0, 0x4f, 0xb9, 0x98, 0xa2 } }; +static GUID UdpIpGuid_I = { 0xbf3a50c5, 0xa9c9, 0x4988, { 0xa0, 0x05, 0x2d, 0xf0, 0xb7, 0xc8, 0x0f, 0x80 } }; + +// ETW tracing layer + +BOOLEAN EtEtwEnabled; +static UNICODE_STRING EtpSharedKernelLoggerName = RTL_CONSTANT_STRING(KERNEL_LOGGER_NAME); +static UNICODE_STRING EtpPrivateKernelLoggerName = RTL_CONSTANT_STRING(L"PhEtKernelLogger"); +static TRACEHANDLE EtpSessionHandle; +static PUNICODE_STRING EtpActualKernelLoggerName; +static PGUID EtpActualSessionGuid; +static PEVENT_TRACE_PROPERTIES EtpTraceProperties; +static BOOLEAN EtpEtwActive; +static BOOLEAN EtpStartedSession; +static BOOLEAN EtpEtwExiting; +static HANDLE EtpEtwMonitorThreadHandle; + +// ETW rundown layer + +static UNICODE_STRING EtpRundownLoggerName = RTL_CONSTANT_STRING(L"PhEtRundownLogger"); +static TRACEHANDLE EtpRundownSessionHandle; +static PEVENT_TRACE_PROPERTIES EtpRundownTraceProperties; +static BOOLEAN EtpRundownActive; +static HANDLE EtpRundownEtwMonitorThreadHandle; + +VOID EtEtwMonitorInitialization( + VOID + ) +{ + if (PhGetOwnTokenAttributes().Elevated && PhGetIntegerSetting(SETTING_NAME_ENABLE_ETW_MONITOR)) + { + EtStartEtwSession(); + + if (EtEtwEnabled) + EtpEtwMonitorThreadHandle = PhCreateThread(0, EtpEtwMonitorThreadStart, NULL); + } +} + +VOID EtEtwMonitorUninitialization( + VOID + ) +{ + if (EtEtwEnabled) + { + EtpEtwExiting = TRUE; + EtStopEtwSession(); + } + + if (EtpRundownActive) + { + EtpStopEtwRundownSession(); + } +} + +VOID EtStartEtwSession( + VOID + ) +{ + ULONG result; + ULONG bufferSize; + + if (WindowsVersion >= WINDOWS_8) + { + EtpActualKernelLoggerName = &EtpPrivateKernelLoggerName; + EtpActualSessionGuid = &ProcessHackerGuid; + } + else + { + EtpActualKernelLoggerName = &EtpSharedKernelLoggerName; + EtpActualSessionGuid = &SystemTraceControlGuid_I; + } + + bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpActualKernelLoggerName->Length + sizeof(WCHAR); + + if (!EtpTraceProperties) + EtpTraceProperties = PhAllocate(bufferSize); + + memset(EtpTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES)); + + EtpTraceProperties->Wnode.BufferSize = bufferSize; + EtpTraceProperties->Wnode.Guid = *EtpActualSessionGuid; + EtpTraceProperties->Wnode.ClientContext = 1; + EtpTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + EtpTraceProperties->MinimumBuffers = 1; + EtpTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + EtpTraceProperties->FlushTimer = 1; + EtpTraceProperties->EnableFlags = EVENT_TRACE_FLAG_DISK_IO | EVENT_TRACE_FLAG_DISK_FILE_IO | EVENT_TRACE_FLAG_NETWORK_TCPIP; + EtpTraceProperties->LogFileNameOffset = 0; + EtpTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + + if (WindowsVersion >= WINDOWS_8) + EtpTraceProperties->LogFileMode |= EVENT_TRACE_SYSTEM_LOGGER_MODE; + + result = StartTrace(&EtpSessionHandle, EtpActualKernelLoggerName->Buffer, EtpTraceProperties); + + if (result == ERROR_SUCCESS) + { + EtEtwEnabled = TRUE; + EtpEtwActive = TRUE; + EtpStartedSession = TRUE; + } + else if (result == ERROR_ALREADY_EXISTS) + { + EtEtwEnabled = TRUE; + EtpEtwActive = TRUE; + EtpStartedSession = FALSE; + // The session already exists. + //result = ControlTrace(0, EtpActualKernelLoggerName->Buffer, EtpTraceProperties, EVENT_TRACE_CONTROL_UPDATE); + } + else + { + EtpEtwActive = FALSE; + EtpStartedSession = FALSE; + } +} + +ULONG EtpControlEtwSession( + _In_ ULONG ControlCode + ) +{ + // If we have a session handle, we use that instead of the logger name. + + EtpTraceProperties->LogFileNameOffset = 0; // make sure it is 0, otherwise ControlTrace crashes + + return ControlTrace( + EtpStartedSession ? EtpSessionHandle : 0, + EtpStartedSession ? NULL : EtpActualKernelLoggerName->Buffer, + EtpTraceProperties, + ControlCode + ); +} + +VOID EtStopEtwSession( + VOID + ) +{ + if (EtEtwEnabled) + EtpControlEtwSession(EVENT_TRACE_CONTROL_STOP); +} + +VOID EtFlushEtwSession( + VOID + ) +{ + if (EtEtwEnabled) + EtpControlEtwSession(EVENT_TRACE_CONTROL_FLUSH); +} + +ULONG NTAPI EtpEtwBufferCallback( + _In_ PEVENT_TRACE_LOGFILE Buffer + ) +{ + return !EtpEtwExiting; +} + +VOID NTAPI EtpEtwEventCallback( + _In_ PEVENT_RECORD EventRecord + ) +{ + if (memcmp(&EventRecord->EventHeader.ProviderId, &DiskIoGuid_I, sizeof(GUID)) == 0) + { + // DiskIo + + ET_ETW_DISK_EVENT diskEvent; + + memset(&diskEvent, 0, sizeof(ET_ETW_DISK_EVENT)); + diskEvent.Type = -1; + + switch (EventRecord->EventHeader.EventDescriptor.Opcode) + { + case EVENT_TRACE_TYPE_IO_READ: + diskEvent.Type = EtEtwDiskReadType; + break; + case EVENT_TRACE_TYPE_IO_WRITE: + diskEvent.Type = EtEtwDiskWriteType; + break; + default: + break; + } + + if (diskEvent.Type != -1) + { + DiskIo_TypeGroup1 *data = EventRecord->UserData; + + if (WindowsVersion >= WINDOWS_8) + { + diskEvent.ClientId.UniqueThread = UlongToHandle(data->IssuingThreadId); + diskEvent.ClientId.UniqueProcess = EtThreadIdToProcessId(diskEvent.ClientId.UniqueThread); + } + else + { + if (EventRecord->EventHeader.ProcessId != -1) + { + diskEvent.ClientId.UniqueProcess = UlongToHandle(EventRecord->EventHeader.ProcessId); + diskEvent.ClientId.UniqueThread = UlongToHandle(EventRecord->EventHeader.ThreadId); + } + } + + diskEvent.IrpFlags = data->IrpFlags; + diskEvent.TransferSize = data->TransferSize; + diskEvent.FileObject = (PVOID)data->FileObject; + diskEvent.HighResResponseTime = data->HighResResponseTime; + + EtProcessDiskEvent(&diskEvent); + EtDiskProcessDiskEvent(&diskEvent); + } + } + else if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0) + { + // FileIo + + ET_ETW_FILE_EVENT fileEvent; + + memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT)); + fileEvent.Type = -1; + + switch (EventRecord->EventHeader.EventDescriptor.Opcode) + { + case 0: // Name + fileEvent.Type = EtEtwFileNameType; + break; + case 32: // FileCreate + fileEvent.Type = EtEtwFileCreateType; + break; + case 35: // FileDelete + fileEvent.Type = EtEtwFileDeleteType; + break; + default: + break; + } + + if (fileEvent.Type != -1) + { + FileIo_Name *data = EventRecord->UserData; + + fileEvent.FileObject = (PVOID)data->FileObject; + PhInitializeStringRef(&fileEvent.FileName, data->FileName); + + EtDiskProcessFileEvent(&fileEvent); + } + } + else if ( + memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0 || + memcmp(&EventRecord->EventHeader.ProviderId, &UdpIpGuid_I, sizeof(GUID)) == 0 + ) + { + // TcpIp/UdpIp + + ET_ETW_NETWORK_EVENT networkEvent; + + memset(&networkEvent, 0, sizeof(ET_ETW_NETWORK_EVENT)); + networkEvent.Type = -1; + + switch (EventRecord->EventHeader.EventDescriptor.Opcode) + { + case EVENT_TRACE_TYPE_SEND: // send + networkEvent.Type = EtEtwNetworkSendType; + networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE; + break; + case EVENT_TRACE_TYPE_RECEIVE: // receive + networkEvent.Type = EtEtwNetworkReceiveType; + networkEvent.ProtocolType = PH_IPV4_NETWORK_TYPE; + break; + case EVENT_TRACE_TYPE_SEND + 16: // send ipv6 + networkEvent.Type = EtEtwNetworkSendType; + networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE; + break; + case EVENT_TRACE_TYPE_RECEIVE + 16: // receive ipv6 + networkEvent.Type = EtEtwNetworkReceiveType; + networkEvent.ProtocolType = PH_IPV6_NETWORK_TYPE; + break; + } + + if (memcmp(&EventRecord->EventHeader.ProviderId, &TcpIpGuid_I, sizeof(GUID)) == 0) + networkEvent.ProtocolType |= PH_TCP_PROTOCOL_TYPE; + else + networkEvent.ProtocolType |= PH_UDP_PROTOCOL_TYPE; + + if (networkEvent.Type != -1) + { + PH_IP_ENDPOINT source; + PH_IP_ENDPOINT destination; + + if (networkEvent.ProtocolType & PH_IPV4_NETWORK_TYPE) + { + TcpIpOrUdpIp_IPV4_Header *data = EventRecord->UserData; + + networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID); + networkEvent.TransferSize = data->size; + + source.Address.Type = PH_IPV4_NETWORK_TYPE; + source.Address.Ipv4 = data->saddr; + source.Port = _byteswap_ushort(data->sport); + destination.Address.Type = PH_IPV4_NETWORK_TYPE; + destination.Address.Ipv4 = data->daddr; + destination.Port = _byteswap_ushort(data->dport); + } + else if (networkEvent.ProtocolType & PH_IPV6_NETWORK_TYPE) + { + TcpIpOrUdpIp_IPV6_Header *data = EventRecord->UserData; + + networkEvent.ClientId.UniqueProcess = UlongToHandle(data->PID); + networkEvent.TransferSize = data->size; + + source.Address.Type = PH_IPV6_NETWORK_TYPE; + source.Address.In6Addr = data->saddr; + source.Port = _byteswap_ushort(data->sport); + destination.Address.Type = PH_IPV6_NETWORK_TYPE; + destination.Address.In6Addr = data->daddr; + destination.Port = _byteswap_ushort(data->dport); + } + + networkEvent.LocalEndpoint = source; + + if (networkEvent.ProtocolType & PH_TCP_PROTOCOL_TYPE) + networkEvent.RemoteEndpoint = destination; + + EtProcessNetworkEvent(&networkEvent); + } + } +} + +NTSTATUS EtpEtwMonitorThreadStart( + _In_ PVOID Parameter + ) +{ + ULONG result; + EVENT_TRACE_LOGFILE logFile; + TRACEHANDLE traceHandle; + + // See comment in EtEtwProcessesUpdatedCallback. + if (WindowsVersion >= WINDOWS_8) + EtUpdateProcessInformation(); + + memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); + logFile.LoggerName = EtpActualKernelLoggerName->Buffer; + logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + logFile.BufferCallback = EtpEtwBufferCallback; + logFile.EventRecordCallback = EtpEtwEventCallback; + + while (TRUE) + { + result = ERROR_SUCCESS; + traceHandle = OpenTrace(&logFile); + + if (traceHandle != INVALID_PROCESSTRACE_HANDLE) + { + while (!EtpEtwExiting && (result = ProcessTrace(&traceHandle, 1, NULL, NULL)) == ERROR_SUCCESS) + NOTHING; + + CloseTrace(traceHandle); + } + + if (EtpEtwExiting) + break; + + if (result == ERROR_WMI_INSTANCE_NOT_FOUND) + { + // The session was stopped by another program. Try to start it again. + EtStartEtwSession(); + } + + // Some error occurred, so sleep for a while before trying again. + // Don't sleep if we just successfully started a session, though. + if (!EtpEtwActive) + Sleep(250); + } + + return STATUS_SUCCESS; +} + +ULONG EtStartEtwRundown( + VOID + ) +{ + ULONG result; + ULONG bufferSize; + + bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + EtpRundownLoggerName.Length + sizeof(WCHAR); + + if (!EtpRundownTraceProperties) + EtpRundownTraceProperties = PhAllocate(bufferSize); + + memset(EtpRundownTraceProperties, 0, sizeof(EVENT_TRACE_PROPERTIES)); + + EtpRundownTraceProperties->Wnode.BufferSize = bufferSize; + EtpRundownTraceProperties->Wnode.ClientContext = 1; + EtpRundownTraceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; + EtpRundownTraceProperties->MinimumBuffers = 1; + EtpRundownTraceProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + EtpRundownTraceProperties->FlushTimer = 1; + EtpRundownTraceProperties->LogFileNameOffset = 0; + EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + + result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties); + + if (result == ERROR_ALREADY_EXISTS) + { + EtpStopEtwRundownSession(); + // ControlTrace (called from EtpStopEtwRundownSession) screws up the structure. + EtpRundownTraceProperties->Wnode.BufferSize = bufferSize; + EtpRundownTraceProperties->LogFileNameOffset = 0; + EtpRundownTraceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + result = StartTrace(&EtpRundownSessionHandle, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties); + } + + if (result != ERROR_SUCCESS) + return result; + + result = EnableTraceEx(&KernelRundownGuid_I, NULL, EtpRundownSessionHandle, 1, 0, 0x10, 0, 0, NULL); + + if (result != ERROR_SUCCESS) + { + EtpStopEtwRundownSession(); + return result; + } + + EtpRundownActive = TRUE; + EtpRundownEtwMonitorThreadHandle = PhCreateThread(0, EtpRundownEtwMonitorThreadStart, NULL); + + return result; +} + +ULONG EtpStopEtwRundownSession( + VOID + ) +{ + EtpRundownTraceProperties->LogFileNameOffset = 0; + return ControlTrace(0, EtpRundownLoggerName.Buffer, EtpRundownTraceProperties, EVENT_TRACE_CONTROL_STOP); +} + +ULONG NTAPI EtpRundownEtwBufferCallback( + _In_ PEVENT_TRACE_LOGFILE Buffer + ) +{ + return !EtpEtwExiting; +} + +VOID NTAPI EtpRundownEtwEventCallback( + _In_ PEVENT_RECORD EventRecord + ) +{ + // TODO: Find a way to call CloseTrace when the enumeration finishes so we can + // stop the trace cleanly. + + if (memcmp(&EventRecord->EventHeader.ProviderId, &FileIoGuid_I, sizeof(GUID)) == 0) + { + // FileIo + + ET_ETW_FILE_EVENT fileEvent; + + memset(&fileEvent, 0, sizeof(ET_ETW_FILE_EVENT)); + fileEvent.Type = -1; + + switch (EventRecord->EventHeader.EventDescriptor.Opcode) + { + case 36: // FileRundown + fileEvent.Type = EtEtwFileRundownType; + break; + default: + break; + } + + if (fileEvent.Type != -1) + { + FileIo_Name *data = EventRecord->UserData; + + fileEvent.FileObject = (PVOID)data->FileObject; + PhInitializeStringRef(&fileEvent.FileName, data->FileName); + + EtDiskProcessFileEvent(&fileEvent); + } + } +} + +NTSTATUS EtpRundownEtwMonitorThreadStart( + _In_ PVOID Parameter + ) +{ + EVENT_TRACE_LOGFILE logFile; + TRACEHANDLE traceHandle; + + memset(&logFile, 0, sizeof(EVENT_TRACE_LOGFILE)); + logFile.LoggerName = EtpRundownLoggerName.Buffer; + logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + logFile.BufferCallback = EtpRundownEtwBufferCallback; + logFile.EventRecordCallback = EtpRundownEtwEventCallback; + logFile.Context = &traceHandle; + + traceHandle = OpenTrace(&logFile); + + if (traceHandle != INVALID_PROCESSTRACE_HANDLE) + { + ProcessTrace(&traceHandle, 1, NULL, NULL); + + if (traceHandle != 0) + CloseTrace(traceHandle); + } + + NtClose(EtpRundownEtwMonitorThreadHandle); + EtpRundownEtwMonitorThreadHandle = NULL; + + return STATUS_SUCCESS; +} diff --git a/plugins/ExtendedTools/etwmon.h b/plugins/ExtendedTools/etwmon.h new file mode 100644 index 0000000..85f3e39 --- /dev/null +++ b/plugins/ExtendedTools/etwmon.h @@ -0,0 +1,140 @@ +#ifndef ETWMON_H +#define ETWMON_H + +#include + +typedef struct +{ + ULONG DiskNumber; + ULONG IrpFlags; + ULONG TransferSize; + ULONG ResponseTime; + ULONG64 ByteOffset; + ULONG_PTR FileObject; + ULONG_PTR Irp; + ULONG64 HighResResponseTime; + ULONG IssuingThreadId; // since WIN8 (ETW_DISKIO_READWRITE_V3) +} DiskIo_TypeGroup1; + +typedef struct +{ + ULONG_PTR FileObject; + WCHAR FileName[1]; +} FileIo_Name; + +typedef struct +{ + ULONG PID; + ULONG size; + ULONG daddr; + ULONG saddr; + USHORT dport; + USHORT sport; +} TcpIpOrUdpIp_IPV4_Header; + +typedef struct +{ + ULONG PID; + ULONG size; + IN6_ADDR daddr; + IN6_ADDR saddr; + USHORT dport; + USHORT sport; +} TcpIpOrUdpIp_IPV6_Header; + +// etwmon + +VOID EtEtwMonitorInitialization( + VOID + ); + +VOID EtEtwMonitorUninitialization( + VOID + ); + +VOID EtStartEtwSession( + VOID + ); + +VOID EtStopEtwSession( + VOID + ); + +VOID EtFlushEtwSession( + VOID + ); + +ULONG EtStartEtwRundown( + VOID + ); + +// etwstat + +typedef enum _ET_ETW_EVENT_TYPE +{ + EtEtwDiskReadType = 1, + EtEtwDiskWriteType, + EtEtwFileNameType, + EtEtwFileCreateType, + EtEtwFileDeleteType, + EtEtwFileRundownType, + EtEtwNetworkReceiveType, + EtEtwNetworkSendType +} ET_ETW_EVENT_TYPE; + +typedef struct _ET_ETW_DISK_EVENT +{ + ET_ETW_EVENT_TYPE Type; + CLIENT_ID ClientId; + ULONG IrpFlags; + ULONG TransferSize; + PVOID FileObject; + ULONG64 HighResResponseTime; +} ET_ETW_DISK_EVENT, *PET_ETW_DISK_EVENT; + +typedef struct _ET_ETW_FILE_EVENT +{ + ET_ETW_EVENT_TYPE Type; + PVOID FileObject; + PH_STRINGREF FileName; +} ET_ETW_FILE_EVENT, *PET_ETW_FILE_EVENT; + +typedef struct _ET_ETW_NETWORK_EVENT +{ + ET_ETW_EVENT_TYPE Type; + CLIENT_ID ClientId; + ULONG ProtocolType; + ULONG TransferSize; + PH_IP_ENDPOINT LocalEndpoint; + PH_IP_ENDPOINT RemoteEndpoint; +} ET_ETW_NETWORK_EVENT, *PET_ETW_NETWORK_EVENT; + +// etwstat + +VOID EtProcessDiskEvent( + _In_ PET_ETW_DISK_EVENT Event + ); + +VOID EtProcessNetworkEvent( + _In_ PET_ETW_NETWORK_EVENT Event + ); + +VOID EtUpdateProcessInformation( + VOID + ); + +HANDLE EtThreadIdToProcessId( + _In_ HANDLE ThreadId + ); + +// etwdisk + +VOID EtDiskProcessDiskEvent( + _In_ PET_ETW_DISK_EVENT Event + ); + +VOID EtDiskProcessFileEvent( + _In_ PET_ETW_FILE_EVENT Event + ); + +#endif diff --git a/plugins/ExtendedTools/etwprprp.c b/plugins/ExtendedTools/etwprprp.c new file mode 100644 index 0000000..1e87ffb --- /dev/null +++ b/plugins/ExtendedTools/etwprprp.c @@ -0,0 +1,615 @@ +/* + * Process Hacker Extended Tools - + * ETW process properties page + * + * Copyright (C) 2010-2011 wj32 + * Copyright (C) 2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +static RECT NormalGraphTextMargin = { 5, 5, 5, 5 }; +static RECT NormalGraphTextPadding = { 3, 3, 3, 3 }; + +typedef struct _ET_DISKNET_CONTEXT +{ + HWND WindowHandle; + PET_PROCESS_BLOCK Block; + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + BOOLEAN Enabled; + + PH_LAYOUT_MANAGER LayoutManager; + + HWND DiskGroupBox; + HWND NetworkGroupBox; + + HWND DiskGraphHandle; + HWND NetworkGraphHandle; + HWND PanelHandle; + + ULONG64 CurrentDiskRead; + ULONG64 CurrentDiskWrite; + ULONG64 CurrentNetworkSend; + ULONG64 CurrentNetworkReceive; + + PH_GRAPH_STATE DiskGraphState; + PH_GRAPH_STATE NetworkGraphState; + + PH_CIRCULAR_BUFFER_ULONG64 DiskReadHistory; + PH_CIRCULAR_BUFFER_ULONG64 DiskWriteHistory; + PH_CIRCULAR_BUFFER_ULONG64 NetworkSendHistory; + PH_CIRCULAR_BUFFER_ULONG64 NetworkReceiveHistory; +} ET_DISKNET_CONTEXT, *PET_DISKNET_CONTEXT; + +INT_PTR CALLBACK EtwDiskNetworkPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +VOID EtwDiskNetworkCreateGraphs( + _In_ PET_DISKNET_CONTEXT Context + ) +{ + Context->DiskGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + Context->WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(Context->DiskGraphHandle, TRUE); + + Context->NetworkGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + Context->WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(Context->NetworkGraphHandle, TRUE); +} + +VOID EtwDiskNetworkCreatePanel( + _In_ PET_DISKNET_CONTEXT Context + ) +{ + RECT margin; + + Context->PanelHandle = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_PROCDISKNET_PANEL), + Context->WindowHandle, + EtwDiskNetworkPanelDialogProc, + (LPARAM)Context + ); + + SetWindowPos( + Context->PanelHandle, + NULL, + 10, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER + ); + + ShowWindow(Context->PanelHandle, SW_SHOW); + + margin.left = 0; + margin.top = 0; + margin.right = 0; + margin.bottom = 10; + MapDialogRect(Context->WindowHandle, &margin); + + PhAddLayoutItemEx( + &Context->LayoutManager, + Context->PanelHandle, + NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT, + margin + ); + + SendMessage(Context->WindowHandle, WM_SIZE, 0, 0); +} + +VOID EtwDiskNetworkLayoutGraphs( + _In_ PET_DISKNET_CONTEXT Context + ) +{ + HDWP deferHandle; + RECT clientRect; + RECT panelRect; + RECT margin = { ET_SCALE_DPI(13), ET_SCALE_DPI(13), ET_SCALE_DPI(13), ET_SCALE_DPI(13) }; + RECT innerMargin = { ET_SCALE_DPI(10), ET_SCALE_DPI(20), ET_SCALE_DPI(10), ET_SCALE_DPI(10) }; + LONG between = ET_SCALE_DPI(3); + ULONG graphWidth; + ULONG graphHeight; + + PhLayoutManagerLayout(&Context->LayoutManager); + + Context->DiskGraphState.Valid = FALSE; + Context->NetworkGraphState.Valid = FALSE; + + GetClientRect(Context->WindowHandle, &clientRect); + + // Limit the rectangle bottom to the top of the panel. + GetWindowRect(Context->PanelHandle, &panelRect); + MapWindowPoints(NULL, Context->WindowHandle, (PPOINT)&panelRect, 2); + clientRect.bottom = panelRect.top + 10; // +10 removing extra spacing + + graphWidth = clientRect.right - margin.left - margin.right; + graphHeight = (clientRect.bottom - margin.top - margin.bottom - between * 2) / 2; + + deferHandle = BeginDeferWindowPos(4); + + deferHandle = DeferWindowPos(deferHandle, Context->DiskGroupBox, NULL, margin.left, margin.top, graphWidth, graphHeight, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + Context->DiskGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + innerMargin.top, + graphWidth - innerMargin.left - innerMargin.right, + graphHeight - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos(deferHandle, Context->NetworkGroupBox, NULL, margin.left, margin.top + graphHeight + between, graphWidth, graphHeight, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + Context->NetworkGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + graphHeight + between + innerMargin.top, + graphWidth - innerMargin.left - innerMargin.right, + graphHeight - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); +} + +VOID EtwDiskNetworkUpdateGraphs( + _In_ PET_DISKNET_CONTEXT Context + ) +{ + Context->DiskGraphState.Valid = FALSE; + Context->DiskGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->DiskGraphHandle, 1); + Graph_Draw(Context->DiskGraphHandle); + Graph_UpdateTooltip(Context->DiskGraphHandle); + InvalidateRect(Context->DiskGraphHandle, NULL, FALSE); + + Context->NetworkGraphState.Valid = FALSE; + Context->NetworkGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->NetworkGraphHandle, 1); + Graph_Draw(Context->NetworkGraphHandle); + Graph_UpdateTooltip(Context->NetworkGraphHandle); + InvalidateRect(Context->NetworkGraphHandle, NULL, FALSE); +} + +VOID EtwDiskNetworkUpdatePanel( + _Inout_ PET_DISKNET_CONTEXT Context + ) +{ + PET_PROCESS_BLOCK block = Context->Block; + + SetDlgItemText(Context->PanelHandle, IDC_ZREADS_V, PhaFormatUInt64(block->DiskReadCount, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZREADBYTES_V, PhaFormatSize(block->DiskReadRawDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZREADBYTESDELTA_V, PhaFormatSize(block->DiskReadRawDelta.Delta, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZWRITES_V, PhaFormatUInt64(block->DiskWriteCount, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZWRITEBYTES_V, PhaFormatSize(block->DiskWriteRawDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZWRITEBYTESDELTA_V, PhaFormatSize(block->DiskWriteRawDelta.Delta, -1)->Buffer); + + SetDlgItemText(Context->PanelHandle, IDC_ZRECEIVES_V, PhaFormatUInt64(block->NetworkReceiveCount, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZRECEIVEBYTES_V, PhaFormatSize(block->NetworkReceiveRawDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZRECEIVEBYTESDELTA_V, PhaFormatSize(block->NetworkReceiveRawDelta.Delta, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZSENDS_V, PhaFormatUInt64(block->NetworkSendCount, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZSENDBYTES_V, PhaFormatSize(block->NetworkSendRawDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZSENDBYTESDELTA_V, PhaFormatSize(block->NetworkSendRawDelta.Delta, -1)->Buffer); +} + +VOID EtwDiskNetworkUpdateInfo( + _In_ PET_DISKNET_CONTEXT Context + ) +{ + PET_PROCESS_BLOCK block = Context->Block; + + Context->CurrentDiskRead = block->DiskReadRawDelta.Delta; + Context->CurrentDiskWrite = block->DiskWriteRawDelta.Delta; + Context->CurrentNetworkSend = block->NetworkSendRawDelta.Delta; + Context->CurrentNetworkReceive = block->NetworkReceiveRawDelta.Delta; + + PhAddItemCircularBuffer_ULONG64(&Context->DiskReadHistory, Context->CurrentDiskRead); + PhAddItemCircularBuffer_ULONG64(&Context->DiskWriteHistory, Context->CurrentDiskWrite); + PhAddItemCircularBuffer_ULONG64(&Context->NetworkSendHistory, Context->CurrentNetworkSend); + PhAddItemCircularBuffer_ULONG64(&Context->NetworkReceiveHistory, Context->CurrentNetworkReceive); +} + +VOID NTAPI EtwDiskNetworkUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PET_DISKNET_CONTEXT context = Context; + + if (!context->Enabled) + return; + + if (context->WindowHandle) + { + PostMessage(context->WindowHandle, UPDATE_MSG, 0, 0); + } +} + +INT_PTR CALLBACK EtwDiskNetworkPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PET_DISKNET_CONTEXT context; + + if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) + { + context = propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG sampleCount; + + sampleCount = PhGetIntegerSetting(L"SampleCount"); + + context = PhAllocate(sizeof(ET_DISKNET_CONTEXT)); + memset(context, 0, sizeof(ET_DISKNET_CONTEXT)); + + context->WindowHandle = hwndDlg; + context->Block = EtGetProcessBlock(processItem); + context->Enabled = TRUE; + context->DiskGroupBox = GetDlgItem(hwndDlg, IDC_GROUPDISK); + context->NetworkGroupBox = GetDlgItem(hwndDlg, IDC_GROUPNETWORK); + propPageContext->Context = context; + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + + PhInitializeGraphState(&context->DiskGraphState); + PhInitializeGraphState(&context->NetworkGraphState); + + PhInitializeCircularBuffer_ULONG64(&context->DiskReadHistory, sampleCount); + PhInitializeCircularBuffer_ULONG64(&context->DiskWriteHistory, sampleCount); + PhInitializeCircularBuffer_ULONG64(&context->NetworkSendHistory, sampleCount); + PhInitializeCircularBuffer_ULONG64(&context->NetworkReceiveHistory, sampleCount); + + EtwDiskNetworkCreateGraphs(context); + EtwDiskNetworkCreatePanel(context); + EtwDiskNetworkUpdateInfo(context); + EtwDiskNetworkUpdatePanel(context); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + EtwDiskNetworkUpdateHandler, + context, + &context->ProcessesUpdatedRegistration + ); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + + PhDeleteGraphState(&context->DiskGraphState); + PhDeleteGraphState(&context->NetworkGraphState); + + PhDeleteCircularBuffer_ULONG64(&context->DiskReadHistory); + PhDeleteCircularBuffer_ULONG64(&context->DiskWriteHistory); + PhDeleteCircularBuffer_ULONG64(&context->NetworkSendHistory); + PhDeleteCircularBuffer_ULONG64(&context->NetworkReceiveHistory); + + if (context->DiskGraphHandle) + DestroyWindow(context->DiskGraphHandle); + if (context->NetworkGraphHandle) + DestroyWindow(context->NetworkGraphHandle); + if (context->PanelHandle) + DestroyWindow(context->PanelHandle); + + PhUnregisterCallback(&PhProcessesUpdatedEvent, &context->ProcessesUpdatedRegistration); + PhFree(context); + + PhPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (PhBeginPropPageLayout(hwndDlg, propPageContext)) + PhEndPropPageLayout(hwndDlg, propPageContext); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + context->Enabled = TRUE; + break; + case PSN_KILLACTIVE: + context->Enabled = FALSE; + break; + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + if (header->hwndFrom == context->DiskGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc; + + PhMoveReference(&context->DiskGraphState.Text, PhFormatString( + L"R: %s, W: %s", + PhaFormatSize(context->CurrentDiskRead, -1)->Buffer, + PhaFormatSize(context->CurrentDiskWrite, -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(context->DiskGraphHandle); + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &context->DiskGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGraphStateGetDrawInfo(&context->DiskGraphState, getDrawInfo, context->DiskReadHistory.Count); + + if (!context->DiskGraphState.Valid) + { + FLOAT max = 0; + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + context->DiskGraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskReadHistory, i); + context->DiskGraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + // Minimum scaling of 1 MB. + //if (max < 1024 * 1024) + // max = 1024 * 1024; + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + context->DiskGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + context->DiskGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + context->DiskGraphState.Valid = TRUE; + } + } + else if (header->hwndFrom == context->NetworkGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc; + + PhMoveReference(&context->NetworkGraphState.Text, PhFormatString( + L"R: %s, S: %s", + PhaFormatSize(context->CurrentNetworkReceive, -1)->Buffer, + PhaFormatSize(context->CurrentNetworkSend, -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(context->NetworkGraphHandle); + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &context->NetworkGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGraphStateGetDrawInfo(&context->NetworkGraphState, getDrawInfo, context->NetworkSendHistory.Count); + + if (!context->NetworkGraphState.Valid) + { + FLOAT max = 0; + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + context->NetworkGraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->NetworkReceiveHistory, i); + context->NetworkGraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->NetworkSendHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + // Minimum scaling of 1 MB. + //if (max < 1024 * 1024) + // max = 1024 * 1024; + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + context->NetworkGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + context->NetworkGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + context->NetworkGraphState.Valid = TRUE; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)lParam; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (header->hwndFrom == context->DiskGraphHandle) + { + if (context->DiskGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 diskRead = PhGetItemCircularBuffer_ULONG64( + &context->DiskReadHistory, + getTooltipText->Index + ); + + ULONG64 diskWrite = PhGetItemCircularBuffer_ULONG64( + &context->DiskWriteHistory, + getTooltipText->Index + ); + + PhMoveReference(&context->DiskGraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\n%s", + PhaFormatSize(diskRead, -1)->Buffer, + PhaFormatSize(diskWrite, -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = context->DiskGraphState.TooltipText->sr; + } + else if (header->hwndFrom == context->NetworkGraphHandle) + { + if (context->NetworkGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 networkSend = PhGetItemCircularBuffer_ULONG64( + &context->NetworkSendHistory, + getTooltipText->Index + ); + + ULONG64 networkReceive = PhGetItemCircularBuffer_ULONG64( + &context->NetworkReceiveHistory, + getTooltipText->Index + ); + + PhMoveReference(&context->NetworkGraphState.TooltipText, PhFormatString( + L"S: %s\nR: %s\n%s", + PhaFormatSize(networkSend, -1)->Buffer, + PhaFormatSize(networkReceive, -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = context->NetworkGraphState.TooltipText->sr; + } + } + } + break; + } + } + break; + case UPDATE_MSG: + { + if (context->Enabled) + { + EtwDiskNetworkUpdateInfo(context); + EtwDiskNetworkUpdateGraphs(context); + EtwDiskNetworkUpdatePanel(context); + } + } + break; + case WM_SIZE: + { + EtwDiskNetworkLayoutGraphs(context); + } + break; + } + + return FALSE; +} + +VOID EtProcessEtwPropertiesInitializing( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_PROCESS_PROPCONTEXT propContext = Parameter; + + if (EtEtwEnabled) + { + PhAddProcessPropPage( + propContext->PropContext, + PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCDISKNET), EtwDiskNetworkPageDlgProc, NULL) + ); + } +} \ No newline at end of file diff --git a/plugins/ExtendedTools/etwstat.c b/plugins/ExtendedTools/etwstat.c new file mode 100644 index 0000000..6444476 --- /dev/null +++ b/plugins/ExtendedTools/etwstat.c @@ -0,0 +1,419 @@ +/* + * Process Hacker Extended Tools - + * ETW statistics collection + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwmon.h" + +VOID NTAPI EtEtwProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI EtEtwNetworkItemsUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +static PH_CALLBACK_REGISTRATION EtpProcessesUpdatedCallbackRegistration; +static PH_CALLBACK_REGISTRATION EtpNetworkItemsUpdatedCallbackRegistration; + +ULONG EtpDiskReadRaw; +ULONG EtpDiskWriteRaw; +ULONG EtpNetworkReceiveRaw; +ULONG EtpNetworkSendRaw; + +ULONG EtDiskReadCount; +ULONG EtDiskWriteCount; +ULONG EtNetworkReceiveCount; +ULONG EtNetworkSendCount; + +PH_UINT32_DELTA EtDiskReadDelta; +PH_UINT32_DELTA EtDiskWriteDelta; +PH_UINT32_DELTA EtNetworkReceiveDelta; +PH_UINT32_DELTA EtNetworkSendDelta; + +PH_UINT32_DELTA EtDiskReadCountDelta; +PH_UINT32_DELTA EtDiskWriteCountDelta; +PH_UINT32_DELTA EtNetworkReceiveCountDelta; +PH_UINT32_DELTA EtNetworkSendCountDelta; + +PH_CIRCULAR_BUFFER_ULONG EtDiskReadHistory; +PH_CIRCULAR_BUFFER_ULONG EtDiskWriteHistory; +PH_CIRCULAR_BUFFER_ULONG EtNetworkReceiveHistory; +PH_CIRCULAR_BUFFER_ULONG EtNetworkSendHistory; +PH_CIRCULAR_BUFFER_ULONG EtMaxDiskHistory; // ID of max. disk usage process +PH_CIRCULAR_BUFFER_ULONG EtMaxNetworkHistory; // ID of max. network usage process + +PVOID EtpProcessInformation; +PH_QUEUED_LOCK EtpProcessInformationLock = PH_QUEUED_LOCK_INIT; + +VOID EtEtwStatisticsInitialization( + VOID + ) +{ + EtEtwMonitorInitialization(); + + if (EtEtwEnabled) + { + ULONG sampleCount; + + sampleCount = PhGetIntegerSetting(L"SampleCount"); + PhInitializeCircularBuffer_ULONG(&EtDiskReadHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtDiskWriteHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtNetworkReceiveHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtNetworkSendHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtMaxDiskHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtMaxNetworkHistory, sampleCount); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + EtEtwProcessesUpdatedCallback, + NULL, + &EtpProcessesUpdatedCallbackRegistration + ); + PhRegisterCallback( + &PhNetworkItemsUpdatedEvent, + EtEtwNetworkItemsUpdatedCallback, + NULL, + &EtpNetworkItemsUpdatedCallbackRegistration + ); + } +} + +VOID EtEtwStatisticsUninitialization( + VOID + ) +{ + EtEtwMonitorUninitialization(); +} + +VOID EtProcessDiskEvent( + _In_ PET_ETW_DISK_EVENT Event + ) +{ + PPH_PROCESS_ITEM processItem; + PET_PROCESS_BLOCK block; + + if (Event->Type == EtEtwDiskReadType) + { + EtpDiskReadRaw += Event->TransferSize; + EtDiskReadCount++; + } + else + { + EtpDiskWriteRaw += Event->TransferSize; + EtDiskWriteCount++; + } + + if (processItem = PhReferenceProcessItem(Event->ClientId.UniqueProcess)) + { + block = EtGetProcessBlock(processItem); + + if (Event->Type == EtEtwDiskReadType) + { + block->DiskReadRaw += Event->TransferSize; + block->DiskReadCount++; + } + else + { + block->DiskWriteRaw += Event->TransferSize; + block->DiskWriteCount++; + } + + PhDereferenceObject(processItem); + } +} + +VOID EtProcessNetworkEvent( + _In_ PET_ETW_NETWORK_EVENT Event + ) +{ + PPH_PROCESS_ITEM processItem; + PET_PROCESS_BLOCK block; + PPH_NETWORK_ITEM networkItem; + PET_NETWORK_BLOCK networkBlock; + + if (Event->Type == EtEtwNetworkReceiveType) + { + EtpNetworkReceiveRaw += Event->TransferSize; + EtNetworkReceiveCount++; + } + else + { + EtpNetworkSendRaw += Event->TransferSize; + EtNetworkSendCount++; + } + + // Note: there is always the possibility of us receiving the event too early, + // before the process item or network item is created. So events may be lost. + + if (processItem = PhReferenceProcessItem(Event->ClientId.UniqueProcess)) + { + block = EtGetProcessBlock(processItem); + + if (Event->Type == EtEtwNetworkReceiveType) + { + block->NetworkReceiveRaw += Event->TransferSize; + block->NetworkReceiveCount++; + } + else + { + block->NetworkSendRaw += Event->TransferSize; + block->NetworkSendCount++; + } + + PhDereferenceObject(processItem); + } + + if (networkItem = PhReferenceNetworkItem( + Event->ProtocolType, + &Event->LocalEndpoint, + &Event->RemoteEndpoint, + Event->ClientId.UniqueProcess + )) + { + networkBlock = EtGetNetworkBlock(networkItem); + + if (Event->Type == EtEtwNetworkReceiveType) + { + networkBlock->ReceiveRaw += Event->TransferSize; + networkBlock->ReceiveCount++; + } + else + { + networkBlock->SendRaw += Event->TransferSize; + networkBlock->SendCount++; + } + + PhDereferenceObject(networkItem); + } +} + +VOID NTAPI EtEtwProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + static ULONG runCount = 0; // MUST keep in sync with runCount in process provider + + PLIST_ENTRY listEntry; + ULONG64 maxDiskValue = 0; + PET_PROCESS_BLOCK maxDiskBlock = NULL; + ULONG64 maxNetworkValue = 0; + PET_PROCESS_BLOCK maxNetworkBlock = NULL; + + // Since Windows 8, we no longer get the correct process/thread IDs in the + // event headers for disk events. We need to update our process information since + // etwmon uses our EtThreadIdToProcessId function. + if (WindowsVersion >= WINDOWS_8) + EtUpdateProcessInformation(); + + // ETW is extremely lazy when it comes to flushing buffers, so we must do it + // manually. + EtFlushEtwSession(); + + // Update global statistics. + + PhUpdateDelta(&EtDiskReadDelta, EtpDiskReadRaw); + PhUpdateDelta(&EtDiskWriteDelta, EtpDiskWriteRaw); + PhUpdateDelta(&EtNetworkReceiveDelta, EtpNetworkReceiveRaw); + PhUpdateDelta(&EtNetworkSendDelta, EtpNetworkSendRaw); + + PhUpdateDelta(&EtDiskReadCountDelta, EtDiskReadCount); + PhUpdateDelta(&EtDiskWriteCountDelta, EtDiskWriteCount); + PhUpdateDelta(&EtNetworkReceiveCountDelta, EtNetworkReceiveCount); + PhUpdateDelta(&EtNetworkSendCountDelta, EtNetworkSendCount); + + // Update per-process statistics. + // Note: no lock is needed because we only ever modify the list on this same thread. + + listEntry = EtProcessBlockListHead.Flink; + + while (listEntry != &EtProcessBlockListHead) + { + PET_PROCESS_BLOCK block; + + block = CONTAINING_RECORD(listEntry, ET_PROCESS_BLOCK, ListEntry); + + PhUpdateDelta(&block->DiskReadDelta, block->DiskReadCount); + PhUpdateDelta(&block->DiskReadRawDelta, block->DiskReadRaw); + PhUpdateDelta(&block->DiskWriteDelta, block->DiskWriteCount); + PhUpdateDelta(&block->DiskWriteRawDelta, block->DiskWriteRaw); + PhUpdateDelta(&block->NetworkReceiveDelta, block->NetworkReceiveCount); + PhUpdateDelta(&block->NetworkReceiveRawDelta, block->NetworkReceiveRaw); + PhUpdateDelta(&block->NetworkSendDelta, block->NetworkSendCount); + PhUpdateDelta(&block->NetworkSendRawDelta, block->NetworkSendRaw); + + if (maxDiskValue < block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta) + { + maxDiskValue = block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta; + maxDiskBlock = block; + } + + if (maxNetworkValue < block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta) + { + maxNetworkValue = block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta; + maxNetworkBlock = block; + } + + listEntry = listEntry->Flink; + } + + // Update history buffers. + + if (runCount != 0) + { + PhAddItemCircularBuffer_ULONG(&EtDiskReadHistory, EtDiskReadDelta.Delta); + PhAddItemCircularBuffer_ULONG(&EtDiskWriteHistory, EtDiskWriteDelta.Delta); + PhAddItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, EtNetworkReceiveDelta.Delta); + PhAddItemCircularBuffer_ULONG(&EtNetworkSendHistory, EtNetworkSendDelta.Delta); + + if (maxDiskBlock) + { + PhAddItemCircularBuffer_ULONG(&EtMaxDiskHistory, HandleToUlong(maxDiskBlock->ProcessItem->ProcessId)); + PhReferenceProcessRecordForStatistics(maxDiskBlock->ProcessItem->Record); + } + else + { + PhAddItemCircularBuffer_ULONG(&EtMaxDiskHistory, 0); + } + + if (maxNetworkBlock) + { + PhAddItemCircularBuffer_ULONG(&EtMaxNetworkHistory, HandleToUlong(maxNetworkBlock->ProcessItem->ProcessId)); + PhReferenceProcessRecordForStatistics(maxNetworkBlock->ProcessItem->Record); + } + else + { + PhAddItemCircularBuffer_ULONG(&EtMaxNetworkHistory, 0); + } + } + + runCount++; +} + +static VOID NTAPI EtpInvalidateNetworkNode( + _In_ PVOID Parameter + ) +{ + PPH_NETWORK_ITEM networkItem = Parameter; + PPH_NETWORK_NODE networkNode; + + if (networkNode = PhFindNetworkNode(networkItem)) + TreeNew_InvalidateNode(NetworkTreeNewHandle, &networkNode->Node); + + PhDereferenceObject(networkItem); +} + +VOID NTAPI EtEtwNetworkItemsUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PLIST_ENTRY listEntry; + + // ETW is flushed in the processes-updated callback above. This may cause us the network + // blocks to all fall one update interval behind, however. + + // Update per-connection statistics. + // Note: no lock is needed because we only ever modify the list on this same thread. + + listEntry = EtNetworkBlockListHead.Flink; + + while (listEntry != &EtNetworkBlockListHead) + { + PET_NETWORK_BLOCK block; + PH_UINT64_DELTA oldDeltas[4]; + + block = CONTAINING_RECORD(listEntry, ET_NETWORK_BLOCK, ListEntry); + + memcpy(oldDeltas, block->Deltas, sizeof(block->Deltas)); + + PhUpdateDelta(&block->ReceiveDelta, block->ReceiveCount); + PhUpdateDelta(&block->ReceiveRawDelta, block->ReceiveRaw); + PhUpdateDelta(&block->SendDelta, block->SendCount); + PhUpdateDelta(&block->SendRawDelta, block->SendRaw); + + if (memcmp(oldDeltas, block->Deltas, sizeof(block->Deltas))) + { + // Values have changed. Invalidate the network node. + PhReferenceObject(block->NetworkItem); + ProcessHacker_Invoke(PhMainWndHandle, EtpInvalidateNetworkNode, block->NetworkItem); + } + + listEntry = listEntry->Flink; + } +} + +VOID EtUpdateProcessInformation( + VOID + ) +{ + PhAcquireQueuedLockExclusive(&EtpProcessInformationLock); + + if (EtpProcessInformation) + { + PhFree(EtpProcessInformation); + EtpProcessInformation = NULL; + } + + PhEnumProcesses(&EtpProcessInformation); + + PhReleaseQueuedLockExclusive(&EtpProcessInformationLock); +} + +HANDLE EtThreadIdToProcessId( + _In_ HANDLE ThreadId + ) +{ + PSYSTEM_PROCESS_INFORMATION process; + ULONG i; + HANDLE processId; + + PhAcquireQueuedLockShared(&EtpProcessInformationLock); + + if (!EtpProcessInformation) + { + PhReleaseQueuedLockShared(&EtpProcessInformationLock); + return NULL; + } + + process = PH_FIRST_PROCESS(EtpProcessInformation); + + do + { + for (i = 0; i < process->NumberOfThreads; i++) + { + if (process->Threads[i].ClientId.UniqueThread == ThreadId) + { + processId = process->UniqueProcessId; + PhReleaseQueuedLockShared(&EtpProcessInformationLock); + + return processId; + } + } + } while (process = PH_NEXT_PROCESS(process)); + + PhReleaseQueuedLockShared(&EtpProcessInformationLock); + + return NULL; +} diff --git a/plugins/ExtendedTools/etwsys.c b/plugins/ExtendedTools/etwsys.c new file mode 100644 index 0000000..dd813a8 --- /dev/null +++ b/plugins/ExtendedTools/etwsys.c @@ -0,0 +1,811 @@ +/* + * Process Hacker Extended Tools - + * ETW system information section + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "etwsys.h" + +static PPH_SYSINFO_SECTION DiskSection; +static HWND DiskDialog; +static PH_LAYOUT_MANAGER DiskLayoutManager; +static HWND DiskGraphHandle; +static PH_GRAPH_STATE DiskGraphState; +static HWND DiskPanel; + +static PPH_SYSINFO_SECTION NetworkSection; +static HWND NetworkDialog; +static PH_LAYOUT_MANAGER NetworkLayoutManager; +static HWND NetworkGraphHandle; +static PH_GRAPH_STATE NetworkGraphState; +static HWND NetworkPanel; + +VOID EtEtwSystemInformationInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ) +{ + PH_SYSINFO_SECTION section; + + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + PhInitializeStringRef(§ion.Name, L"Disk"); + section.Flags = 0; + section.Callback = EtpDiskSysInfoSectionCallback; + + DiskSection = Pointers->CreateSection(§ion); + + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + PhInitializeStringRef(§ion.Name, L"Network"); + section.Flags = 0; + section.Callback = EtpNetworkSysInfoSectionCallback; + + NetworkSection = Pointers->CreateSection(§ion); +} + +BOOLEAN EtpDiskSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoDestroy: + { + if (DiskDialog) + { + PhDeleteGraphState(&DiskGraphState); + DiskDialog = NULL; + } + } + return TRUE; + case SysInfoTick: + { + if (DiskDialog) + { + EtpUpdateDiskGraph(); + EtpUpdateDiskPanel(); + } + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_DISK); + createDialog->DialogProc = EtpDiskDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, EtDiskReadHistory.Count); + + if (!Section->GraphState.Valid) + { + ULONG i; + FLOAT max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + Section->GraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskReadHistory, i); + Section->GraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + Section->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + Section->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + ULONG64 diskRead; + ULONG64 diskWrite; + + diskRead = PhGetItemCircularBuffer_ULONG(&EtDiskReadHistory, getTooltipText->Index); + diskWrite = PhGetItemCircularBuffer_ULONG(&EtDiskWriteHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s%s\n%s", + PhaFormatSize(diskRead, -1)->Buffer, + PhaFormatSize(diskWrite, -1)->Buffer, + PhGetStringOrEmpty(EtpGetMaxDiskString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + + drawPanel->Title = PhCreateString(L"Disk"); + drawPanel->SubTitle = PhFormatString( + L"R: %s\nW: %s", + PhaFormatSize(EtDiskReadDelta.Delta, -1)->Buffer, + PhaFormatSize(EtDiskWriteDelta.Delta, -1)->Buffer + ); + } + return TRUE; + } + + return FALSE; +} + +INT_PTR CALLBACK EtpDiskDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + PhInitializeGraphState(&DiskGraphState); + + DiskDialog = hwndDlg; + PhInitializeLayoutManager(&DiskLayoutManager, hwndDlg); + graphItem = PhAddLayoutItem(&DiskLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + panelItem = PhAddLayoutItem(&DiskLayoutManager, GetDlgItem(hwndDlg, IDC_PANEL_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)DiskSection->Parameters->LargeFont, FALSE); + + DiskPanel = CreateDialog(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_SYSINFO_DISKPANEL), hwndDlg, EtpDiskPanelDialogProc); + ShowWindow(DiskPanel, SW_SHOW); + PhAddLayoutItemEx(&DiskLayoutManager, DiskPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + DiskGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(DiskGraphHandle, TRUE); + + PhAddLayoutItemEx(&DiskLayoutManager, DiskGraphHandle, NULL, PH_ANCHOR_ALL, graphItem->Margin); + + EtpUpdateDiskGraph(); + EtpUpdateDiskPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&DiskLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&DiskLayoutManager); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + if (header->hwndFrom == DiskGraphHandle) + { + EtpNotifyDiskGraph(header); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK EtpDiskPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +VOID EtpNotifyDiskGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + DiskSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + + PhGraphStateGetDrawInfo( + &DiskGraphState, + getDrawInfo, + EtDiskReadHistory.Count + ); + + if (!DiskGraphState.Valid) + { + ULONG i; + FLOAT max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + DiskGraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskReadHistory, i); + DiskGraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskWriteHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + DiskGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + DiskGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + DiskGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (DiskGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 diskRead; + ULONG64 diskWrite; + + diskRead = PhGetItemCircularBuffer_ULONG(&EtDiskReadHistory, getTooltipText->Index); + diskWrite = PhGetItemCircularBuffer_ULONG(&EtDiskWriteHistory, getTooltipText->Index); + + PhMoveReference(&DiskGraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s%s\n%s", + PhaFormatSize(diskRead, -1)->Buffer, + PhaFormatSize(diskWrite, -1)->Buffer, + PhGetStringOrEmpty(EtpGetMaxDiskString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = DiskGraphState.TooltipText->sr; + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record; + + record = NULL; + + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = EtpReferenceMaxDiskRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(DiskDialog, record); + PhDereferenceProcessRecord(record); + } + } + break; + } +} + +VOID EtpUpdateDiskGraph( + VOID + ) +{ + DiskGraphState.Valid = FALSE; + DiskGraphState.TooltipIndex = -1; + Graph_MoveGrid(DiskGraphHandle, 1); + Graph_Draw(DiskGraphHandle); + Graph_UpdateTooltip(DiskGraphHandle); + InvalidateRect(DiskGraphHandle, NULL, FALSE); +} + +VOID EtpUpdateDiskPanel( + VOID + ) +{ + SetDlgItemText(DiskPanel, IDC_ZREADSDELTA_V, PhaFormatUInt64(EtDiskReadCountDelta.Delta, TRUE)->Buffer); + SetDlgItemText(DiskPanel, IDC_ZREADBYTESDELTA_V, PhaFormatSize(EtDiskReadDelta.Delta, -1)->Buffer); + SetDlgItemText(DiskPanel, IDC_ZWRITESDELTA_V, PhaFormatUInt64(EtDiskWriteCountDelta.Delta, TRUE)->Buffer); + SetDlgItemText(DiskPanel, IDC_ZWRITEBYTESDELTA_V, PhaFormatSize(EtDiskWriteDelta.Delta, -1)->Buffer); +} + +PPH_PROCESS_RECORD EtpReferenceMaxDiskRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + ULONG maxProcessId; + + maxProcessId = PhGetItemCircularBuffer_ULONG(&EtMaxDiskHistory, Index); + + if (!maxProcessId) + return NULL; + + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); +} + +PPH_STRING EtpGetMaxDiskString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = EtpReferenceMaxDiskRecord(Index)) + { + maxUsageString = PhaFormatString( + L"\n%s (%lu)", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId) + ); + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} + +BOOLEAN EtpNetworkSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoDestroy: + { + if (NetworkDialog) + { + PhDeleteGraphState(&NetworkGraphState); + NetworkDialog = NULL; + } + } + return TRUE; + case SysInfoTick: + { + if (NetworkDialog) + { + EtpUpdateNetworkGraph(); + EtpUpdateNetworkPanel(); + } + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_NET); + createDialog->DialogProc = EtpNetworkDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, EtNetworkReceiveHistory.Count); + + if (!Section->GraphState.Valid) + { + ULONG i; + FLOAT max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + Section->GraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, i); + Section->GraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkSendHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + Section->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + Section->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + ULONG64 networkReceive; + ULONG64 networkSend; + + networkReceive = PhGetItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, getTooltipText->Index); + networkSend = PhGetItemCircularBuffer_ULONG(&EtNetworkSendHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"R: %s\nS: %s%s\n%s", + PhaFormatSize(networkReceive, -1)->Buffer, + PhaFormatSize(networkSend, -1)->Buffer, + PhGetStringOrEmpty(EtpGetMaxNetworkString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + + drawPanel->Title = PhCreateString(L"Network"); + drawPanel->SubTitle = PhFormatString( + L"R: %s\nS: %s", + PhaFormatSize(EtNetworkReceiveDelta.Delta, -1)->Buffer, + PhaFormatSize(EtNetworkSendDelta.Delta, -1)->Buffer + ); + } + return TRUE; + } + + return FALSE; +} + +INT_PTR CALLBACK EtpNetworkDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + PhInitializeGraphState(&NetworkGraphState); + + NetworkDialog = hwndDlg; + PhInitializeLayoutManager(&NetworkLayoutManager, hwndDlg); + graphItem = PhAddLayoutItem(&NetworkLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + panelItem = PhAddLayoutItem(&NetworkLayoutManager, GetDlgItem(hwndDlg, IDC_PANEL_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)NetworkSection->Parameters->LargeFont, FALSE); + + NetworkPanel = CreateDialog(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_SYSINFO_NETPANEL), hwndDlg, EtpNetworkPanelDialogProc); + ShowWindow(NetworkPanel, SW_SHOW); + PhAddLayoutItemEx(&NetworkLayoutManager, NetworkPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + NetworkGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(NetworkGraphHandle, TRUE); + + PhAddLayoutItemEx(&NetworkLayoutManager, NetworkGraphHandle, NULL, PH_ANCHOR_ALL, graphItem->Margin); + + EtpUpdateNetworkGraph(); + EtpUpdateNetworkPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&NetworkLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&NetworkLayoutManager); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + if (header->hwndFrom == NetworkGraphHandle) + { + EtpNotifyNetworkGraph(header); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK EtpNetworkPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +VOID EtpNotifyNetworkGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + NetworkSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + + PhGraphStateGetDrawInfo( + &NetworkGraphState, + getDrawInfo, + EtNetworkReceiveHistory.Count + ); + + if (!NetworkGraphState.Valid) + { + ULONG i; + FLOAT max = 1024 * 1024; // Minimum scaling of 1 MB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + NetworkGraphState.Data1[i] = data1 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, i); + NetworkGraphState.Data2[i] = data2 = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkSendHistory, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + NetworkGraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + NetworkGraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + NetworkGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (NetworkGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 networkReceive; + ULONG64 networkSend; + + networkReceive = PhGetItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, getTooltipText->Index); + networkSend = PhGetItemCircularBuffer_ULONG(&EtNetworkSendHistory, getTooltipText->Index); + + PhMoveReference(&NetworkGraphState.TooltipText, PhFormatString( + L"R: %s\nS: %s%s\n%s", + PhaFormatSize(networkReceive, -1)->Buffer, + PhaFormatSize(networkSend, -1)->Buffer, + PhGetStringOrEmpty(EtpGetMaxNetworkString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = NetworkGraphState.TooltipText->sr; + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record; + + record = NULL; + + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = EtpReferenceMaxNetworkRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(NetworkDialog, record); + PhDereferenceProcessRecord(record); + } + } + break; + } +} + +VOID EtpUpdateNetworkGraph( + VOID + ) +{ + NetworkGraphState.Valid = FALSE; + NetworkGraphState.TooltipIndex = -1; + Graph_MoveGrid(NetworkGraphHandle, 1); + Graph_Draw(NetworkGraphHandle); + Graph_UpdateTooltip(NetworkGraphHandle); + InvalidateRect(NetworkGraphHandle, NULL, FALSE); +} + +VOID EtpUpdateNetworkPanel( + VOID + ) +{ + SetDlgItemText(NetworkPanel, IDC_ZRECEIVESDELTA_V, PhaFormatUInt64(EtNetworkReceiveCountDelta.Delta, TRUE)->Buffer); + SetDlgItemText(NetworkPanel, IDC_ZRECEIVEBYTESDELTA_V, PhaFormatSize(EtNetworkReceiveDelta.Delta, -1)->Buffer); + SetDlgItemText(NetworkPanel, IDC_ZSENDSDELTA_V, PhaFormatUInt64(EtNetworkSendCountDelta.Delta, TRUE)->Buffer); + SetDlgItemText(NetworkPanel, IDC_ZSENDBYTESDELTA_V, PhaFormatSize(EtNetworkSendDelta.Delta, -1)->Buffer); +} + +PPH_PROCESS_RECORD EtpReferenceMaxNetworkRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + ULONG maxProcessId; + + maxProcessId = PhGetItemCircularBuffer_ULONG(&EtMaxNetworkHistory, Index); + + if (!maxProcessId) + return NULL; + + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); +} + +PPH_STRING EtpGetMaxNetworkString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = EtpReferenceMaxNetworkRecord(Index)) + { + maxUsageString = PhaFormatString( + L"\n%s (%lu)", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId) + ); + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} diff --git a/plugins/ExtendedTools/etwsys.h b/plugins/ExtendedTools/etwsys.h new file mode 100644 index 0000000..b33ac17 --- /dev/null +++ b/plugins/ExtendedTools/etwsys.h @@ -0,0 +1,90 @@ +#ifndef ETWSYS_H +#define ETWSYS_H + +// Disk section + +BOOLEAN EtpDiskSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +INT_PTR CALLBACK EtpDiskDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EtpDiskPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtpNotifyDiskGraph( + _In_ NMHDR *Header + ); + +VOID EtpUpdateDiskGraph( + VOID + ); + +VOID EtpUpdateDiskPanel( + VOID + ); + +PPH_PROCESS_RECORD EtpReferenceMaxDiskRecord( + _In_ LONG Index + ); + +PPH_STRING EtpGetMaxDiskString( + _In_ LONG Index + ); + +// Network section + +BOOLEAN EtpNetworkSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +INT_PTR CALLBACK EtpNetworkDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EtpNetworkPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtpNotifyNetworkGraph( + _In_ NMHDR *Header + ); + +VOID EtpUpdateNetworkGraph( + VOID + ); + +VOID EtpUpdateNetworkPanel( + VOID + ); + +PPH_PROCESS_RECORD EtpReferenceMaxNetworkRecord( + _In_ LONG Index + ); + +PPH_STRING EtpGetMaxNetworkString( + _In_ LONG Index + ); + +#endif diff --git a/plugins/ExtendedTools/exttools.h b/plugins/ExtendedTools/exttools.h new file mode 100644 index 0000000..7904903 --- /dev/null +++ b/plugins/ExtendedTools/exttools.h @@ -0,0 +1,549 @@ +#ifndef EXTTOOLS_H +#define EXTTOOLS_H + +#define PHNT_VERSION PHNT_VISTA +#include +#include +#include + +#include "resource.h" + +extern PPH_PLUGIN PluginInstance; +extern LIST_ENTRY EtProcessBlockListHead; +extern LIST_ENTRY EtNetworkBlockListHead; +extern HWND ProcessTreeNewHandle; +extern HWND NetworkTreeNewHandle; + +#define PLUGIN_NAME L"ProcessHacker.ExtendedTools" +#define SETTING_NAME_DISK_TREE_LIST_COLUMNS (PLUGIN_NAME L".DiskTreeListColumns") +#define SETTING_NAME_DISK_TREE_LIST_SORT (PLUGIN_NAME L".DiskTreeListSort") +#define SETTING_NAME_ENABLE_ETW_MONITOR (PLUGIN_NAME L".EnableEtwMonitor") +#define SETTING_NAME_ENABLE_GPU_MONITOR (PLUGIN_NAME L".EnableGpuMonitor") +#define SETTING_NAME_GPU_NODE_BITMAP (PLUGIN_NAME L".GpuNodeBitmap") +#define SETTING_NAME_GPU_LAST_NODE_COUNT (PLUGIN_NAME L".GpuLastNodeCount") + +#define ET_SCALE_DPI(Value) PhMultiplyDivide(Value, PhGlobalDpi, 96) + +// Graph update message + +#define UPDATE_MSG (WM_APP + 1) + +// Process icon + +typedef struct _ET_PROCESS_ICON +{ + LONG RefCount; + HICON Icon; +} ET_PROCESS_ICON, *PET_PROCESS_ICON; + +// Disk item + +#define HISTORY_SIZE 60 + +typedef struct _ET_DISK_ITEM +{ + LIST_ENTRY AgeListEntry; + ULONG AddTime; + ULONG FreshTime; + + HANDLE ProcessId; + PPH_STRING FileName; + PPH_STRING FileNameWin32; + + PPH_STRING ProcessName; + PET_PROCESS_ICON ProcessIcon; + PPH_PROCESS_RECORD ProcessRecord; + + ULONG IoPriority; + ULONG ResponseTimeCount; + FLOAT ResponseTimeTotal; // in milliseconds + FLOAT ResponseTimeAverage; + + ULONG64 ReadTotal; + ULONG64 WriteTotal; + ULONG64 ReadDelta; + ULONG64 WriteDelta; + ULONG64 ReadAverage; + ULONG64 WriteAverage; + + ULONG64 ReadHistory[HISTORY_SIZE]; + ULONG64 WriteHistory[HISTORY_SIZE]; + ULONG HistoryCount; + ULONG HistoryPosition; +} ET_DISK_ITEM, *PET_DISK_ITEM; + +// Disk node + +#define ETDSTNC_NAME 0 +#define ETDSTNC_FILE 1 +#define ETDSTNC_READRATEAVERAGE 2 +#define ETDSTNC_WRITERATEAVERAGE 3 +#define ETDSTNC_TOTALRATEAVERAGE 4 +#define ETDSTNC_IOPRIORITY 5 +#define ETDSTNC_RESPONSETIME 6 +#define ETDSTNC_MAXIMUM 7 + +typedef struct _ET_DISK_NODE +{ + PH_TREENEW_NODE Node; + + PET_DISK_ITEM DiskItem; + + PH_STRINGREF TextCache[ETDSTNC_MAXIMUM]; + + PPH_STRING ProcessNameText; + PPH_STRING ReadRateAverageText; + PPH_STRING WriteRateAverageText; + PPH_STRING TotalRateAverageText; + PPH_STRING ResponseTimeText; + + PPH_STRING TooltipText; +} ET_DISK_NODE, *PET_DISK_NODE; + +// Process tree columns + +#define ETPRTNC_DISKREADS 1 +#define ETPRTNC_DISKWRITES 2 +#define ETPRTNC_DISKREADBYTES 3 +#define ETPRTNC_DISKWRITEBYTES 4 +#define ETPRTNC_DISKTOTALBYTES 5 +#define ETPRTNC_DISKREADSDELTA 6 +#define ETPRTNC_DISKWRITESDELTA 7 +#define ETPRTNC_DISKREADBYTESDELTA 8 +#define ETPRTNC_DISKWRITEBYTESDELTA 9 +#define ETPRTNC_DISKTOTALBYTESDELTA 10 +#define ETPRTNC_NETWORKRECEIVES 11 +#define ETPRTNC_NETWORKSENDS 12 +#define ETPRTNC_NETWORKRECEIVEBYTES 13 +#define ETPRTNC_NETWORKSENDBYTES 14 +#define ETPRTNC_NETWORKTOTALBYTES 15 +#define ETPRTNC_NETWORKRECEIVESDELTA 16 +#define ETPRTNC_NETWORKSENDSDELTA 17 +#define ETPRTNC_NETWORKRECEIVEBYTESDELTA 18 +#define ETPRTNC_NETWORKSENDBYTESDELTA 19 +#define ETPRTNC_NETWORKTOTALBYTESDELTA 20 +#define ETPRTNC_HARDFAULTS 21 +#define ETPRTNC_HARDFAULTSDELTA 22 +#define ETPRTNC_PEAKTHREADS 23 +#define ETPRTNC_GPU 24 +#define ETPRTNC_GPUDEDICATEDBYTES 25 +#define ETPRTNC_GPUSHAREDBYTES 26 +#define ETPRTNC_DISKREADRATE 27 +#define ETPRTNC_DISKWRITERATE 28 +#define ETPRTNC_DISKTOTALRATE 29 +#define ETPRTNC_NETWORKRECEIVERATE 30 +#define ETPRTNC_NETWORKSENDRATE 31 +#define ETPRTNC_NETWORKTOTALRATE 32 +#define ETPRTNC_MAXIMUM 32 + +// Network list columns + +#define ETNETNC_RECEIVES 1 +#define ETNETNC_SENDS 2 +#define ETNETNC_RECEIVEBYTES 3 +#define ETNETNC_SENDBYTES 4 +#define ETNETNC_TOTALBYTES 5 +#define ETNETNC_RECEIVESDELTA 6 +#define ETNETNC_SENDSDELTA 7 +#define ETNETNC_RECEIVEBYTESDELTA 8 +#define ETNETNC_SENDBYTESDELTA 9 +#define ETNETNC_TOTALBYTESDELTA 10 +#define ETNETNC_FIREWALLSTATUS 11 +#define ETNETNC_RECEIVERATE 12 +#define ETNETNC_SENDRATE 13 +#define ETNETNC_TOTALRATE 14 +#define ETNETNC_MAXIMUM 14 + +// Firewall status + +typedef enum _ET_FIREWALL_STATUS +{ + FirewallUnknownStatus, + FirewallAllowedNotRestricted, + FirewallAllowedRestricted, + FirewallNotAllowedNotRestricted, + FirewallNotAllowedRestricted, + FirewallMaximumStatus +} ET_FIREWALL_STATUS; + +// Object extensions + +typedef struct _ET_PROCESS_BLOCK +{ + LIST_ENTRY ListEntry; + PPH_PROCESS_ITEM ProcessItem; + + ULONG64 DiskReadCount; + ULONG64 DiskWriteCount; + ULONG64 NetworkReceiveCount; + ULONG64 NetworkSendCount; + + ULONG64 DiskReadRaw; + ULONG64 DiskWriteRaw; + ULONG64 NetworkReceiveRaw; + ULONG64 NetworkSendRaw; + + PH_UINT64_DELTA DiskReadDelta; + PH_UINT64_DELTA DiskReadRawDelta; + PH_UINT64_DELTA DiskWriteDelta; + PH_UINT64_DELTA DiskWriteRawDelta; + PH_UINT64_DELTA NetworkReceiveDelta; + PH_UINT64_DELTA NetworkReceiveRawDelta; + PH_UINT64_DELTA NetworkSendDelta; + PH_UINT64_DELTA NetworkSendRawDelta; + + PH_UINT64_DELTA GpuRunningTimeDelta; + FLOAT GpuNodeUsage; + ULONG64 GpuDedicatedUsage; + ULONG64 GpuSharedUsage; + + PH_UINT32_DELTA HardFaultsDelta; + + PH_QUEUED_LOCK TextCacheLock; + PPH_STRING TextCache[ETPRTNC_MAXIMUM + 1]; + BOOLEAN TextCacheValid[ETPRTNC_MAXIMUM + 1]; + + PET_PROCESS_ICON SmallProcessIcon; +} ET_PROCESS_BLOCK, *PET_PROCESS_BLOCK; + +typedef struct _ET_NETWORK_BLOCK +{ + LIST_ENTRY ListEntry; + PPH_NETWORK_ITEM NetworkItem; + + ULONG64 ReceiveCount; + ULONG64 SendCount; + ULONG64 ReceiveRaw; + ULONG64 SendRaw; + + union + { + struct + { + PH_UINT64_DELTA ReceiveDelta; + PH_UINT64_DELTA ReceiveRawDelta; + PH_UINT64_DELTA SendDelta; + PH_UINT64_DELTA SendRawDelta; + }; + PH_UINT64_DELTA Deltas[4]; + }; + + ET_FIREWALL_STATUS FirewallStatus; + BOOLEAN FirewallStatusValid; + + PH_QUEUED_LOCK TextCacheLock; + PPH_STRING TextCache[ETNETNC_MAXIMUM + 1]; + BOOLEAN TextCacheValid[ETNETNC_MAXIMUM + 1]; +} ET_NETWORK_BLOCK, *PET_NETWORK_BLOCK; + +// main + +PET_PROCESS_BLOCK EtGetProcessBlock( + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +PET_NETWORK_BLOCK EtGetNetworkBlock( + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +// utils + +VOID EtFormatRate( + _In_ ULONG64 ValuePerPeriod, + _Inout_ PPH_STRING *Buffer, + _Out_opt_ PPH_STRINGREF String + ); + +// etwmon + +extern BOOLEAN EtEtwEnabled; + +// etwstat + +extern ULONG EtDiskReadCount; +extern ULONG EtDiskWriteCount; +extern ULONG EtNetworkReceiveCount; +extern ULONG EtNetworkSendCount; + +extern PH_UINT32_DELTA EtDiskReadDelta; +extern PH_UINT32_DELTA EtDiskWriteDelta; +extern PH_UINT32_DELTA EtNetworkReceiveDelta; +extern PH_UINT32_DELTA EtNetworkSendDelta; + +extern PH_UINT32_DELTA EtDiskReadCountDelta; +extern PH_UINT32_DELTA EtDiskWriteCountDelta; +extern PH_UINT32_DELTA EtNetworkReceiveCountDelta; +extern PH_UINT32_DELTA EtNetworkSendCountDelta; + +extern PH_CIRCULAR_BUFFER_ULONG EtDiskReadHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtDiskWriteHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtNetworkReceiveHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtNetworkSendHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtMaxDiskHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtMaxNetworkHistory; + +VOID EtEtwStatisticsInitialization( + VOID + ); + +VOID EtEtwStatisticsUninitialization( + VOID + ); + +// etwdisk + +extern BOOLEAN EtDiskEnabled; + +extern PPH_OBJECT_TYPE EtDiskItemType; +extern PH_CALLBACK EtDiskItemAddedEvent; +extern PH_CALLBACK EtDiskItemModifiedEvent; +extern PH_CALLBACK EtDiskItemRemovedEvent; +extern PH_CALLBACK EtDiskItemsUpdatedEvent; + +VOID EtInitializeDiskInformation( + VOID + ); + +PET_DISK_ITEM EtCreateDiskItem( + VOID + ); + +PET_DISK_ITEM EtReferenceDiskItem( + _In_ HANDLE ProcessId, + _In_ PPH_STRING FileName + ); + +PPH_STRING EtFileObjectToFileName( + _In_ PVOID FileObject + ); + +// procicon + +PET_PROCESS_ICON EtProcIconCreateProcessIcon( + _In_ HICON Icon + ); + +VOID EtProcIconReferenceProcessIcon( + _Inout_ PET_PROCESS_ICON ProcessIcon + ); + +VOID EtProcIconDereferenceProcessIcon( + _Inout_ PET_PROCESS_ICON ProcessIcon + ); + +PET_PROCESS_ICON EtProcIconReferenceSmallProcessIcon( + _Inout_ PET_PROCESS_BLOCK Block + ); + +VOID EtProcIconNotifyProcessDelete( + _Inout_ PET_PROCESS_BLOCK Block + ); + +// etwprprp + +VOID EtProcessEtwPropertiesInitializing( + _In_ PVOID Parameter + ); + +// disktab + +VOID EtInitializeDiskTab( + VOID + ); + +VOID EtLoadSettingsDiskTreeList( + VOID + ); + +VOID EtSaveSettingsDiskTreeList( + VOID + ); + +// gpumon + +extern BOOLEAN EtGpuEnabled; + +extern ULONG EtGpuTotalNodeCount; +extern ULONG EtGpuTotalSegmentCount; +extern ULONG64 EtGpuDedicatedLimit; +extern ULONG64 EtGpuSharedLimit; +extern RTL_BITMAP EtGpuNodeBitMap; + +extern PH_UINT64_DELTA EtClockTotalRunningTimeDelta; +extern LARGE_INTEGER EtClockTotalRunningTimeFrequency; +extern PH_UINT64_DELTA EtGpuTotalRunningTimeDelta; +extern PH_UINT64_DELTA EtGpuSystemRunningTimeDelta; +extern FLOAT EtGpuNodeUsage; +extern PH_CIRCULAR_BUFFER_FLOAT EtGpuNodeHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtMaxGpuNodeHistory; // ID of max. GPU usage process +extern PH_CIRCULAR_BUFFER_FLOAT EtMaxGpuNodeUsageHistory; + +extern PPH_UINT64_DELTA EtGpuNodesTotalRunningTimeDelta; +extern PPH_CIRCULAR_BUFFER_FLOAT EtGpuNodesHistory; + +extern ULONG64 EtGpuDedicatedUsage; +extern ULONG64 EtGpuSharedUsage; +extern PH_CIRCULAR_BUFFER_ULONG EtGpuDedicatedHistory; +extern PH_CIRCULAR_BUFFER_ULONG EtGpuSharedHistory; + +VOID EtGpuMonitorInitialization( + VOID + ); + +typedef struct _ET_PROCESS_GPU_STATISTICS +{ + ULONG SegmentCount; + ULONG NodeCount; + + ULONG64 DedicatedCommitted; + ULONG64 SharedCommitted; + + ULONG64 BytesAllocated; + ULONG64 BytesReserved; + ULONG64 WriteCombinedBytesAllocated; + ULONG64 WriteCombinedBytesReserved; + ULONG64 CachedBytesAllocated; + ULONG64 CachedBytesReserved; + ULONG64 SectionBytesAllocated; + ULONG64 SectionBytesReserved; + + ULONG64 RunningTime; + ULONG64 ContextSwitches; +} ET_PROCESS_GPU_STATISTICS, *PET_PROCESS_GPU_STATISTICS; + +VOID EtSaveGpuMonitorSettings( + VOID + ); + +ULONG EtGetGpuAdapterCount( + VOID + ); + +ULONG EtGetGpuAdapterIndexFromNodeIndex( + _In_ ULONG NodeIndex + ); + +PPH_STRING EtGetGpuAdapterDescription( + _In_ ULONG Index + ); + +VOID EtAllocateGpuNodeBitMap( + _Out_ PRTL_BITMAP BitMap + ); + +VOID EtUpdateGpuNodeBitMap( + _In_ PRTL_BITMAP NewBitMap + ); + +VOID EtQueryProcessGpuStatistics( + _In_ HANDLE ProcessHandle, + _Out_ PET_PROCESS_GPU_STATISTICS Statistics + ); + +// gpuprprp + +VOID EtProcessGpuPropertiesInitializing( + _In_ PVOID Parameter + ); + +// treeext + +VOID EtProcessTreeNewInitializing( + _In_ PVOID Parameter + ); + +VOID EtProcessTreeNewMessage( + _In_ PVOID Parameter + ); + +VOID EtNetworkTreeNewInitializing( + _In_ PVOID Parameter + ); + +VOID EtNetworkTreeNewMessage( + _In_ PVOID Parameter + ); + +ET_FIREWALL_STATUS EtQueryFirewallStatus( + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +// etwsys + +VOID EtEtwSystemInformationInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ); + +// etwmini + +VOID EtEtwMiniInformationInitializing( + _In_ PPH_PLUGIN_MINIINFO_POINTERS Pointers + ); + +// gpunodes + +VOID EtShowGpuNodesDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_SYSINFO_PARAMETERS Parameters + ); + +// gpusys + +VOID EtGpuSystemInformationInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ); + +// gpumini + +VOID EtGpuMiniInformationInitializing( + _In_ PPH_PLUGIN_MINIINFO_POINTERS Pointers + ); + +// iconext + +VOID EtRegisterNotifyIcons( + VOID + ); + +// modsrv + +VOID EtShowModuleServicesDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ PWSTR ModuleName + ); + +// objprp + +VOID EtHandlePropertiesInitializing( + _In_ PVOID Parameter + ); + +// options + +VOID EtShowOptionsDialog( + _In_ HWND ParentWindowHandle + ); + +// thrdact + +BOOLEAN EtUiCancelIoThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread + ); + +// unldll + +VOID EtShowUnloadedDllsDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +// wswatch + +VOID EtShowWsWatchDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ); + +#endif diff --git a/plugins/ExtendedTools/gpumini.c b/plugins/ExtendedTools/gpumini.c new file mode 100644 index 0000000..db59945 --- /dev/null +++ b/plugins/ExtendedTools/gpumini.c @@ -0,0 +1,129 @@ +/* + * Process Hacker Extended Tools - + * GPU mini information section + * + * Copyright (C) 2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "gpumini.h" + +VOID EtGpuMiniInformationInitializing( + _In_ PPH_PLUGIN_MINIINFO_POINTERS Pointers + ) +{ + PH_MINIINFO_LIST_SECTION section; + + memset(§ion, 0, sizeof(PH_MINIINFO_LIST_SECTION)); + section.Callback = EtpGpuListSectionCallback; + Pointers->CreateListSection(L"GPU", 0, §ion); +} + +BOOLEAN EtpGpuListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case MiListSectionTick: + { + ListSection->Section->Parameters->SetSectionText(ListSection->Section, + PhaFormatString(L"GPU %.2f%%", EtGpuNodeUsage * 100)); + } + break; + case MiListSectionSortProcessList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_PROCESS_NODE), EtpGpuListSectionProcessCompareFunction); + } + return TRUE; + case MiListSectionAssignSortData: + { + PPH_MINIINFO_LIST_SECTION_ASSIGN_SORT_DATA assignSortData = Parameter1; + PPH_LIST processes = assignSortData->ProcessGroup->Processes; + FLOAT gpuUsage = 0; + ULONG i; + + for (i = 0; i < processes->Count; i++) + { + PPH_PROCESS_ITEM processItem = processes->Items[i]; + PET_PROCESS_BLOCK block = EtGetProcessBlock(processItem); + gpuUsage += block->GpuNodeUsage; + } + + *(PFLOAT)assignSortData->SortData->UserData = gpuUsage; + } + return TRUE; + case MiListSectionSortGroupList: + { + PPH_MINIINFO_LIST_SECTION_SORT_LIST sortList = Parameter1; + + qsort(sortList->List->Items, sortList->List->Count, + sizeof(PPH_MINIINFO_LIST_SECTION_SORT_DATA), EtpGpuListSectionNodeCompareFunction); + } + return TRUE; + case MiListSectionGetUsageText: + { + PPH_MINIINFO_LIST_SECTION_GET_USAGE_TEXT getUsageText = Parameter1; + PPH_LIST processes = getUsageText->ProcessGroup->Processes; + FLOAT gpuUsage = *(PFLOAT)getUsageText->SortData->UserData; + + PhMoveReference(&getUsageText->Line1, PhFormatString(L"%.2f%%", gpuUsage * 100)); + } + return TRUE; + } + + return FALSE; +} + +int __cdecl EtpGpuListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + int result; + PPH_PROCESS_NODE node1 = *(PPH_PROCESS_NODE *)elem1; + PPH_PROCESS_NODE node2 = *(PPH_PROCESS_NODE *)elem2; + PET_PROCESS_BLOCK block1 = EtGetProcessBlock(node1->ProcessItem); + PET_PROCESS_BLOCK block2 = EtGetProcessBlock(node2->ProcessItem); + + result = singlecmp(block2->GpuNodeUsage, block1->GpuNodeUsage); + + if (result == 0) + result = uint64cmp(block2->GpuRunningTimeDelta.Value, block1->GpuRunningTimeDelta.Value); + if (result == 0) + result = singlecmp(node2->ProcessItem->CpuUsage, node1->ProcessItem->CpuUsage); + + return result; +} + +int __cdecl EtpGpuListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PPH_MINIINFO_LIST_SECTION_SORT_DATA data1 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem1; + PPH_MINIINFO_LIST_SECTION_SORT_DATA data2 = *(PPH_MINIINFO_LIST_SECTION_SORT_DATA *)elem2; + + return singlecmp(*(PFLOAT)data2->UserData, *(PFLOAT)data1->UserData); +} diff --git a/plugins/ExtendedTools/gpumini.h b/plugins/ExtendedTools/gpumini.h new file mode 100644 index 0000000..d3cb682 --- /dev/null +++ b/plugins/ExtendedTools/gpumini.h @@ -0,0 +1,21 @@ +#ifndef GPUMINI_H +#define GPUMINI_H + +BOOLEAN EtpGpuListSectionCallback( + _In_ struct _PH_MINIINFO_LIST_SECTION *ListSection, + _In_ PH_MINIINFO_LIST_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +int __cdecl EtpGpuListSectionProcessCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +int __cdecl EtpGpuListSectionNodeCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ); + +#endif \ No newline at end of file diff --git a/plugins/ExtendedTools/gpumon.c b/plugins/ExtendedTools/gpumon.c new file mode 100644 index 0000000..1f40168 --- /dev/null +++ b/plugins/ExtendedTools/gpumon.c @@ -0,0 +1,846 @@ +/* + * Process Hacker Extended Tools - + * GPU monitoring + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#define INITGUID +#include "exttools.h" +#include +#include +#include "d3dkmt.h" +#include "gpumon.h" + +static GUID GUID_DISPLAY_DEVICE_ARRIVAL_I = { 0x1ca05180, 0xa699, 0x450a, { 0x9a, 0x0c, 0xde, 0x4f, 0xbe, 0x3d, 0xdd, 0x89 } }; + +static PFND3DKMT_OPENADAPTERFROMDEVICENAME D3DKMTOpenAdapterFromDeviceName_I; +static PFND3DKMT_CLOSEADAPTER D3DKMTCloseAdapter_I; +static PFND3DKMT_QUERYSTATISTICS D3DKMTQueryStatistics_I; + +BOOLEAN EtGpuEnabled; +static PPH_LIST EtpGpuAdapterList; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; + +ULONG EtGpuTotalNodeCount; +ULONG EtGpuTotalSegmentCount; +ULONG64 EtGpuDedicatedLimit; +ULONG64 EtGpuSharedLimit; +ULONG EtGpuNextNodeIndex = 0; +RTL_BITMAP EtGpuNodeBitMap; +PULONG EtGpuNodeBitMapBuffer; +ULONG EtGpuNodeBitMapBitsSet; +PULONG EtGpuNewNodeBitMapBuffer; + +PH_UINT64_DELTA EtClockTotalRunningTimeDelta; +LARGE_INTEGER EtClockTotalRunningTimeFrequency; +PH_UINT64_DELTA EtGpuTotalRunningTimeDelta; +PH_UINT64_DELTA EtGpuSystemRunningTimeDelta; +FLOAT EtGpuNodeUsage; +PH_CIRCULAR_BUFFER_FLOAT EtGpuNodeHistory; +PH_CIRCULAR_BUFFER_ULONG EtMaxGpuNodeHistory; // ID of max. GPU usage process +PH_CIRCULAR_BUFFER_FLOAT EtMaxGpuNodeUsageHistory; + +PPH_UINT64_DELTA EtGpuNodesTotalRunningTimeDelta; +PPH_CIRCULAR_BUFFER_FLOAT EtGpuNodesHistory; + +ULONG64 EtGpuDedicatedUsage; +ULONG64 EtGpuSharedUsage; +PH_CIRCULAR_BUFFER_ULONG EtGpuDedicatedHistory; +PH_CIRCULAR_BUFFER_ULONG EtGpuSharedHistory; + +VOID EtGpuMonitorInitialization( + VOID + ) +{ + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_GPU_MONITOR) && WindowsVersion >= WINDOWS_7) + { + PVOID gdi32Handle; + + if (gdi32Handle = PhGetDllHandle(L"gdi32.dll")) + { + D3DKMTOpenAdapterFromDeviceName_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTOpenAdapterFromDeviceName", 0); + D3DKMTCloseAdapter_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTCloseAdapter", 0); + D3DKMTQueryStatistics_I = PhGetProcedureAddress(gdi32Handle, "D3DKMTQueryStatistics", 0); + } + + if ( + D3DKMTOpenAdapterFromDeviceName_I && + D3DKMTCloseAdapter_I && + D3DKMTQueryStatistics_I + ) + { + EtpGpuAdapterList = PhCreateList(4); + + if (EtpInitializeD3DStatistics() && EtpGpuAdapterList->Count != 0) + EtGpuEnabled = TRUE; + } + } + + if (EtGpuEnabled) + { + ULONG sampleCount; + ULONG i; + ULONG j; + PPH_STRING bitmapString; + D3DKMT_QUERYSTATISTICS queryStatistics; + + sampleCount = PhGetIntegerSetting(L"SampleCount"); + PhInitializeCircularBuffer_FLOAT(&EtGpuNodeHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtMaxGpuNodeHistory, sampleCount); + PhInitializeCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtGpuDedicatedHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&EtGpuSharedHistory, sampleCount); + + EtGpuNodesTotalRunningTimeDelta = PhAllocate(sizeof(PH_UINT64_DELTA) * EtGpuTotalNodeCount); + memset(EtGpuNodesTotalRunningTimeDelta, 0, sizeof(PH_UINT64_DELTA) * EtGpuTotalNodeCount); + EtGpuNodesHistory = PhAllocate(sizeof(PH_CIRCULAR_BUFFER_FLOAT) * EtGpuTotalNodeCount); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + PhInitializeCircularBuffer_FLOAT(&EtGpuNodesHistory[i], sampleCount); + } + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + EtGpuProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + + // Load the node bitmap. + + bitmapString = PhGetStringSetting(SETTING_NAME_GPU_NODE_BITMAP); + + if (!(bitmapString->Length & 3) && bitmapString->Length / 4 <= BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount)) + { + PhHexStringToBuffer(&bitmapString->sr, (PUCHAR)EtGpuNodeBitMapBuffer); + EtGpuNodeBitMapBitsSet = RtlNumberOfSetBits(&EtGpuNodeBitMap); + } + + PhDereferenceObject(bitmapString); + + // Fix up the node bitmap if the current node count differs from what we've seen. + if (EtGpuTotalNodeCount != PhGetIntegerSetting(SETTING_NAME_GPU_LAST_NODE_COUNT)) + { + ULONG maxContextSwitch = 0; + ULONG maxContextSwitchNodeIndex = 0; + + RtlClearAllBits(&EtGpuNodeBitMap); + EtGpuNodeBitMapBitsSet = 0; + + for (i = 0; i < EtpGpuAdapterList->Count; i++) + { + PETP_GPU_ADAPTER gpuAdapter = EtpGpuAdapterList->Items[i]; + + for (j = 0; j < gpuAdapter->NodeCount; j++) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_NODE; + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + queryStatistics.QueryNode.NodeId = j; + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + // The numbers below are quite arbitrary. + if (queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart != 0 && + queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch > 10000) + { + RtlSetBits(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j, 1); + EtGpuNodeBitMapBitsSet++; + } + + if (maxContextSwitch < queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch) + { + maxContextSwitch = queryStatistics.QueryResult.NodeInformation.GlobalInformation.ContextSwitch; + maxContextSwitchNodeIndex = gpuAdapter->FirstNodeIndex + j; + } + } + } + } + + // Just in case + if (EtGpuNodeBitMapBitsSet == 0) + { + RtlSetBits(&EtGpuNodeBitMap, maxContextSwitchNodeIndex, 1); + EtGpuNodeBitMapBitsSet = 1; + } + + PhSetIntegerSetting(SETTING_NAME_GPU_LAST_NODE_COUNT, EtGpuTotalNodeCount); + } + } +} + +BOOLEAN EtpInitializeD3DStatistics( + VOID + ) +{ + PWSTR deviceInterfaceList = NULL; + ULONG deviceInterfaceListLength = 0; + PWSTR deviceInterface; + D3DKMT_OPENADAPTERFROMDEVICENAME openAdapterFromDeviceName; + D3DKMT_QUERYSTATISTICS queryStatistics; + D3DKMT_CLOSEADAPTER closeAdapter; + + if (CM_Get_Device_Interface_List_Size( + &deviceInterfaceListLength, + &GUID_DISPLAY_DEVICE_ARRIVAL_I, + NULL, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT + ) != CR_SUCCESS) + { + return FALSE; + } + + deviceInterfaceList = PhAllocate(deviceInterfaceListLength * sizeof(WCHAR)); + memset(deviceInterfaceList, 0, deviceInterfaceListLength * sizeof(WCHAR)); + + if (CM_Get_Device_Interface_List( + &GUID_DISPLAY_DEVICE_ARRIVAL_I, + NULL, + deviceInterfaceList, + deviceInterfaceListLength, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT + ) != CR_SUCCESS) + { + PhFree(deviceInterfaceList); + return FALSE; + } + + for (deviceInterface = deviceInterfaceList; *deviceInterface; deviceInterface += PhCountStringZ(deviceInterface) + 1) + { + memset(&openAdapterFromDeviceName, 0, sizeof(D3DKMT_OPENADAPTERFROMDEVICENAME)); + openAdapterFromDeviceName.pDeviceName = deviceInterface; + + if (NT_SUCCESS(D3DKMTOpenAdapterFromDeviceName_I(&openAdapterFromDeviceName))) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_ADAPTER; + queryStatistics.AdapterLuid = openAdapterFromDeviceName.AdapterLuid; + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + PETP_GPU_ADAPTER gpuAdapter; + ULONG i; + + gpuAdapter = EtpAllocateGpuAdapter(queryStatistics.QueryResult.AdapterInformation.NbSegments); + gpuAdapter->AdapterLuid = openAdapterFromDeviceName.AdapterLuid; + gpuAdapter->Description = EtpQueryDeviceDescription(deviceInterface); + gpuAdapter->NodeCount = queryStatistics.QueryResult.AdapterInformation.NodeCount; + gpuAdapter->SegmentCount = queryStatistics.QueryResult.AdapterInformation.NbSegments; + RtlInitializeBitMap(&gpuAdapter->ApertureBitMap, gpuAdapter->ApertureBitMapBuffer, queryStatistics.QueryResult.AdapterInformation.NbSegments); + + PhAddItemList(EtpGpuAdapterList, gpuAdapter); + EtGpuTotalNodeCount += gpuAdapter->NodeCount; + EtGpuTotalSegmentCount += gpuAdapter->SegmentCount; + + gpuAdapter->FirstNodeIndex = EtGpuNextNodeIndex; + EtGpuNextNodeIndex += gpuAdapter->NodeCount; + + for (i = 0; i < gpuAdapter->SegmentCount; i++) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_SEGMENT; + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + queryStatistics.QuerySegment.SegmentId = i; + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + ULONG64 commitLimit; + ULONG aperture; + + if (WindowsVersion >= WINDOWS_8) + { + commitLimit = queryStatistics.QueryResult.SegmentInformation.CommitLimit; + aperture = queryStatistics.QueryResult.SegmentInformation.Aperture; + } + else + { + commitLimit = queryStatistics.QueryResult.SegmentInformationV1.CommitLimit; + aperture = queryStatistics.QueryResult.SegmentInformationV1.Aperture; + } + + if (aperture) + EtGpuSharedLimit += commitLimit; + else + EtGpuDedicatedLimit += commitLimit; + + if (aperture) + RtlSetBits(&gpuAdapter->ApertureBitMap, i, 1); + } + } + } + + memset(&closeAdapter, 0, sizeof(D3DKMT_CLOSEADAPTER)); + closeAdapter.hAdapter = openAdapterFromDeviceName.hAdapter; + D3DKMTCloseAdapter_I(&closeAdapter); + } + } + + PhFree(deviceInterfaceList); + + EtGpuNodeBitMapBuffer = PhAllocate(BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount)); + RtlInitializeBitMap(&EtGpuNodeBitMap, EtGpuNodeBitMapBuffer, EtGpuTotalNodeCount); + RtlSetBits(&EtGpuNodeBitMap, 0, 1); + EtGpuNodeBitMapBitsSet = 1; + + return TRUE; +} + +PETP_GPU_ADAPTER EtpAllocateGpuAdapter( + _In_ ULONG NumberOfSegments + ) +{ + PETP_GPU_ADAPTER adapter; + SIZE_T sizeNeeded; + + sizeNeeded = FIELD_OFFSET(ETP_GPU_ADAPTER, ApertureBitMapBuffer); + sizeNeeded += BYTES_NEEDED_FOR_BITS(NumberOfSegments); + + adapter = PhAllocate(sizeNeeded); + memset(adapter, 0, sizeNeeded); + + return adapter; +} + +PPH_STRING EtpQueryDeviceDescription( + _In_ PWSTR DeviceInterface + ) +{ + CONFIGRET result; + PPH_STRING string; + ULONG bufferSize; + DEVPROPTYPE devicePropertyType; + DEVINST deviceInstanceHandle; + ULONG deviceInstanceIdLength = MAX_DEVICE_ID_LEN; + WCHAR deviceInstanceId[MAX_DEVICE_ID_LEN]; + + if (CM_Get_Device_Interface_Property( + DeviceInterface, + &DEVPKEY_Device_InstanceId, + &devicePropertyType, + (PBYTE)deviceInstanceId, + &deviceInstanceIdLength, + 0 + ) != CR_SUCCESS) + { + return NULL; + } + + if (CM_Locate_DevNode( + &deviceInstanceHandle, + deviceInstanceId, + CM_LOCATE_DEVNODE_NORMAL + ) != CR_SUCCESS) + { + return NULL; + } + + bufferSize = 0x40; + string = PhCreateStringEx(NULL, bufferSize); + + if ((result = CM_Get_DevNode_Property( // CM_Get_DevNode_Registry_Property with CM_DRP_DEVICEDESC?? + deviceInstanceHandle, + &DEVPKEY_Device_DeviceDesc, + &devicePropertyType, + (PBYTE)string->Buffer, + &bufferSize, + 0 + )) != CR_SUCCESS) + { + PhDereferenceObject(string); + string = PhCreateStringEx(NULL, bufferSize); + + result = CM_Get_DevNode_Property( + deviceInstanceHandle, + &DEVPKEY_Device_DeviceDesc, + &devicePropertyType, + (PBYTE)string->Buffer, + &bufferSize, + 0 + ); + } + + if (result != CR_SUCCESS) + { + PhDereferenceObject(string); + return NULL; + } + + PhTrimToNullTerminatorString(string); + + return string; +} + +VOID EtpUpdateSegmentInformation( + _In_opt_ PET_PROCESS_BLOCK Block + ) +{ + ULONG i; + ULONG j; + PETP_GPU_ADAPTER gpuAdapter; + D3DKMT_QUERYSTATISTICS queryStatistics; + ULONG64 dedicatedUsage; + ULONG64 sharedUsage; + + if (Block && !Block->ProcessItem->QueryHandle) + return; + + dedicatedUsage = 0; + sharedUsage = 0; + + for (i = 0; i < EtpGpuAdapterList->Count; i++) + { + gpuAdapter = EtpGpuAdapterList->Items[i]; + + for (j = 0; j < gpuAdapter->SegmentCount; j++) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + + if (Block) + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT; + else + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_SEGMENT; + + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + + if (Block) + { + queryStatistics.hProcess = Block->ProcessItem->QueryHandle; + queryStatistics.QueryProcessSegment.SegmentId = j; + } + else + { + queryStatistics.QuerySegment.SegmentId = j; + } + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + if (Block) + { + ULONG64 bytesCommitted; + + if (WindowsVersion >= WINDOWS_8) + { + bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted; + } + else + { + bytesCommitted = (ULONG)queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted; + } + + if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j)) + sharedUsage += bytesCommitted; + else + dedicatedUsage += bytesCommitted; + } + else + { + ULONG64 bytesCommitted; + + if (WindowsVersion >= WINDOWS_8) + { + bytesCommitted = queryStatistics.QueryResult.SegmentInformation.BytesCommitted; + } + else + { + bytesCommitted = queryStatistics.QueryResult.SegmentInformationV1.BytesCommitted; + } + + if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j)) + sharedUsage += bytesCommitted; + else + dedicatedUsage += bytesCommitted; + } + } + } + } + + if (Block) + { + Block->GpuDedicatedUsage = dedicatedUsage; + Block->GpuSharedUsage = sharedUsage; + } + else + { + EtGpuDedicatedUsage = dedicatedUsage; + EtGpuSharedUsage = sharedUsage; + } +} + +VOID EtpUpdateNodeInformation( + _In_opt_ PET_PROCESS_BLOCK Block + ) +{ + ULONG i; + ULONG j; + PETP_GPU_ADAPTER gpuAdapter; + D3DKMT_QUERYSTATISTICS queryStatistics; + ULONG64 totalRunningTime; + ULONG64 systemRunningTime; + + if (Block && !Block->ProcessItem->QueryHandle) + return; + + totalRunningTime = 0; + systemRunningTime = 0; + + for (i = 0; i < EtpGpuAdapterList->Count; i++) + { + gpuAdapter = EtpGpuAdapterList->Items[i]; + + for (j = 0; j < gpuAdapter->NodeCount; j++) + { + if (Block && !RtlCheckBit(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j)) + continue; + + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + + if (Block) + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_NODE; + else + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_NODE; + + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + + if (Block) + { + queryStatistics.hProcess = Block->ProcessItem->QueryHandle; + queryStatistics.QueryProcessNode.NodeId = j; + } + else + { + queryStatistics.QueryNode.NodeId = j; + } + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + if (Block) + { + totalRunningTime += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime.QuadPart; + } + else + { + ULONG nodeIndex; + + nodeIndex = gpuAdapter->FirstNodeIndex + j; + + PhUpdateDelta(&EtGpuNodesTotalRunningTimeDelta[nodeIndex], queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart); + + if (RtlCheckBit(&EtGpuNodeBitMap, gpuAdapter->FirstNodeIndex + j)) + { + totalRunningTime += queryStatistics.QueryResult.NodeInformation.GlobalInformation.RunningTime.QuadPart; + systemRunningTime += queryStatistics.QueryResult.NodeInformation.SystemInformation.RunningTime.QuadPart; + } + } + } + } + } + + if (Block) + { + PhUpdateDelta(&Block->GpuRunningTimeDelta, totalRunningTime); + } + else + { + LARGE_INTEGER performanceCounter; + + NtQueryPerformanceCounter(&performanceCounter, &EtClockTotalRunningTimeFrequency); + PhUpdateDelta(&EtClockTotalRunningTimeDelta, performanceCounter.QuadPart); + PhUpdateDelta(&EtGpuTotalRunningTimeDelta, totalRunningTime); + PhUpdateDelta(&EtGpuSystemRunningTimeDelta, systemRunningTime); + } +} + +VOID NTAPI EtGpuProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + static ULONG runCount = 0; // MUST keep in sync with runCount in process provider + + DOUBLE elapsedTime; // total GPU node elapsed time in micro-seconds + ULONG i; + PLIST_ENTRY listEntry; + FLOAT maxNodeValue = 0; + PET_PROCESS_BLOCK maxNodeBlock = NULL; + + // Update global statistics. + + EtpUpdateSegmentInformation(NULL); + EtpUpdateNodeInformation(NULL); + + elapsedTime = (DOUBLE)EtClockTotalRunningTimeDelta.Delta * 10000000 / EtClockTotalRunningTimeFrequency.QuadPart; + + if (elapsedTime != 0) + EtGpuNodeUsage = (FLOAT)(EtGpuTotalRunningTimeDelta.Delta / (elapsedTime * EtGpuNodeBitMapBitsSet)); + else + EtGpuNodeUsage = 0; + + if (EtGpuNodeUsage > 1) + EtGpuNodeUsage = 1; + + // Do the update of the node bitmap if needed. + if (EtGpuNewNodeBitMapBuffer) + { + PULONG newBuffer; + + newBuffer = _InterlockedExchangePointer(&EtGpuNewNodeBitMapBuffer, NULL); + + if (newBuffer) + { + PhFree(EtGpuNodeBitMap.Buffer); + EtGpuNodeBitMap.Buffer = newBuffer; + EtGpuNodeBitMapBuffer = newBuffer; + EtGpuNodeBitMapBitsSet = RtlNumberOfSetBits(&EtGpuNodeBitMap); + EtSaveGpuMonitorSettings(); + } + } + + // Update per-process statistics. + // Note: no lock is needed because we only ever modify the list on this same thread. + + listEntry = EtProcessBlockListHead.Flink; + + while (listEntry != &EtProcessBlockListHead) + { + PET_PROCESS_BLOCK block; + + block = CONTAINING_RECORD(listEntry, ET_PROCESS_BLOCK, ListEntry); + + EtpUpdateSegmentInformation(block); + EtpUpdateNodeInformation(block); + + if (elapsedTime != 0) + { + block->GpuNodeUsage = (FLOAT)(block->GpuRunningTimeDelta.Delta / (elapsedTime * EtGpuNodeBitMapBitsSet)); + + if (block->GpuNodeUsage > 1) + block->GpuNodeUsage = 1; + } + + if (maxNodeValue < block->GpuNodeUsage) + { + maxNodeValue = block->GpuNodeUsage; + maxNodeBlock = block; + } + + listEntry = listEntry->Flink; + } + + // Update history buffers. + + if (runCount != 0) + { + PhAddItemCircularBuffer_FLOAT(&EtGpuNodeHistory, EtGpuNodeUsage); + PhAddItemCircularBuffer_ULONG(&EtGpuDedicatedHistory, (ULONG)(EtGpuDedicatedUsage / PAGE_SIZE)); + PhAddItemCircularBuffer_ULONG(&EtGpuSharedHistory, (ULONG)(EtGpuSharedUsage / PAGE_SIZE)); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + FLOAT usage; + + usage = (FLOAT)(EtGpuNodesTotalRunningTimeDelta[i].Delta / elapsedTime); + + if (usage > 1) + usage = 1; + + PhAddItemCircularBuffer_FLOAT(&EtGpuNodesHistory[i], usage); + } + + if (maxNodeBlock) + { + PhAddItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, HandleToUlong(maxNodeBlock->ProcessItem->ProcessId)); + PhAddItemCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, maxNodeBlock->GpuNodeUsage); + PhReferenceProcessRecordForStatistics(maxNodeBlock->ProcessItem->Record); + } + else + { + PhAddItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, 0); + PhAddItemCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, 0); + } + } + + runCount++; +} + +VOID EtSaveGpuMonitorSettings( + VOID + ) +{ + PPH_STRING string; + + string = PhBufferToHexString((PUCHAR)EtGpuNodeBitMapBuffer, BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount)); + PhSetStringSetting2(SETTING_NAME_GPU_NODE_BITMAP, &string->sr); + PhDereferenceObject(string); +} + +ULONG EtGetGpuAdapterCount( + VOID + ) +{ + return EtpGpuAdapterList->Count; +} + +ULONG EtGetGpuAdapterIndexFromNodeIndex( + _In_ ULONG NodeIndex + ) +{ + ULONG i; + PETP_GPU_ADAPTER gpuAdapter; + + for (i = 0; i < EtpGpuAdapterList->Count; i++) + { + gpuAdapter = EtpGpuAdapterList->Items[i]; + + if (NodeIndex >= gpuAdapter->FirstNodeIndex && NodeIndex < gpuAdapter->FirstNodeIndex + gpuAdapter->NodeCount) + return i; + } + + return -1; +} + +PPH_STRING EtGetGpuAdapterDescription( + _In_ ULONG Index + ) +{ + PPH_STRING description; + + if (Index >= EtpGpuAdapterList->Count) + return NULL; + + description = ((PETP_GPU_ADAPTER)EtpGpuAdapterList->Items[Index])->Description; + + if (description) + { + PhReferenceObject(description); + return description; + } + else + { + return NULL; + } +} + +VOID EtAllocateGpuNodeBitMap( + _Out_ PRTL_BITMAP BitMap + ) +{ + SIZE_T numberOfBytes; + + numberOfBytes = BYTES_NEEDED_FOR_BITS(EtGpuTotalNodeCount); + + BitMap->Buffer = PhAllocate(numberOfBytes); + BitMap->SizeOfBitMap = EtGpuTotalNodeCount; + memset(BitMap->Buffer, 0, numberOfBytes); +} + +VOID EtUpdateGpuNodeBitMap( + _In_ PRTL_BITMAP NewBitMap + ) +{ + PULONG buffer; + + buffer = _InterlockedExchangePointer(&EtGpuNewNodeBitMapBuffer, NewBitMap->Buffer); + + if (buffer) + PhFree(buffer); +} + +VOID EtQueryProcessGpuStatistics( + _In_ HANDLE ProcessHandle, + _Out_ PET_PROCESS_GPU_STATISTICS Statistics + ) +{ + NTSTATUS status; + ULONG i; + ULONG j; + PETP_GPU_ADAPTER gpuAdapter; + D3DKMT_QUERYSTATISTICS queryStatistics; + + memset(Statistics, 0, sizeof(ET_PROCESS_GPU_STATISTICS)); + + for (i = 0; i < EtpGpuAdapterList->Count; i++) + { + gpuAdapter = EtpGpuAdapterList->Items[i]; + + Statistics->SegmentCount += gpuAdapter->SegmentCount; + Statistics->NodeCount += gpuAdapter->NodeCount; + + for (j = 0; j < gpuAdapter->SegmentCount; j++) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_SEGMENT; + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + queryStatistics.QueryProcessSegment.SegmentId = j; + + if (NT_SUCCESS(status = D3DKMTQueryStatistics_I(&queryStatistics))) + { + ULONG64 bytesCommitted; + + if (WindowsVersion >= WINDOWS_8) + { + bytesCommitted = queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted; + } + else + { + bytesCommitted = (ULONG)queryStatistics.QueryResult.ProcessSegmentInformation.BytesCommitted; + } + + if (RtlCheckBit(&gpuAdapter->ApertureBitMap, j)) + Statistics->SharedCommitted += bytesCommitted; + else + Statistics->DedicatedCommitted += bytesCommitted; + } + } + + for (j = 0; j < gpuAdapter->NodeCount; j++) + { + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS_NODE; + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + queryStatistics.QueryProcessNode.NodeId = j; + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + Statistics->RunningTime += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime.QuadPart; + Statistics->ContextSwitches += queryStatistics.QueryResult.ProcessNodeInformation.ContextSwitch; + } + } + + memset(&queryStatistics, 0, sizeof(D3DKMT_QUERYSTATISTICS)); + queryStatistics.Type = D3DKMT_QUERYSTATISTICS_PROCESS; + queryStatistics.AdapterLuid = gpuAdapter->AdapterLuid; + queryStatistics.hProcess = ProcessHandle; + + if (NT_SUCCESS(D3DKMTQueryStatistics_I(&queryStatistics))) + { + Statistics->BytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.BytesAllocated; + Statistics->BytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.BytesReserved; + Statistics->WriteCombinedBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.WriteCombinedBytesAllocated; + Statistics->WriteCombinedBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.WriteCombinedBytesReserved; + Statistics->CachedBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.CachedBytesAllocated; + Statistics->CachedBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.CachedBytesReserved; + Statistics->SectionBytesAllocated += queryStatistics.QueryResult.ProcessInformation.SystemMemory.SectionBytesAllocated; + Statistics->SectionBytesReserved += queryStatistics.QueryResult.ProcessInformation.SystemMemory.SectionBytesReserved; + } + } +} diff --git a/plugins/ExtendedTools/gpumon.h b/plugins/ExtendedTools/gpumon.h new file mode 100644 index 0000000..0ec6f15 --- /dev/null +++ b/plugins/ExtendedTools/gpumon.h @@ -0,0 +1,43 @@ +#ifndef GPUMON_H +#define GPUMON_H + +// Macros + +#define BYTES_NEEDED_FOR_BITS(Bits) ((((Bits) + sizeof(ULONG) * 8 - 1) / 8) & ~(SIZE_T)(sizeof(ULONG) - 1)) // divide round up + +// Structures + +typedef struct _ETP_GPU_ADAPTER +{ + LUID AdapterLuid; + PPH_STRING Description; + ULONG SegmentCount; + ULONG NodeCount; + ULONG FirstNodeIndex; + + BOOLEAN HasActivity; + + RTL_BITMAP ApertureBitMap; + ULONG ApertureBitMapBuffer[1]; +} ETP_GPU_ADAPTER, *PETP_GPU_ADAPTER; + +// Functions + +BOOLEAN EtpInitializeD3DStatistics( + VOID + ); + +PETP_GPU_ADAPTER EtpAllocateGpuAdapter( + _In_ ULONG NumberOfSegments + ); + +PPH_STRING EtpQueryDeviceDescription( + _In_ PWSTR DeviceInterface + ); + +VOID NTAPI EtGpuProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +#endif diff --git a/plugins/ExtendedTools/gpunodes.c b/plugins/ExtendedTools/gpunodes.c new file mode 100644 index 0000000..26dc8df --- /dev/null +++ b/plugins/ExtendedTools/gpunodes.c @@ -0,0 +1,430 @@ +/* + * Process Hacker Extended Tools - + * GPU nodes window + * + * Copyright (C) 2011-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +#define GRAPH_PADDING 5 +#define CHECKBOX_PADDING 3 + +INT_PTR CALLBACK EtpGpuNodesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static HWND WindowHandle; +static RECT MinimumSize; +static PH_LAYOUT_MANAGER LayoutManager; +static RECT LayoutMargin; +static HWND *GraphHandle; +static HWND *CheckBoxHandle; +static PPH_GRAPH_STATE GraphState; +static PPH_SYSINFO_PARAMETERS SysInfoParameters; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; + +VOID EtShowGpuNodesDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_SYSINFO_PARAMETERS Parameters + ) +{ + SysInfoParameters = Parameters; + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_GPUNODES), + ParentWindowHandle, + EtpGpuNodesDlgProc + ); +} + +static VOID ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PostMessage(WindowHandle, UPDATE_MSG, 0, 0); +} + +VOID EtpLoadNodeBitMap( + VOID + ) +{ + ULONG i; + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + Button_SetCheck( + CheckBoxHandle[i], + RtlCheckBit(&EtGpuNodeBitMap, i) ? BST_CHECKED : BST_UNCHECKED + ); + } +} + +VOID EtpSaveNodeBitMap( + VOID + ) +{ + RTL_BITMAP newBitMap; + ULONG i; + + EtAllocateGpuNodeBitMap(&newBitMap); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + if (Button_GetCheck(CheckBoxHandle[i]) == BST_CHECKED) + RtlSetBits(&newBitMap, i, 1); + } + + if (RtlNumberOfSetBits(&newBitMap) == 0) + RtlSetBits(&newBitMap, 0, 1); + + EtUpdateGpuNodeBitMap(&newBitMap); +} + +INT_PTR CALLBACK EtpGpuNodesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG i; + HFONT font; + PPH_STRING nodeString; + RECT labelRect; + RECT tempRect; + ULONG numberOfRows; + ULONG numberOfColumns; + + WindowHandle = hwndDlg; + + PhInitializeLayoutManager(&LayoutManager, hwndDlg); + PhAddLayoutItem(&LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + LayoutMargin = PhAddLayoutItem(&LayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_ALL)->Margin; + + PhRegisterCallback(&PhProcessesUpdatedEvent, ProcessesUpdatedCallback, NULL, &ProcessesUpdatedCallbackRegistration); + + GraphHandle = PhAllocate(sizeof(HWND) * EtGpuTotalNodeCount); + CheckBoxHandle = PhAllocate(sizeof(HWND) * EtGpuTotalNodeCount); + GraphState = PhAllocate(sizeof(PH_GRAPH_STATE) * EtGpuTotalNodeCount); + + font = (HFONT)SendMessage(hwndDlg, WM_GETFONT, 0, 0); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + nodeString = PhFormatString(L"Node %lu", i); + + GraphHandle[i] = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(GraphHandle[i], TRUE); + CheckBoxHandle[i] = CreateWindow( + WC_BUTTON, + nodeString->Buffer, + WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + SendMessage(CheckBoxHandle[i], WM_SETFONT, (WPARAM)font, FALSE); + PhInitializeGraphState(&GraphState[i]); + + PhDereferenceObject(nodeString); + } + + // Calculate the minimum size. + + numberOfRows = (ULONG)sqrt(EtGpuTotalNodeCount); + numberOfColumns = (EtGpuTotalNodeCount + numberOfRows - 1) / numberOfRows; + + MinimumSize.left = 0; + MinimumSize.top = 0; + MinimumSize.right = 55; + MinimumSize.bottom = 60; + MapDialogRect(hwndDlg, &MinimumSize); + MinimumSize.right += (MinimumSize.right + GRAPH_PADDING) * numberOfColumns; + MinimumSize.bottom += (MinimumSize.bottom + GRAPH_PADDING) * numberOfRows; + + GetWindowRect(GetDlgItem(hwndDlg, IDC_INSTRUCTION), &labelRect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&labelRect, 2); + labelRect.right += GetSystemMetrics(SM_CXFRAME) * 2; + + tempRect.left = 0; + tempRect.top = 0; + tempRect.right = 7; + tempRect.bottom = 0; + MapDialogRect(hwndDlg, &tempRect); + labelRect.right += tempRect.right; + + if (MinimumSize.right < labelRect.right) + MinimumSize.right = labelRect.right; + + SetWindowPos(hwndDlg, NULL, 0, 0, MinimumSize.right, MinimumSize.bottom, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER); + + // Note: This dialog must be centered after all other graphs and controls have been added. + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + EtpLoadNodeBitMap(); + } + break; + case WM_DESTROY: + { + ULONG i; + + EtpSaveNodeBitMap(); + + PhUnregisterCallback(&PhProcessesUpdatedEvent, &ProcessesUpdatedCallbackRegistration); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + PhDeleteGraphState(&GraphState[i]); + } + + PhFree(GraphHandle); + PhFree(CheckBoxHandle); + PhFree(GraphState); + + PhDeleteLayoutManager(&LayoutManager); + } + break; + case WM_SIZE: + { + HDWP deferHandle; + RECT clientRect; + RECT checkBoxRect; + ULONG numberOfRows = (ULONG)sqrt(EtGpuTotalNodeCount); + ULONG numberOfColumns = (EtGpuTotalNodeCount + numberOfRows - 1) / numberOfRows; + ULONG numberOfYPaddings = numberOfRows - 1; + ULONG numberOfXPaddings = numberOfColumns - 1; + ULONG cellHeight; + ULONG y; + ULONG cellWidth; + ULONG x; + ULONG i; + + PhLayoutManagerLayout(&LayoutManager); + + deferHandle = BeginDeferWindowPos(EtGpuTotalNodeCount * 2); + + GetClientRect(hwndDlg, &clientRect); + GetClientRect(GetDlgItem(hwndDlg, IDC_EXAMPLE), &checkBoxRect); + cellHeight = (clientRect.bottom - LayoutMargin.top - LayoutMargin.bottom - GRAPH_PADDING * numberOfYPaddings) / numberOfRows; + y = LayoutMargin.top; + i = 0; + + for (ULONG row = 0; row < numberOfRows; ++row) + { + // Give the last row the remaining space; the height we calculated might be off by a few + // pixels due to integer division. + if (row == numberOfRows - 1) + cellHeight = clientRect.bottom - LayoutMargin.bottom - y; + + cellWidth = (clientRect.right - LayoutMargin.left - LayoutMargin.right - GRAPH_PADDING * numberOfXPaddings) / numberOfColumns; + x = LayoutMargin.left; + + for (ULONG column = 0; column < numberOfColumns; column++) + { + // Give the last cell the remaining space; the width we calculated might be off by a few + // pixels due to integer division. + if (column == numberOfColumns - 1) + cellWidth = clientRect.right - LayoutMargin.right - x; + + if (i < EtGpuTotalNodeCount) + { + deferHandle = DeferWindowPos( + deferHandle, + GraphHandle[i], + NULL, + x, + y, + cellWidth, + cellHeight - checkBoxRect.bottom - CHECKBOX_PADDING, + SWP_NOACTIVATE | SWP_NOZORDER + ); + deferHandle = DeferWindowPos( + deferHandle, + CheckBoxHandle[i], + NULL, + x, + y + cellHeight - checkBoxRect.bottom, + cellWidth, + checkBoxRect.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + i++; + } + x += cellWidth + GRAPH_PADDING; + } + + y += cellHeight + GRAPH_PADDING; + } + + EndDeferWindowPos(deferHandle); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + { + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + ULONG i; + + switch (header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + SysInfoParameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + if (header->hwndFrom == GraphHandle[i]) + { + PhGraphStateGetDrawInfo( + &GraphState[i], + getDrawInfo, + EtGpuNodesHistory[i].Count + ); + + if (!GraphState[i].Valid) + { + PhCopyCircularBuffer_FLOAT(&EtGpuNodesHistory[i], GraphState[i].Data1, drawInfo->LineDataCount); + GraphState[i].Valid = TRUE; + } + + break; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + if (header->hwndFrom == GraphHandle[i]) + { + if (GraphState[i].TooltipIndex != getTooltipText->Index) + { + FLOAT gpu; + ULONG adapterIndex; + PPH_STRING adapterDescription; + + gpu = PhGetItemCircularBuffer_FLOAT(&EtGpuNodesHistory[i], getTooltipText->Index); + adapterIndex = EtGetGpuAdapterIndexFromNodeIndex(i); + + if (adapterIndex != -1) + { + adapterDescription = EtGetGpuAdapterDescription(adapterIndex); + + if (adapterDescription && adapterDescription->Length == 0) + PhClearReference(&adapterDescription); + + if (!adapterDescription) + adapterDescription = PhFormatString(L"Adapter %lu", adapterIndex); + } + else + { + adapterDescription = PhCreateString(L"Unknown Adapter"); + } + + PhMoveReference(&GraphState[i].TooltipText, PhFormatString( + L"Node %lu on %s\n%.2f%%\n%s", + i, + adapterDescription->Buffer, + gpu * 100, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + PhDereferenceObject(adapterDescription); + } + + getTooltipText->Text = GraphState[i].TooltipText->sr; + + break; + } + } + } + } + break; + } + } + break; + case UPDATE_MSG: + { + ULONG i; + + for (i = 0; i < EtGpuTotalNodeCount; i++) + { + GraphState[i].Valid = FALSE; + GraphState[i].TooltipIndex = -1; + Graph_MoveGrid(GraphHandle[i], 1); + Graph_Draw(GraphHandle[i]); + Graph_UpdateTooltip(GraphHandle[i]); + InvalidateRect(GraphHandle[i], NULL, FALSE); + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/gpuprprp.c b/plugins/ExtendedTools/gpuprprp.c new file mode 100644 index 0000000..c75e719 --- /dev/null +++ b/plugins/ExtendedTools/gpuprprp.c @@ -0,0 +1,767 @@ +/* + * Process Hacker Extended Tools - + * GPU process properties page + * + * Copyright (C) 2011 wj32 + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +typedef struct _ET_GPU_CONTEXT +{ + HWND WindowHandle; + HWND PanelHandle; + HWND DetailsHandle; + PET_PROCESS_BLOCK Block; + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + BOOLEAN Enabled; + PH_LAYOUT_MANAGER LayoutManager; + + HWND GpuGroupBox; + HWND MemGroupBox; + HWND SharedGroupBox; + + HWND GpuGraphHandle; + HWND MemGraphHandle; + HWND SharedGraphHandle; + + FLOAT CurrentGpuUsage; + ULONG CurrentMemUsage; + ULONG CurrentMemSharedUsage; + + PH_GRAPH_STATE GpuGraphState; + PH_GRAPH_STATE MemoryGraphState; + PH_GRAPH_STATE MemorySharedGraphState; + + PH_CIRCULAR_BUFFER_FLOAT GpuHistory; + PH_CIRCULAR_BUFFER_ULONG MemoryHistory; + PH_CIRCULAR_BUFFER_ULONG MemorySharedHistory; +} ET_GPU_CONTEXT, *PET_GPU_CONTEXT; + +static RECT NormalGraphTextMargin = { 5, 5, 5, 5 }; +static RECT NormalGraphTextPadding = { 3, 3, 3, 3 }; + +VOID GpuPropUpdatePanel( + _Inout_ PET_GPU_CONTEXT Context + ); + +INT_PTR CALLBACK GpuDetailsDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PET_GPU_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PET_GPU_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PET_GPU_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + context->DetailsHandle = NULL; + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + context->DetailsHandle = hwndDlg; + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + GpuPropUpdatePanel(context); + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lparam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK GpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PET_GPU_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PET_GPU_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PET_GPU_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lparam)) + { + case IDC_GPUDETAILS: + { + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_PROCGPU_DETAILS), + hwndDlg, + GpuDetailsDialogProc, + (LPARAM)context + ); + } + break; + } + } + break; + } + + return FALSE; +} + +VOID GpuPropCreateGraphs( + _In_ PET_GPU_CONTEXT Context + ) +{ + Context->GpuGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + Context->WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(Context->GpuGraphHandle, TRUE); + + Context->MemGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + Context->WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(Context->MemGraphHandle, TRUE); + + Context->SharedGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + Context->WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(Context->SharedGraphHandle, TRUE); +} + +VOID GpuPropCreatePanel( + _In_ PET_GPU_CONTEXT Context + ) +{ + RECT margin; + + Context->PanelHandle = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_PROCGPU_PANEL), + Context->WindowHandle, + GpuPanelDialogProc, + (LPARAM)Context + ); + + SetWindowPos( + Context->PanelHandle, + NULL, + 10, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER + ); + + ShowWindow(Context->PanelHandle, SW_SHOW); + + margin.left = 0; + margin.top = 0; + margin.right = 0; + margin.bottom = 10; + MapDialogRect(Context->WindowHandle, &margin); + + PhAddLayoutItemEx( + &Context->LayoutManager, + Context->PanelHandle, + NULL, + PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT, + margin + ); + + SendMessage(Context->WindowHandle, WM_SIZE, 0, 0); +} + +VOID GpuPropLayoutGraphs( + _In_ PET_GPU_CONTEXT Context + ) +{ + HDWP deferHandle; + RECT clientRect; + RECT panelRect; + RECT margin = { ET_SCALE_DPI(13), ET_SCALE_DPI(13), ET_SCALE_DPI(13), ET_SCALE_DPI(13) }; + RECT innerMargin = { ET_SCALE_DPI(10), ET_SCALE_DPI(20), ET_SCALE_DPI(10), ET_SCALE_DPI(10) }; + LONG between = ET_SCALE_DPI(3); + ULONG graphWidth; + ULONG graphHeight; + + PhLayoutManagerLayout(&Context->LayoutManager); + + Context->GpuGraphState.Valid = FALSE; + Context->MemoryGraphState.Valid = FALSE; + Context->MemorySharedGraphState.Valid = FALSE; + + GetClientRect(Context->WindowHandle, &clientRect); + + // Limit the rectangle bottom to the top of the panel. + GetWindowRect(Context->PanelHandle, &panelRect); + MapWindowPoints(NULL, Context->WindowHandle, (PPOINT)&panelRect, 2); + clientRect.bottom = panelRect.top + 10; // +10 removing extra spacing + + graphWidth = clientRect.right - margin.left - margin.right; + graphHeight = (clientRect.bottom - margin.top - margin.bottom - between * 2) / 3; + + deferHandle = BeginDeferWindowPos(6); + + deferHandle = DeferWindowPos(deferHandle, Context->GpuGroupBox, NULL, margin.left, margin.top, graphWidth, graphHeight, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + Context->GpuGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + innerMargin.top, + graphWidth - innerMargin.left - innerMargin.right, + graphHeight - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos(deferHandle, Context->MemGroupBox, NULL, margin.left, margin.top + graphHeight + between, graphWidth, graphHeight, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + Context->MemGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + graphHeight + between + innerMargin.top, + graphWidth - innerMargin.left - innerMargin.right, + graphHeight - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + deferHandle = DeferWindowPos(deferHandle, Context->SharedGroupBox, NULL, margin.left, margin.top + (graphHeight + between) * 2, graphWidth, graphHeight, SWP_NOACTIVATE | SWP_NOZORDER); + deferHandle = DeferWindowPos( + deferHandle, + Context->SharedGraphHandle, + NULL, + margin.left + innerMargin.left, + margin.top + (graphHeight + between) * 2 + innerMargin.top, + graphWidth - innerMargin.left - innerMargin.right, + graphHeight - innerMargin.top - innerMargin.bottom, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); +} + +VOID GpuPropUpdateGraphs( + _In_ PET_GPU_CONTEXT Context + ) +{ + Context->GpuGraphState.Valid = FALSE; + Context->GpuGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->GpuGraphHandle, 1); + Graph_Draw(Context->GpuGraphHandle); + Graph_UpdateTooltip(Context->GpuGraphHandle); + InvalidateRect(Context->GpuGraphHandle, NULL, FALSE); + + Context->MemoryGraphState.Valid = FALSE; + Context->MemoryGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->MemGraphHandle, 1); + Graph_Draw(Context->MemGraphHandle); + Graph_UpdateTooltip(Context->MemGraphHandle); + InvalidateRect(Context->MemGraphHandle, NULL, FALSE); + + Context->MemorySharedGraphState.Valid = FALSE; + Context->MemorySharedGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->SharedGraphHandle, 1); + Graph_Draw(Context->SharedGraphHandle); + Graph_UpdateTooltip(Context->SharedGraphHandle); + InvalidateRect(Context->SharedGraphHandle, NULL, FALSE); +} + +VOID GpuPropUpdatePanel( + _Inout_ PET_GPU_CONTEXT Context + ) +{ + ET_PROCESS_GPU_STATISTICS statistics; + WCHAR runningTimeString[PH_TIMESPAN_STR_LEN_1] = L"N/A"; + + if (Context->Block->ProcessItem->QueryHandle) + EtQueryProcessGpuStatistics(Context->Block->ProcessItem->QueryHandle, &statistics); + else + memset(&statistics, 0, sizeof(ET_PROCESS_GPU_STATISTICS)); + + PhPrintTimeSpan(runningTimeString, statistics.RunningTime * 10, PH_TIMESPAN_HMSM); + + SetDlgItemText(Context->PanelHandle, IDC_ZRUNNINGTIME_V, runningTimeString); + SetDlgItemText(Context->PanelHandle, IDC_ZCONTEXTSWITCHES_V, PhaFormatUInt64(statistics.ContextSwitches, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZTOTALNODES_V, PhaFormatUInt64(statistics.NodeCount, TRUE)->Buffer); + SetDlgItemText(Context->PanelHandle, IDC_ZTOTALSEGMENTS_V, PhaFormatUInt64(statistics.SegmentCount, TRUE)->Buffer); + + if (Context->DetailsHandle) + { + // Note: no lock is needed because we only ever update the 'details' dialog text on this same thread. + SetDlgItemText(Context->DetailsHandle, IDC_ZDEDICATEDCOMMITTED_V, PhaFormatSize(statistics.DedicatedCommitted, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZSHAREDCOMMITTED_V, PhaFormatSize(statistics.SharedCommitted, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZTOTALALLOCATED_V, PhaFormatSize(statistics.BytesAllocated, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZTOTALRESERVED_V, PhaFormatSize(statistics.BytesReserved, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZWRITECOMBINEDALLOCATED_V, PhaFormatSize(statistics.WriteCombinedBytesAllocated, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZWRITECOMBINEDRESERVED_V, PhaFormatSize(statistics.WriteCombinedBytesReserved, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZCACHEDALLOCATED_V, PhaFormatSize(statistics.CachedBytesAllocated, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZCACHEDRESERVED_V, PhaFormatSize(statistics.CachedBytesReserved, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZSECTIONALLOCATED_V, PhaFormatSize(statistics.SectionBytesAllocated, -1)->Buffer); + SetDlgItemText(Context->DetailsHandle, IDC_ZSECTIONRESERVED_V, PhaFormatSize(statistics.SectionBytesReserved, -1)->Buffer); + } +} + +VOID GpuPropUpdateInfo( + _In_ PET_GPU_CONTEXT Context + ) +{ + PET_PROCESS_BLOCK block = Context->Block; + + Context->CurrentGpuUsage = block->GpuNodeUsage; + Context->CurrentMemUsage = (ULONG)(block->GpuDedicatedUsage / PAGE_SIZE); + Context->CurrentMemSharedUsage = (ULONG)(block->GpuSharedUsage / PAGE_SIZE); + + PhAddItemCircularBuffer_FLOAT(&Context->GpuHistory, Context->CurrentGpuUsage); + PhAddItemCircularBuffer_ULONG(&Context->MemoryHistory, Context->CurrentMemUsage); + PhAddItemCircularBuffer_ULONG(&Context->MemorySharedHistory, Context->CurrentMemSharedUsage); +} + +VOID NTAPI ProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PET_GPU_CONTEXT context = Context; + + if (!context->Enabled) + return; + + if (context->WindowHandle) + { + PostMessage(context->WindowHandle, UPDATE_MSG, 0, 0); + } +} + +INT_PTR CALLBACK EtpGpuPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PET_GPU_CONTEXT context; + + if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) + { + context = propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG sampleCount; + + sampleCount = PhGetIntegerSetting(L"SampleCount"); + + context = PhAllocate(sizeof(ET_GPU_CONTEXT)); + memset(context, 0, sizeof(ET_GPU_CONTEXT)); + + context->WindowHandle = hwndDlg; + context->Block = EtGetProcessBlock(processItem); + context->Enabled = TRUE; + context->GpuGroupBox = GetDlgItem(hwndDlg, IDC_GROUPGPU); + context->MemGroupBox = GetDlgItem(hwndDlg, IDC_GROUPMEM); + context->SharedGroupBox = GetDlgItem(hwndDlg, IDC_GROUPSHARED); + propPageContext->Context = context; + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + + PhInitializeGraphState(&context->GpuGraphState); + PhInitializeGraphState(&context->MemoryGraphState); + PhInitializeGraphState(&context->MemorySharedGraphState); + + PhInitializeCircularBuffer_FLOAT(&context->GpuHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&context->MemoryHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&context->MemorySharedHistory, sampleCount); + + GpuPropCreateGraphs(context); + GpuPropCreatePanel(context); + GpuPropUpdateInfo(context); + GpuPropUpdatePanel(context); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + ProcessesUpdatedHandler, + context, + &context->ProcessesUpdatedRegistration + ); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + + PhDeleteGraphState(&context->GpuGraphState); + PhDeleteGraphState(&context->MemoryGraphState); + PhDeleteGraphState(&context->MemorySharedGraphState); + + PhDeleteCircularBuffer_FLOAT(&context->GpuHistory); + PhDeleteCircularBuffer_ULONG(&context->MemoryHistory); + PhDeleteCircularBuffer_ULONG(&context->MemorySharedHistory); + + if (context->GpuGraphHandle) + DestroyWindow(context->GpuGraphHandle); + if (context->MemGraphHandle) + DestroyWindow(context->MemGraphHandle); + if (context->SharedGraphHandle) + DestroyWindow(context->SharedGraphHandle); + if (context->PanelHandle) + DestroyWindow(context->PanelHandle); + + PhUnregisterCallback(&PhProcessesUpdatedEvent, &context->ProcessesUpdatedRegistration); + PhFree(context); + + PhPropPageDlgProcDestroy(hwndDlg); + } + break; + case WM_SHOWWINDOW: + { + if (PhBeginPropPageLayout(hwndDlg, propPageContext)) + PhEndPropPageLayout(hwndDlg, propPageContext); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_SETACTIVE: + context->Enabled = TRUE; + break; + case PSN_KILLACTIVE: + context->Enabled = FALSE; + break; + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + if (header->hwndFrom == context->GpuGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc; + + PhMoveReference(&context->GpuGraphState.Text, PhFormatString( + L"%.2f%%", + context->CurrentGpuUsage * 100 + )); + + hdc = Graph_GetBufferedContext(context->GpuGraphHandle); + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &context->GpuGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGraphStateGetDrawInfo(&context->GpuGraphState, getDrawInfo, context->GpuHistory.Count); + + if (!context->GpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&context->GpuHistory, context->GpuGraphState.Data1, drawInfo->LineDataCount); + context->GpuGraphState.Valid = TRUE; + } + } + else if (header->hwndFrom == context->MemGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc; + + PhMoveReference(&context->MemoryGraphState.Text, PhFormatString( + L"%s", + PhaFormatSize(UInt32x32To64(context->CurrentMemUsage, PAGE_SIZE), -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(context->MemGraphHandle); + SelectObject(hdc, PhApplicationFont); + PhSetGraphText( + hdc, + drawInfo, + &context->MemoryGraphState.Text->sr, + &NormalGraphTextMargin, + &NormalGraphTextPadding, + PH_ALIGN_TOP | PH_ALIGN_LEFT + ); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0); + PhGraphStateGetDrawInfo( + &context->MemoryGraphState, + getDrawInfo, + context->MemoryHistory.Count + ); + + if (!context->MemoryGraphState.Valid) + { + ULONG i = 0; + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + context->MemoryGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&context->MemoryHistory, i); + } + + if (EtGpuDedicatedLimit != 0) + { + PhDivideSinglesBySingle( + context->MemoryGraphState.Data1, + (FLOAT)EtGpuDedicatedLimit / PAGE_SIZE, + drawInfo->LineDataCount + ); + } + + context->MemoryGraphState.Valid = TRUE; + } + } + else if (header->hwndFrom == context->SharedGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc; + + PhMoveReference(&context->MemorySharedGraphState.Text, PhFormatString( + L"%s", + PhaFormatSize(UInt32x32To64(context->CurrentMemSharedUsage, PAGE_SIZE), -1)->Buffer + )); + + hdc = Graph_GetBufferedContext(context->SharedGraphHandle); + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &context->MemorySharedGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPrivate"), 0); + PhGraphStateGetDrawInfo( + &context->MemorySharedGraphState, + getDrawInfo, + context->MemorySharedHistory.Count + ); + + if (!context->MemorySharedGraphState.Valid) + { + ULONG i = 0; + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + context->MemorySharedGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&context->MemorySharedHistory, i); + } + + if (EtGpuSharedLimit != 0) + { + PhDivideSinglesBySingle( + context->MemorySharedGraphState.Data1, + (FLOAT)EtGpuSharedLimit / PAGE_SIZE, + drawInfo->LineDataCount + ); + } + + context->MemorySharedGraphState.Valid = TRUE; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)lParam; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (header->hwndFrom == context->GpuGraphHandle) + { + if (context->GpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT gpuUsage = PhGetItemCircularBuffer_FLOAT( + &context->GpuHistory, + getTooltipText->Index + ); + + PhMoveReference(&context->GpuGraphState.TooltipText, PhFormatString( + L"%.2f%%", + gpuUsage * 100 + )); + } + + getTooltipText->Text = context->GpuGraphState.TooltipText->sr; + } + else if (header->hwndFrom == context->MemGraphHandle) + { + if (context->MemoryGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG gpuMemory = PhGetItemCircularBuffer_ULONG( + &context->MemoryHistory, + getTooltipText->Index + ); + + PhMoveReference(&context->MemoryGraphState.TooltipText, + PhFormatSize(UInt32x32To64(gpuMemory, PAGE_SIZE), -1) + ); + } + + getTooltipText->Text = context->MemoryGraphState.TooltipText->sr; + } + else if (header->hwndFrom == context->SharedGraphHandle) + { + if (context->MemorySharedGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG gpuSharedMemory = PhGetItemCircularBuffer_ULONG( + &context->MemorySharedHistory, + getTooltipText->Index + ); + + PhMoveReference(&context->MemorySharedGraphState.TooltipText, + PhFormatSize(UInt32x32To64(gpuSharedMemory, PAGE_SIZE), -1) + ); + } + + getTooltipText->Text = context->MemorySharedGraphState.TooltipText->sr; + } + } + } + break; + } + } + break; + case UPDATE_MSG: + { + if (context->Enabled) + { + GpuPropUpdateInfo(context); + GpuPropUpdateGraphs(context); + GpuPropUpdatePanel(context); + } + } + break; + case WM_SIZE: + { + GpuPropLayoutGraphs(context); + } + break; + } + + return FALSE; +} + +VOID EtProcessGpuPropertiesInitializing( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_PROCESS_PROPCONTEXT propContext = Parameter; + + if (EtGpuEnabled) + { + PhAddProcessPropPage( + propContext->PropContext, + PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCGPU), EtpGpuPageDlgProc, NULL) + ); + } +} \ No newline at end of file diff --git a/plugins/ExtendedTools/gpusys.c b/plugins/ExtendedTools/gpusys.c new file mode 100644 index 0000000..57c9dcb --- /dev/null +++ b/plugins/ExtendedTools/gpusys.c @@ -0,0 +1,722 @@ +/* + * Process Hacker Extended Tools - + * GPU system information section + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include "gpusys.h" + +static PPH_SYSINFO_SECTION GpuSection; +static HWND GpuDialog; +static PH_LAYOUT_MANAGER GpuLayoutManager; +static RECT GpuGraphMargin; +static HWND GpuGraphHandle; +static PH_GRAPH_STATE GpuGraphState; +static HWND DedicatedGraphHandle; +static PH_GRAPH_STATE DedicatedGraphState; +static HWND SharedGraphHandle; +static PH_GRAPH_STATE SharedGraphState; +static HWND GpuPanel; + +VOID EtGpuSystemInformationInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ) +{ + PH_SYSINFO_SECTION section; + + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + PhInitializeStringRef(§ion.Name, L"GPU"); + section.Flags = 0; + section.Callback = EtpGpuSysInfoSectionCallback; + + GpuSection = Pointers->CreateSection(§ion); +} + +BOOLEAN EtpGpuSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoDestroy: + { + if (GpuDialog) + { + EtpUninitializeGpuDialog(); + GpuDialog = NULL; + } + } + return TRUE; + case SysInfoTick: + { + if (GpuDialog) + { + EtpTickGpuDialog(); + } + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_SYSINFO_GPU); + createDialog->DialogProc = EtpGpuDialogProc; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, EtGpuNodeHistory.Count); + + if (!Section->GraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&EtGpuNodeHistory, Section->GraphState.Data1, drawInfo->LineDataCount); + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = Parameter1; + FLOAT gpu; + + gpu = PhGetItemCircularBuffer_FLOAT(&EtGpuNodeHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"%.2f%%%s\n%s", + gpu * 100, + PhGetStringOrEmpty(EtpGetMaxNodeString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = Parameter1; + + drawPanel->Title = PhCreateString(L"GPU"); + drawPanel->SubTitle = PhFormatString(L"%.2f%%", EtGpuNodeUsage * 100); + } + return TRUE; + } + + return FALSE; +} + +VOID EtpInitializeGpuDialog( + VOID + ) +{ + PhInitializeGraphState(&GpuGraphState); + PhInitializeGraphState(&DedicatedGraphState); + PhInitializeGraphState(&SharedGraphState); +} + +VOID EtpUninitializeGpuDialog( + VOID + ) +{ + PhDeleteGraphState(&GpuGraphState); + PhDeleteGraphState(&DedicatedGraphState); + PhDeleteGraphState(&SharedGraphState); +} + +VOID EtpTickGpuDialog( + VOID + ) +{ + EtpUpdateGpuGraphs(); + EtpUpdateGpuPanel(); +} + +INT_PTR CALLBACK EtpGpuDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + EtpInitializeGpuDialog(); + + GpuDialog = hwndDlg; + PhInitializeLayoutManager(&GpuLayoutManager, hwndDlg); + PhAddLayoutItem(&GpuLayoutManager, GetDlgItem(hwndDlg, IDC_GPUNAME), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&GpuLayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + GpuGraphMargin = graphItem->Margin; + panelItem = PhAddLayoutItem(&GpuLayoutManager, GetDlgItem(hwndDlg, IDC_PANEL_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)GpuSection->Parameters->LargeFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_GPUNAME), WM_SETFONT, (WPARAM)GpuSection->Parameters->MediumFont, FALSE); + + SetDlgItemText(hwndDlg, IDC_GPUNAME, ((PPH_STRING)PH_AUTO(EtpGetGpuNameString()))->Buffer); + + GpuPanel = CreateDialog(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_SYSINFO_GPUPANEL), hwndDlg, EtpGpuPanelDialogProc); + ShowWindow(GpuPanel, SW_SHOW); + PhAddLayoutItemEx(&GpuLayoutManager, GpuPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + EtpCreateGpuGraphs(); + EtpUpdateGpuGraphs(); + EtpUpdateGpuPanel(); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&GpuLayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&GpuLayoutManager); + EtpLayoutGpuGraphs(); + } + break; + case WM_NOTIFY: + { + NMHDR *header = (NMHDR *)lParam; + + if (header->hwndFrom == GpuGraphHandle) + { + EtpNotifyGpuGraph(header); + } + else if (header->hwndFrom == DedicatedGraphHandle) + { + EtpNotifyDedicatedGraph(header); + } + else if (header->hwndFrom == SharedGraphHandle) + { + EtpNotifySharedGraph(header); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK EtpGpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_NODES: + EtShowGpuNodesDialog(GpuDialog, GpuSection->Parameters); + break; + } + } + break; + } + + return FALSE; +} + +VOID EtpCreateGpuGraphs( + VOID + ) +{ + GpuGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + GpuDialog, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(GpuGraphHandle, TRUE); + + DedicatedGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + GpuDialog, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(DedicatedGraphHandle, TRUE); + + SharedGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + GpuDialog, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(SharedGraphHandle, TRUE); +} + +VOID EtpLayoutGpuGraphs( + VOID + ) +{ + RECT clientRect; + RECT labelRect; + ULONG graphWidth; + ULONG graphHeight; + HDWP deferHandle; + ULONG y; + + GetClientRect(GpuDialog, &clientRect); + GetClientRect(GetDlgItem(GpuDialog, IDC_GPU_L), &labelRect); + graphWidth = clientRect.right - GpuGraphMargin.left - GpuGraphMargin.right; + graphHeight = (clientRect.bottom - GpuGraphMargin.top - GpuGraphMargin.bottom - labelRect.bottom * 3 - ET_GPU_PADDING * 5) / 3; + + deferHandle = BeginDeferWindowPos(6); + y = GpuGraphMargin.top; + + deferHandle = DeferWindowPos( + deferHandle, + GetDlgItem(GpuDialog, IDC_GPU_L), + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + GpuGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + GetDlgItem(GpuDialog, IDC_DEDICATED_L), + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + DedicatedGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + GetDlgItem(GpuDialog, IDC_SHARED_L), + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + SharedGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + clientRect.bottom - GpuGraphMargin.bottom - y, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); +} + +VOID EtpNotifyGpuGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + GpuSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + + PhGraphStateGetDrawInfo( + &GpuGraphState, + getDrawInfo, + EtGpuNodeHistory.Count + ); + + if (!GpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&EtGpuNodeHistory, GpuGraphState.Data1, drawInfo->LineDataCount); + GpuGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (GpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT gpu; + + gpu = PhGetItemCircularBuffer_FLOAT(&EtGpuNodeHistory, getTooltipText->Index); + + PhMoveReference(&GpuGraphState.TooltipText, PhFormatString( + L"%.2f%%%s\n%s", + gpu * 100, + PhGetStringOrEmpty(EtpGetMaxNodeString(getTooltipText->Index)), + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = GpuGraphState.TooltipText->sr; + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record; + + record = NULL; + + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = EtpReferenceMaxNodeRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(GpuDialog, record); + PhDereferenceProcessRecord(record); + } + } + break; + } +} + +VOID EtpNotifyDedicatedGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + GpuSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorPrivate"), 0); + + PhGraphStateGetDrawInfo( + &DedicatedGraphState, + getDrawInfo, + EtGpuDedicatedHistory.Count + ); + + if (!DedicatedGraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + DedicatedGraphState.Data1[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtGpuDedicatedHistory, i); + } + + if (EtGpuDedicatedLimit != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + DedicatedGraphState.Data1, + (FLOAT)EtGpuDedicatedLimit / PAGE_SIZE, + drawInfo->LineDataCount + ); + } + + DedicatedGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (DedicatedGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG usedPages; + + usedPages = PhGetItemCircularBuffer_ULONG(&EtGpuDedicatedHistory, getTooltipText->Index); + + PhMoveReference(&DedicatedGraphState.TooltipText, PhFormatString( + L"Dedicated Memory: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = DedicatedGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID EtpNotifySharedGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + ULONG i; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + GpuSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0); + + PhGraphStateGetDrawInfo( + &SharedGraphState, + getDrawInfo, + EtGpuSharedHistory.Count + ); + + if (!SharedGraphState.Valid) + { + for (i = 0; i < drawInfo->LineDataCount; i++) + { + SharedGraphState.Data1[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG(&EtGpuSharedHistory, i); + } + + if (EtGpuSharedLimit != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + SharedGraphState.Data1, + (FLOAT)EtGpuSharedLimit / PAGE_SIZE, + drawInfo->LineDataCount + ); + } + + SharedGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (SharedGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG usedPages; + + usedPages = PhGetItemCircularBuffer_ULONG(&EtGpuSharedHistory, getTooltipText->Index); + + PhMoveReference(&SharedGraphState.TooltipText, PhFormatString( + L"Shared Memory: %s\n%s", + PhaFormatSize(UInt32x32To64(usedPages, PAGE_SIZE), -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = SharedGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID EtpUpdateGpuGraphs( + VOID + ) +{ + GpuGraphState.Valid = FALSE; + GpuGraphState.TooltipIndex = -1; + Graph_MoveGrid(GpuGraphHandle, 1); + Graph_Draw(GpuGraphHandle); + Graph_UpdateTooltip(GpuGraphHandle); + InvalidateRect(GpuGraphHandle, NULL, FALSE); + + DedicatedGraphState.Valid = FALSE; + DedicatedGraphState.TooltipIndex = -1; + Graph_MoveGrid(DedicatedGraphHandle, 1); + Graph_Draw(DedicatedGraphHandle); + Graph_UpdateTooltip(DedicatedGraphHandle); + InvalidateRect(DedicatedGraphHandle, NULL, FALSE); + + SharedGraphState.Valid = FALSE; + SharedGraphState.TooltipIndex = -1; + Graph_MoveGrid(SharedGraphHandle, 1); + Graph_Draw(SharedGraphHandle); + Graph_UpdateTooltip(SharedGraphHandle); + InvalidateRect(SharedGraphHandle, NULL, FALSE); +} + +VOID EtpUpdateGpuPanel( + VOID + ) +{ + SetDlgItemText(GpuPanel, IDC_ZDEDICATEDCURRENT_V, PhaFormatSize(EtGpuDedicatedUsage, -1)->Buffer); + SetDlgItemText(GpuPanel, IDC_ZDEDICATEDLIMIT_V, PhaFormatSize(EtGpuDedicatedLimit, -1)->Buffer); + + SetDlgItemText(GpuPanel, IDC_ZSHAREDCURRENT_V, PhaFormatSize(EtGpuSharedUsage, -1)->Buffer); + SetDlgItemText(GpuPanel, IDC_ZSHAREDLIMIT_V, PhaFormatSize(EtGpuSharedLimit, -1)->Buffer); +} + +PPH_PROCESS_RECORD EtpReferenceMaxNodeRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + ULONG maxProcessId; + + maxProcessId = PhGetItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, Index); + + if (!maxProcessId) + return NULL; + + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); +} + +PPH_STRING EtpGetMaxNodeString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; + FLOAT maxGpuUsage; + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = EtpReferenceMaxNodeRecord(Index)) + { + maxGpuUsage = PhGetItemCircularBuffer_FLOAT(&EtMaxGpuNodeUsageHistory, Index); + + maxUsageString = PhaFormatString( + L"\n%s (%lu): %.2f%%", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId), + maxGpuUsage * 100 + ); + + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} + +PPH_STRING EtpGetGpuNameString( + VOID + ) +{ + ULONG i; + ULONG count; + PH_STRING_BUILDER sb; + + count = EtGetGpuAdapterCount(); + PhInitializeStringBuilder(&sb, 100); + + for (i = 0; i < count; i++) + { + PPH_STRING description; + + description = EtGetGpuAdapterDescription(i); + + if (!PhIsNullOrEmptyString(description)) + { + // Ignore "Microsoft Basic Render Driver" unless we don't have any other adapters. + // This does not take into account localization. + if (count == 1 || !PhEqualString2(description, L"Microsoft Basic Render Driver", TRUE)) + { + PhAppendStringBuilder(&sb, &description->sr); + PhAppendStringBuilder2(&sb, L", "); + } + } + + if (description) + PhDereferenceObject(description); + } + + if (sb.String->Length != 0) + PhRemoveEndStringBuilder(&sb, 2); + + return PhFinalStringBuilderString(&sb); +} diff --git a/plugins/ExtendedTools/gpusys.h b/plugins/ExtendedTools/gpusys.h new file mode 100644 index 0000000..9cc138a --- /dev/null +++ b/plugins/ExtendedTools/gpusys.h @@ -0,0 +1,79 @@ +#ifndef GPUSYS_H +#define GPUSYS_H + +#define ET_GPU_PADDING 3 + +BOOLEAN EtpGpuSysInfoSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ); + +VOID EtpInitializeGpuDialog( + VOID + ); + +VOID EtpUninitializeGpuDialog( + VOID + ); + +VOID EtpTickGpuDialog( + VOID + ); + +INT_PTR CALLBACK EtpGpuDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EtpGpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtpCreateGpuGraphs( + VOID + ); + +VOID EtpLayoutGpuGraphs( + VOID + ); + +VOID EtpNotifyGpuGraph( + _In_ NMHDR *Header + ); + +VOID EtpNotifyDedicatedGraph( + _In_ NMHDR *Header + ); + +VOID EtpNotifySharedGraph( + _In_ NMHDR *Header + ); + +VOID EtpUpdateGpuGraphs( + VOID + ); + +VOID EtpUpdateGpuPanel( + VOID + ); + +PPH_PROCESS_RECORD EtpReferenceMaxNodeRecord( + _In_ LONG Index + ); + +PPH_STRING EtpGetMaxNodeString( + _In_ LONG Index + ); + +PPH_STRING EtpGetGpuNameString( + VOID + ); + +#endif diff --git a/plugins/ExtendedTools/iconext.c b/plugins/ExtendedTools/iconext.c new file mode 100644 index 0000000..e125bf3 --- /dev/null +++ b/plugins/ExtendedTools/iconext.c @@ -0,0 +1,466 @@ +/* + * Process Hacker Extended Tools - + * notification icon extensions + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +#define GPU_ICON_ID 1 +#define DISK_ICON_ID 2 +#define NETWORK_ICON_ID 3 + +VOID EtpGpuIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ); + +BOOLEAN EtpGpuIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ); + +VOID EtpDiskIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ); + +BOOLEAN EtpDiskIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ); + +VOID EtpNetworkIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ); + +BOOLEAN EtpNetworkIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ); + +VOID EtRegisterNotifyIcons( + VOID + ) +{ + PH_NF_ICON_REGISTRATION_DATA data; + + data.MessageCallback = NULL; + + data.UpdateCallback = EtpGpuIconUpdateCallback; + data.MessageCallback = EtpGpuIconMessageCallback; + PhPluginRegisterIcon( + PluginInstance, + GPU_ICON_ID, + NULL, + L"GPU history", + PH_NF_ICON_SHOW_MINIINFO | (EtGpuEnabled ? 0 : PH_NF_ICON_UNAVAILABLE), + &data + ); + + data.UpdateCallback = EtpDiskIconUpdateCallback; + data.MessageCallback = EtpDiskIconMessageCallback; + PhPluginRegisterIcon( + PluginInstance, + DISK_ICON_ID, + NULL, + L"Disk history", + PH_NF_ICON_SHOW_MINIINFO | (EtEtwEnabled ? 0 : PH_NF_ICON_UNAVAILABLE), + &data + ); + + data.UpdateCallback = EtpNetworkIconUpdateCallback; + data.MessageCallback = EtpNetworkIconMessageCallback; + PhPluginRegisterIcon( + PluginInstance, + NETWORK_ICON_ID, + NULL, + L"Network history", + PH_NF_ICON_SHOW_MINIINFO | (EtEtwEnabled ? 0 : PH_NF_ICON_UNAVAILABLE), + &data + ); +} + +VOID EtpGpuIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + 0, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HANDLE maxGpuProcessId; + PPH_PROCESS_ITEM maxGpuProcessItem; + PH_FORMAT format[8]; + + // Icon + + Icon->Pointers->BeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, EtGpuNodeHistory.Count); + PhCopyCircularBuffer_FLOAT(&EtGpuNodeHistory, lineData1, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineColor1 = PhGetIntegerSetting(L"ColorCpuKernel"); + drawInfo.LineBackColor1 = PhHalveColorBrightness(drawInfo.LineColor1); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + *NewIconOrBitmap = bitmap; + *Flags = PH_NF_UPDATE_IS_BITMAP; + + // Text + + if (EtMaxGpuNodeHistory.Count != 0) + maxGpuProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&EtMaxGpuNodeHistory, 0)); + else + maxGpuProcessId = NULL; + + if (maxGpuProcessId) + maxGpuProcessItem = PhReferenceProcessItem(maxGpuProcessId); + else + maxGpuProcessItem = NULL; + + PhInitFormatS(&format[0], L"GPU Usage: "); + PhInitFormatF(&format[1], EtGpuNodeUsage * 100, 2); + PhInitFormatC(&format[2], '%'); + + if (maxGpuProcessItem) + { + PhInitFormatC(&format[3], '\n'); + PhInitFormatSR(&format[4], maxGpuProcessItem->ProcessName->sr); + PhInitFormatS(&format[5], L": "); + PhInitFormatF(&format[6], EtGetProcessBlock(maxGpuProcessItem)->GpuNodeUsage * 100, 2); + PhInitFormatC(&format[7], '%'); + } + + *NewText = PhFormat(format, maxGpuProcessItem ? 8 : 3, 128); + if (maxGpuProcessItem) PhDereferenceObject(maxGpuProcessItem); +} + +BOOLEAN EtpGpuIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ) +{ + switch (LOWORD(LParam)) + { + case PH_NF_MSG_SHOWMINIINFOSECTION: + { + PPH_NF_MSG_SHOWMINIINFOSECTION_DATA data = (PVOID)WParam; + + data->SectionName = L"GPU"; + } + return TRUE; + } + + return FALSE; +} + +VOID EtpDiskIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + PH_GRAPH_USE_LINE_2, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + PFLOAT lineData2; + FLOAT max; + ULONG i; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HANDLE maxDiskProcessId; + PPH_PROCESS_ITEM maxDiskProcessItem; + PH_FORMAT format[6]; + + // Icon + + Icon->Pointers->BeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + lineData2 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, EtDiskReadHistory.Count); + max = 1024 * 1024; // minimum scaling of 1 MB. + + for (i = 0; i < lineDataCount; i++) + { + lineData1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskReadHistory, i); + lineData2[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&EtDiskWriteHistory, i); + + if (max < lineData1[i] + lineData2[i]) + max = lineData1[i] + lineData2[i]; + } + + PhDivideSinglesBySingle(lineData1, max, lineDataCount); + PhDivideSinglesBySingle(lineData2, max, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineData2 = lineData2; + drawInfo.LineColor1 = PhGetIntegerSetting(L"ColorIoReadOther"); + drawInfo.LineColor2 = PhGetIntegerSetting(L"ColorIoWrite"); + drawInfo.LineBackColor1 = PhHalveColorBrightness(drawInfo.LineColor1); + drawInfo.LineBackColor2 = PhHalveColorBrightness(drawInfo.LineColor2); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + *NewIconOrBitmap = bitmap; + *Flags = PH_NF_UPDATE_IS_BITMAP; + + // Text + + if (EtMaxDiskHistory.Count != 0) + maxDiskProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&EtMaxDiskHistory, 0)); + else + maxDiskProcessId = NULL; + + if (maxDiskProcessId) + maxDiskProcessItem = PhReferenceProcessItem(maxDiskProcessId); + else + maxDiskProcessItem = NULL; + + PhInitFormatS(&format[0], L"Disk\nR: "); + PhInitFormatSize(&format[1], EtDiskReadDelta.Delta); + PhInitFormatS(&format[2], L"\nW: "); + PhInitFormatSize(&format[3], EtDiskWriteDelta.Delta); + + if (maxDiskProcessItem) + { + PhInitFormatC(&format[4], '\n'); + PhInitFormatSR(&format[5], maxDiskProcessItem->ProcessName->sr); + } + + *NewText = PhFormat(format, maxDiskProcessItem ? 6 : 4, 128); + if (maxDiskProcessItem) PhDereferenceObject(maxDiskProcessItem); +} + +BOOLEAN EtpDiskIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ) +{ + switch (LOWORD(LParam)) + { + case PH_NF_MSG_SHOWMINIINFOSECTION: + { + PPH_NF_MSG_SHOWMINIINFOSECTION_DATA data = (PVOID)WParam; + + data->SectionName = L"Disk"; + } + return TRUE; + } + + return FALSE; +} + +VOID EtpNetworkIconUpdateCallback( + _In_ struct _PH_NF_ICON *Icon, + _Out_ PVOID *NewIconOrBitmap, + _Out_ PULONG Flags, + _Out_ PPH_STRING *NewText, + _In_opt_ PVOID Context + ) +{ + static PH_GRAPH_DRAW_INFO drawInfo = + { + 16, + 16, + PH_GRAPH_USE_LINE_2, + 2, + RGB(0x00, 0x00, 0x00), + + 16, + NULL, + NULL, + 0, + 0, + 0, + 0 + }; + ULONG maxDataCount; + ULONG lineDataCount; + PFLOAT lineData1; + PFLOAT lineData2; + FLOAT max; + ULONG i; + HBITMAP bitmap; + PVOID bits; + HDC hdc; + HBITMAP oldBitmap; + HANDLE maxNetworkProcessId; + PPH_PROCESS_ITEM maxNetworkProcessItem; + PH_FORMAT format[6]; + + // Icon + + Icon->Pointers->BeginBitmap(&drawInfo.Width, &drawInfo.Height, &bitmap, &bits, &hdc, &oldBitmap); + maxDataCount = drawInfo.Width / 2 + 1; + lineData1 = _alloca(maxDataCount * sizeof(FLOAT)); + lineData2 = _alloca(maxDataCount * sizeof(FLOAT)); + + lineDataCount = min(maxDataCount, EtNetworkReceiveHistory.Count); + max = 1024 * 1024; // minimum scaling of 1 MB. + + for (i = 0; i < lineDataCount; i++) + { + lineData1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, i); + lineData2[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&EtNetworkSendHistory, i); + + if (max < lineData1[i] + lineData2[i]) + max = lineData1[i] + lineData2[i]; + } + + PhDivideSinglesBySingle(lineData1, max, lineDataCount); + PhDivideSinglesBySingle(lineData2, max, lineDataCount); + + drawInfo.LineDataCount = lineDataCount; + drawInfo.LineData1 = lineData1; + drawInfo.LineData2 = lineData2; + drawInfo.LineColor1 = PhGetIntegerSetting(L"ColorIoReadOther"); + drawInfo.LineColor2 = PhGetIntegerSetting(L"ColorIoWrite"); + drawInfo.LineBackColor1 = PhHalveColorBrightness(drawInfo.LineColor1); + drawInfo.LineBackColor2 = PhHalveColorBrightness(drawInfo.LineColor2); + + if (bits) + PhDrawGraphDirect(hdc, bits, &drawInfo); + + SelectObject(hdc, oldBitmap); + *NewIconOrBitmap = bitmap; + *Flags = PH_NF_UPDATE_IS_BITMAP; + + // Text + + if (EtMaxNetworkHistory.Count != 0) + maxNetworkProcessId = UlongToHandle(PhGetItemCircularBuffer_ULONG(&EtMaxNetworkHistory, 0)); + else + maxNetworkProcessId = NULL; + + if (maxNetworkProcessId) + maxNetworkProcessItem = PhReferenceProcessItem(maxNetworkProcessId); + else + maxNetworkProcessItem = NULL; + + PhInitFormatS(&format[0], L"Network\nR: "); + PhInitFormatSize(&format[1], EtNetworkReceiveDelta.Delta); + PhInitFormatS(&format[2], L"\nS: "); + PhInitFormatSize(&format[3], EtNetworkSendDelta.Delta); + + if (maxNetworkProcessItem) + { + PhInitFormatC(&format[4], '\n'); + PhInitFormatSR(&format[5], maxNetworkProcessItem->ProcessName->sr); + } + + *NewText = PhFormat(format, maxNetworkProcessItem ? 6 : 4, 128); + if (maxNetworkProcessItem) PhDereferenceObject(maxNetworkProcessItem); +} + +BOOLEAN EtpNetworkIconMessageCallback( + _In_ struct _PH_NF_ICON *Icon, + _In_ ULONG_PTR WParam, + _In_ ULONG_PTR LParam, + _In_opt_ PVOID Context + ) +{ + switch (LOWORD(LParam)) + { + case PH_NF_MSG_SHOWMINIINFOSECTION: + { + PPH_NF_MSG_SHOWMINIINFOSECTION_DATA data = (PVOID)WParam; + + data->SectionName = L"Network"; + } + return TRUE; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/main.c b/plugins/ExtendedTools/main.c new file mode 100644 index 0000000..4d7f3d1 --- /dev/null +++ b/plugins/ExtendedTools/main.c @@ -0,0 +1,616 @@ +/* + * Process Hacker Extended Tools - + * main program + * + * Copyright (C) 2010-2015 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +PPH_PLUGIN PluginInstance; +LIST_ENTRY EtProcessBlockListHead; +LIST_ENTRY EtNetworkBlockListHead; +HWND ProcessTreeNewHandle; +HWND NetworkTreeNewHandle; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginUnloadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginTreeNewMessageCallbackRegistration; +PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessPropertiesInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION HandlePropertiesInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ThreadMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ModuleMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessTreeNewInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION NetworkTreeNewInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION SystemInformationInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION MiniInformationInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +PH_CALLBACK_REGISTRATION NetworkItemsUpdatedCallbackRegistration; + +static HANDLE ModuleProcessId; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtEtwStatisticsInitialization(); + EtGpuMonitorInitialization(); + + EtRegisterNotifyIcons(); +} + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtSaveSettingsDiskTreeList(); + EtEtwStatisticsUninitialization(); +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtShowOptionsDialog((HWND)Parameter); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + case ID_PROCESS_UNLOADEDMODULES: + { + EtShowUnloadedDllsDialog(PhMainWndHandle, menuItem->Context); + } + break; + case ID_PROCESS_WSWATCH: + { + EtShowWsWatchDialog(PhMainWndHandle, menuItem->Context); + } + break; + case ID_THREAD_CANCELIO: + { + EtUiCancelIoThread(menuItem->OwnerWindow, menuItem->Context); + } + break; + case ID_MODULE_SERVICES: + { + EtShowModuleServicesDialog( + menuItem->OwnerWindow, + ModuleProcessId, + ((PPH_MODULE_ITEM)menuItem->Context)->Name->Buffer + ); + } + break; + } +} + +VOID NTAPI TreeNewMessageCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + + if (message->TreeNewHandle == ProcessTreeNewHandle) + EtProcessTreeNewMessage(Parameter); + else if (message->TreeNewHandle == NetworkTreeNewHandle) + EtNetworkTreeNewMessage(Parameter); +} + +VOID NTAPI MainWindowShowingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtInitializeDiskTab(); +} + +VOID NTAPI ProcessPropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtProcessGpuPropertiesInitializing(Parameter); + EtProcessEtwPropertiesInitializing(Parameter); +} + +VOID NTAPI HandlePropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + EtHandlePropertiesInitializing(Parameter); +} + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem; + ULONG flags; + PPH_EMENU_ITEM miscMenu; + + if (menuInfo->u.Process.NumberOfProcesses == 1) + processItem = menuInfo->u.Process.Processes[0]; + else + processItem = NULL; + + flags = 0; + + if (!processItem) + flags = PH_EMENU_DISABLED; + + miscMenu = PhFindEMenuItem(menuInfo->Menu, 0, L"Miscellaneous", 0); + + if (miscMenu) + { + PhInsertEMenuItem(miscMenu, PhPluginCreateEMenuItem(PluginInstance, flags, ID_PROCESS_UNLOADEDMODULES, L"Unloaded modules", processItem), -1); + PhInsertEMenuItem(miscMenu, PhPluginCreateEMenuItem(PluginInstance, flags, ID_PROCESS_WSWATCH, L"WS watch", processItem), -1); + } +} + +VOID NTAPI ThreadMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_THREAD_ITEM threadItem; + ULONG insertIndex; + PPH_EMENU_ITEM menuItem; + + if (menuInfo->u.Thread.NumberOfThreads == 1) + threadItem = menuInfo->u.Thread.Threads[0]; + else + threadItem = NULL; + + if (menuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Resume", 0)) + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, menuItem) + 1; + else + insertIndex = 0; + + PhInsertEMenuItem(menuInfo->Menu, menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_THREAD_CANCELIO, + L"Cancel I/O", threadItem), insertIndex); + + if (!threadItem) menuItem->Flags |= PH_EMENU_DISABLED; +} + +VOID NTAPI ModuleMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem; + BOOLEAN addMenuItem; + PPH_MODULE_ITEM moduleItem; + ULONG insertIndex; + PPH_EMENU_ITEM menuItem; + + addMenuItem = FALSE; + + if (processItem = PhReferenceProcessItem(menuInfo->u.Module.ProcessId)) + { + if (processItem->ServiceList && processItem->ServiceList->Count != 0) + addMenuItem = TRUE; + + PhDereferenceObject(processItem); + } + + if (!addMenuItem) + return; + + if (menuInfo->u.Module.NumberOfModules == 1) + moduleItem = menuInfo->u.Module.Modules[0]; + else + moduleItem = NULL; + + if (menuItem = PhFindEMenuItem(menuInfo->Menu, PH_EMENU_FIND_STARTSWITH, L"Inspect", 0)) + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, menuItem) + 1; + else + insertIndex = 0; + + ModuleProcessId = menuInfo->u.Module.ProcessId; + + PhInsertEMenuItem(menuInfo->Menu, menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_MODULE_SERVICES, + L"Services", moduleItem), insertIndex); + + if (!moduleItem) menuItem->Flags |= PH_EMENU_DISABLED; +} + +VOID NTAPI ProcessTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_INFORMATION treeNewInfo = Parameter; + + ProcessTreeNewHandle = treeNewInfo->TreeNewHandle; + EtProcessTreeNewInitializing(Parameter); +} + +VOID NTAPI NetworkTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_INFORMATION treeNewInfo = Parameter; + + NetworkTreeNewHandle = treeNewInfo->TreeNewHandle; + EtNetworkTreeNewInitializing(Parameter); +} + +VOID NTAPI SystemInformationInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (EtGpuEnabled) + EtGpuSystemInformationInitializing(Parameter); + if (EtEtwEnabled) + EtEtwSystemInformationInitializing(Parameter); +} + +VOID NTAPI MiniInformationInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (EtGpuEnabled) + EtGpuMiniInformationInitializing(Parameter); + if (EtEtwEnabled) + EtEtwMiniInformationInitializing(Parameter); +} + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PLIST_ENTRY listEntry; + + // Note: no lock is needed because we only ever modify the list on this same thread. + + listEntry = EtProcessBlockListHead.Flink; + + while (listEntry != &EtProcessBlockListHead) + { + PET_PROCESS_BLOCK block; + + block = CONTAINING_RECORD(listEntry, ET_PROCESS_BLOCK, ListEntry); + + PhUpdateDelta(&block->HardFaultsDelta, block->ProcessItem->HardFaultCount); + + // Invalidate all text. + + PhAcquireQueuedLockExclusive(&block->TextCacheLock); + memset(block->TextCacheValid, 0, sizeof(block->TextCacheValid)); + PhReleaseQueuedLockExclusive(&block->TextCacheLock); + + listEntry = listEntry->Flink; + } +} + +VOID NTAPI NetworkItemsUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PLIST_ENTRY listEntry; + + // Note: no lock is needed because we only ever modify the list on this same thread. + + listEntry = EtNetworkBlockListHead.Flink; + + while (listEntry != &EtNetworkBlockListHead) + { + PET_NETWORK_BLOCK block; + + block = CONTAINING_RECORD(listEntry, ET_NETWORK_BLOCK, ListEntry); + + // Invalidate all text. + + PhAcquireQueuedLockExclusive(&block->TextCacheLock); + memset(block->TextCacheValid, 0, sizeof(block->TextCacheValid)); + PhReleaseQueuedLockExclusive(&block->TextCacheLock); + + listEntry = listEntry->Flink; + } +} + +PET_PROCESS_BLOCK EtGetProcessBlock( + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + return PhPluginGetObjectExtension(PluginInstance, ProcessItem, EmProcessItemType); +} + +PET_NETWORK_BLOCK EtGetNetworkBlock( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + return PhPluginGetObjectExtension(PluginInstance, NetworkItem, EmNetworkItemType); +} + +VOID EtInitializeProcessBlock( + _Out_ PET_PROCESS_BLOCK Block, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + memset(Block, 0, sizeof(ET_PROCESS_BLOCK)); + Block->ProcessItem = ProcessItem; + PhInitializeQueuedLock(&Block->TextCacheLock); + InsertTailList(&EtProcessBlockListHead, &Block->ListEntry); +} + +VOID EtDeleteProcessBlock( + _In_ PET_PROCESS_BLOCK Block + ) +{ + ULONG i; + + EtProcIconNotifyProcessDelete(Block); + + for (i = 1; i <= ETPRTNC_MAXIMUM; i++) + { + PhClearReference(&Block->TextCache[i]); + } + + RemoveEntryList(&Block->ListEntry); +} + +VOID EtInitializeNetworkBlock( + _Out_ PET_NETWORK_BLOCK Block, + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + memset(Block, 0, sizeof(ET_NETWORK_BLOCK)); + Block->NetworkItem = NetworkItem; + PhInitializeQueuedLock(&Block->TextCacheLock); + InsertTailList(&EtNetworkBlockListHead, &Block->ListEntry); +} + +VOID EtDeleteNetworkBlock( + _In_ PET_NETWORK_BLOCK Block + ) +{ + ULONG i; + + for (i = 1; i <= ETNETNC_MAXIMUM; i++) + { + PhClearReference(&Block->TextCache[i]); + } + + RemoveEntryList(&Block->ListEntry); +} + +VOID NTAPI ProcessItemCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + EtInitializeProcessBlock(Extension, Object); +} + +VOID NTAPI ProcessItemDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + EtDeleteProcessBlock(Extension); +} + +VOID NTAPI NetworkItemCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + EtInitializeNetworkBlock(Extension, Object); +} + +VOID NTAPI NetworkItemDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + EtDeleteNetworkBlock(Extension); +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Extended Tools"; + info->Author = L"wj32"; + info->Description = L"Extended functionality for Windows Vista and above, including ETW monitoring, GPU monitoring and a Disk tab."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1114"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackUnload), + UnloadCallback, + NULL, + &PluginUnloadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackTreeNewMessage), + TreeNewMessageCallback, + NULL, + &PluginTreeNewMessageCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessPropertiesInitializing), + ProcessPropertiesInitializingCallback, + NULL, + &ProcessPropertiesInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackHandlePropertiesInitializing), + HandlePropertiesInitializingCallback, + NULL, + &HandlePropertiesInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + ProcessMenuInitializingCallback, + NULL, + &ProcessMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackThreadMenuInitializing), + ThreadMenuInitializingCallback, + NULL, + &ThreadMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackModuleMenuInitializing), + ModuleMenuInitializingCallback, + NULL, + &ModuleMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), + ProcessTreeNewInitializingCallback, + NULL, + &ProcessTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackNetworkTreeNewInitializing), + NetworkTreeNewInitializingCallback, + NULL, + &NetworkTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackSystemInformationInitializing), + SystemInformationInitializingCallback, + NULL, + &SystemInformationInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMiniInformationInitializing), + MiniInformationInitializingCallback, + NULL, + &MiniInformationInitializingCallbackRegistration + ); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + ProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + PhRegisterCallback( + &PhNetworkItemsUpdatedEvent, + NetworkItemsUpdatedCallback, + NULL, + &NetworkItemsUpdatedCallbackRegistration + ); + + InitializeListHead(&EtProcessBlockListHead); + InitializeListHead(&EtNetworkBlockListHead); + + PhPluginSetObjectExtension( + PluginInstance, + EmProcessItemType, + sizeof(ET_PROCESS_BLOCK), + ProcessItemCreateCallback, + ProcessItemDeleteCallback + ); + PhPluginSetObjectExtension( + PluginInstance, + EmNetworkItemType, + sizeof(ET_NETWORK_BLOCK), + NetworkItemCreateCallback, + NetworkItemDeleteCallback + ); + + { + static PH_SETTING_CREATE settings[] = + { + { StringSettingType, SETTING_NAME_DISK_TREE_LIST_COLUMNS, L"" }, + { IntegerPairSettingType, SETTING_NAME_DISK_TREE_LIST_SORT, L"4,2" }, // 4, DescendingSortOrder + { IntegerSettingType, SETTING_NAME_ENABLE_ETW_MONITOR, L"1" }, + { IntegerSettingType, SETTING_NAME_ENABLE_GPU_MONITOR, L"1" }, + { StringSettingType, SETTING_NAME_GPU_NODE_BITMAP, L"01000000" }, + { IntegerSettingType, SETTING_NAME_GPU_LAST_NODE_COUNT, L"0" } + }; + + PhAddSettings(settings, sizeof(settings) / sizeof(PH_SETTING_CREATE)); + } + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/ExtendedTools/modsrv.c b/plugins/ExtendedTools/modsrv.c new file mode 100644 index 0000000..16b110a --- /dev/null +++ b/plugins/ExtendedTools/modsrv.c @@ -0,0 +1,173 @@ +/* + * Process Hacker Extended Tools - + * services referencing module + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include + +typedef struct _MODULE_SERVICES_CONTEXT +{ + HANDLE ProcessId; + PWSTR ModuleName; +} MODULE_SERVICES_CONTEXT, *PMODULE_SERVICES_CONTEXT; + +INT_PTR CALLBACK EtpModuleServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtShowModuleServicesDialog( + _In_ HWND ParentWindowHandle, + _In_ HANDLE ProcessId, + _In_ PWSTR ModuleName + ) +{ + MODULE_SERVICES_CONTEXT context; + + context.ProcessId = ProcessId; + context.ModuleName = ModuleName; + + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_MODSERVICES), + ParentWindowHandle, + EtpModuleServicesDlgProc, + (LPARAM)&context + ); +} + +INT_PTR CALLBACK EtpModuleServicesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PMODULE_SERVICES_CONTEXT context = (PMODULE_SERVICES_CONTEXT)lParam; + ULONG win32Result; + PQUERY_TAG_INFORMATION I_QueryTagInformation; + TAG_INFO_NAMES_REFERENCING_MODULE namesReferencingModule; + PPH_LIST serviceList; + PPH_SERVICE_ITEM *serviceItems; + HWND serviceListHandle; + RECT rect; + PPH_PROCESS_ITEM processItem; + PPH_STRING message; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + I_QueryTagInformation = PhGetModuleProcAddress(L"advapi32.dll", "I_QueryTagInformation"); + + if (!I_QueryTagInformation) + { + PhShowError(hwndDlg, L"Unable to query services because the feature is not supported by the operating system."); + EndDialog(hwndDlg, IDCANCEL); + return FALSE; + } + + memset(&namesReferencingModule, 0, sizeof(TAG_INFO_NAMES_REFERENCING_MODULE)); + namesReferencingModule.InParams.dwPid = HandleToUlong(context->ProcessId); + namesReferencingModule.InParams.pszModule = context->ModuleName; + + win32Result = I_QueryTagInformation(NULL, eTagInfoLevelNamesReferencingModule, &namesReferencingModule); + + if (win32Result == ERROR_NO_MORE_ITEMS) + win32Result = 0; + + if (win32Result != 0) + { + PhShowStatus(hwndDlg, L"Unable to query services", 0, win32Result); + EndDialog(hwndDlg, IDCANCEL); + return FALSE; + } + + serviceList = PhCreateList(16); + + if (namesReferencingModule.OutParams.pmszNames) + { + PPH_SERVICE_ITEM serviceItem; + PWSTR serviceName; + ULONG nameLength; + + serviceName = namesReferencingModule.OutParams.pmszNames; + + while (TRUE) + { + nameLength = (ULONG)PhCountStringZ(serviceName); + + if (nameLength == 0) + break; + + if (serviceItem = PhReferenceServiceItem(serviceName)) + PhAddItemList(serviceList, serviceItem); + + serviceName += nameLength + 1; + } + + LocalFree(namesReferencingModule.OutParams.pmszNames); + } + + serviceItems = PhAllocateCopy(serviceList->Items, serviceList->Count * sizeof(PPH_SERVICE_ITEM)); + PhDereferenceObject(serviceList); + serviceListHandle = PhCreateServiceListControl(hwndDlg, serviceItems, serviceList->Count); + + // Position the control. + GetWindowRect(GetDlgItem(hwndDlg, IDC_SERVICES_LAYOUT), &rect); + MapWindowPoints(NULL, hwndDlg, (POINT *)&rect, 2); + MoveWindow(serviceListHandle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, FALSE); + + ShowWindow(serviceListHandle, SW_SHOW); + + if (processItem = PhReferenceProcessItem(context->ProcessId)) + { + message = PhFormatString(L"Services referencing %s in %s:", context->ModuleName, processItem->ProcessName->Buffer); + PhDereferenceObject(processItem); + } + else + { + message = PhFormatString(L"Services referencing %s:", context->ModuleName); + } + + SetDlgItemText(hwndDlg, IDC_MESSAGE, message->Buffer); + PhDereferenceObject(message); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/objprp.c b/plugins/ExtendedTools/objprp.c new file mode 100644 index 0000000..6716448 --- /dev/null +++ b/plugins/ExtendedTools/objprp.c @@ -0,0 +1,312 @@ +/* + * Process Hacker Extended Tools - + * handle properties extensions + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include + +typedef struct _COMMON_PAGE_CONTEXT +{ + PPH_HANDLE_ITEM HandleItem; + HANDLE ProcessId; +} COMMON_PAGE_CONTEXT, *PCOMMON_PAGE_CONTEXT; + +HPROPSHEETPAGE EtpCommonCreatePage( + _In_ PPH_PLUGIN_HANDLE_PROPERTIES_CONTEXT Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ); + +INT CALLBACK EtpCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +INT_PTR CALLBACK EtpAlpcPortPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK EtpTpWorkerFactoryPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtHandlePropertiesInitializing( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_OBJECT_PROPERTIES objectProperties = Parameter; + PPH_PLUGIN_HANDLE_PROPERTIES_CONTEXT context = objectProperties->Parameter; + + if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + HPROPSHEETPAGE page = NULL; + + if (PhEqualString2(context->HandleItem->TypeName, L"ALPC Port", TRUE)) + { + page = EtpCommonCreatePage( + context, + MAKEINTRESOURCE(IDD_OBJALPCPORT), + EtpAlpcPortPageDlgProc + ); + } + else if (PhEqualString2(context->HandleItem->TypeName, L"TpWorkerFactory", TRUE)) + { + page = EtpCommonCreatePage( + context, + MAKEINTRESOURCE(IDD_OBJTPWORKERFACTORY), + EtpTpWorkerFactoryPageDlgProc + ); + } + + // Insert our page into the second slot. + + if (page) + { + if (objectProperties->NumberOfPages > 1) + { + memmove(&objectProperties->Pages[2], &objectProperties->Pages[1], + (objectProperties->NumberOfPages - 1) * sizeof(HPROPSHEETPAGE)); + } + + objectProperties->Pages[1] = page; + objectProperties->NumberOfPages++; + } + } +} + +static HPROPSHEETPAGE EtpCommonCreatePage( + _In_ PPH_PLUGIN_HANDLE_PROPERTIES_CONTEXT Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + PROPSHEETPAGE propSheetPage; + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhCreateAlloc(sizeof(COMMON_PAGE_CONTEXT)); + memset(pageContext, 0, sizeof(COMMON_PAGE_CONTEXT)); + pageContext->HandleItem = Context->HandleItem; + pageContext->ProcessId = Context->ProcessId; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USECALLBACK; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = Template; + propSheetPage.pfnDlgProc = DlgProc; + propSheetPage.lParam = (LPARAM)pageContext; + propSheetPage.pfnCallback = EtpCommonPropPageProc; + + propSheetPageHandle = CreatePropertySheetPage(&propSheetPage); + PhDereferenceObject(pageContext); // already got a ref from above call + + return propSheetPageHandle; +} + +INT CALLBACK EtpCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = (PCOMMON_PAGE_CONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + PhReferenceObject(pageContext); + else if (uMsg == PSPCB_RELEASE) + PhDereferenceObject(pageContext); + + return 1; +} + +static NTSTATUS EtpDuplicateHandleFromProcess( + _Out_ PHANDLE Handle, + _In_ ACCESS_MASK DesiredAccess, + _In_ PCOMMON_PAGE_CONTEXT Context + ) +{ + NTSTATUS status; + HANDLE processHandle; + + if (!NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_DUP_HANDLE, + Context->ProcessId + ))) + return status; + + status = NtDuplicateObject( + processHandle, + Context->HandleItem->Handle, + NtCurrentProcess(), + Handle, + DesiredAccess, + 0, + 0 + ); + NtClose(processHandle); + + return status; +} + +INT_PTR CALLBACK EtpAlpcPortPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PCOMMON_PAGE_CONTEXT context = (PCOMMON_PAGE_CONTEXT)propSheetPage->lParam; + HANDLE portHandle; + + if (NT_SUCCESS(EtpDuplicateHandleFromProcess(&portHandle, READ_CONTROL, context))) + { + ALPC_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(NtAlpcQueryInformation( + portHandle, + AlpcBasicInformation, + &basicInfo, + sizeof(ALPC_BASIC_INFORMATION), + NULL + ))) + { + PH_FORMAT format[2]; + PPH_STRING string; + + PhInitFormatS(&format[0], L"Sequence Number: "); + PhInitFormatD(&format[1], basicInfo.SequenceNo); + format[1].Type |= FormatGroupDigits; + + string = PhFormat(format, 2, 128); + SetDlgItemText(hwndDlg, IDC_SEQUENCENUMBER, string->Buffer); + PhDereferenceObject(string); + + SetDlgItemText(hwndDlg, IDC_PORTCONTEXT, + PhaFormatString(L"Port Context: 0x%Ix", basicInfo.PortContext)->Buffer); + } + + NtClose(portHandle); + } + } + break; + } + + return FALSE; +} + +static BOOLEAN NTAPI EnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + if (Module->Type == PH_MODULE_TYPE_MODULE || Module->Type == PH_MODULE_TYPE_WOW64_MODULE) + { + PhLoadModuleSymbolProvider(Context, Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, Module->Size); + } + + return TRUE; +} + +INT_PTR CALLBACK EtpTpWorkerFactoryPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PCOMMON_PAGE_CONTEXT context = (PCOMMON_PAGE_CONTEXT)propSheetPage->lParam; + HANDLE workerFactoryHandle; + + if (NT_SUCCESS(EtpDuplicateHandleFromProcess(&workerFactoryHandle, WORKER_FACTORY_QUERY_INFORMATION, context))) + { + WORKER_FACTORY_BASIC_INFORMATION basicInfo; + + if (NT_SUCCESS(NtQueryInformationWorkerFactory( + workerFactoryHandle, + WorkerFactoryBasicInformation, + &basicInfo, + sizeof(WORKER_FACTORY_BASIC_INFORMATION), + NULL + ))) + { + PPH_SYMBOL_PROVIDER symbolProvider; + PPH_STRING symbol = NULL; + + symbolProvider = PhCreateSymbolProvider(basicInfo.ProcessId); + PhLoadSymbolProviderOptions(symbolProvider); + + if (symbolProvider->IsRealHandle) + { + PhEnumGenericModules(basicInfo.ProcessId, symbolProvider->ProcessHandle, + 0, EnumGenericModulesCallback, symbolProvider); + + symbol = PhGetSymbolFromAddress(symbolProvider, (ULONG64)basicInfo.StartRoutine, + NULL, NULL, NULL, NULL); + } + + PhDereferenceObject(symbolProvider); + + if (symbol) + { + SetDlgItemText(hwndDlg, IDC_WORKERTHREADSTART, + PhaFormatString(L"Worker Thread Start: %s", symbol->Buffer)->Buffer); + PhDereferenceObject(symbol); + } + else + { + SetDlgItemText(hwndDlg, IDC_WORKERTHREADSTART, + PhaFormatString(L"Worker Thread Start: 0x%Ix", basicInfo.StartRoutine)->Buffer); + } + + SetDlgItemText(hwndDlg, IDC_WORKERTHREADCONTEXT, + PhaFormatString(L"Worker Thread Context: 0x%Ix", basicInfo.StartParameter)->Buffer); + } + + NtClose(workerFactoryHandle); + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/options.c b/plugins/ExtendedTools/options.c new file mode 100644 index 0000000..d3371fd --- /dev/null +++ b/plugins/ExtendedTools/options.c @@ -0,0 +1,85 @@ +/* + * Process Hacker Extended Tools - + * options dialog + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtShowOptionsDialog( + _In_ HWND ParentWindowHandle + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + ParentWindowHandle, + OptionsDlgProc + ); +} + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEETWMONITOR), PhGetIntegerSetting(SETTING_NAME_ENABLE_ETW_MONITOR) ? BST_CHECKED : BST_UNCHECKED); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLEGPUMONITOR), PhGetIntegerSetting(SETTING_NAME_ENABLE_GPU_MONITOR) ? BST_CHECKED : BST_UNCHECKED); + + if (WindowsVersion < WINDOWS_7) + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLEGPUMONITOR), FALSE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PhSetIntegerSetting(SETTING_NAME_ENABLE_ETW_MONITOR, + Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEETWMONITOR)) == BST_CHECKED); + PhSetIntegerSetting(SETTING_NAME_ENABLE_GPU_MONITOR, + Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLEGPUMONITOR)) == BST_CHECKED); + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/procicon.c b/plugins/ExtendedTools/procicon.c new file mode 100644 index 0000000..bfb202f --- /dev/null +++ b/plugins/ExtendedTools/procicon.c @@ -0,0 +1,95 @@ +/* + * Process Hacker Extended Tools - + * process icon duplication + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +PET_PROCESS_ICON EtProcIconCreateProcessIcon( + _In_ HICON Icon + ) +{ + PET_PROCESS_ICON processIcon; + + processIcon = PhAllocate(sizeof(ET_PROCESS_ICON)); + processIcon->RefCount = 1; + processIcon->Icon = CopyIcon(Icon); + + return processIcon; +} + +VOID EtProcIconReferenceProcessIcon( + _Inout_ PET_PROCESS_ICON ProcessIcon + ) +{ + _InterlockedIncrement(&ProcessIcon->RefCount); +} + +VOID EtProcIconDereferenceProcessIcon( + _Inout_ PET_PROCESS_ICON ProcessIcon + ) +{ + if (_InterlockedDecrement(&ProcessIcon->RefCount) == 0) + { + DestroyIcon(ProcessIcon->Icon); + PhFree(ProcessIcon); + } +} + +PET_PROCESS_ICON EtProcIconReferenceSmallProcessIcon( + _Inout_ PET_PROCESS_BLOCK Block + ) +{ + PET_PROCESS_ICON smallProcessIcon; + + smallProcessIcon = Block->SmallProcessIcon; + + if (!smallProcessIcon && PhTestEvent(&Block->ProcessItem->Stage1Event)) + { + smallProcessIcon = EtProcIconCreateProcessIcon(Block->ProcessItem->SmallIcon); + + if (_InterlockedCompareExchangePointer( + &Block->SmallProcessIcon, + smallProcessIcon, + NULL + ) != NULL) + { + EtProcIconDereferenceProcessIcon(smallProcessIcon); + smallProcessIcon = Block->SmallProcessIcon; + } + } + + if (smallProcessIcon) + { + EtProcIconReferenceProcessIcon(smallProcessIcon); + } + + return smallProcessIcon; +} + +VOID EtProcIconNotifyProcessDelete( + _Inout_ PET_PROCESS_BLOCK Block + ) +{ + if (Block->SmallProcessIcon) + { + EtProcIconDereferenceProcessIcon(Block->SmallProcessIcon); + } +} diff --git a/plugins/ExtendedTools/resource.h b/plugins/ExtendedTools/resource.h new file mode 100644 index 0000000..a6d9609 --- /dev/null +++ b/plugins/ExtendedTools/resource.h @@ -0,0 +1,113 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ExtendedTools.rc +// +#define ID_PROCESS_UNLOADEDMODULES 101 +#define IDD_UNLOADEDDLLS 102 +#define IDD_OBJALPCPORT 103 +#define ID_THREAD_CANCELIO 104 +#define IDD_OBJTPWORKERFACTORY 105 +#define ID_MODULE_SERVICES 106 +#define IDD_MODSERVICES 107 +#define ID_VIEW_MEMORYLISTS 108 +#define IDD_PROCDISKNET 110 +#define ID_VIEW_DISKANDNETWORK 111 +#define ID_PROCESS_WSWATCH 112 +#define IDD_SYSINFO_DISKPANEL 113 +#define IDD_OPTIONS 114 +#define IDD_WSWATCH 115 +#define IDD_SYSINFO_GPU 117 +#define IDR_DISK 118 +#define ID_VIEW_GPUINFORMATION 119 +#define IDD_SYSINFO_GPUPANEL 120 +#define IDD_PROCGPU 121 +#define IDD_SYSINFO_DISK 122 +#define IDD_GPUNODES 123 +#define IDD_SYSINFO_NETPANEL 124 +#define IDD_SYSINFO_NET 125 +#define IDD_PROCGPU_PANEL 126 +#define IDD_DISKTABRESTART 127 +#define IDD_DISKTABERROR 128 +#define IDD_PROCDISKNET_PANEL 129 +#define IDD_PROCGPU_DETAILS 131 +#define IDC_LIST 1001 +#define IDC_REFRESH 1002 +#define IDC_SEQUENCENUMBER 1003 +#define IDC_PORTCONTEXT 1004 +#define IDC_WORKERTHREADSTART 1005 +#define IDC_WORKERTHREADCONTEXT 1006 +#define IDC_SERVICES_LAYOUT 1007 +#define IDC_MESSAGE 1008 +#define IDC_ZREADS_V 1023 +#define IDC_ZREADBYTES_V 1024 +#define IDC_ZREADBYTESDELTA_V 1025 +#define IDC_ZWRITES_V 1026 +#define IDC_ZWRITEBYTES_V 1027 +#define IDC_ZWRITEBYTESDELTA_V 1028 +#define IDC_ZRECEIVES_V 1029 +#define IDC_ZRECEIVEBYTES_V 1030 +#define IDC_ZRECEIVEBYTESDELTA_V 1031 +#define IDC_ZSENDS_V 1032 +#define IDC_ZSENDBYTES_V 1033 +#define IDC_ZSENDBYTESDELTA_V 1034 +#define IDC_ENABLEETWMONITOR 1035 +#define IDC_ENABLE 1036 +#define IDC_WSWATCHENABLED 1037 +#define IDC_NODES 1048 +#define IDC_ENABLEGPUMONITOR 1049 +#define IDC_EXAMPLE 1050 +#define IDC_GROUPGPU 1051 +#define IDC_GROUPMEM 1052 +#define IDC_GROUPSHARED 1053 +#define IDC_GROUPDEDICATED 1054 +#define IDC_ZDEDICATEDCURRENT_V 1055 +#define IDC_ZDEDICATEDLIMIT_V 1056 +#define IDC_ZSHAREDCURRENT_V 1057 +#define IDC_ZSHAREDLIMIT_V 1058 +#define IDC_ZDEDICATEDCOMMITTED_V 1059 +#define IDC_ZRUNNINGTIME_V 1060 +#define IDC_ZCONTEXTSWITCHES_V 1061 +#define IDC_ZTOTALNODES_V 1062 +#define IDC_ZCACHEDALLOCATED_V 1063 +#define IDC_ZCACHEDRESERVED_V 1064 +#define IDC_ZSHAREDCOMMITTED_V 1065 +#define IDC_ZTOTALALLOCATED_V 1066 +#define IDC_ZTOTALRESERVED_V 1067 +#define IDC_ZWRITECOMBINEDALLOCATED_V 1068 +#define IDC_ZWRITECOMBINEDRESERVED_V 1069 +#define IDC_ZSECTIONALLOCATED_V 1070 +#define IDC_ZSECTIONRESERVED_V 1071 +#define IDC_ZTOTALSEGMENTS_V 1072 +#define IDC_TITLE 1073 +#define IDC_GRAPH_LAYOUT 1074 +#define IDC_LAYOUT 1075 +#define IDC_GPUNAME 1076 +#define IDC_GPU_L 1077 +#define IDC_DEDICATED_L 1078 +#define IDC_SHARED_L 1079 +#define IDC_ZRECEIVESDELTA_V 1080 +#define IDC_ZSENDSDELTA_V 1081 +#define IDC_ZWRITESDELTA_V 1082 +#define IDC_ZREADSDELTA_V 1083 +#define IDC_INSTRUCTION 1084 +#define IDC_PANEL_LAYOUT 1085 +#define IDC_RESTART 1086 +#define IDC_ERROR 1087 +#define IDC_GROUPDISK 1088 +#define IDC_GROUPNETWORK 1089 +#define IDC_GPUDETAILS 1090 +#define ID_DISK_GOTOPROCESS 40005 +#define ID_DISK_COPY 40006 +#define ID_DISK_PROPERTIES 40007 +#define ID_DISK_OPENFILELOCATION 40008 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 134 +#define _APS_NEXT_COMMAND_VALUE 40009 +#define _APS_NEXT_CONTROL_VALUE 1091 +#define _APS_NEXT_SYMED_VALUE 130 +#endif +#endif diff --git a/plugins/ExtendedTools/thrdact.c b/plugins/ExtendedTools/thrdact.c new file mode 100644 index 0000000..f30be0d --- /dev/null +++ b/plugins/ExtendedTools/thrdact.c @@ -0,0 +1,64 @@ +/* + * Process Hacker Extended Tools - + * thread actions extensions + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +BOOLEAN EtUiCancelIoThread( + _In_ HWND hWnd, + _In_ PPH_THREAD_ITEM Thread + ) +{ + NTSTATUS status; + BOOLEAN cont = FALSE; + HANDLE threadHandle; + IO_STATUS_BLOCK isb; + + if (!PhGetIntegerSetting(L"EnableWarnings") || PhShowConfirmMessage( + hWnd, + L"end", + L"I/O for the selected thread", + NULL, + FALSE + )) + cont = TRUE; + + if (!cont) + return FALSE; + + if (NT_SUCCESS(status = PhOpenThread(&threadHandle, THREAD_TERMINATE, Thread->ThreadId))) + { + status = NtCancelSynchronousIoFile(threadHandle, NULL, &isb); + } + + if (status == STATUS_NOT_FOUND) + { + PhShowInformation(hWnd, L"There is no synchronous I/O to cancel."); + return FALSE; + } + else if (!NT_SUCCESS(status)) + { + PhShowStatus(hWnd, L"Unable to cancel synchronous I/O", status, 0); + return FALSE; + } + + return TRUE; +} diff --git a/plugins/ExtendedTools/treeext.c b/plugins/ExtendedTools/treeext.c new file mode 100644 index 0000000..ac9a2e1 --- /dev/null +++ b/plugins/ExtendedTools/treeext.c @@ -0,0 +1,783 @@ +/* + * Process Hacker Extended Tools - + * process and network tree support + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#define CINTERFACE +#define COBJMACROS +#include + +LONG EtpProcessTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ); + +LONG EtpNetworkTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ); + +typedef struct _COLUMN_INFO +{ + ULONG SubId; + PWSTR Text; + ULONG Width; + ULONG Alignment; + ULONG TextFlags; + BOOLEAN SortDescending; +} COLUMN_INFO, *PCOLUMN_INFO; + +static ULONG ProcessTreeListSortColumn; +static PH_SORT_ORDER ProcessTreeListSortOrder; + +static GUID IID_INetFwMgr_I = { 0xf7898af5, 0xcac4, 0x4632, { 0xa2, 0xec, 0xda, 0x06, 0xe5, 0x11, 0x1a, 0xf2 } }; +static GUID CLSID_NetFwMgr_I = { 0x304ce942, 0x6e39, 0x40d8, { 0x94, 0x3a, 0xb9, 0x13, 0xc4, 0x0c, 0x9c, 0xd4 } }; + +VOID EtpAddTreeNewColumn( + _In_ PPH_PLUGIN_TREENEW_INFORMATION TreeNewInfo, + _In_ ULONG SubId, + _In_ PWSTR Text, + _In_ ULONG Width, + _In_ ULONG Alignment, + _In_ ULONG TextFlags, + _In_ BOOLEAN SortDescending, + _In_ PPH_PLUGIN_TREENEW_SORT_FUNCTION SortFunction + ) +{ + PH_TREENEW_COLUMN column; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.SortDescending = SortDescending; + column.Text = Text; + column.Width = Width; + column.Alignment = Alignment; + column.TextFlags = TextFlags; + + PhPluginAddTreeNewColumn( + PluginInstance, + TreeNewInfo->CmData, + &column, + SubId, + NULL, + SortFunction + ); +} + +VOID EtProcessTreeNewInitializing( + _In_ PVOID Parameter + ) +{ + static COLUMN_INFO columns[] = + { + { ETPRTNC_DISKREADS, L"Disk reads", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKWRITES, L"Disk writes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKREADBYTES, L"Disk read bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKWRITEBYTES, L"Disk write bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKTOTALBYTES, L"Disk total bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKREADSDELTA, L"Disk reads delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKWRITESDELTA, L"Disk writes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKREADBYTESDELTA, L"Disk read bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKWRITEBYTESDELTA, L"Disk write bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKTOTALBYTESDELTA, L"Disk total bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKRECEIVES, L"Network receives", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKSENDS, L"Network sends", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKRECEIVEBYTES, L"Network receive bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKSENDBYTES, L"Network send bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKTOTALBYTES, L"Network total bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKRECEIVESDELTA, L"Network receives delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKSENDSDELTA, L"Network sends delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKRECEIVEBYTESDELTA, L"Network receive bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKSENDBYTESDELTA, L"Network send bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKTOTALBYTESDELTA, L"Network total bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_HARDFAULTS, L"Hard faults", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_HARDFAULTSDELTA, L"Hard faults delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_PEAKTHREADS, L"Peak threads", 45, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_GPU, L"GPU", 45, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_GPUDEDICATEDBYTES, L"GPU dedicated bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_GPUSHAREDBYTES, L"GPU shared bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKREADRATE, L"Disk read rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKWRITERATE, L"Disk write rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_DISKTOTALRATE, L"Disk total rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKRECEIVERATE, L"Network receive rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKSENDRATE, L"Network send rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETPRTNC_NETWORKTOTALRATE, L"Network total rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE } + }; + + PPH_PLUGIN_TREENEW_INFORMATION treeNewInfo = Parameter; + ULONG i; + + for (i = 0; i < sizeof(columns) / sizeof(COLUMN_INFO); i++) + { + EtpAddTreeNewColumn(treeNewInfo, columns[i].SubId, columns[i].Text, columns[i].Width, columns[i].Alignment, + columns[i].TextFlags, columns[i].SortDescending, EtpProcessTreeNewSortFunction); + } + + PhPluginEnableTreeNewNotify(PluginInstance, treeNewInfo->CmData); +} + +FLOAT EtpCalculateInclusiveGpuUsage( + _In_ PPH_PROCESS_NODE ProcessNode + ) +{ + FLOAT gpuUsage; + ULONG i; + + gpuUsage = EtGetProcessBlock(ProcessNode->ProcessItem)->GpuNodeUsage; + + for (i = 0; i < ProcessNode->Children->Count; i++) + { + gpuUsage += EtpCalculateInclusiveGpuUsage(ProcessNode->Children->Items[i]); + } + + return gpuUsage; +} + +VOID EtProcessTreeNewMessage( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + PPH_PROCESS_NODE processNode; + PET_PROCESS_BLOCK block; + + if (message->Message == TreeNewGetCellText) + { + PPH_TREENEW_GET_CELL_TEXT getCellText = message->Parameter1; + PPH_STRING text; + + processNode = (PPH_PROCESS_NODE)getCellText->Node; + block = EtGetProcessBlock(processNode->ProcessItem); + + PhAcquireQueuedLockExclusive(&block->TextCacheLock); + + if (block->TextCacheValid[message->SubId]) + { + if (block->TextCache[message->SubId]) + getCellText->Text = block->TextCache[message->SubId]->sr; + } + else + { + text = NULL; + + switch (message->SubId) + { + case ETPRTNC_DISKREADS: + if (block->DiskReadCount != 0) + text = PhFormatUInt64(block->DiskReadCount, TRUE); + break; + case ETPRTNC_DISKWRITES: + if (block->DiskWriteCount != 0) + text = PhFormatUInt64(block->DiskWriteCount, TRUE); + break; + case ETPRTNC_DISKREADBYTES: + if (block->DiskReadRaw != 0) + text = PhFormatSize(block->DiskReadRaw, -1); + break; + case ETPRTNC_DISKWRITEBYTES: + if (block->DiskWriteRaw != 0) + text = PhFormatSize(block->DiskWriteRaw, -1); + break; + case ETPRTNC_DISKTOTALBYTES: + if (block->DiskReadRaw + block->DiskWriteRaw != 0) + text = PhFormatSize(block->DiskReadRaw + block->DiskWriteRaw, -1); + break; + case ETPRTNC_DISKREADSDELTA: + if (block->DiskReadDelta.Delta != 0) + text = PhFormatUInt64(block->DiskReadDelta.Delta, TRUE); + break; + case ETPRTNC_DISKWRITESDELTA: + if (block->DiskWriteDelta.Delta != 0) + text = PhFormatUInt64(block->DiskWriteDelta.Delta, TRUE); + break; + case ETPRTNC_DISKREADBYTESDELTA: + if (block->DiskReadRawDelta.Delta != 0) + text = PhFormatSize(block->DiskReadRawDelta.Delta, -1); + break; + case ETPRTNC_DISKWRITEBYTESDELTA: + if (block->DiskWriteRawDelta.Delta != 0) + text = PhFormatSize(block->DiskWriteRawDelta.Delta, -1); + break; + case ETPRTNC_DISKTOTALBYTESDELTA: + if (block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta != 0) + text = PhFormatSize(block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta, -1); + break; + case ETPRTNC_NETWORKRECEIVES: + if (block->NetworkReceiveCount != 0) + text = PhFormatUInt64(block->NetworkReceiveCount, TRUE); + break; + case ETPRTNC_NETWORKSENDS: + if (block->NetworkSendCount != 0) + text = PhFormatUInt64(block->NetworkSendCount, TRUE); + break; + case ETPRTNC_NETWORKRECEIVEBYTES: + if (block->NetworkReceiveRaw != 0) + text = PhFormatSize(block->NetworkReceiveRaw, -1); + break; + case ETPRTNC_NETWORKSENDBYTES: + if (block->NetworkSendRaw != 0) + text = PhFormatSize(block->NetworkSendRaw, -1); + break; + case ETPRTNC_NETWORKTOTALBYTES: + if (block->NetworkReceiveRaw + block->NetworkSendRaw != 0) + text = PhFormatSize(block->NetworkReceiveRaw + block->NetworkSendRaw, -1); + break; + case ETPRTNC_NETWORKRECEIVESDELTA: + if (block->NetworkReceiveDelta.Delta != 0) + text = PhFormatUInt64(block->NetworkReceiveDelta.Delta, TRUE); + break; + case ETPRTNC_NETWORKSENDSDELTA: + if (block->NetworkSendDelta.Delta != 0) + text = PhFormatUInt64(block->NetworkSendDelta.Delta, TRUE); + break; + case ETPRTNC_NETWORKRECEIVEBYTESDELTA: + if (block->NetworkReceiveRawDelta.Delta != 0) + text = PhFormatSize(block->NetworkReceiveRawDelta.Delta, -1); + break; + case ETPRTNC_NETWORKSENDBYTESDELTA: + if (block->NetworkSendRawDelta.Delta != 0) + text = PhFormatSize(block->NetworkSendRawDelta.Delta, -1); + break; + case ETPRTNC_NETWORKTOTALBYTESDELTA: + if (block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta != 0) + text = PhFormatSize(block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta, -1); + break; + case ETPRTNC_HARDFAULTS: + text = PhFormatUInt64(block->HardFaultsDelta.Value, TRUE); + break; + case ETPRTNC_HARDFAULTSDELTA: + if (block->HardFaultsDelta.Delta != 0) + text = PhFormatUInt64(block->HardFaultsDelta.Delta, TRUE); + break; + case ETPRTNC_PEAKTHREADS: + text = PhFormatUInt64(block->ProcessItem->PeakNumberOfThreads, TRUE); + break; + case ETPRTNC_GPU: + { + FLOAT gpuUsage; + + if (!PhGetIntegerSetting(L"PropagateCpuUsage") || processNode->Node.Expanded || ProcessTreeListSortOrder != NoSortOrder) + { + gpuUsage = block->GpuNodeUsage * 100; + } + else + { + gpuUsage = EtpCalculateInclusiveGpuUsage(processNode) * 100; + } + + if (gpuUsage >= 0.01) + { + PH_FORMAT format; + + PhInitFormatF(&format, gpuUsage, 2); + text = PhFormat(&format, 1, 0); + } + } + break; + case ETPRTNC_GPUDEDICATEDBYTES: + if (block->GpuDedicatedUsage != 0) + text = PhFormatSize(block->GpuDedicatedUsage, -1); + break; + case ETPRTNC_GPUSHAREDBYTES: + if (block->GpuSharedUsage != 0) + text = PhFormatSize(block->GpuSharedUsage, -1); + break; + case ETPRTNC_DISKREADRATE: + if (block->DiskReadRawDelta.Delta != 0) + EtFormatRate(block->DiskReadRawDelta.Delta, &text, NULL); + break; + case ETPRTNC_DISKWRITERATE: + if (block->DiskWriteRawDelta.Delta != 0) + EtFormatRate(block->DiskWriteRawDelta.Delta, &text, NULL); + break; + case ETPRTNC_DISKTOTALRATE: + if (block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta != 0) + EtFormatRate(block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta, &text, NULL); + break; + case ETPRTNC_NETWORKRECEIVERATE: + if (block->NetworkReceiveRawDelta.Delta != 0) + EtFormatRate(block->NetworkReceiveRawDelta.Delta, &text, NULL); + break; + case ETPRTNC_NETWORKSENDRATE: + if (block->NetworkSendRawDelta.Delta != 0) + EtFormatRate(block->NetworkSendRawDelta.Delta, &text, NULL); + break; + case ETPRTNC_NETWORKTOTALRATE: + if (block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta != 0) + EtFormatRate(block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta, &text, NULL); + break; + } + + if (text) + { + getCellText->Text = text->sr; + } + + PhMoveReference(&block->TextCache[message->SubId], text); + block->TextCacheValid[message->SubId] = TRUE; + } + + PhReleaseQueuedLockExclusive(&block->TextCacheLock); + } + else if (message->Message == TreeNewSortChanged) + { + TreeNew_GetSort(message->TreeNewHandle, &ProcessTreeListSortColumn, &ProcessTreeListSortOrder); + } + else if (message->Message == TreeNewNodeExpanding) + { + processNode = message->Parameter1; + block = EtGetProcessBlock(processNode->ProcessItem); + + if (PhGetIntegerSetting(L"PropagateCpuUsage")) + block->TextCacheValid[ETPRTNC_GPU] = FALSE; + } +} + +LONG EtpProcessTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ) +{ + LONG result; + PPH_PROCESS_NODE node1 = Node1; + PPH_PROCESS_NODE node2 = Node2; + PET_PROCESS_BLOCK block1; + PET_PROCESS_BLOCK block2; + + block1 = EtGetProcessBlock(node1->ProcessItem); + block2 = EtGetProcessBlock(node2->ProcessItem); + + result = 0; + + switch (SubId) + { + case ETPRTNC_DISKREADS: + result = uint64cmp(block1->DiskReadCount, block2->DiskReadCount); + break; + case ETPRTNC_DISKWRITES: + result = uint64cmp(block1->DiskWriteCount, block2->DiskWriteCount); + break; + case ETPRTNC_DISKREADBYTES: + result = uint64cmp(block1->DiskReadRaw, block2->DiskReadRaw); + break; + case ETPRTNC_DISKWRITEBYTES: + result = uint64cmp(block1->DiskWriteRaw, block2->DiskWriteRaw); + break; + case ETPRTNC_DISKTOTALBYTES: + result = uint64cmp(block1->DiskReadRaw + block1->DiskWriteRaw, block2->DiskReadRaw + block2->DiskWriteRaw); + break; + case ETPRTNC_DISKREADSDELTA: + result = uint64cmp(block1->DiskReadDelta.Delta, block2->DiskReadDelta.Delta); + break; + case ETPRTNC_DISKWRITESDELTA: + result = uint64cmp(block1->DiskWriteDelta.Delta, block2->DiskWriteDelta.Delta); + break; + case ETPRTNC_DISKREADBYTESDELTA: + result = uint64cmp(block1->DiskReadRawDelta.Delta, block2->DiskReadRawDelta.Delta); + break; + case ETPRTNC_DISKWRITEBYTESDELTA: + result = uint64cmp(block1->DiskWriteRawDelta.Delta, block2->DiskWriteRawDelta.Delta); + break; + case ETPRTNC_DISKTOTALBYTESDELTA: + result = uint64cmp(block1->DiskReadRawDelta.Delta + block1->DiskWriteRawDelta.Delta, block2->DiskReadRawDelta.Delta + block2->DiskWriteRawDelta.Delta); + break; + case ETPRTNC_NETWORKRECEIVES: + result = uint64cmp(block1->NetworkReceiveCount, block2->NetworkReceiveCount); + break; + case ETPRTNC_NETWORKSENDS: + result = uint64cmp(block1->NetworkSendCount, block2->NetworkSendCount); + break; + case ETPRTNC_NETWORKRECEIVEBYTES: + result = uint64cmp(block1->NetworkReceiveRaw, block2->NetworkReceiveRaw); + break; + case ETPRTNC_NETWORKSENDBYTES: + result = uint64cmp(block1->NetworkSendRaw, block2->NetworkSendRaw); + break; + case ETPRTNC_NETWORKTOTALBYTES: + result = uint64cmp(block1->NetworkReceiveRaw + block1->NetworkSendRaw, block2->NetworkReceiveRaw + block2->NetworkSendRaw); + break; + case ETPRTNC_NETWORKRECEIVESDELTA: + result = uint64cmp(block1->NetworkReceiveDelta.Delta, block2->NetworkReceiveDelta.Delta); + break; + case ETPRTNC_NETWORKSENDSDELTA: + result = uint64cmp(block1->NetworkSendDelta.Delta, block2->NetworkSendDelta.Delta); + break; + case ETPRTNC_NETWORKRECEIVEBYTESDELTA: + result = uint64cmp(block1->NetworkReceiveRawDelta.Delta, block2->NetworkReceiveRawDelta.Delta); + break; + case ETPRTNC_NETWORKSENDBYTESDELTA: + result = uint64cmp(block1->NetworkSendRawDelta.Delta, block2->NetworkSendRawDelta.Delta); + break; + case ETPRTNC_NETWORKTOTALBYTESDELTA: + result = uint64cmp(block1->NetworkReceiveRawDelta.Delta + block1->NetworkSendRawDelta.Delta, block2->NetworkReceiveRawDelta.Delta + block2->NetworkSendRawDelta.Delta); + break; + case ETPRTNC_HARDFAULTS: + result = uintcmp(block1->HardFaultsDelta.Value, block2->HardFaultsDelta.Value); + break; + case ETPRTNC_HARDFAULTSDELTA: + result = uintcmp(block1->HardFaultsDelta.Delta, block2->HardFaultsDelta.Delta); + break; + case ETPRTNC_PEAKTHREADS: + result = uintcmp(block1->ProcessItem->PeakNumberOfThreads, block2->ProcessItem->PeakNumberOfThreads); + break; + case ETPRTNC_GPU: + result = singlecmp(block1->GpuNodeUsage, block2->GpuNodeUsage); + break; + case ETPRTNC_GPUDEDICATEDBYTES: + result = uint64cmp(block1->GpuDedicatedUsage, block2->GpuDedicatedUsage); + break; + case ETPRTNC_GPUSHAREDBYTES: + result = uint64cmp(block1->GpuSharedUsage, block2->GpuSharedUsage); + break; + case ETPRTNC_DISKREADRATE: + result = uint64cmp(block1->DiskReadRawDelta.Delta, block2->DiskReadRawDelta.Delta); + break; + case ETPRTNC_DISKWRITERATE: + result = uint64cmp(block1->DiskWriteRawDelta.Delta, block2->DiskWriteRawDelta.Delta); + break; + case ETPRTNC_DISKTOTALRATE: + result = uint64cmp(block1->DiskReadRawDelta.Delta + block1->DiskWriteRawDelta.Delta, block2->DiskReadRawDelta.Delta + block2->DiskWriteRawDelta.Delta); + break; + case ETPRTNC_NETWORKRECEIVERATE: + result = uint64cmp(block1->NetworkReceiveRawDelta.Delta, block2->NetworkReceiveRawDelta.Delta); + break; + case ETPRTNC_NETWORKSENDRATE: + result = uint64cmp(block1->NetworkSendRawDelta.Delta, block2->NetworkSendRawDelta.Delta); + break; + case ETPRTNC_NETWORKTOTALRATE: + result = uint64cmp(block1->NetworkReceiveRawDelta.Delta + block1->NetworkSendRawDelta.Delta, block2->NetworkReceiveRawDelta.Delta + block2->NetworkSendRawDelta.Delta); + break; + } + + return result; +} + +VOID EtNetworkTreeNewInitializing( + _In_ PVOID Parameter + ) +{ + static COLUMN_INFO columns[] = + { + { ETNETNC_RECEIVES, L"Receives", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_SENDS, L"Sends", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_RECEIVEBYTES, L"Receive bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_SENDBYTES, L"Send bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_TOTALBYTES, L"Total bytes", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_RECEIVESDELTA, L"Receives delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_SENDSDELTA, L"Sends delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_RECEIVEBYTESDELTA, L"Receive bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_SENDBYTESDELTA, L"Send bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_TOTALBYTESDELTA, L"Total bytes delta", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_FIREWALLSTATUS, L"Firewall status", 170, PH_ALIGN_LEFT, 0, FALSE }, + { ETNETNC_RECEIVERATE, L"Receive rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_SENDRATE, L"Send rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE }, + { ETNETNC_TOTALRATE, L"Total rate", 70, PH_ALIGN_RIGHT, DT_RIGHT, TRUE } + }; + + PPH_PLUGIN_TREENEW_INFORMATION treeNewInfo = Parameter; + ULONG i; + + for (i = 0; i < sizeof(columns) / sizeof(COLUMN_INFO); i++) + { + EtpAddTreeNewColumn(treeNewInfo, columns[i].SubId, columns[i].Text, columns[i].Width, columns[i].Alignment, + columns[i].TextFlags, columns[i].SortDescending, EtpNetworkTreeNewSortFunction); + } +} + +VOID EtpUpdateFirewallStatus( + _Inout_ PET_NETWORK_BLOCK Block + ) +{ + if (!Block->FirewallStatusValid) + { + Block->FirewallStatus = EtQueryFirewallStatus(Block->NetworkItem); + Block->FirewallStatusValid = TRUE; + } +} + +VOID EtNetworkTreeNewMessage( + _In_ PVOID Parameter + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + + if (message->Message == TreeNewGetCellText) + { + PPH_TREENEW_GET_CELL_TEXT getCellText = message->Parameter1; + PPH_NETWORK_NODE networkNode = (PPH_NETWORK_NODE)getCellText->Node; + PET_NETWORK_BLOCK block; + PPH_STRING text; + + block = EtGetNetworkBlock(networkNode->NetworkItem); + + PhAcquireQueuedLockExclusive(&block->TextCacheLock); + + if (block->TextCacheValid[message->SubId]) + { + if (block->TextCache[message->SubId]) + getCellText->Text = block->TextCache[message->SubId]->sr; + } + else + { + text = NULL; + + switch (message->SubId) + { + case ETNETNC_RECEIVES: + if (block->ReceiveCount != 0) + text = PhFormatUInt64(block->ReceiveCount, TRUE); + break; + case ETNETNC_SENDS: + if (block->SendCount != 0) + text = PhFormatUInt64(block->SendCount, TRUE); + break; + case ETNETNC_RECEIVEBYTES: + if (block->ReceiveRaw != 0) + text = PhFormatSize(block->ReceiveRaw, -1); + break; + case ETNETNC_SENDBYTES: + if (block->SendRaw != 0) + text = PhFormatSize(block->SendRaw, -1); + break; + case ETNETNC_TOTALBYTES: + if (block->ReceiveRaw + block->SendRaw != 0) + text = PhFormatSize(block->ReceiveRaw + block->SendRaw, -1); + break; + case ETNETNC_RECEIVESDELTA: + if (block->ReceiveDelta.Delta != 0) + text = PhFormatUInt64(block->ReceiveDelta.Delta, TRUE); + break; + case ETNETNC_SENDSDELTA: + if (block->SendDelta.Delta != 0) + text = PhFormatUInt64(block->SendDelta.Delta, TRUE); + break; + case ETNETNC_RECEIVEBYTESDELTA: + if (block->ReceiveRawDelta.Delta != 0) + text = PhFormatSize(block->ReceiveRawDelta.Delta, -1); + break; + case ETNETNC_SENDBYTESDELTA: + if (block->SendRawDelta.Delta != 0) + text = PhFormatSize(block->SendRawDelta.Delta, -1); + break; + case ETNETNC_TOTALBYTESDELTA: + if (block->ReceiveRawDelta.Delta + block->SendRawDelta.Delta != 0) + text = PhFormatSize(block->ReceiveRawDelta.Delta + block->SendRawDelta.Delta, -1); + break; + case ETNETNC_FIREWALLSTATUS: + { + static PPH_STRING strings[FirewallMaximumStatus]; + static PH_INITONCE initOnce = PH_INITONCE_INIT; + + if (PhBeginInitOnce(&initOnce)) + { + strings[FirewallUnknownStatus] = NULL; + strings[FirewallAllowedNotRestricted] = PhCreateString(L"Allowed, not restricted"); + strings[FirewallAllowedRestricted] = PhCreateString(L"Allowed, restricted"); + strings[FirewallNotAllowedNotRestricted] = PhCreateString(L"Not allowed, not restricted"); + strings[FirewallNotAllowedRestricted] = PhCreateString(L"Not allowed, restricted"); + PhEndInitOnce(&initOnce); + } + + EtpUpdateFirewallStatus(block); + + if (block->FirewallStatus < FirewallMaximumStatus) + PhSetReference(&text, strings[block->FirewallStatus]); + } + break; + case ETNETNC_RECEIVERATE: + if (block->ReceiveRawDelta.Delta != 0) + EtFormatRate(block->ReceiveRawDelta.Delta, &text, NULL); + break; + case ETNETNC_SENDRATE: + if (block->SendRawDelta.Delta != 0) + EtFormatRate(block->SendRawDelta.Delta, &text, NULL); + break; + case ETNETNC_TOTALRATE: + if (block->ReceiveRawDelta.Delta + block->SendRawDelta.Delta != 0) + EtFormatRate(block->ReceiveRawDelta.Delta + block->SendRawDelta.Delta, &text, NULL); + break; + } + + if (text) + { + getCellText->Text = text->sr; + } + + PhMoveReference(&block->TextCache[message->SubId], text); + block->TextCacheValid[message->SubId] = TRUE; + } + + PhReleaseQueuedLockExclusive(&block->TextCacheLock); + } +} + +LONG EtpNetworkTreeNewSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ) +{ + LONG result; + PPH_NETWORK_NODE node1 = Node1; + PPH_NETWORK_NODE node2 = Node2; + PET_NETWORK_BLOCK block1; + PET_NETWORK_BLOCK block2; + + block1 = EtGetNetworkBlock(node1->NetworkItem); + block2 = EtGetNetworkBlock(node2->NetworkItem); + + result = 0; + + switch (SubId) + { + case ETNETNC_RECEIVES: + result = uint64cmp(block1->ReceiveCount, block2->ReceiveCount); + break; + case ETNETNC_SENDS: + result = uint64cmp(block1->SendCount, block2->SendCount); + break; + case ETNETNC_RECEIVEBYTES: + result = uint64cmp(block1->ReceiveRaw, block2->ReceiveRaw); + break; + case ETNETNC_SENDBYTES: + result = uint64cmp(block1->SendRaw, block2->SendRaw); + break; + case ETNETNC_TOTALBYTES: + result = uint64cmp(block1->ReceiveRaw + block1->SendRaw, block2->ReceiveRaw + block2->SendRaw); + break; + case ETNETNC_RECEIVESDELTA: + result = uint64cmp(block1->ReceiveDelta.Delta, block2->ReceiveDelta.Delta); + break; + case ETNETNC_SENDSDELTA: + result = uint64cmp(block1->SendDelta.Delta, block2->SendDelta.Delta); + break; + case ETNETNC_RECEIVEBYTESDELTA: + result = uint64cmp(block1->ReceiveRawDelta.Delta, block2->ReceiveRawDelta.Delta); + break; + case ETNETNC_SENDBYTESDELTA: + result = uint64cmp(block1->SendRawDelta.Delta, block2->SendRawDelta.Delta); + break; + case ETNETNC_TOTALBYTESDELTA: + result = uint64cmp(block1->ReceiveRawDelta.Delta + block1->SendRawDelta.Delta, block2->ReceiveRawDelta.Delta + block2->SendRawDelta.Delta); + break; + case ETNETNC_FIREWALLSTATUS: + EtpUpdateFirewallStatus(block1); + EtpUpdateFirewallStatus(block2); + result = intcmp(block1->FirewallStatus, block2->FirewallStatus); + break; + case ETNETNC_RECEIVERATE: + result = uint64cmp(block1->ReceiveRawDelta.Delta, block2->ReceiveRawDelta.Delta); + break; + case ETNETNC_SENDRATE: + result = uint64cmp(block1->SendRawDelta.Delta, block2->SendRawDelta.Delta); + break; + case ETNETNC_TOTALRATE: + result = uint64cmp(block1->ReceiveRawDelta.Delta + block1->SendRawDelta.Delta, block2->ReceiveRawDelta.Delta + block2->SendRawDelta.Delta); + break; + } + + return result; +} + +ET_FIREWALL_STATUS EtQueryFirewallStatus( + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + static INetFwMgr* manager = NULL; + ET_FIREWALL_STATUS result; + PPH_PROCESS_ITEM processItem; + BSTR imageFileNameBStr; + BSTR localAddressBStr; + VARIANT allowed; + VARIANT restricted; + + if (!manager) + { + if (!SUCCEEDED(CoCreateInstance(&CLSID_NetFwMgr_I, NULL, CLSCTX_INPROC_SERVER, &IID_INetFwMgr_I, &manager))) + return FirewallUnknownStatus; + + if (!manager) + return FirewallUnknownStatus; + } + + processItem = PhReferenceProcessItem(NetworkItem->ProcessId); + + if (!processItem) + return FirewallUnknownStatus; + + if (!processItem->FileName) + { + PhDereferenceObject(processItem); + return FirewallUnknownStatus; + } + + result = FirewallUnknownStatus; + + if (imageFileNameBStr = SysAllocStringLen(processItem->FileName->Buffer, (ULONG)processItem->FileName->Length / sizeof(WCHAR))) + { + localAddressBStr = NULL; + + if (!PhIsNullIpAddress(&NetworkItem->LocalEndpoint.Address)) + localAddressBStr = SysAllocString(NetworkItem->LocalAddressString); + + if (SUCCEEDED(INetFwMgr_IsPortAllowed( + manager, + imageFileNameBStr, + (NetworkItem->ProtocolType & PH_IPV6_NETWORK_TYPE) ? NET_FW_IP_VERSION_V6 : NET_FW_IP_VERSION_V4, + NetworkItem->LocalEndpoint.Port, + localAddressBStr, + (NetworkItem->ProtocolType & PH_UDP_PROTOCOL_TYPE) ? NET_FW_IP_PROTOCOL_UDP : NET_FW_IP_PROTOCOL_TCP, + &allowed, + &restricted + ))) + { + if (allowed.boolVal) + { + if (restricted.boolVal) + result = FirewallAllowedRestricted; + else + result = FirewallAllowedNotRestricted; + } + else + { + if (restricted.boolVal) + result = FirewallNotAllowedRestricted; + else + result = FirewallNotAllowedNotRestricted; + } + } + + if (localAddressBStr) + SysFreeString(localAddressBStr); + + SysFreeString(imageFileNameBStr); + } + + PhDereferenceObject(processItem); + + return result; +} diff --git a/plugins/ExtendedTools/unldll.c b/plugins/ExtendedTools/unldll.c new file mode 100644 index 0000000..5145c62 --- /dev/null +++ b/plugins/ExtendedTools/unldll.c @@ -0,0 +1,357 @@ +/* + * Process Hacker Extended Tools - + * unloaded DLLs display + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +typedef struct _UNLOADED_DLLS_CONTEXT +{ + PPH_PROCESS_ITEM ProcessItem; + HWND ListViewHandle; + PVOID CapturedEventTrace; +} UNLOADED_DLLS_CONTEXT, *PUNLOADED_DLLS_CONTEXT; + +INT_PTR CALLBACK EtpUnloadedDllsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtShowUnloadedDllsDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + UNLOADED_DLLS_CONTEXT context; + + context.ProcessItem = ProcessItem; + context.CapturedEventTrace = NULL; + + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_UNLOADEDDLLS), + ParentWindowHandle, + EtpUnloadedDllsDlgProc, + (LPARAM)&context + ); + + if (context.CapturedEventTrace) + PhFree(context.CapturedEventTrace); +} + +BOOLEAN EtpRefreshUnloadedDlls( + _In_ HWND hwndDlg, + _In_ PUNLOADED_DLLS_CONTEXT Context + ) +{ + NTSTATUS status; + PULONG elementSize; + PULONG elementCount; + PVOID eventTrace; + HANDLE processHandle = NULL; + ULONG eventTraceSize; + ULONG capturedElementSize; + ULONG capturedElementCount; + PVOID capturedEventTracePointer; + PVOID capturedEventTrace = NULL; + ULONG i; + PVOID currentEvent; + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + ListView_DeleteAllItems(lvHandle); + + RtlGetUnloadEventTraceEx(&elementSize, &elementCount, &eventTrace); + + if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, PROCESS_VM_READ, Context->ProcessItem->ProcessId))) + goto CleanupExit; + + // We have the pointers for the unload event trace information. + // Since ntdll is loaded at the same base address across all processes, + // we can read the information in. + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + processHandle, + elementSize, + &capturedElementSize, + sizeof(ULONG), + NULL + ))) + goto CleanupExit; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + processHandle, + elementCount, + &capturedElementCount, + sizeof(ULONG), + NULL + ))) + goto CleanupExit; + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + processHandle, + eventTrace, + &capturedEventTracePointer, + sizeof(PVOID), + NULL + ))) + goto CleanupExit; + + if (!capturedEventTracePointer) + goto CleanupExit; // no events + + if (capturedElementCount > 0x4000) + capturedElementCount = 0x4000; + + eventTraceSize = capturedElementSize * capturedElementCount; + + capturedEventTrace = PhAllocateSafe(eventTraceSize); + + if (!capturedEventTrace) + { + status = STATUS_NO_MEMORY; + goto CleanupExit; + } + + if (!NT_SUCCESS(status = NtReadVirtualMemory( + processHandle, + capturedEventTracePointer, + capturedEventTrace, + eventTraceSize, + NULL + ))) + goto CleanupExit; + + currentEvent = capturedEventTrace; + + ExtendedListView_SetRedraw(lvHandle, FALSE); + + for (i = 0; i < capturedElementCount; i++) + { + PRTL_UNLOAD_EVENT_TRACE rtlEvent = currentEvent; + INT lvItemIndex; + WCHAR buffer[128]; + PPH_STRING string; + LARGE_INTEGER time; + SYSTEMTIME systemTime; + + if (!rtlEvent->BaseAddress) + break; + + PhPrintUInt32(buffer, rtlEvent->Sequence); + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, buffer, rtlEvent); + + // Name + if (PhCopyStringZ(rtlEvent->ImageName, sizeof(rtlEvent->ImageName) / sizeof(WCHAR), + buffer, sizeof(buffer) / sizeof(WCHAR), NULL)) + { + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, buffer); + } + + // Base Address + PhPrintPointer(buffer, rtlEvent->BaseAddress); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, buffer); + + // Size + string = PhFormatSize(rtlEvent->SizeOfImage, -1); + PhSetListViewSubItem(lvHandle, lvItemIndex, 3, string->Buffer); + PhDereferenceObject(string); + + // Time Stamp + RtlSecondsSince1970ToTime(rtlEvent->TimeDateStamp, &time); + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + string = PhFormatDateTime(&systemTime); + PhSetListViewSubItem(lvHandle, lvItemIndex, 4, string->Buffer); + PhDereferenceObject(string); + + // Checksum + PhPrintPointer(buffer, UlongToPtr(rtlEvent->CheckSum)); + PhSetListViewSubItem(lvHandle, lvItemIndex, 5, buffer); + + currentEvent = PTR_ADD_OFFSET(currentEvent, capturedElementSize); + } + + ExtendedListView_SortItems(lvHandle); + ExtendedListView_SetRedraw(lvHandle, TRUE); + + if (Context->CapturedEventTrace) + PhFree(Context->CapturedEventTrace); + + Context->CapturedEventTrace = capturedEventTrace; + +CleanupExit: + + if (processHandle) + NtClose(processHandle); + + if (NT_SUCCESS(status)) + { + return TRUE; + } + else + { + PhShowStatus(hwndDlg, L"Unable to retrieve unload event trace information", status, 0); + return FALSE; + } +} + +static INT NTAPI EtpNumberCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PRTL_UNLOAD_EVENT_TRACE item1 = Item1; + PRTL_UNLOAD_EVENT_TRACE item2 = Item2; + + return uintcmp(item1->Sequence, item2->Sequence); +} + +static INT NTAPI EtpBaseAddressCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PRTL_UNLOAD_EVENT_TRACE item1 = Item1; + PRTL_UNLOAD_EVENT_TRACE item2 = Item2; + + return uintptrcmp((ULONG_PTR)item1->BaseAddress, (ULONG_PTR)item2->BaseAddress); +} + +static INT NTAPI EtpSizeCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PRTL_UNLOAD_EVENT_TRACE item1 = Item1; + PRTL_UNLOAD_EVENT_TRACE item2 = Item2; + + return uintptrcmp(item1->SizeOfImage, item2->SizeOfImage); +} + +static INT NTAPI EtpTimeStampCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PRTL_UNLOAD_EVENT_TRACE item1 = Item1; + PRTL_UNLOAD_EVENT_TRACE item2 = Item2; + + return uintcmp(item1->TimeDateStamp, item2->TimeDateStamp); +} + +static INT NTAPI EtpCheckSumCompareFunction( + _In_ PVOID Item1, + _In_ PVOID Item2, + _In_opt_ PVOID Context + ) +{ + PRTL_UNLOAD_EVENT_TRACE item1 = Item1; + PRTL_UNLOAD_EVENT_TRACE item2 = Item2; + + return uintcmp(item1->CheckSum, item2->CheckSum); +} + +INT_PTR CALLBACK EtpUnloadedDllsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PUNLOADED_DLLS_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PUNLOADED_DLLS_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PUNLOADED_DLLS_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + context->ListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 40, L"No."); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 120, L"Name"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 80, L"Base Address"); + PhAddListViewColumn(lvHandle, 3, 3, 3, LVCFMT_LEFT, 60, L"Size"); + PhAddListViewColumn(lvHandle, 4, 4, 4, LVCFMT_LEFT, 100, L"Time Stamp"); + PhAddListViewColumn(lvHandle, 5, 5, 5, LVCFMT_LEFT, 60, L"Checksum"); + + PhSetExtendedListView(lvHandle); + ExtendedListView_SetCompareFunction(lvHandle, 0, EtpNumberCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 2, EtpBaseAddressCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 3, EtpSizeCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 4, EtpTimeStampCompareFunction); + ExtendedListView_SetCompareFunction(lvHandle, 5, EtpCheckSumCompareFunction); + + if (!EtpRefreshUnloadedDlls(hwndDlg, context)) + { + EndDialog(hwndDlg, IDCANCEL); + return FALSE; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_REFRESH: + EtpRefreshUnloadedDlls(hwndDlg, context); + break; + } + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyForCopy(lParam, context->ListViewHandle); + } + break; + } + + return FALSE; +} diff --git a/plugins/ExtendedTools/utils.c b/plugins/ExtendedTools/utils.c new file mode 100644 index 0000000..2d5f23a --- /dev/null +++ b/plugins/ExtendedTools/utils.c @@ -0,0 +1,48 @@ +/* + * Process Hacker Extended Tools - + * utility functions + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" + +VOID EtFormatRate( + _In_ ULONG64 ValuePerPeriod, + _Inout_ PPH_STRING *Buffer, + _Out_opt_ PPH_STRINGREF String + ) +{ + ULONG64 number; + + number = ValuePerPeriod; + number *= 1000; + number /= PhGetIntegerSetting(L"UpdateInterval"); + + if (number != 0) + { + PH_FORMAT format[2]; + + PhInitFormatSize(&format[0], number); + PhInitFormatS(&format[1], L"/s"); + PhMoveReference(Buffer, PhFormat(format, 2, 0)); + + if (String) + *String = (*Buffer)->sr; + } +} diff --git a/plugins/ExtendedTools/wswatch.c b/plugins/ExtendedTools/wswatch.c new file mode 100644 index 0000000..470092c --- /dev/null +++ b/plugins/ExtendedTools/wswatch.c @@ -0,0 +1,571 @@ +/* + * Process Hacker Extended Tools - + * working set watch + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "exttools.h" +#include +#include + +typedef struct _WS_WATCH_CONTEXT +{ + LONG RefCount; + + PPH_PROCESS_ITEM ProcessItem; + HWND WindowHandle; + HWND ListViewHandle; + BOOLEAN Enabled; + BOOLEAN Destroying; + PPH_HASHTABLE Hashtable; + HANDLE ProcessHandle; + PVOID Buffer; + ULONG BufferSize; + + PPH_SYMBOL_PROVIDER SymbolProvider; + HANDLE LoadingSymbolsForProcessId; + SINGLE_LIST_ENTRY ResultListHead; + PH_QUEUED_LOCK ResultListLock; +} WS_WATCH_CONTEXT, *PWS_WATCH_CONTEXT; + +typedef struct _SYMBOL_LOOKUP_RESULT +{ + SINGLE_LIST_ENTRY ListEntry; + PWS_WATCH_CONTEXT Context; + PVOID Address; + PPH_STRING Symbol; +} SYMBOL_LOOKUP_RESULT, *PSYMBOL_LOOKUP_RESULT; + +VOID EtpReferenceWsWatchContext( + _Inout_ PWS_WATCH_CONTEXT Context + ); + +VOID EtpDereferenceWsWatchContext( + _Inout_ PWS_WATCH_CONTEXT Context + ); + +INT_PTR CALLBACK EtpWsWatchDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +VOID EtShowWsWatchDialog( + _In_ HWND ParentWindowHandle, + _In_ PPH_PROCESS_ITEM ProcessItem + ) +{ + PWS_WATCH_CONTEXT context; + + context = PhAllocate(sizeof(WS_WATCH_CONTEXT)); + memset(context, 0, sizeof(WS_WATCH_CONTEXT)); + context->RefCount = 1; + context->ProcessItem = ProcessItem; + + DialogBoxParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_WSWATCH), + ParentWindowHandle, + EtpWsWatchDlgProc, + (LPARAM)context + ); + EtpDereferenceWsWatchContext(context); +} + +static VOID EtpReferenceWsWatchContext( + _Inout_ PWS_WATCH_CONTEXT Context + ) +{ + _InterlockedIncrement(&Context->RefCount); +} + +static VOID EtpDereferenceWsWatchContext( + _Inout_ PWS_WATCH_CONTEXT Context + ) +{ + if (_InterlockedDecrement(&Context->RefCount) == 0) + { + PSINGLE_LIST_ENTRY listEntry; + + if (Context->SymbolProvider) + PhDereferenceObject(Context->SymbolProvider); + + // Free all unused results. + + PhAcquireQueuedLockExclusive(&Context->ResultListLock); + + listEntry = Context->ResultListHead.Next; + + while (listEntry) + { + PSYMBOL_LOOKUP_RESULT result; + + result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry); + listEntry = listEntry->Next; + PhDereferenceObject(result->Symbol); + PhFree(result); + } + + PhReleaseQueuedLockExclusive(&Context->ResultListLock); + + PhFree(Context); + } +} + +static NTSTATUS EtpSymbolLookupFunction( + _In_ PVOID Parameter + ) +{ + PSYMBOL_LOOKUP_RESULT result; + + result = Parameter; + + // Don't bother looking up the symbol if the window has already closed. + if (result->Context->Destroying) + { + EtpDereferenceWsWatchContext(result->Context); + PhFree(result); + return STATUS_SUCCESS; + } + + result->Symbol = PhGetSymbolFromAddress( + result->Context->SymbolProvider, + (ULONG64)result->Address, + NULL, + NULL, + NULL, + NULL + ); + + // Fail if we don't have a symbol. + if (!result->Symbol) + { + EtpDereferenceWsWatchContext(result->Context); + PhFree(result); + return STATUS_SUCCESS; + } + + PhAcquireQueuedLockExclusive(&result->Context->ResultListLock); + PushEntryList(&result->Context->ResultListHead, &result->ListEntry); + PhReleaseQueuedLockExclusive(&result->Context->ResultListLock); + EtpDereferenceWsWatchContext(result->Context); + + return STATUS_SUCCESS; +} + +static VOID EtpQueueSymbolLookup( + _In_ PWS_WATCH_CONTEXT Context, + _In_ PVOID Address + ) +{ + PSYMBOL_LOOKUP_RESULT result; + + result = PhAllocate(sizeof(SYMBOL_LOOKUP_RESULT)); + result->Context = Context; + result->Address = Address; + EtpReferenceWsWatchContext(Context); + + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), EtpSymbolLookupFunction, result); +} + +static PPH_STRING EtpGetBasicSymbol( + _In_ PPH_SYMBOL_PROVIDER SymbolProvider, + _In_ ULONG64 Address + ) +{ + ULONG64 modBase; + PPH_STRING fileName = NULL; + PPH_STRING baseName = NULL; + PPH_STRING symbol; + + modBase = PhGetModuleFromAddress(SymbolProvider, Address, &fileName); + + if (!fileName) + { + symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2); + PhPrintPointer(symbol->Buffer, (PVOID)Address); + PhTrimToNullTerminatorString(symbol); + } + else + { + PH_FORMAT format[3]; + + baseName = PhGetBaseName(fileName); + + PhInitFormatSR(&format[0], baseName->sr); + PhInitFormatS(&format[1], L"+0x"); + PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase)); + + symbol = PhFormat(format, 3, baseName->Length + 6 + 32); + } + + if (fileName) + PhDereferenceObject(fileName); + if (baseName) + PhDereferenceObject(baseName); + + return symbol; +} + +static VOID EtpProcessSymbolLookupResults( + _In_ HWND hwndDlg, + _In_ PWS_WATCH_CONTEXT Context + ) +{ + PSINGLE_LIST_ENTRY listEntry; + + // Remove all results. + PhAcquireQueuedLockExclusive(&Context->ResultListLock); + listEntry = Context->ResultListHead.Next; + Context->ResultListHead.Next = NULL; + PhReleaseQueuedLockExclusive(&Context->ResultListLock); + + // Update the list view with the results. + while (listEntry) + { + PSYMBOL_LOOKUP_RESULT result; + + result = CONTAINING_RECORD(listEntry, SYMBOL_LOOKUP_RESULT, ListEntry); + listEntry = listEntry->Next; + + PhSetListViewSubItem( + Context->ListViewHandle, + PhFindListViewItemByParam(Context->ListViewHandle, -1, result->Address), + 0, + result->Symbol->Buffer + ); + + PhDereferenceObject(result->Symbol); + PhFree(result); + } +} + +static BOOLEAN EtpUpdateWsWatch( + _In_ HWND hwndDlg, + _In_ PWS_WATCH_CONTEXT Context + ) +{ + NTSTATUS status; + BOOLEAN result; + ULONG returnLength; + PPROCESS_WS_WATCH_INFORMATION_EX wsWatchInfo; + + // Query WS watch information. + + if (!Context->Buffer) + return FALSE; + + status = NtQueryInformationProcess( + Context->ProcessHandle, + ProcessWorkingSetWatchEx, + Context->Buffer, + Context->BufferSize, + &returnLength + ); + + if (status == STATUS_UNSUCCESSFUL) + { + // WS Watch is not enabled. + return FALSE; + } + + if (status == STATUS_NO_MORE_ENTRIES) + { + // There were no new faults, but we still need to process symbol lookup results. + result = TRUE; + goto SkipBuffer; + } + + if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) + { + PhFree(Context->Buffer); + Context->Buffer = PhAllocate(returnLength); + Context->BufferSize = returnLength; + + status = NtQueryInformationProcess( + Context->ProcessHandle, + ProcessWorkingSetWatchEx, + Context->Buffer, + Context->BufferSize, + &returnLength + ); + } + + if (!NT_SUCCESS(status)) + { + // Error related to the buffer size. Try again later. + result = FALSE; + goto SkipBuffer; + } + + // Update the hashtable and list view. + + ExtendedListView_SetRedraw(Context->ListViewHandle, FALSE); + + wsWatchInfo = Context->Buffer; + + while (wsWatchInfo->BasicInfo.FaultingPc) + { + PVOID *entry; + WCHAR buffer[PH_INT32_STR_LEN_1]; + INT lvItemIndex; + ULONG newCount; + + // Update the count in the entry for this instruction pointer, or add a new entry if it doesn't exist. + + entry = PhFindItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc); + + if (entry) + { + newCount = PtrToUlong(*entry) + 1; + *entry = UlongToPtr(newCount); + lvItemIndex = PhFindListViewItemByParam(Context->ListViewHandle, -1, wsWatchInfo->BasicInfo.FaultingPc); + } + else + { + PPH_STRING basicSymbol; + + newCount = 1; + PhAddItemSimpleHashtable(Context->Hashtable, wsWatchInfo->BasicInfo.FaultingPc, UlongToPtr(1)); + + // Get a basic symbol name (module+offset). + basicSymbol = EtpGetBasicSymbol(Context->SymbolProvider, (ULONG64)wsWatchInfo->BasicInfo.FaultingPc); + + lvItemIndex = PhAddListViewItem(Context->ListViewHandle, MAXINT, basicSymbol->Buffer, wsWatchInfo->BasicInfo.FaultingPc); + PhDereferenceObject(basicSymbol); + + // Queue a full symbol lookup. + EtpQueueSymbolLookup(Context, wsWatchInfo->BasicInfo.FaultingPc); + } + + // Update the count in the list view item. + PhPrintUInt32(buffer, newCount); + PhSetListViewSubItem( + Context->ListViewHandle, + lvItemIndex, + 1, + buffer + ); + + wsWatchInfo++; + } + + ExtendedListView_SetRedraw(Context->ListViewHandle, TRUE); + result = TRUE; + +SkipBuffer: + EtpProcessSymbolLookupResults(hwndDlg, Context); + ExtendedListView_SortItems(Context->ListViewHandle); + + return result; +} + +static BOOLEAN NTAPI EnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PWS_WATCH_CONTEXT context = Context; + + // If we're loading kernel module symbols for a process other than + // System, ignore modules which are in user space. This may happen + // in Windows 7. + if ( + context->LoadingSymbolsForProcessId == SYSTEM_PROCESS_ID && + (ULONG_PTR)Module->BaseAddress <= PhSystemBasicInformation.MaximumUserModeAddress + ) + return TRUE; + + PhLoadModuleSymbolProvider(context->SymbolProvider, Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, Module->Size); + + return TRUE; +} + +INT_PTR CALLBACK EtpWsWatchDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWS_WATCH_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PWS_WATCH_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PWS_WATCH_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + context->WindowHandle = hwndDlg; + context->ListViewHandle = lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 340, L"Instruction"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"Count"); + PhSetExtendedListView(lvHandle); + ExtendedListView_SetSort(lvHandle, 1, DescendingSortOrder); + + context->Hashtable = PhCreateSimpleHashtable(64); + context->BufferSize = 0x2000; + context->Buffer = PhAllocate(context->BufferSize); + + PhInitializeQueuedLock(&context->ResultListLock); + context->SymbolProvider = PhCreateSymbolProvider(context->ProcessItem->ProcessId); + PhLoadSymbolProviderOptions(context->SymbolProvider); + + if (!context->SymbolProvider || !context->SymbolProvider->IsRealHandle) + { + PhShowError(hwndDlg, L"Unable to open the process."); + EndDialog(hwndDlg, IDCANCEL); + break; + } + + context->ProcessHandle = context->SymbolProvider->ProcessHandle; + + // Load symbols for both process and kernel modules. + context->LoadingSymbolsForProcessId = context->ProcessItem->ProcessId; + PhEnumGenericModules( + NULL, + context->ProcessHandle, + 0, + EnumGenericModulesCallback, + context + ); + context->LoadingSymbolsForProcessId = SYSTEM_PROCESS_ID; + PhEnumGenericModules( + SYSTEM_PROCESS_ID, + NULL, + 0, + EnumGenericModulesCallback, + context + ); + + context->Enabled = EtpUpdateWsWatch(hwndDlg, context); + + if (context->Enabled) + { + // WS Watch is already enabled for the process. Enable updating. + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE); + ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW); + SetTimer(hwndDlg, 1, 1000, NULL); + } + else + { + // WS Watch has not yet been enabled for the process. + } + } + break; + case WM_DESTROY: + { + context->Destroying = TRUE; + + PhDereferenceObject(context->Hashtable); + + if (context->Buffer) + { + PhFree(context->Buffer); + context->Buffer = NULL; + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + EndDialog(hwndDlg, IDOK); + break; + case IDC_ENABLE: + { + NTSTATUS status; + HANDLE processHandle; + + if (NT_SUCCESS(status = PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + context->ProcessItem->ProcessId + ))) + { + status = NtSetInformationProcess( + processHandle, + ProcessWorkingSetWatchEx, + NULL, + 0 + ); + NtClose(processHandle); + } + + if (NT_SUCCESS(status)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_ENABLE), FALSE); + ShowWindow(GetDlgItem(hwndDlg, IDC_WSWATCHENABLED), SW_SHOW); + SetTimer(hwndDlg, 1, 1000, NULL); + } + else + { + PhShowStatus(hwndDlg, L"Unable to enable WS watch", status, 0); + } + } + break; + } + } + break; + case WM_NOTIFY: + { + PhHandleListViewNotifyForCopy(lParam, context->ListViewHandle); + } + break; + case WM_TIMER: + { + switch (wParam) + { + case 1: + { + EtpUpdateWsWatch(hwndDlg, context); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/HardwareDevices/CHANGELOG.txt b/plugins/HardwareDevices/CHANGELOG.txt new file mode 100644 index 0000000..c69d897 --- /dev/null +++ b/plugins/HardwareDevices/CHANGELOG.txt @@ -0,0 +1,17 @@ +1.4 + * Fixed incorrect drive letters + * Fixed drive letter and panel clipping issue + +1.3 + * Fixed Vista support + * Adapter information is now updated regardless of whether System Information is open + * Added disk statistics support + +1.2 + * Added network adapter details window + +1.1 + * Added native network driver statistics support + +1.0 + * Initial release diff --git a/plugins/HardwareDevices/HardwareDevices.rc b/plugins/HardwareDevices/HardwareDevices.rc new file mode 100644 index 0000000..785c319 --- /dev/null +++ b/plugins/HardwareDevices/HardwareDevices.rc @@ -0,0 +1,402 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,4,0,0 + PRODUCTVERSION 1,4,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "dmex" + VALUE "FileDescription", "Hardware Devices plugin for Process Hacker" + VALUE "FileVersion", "1.4" + VALUE "InternalName", "HardwareDevices" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "HardwareDevices.dll" + VALUE "ProductName", "Hardware Devices plugin for Process Hacker" + VALUE "ProductVersion", "1.4" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_NETADAPTER_OPTIONS DIALOGEX 0, 0, 265, 177 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Network Adapters" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_NETADAPTERS_LISTVIEW,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,7,251,146 + CONTROL "Show hidden adapters",IDC_SHOW_HIDDEN_ADAPTERS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,159,87,10 +END + +IDD_NETADAPTER_DIALOG DIALOGEX 0, 0, 269, 130 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "",IDC_GRAPH_LAYOUT,0,21,269,53,NOT WS_VISIBLE | WS_BORDER + LTEXT "Static",IDC_ADAPTERNAME,0,0,269,21 + LTEXT "Panel layout",IDC_LAYOUT,0,91,268,36,NOT WS_VISIBLE +END + +IDD_NETADAPTER_PANEL DIALOGEX 0, 0, 254, 50 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Link speed",IDC_STATIC,7,11,35,8 + RTEXT "Static",IDC_LINK_SPEED,50,11,54,8,SS_ENDELLIPSIS + GROUPBOX "Adapter",IDC_STATIC,0,0,111,34 + LTEXT "Link state",IDC_STATIC,7,23,32,8 + RTEXT "Static",IDC_LINK_STATE,50,23,54,8,SS_ENDELLIPSIS + GROUPBOX "Statistics",IDC_STATIC,116,0,136,45 + LTEXT "Bytes sent",IDC_STATIC,125,11,36,8 + LTEXT "Bytes received",IDC_STATIC,125,22,50,8 + RTEXT "Static",IDC_STAT_BSENT,182,11,62,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_STAT_BRECEIVED,182,22,62,8,SS_ENDELLIPSIS + LTEXT "Bytes total",IDC_STATIC,125,33,37,8 + RTEXT "Static",IDC_STAT_BTOTAL,182,33,62,8,SS_ENDELLIPSIS + PUSHBUTTON "Details",IDC_DETAILS,0,35,50,14 +END + +IDD_NETADAPTER_DETAILS DIALOGEX 0, 0, 309, 265 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Adapter Details" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Close",IDOK,253,244,50,14 + CONTROL "",IDC_DETAILS_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,295,235 +END + +IDD_DISKDRIVE_OPTIONS DIALOGEX 0, 0, 265, 177 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Disk Drives" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_DISKDRIVE_LISTVIEW,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP,7,7,251,163 +END + +IDD_DISKDRIVE_DIALOG DIALOGEX 0, 0, 315, 135 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "",IDC_GRAPH_LAYOUT,0,21,314,60,NOT WS_VISIBLE | WS_BORDER + LTEXT "Disk",IDC_DISKMOUNTPATH,0,0,105,21 + LTEXT "Panel layout",IDC_LAYOUT,0,98,314,36,NOT WS_VISIBLE + RTEXT "Disk name",IDC_DISKNAME,107,4,207,16,SS_WORDELLIPSIS +END + +IDD_DISKDRIVE_PANEL DIALOGEX 0, 0, 330, 50 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Statistics",IDC_STATIC,139,0,136,44 + LTEXT "Bytes read",IDC_STATIC,148,11,38,8 + LTEXT "Bytes written",IDC_STATIC,148,22,45,8 + RTEXT "Static",IDC_STAT_BREAD,205,11,62,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_STAT_BWRITE,205,22,62,8,SS_ENDELLIPSIS + LTEXT "Bytes total",IDC_STATIC,148,33,37,8 + RTEXT "Static",IDC_STAT_BTOTAL,205,33,62,8,SS_ENDELLIPSIS + LTEXT "Active time",IDC_STATIC,8,11,53,8,SS_ENDELLIPSIS + LTEXT "Response time",IDC_STATIC,8,22,64,8,SS_ENDELLIPSIS + LTEXT "Queue length",IDC_STATIC,8,33,57,8,SS_ENDELLIPSIS + GROUPBOX "Disk",IDC_STATIC,0,0,135,44 + PUSHBUTTON "Details",IDC_DETAILS,278,30,50,14,NOT WS_VISIBLE + RTEXT "Static",IDC_STAT_ACTIVE,65,11,62,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_STAT_RESPONSETIME,65,22,62,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_STAT_QUEUELENGTH,65,33,62,8,SS_ENDELLIPSIS +END + +IDD_DISKDRIVE_DETAILS_SMART DIALOGEX 0, 0, 309, 265 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "SMART" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_DETAILS_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,295,182 + EDITTEXT IDC_EDIT1,7,200,295,58,ES_MULTILINE | ES_READONLY | WS_VSCROLL + LTEXT "Description:",IDC_DESCRIPTION,7,190,39,8 +END + +IDD_DISKDRIVE_DETAILS_FILESYSTEM DIALOGEX 0, 0, 309, 265 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Volume(s)" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_DETAILS_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,295,182 + EDITTEXT IDC_EDIT1,7,200,295,58,ES_MULTILINE | ES_READONLY | WS_VSCROLL + LTEXT "Description:",IDC_DESCRIPTION,7,190,39,8 +END + +IDD_DISKDRIVE_DETAILS_GENERAL DIALOGEX 0, 0, 275, 262 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN +END + +IDD_GPU_DIALOG DIALOGEX 0, 0, 315, 191 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "GPU",IDC_TITLE,0,0,72,21 + RTEXT "GPU Name",IDC_GPUNAME,83,4,232,16,SS_WORDELLIPSIS + LTEXT "Panel layout",IDC_LAYOUT,0,145,315,46,NOT WS_VISIBLE + LTEXT "Graph layout",IDC_GRAPH_LAYOUT,0,22,315,120,NOT WS_VISIBLE + LTEXT "GPU:",IDC_GPU_L,73,0,17,8 + LTEXT "Memory:",IDC_MEMORY_L,73,5,29,8 + LTEXT "Memory Controller:",IDC_SHARED_L,74,11,62,8 + LTEXT "Bus Interface:",IDC_BUS_L,74,18,47,8 +END + +IDD_GPU_PANEL DIALOGEX 0, 0, 372, 47 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Clocks",IDC_STATIC,0,0,118,43 + LTEXT "Core",IDC_STATIC,8,10,16,8 + LTEXT "Memory",IDC_STATIC,8,21,26,8 + RTEXT "Static",IDC_CLOCK_CORE,55,10,54,8,SS_ENDELLIPSIS + RTEXT "Static",IDC_CLOCK_MEMORY,55,21,54,8,SS_ENDELLIPSIS + GROUPBOX "Fan",IDC_STATIC,122,0,124,22 + LTEXT "Speed",IDC_STATIC,130,10,21,8 + RTEXT "Static",IDC_FAN_PERCENT,158,10,78,8,SS_ENDELLIPSIS + LTEXT "Shader",IDC_STATIC,8,32,24,8 + RTEXT "Static",IDC_CLOCK_SHADER,55,32,54,8,SS_ENDELLIPSIS + GROUPBOX "Temperature",IDC_STATIC,122,22,124,21 + LTEXT "Core",IDC_STATIC,130,32,16,8 + RTEXT "Static",IDC_TEMP_VALUE,158,32,78,8,SS_ENDELLIPSIS + GROUPBOX "Voltage",IDC_STATIC,248,0,124,22 + LTEXT "Core",IDC_STATIC,256,10,16,8 + RTEXT "Static",IDC_VOLTAGE,284,10,78,8,SS_ENDELLIPSIS + PUSHBUTTON "Details",IDC_DETAILS,249,27,50,14,NOT WS_VISIBLE +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_NETADAPTER_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 258 + TOPMARGIN, 7 + BOTTOMMARGIN, 170 + END + + IDD_NETADAPTER_DIALOG, DIALOG + BEGIN + END + + IDD_NETADAPTER_PANEL, DIALOG + BEGIN + RIGHTMARGIN, 253 + TOPMARGIN, 1 + END + + IDD_NETADAPTER_DETAILS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 258 + END + + IDD_DISKDRIVE_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 258 + TOPMARGIN, 7 + BOTTOMMARGIN, 170 + END + + IDD_DISKDRIVE_DIALOG, DIALOG + BEGIN + RIGHTMARGIN, 314 + BOTTOMMARGIN, 134 + END + + IDD_DISKDRIVE_PANEL, DIALOG + BEGIN + RIGHTMARGIN, 329 + BOTTOMMARGIN, 49 + END + + IDD_DISKDRIVE_DETAILS_SMART, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 258 + END + + IDD_DISKDRIVE_DETAILS_FILESYSTEM, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 258 + END + + IDD_DISKDRIVE_DETAILS_GENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 268 + TOPMARGIN, 7 + BOTTOMMARGIN, 255 + END + + IDD_GPU_DIALOG, DIALOG + BEGIN + END + + IDD_GPU_PANEL, DIALOG + BEGIN + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_NETADAPTER_PANEL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_NETADAPTER_DETAILS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_NETADAPTER_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_NETADAPTER_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_PANEL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_DETAILS_SMART AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_DETAILS_FILESYSTEM AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_DISKDRIVE_DETAILS_GENERAL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_GPU_DIALOG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_GPU_PANEL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + diff --git a/plugins/HardwareDevices/HardwareDevices.vcxproj b/plugins/HardwareDevices/HardwareDevices.vcxproj new file mode 100644 index 0000000..9bd1f74 --- /dev/null +++ b/plugins/HardwareDevices/HardwareDevices.vcxproj @@ -0,0 +1,120 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {5F0D72C4-8319-4B61-9E13-6084B680EB90} + HardwareDevices + Win32Proj + HardwareDevices + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + iphlpapi.lib;setupapi.lib;uxtheme.lib;%(AdditionalDependencies) + iphlpapi.dll;setupapi.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;setupapi.lib;uxtheme.lib;%(AdditionalDependencies) + iphlpapi.dll;setupapi.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;setupapi.lib;uxtheme.lib;%(AdditionalDependencies) + iphlpapi.dll;setupapi.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;setupapi.lib;uxtheme.lib;%(AdditionalDependencies) + iphlpapi.dll;setupapi.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/HardwareDevices/HardwareDevices.vcxproj.filters b/plugins/HardwareDevices/HardwareDevices.vcxproj.filters new file mode 100644 index 0000000..f1ccd0b --- /dev/null +++ b/plugins/HardwareDevices/HardwareDevices.vcxproj.filters @@ -0,0 +1,95 @@ + + + + + {47359E56-A930-4DDC-A651-E2E99D48E957} + + + {4965CB7A-371A-4B22-AC3F-E70DC77C5D24} + + + {00475F04-2369-42D8-9A52-9DB77E37AE62} + + + {1ccecf79-cd82-4ae5-b891-64a0a2d18e8e} + + + {c7c48d78-64cd-4559-87c1-74e2141dcd2e} + + + {17660e06-b187-4eca-81d7-f6d3548f3c2f} + + + {987f6036-c8df-4352-abc3-060d5a1bc7e4} + + + + + Header Files + + + Header Files + + + Header Files\Graphics + + + Header Files\Graphics + + + Header Files\Graphics + + + + + Source Files + + + Source Files\Disk + + + Source Files\Network + + + Source Files\Disk + + + Source Files\Network + + + Source Files\Network + + + Source Files\Network + + + Source Files\Network + + + Source Files\Disk + + + Source Files\Disk + + + Source Files\Disk + + + Source Files\Disk + + + Source Files\Graphics + + + Source Files\Graphics + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/HardwareDevices/adapter.c b/plugins/HardwareDevices/adapter.c new file mode 100644 index 0000000..ba84f35 --- /dev/null +++ b/plugins/HardwareDevices/adapter.c @@ -0,0 +1,292 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +VOID AdapterEntryDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PDV_NETADAPTER_ENTRY entry = Object; + + PhAcquireQueuedLockExclusive(&NetworkAdaptersListLock); + PhRemoveItemList(NetworkAdaptersList, PhFindItemList(NetworkAdaptersList, entry)); + PhReleaseQueuedLockExclusive(&NetworkAdaptersListLock); + + DeleteNetAdapterId(&entry->Id); + PhClearReference(&entry->AdapterName); + + PhDeleteCircularBuffer_ULONG64(&entry->InboundBuffer); + PhDeleteCircularBuffer_ULONG64(&entry->OutboundBuffer); +} + +VOID NetAdaptersInitialize( + VOID + ) +{ + NetworkAdaptersList = PhCreateList(1); + NetAdapterEntryType = PhCreateObjectType(L"NetAdapterEntry", 0, AdapterEntryDeleteProcedure); +} + +VOID NetAdaptersUpdate( + VOID + ) +{ + static ULONG runCount = 0; // MUST keep in sync with runCount in process provider + + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + HANDLE deviceHandle = NULL; + PDV_NETADAPTER_ENTRY entry; + ULONG64 networkInOctets = 0; + ULONG64 networkOutOctets = 0; + ULONG64 networkRcvSpeed = 0; + ULONG64 networkXmitSpeed = 0; + NDIS_MEDIA_CONNECT_STATE mediaState = MediaConnectStateUnknown; + + entry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!entry) + continue; + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_NDIS)) + { + if (NT_SUCCESS(NetworkAdapterCreateHandle(&deviceHandle, entry->Id.InterfaceGuid))) + { + if (!entry->CheckedDeviceSupport) + { + // Check the network adapter supports the OIDs we're going to be using. + if (NetworkAdapterQuerySupported(deviceHandle)) + { + entry->DeviceSupported = TRUE; + } + + entry->CheckedDeviceSupport = TRUE; + } + + if (!entry->DeviceSupported) + { + // Device is faulty. Close the handle so we can fallback to GetIfEntry. + NtClose(deviceHandle); + deviceHandle = NULL; + } + } + } + + if (deviceHandle) + { + NDIS_STATISTICS_INFO interfaceStats; + NDIS_LINK_STATE interfaceState; + + memset(&interfaceStats, 0, sizeof(NDIS_STATISTICS_INFO)); + + NetworkAdapterQueryStatistics(deviceHandle, &interfaceStats); + + if (NT_SUCCESS(NetworkAdapterQueryLinkState(deviceHandle, &interfaceState))) + { + mediaState = interfaceState.MediaConnectState; + } + + if (!(interfaceStats.SupportedStatistics & NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV)) + networkInOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_RCV); + else + networkInOctets = interfaceStats.ifHCInOctets; + + if (!(interfaceStats.SupportedStatistics & NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT)) + networkOutOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_XMIT); + else + networkOutOctets = interfaceStats.ifHCOutOctets; + + networkRcvSpeed = networkInOctets - entry->LastInboundValue; + networkXmitSpeed = networkOutOctets - entry->LastOutboundValue; + + // HACK: Pull the Adapter name from the current query. + if (!entry->AdapterName) + { + entry->AdapterName = NetworkAdapterQueryName(deviceHandle, entry->Id.InterfaceGuid); + } + + entry->DevicePresent = TRUE; + + NtClose(deviceHandle); + } + else if (WindowsVersion >= WINDOWS_VISTA && GetIfEntry2) + { + MIB_IF_ROW2 interfaceRow; + + if (QueryInterfaceRowVista(&entry->Id, &interfaceRow)) + { + networkInOctets = interfaceRow.InOctets; + networkOutOctets = interfaceRow.OutOctets; + mediaState = interfaceRow.MediaConnectState; + networkRcvSpeed = networkInOctets - entry->LastInboundValue; + networkXmitSpeed = networkOutOctets - entry->LastOutboundValue; + + // HACK: Pull the Adapter name from the current query. + if (!entry->AdapterName && PhCountStringZ(interfaceRow.Description) > 0) + { + entry->AdapterName = PhCreateString(interfaceRow.Description); + } + + entry->DevicePresent = TRUE; + } + else + { + entry->DevicePresent = FALSE; + } + } + else + { + MIB_IFROW interfaceRow; + + if (QueryInterfaceRowXP(&entry->Id, &interfaceRow)) + { + networkInOctets = interfaceRow.dwInOctets; + networkOutOctets = interfaceRow.dwOutOctets; + mediaState = interfaceRow.dwOperStatus == IF_OPER_STATUS_OPERATIONAL ? MediaConnectStateConnected : MediaConnectStateDisconnected; + networkRcvSpeed = networkInOctets - entry->LastInboundValue; + networkXmitSpeed = networkOutOctets - entry->LastOutboundValue; + + // HACK: Pull the Adapter name from the current query. + if (!entry->AdapterName && strlen(interfaceRow.bDescr) > 0) + { + entry->AdapterName = PhConvertMultiByteToUtf16(interfaceRow.bDescr); + } + + entry->DevicePresent = TRUE; + } + else + { + entry->DevicePresent = FALSE; + } + } + + if (mediaState == MediaConnectStateUnknown) + { + // We don't want incorrect data when the adapter is disabled. + networkRcvSpeed = 0; + networkXmitSpeed = 0; + } + + if (!entry->HaveFirstSample) + { + // The first sample must be zero. + networkRcvSpeed = 0; + networkXmitSpeed = 0; + entry->HaveFirstSample = TRUE; + } + + if (runCount != 0) + { + PhAddItemCircularBuffer_ULONG64(&entry->InboundBuffer, networkRcvSpeed); + PhAddItemCircularBuffer_ULONG64(&entry->OutboundBuffer, networkXmitSpeed); + } + + //context->LinkSpeed = networkLinkSpeed; + entry->InboundValue = networkRcvSpeed; + entry->OutboundValue = networkXmitSpeed; + entry->LastInboundValue = networkInOctets; + entry->LastOutboundValue = networkOutOctets; + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + + runCount++; +} + +VOID InitializeNetAdapterId( + _Out_ PDV_NETADAPTER_ID Id, + _In_ NET_IFINDEX InterfaceIndex, + _In_ IF_LUID InterfaceLuid, + _In_ PPH_STRING InterfaceGuid + ) +{ + Id->InterfaceIndex = InterfaceIndex; + Id->InterfaceLuid = InterfaceLuid; + PhSetReference(&Id->InterfaceGuid, InterfaceGuid); +} + +VOID CopyNetAdapterId( + _Out_ PDV_NETADAPTER_ID Destination, + _In_ PDV_NETADAPTER_ID Source + ) +{ + InitializeNetAdapterId( + Destination, + Source->InterfaceIndex, + Source->InterfaceLuid, + Source->InterfaceGuid + ); +} + +VOID DeleteNetAdapterId( + _Inout_ PDV_NETADAPTER_ID Id + ) +{ + PhClearReference(&Id->InterfaceGuid); +} + +BOOLEAN EquivalentNetAdapterId( + _In_ PDV_NETADAPTER_ID Id1, + _In_ PDV_NETADAPTER_ID Id2 + ) +{ + if (WindowsVersion >= WINDOWS_VISTA) + { + if (Id1->InterfaceLuid.Value == Id2->InterfaceLuid.Value) + return TRUE; + } + else + { + if (Id1->InterfaceIndex == Id2->InterfaceIndex) + return TRUE; + } + + return FALSE; +} + +PDV_NETADAPTER_ENTRY CreateNetAdapterEntry( + _In_ PDV_NETADAPTER_ID Id + ) +{ + PDV_NETADAPTER_ENTRY entry; + + entry = PhCreateObject(sizeof(DV_NETADAPTER_ENTRY), NetAdapterEntryType); + memset(entry, 0, sizeof(DV_NETADAPTER_ENTRY)); + + CopyNetAdapterId(&entry->Id, Id); + + PhInitializeCircularBuffer_ULONG64(&entry->InboundBuffer, PhGetIntegerSetting(L"SampleCount")); + PhInitializeCircularBuffer_ULONG64(&entry->OutboundBuffer, PhGetIntegerSetting(L"SampleCount")); + + PhAcquireQueuedLockExclusive(&NetworkAdaptersListLock); + PhAddItemList(NetworkAdaptersList, entry); + PhReleaseQueuedLockExclusive(&NetworkAdaptersListLock); + + return entry; +} diff --git a/plugins/HardwareDevices/devices.h b/plugins/HardwareDevices/devices.h new file mode 100644 index 0000000..aa84e4d --- /dev/null +++ b/plugins/HardwareDevices/devices.h @@ -0,0 +1,773 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef _DEVICES_H_ +#define _DEVICES_H_ + +#define PLUGIN_NAME L"ProcessHacker.HardwareDevices" +#define SETTING_NAME_ENABLE_NDIS (PLUGIN_NAME L".EnableNDIS") +#define SETTING_NAME_INTERFACE_LIST (PLUGIN_NAME L".NetworkList") +#define SETTING_NAME_DISK_LIST (PLUGIN_NAME L".DiskList") +#ifdef _NV_GPU_BUILD +#define SETTING_NAME_ENABLE_GPU (PLUGIN_NAME L".EnableGpu") +#define SETTING_NAME_ENABLE_FAHRENHEIT (PLUGIN_NAME L".ShowFahrenheit") +#endif + +#define CINTERFACE +#define COBJMACROS +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "resource.h" + +#define WM_SHOWDIALOG (WM_APP + 1) +#define UPDATE_MSG (WM_APP + 2) + +extern PPH_PLUGIN PluginInstance; + +extern PPH_OBJECT_TYPE NetAdapterEntryType; +extern PPH_LIST NetworkAdaptersList; +extern PH_QUEUED_LOCK NetworkAdaptersListLock; + +extern PPH_OBJECT_TYPE DiskDriveEntryType; +extern PPH_LIST DiskDrivesList; +extern PH_QUEUED_LOCK DiskDrivesListLock; + +// main.c + +PPH_STRING TrimString( + _In_ PPH_STRING String + ); + +INT AddListViewGroup( + _In_ HWND ListViewHandle, + _In_ INT Index, + _In_ PWSTR Text + ); + +INT AddListViewItemGroupId( + _In_ HWND ListViewHandle, + _In_ INT GroupId, + _In_ INT Index, + _In_ PWSTR Text, + _In_opt_ PVOID Param + ); + +ULONG64 RegQueryUlong64( + _In_ HANDLE KeyHandle, + _In_ PWSTR ValueName + ); + +VOID ShowDeviceMenu( + _In_ HWND ParentWindow, + _In_ PPH_STRING DeviceInstance + ); + +// adapter.c + +typedef struct _DV_NETADAPTER_ID +{ + NET_IFINDEX InterfaceIndex; + IF_LUID InterfaceLuid; + PPH_STRING InterfaceGuid; +} DV_NETADAPTER_ID, *PDV_NETADAPTER_ID; + +typedef struct _DV_NETADAPTER_ENTRY +{ + DV_NETADAPTER_ID Id; + PPH_STRING AdapterName; + + union + { + BOOLEAN BitField; + struct + { + BOOLEAN UserReference : 1; + BOOLEAN HaveFirstSample : 1; + BOOLEAN CheckedDeviceSupport : 1; + BOOLEAN DeviceSupported : 1; + BOOLEAN DevicePresent : 1; + BOOLEAN Spare : 3; + }; + }; + + //ULONG64 LinkSpeed; + ULONG64 InboundValue; + ULONG64 OutboundValue; + ULONG64 LastInboundValue; + ULONG64 LastOutboundValue; + + PH_CIRCULAR_BUFFER_ULONG64 InboundBuffer; + PH_CIRCULAR_BUFFER_ULONG64 OutboundBuffer; +} DV_NETADAPTER_ENTRY, *PDV_NETADAPTER_ENTRY; + +typedef struct _DV_NETADAPTER_SYSINFO_CONTEXT +{ + PDV_NETADAPTER_ENTRY AdapterEntry; + PPH_STRING SectionName; + + HWND WindowHandle; + HWND PanelWindowHandle; + HWND GraphHandle; + + PPH_SYSINFO_SECTION SysinfoSection; + PH_GRAPH_STATE GraphState; + PH_LAYOUT_MANAGER LayoutManager; +} DV_NETADAPTER_SYSINFO_CONTEXT, *PDV_NETADAPTER_SYSINFO_CONTEXT; + +typedef struct _DV_NETADAPTER_DETAILS_CONTEXT +{ + PPH_STRING AdapterName; + DV_NETADAPTER_ID AdapterId; + + union + { + BOOLEAN BitField; + struct + { + BOOLEAN HaveFirstSample : 1; + BOOLEAN CheckedDeviceSupport : 1; + BOOLEAN DeviceSupported : 1; + BOOLEAN Spare : 5; + }; + }; + + HWND WindowHandle; + HWND ParentHandle; + HWND ListViewHandle; + + HANDLE NotifyHandle; + + PH_LAYOUT_MANAGER LayoutManager; + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + + ULONG64 LastDetailsInboundValue; + ULONG64 LastDetailsIOutboundValue; +} DV_NETADAPTER_DETAILS_CONTEXT, *PDV_NETADAPTER_DETAILS_CONTEXT; + +typedef struct _DV_NETADAPTER_CONTEXT +{ + HWND ListViewHandle; + //HIMAGELIST ImageList; + BOOLEAN OptionsChanged; + BOOLEAN EnumeratingAdapters; + BOOLEAN UseAlternateMethod; +} DV_NETADAPTER_CONTEXT, *PDV_NETADAPTER_CONTEXT; + +VOID NetAdaptersLoadList( + VOID + ); + +VOID ShowOptionsDialog( + _In_ HWND ParentHandle + ); + +// adapter.c + +VOID NetAdaptersInitialize( + VOID + ); + +VOID NetAdaptersUpdate( + VOID + ); + +VOID InitializeNetAdapterId( + _Out_ PDV_NETADAPTER_ID Id, + _In_ NET_IFINDEX InterfaceIndex, + _In_ IF_LUID InterfaceLuid, + _In_ PPH_STRING InterfaceGuid + ); + +VOID CopyNetAdapterId( + _Out_ PDV_NETADAPTER_ID Destination, + _In_ PDV_NETADAPTER_ID Source + ); + +VOID DeleteNetAdapterId( + _Inout_ PDV_NETADAPTER_ID Id + ); + +BOOLEAN EquivalentNetAdapterId( + _In_ PDV_NETADAPTER_ID Id1, + _In_ PDV_NETADAPTER_ID Id2 + ); + +PDV_NETADAPTER_ENTRY CreateNetAdapterEntry( + _In_ PDV_NETADAPTER_ID Id + ); + +// dialog.c + +typedef enum _NETADAPTER_DETAILS_CATEGORY +{ + NETADAPTER_DETAILS_CATEGORY_ADAPTER, + NETADAPTER_DETAILS_CATEGORY_UNICAST, + NETADAPTER_DETAILS_CATEGORY_BROADCAST, + NETADAPTER_DETAILS_CATEGORY_MULTICAST, + NETADAPTER_DETAILS_CATEGORY_ERRORS +} NETADAPTER_DETAILS_CATEGORY; + +typedef enum _NETADAPTER_DETAILS_INDEX +{ + NETADAPTER_DETAILS_INDEX_STATE, + //NETADAPTER_DETAILS_INDEX_CONNECTIVITY, + + NETADAPTER_DETAILS_INDEX_IPADDRESS, + NETADAPTER_DETAILS_INDEX_SUBNET, + NETADAPTER_DETAILS_INDEX_GATEWAY, + NETADAPTER_DETAILS_INDEX_DNS, + NETADAPTER_DETAILS_INDEX_DOMAIN, + + NETADAPTER_DETAILS_INDEX_LINKSPEED, + NETADAPTER_DETAILS_INDEX_SENT, + NETADAPTER_DETAILS_INDEX_RECEIVED, + NETADAPTER_DETAILS_INDEX_TOTAL, + NETADAPTER_DETAILS_INDEX_SENDING, + NETADAPTER_DETAILS_INDEX_RECEIVING, + NETADAPTER_DETAILS_INDEX_UTILIZATION, + + NETADAPTER_DETAILS_INDEX_UNICAST_SENTPKTS, + NETADAPTER_DETAILS_INDEX_UNICAST_RECVPKTS, + NETADAPTER_DETAILS_INDEX_UNICAST_TOTALPKTS, + NETADAPTER_DETAILS_INDEX_UNICAST_SENT, + NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVED, + NETADAPTER_DETAILS_INDEX_UNICAST_TOTAL, + //NETADAPTER_DETAILS_INDEX_UNICAST_SENDING, + //NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVING, + //NETADAPTER_DETAILS_INDEX_UNICAST_UTILIZATION, + + NETADAPTER_DETAILS_INDEX_BROADCAST_SENTPKTS, + NETADAPTER_DETAILS_INDEX_BROADCAST_RECVPKTS, + NETADAPTER_DETAILS_INDEX_BROADCAST_TOTALPKTS, + NETADAPTER_DETAILS_INDEX_BROADCAST_SENT, + NETADAPTER_DETAILS_INDEX_BROADCAST_RECEIVED, + NETADAPTER_DETAILS_INDEX_BROADCAST_TOTAL, + + NETADAPTER_DETAILS_INDEX_MULTICAST_SENTPKTS, + NETADAPTER_DETAILS_INDEX_MULTICAST_RECVPKTS, + NETADAPTER_DETAILS_INDEX_MULTICAST_TOTALPKTS, + NETADAPTER_DETAILS_INDEX_MULTICAST_SENT, + NETADAPTER_DETAILS_INDEX_MULTICAST_RECEIVED, + NETADAPTER_DETAILS_INDEX_MULTICAST_TOTAL, + + NETADAPTER_DETAILS_INDEX_ERRORS_SENTPKTS, + NETADAPTER_DETAILS_INDEX_ERRORS_RECVPKTS, + NETADAPTER_DETAILS_INDEX_ERRORS_TOTALPKTS, + NETADAPTER_DETAILS_INDEX_ERRORS_SENT, + NETADAPTER_DETAILS_INDEX_ERRORS_RECEIVED, + NETADAPTER_DETAILS_INDEX_ERRORS_TOTAL +} NETADAPTER_DETAILS_INDEX; + +VOID ShowNetAdapterDetailsDialog( + _In_ PDV_NETADAPTER_SYSINFO_CONTEXT Context + ); + +// ndis.c + +#define BITS_IN_ONE_BYTE 8 +#define NDIS_UNIT_OF_MEASUREMENT 100 + +// dmex: rev +typedef ULONG (WINAPI* _GetInterfaceDescriptionFromGuid)( + _Inout_ PGUID InterfaceGuid, + _Out_opt_ PWSTR InterfaceDescription, + _Inout_ PSIZE_T LengthAddress, + PVOID Unknown1, + PVOID Unknown2 + ); + +NTSTATUS NetworkAdapterCreateHandle( + _Out_ PHANDLE DeviceHandle, + _In_ PPH_STRING InterfaceGuid + ); + +BOOLEAN NetworkAdapterQuerySupported( + _In_ HANDLE DeviceHandle + ); + +BOOLEAN NetworkAdapterQueryNdisVersion( + _In_ HANDLE DeviceHandle, + _Out_opt_ PUINT MajorVersion, + _Out_opt_ PUINT MinorVersion + ); + +PPH_STRING NetworkAdapterQueryName( + _In_ HANDLE DeviceHandle, + _In_ PPH_STRING InterfaceGuid + ); + +NTSTATUS NetworkAdapterQueryStatistics( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_STATISTICS_INFO Info + ); + +NTSTATUS NetworkAdapterQueryLinkState( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_LINK_STATE State + ); + +BOOLEAN NetworkAdapterQueryMediaType( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_PHYSICAL_MEDIUM Medium + ); + +NTSTATUS NetworkAdapterQueryLinkSpeed( + _In_ HANDLE DeviceHandle, + _Out_ PULONG64 LinkSpeed + ); + +ULONG64 NetworkAdapterQueryValue( + _In_ HANDLE DeviceHandle, + _In_ NDIS_OID OpCode + ); + +BOOLEAN QueryInterfaceRowVista( + _In_ PDV_NETADAPTER_ID Id, + _Out_ PMIB_IF_ROW2 InterfaceRow + ); + +BOOLEAN QueryInterfaceRowXP( + _In_ PDV_NETADAPTER_ID Id, + _Out_ PMIB_IFROW InterfaceRow + ); + +// netoptions.c + +INT_PTR CALLBACK NetworkAdapterOptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// diskoptions.c + +INT_PTR CALLBACK DiskDriveOptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// disk.c + +typedef struct _DV_DISK_ID +{ + PPH_STRING DevicePath; +} DV_DISK_ID, *PDV_DISK_ID; + +typedef struct _DV_DISK_ENTRY +{ + DV_DISK_ID Id; + + PPH_STRING DiskName; + PPH_STRING DiskIndexName; + ULONG DiskIndex; + + union + { + BOOLEAN BitField; + struct + { + BOOLEAN UserReference : 1; + BOOLEAN HaveFirstSample : 1; + BOOLEAN DevicePresent : 1; + BOOLEAN Spare : 5; + }; + }; + + PH_CIRCULAR_BUFFER_ULONG64 ReadBuffer; + PH_CIRCULAR_BUFFER_ULONG64 WriteBuffer; + + PH_UINT64_DELTA BytesReadDelta; + PH_UINT64_DELTA BytesWrittenDelta; + PH_UINT64_DELTA ReadTimeDelta; + PH_UINT64_DELTA WriteTimeDelta; + PH_UINT64_DELTA IdleTimeDelta; + PH_UINT32_DELTA ReadCountDelta; + PH_UINT32_DELTA WriteCountDelta; + PH_UINT64_DELTA QueryTimeDelta; + + FLOAT ResponseTime; + FLOAT ActiveTime; + ULONG QueueDepth; + ULONG SplitCount; +} DV_DISK_ENTRY, *PDV_DISK_ENTRY; + +typedef struct _DV_DISK_SYSINFO_CONTEXT +{ + PDV_DISK_ENTRY DiskEntry; + PPH_STRING SectionName; + + HWND WindowHandle; + HWND PanelWindowHandle; + HWND GraphHandle; + + PPH_SYSINFO_SECTION SysinfoSection; + PH_GRAPH_STATE GraphState; + PH_LAYOUT_MANAGER LayoutManager; +} DV_DISK_SYSINFO_CONTEXT, *PDV_DISK_SYSINFO_CONTEXT; + +typedef struct _DV_DISK_OPTIONS_CONTEXT +{ + HWND ListViewHandle; + //HIMAGELIST ImageList; + BOOLEAN OptionsChanged; + BOOLEAN EnumeratingDisks; +} DV_DISK_OPTIONS_CONTEXT, *PDV_DISK_OPTIONS_CONTEXT; + +VOID DiskDrivesInitialize(VOID); +VOID DiskDrivesLoadList(VOID); +VOID DiskDrivesUpdate(VOID); + +VOID DiskDriveUpdateDeviceInfo( + _In_opt_ HANDLE DeviceHandle, + _In_ PDV_DISK_ENTRY DiskEntry + ); + +VOID InitializeDiskId( + _Out_ PDV_DISK_ID Id, + _In_ PPH_STRING DevicePath + ); +VOID CopyDiskId( + _Out_ PDV_DISK_ID Destination, + _In_ PDV_DISK_ID Source + ); +VOID DeleteDiskId( + _Inout_ PDV_DISK_ID Id + ); +BOOLEAN EquivalentDiskId( + _In_ PDV_DISK_ID Id1, + _In_ PDV_DISK_ID Id2 + ); +PDV_DISK_ENTRY CreateDiskEntry( + _In_ PDV_DISK_ID Id + ); + +// diskdetails.c + +VOID ShowDiskDriveDetailsDialog( + _In_ PDV_DISK_SYSINFO_CONTEXT Context + ); + +// disknotify.c + +VOID AddRemoveDeviceChangeCallback( + VOID + ); + +// storage.c + +NTSTATUS DiskDriveCreateHandle( + _Out_ PHANDLE DeviceHandle, + _In_ PPH_STRING DevicePath + ); + +PPH_STRING DiskDriveQueryDosMountPoints( + _In_ ULONG DeviceNumber + ); + +BOOLEAN DiskDriveQueryDeviceInformation( + _In_ HANDLE DeviceHandle, + _Out_opt_ PPH_STRING* DiskVendor, + _Out_opt_ PPH_STRING* DiskModel, + _Out_opt_ PPH_STRING* DiskRevision, + _Out_opt_ PPH_STRING* DiskSerial + ); + +NTSTATUS DiskDriveQueryDeviceTypeAndNumber( + _In_ HANDLE DeviceHandle, + _Out_opt_ PULONG DeviceNumber, + _Out_opt_ DEVICE_TYPE* DeviceType + ); + +NTSTATUS DiskDriveQueryStatistics( + _In_ HANDLE DeviceHandle, + _Out_ PDISK_PERFORMANCE Info + ); + +PPH_STRING DiskDriveQueryGeometry( + _In_ HANDLE DeviceHandle + ); + +BOOLEAN DiskDriveQueryImminentFailure( + _In_ HANDLE DeviceHandle, + _Out_ PPH_LIST* DiskSmartAttributes + ); + +typedef struct _DISK_HANDLE_ENTRY +{ + WCHAR DeviceLetter; + HANDLE DeviceHandle; +} DISK_HANDLE_ENTRY, *PDISK_HANDLE_ENTRY; + +PPH_LIST DiskDriveQueryMountPointHandles( + _In_ ULONG DeviceNumber + ); + +typedef struct _NTFS_FILESYSTEM_STATISTICS +{ + FILESYSTEM_STATISTICS FileSystemStatistics; + NTFS_STATISTICS NtfsStatistics; +} NTFS_FILESYSTEM_STATISTICS, *PNTFS_FILESYSTEM_STATISTICS; + +typedef struct _FAT_FILESYSTEM_STATISTICS +{ + FILESYSTEM_STATISTICS FileSystemStatistics; + NTFS_STATISTICS FatStatistics; +} FAT_FILESYSTEM_STATISTICS, *PFAT_FILESYSTEM_STATISTICS; + +typedef struct _EXFAT_FILESYSTEM_STATISTICS +{ + FILESYSTEM_STATISTICS FileSystemStatistics; + EXFAT_STATISTICS ExFatStatistics; +} EXFAT_FILESYSTEM_STATISTICS, *PEXFAT_FILESYSTEM_STATISTICS; + +BOOLEAN DiskDriveQueryFileSystemInfo( + _In_ HANDLE DeviceHandle, + _Out_ USHORT* FileSystemType, + _Out_ PVOID* FileSystemStatistics + ); + +typedef struct _NTFS_VOLUME_INFO +{ + NTFS_VOLUME_DATA_BUFFER VolumeData; + NTFS_EXTENDED_VOLUME_DATA ExtendedVolumeData; +} NTFS_VOLUME_INFO, *PNTFS_VOLUME_INFO; + +BOOLEAN DiskDriveQueryNtfsVolumeInfo( + _In_ HANDLE DosDeviceHandle, + _Out_ PNTFS_VOLUME_INFO VolumeInfo + ); + +BOOLEAN DiskDriveQueryRefsVolumeInfo( + _In_ HANDLE DosDeviceHandle, + _Out_ PREFS_VOLUME_DATA_BUFFER VolumeInfo + ); + +NTSTATUS DiskDriveQueryVolumeInformation( + _In_ HANDLE DosDeviceHandle, + _Out_ PFILE_FS_VOLUME_INFORMATION* VolumeInfo + ); + +NTSTATUS DiskDriveQueryVolumeAttributes( + _In_ HANDLE DosDeviceHandle, + _Out_ PFILE_FS_ATTRIBUTE_INFORMATION* AttributeInfo + ); + +typedef enum _SMART_ATTRIBUTE_ID +{ + SMART_ATTRIBUTE_ID_READ_ERROR_RATE = 0x01, + SMART_ATTRIBUTE_ID_THROUGHPUT_PERFORMANCE = 0x02, + SMART_ATTRIBUTE_ID_SPIN_UP_TIME = 0x03, + SMART_ATTRIBUTE_ID_START_STOP_COUNT = 0x04, + SMART_ATTRIBUTE_ID_REALLOCATED_SECTORS_COUNT = 0x05, + SMART_ATTRIBUTE_ID_READ_CHANNEL_MARGIN = 0x06, + SMART_ATTRIBUTE_ID_SEEK_ERROR_RATE = 0x07, + SMART_ATTRIBUTE_ID_SEEK_TIME_PERFORMANCE = 0x08, + SMART_ATTRIBUTE_ID_POWER_ON_HOURS = 0x09, + SMART_ATTRIBUTE_ID_SPIN_RETRY_COUNT = 0x0A, + SMART_ATTRIBUTE_ID_CALIBRATION_RETRY_COUNT = 0x0B, + SMART_ATTRIBUTE_ID_POWER_CYCLE_COUNT = 0x0C, + SMART_ATTRIBUTE_ID_SOFT_READ_ERROR_RATE = 0x0D, + // Unknown values 14-182 + SMART_ATTRIBUTE_ID_SATA_DOWNSHIFT_ERROR_COUNT = 0xB7, + SMART_ATTRIBUTE_ID_END_TO_END_ERROR = 0xB8, + SMART_ATTRIBUTE_ID_HEAD_STABILITY = 0xB9, + SMART_ATTRIBUTE_ID_INDUCED_OP_VIBRATION_DETECTION = 0xBA, + SMART_ATTRIBUTE_ID_REPORTED_UNCORRECTABLE_ERRORS = 0xBB, + SMART_ATTRIBUTE_ID_COMMAND_TIMEOUT = 0xBC, + SMART_ATTRIBUTE_ID_HIGH_FLY_WRITES = 0xBD, + SMART_ATTRIBUTE_ID_TEMPERATURE_DIFFERENCE_FROM_100 = 0xBE, // AirflowTemperature + SMART_ATTRIBUTE_ID_GSENSE_ERROR_RATE = 0xBF, + SMART_ATTRIBUTE_ID_POWER_OFF_RETRACT_COUNT = 0xC0, + SMART_ATTRIBUTE_ID_LOAD_CYCLE_COUNT = 0xC1, + SMART_ATTRIBUTE_ID_TEMPERATURE = 0xC2, + SMART_ATTRIBUTE_ID_HARDWARE_ECC_RECOVERED = 0xC3, + SMART_ATTRIBUTE_ID_REALLOCATION_EVENT_COUNT = 0xC4, + SMART_ATTRIBUTE_ID_CURRENT_PENDING_SECTOR_COUNT = 0xC5, + SMART_ATTRIBUTE_ID_UNCORRECTABLE_SECTOR_COUNT = 0xC6, + SMART_ATTRIBUTE_ID_ULTRADMA_CRC_ERROR_COUNT = 0xC7, + SMART_ATTRIBUTE_ID_MULTI_ZONE_ERROR_RATE = 0xC8, + SMART_ATTRIBUTE_ID_OFFTRACK_SOFT_READ_ERROR_RATE = 0xC9, + SMART_ATTRIBUTE_ID_DATA_ADDRESS_MARK_ERRORS = 0xCA, + SMART_ATTRIBUTE_ID_RUN_OUT_CANCEL = 0xCB, + SMART_ATTRIBUTE_ID_SOFT_ECC_CORRECTION = 0xCC, + SMART_ATTRIBUTE_ID_THERMAL_ASPERITY_RATE_TAR = 0xCD, + SMART_ATTRIBUTE_ID_FLYING_HEIGHT = 0xCE, + SMART_ATTRIBUTE_ID_SPIN_HIGH_CURRENT = 0xCF, + SMART_ATTRIBUTE_ID_SPIN_BUZZ = 0xD0, + SMART_ATTRIBUTE_ID_OFFLINE_SEEK_PERFORMANCE = 0xD1, + SMART_ATTRIBUTE_ID_VIBRATION_DURING_WRITE = 0xD3, + SMART_ATTRIBUTE_ID_SHOCK_DURING_WRITE = 0xD4, + SMART_ATTRIBUTE_ID_DISK_SHIFT = 0xDC, + SMART_ATTRIBUTE_ID_GSENSE_ERROR_RATE_ALT = 0xDD, + SMART_ATTRIBUTE_ID_LOADED_HOURS = 0xDE, + SMART_ATTRIBUTE_ID_LOAD_UNLOAD_RETRY_COUNT = 0xDF, + SMART_ATTRIBUTE_ID_LOAD_FRICTION = 0xE0, + SMART_ATTRIBUTE_ID_LOAD_UNLOAD_CYCLE_COUNT = 0xE1, + SMART_ATTRIBUTE_ID_LOAD_IN_TIME = 0xE2, + SMART_ATTRIBUTE_ID_TORQUE_AMPLIFICATION_COUNT = 0xE3, + SMART_ATTRIBUTE_ID_POWER_OFF_RETTRACT_CYCLE = 0xE4, + // Unknown values 229 + SMART_ATTRIBUTE_ID_GMR_HEAD_AMPLITUDE = 0xE6, + SMART_ATTRIBUTE_ID_DRIVE_TEMPERATURE = 0xE7, + // Unknown values 232-239 + SMART_ATTRIBUTE_ID_HEAD_FLYING_HOURS = 0xF0, + SMART_ATTRIBUTE_ID_TOTAL_LBA_WRITTEN = 0xF1, + SMART_ATTRIBUTE_ID_TOTAL_LBA_READ = 0xF2, + // Unknown values 243-249 + SMART_ATTRIBUTE_ID_READ_ERROR_RETY_RATE = 0xFA, + // Unknown values 251-253 + SMART_ATTRIBUTE_ID_FREE_FALL_PROTECTION = 0xFE, +} SMART_ATTRIBUTE_ID; + + +#define SMART_HEADER_SIZE 2 + +#include +typedef struct _SMART_ATTRIBUTE +{ + BYTE Id; + USHORT Flags; + BYTE CurrentValue; + BYTE WorstValue; + BYTE RawValue[6]; + BYTE Reserved; +} SMART_ATTRIBUTE, *PSMART_ATTRIBUTE; +#include + +typedef struct _SMART_ATTRIBUTES +{ + SMART_ATTRIBUTE_ID AttributeId; + UINT CurrentValue; + UINT WorstValue; + UINT RawValue; + + // Pre-fail/Advisory bit + // This bit is applicable only when the value of this attribute is less than or equal to its threshhold. + // 0 : Advisory: The device has exceeded its intended design life period. + // 1 : Pre-failure notification : Failure is predicted within 24 hours. + BOOLEAN Advisory; + BOOLEAN FailureImminent; + + // Online data collection bit + // 0 : This value of this attribute is only updated during offline activities. + // 1 : The value of this attribute is updated during both normal operation and offline activities. + BOOLEAN OnlineDataCollection; + + // TRUE: This attribute characterizes a performance aspect of the drive, + // degradation of which may indicate imminent drive failure, such as data throughput, seektimes, spin up time, etc. + BOOLEAN Performance; + + // TRUE: This attribute is based on the expected, non-fatal errors that are inherent in disk drives, + // increases in which may indicate imminent drive failure, such as ECC errors, seek errors, etc. + BOOLEAN ErrorRate; + + // TRUE: This attribute counts events, of which an excessive number of which may + // indicate imminent drive failure, such as number of re-allocated sectors, etc. + BOOLEAN EventCount; + + // TRUE: This type is used to specify an attribute that is collected and saved by the drive automatically. + BOOLEAN SelfPreserving; +} SMART_ATTRIBUTES, *PSMART_ATTRIBUTES; + +PWSTR SmartAttributeGetText( + _In_ SMART_ATTRIBUTE_ID AttributeId + ); + +PWSTR SmartAttributeGetDescription( + _In_ SMART_ATTRIBUTE_ID AttributeId + ); + +// diskgraph.c + +VOID DiskDriveSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers, + _In_ _Assume_refs_(1) PDV_DISK_ENTRY DiskEntry + ); + +// netgraph.c + +VOID NetAdapterSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers, + _In_ _Assume_refs_(1) PDV_NETADAPTER_ENTRY AdapterEntry + ); + +#ifdef _NV_GPU_BUILD +// Graphics +extern BOOLEAN NvApiInitialized; +extern ULONG GpuMemoryLimit; +extern FLOAT GpuCurrentGpuUsage; +extern FLOAT GpuCurrentCoreUsage; +extern FLOAT GpuCurrentBusUsage; +extern ULONG GpuCurrentMemUsage; +extern ULONG GpuCurrentMemSharedUsage; +extern ULONG GpuCurrentCoreTemp; +extern ULONG GpuCurrentBoardTemp; +extern ULONG GpuCurrentCoreClock; +extern ULONG GpuCurrentMemoryClock; +extern ULONG GpuCurrentShaderClock; +extern ULONG GpuCurrentVoltage; +extern PH_CIRCULAR_BUFFER_FLOAT GpuUtilizationHistory; +extern PH_CIRCULAR_BUFFER_ULONG GpuMemoryHistory; +extern PH_CIRCULAR_BUFFER_FLOAT GpuBoardHistory; +extern PH_CIRCULAR_BUFFER_FLOAT GpuBusHistory; + +VOID NvGpuSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ); + +VOID NvApiInitialize(VOID); +BOOLEAN DestroyNvApi(VOID); +PPH_STRING NvGpuQueryDriverVersion(VOID); +PPH_STRING NvGpuQueryVbiosVersionString(VOID); +PPH_STRING NvGpuQueryName(VOID); +PPH_STRING NvGpuQueryShortName(VOID); +PPH_STRING NvGpuQueryRevision(VOID); +PPH_STRING NvGpuQueryRamType(VOID); +PPH_STRING NvGpuQueryFoundry(VOID); +PPH_STRING NvGpuQueryDeviceId(VOID); +PPH_STRING NvGpuQueryRopsCount(VOID); +PPH_STRING NvGpuQueryShaderCount(VOID); +PPH_STRING NvGpuQueryPciInfo(VOID); +PPH_STRING NvGpuQueryBusWidth(VOID); +PPH_STRING NvGpuQueryPcbValue(VOID); +PPH_STRING NvGpuQueryDriverSettings(VOID); +PPH_STRING NvGpuQueryFanSpeed(VOID); +BOOLEAN NvGpuDriverIsWHQL(VOID); +VOID NvGpuUpdate(VOID); +#endif + +#endif _DEVICES_H_ \ No newline at end of file diff --git a/plugins/HardwareDevices/disk.c b/plugins/HardwareDevices/disk.c new file mode 100644 index 0000000..b0ac6f7 --- /dev/null +++ b/plugins/HardwareDevices/disk.c @@ -0,0 +1,303 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +VOID DiskEntryDeleteProcedure( + _In_ PVOID Object, + _In_ ULONG Flags + ) +{ + PDV_DISK_ENTRY entry = Object; + + PhAcquireQueuedLockExclusive(&DiskDrivesListLock); + PhRemoveItemList(DiskDrivesList, PhFindItemList(DiskDrivesList, entry)); + PhReleaseQueuedLockExclusive(&DiskDrivesListLock); + + DeleteDiskId(&entry->Id); + PhClearReference(&entry->DiskName); + + PhDeleteCircularBuffer_ULONG64(&entry->ReadBuffer); + PhDeleteCircularBuffer_ULONG64(&entry->WriteBuffer); + + AddRemoveDeviceChangeCallback(); +} + +VOID DiskDrivesInitialize( + VOID + ) +{ + DiskDrivesList = PhCreateList(1); + DiskDriveEntryType = PhCreateObjectType(L"DiskDriveEntry", 0, DiskEntryDeleteProcedure); +} + +VOID DiskDrivesUpdate( + VOID + ) +{ + static ULONG runCount = 0; // MUST keep in sync with runCount in process provider + + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + HANDLE deviceHandle; + PDV_DISK_ENTRY entry; + + entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + if (NT_SUCCESS(DiskDriveCreateHandle(&deviceHandle, entry->Id.DevicePath))) + { + DISK_PERFORMANCE diskPerformance; + + if (NT_SUCCESS(DiskDriveQueryStatistics(deviceHandle, &diskPerformance))) + { + ULONG64 readTime; + ULONG64 writeTime; + ULONG64 idleTime; + ULONG readCount; + ULONG writeCount; + ULONG64 queryTime; + + PhUpdateDelta(&entry->BytesReadDelta, diskPerformance.BytesRead.QuadPart); + PhUpdateDelta(&entry->BytesWrittenDelta, diskPerformance.BytesWritten.QuadPart); + PhUpdateDelta(&entry->ReadTimeDelta, diskPerformance.ReadTime.QuadPart); + PhUpdateDelta(&entry->WriteTimeDelta, diskPerformance.WriteTime.QuadPart); + PhUpdateDelta(&entry->IdleTimeDelta, diskPerformance.IdleTime.QuadPart); + PhUpdateDelta(&entry->ReadCountDelta, diskPerformance.ReadCount); + PhUpdateDelta(&entry->WriteCountDelta, diskPerformance.WriteCount); + PhUpdateDelta(&entry->QueryTimeDelta, diskPerformance.QueryTime.QuadPart); + + readTime = entry->ReadTimeDelta.Delta; + writeTime = entry->WriteTimeDelta.Delta; + idleTime = entry->IdleTimeDelta.Delta; + readCount = entry->ReadCountDelta.Delta; + writeCount = entry->WriteCountDelta.Delta; + queryTime = entry->QueryTimeDelta.Delta; + + if (readCount + writeCount != 0) + entry->ResponseTime = ((FLOAT)readTime + (FLOAT)writeTime) / (readCount + writeCount); + else + entry->ResponseTime = 0; + + if (queryTime != 0) + entry->ActiveTime = (FLOAT)(queryTime - idleTime) / queryTime * 100; + else + entry->ActiveTime = 0.0f; + + if (entry->ActiveTime > 100.f) + entry->ActiveTime = 0.f; + if (entry->ActiveTime < 0.f) + entry->ActiveTime = 0.f; + + entry->QueueDepth = diskPerformance.QueueDepth; + entry->SplitCount = diskPerformance.SplitCount; + entry->DiskIndex = diskPerformance.StorageDeviceNumber; + entry->DevicePresent = TRUE; + } + else + { + // Disk has been disconnected or dismounted. + PhInitializeDelta(&entry->BytesReadDelta); + PhInitializeDelta(&entry->BytesWrittenDelta); + PhInitializeDelta(&entry->ReadTimeDelta); + PhInitializeDelta(&entry->WriteTimeDelta); + PhInitializeDelta(&entry->IdleTimeDelta); + PhInitializeDelta(&entry->ReadCountDelta); + PhInitializeDelta(&entry->WriteCountDelta); + PhInitializeDelta(&entry->QueryTimeDelta); + + entry->ResponseTime = 0; + entry->ActiveTime = 0.0f; + entry->QueueDepth = 0; + entry->SplitCount = 0; + entry->DiskIndex = ULONG_MAX; + entry->DevicePresent = FALSE; + PhClearReference(&entry->DiskIndexName); + } + + if (runCount > 1) + { + // Delay the first query for the disk name, index and type. + // 1) This information is not needed until the user opens the sysinfo window. + // 2) Try not to query this information while opening the sysinfo window (e.g. delay). + // 3) Try not to query this information during startup (e.g. delay). + // + // Note: If the user opens the Sysinfo window before we query the disk info, + // we have a second check in diskgraph.c that queries the information on demand. + DiskDriveUpdateDeviceInfo(deviceHandle, entry); + } + + NtClose(deviceHandle); + } + else + { + // Disk has been disconnected or dismounted. + PhInitializeDelta(&entry->BytesReadDelta); + PhInitializeDelta(&entry->BytesWrittenDelta); + PhInitializeDelta(&entry->ReadTimeDelta); + PhInitializeDelta(&entry->WriteTimeDelta); + PhInitializeDelta(&entry->IdleTimeDelta); + PhInitializeDelta(&entry->ReadCountDelta); + PhInitializeDelta(&entry->WriteCountDelta); + PhInitializeDelta(&entry->QueryTimeDelta); + + entry->ResponseTime = 0; + entry->ActiveTime = 0.0f; + entry->QueueDepth = 0; + entry->SplitCount = 0; + entry->DiskIndex = ULONG_MAX; + entry->DevicePresent = FALSE; + PhClearReference(&entry->DiskIndexName); + } + + if (!entry->HaveFirstSample) + { + // The first sample must be zero. + entry->BytesReadDelta.Delta = 0; + entry->BytesWrittenDelta.Delta = 0; + entry->HaveFirstSample = TRUE; + } + + if (runCount != 0) + { + PhAddItemCircularBuffer_ULONG64(&entry->ReadBuffer, entry->BytesReadDelta.Delta); + PhAddItemCircularBuffer_ULONG64(&entry->WriteBuffer, entry->BytesWrittenDelta.Delta); + } + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + + runCount++; +} + +VOID DiskDriveUpdateDeviceInfo( + _In_opt_ HANDLE DeviceHandle, + _In_ PDV_DISK_ENTRY DiskEntry + ) +{ + if (!DiskEntry->DiskName || DiskEntry->DiskIndex == ULONG_MAX) + { + HANDLE deviceHandle = NULL; + + if (!DeviceHandle) + { + DiskDriveCreateHandle(&deviceHandle, DiskEntry->Id.DevicePath); + } + else + { + deviceHandle = DeviceHandle; + } + + if (deviceHandle) + { + if (!DiskEntry->DiskName) + { + PPH_STRING diskName = NULL; + + if (NT_SUCCESS(DiskDriveQueryDeviceInformation(deviceHandle, NULL, &diskName, NULL, NULL))) + { + DiskEntry->DiskName = diskName; + } + } + + if (DiskEntry->DiskIndex == ULONG_MAX) + { + ULONG diskIndex = ULONG_MAX; // Note: Do not initialize to zero. + + if (NT_SUCCESS(DiskDriveQueryDeviceTypeAndNumber(deviceHandle, &diskIndex, NULL))) + { + DiskEntry->DiskIndex = diskIndex; + } + } + + if (!DeviceHandle) + { + NtClose(deviceHandle); + } + } + } +} + +VOID InitializeDiskId( + _Out_ PDV_DISK_ID Id, + _In_ PPH_STRING DevicePath + ) +{ + PhSetReference(&Id->DevicePath, DevicePath); +} + +VOID CopyDiskId( + _Out_ PDV_DISK_ID Destination, + _In_ PDV_DISK_ID Source + ) +{ + InitializeDiskId( + Destination, + Source->DevicePath + ); +} + +VOID DeleteDiskId( + _Inout_ PDV_DISK_ID Id + ) +{ + PhClearReference(&Id->DevicePath); +} + +BOOLEAN EquivalentDiskId( + _In_ PDV_DISK_ID Id1, + _In_ PDV_DISK_ID Id2 + ) +{ + return PhEqualString(Id1->DevicePath, Id2->DevicePath, TRUE); +} + +PDV_DISK_ENTRY CreateDiskEntry( + _In_ PDV_DISK_ID Id + ) +{ + PDV_DISK_ENTRY entry; + + entry = PhCreateObject(sizeof(DV_DISK_ENTRY), DiskDriveEntryType); + memset(entry, 0, sizeof(DV_DISK_ENTRY)); + + entry->DiskIndex = ULONG_MAX; + CopyDiskId(&entry->Id, Id); + + PhInitializeCircularBuffer_ULONG64(&entry->ReadBuffer, PhGetIntegerSetting(L"SampleCount")); + PhInitializeCircularBuffer_ULONG64(&entry->WriteBuffer, PhGetIntegerSetting(L"SampleCount")); + + PhAcquireQueuedLockExclusive(&DiskDrivesListLock); + PhAddItemList(DiskDrivesList, entry); + PhReleaseQueuedLockExclusive(&DiskDrivesListLock); + + AddRemoveDeviceChangeCallback(); + + return entry; +} \ No newline at end of file diff --git a/plugins/HardwareDevices/diskdetails.c b/plugins/HardwareDevices/diskdetails.c new file mode 100644 index 0000000..355b2cd --- /dev/null +++ b/plugins/HardwareDevices/diskdetails.c @@ -0,0 +1,1092 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +typedef struct _COMMON_PAGE_CONTEXT +{ + HWND ParentHandle; + + PPH_STRING DiskName; + DV_DISK_ID DiskId; + ULONG DiskIndex; + + HANDLE DeviceHandle; +} COMMON_PAGE_CONTEXT, *PCOMMON_PAGE_CONTEXT; + +typedef struct _DV_DISK_PAGE_CONTEXT +{ + HWND WindowHandle; + HWND ListViewHandle; + + PH_LAYOUT_MANAGER LayoutManager; + + PCOMMON_PAGE_CONTEXT PageContext; +} DV_DISK_PAGE_CONTEXT, *PDV_DISK_PAGE_CONTEXT; + +typedef enum _DISKDRIVE_DETAILS_INDEX +{ + DISKDRIVE_DETAILS_INDEX_FS_CREATION_TIME, + DISKDRIVE_DETAILS_INDEX_SERIAL_NUMBER, + DISKDRIVE_DETAILS_INDEX_FILE_SYSTEM, + DISKDRIVE_DETAILS_INDEX_FS_VERSION, + DISKDRIVE_DETAILS_INDEX_LFS_VERSION, + + DISKDRIVE_DETAILS_INDEX_TOTAL_SIZE, + DISKDRIVE_DETAILS_INDEX_TOTAL_FREE, + DISKDRIVE_DETAILS_INDEX_TOTAL_SECTORS, + DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS, + DISKDRIVE_DETAILS_INDEX_FREE_CLUSTERS, + DISKDRIVE_DETAILS_INDEX_TOTAL_RESERVED, + DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_SECTOR, + DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_CLUSTER, + DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_RECORD, + DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS_PER_RECORD, + + DISKDRIVE_DETAILS_INDEX_MFT_RECORDS, + DISKDRIVE_DETAILS_INDEX_MFT_SIZE, + DISKDRIVE_DETAILS_INDEX_MFT_START, + DISKDRIVE_DETAILS_INDEX_MFT_ZONE, + DISKDRIVE_DETAILS_INDEX_MFT_ZONE_SIZE, + DISKDRIVE_DETAILS_INDEX_MFT_MIRROR_START, + + DISKDRIVE_DETAILS_INDEX_FILE_READS, + DISKDRIVE_DETAILS_INDEX_FILE_WRITES, + DISKDRIVE_DETAILS_INDEX_DISK_READS, + DISKDRIVE_DETAILS_INDEX_DISK_WRITES, + DISKDRIVE_DETAILS_INDEX_FILE_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_FILE_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_METADATA_READS, + DISKDRIVE_DETAILS_INDEX_METADATA_WRITES, + DISKDRIVE_DETAILS_INDEX_METADATA_DISK_READS, + DISKDRIVE_DETAILS_INDEX_METADATA_DISK_WRITES, + DISKDRIVE_DETAILS_INDEX_METADATA_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_METADATA_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_MFT_READS, + DISKDRIVE_DETAILS_INDEX_MFT_WRITES, + DISKDRIVE_DETAILS_INDEX_MFT_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_MFT_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READS, + DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITES, + DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_BITMAP_READS, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES, + DISKDRIVE_DETAILS_INDEX_BITMAP_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READS, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITES, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_USER_INDEX_READS, + DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITES, + DISKDRIVE_DETAILS_INDEX_USER_INDEX_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_LOGFILE_READS, + DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITES, + DISKDRIVE_DETAILS_INDEX_LOGFILE_READ_BYTES, + DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_WRITE, + DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_CREATE, + DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_SETINFO, + DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_FLUSH, + + DISKDRIVE_DETAILS_INDEX_MFT_WRITES_FLUSH_LOGFILE, + DISKDRIVE_DETAILS_INDEX_MFT_WRITES_LAZY_WRITER, + DISKDRIVE_DETAILS_INDEX_MFT_WRITES_USER_REQUEST, + + DISKDRIVE_DETAILS_INDEX_MFT2_WRITES, + DISKDRIVE_DETAILS_INDEX_MFT2_WRITE_BYTES, + + DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_WRITE, + DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_CREATE, + DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_SETINFO, + DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_FLUSH, + + DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_FLUSH_LOGFILE, + DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_LAZY_WRITER, + DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_USER_REQUEST, + + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_FLUSH_LOGFILE, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_LAZY_WRITER, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_USER_REQUEST, + + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_WRITE, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_CREATE, + DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_SETINFO, + + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_FLUSH_LOGFILE, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_LAZY_WRITER, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_REQUEST, + + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_WRITE, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_CREATE, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_SETINFO, + DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_FLUSH, + + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CALLS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CLUSTERS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_HINTS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_RUNS_RETURNED, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_HONORED, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_CLUSTERS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_CLUSTERS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS, + DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS_CLUSTERS, + + DISKDRIVE_DETAILS_INDEX_LOGFILE_EXCEPTIONS, + DISKDRIVE_DETAILS_INDEX_OTHER_EXCEPTIONS +} DISKDRIVE_DETAILS_INDEX; + +VOID DiskDriveAddListViewItemGroups( + _In_ HWND ListViewHandle, + _In_ INT DiskGroupId + ) +{ + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FS_CREATION_TIME, L"Volume creation time", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_SERIAL_NUMBER, L"Volume serial number", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FILE_SYSTEM, L"Volume file system", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FS_VERSION, L"Volume version", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LFS_VERSION, L"LFS version", NULL); + + //AddListViewItemGroupId(Context->ListViewHandle, diskGroupId, MAXINT, L"BytesPerPhysicalSector", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_SIZE, L"Total size", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_FREE, L"Total free", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_SECTORS, L"Total sectors", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS, L"Total clusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FREE_CLUSTERS, L"Free clusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_RESERVED, L"Reserved clusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_SECTOR, L"Bytes per sector", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_CLUSTER, L"Bytes per cluster", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_RECORD, L"Bytes per file record segment", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS_PER_RECORD, L"Clusters per File record segment", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_RECORDS, L"MFT records", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_SIZE, L"MFT size", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_START, L"MFT start", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_ZONE, L"MFT Zone clusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_ZONE_SIZE, L"MFT zone size", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_MIRROR_START, L"MFT mirror start", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FILE_READS, L"File reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FILE_WRITES, L"File writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_DISK_READS, L"Disk reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_DISK_WRITES, L"Disk writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FILE_READ_BYTES, L"File read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_FILE_WRITE_BYTES, L"File write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_READS, L"Metadata reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_WRITES, L"Metadata writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_DISK_READS, L"Metadata disk reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_DISK_WRITES, L"Metadata disk writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_READ_BYTES, L"Metadata read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_METADATA_WRITE_BYTES, L"Metadata write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_READS, L"Mft reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_WRITES, L"Mft writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_READ_BYTES, L"Mft read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_WRITE_BYTES, L"Mft write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READS, L"RootIndex reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITES, L"RootIndex writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READ_BYTES, L"RootIndex read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITE_BYTES, L"RootIndex write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_READS, L"Bitmap reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES, L"Bitmap writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_READ_BYTES, L"Bitmap read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITE_BYTES, L"Bitmap write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READS, L"Mft bitmap reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITES, L"Mft bitmap writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READ_BYTES, L"Mft bitmap read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITE_BYTES, L"Mft bitmap write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_USER_INDEX_READS, L"User Index reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITES, L"User Index writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_USER_INDEX_READ_BYTES, L"User Index read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITE_BYTES, L"User Index write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LOGFILE_READS, L"LogFile reads", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITES, L"LogFile writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LOGFILE_READ_BYTES, L"LogFile read bytes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITE_BYTES, L"LogFile write bytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_WRITE, L"MftWritesUserLevel-Write", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_CREATE, L"MftWritesUserLevel-Create", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_SETINFO, L"MftWritesUserLevel-SetInfo", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_FLUSH, L"MftWritesUserLevel-Flush", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_FLUSH_LOGFILE, L"MftWritesFlushForLogFileFull", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_LAZY_WRITER, L"MftWritesLazyWriter", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_USER_REQUEST, L"MftWritesUserRequest", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES, L"Mft2Writes", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_WRITE_BYTES, L"Mft2WriteBytes", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_WRITE, L"Mft2WritesUserLevel-Write", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_CREATE, L"Mft2WritesUserLevel-Create", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_SETINFO, L"Mft2WritesUserLevel-SetInfo", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_FLUSH, L"Mft2WritesUserLevel-Flush", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_FLUSH_LOGFILE, L"Mft2WritesFlushForLogFileFull", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_LAZY_WRITER, L"Mft2WritesLazyWriter", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_USER_REQUEST, L"Mft2WritesUserRequest", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_FLUSH_LOGFILE, L"BitmapWritesFlushForLogFileFull", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_LAZY_WRITER, L"BitmapWritesLazyWriter", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_USER_REQUEST, L"BitmapWritesUserRequest", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_WRITE, L"BitmapWritesUserLevel-Write", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_CREATE, L"BitmapWritesUserLevel-Create", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_SETINFO, L"BitmapWritesUserLevel-SetInfo", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_FLUSH_LOGFILE, L"MftBitmapWritesFlushForLogFileFull", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_LAZY_WRITER, L"MftBitmapWritesLazyWriter", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_REQUEST, L"MftBitmapWritesUserRequest", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_WRITE, L"MftBitmapWritesUserLevel-Write", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_CREATE, L"MftBitmapWritesUserLevel-Create", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_SETINFO, L"MftBitmapWritesUserLevel-SetInfo", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_FLUSH, L"MftBitmapWritesUserLevel-Flush", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CALLS, L"Allocate-Calls", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CLUSTERS, L"Allocate-Clusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HINTS, L"Allocate-Hints", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_RUNS_RETURNED, L"Allocate-RunsReturned", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_HONORED, L"Allocate-HintsHonored", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_CLUSTERS, L"Allocate-HintsClusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE, L"Allocate-Cache", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_CLUSTERS, L"Allocate-CacheClusters", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS, L"Allocate-CacheMiss", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS_CLUSTERS, L"Allocate-CacheMissClusters", NULL); + + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_LOGFILE_EXCEPTIONS, L"LogFileFullExceptions", NULL); + AddListViewItemGroupId(ListViewHandle, DiskGroupId, DISKDRIVE_DETAILS_INDEX_OTHER_EXCEPTIONS, L"OtherExceptions", NULL); +} + +VOID DiskDriveQueryFileSystem( + _Inout_ PDV_DISK_PAGE_CONTEXT Context + ) +{ + PPH_LIST deviceMountHandles; + + deviceMountHandles = DiskDriveQueryMountPointHandles(Context->PageContext->DiskIndex); + + for (ULONG i = 0; i < deviceMountHandles->Count; i++) + { + USHORT fsInfoType; + PVOID fsInfoBuffer; + INT diskGroupId = -1; + PFILE_FS_VOLUME_INFORMATION volumeInfo; + PDISK_HANDLE_ENTRY diskEntry; + + diskEntry = deviceMountHandles->Items[i]; + + if (NT_SUCCESS(DiskDriveQueryVolumeInformation(diskEntry->DeviceHandle, &volumeInfo))) + { + SYSTEMTIME systemTime; + + PhLargeIntegerToLocalSystemTime(&systemTime, &volumeInfo->VolumeCreationTime); + + if (volumeInfo->VolumeLabelLength > 0) + { + diskGroupId = AddListViewGroup(Context->ListViewHandle, i, + PhaFormatString(L"Volume %wc: [%s]", diskEntry->DeviceLetter, PhaCreateStringEx(volumeInfo->VolumeLabel, volumeInfo->VolumeLabelLength)->Buffer)->Buffer + ); + } + else + { + diskGroupId = AddListViewGroup(Context->ListViewHandle, i, + PhaFormatString(L"Volume %wc:", diskEntry->DeviceLetter)->Buffer + ); + } + + DiskDriveAddListViewItemGroups(Context->ListViewHandle, diskGroupId); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FS_CREATION_TIME, 1, PhaFormatDateTime(&systemTime)->Buffer); + + PhFree(volumeInfo); + } + else + { + diskGroupId = AddListViewGroup( + Context->ListViewHandle, + i, + PhaFormatString(L"Volume %wc:", diskEntry->DeviceLetter)->Buffer + ); + + DiskDriveAddListViewItemGroups(Context->ListViewHandle, diskGroupId); + } + + if (DiskDriveQueryFileSystemInfo(diskEntry->DeviceHandle, &fsInfoType, &fsInfoBuffer)) + { + switch (fsInfoType) + { + case FILESYSTEM_STATISTICS_TYPE_NTFS: + case FILESYSTEM_STATISTICS_TYPE_REFS: + { + PNTFS_FILESYSTEM_STATISTICS buffer = fsInfoBuffer; + + if (fsInfoType == FILESYSTEM_STATISTICS_TYPE_NTFS) + { + NTFS_VOLUME_INFO ntfsVolumeInfo; + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_SYSTEM, 1, L"NTFS"); + + if (DiskDriveQueryNtfsVolumeInfo(diskEntry->DeviceHandle, &ntfsVolumeInfo)) + { + ntfsVolumeInfo.VolumeData.VolumeSerialNumber.QuadPart = _byteswap_uint64(ntfsVolumeInfo.VolumeData.VolumeSerialNumber.QuadPart); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_SERIAL_NUMBER, 1, + PhaFormatString(L"0x%s", PH_AUTO_T(PH_STRING, PhBufferToHexString((PUCHAR)&ntfsVolumeInfo.VolumeData.VolumeSerialNumber.QuadPart, sizeof(ntfsVolumeInfo.VolumeData.VolumeSerialNumber.QuadPart)))->Buffer)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FS_VERSION, 1, + PhaFormatString(L"%lu.%lu", ntfsVolumeInfo.ExtendedVolumeData.MajorVersion, ntfsVolumeInfo.ExtendedVolumeData.MinorVersion)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LFS_VERSION, 1, + PhaFormatString(L"%lu.%lu", ntfsVolumeInfo.ExtendedVolumeData.LfsMajorVersion, ntfsVolumeInfo.ExtendedVolumeData.LfsMinorVersion)->Buffer + ); + + //PhSetListViewSubItem(Context->ListViewHandle, lvItemIndex, 1, + // PhaFormatSize(ntfsVolumeInfo.ExtendedVolumeData.BytesPerPhysicalSector, -1)->Buffer + // ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_SIZE, 1, + PhaFormatSize(ntfsVolumeInfo.VolumeData.NumberSectors.QuadPart * ntfsVolumeInfo.VolumeData.BytesPerSector, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_FREE, 1, + PhaFormatString(L"%s (%.2f%%)", PhaFormatSize(ntfsVolumeInfo.VolumeData.FreeClusters.QuadPart * ntfsVolumeInfo.VolumeData.BytesPerCluster, -1)->Buffer, (FLOAT)(ntfsVolumeInfo.VolumeData.FreeClusters.QuadPart * 100) / ntfsVolumeInfo.VolumeData.TotalClusters.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_SECTORS, 1, + PhaFormatUInt64(ntfsVolumeInfo.VolumeData.NumberSectors.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS, 1, + PhaFormatUInt64(ntfsVolumeInfo.VolumeData.TotalClusters.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FREE_CLUSTERS, 1, + PhaFormatUInt64(ntfsVolumeInfo.VolumeData.FreeClusters.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_RESERVED, 1, + PhaFormatUInt64(ntfsVolumeInfo.VolumeData.TotalReserved.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_SECTOR, 1, + PhaFormatString(L"%lu", ntfsVolumeInfo.VolumeData.BytesPerSector)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_CLUSTER, 1, + PhaFormatString(L"%lu", ntfsVolumeInfo.VolumeData.BytesPerCluster)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_RECORD, 1, + PhaFormatString(L"%lu", ntfsVolumeInfo.VolumeData.BytesPerFileRecordSegment)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS_PER_RECORD, 1, + PhaFormatString(L"%lu", ntfsVolumeInfo.VolumeData.ClustersPerFileRecordSegment)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_RECORDS, 1, + PhaFormatUInt64(ntfsVolumeInfo.VolumeData.MftValidDataLength.QuadPart / ntfsVolumeInfo.VolumeData.BytesPerFileRecordSegment, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_SIZE, 1, + PhaFormatString(L"%s (%.2f%%)", PhaFormatSize(ntfsVolumeInfo.VolumeData.MftValidDataLength.QuadPart, -1)->Buffer, (FLOAT)(ntfsVolumeInfo.VolumeData.MftValidDataLength.QuadPart * 100 / ntfsVolumeInfo.VolumeData.BytesPerCluster) / ntfsVolumeInfo.VolumeData.TotalClusters.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_START, 1, + PhaFormatString(L"%I64u", ntfsVolumeInfo.VolumeData.MftStartLcn.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_ZONE, 1, + PhaFormatString(L"%I64u - %I64u", ntfsVolumeInfo.VolumeData.MftZoneStart.QuadPart, ntfsVolumeInfo.VolumeData.MftZoneEnd.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_ZONE_SIZE, 1, + PhaFormatString(L"%s (%.2f%%)", PhaFormatSize((ntfsVolumeInfo.VolumeData.MftZoneEnd.QuadPart - ntfsVolumeInfo.VolumeData.MftZoneStart.QuadPart) * ntfsVolumeInfo.VolumeData.BytesPerCluster, -1)->Buffer, (FLOAT)(ntfsVolumeInfo.VolumeData.MftZoneEnd.QuadPart - ntfsVolumeInfo.VolumeData.MftZoneStart.QuadPart) * 100 / ntfsVolumeInfo.VolumeData.TotalClusters.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_MIRROR_START, 1, + PhaFormatString(L"%I64u", ntfsVolumeInfo.VolumeData.Mft2StartLcn.QuadPart)->Buffer + ); + } + } + else + { + REFS_VOLUME_DATA_BUFFER refsVolumeInfo; + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_SYSTEM, 1, L"ReFS"); + + if (DiskDriveQueryRefsVolumeInfo(diskEntry->DeviceHandle, &refsVolumeInfo)) + { + refsVolumeInfo.VolumeSerialNumber.QuadPart = _byteswap_uint64(refsVolumeInfo.VolumeSerialNumber.QuadPart); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_SERIAL_NUMBER, 1, + PhaFormatString(L"0x%s", PH_AUTO_T(PH_STRING, PhBufferToHexString((PUCHAR)&refsVolumeInfo.VolumeSerialNumber.QuadPart, sizeof(LONGLONG)))->Buffer)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FS_VERSION, 1, + PhaFormatString(L"%lu.%lu", refsVolumeInfo.MajorVersion, refsVolumeInfo.MinorVersion)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_SIZE, 1, + PhaFormatSize(refsVolumeInfo.NumberSectors.QuadPart * refsVolumeInfo.BytesPerSector, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_FREE, 1, + PhaFormatString(L"%s (%.2f%%)", PhaFormatSize(refsVolumeInfo.FreeClusters.QuadPart * refsVolumeInfo.BytesPerCluster, -1)->Buffer, (FLOAT)(refsVolumeInfo.FreeClusters.QuadPart * 100) / refsVolumeInfo.TotalClusters.QuadPart)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_SECTORS, 1, + PhaFormatUInt64(refsVolumeInfo.NumberSectors.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_CLUSTERS, 1, + PhaFormatUInt64(refsVolumeInfo.TotalClusters.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FREE_CLUSTERS, 1, + PhaFormatUInt64(refsVolumeInfo.FreeClusters.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_RESERVED, 1, + PhaFormatUInt64(refsVolumeInfo.TotalReserved.QuadPart, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_SECTOR, 1, + PhaFormatUInt64(refsVolumeInfo.BytesPerSector, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_TOTAL_BYTES_PER_CLUSTER, 1, + PhaFormatUInt64(refsVolumeInfo.BytesPerCluster, TRUE)->Buffer + ); + } + } + + // File System + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_READS, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.UserFileReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_WRITES, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.UserFileWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_DISK_READS, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.UserDiskReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_DISK_WRITES, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.UserDiskWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_READ_BYTES, 1, + PhaFormatSize(buffer->FileSystemStatistics.UserFileReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_FILE_WRITE_BYTES, 1, + PhaFormatSize(buffer->FileSystemStatistics.UserFileWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_READS, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.MetaDataReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_WRITES, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.MetaDataWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_DISK_READS, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.MetaDataDiskReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_DISK_WRITES, 1, + PhaFormatUInt64(buffer->FileSystemStatistics.MetaDataDiskWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_READ_BYTES, 1, + PhaFormatSize(buffer->FileSystemStatistics.MetaDataReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_METADATA_WRITE_BYTES, 1, + PhaFormatSize(buffer->FileSystemStatistics.MetaDataWriteBytes, -1)->Buffer + ); + + // NTFS specific + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.MftReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.MftWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.RootIndexReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.RootIndexWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.RootIndexReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ROOT_INDEX_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.RootIndexWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.BitmapReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.BitmapWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.MftBitmapReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.MftBitmapWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_USER_INDEX_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.UserIndexReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.UserIndexWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_USER_INDEX_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.UserIndexReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_USER_INDEX_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.UserIndexWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LOGFILE_READS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.LogFileReads, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.LogFileWrites, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LOGFILE_READ_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.LogFileReadBytes, -1)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LOGFILE_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.LogFileWriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_WRITE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesUserLevel.Write, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_CREATE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesUserLevel.Create, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_SETINFO, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesUserLevel.SetInfo, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_USER_LEVEL_FLUSH, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesUserLevel.Flush, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_FLUSH_LOGFILE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesFlushForLogFileFull, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_LAZY_WRITER, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesLazyWriter, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_WRITES_USER_REQUEST, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftWritesUserRequest, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2Writes, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_WRITE_BYTES, 1, + PhaFormatSize(buffer->NtfsStatistics.Mft2WriteBytes, -1)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_WRITE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesUserLevel.Write, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_CREATE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesUserLevel.Create, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_SETINFO, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesUserLevel.SetInfo, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_USER_LEVEL_FLUSH, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesUserLevel.Flush, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_FLUSH_LOGFILE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesFlushForLogFileFull, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_LAZY_WRITER, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesLazyWriter, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT2_WRITES_USER_REQUEST, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Mft2WritesUserRequest, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_FLUSH_LOGFILE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesFlushForLogFileFull, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_LAZY_WRITER, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesLazyWriter, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_USER_REQUEST, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesUserRequest, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_WRITE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesUserLevel.Write, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_CREATE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesUserLevel.Create, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_BITMAP_WRITES_SETINFO, 1, + PhaFormatUInt64(buffer->NtfsStatistics.BitmapWritesUserLevel.SetInfo, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_FLUSH_LOGFILE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesFlushForLogFileFull, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_LAZY_WRITER, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesLazyWriter, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_REQUEST, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesUserRequest, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_WRITE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesUserLevel.Write, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_CREATE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesUserLevel.Create, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_SETINFO, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesUserLevel.SetInfo, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_MFT_BITMAP_USER_LEVEL_FLUSH, 1, + PhaFormatUInt64(buffer->NtfsStatistics.MftBitmapWritesUserLevel.Flush, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CALLS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.Calls, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CLUSTERS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.Clusters, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HINTS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.Hints, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_RUNS_RETURNED, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.RunsReturned, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_HONORED, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.HintsHonored, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_HITS_CLUSTERS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.HintsClusters, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.Cache, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_CLUSTERS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.CacheClusters, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.CacheMiss, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_ALLOCATE_CACHE_MISS_CLUSTERS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.Allocate.CacheMissClusters, TRUE)->Buffer + ); + + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_LOGFILE_EXCEPTIONS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.LogFileFullExceptions, TRUE)->Buffer + ); + PhSetListViewSubItem(Context->ListViewHandle, DISKDRIVE_DETAILS_INDEX_OTHER_EXCEPTIONS, 1, + PhaFormatUInt64(buffer->NtfsStatistics.OtherExceptions, TRUE)->Buffer + ); + + // TODO: Additions for Windows 8.1 and Windows 10... + } + break; + case FILESYSTEM_STATISTICS_TYPE_FAT: + { + //PFAT_FILESYSTEM_STATISTICS buffer = fsInfoBuffer; + } + break; + case FILESYSTEM_STATISTICS_TYPE_EXFAT: + { + //PEXFAT_FILESYSTEM_STATISTICS buffer = fsInfoBuffer; + } + break; + } + + PhFree(fsInfoBuffer); + } + + NtClose(diskEntry->DeviceHandle); + PhFree(diskEntry); + } + + PhDereferenceObject(deviceMountHandles); +} + +VOID DiskDriveQuerySmart( + _Inout_ PDV_DISK_PAGE_CONTEXT Context + ) +{ + PPH_LIST attributes; + + if (DiskDriveQueryImminentFailure(Context->PageContext->DeviceHandle, &attributes)) + { + for (ULONG i = 0; i < attributes->Count; i++) + { + PSMART_ATTRIBUTES attribute = attributes->Items[i]; + + INT lvItemIndex = PhAddListViewItem( + Context->ListViewHandle, + MAXINT, + SmartAttributeGetText(attribute->AttributeId), + IntToPtr(attribute->AttributeId) + ); + + PhSetListViewSubItem( + Context->ListViewHandle, + lvItemIndex, + 1, + PhaFormatString(L"%lu", attribute->CurrentValue)->Buffer + ); + PhSetListViewSubItem( + Context->ListViewHandle, + lvItemIndex, + 2, + PhaFormatString(L"%lu", attribute->WorstValue)->Buffer + ); + + if (attribute->RawValue) + { + PhSetListViewSubItem( + Context->ListViewHandle, + lvItemIndex, + 3, + PhaFormatString(L"%lu", attribute->RawValue)->Buffer + ); + } + + PhFree(attribute); + } + + PhDereferenceObject(attributes); + } +} + +//COLORREF NTAPI PhpColorItemColorFunction( +// _In_ INT Index, +// _In_ PVOID Param, +// _In_opt_ PVOID Context +// ) +//{ +// PSMART_ATTRIBUTES item = Param; +// +// if (item->FailureImminent) +// return RGB(255, 119, 0);// RGB(255, 60, 40); +// +// return RGB(0xFF, 0xFF, 0xFF); +//} + +INT_PTR CALLBACK DiskDriveSmartDetailsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_DISK_PAGE_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + + context = PhAllocate(sizeof(DV_DISK_PAGE_CONTEXT)); + memset(context, 0, sizeof(DV_DISK_PAGE_CONTEXT)); + + context->PageContext = (PCOMMON_PAGE_CONTEXT)propSheetPage->lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_DISK_PAGE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + context->WindowHandle = hwndDlg; + context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DETAILS_LIST); + + PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); + PhSetControlTheme(context->ListViewHandle, L"explorer"); + PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 240, L"Property"); + PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 50, L"Value"); + PhAddListViewColumn(context->ListViewHandle, 2, 2, 2, LVCFMT_LEFT, 50, L"Best"); + PhAddListViewColumn(context->ListViewHandle, 3, 3, 3, LVCFMT_LEFT, 80, L"Raw"); + PhSetExtendedListView(context->ListViewHandle); + //ExtendedListView_SetItemColorFunction(context->ListViewHandle, PhpColorItemColorFunction); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, context->ListViewHandle, NULL, PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_DESCRIPTION), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_EDIT1), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT); + + DiskDriveQuerySmart(context); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + if (header->code == LVN_ITEMCHANGED) + { + PWSTR description; + + if (ListView_GetSelectedCount(context->ListViewHandle) == 1) + description = SmartAttributeGetDescription((SMART_ATTRIBUTE_ID)PhGetSelectedListViewItemParam(context->ListViewHandle)); + else + description = L""; + + SetDlgItemText(hwndDlg, IDC_EDIT1, description); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK DiskDriveFileSystemDetailsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_DISK_PAGE_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + + context = PhAllocate(sizeof(DV_DISK_PAGE_CONTEXT)); + memset(context, 0, sizeof(DV_DISK_PAGE_CONTEXT)); + + context->PageContext = (PCOMMON_PAGE_CONTEXT)propSheetPage->lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_DISK_PAGE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + context->WindowHandle = hwndDlg; + context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DETAILS_LIST); + + PhCenterWindow(GetParent(hwndDlg), context->PageContext->ParentHandle); + + PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); + PhSetControlTheme(context->ListViewHandle, L"explorer"); + PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 290, L"Property"); + PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 130, L"Value"); + PhSetExtendedListView(context->ListViewHandle); + ListView_EnableGroupView(context->ListViewHandle, TRUE); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, context->ListViewHandle, NULL, PH_ANCHOR_ALL); + + DiskDriveQueryFileSystem(context); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + } + + return FALSE; +} + +VOID FreeDiskDriveDetailsContext( + _In_ PCOMMON_PAGE_CONTEXT Context + ) +{ + if (Context->DeviceHandle) + NtClose(Context->DeviceHandle); + + DeleteDiskId(&Context->DiskId); + PhClearReference(&Context->DiskName); + PhFree(Context); +} + +NTSTATUS ShowDiskDriveDetailsDialogThread( + _In_ PVOID Parameter + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[2]; + PCOMMON_PAGE_CONTEXT pageContext = Parameter; + HANDLE deviceHandle; + + if (NT_SUCCESS(DiskDriveCreateHandle(&deviceHandle, pageContext->DiskId.DevicePath))) + { + pageContext->DeviceHandle = deviceHandle; + } + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP; + propSheetHeader.pszCaption = L"Disk Drive"; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // General + //memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + //propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + //propSheetPage.hInstance = PluginInstance->DllBase; + //propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DISKDRIVE_DETAILS_GENERAL); + //propSheetPage.pfnDlgProc = DiskDriveDetailsDlgProc; + //pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // FileSystem + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DISKDRIVE_DETAILS_FILESYSTEM); + propSheetPage.pfnDlgProc = DiskDriveFileSystemDetailsDlgProc; + propSheetPage.lParam = (LPARAM)pageContext; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // SMART + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DISKDRIVE_DETAILS_SMART); + propSheetPage.pfnDlgProc = DiskDriveSmartDetailsDlgProc; + propSheetPage.lParam = (LPARAM)pageContext; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + PhModalPropertySheet(&propSheetHeader); + + FreeDiskDriveDetailsContext(pageContext); + return STATUS_SUCCESS; +} + +VOID ShowDiskDriveDetailsDialog( + _In_ PDV_DISK_SYSINFO_CONTEXT Context + ) +{ + HANDLE dialogThread = NULL; + PCOMMON_PAGE_CONTEXT pageContext; + + pageContext = PhAllocate(sizeof(COMMON_PAGE_CONTEXT)); + memset(pageContext, 0, sizeof(COMMON_PAGE_CONTEXT)); + + pageContext->ParentHandle = GetParent(GetParent(Context->WindowHandle)); + pageContext->DiskIndex = Context->DiskEntry->DiskIndex; + //pageContext->Length = Context->DiskEntry->DiskLength; + + PhSetReference(&pageContext->DiskName, Context->DiskEntry->DiskName); + CopyDiskId(&pageContext->DiskId, &Context->DiskEntry->Id); + + if (dialogThread = PhCreateThread(0, ShowDiskDriveDetailsDialogThread, pageContext)) + NtClose(dialogThread); + else + FreeDiskDriveDetailsContext(pageContext); +} \ No newline at end of file diff --git a/plugins/HardwareDevices/diskgraph.c b/plugins/HardwareDevices/diskgraph.c new file mode 100644 index 0000000..2881c4c --- /dev/null +++ b/plugins/HardwareDevices/diskgraph.c @@ -0,0 +1,504 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +VOID DiskDriveUpdateGraphs( + _Inout_ PDV_DISK_SYSINFO_CONTEXT Context + ) +{ + Context->GraphState.Valid = FALSE; + Context->GraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->GraphHandle, 1); + Graph_Draw(Context->GraphHandle); + Graph_UpdateTooltip(Context->GraphHandle); + InvalidateRect(Context->GraphHandle, NULL, FALSE); +} + +VOID DiskDriveUpdatePanel( + _Inout_ PDV_DISK_SYSINFO_CONTEXT Context + ) +{ + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BREAD, PhaFormatSize(Context->DiskEntry->BytesReadDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BWRITE, PhaFormatSize(Context->DiskEntry->BytesWrittenDelta.Value, -1)->Buffer); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BTOTAL, PhaFormatSize(Context->DiskEntry->BytesReadDelta.Value + Context->DiskEntry->BytesWrittenDelta.Value, -1)->Buffer); + + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_ACTIVE, + PhaFormatString(L"%.0f%%", Context->DiskEntry->ActiveTime)->Buffer + ); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_RESPONSETIME, + PhaFormatString(L"%.1f ms", Context->DiskEntry->ResponseTime / PH_TICKS_PER_MS)->Buffer + ); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_QUEUELENGTH, + PhaFormatString(L"%lu", Context->DiskEntry->QueueDepth)->Buffer + ); +} + +VOID UpdateDiskDriveDialog( + _Inout_ PDV_DISK_SYSINFO_CONTEXT Context + ) +{ + if (Context->DiskEntry->DiskName) + SetDlgItemText(Context->WindowHandle, IDC_DISKNAME, Context->DiskEntry->DiskName->Buffer); + else + SetDlgItemText(Context->WindowHandle, IDC_DISKNAME, L"Unknown disk"); + + if (Context->DiskEntry->DiskIndexName) + SetDlgItemText(Context->WindowHandle, IDC_DISKMOUNTPATH, Context->DiskEntry->DiskIndexName->Buffer); + else + SetDlgItemText(Context->WindowHandle, IDC_DISKMOUNTPATH, L"Unknown disk"); + + DiskDriveUpdateGraphs(Context); + DiskDriveUpdatePanel(Context); +} + +VOID UpdateDiskIndexText( + _Inout_ PDV_DISK_SYSINFO_CONTEXT Context + ) +{ + // If our delayed lookup of the disk name, index and type hasn't fired then query the information now. + DiskDriveUpdateDeviceInfo(NULL, Context->DiskEntry); + + // TODO: Move into DiskDriveUpdateDeviceInfo. + if (Context->DiskEntry->DiskIndex != ULONG_MAX && !Context->DiskEntry->DiskIndexName) + { + // Query the disk DosDevices mount points. + PPH_STRING diskMountPoints = PH_AUTO_T(PH_STRING, DiskDriveQueryDosMountPoints(Context->DiskEntry->DiskIndex)); + + if (!PhIsNullOrEmptyString(diskMountPoints)) + { + PhMoveReference(&Context->DiskEntry->DiskIndexName, PhFormatString( + L"Disk %lu (%s)", + Context->DiskEntry->DiskIndex, + diskMountPoints->Buffer + )); + } + else + { + PhMoveReference(&Context->DiskEntry->DiskIndexName, PhFormatString( + L"Disk %lu", + Context->DiskEntry->DiskIndex + )); + } + } +} + +INT_PTR CALLBACK DiskDrivePanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_DISK_SYSINFO_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_DISK_SYSINFO_CONTEXT)lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_DISK_SYSINFO_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_DETAILS: + ShowDiskDriveDetailsDialog(context); + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK DiskDriveDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_DISK_SYSINFO_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_DISK_SYSINFO_CONTEXT)lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_DISK_SYSINFO_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + PhDeleteLayoutManager(&context->LayoutManager); + PhDeleteGraphState(&context->GraphState); + + if (context->GraphHandle) + DestroyWindow(context->GraphHandle); + + if (context->PanelWindowHandle) + DestroyWindow(context->PanelWindowHandle); + + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + context->WindowHandle = hwndDlg; + + PhInitializeGraphState(&context->GraphState); + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_DISKMOUNTPATH), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_DISKNAME), NULL, PH_ANCHOR_RIGHT | PH_ANCHOR_TOP | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + panelItem = PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_DISKMOUNTPATH), WM_SETFONT, (WPARAM)context->SysinfoSection->Parameters->LargeFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_DISKNAME), WM_SETFONT, (WPARAM)context->SysinfoSection->Parameters->MediumFont, FALSE); + + if (context->DiskEntry->DiskIndexName) + SetDlgItemText(hwndDlg, IDC_DISKMOUNTPATH, context->DiskEntry->DiskIndexName->Buffer); + else + SetDlgItemText(hwndDlg, IDC_DISKMOUNTPATH, L"Unknown disk"); + + if (context->DiskEntry->DiskName) + SetDlgItemText(hwndDlg, IDC_DISKNAME, context->DiskEntry->DiskName->Buffer); + else + SetDlgItemText(hwndDlg, IDC_DISKNAME, L"Unknown disk"); + + context->PanelWindowHandle = CreateDialogParam(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_DISKDRIVE_PANEL), hwndDlg, DiskDrivePanelDialogProc, (LPARAM)context); + ShowWindow(context->PanelWindowHandle, SW_SHOW); + PhAddLayoutItemEx(&context->LayoutManager, context->PanelWindowHandle, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + // Create the graph control. + context->GraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(context->GraphHandle, TRUE); + + PhAddLayoutItemEx(&context->LayoutManager, context->GraphHandle, NULL, PH_ANCHOR_ALL, graphItem->Margin); + + UpdateDiskDriveDialog(context); + } + break; + case WM_SIZE: + PhLayoutManagerLayout(&context->LayoutManager); + break; + case WM_NOTIFY: + { + NMHDR* header = (NMHDR*)lParam; + + if (header->hwndFrom == context->GraphHandle) + { + switch (header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + context->SysinfoSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + + PhGraphStateGetDrawInfo( + &context->GraphState, + getDrawInfo, + context->DiskEntry->ReadBuffer.Count + ); + + if (!context->GraphState.Valid) + { + FLOAT max = 1024 * 1024; // minimum scaling of 1 MB. + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + context->GraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskEntry->ReadBuffer, i); + context->GraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskEntry->WriteBuffer, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + context->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + + // Scale the data. + PhDivideSinglesBySingle( + context->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + context->GraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (context->GraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 diskReadValue = PhGetItemCircularBuffer_ULONG64( + &context->DiskEntry->ReadBuffer, + getTooltipText->Index + ); + + ULONG64 diskWriteValue = PhGetItemCircularBuffer_ULONG64( + &context->DiskEntry->WriteBuffer, + getTooltipText->Index + ); + + PhMoveReference(&context->GraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\n%s", + PhaFormatSize(diskReadValue, -1)->Buffer, + PhaFormatSize(diskWriteValue, -1)->Buffer, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = context->GraphState.TooltipText->sr; + } + } + break; + } + } + } + break; + case UPDATE_MSG: + { + UpdateDiskDriveDialog(context); + } + break; + } + + return FALSE; +} + +BOOLEAN DiskDriveSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + PDV_DISK_SYSINFO_CONTEXT context = (PDV_DISK_SYSINFO_CONTEXT)Section->Context; + + switch (Message) + { + case SysInfoCreate: + { + UpdateDiskIndexText(context); + } + return TRUE; + case SysInfoDestroy: + { + PhDereferenceObject(context->DiskEntry); + PhDereferenceObject(context->SectionName); + PhFree(context); + } + return TRUE; + case SysInfoTick: + { + UpdateDiskIndexText(context); + + if (context->WindowHandle) + PostMessage(context->WindowHandle, UPDATE_MSG, 0, 0); + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = (PPH_SYSINFO_CREATE_DIALOG)Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_DISKDRIVE_DIALOG); + createDialog->DialogProc = DiskDriveDialogProc; + createDialog->Parameter = context; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, context->DiskEntry->ReadBuffer.Count); + + if (!Section->GraphState.Valid) + { + FLOAT max = 1024 * 1024; // minimum scaling of 1 MB. + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + Section->GraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskEntry->ReadBuffer, i); + Section->GraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->DiskEntry->WriteBuffer, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + Section->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + + // Scale the data. + PhDivideSinglesBySingle( + Section->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = (PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT)Parameter1; + + ULONG64 diskReadValue = PhGetItemCircularBuffer_ULONG64( + &context->DiskEntry->ReadBuffer, + getTooltipText->Index + ); + + ULONG64 diskWriteValue = PhGetItemCircularBuffer_ULONG64( + &context->DiskEntry->WriteBuffer, + getTooltipText->Index + ); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\n%s", + PhaFormatSize(diskReadValue, -1)->Buffer, + PhaFormatSize(diskWriteValue, -1)->Buffer, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = (PPH_SYSINFO_DRAW_PANEL)Parameter1; + + PhSetReference(&drawPanel->Title, context->DiskEntry->DiskIndexName); + drawPanel->SubTitle = PhFormatString( + L"R: %s\nW: %s", + PhaFormatSize(context->DiskEntry->BytesReadDelta.Delta, -1)->Buffer, + PhaFormatSize(context->DiskEntry->BytesWrittenDelta.Delta, -1)->Buffer + ); + + if (!drawPanel->Title) + drawPanel->Title = PhCreateString(L"Unknown disk"); + } + return TRUE; + } + + return FALSE; +} + +VOID DiskDriveSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers, + _In_ _Assume_refs_(1) PDV_DISK_ENTRY DiskEntry + ) +{ + PH_SYSINFO_SECTION section; + PDV_DISK_SYSINFO_CONTEXT context; + + context = (PDV_DISK_SYSINFO_CONTEXT)PhAllocate(sizeof(DV_DISK_SYSINFO_CONTEXT)); + memset(context, 0, sizeof(DV_DISK_SYSINFO_CONTEXT)); + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + + context->DiskEntry = DiskEntry; + context->SectionName = PhConcatStrings2(L"Disk ", DiskEntry->Id.DevicePath->Buffer); + + section.Context = context; + section.Callback = DiskDriveSectionCallback; + section.Name = context->SectionName->sr; + + context->SysinfoSection = Pointers->CreateSection(§ion); +} \ No newline at end of file diff --git a/plugins/HardwareDevices/disknotify.c b/plugins/HardwareDevices/disknotify.c new file mode 100644 index 0000000..e48f3af --- /dev/null +++ b/plugins/HardwareDevices/disknotify.c @@ -0,0 +1,119 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" +#include + +static BOOLEAN SubclassActive = FALSE; + +LRESULT CALLBACK MainWndDevicesSubclassProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ ULONG_PTR dwRefData + ) +{ + // Subclassing the main window just to process drive letter notifications + // is bad and I don't know of any other way to achieve this. + // The IOCTL_MOUNTMGR_CHANGE_NOTIFY callback would have been preferred but + // doesn't work from non-elevated processes. + + switch (uMsg) + { + case WM_DEVICECHANGE: + { + switch (wParam) + { + case DBT_DEVICEARRIVAL: // Drive letter added + case DBT_DEVICEREMOVECOMPLETE: // Drive letter removed + { + DEV_BROADCAST_HDR* deviceBroadcast = (DEV_BROADCAST_HDR*)lParam; + + if (deviceBroadcast->dbch_devicetype == DBT_DEVTYP_VOLUME) + { + PDEV_BROADCAST_VOLUME deviceVolume = (PDEV_BROADCAST_VOLUME)deviceBroadcast; + + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + PDV_DISK_ENTRY entry; + + entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + // Reset the DiskIndex so we can re-query the index on the next interval update. + entry->DiskIndex = ULONG_MAX; + // Reset the DiskIndexName so we can re-query the name on the next interval update. + PhClearReference(&entry->DiskIndexName); + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + } + } + } + + goto DefaultWndProc; + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); + +DefaultWndProc: + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +VOID AddRemoveDeviceChangeCallback( + VOID + ) +{ + // We get called during the plugin LoadCallback, don't do anything. + if (!PhMainWndHandle) + return; + + // Add the subclass only when disks are being monitored, remove when no longer needed. + if (DiskDrivesList->Count != 0) + { + if (!SubclassActive) + { + // We have a disk device, subclass the main window to detect drive letter changes. + SetWindowSubclass(PhMainWndHandle, MainWndDevicesSubclassProc, 0, 0); + SubclassActive = TRUE; + } + } + else + { + if (SubclassActive) + { + // The user has removed the last disk device, remove the subclass. + RemoveWindowSubclass(PhMainWndHandle, MainWndDevicesSubclassProc, 0); + SubclassActive = FALSE; + } + } +} \ No newline at end of file diff --git a/plugins/HardwareDevices/diskoptions.c b/plugins/HardwareDevices/diskoptions.c new file mode 100644 index 0000000..850a3b0 --- /dev/null +++ b/plugins/HardwareDevices/diskoptions.c @@ -0,0 +1,695 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" +#include + +#define ITEM_CHECKED (INDEXTOSTATEIMAGEMASK(2)) +#define ITEM_UNCHECKED (INDEXTOSTATEIMAGEMASK(1)) + +typedef struct _DISK_ENUM_ENTRY +{ + ULONG DeviceIndex; + BOOLEAN DevicePresent; + PPH_STRING DevicePath; + PPH_STRING DeviceName; + PPH_STRING DeviceMountPoints; +} DISK_ENUM_ENTRY, *PDISK_ENUM_ENTRY; + +static int __cdecl DiskEntryCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PDISK_ENUM_ENTRY entry1 = *(PDISK_ENUM_ENTRY *)elem1; + PDISK_ENUM_ENTRY entry2 = *(PDISK_ENUM_ENTRY *)elem2; + + return uint64cmp(entry1->DeviceIndex, entry2->DeviceIndex); +} + +VOID DiskDrivesLoadList( + VOID + ) +{ + PPH_STRING settingsString; + PH_STRINGREF remaining; + + settingsString = PhaGetStringSetting(SETTING_NAME_DISK_LIST); + remaining = settingsString->sr; + + while (remaining.Length != 0) + { + PH_STRINGREF part; + DV_DISK_ID id; + PDV_DISK_ENTRY entry; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, ',', &part, &remaining); + + InitializeDiskId(&id, PhCreateString2(&part)); + entry = CreateDiskEntry(&id); + DeleteDiskId(&id); + + entry->UserReference = TRUE; + } +} + +VOID DiskDrivesSaveList( + VOID + ) +{ + PH_STRING_BUILDER stringBuilder; + PPH_STRING settingsString; + + PhInitializeStringBuilder(&stringBuilder, 260); + + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + if (entry->UserReference) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%s,", + entry->Id.DevicePath->Buffer // This value is SAFE and does not change. + ); + } + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); + PhSetStringSetting2(SETTING_NAME_DISK_LIST, &settingsString->sr); +} + +BOOLEAN FindDiskEntry( + _In_ PDV_DISK_ID Id, + _In_ BOOLEAN RemoveUserReference + ) +{ + BOOLEAN found = FALSE; + + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + PDV_DISK_ENTRY currentEntry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!currentEntry) + continue; + + found = EquivalentDiskId(¤tEntry->Id, Id); + + if (found) + { + if (RemoveUserReference) + { + if (currentEntry->UserReference) + { + PhDereferenceObjectDeferDelete(currentEntry); + currentEntry->UserReference = FALSE; + } + } + + PhDereferenceObjectDeferDelete(currentEntry); + + break; + } + else + { + PhDereferenceObjectDeferDelete(currentEntry); + } + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + + return found; +} + +VOID AddDiskDriveToListView( + _In_ PDV_DISK_OPTIONS_CONTEXT Context, + _In_ BOOLEAN DiskPresent, + _In_ PPH_STRING DiskPath, + _In_ PPH_STRING DiskName + ) +{ + DV_DISK_ID adapterId; + INT lvItemIndex; + BOOLEAN found = FALSE; + PDV_DISK_ID newId = NULL; + + InitializeDiskId(&adapterId, DiskPath); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + if (EquivalentDiskId(&entry->Id, &adapterId)) + { + newId = PhAllocate(sizeof(DV_DISK_ID)); + CopyDiskId(newId, &entry->Id); + + if (entry->UserReference) + found = TRUE; + } + + PhDereferenceObjectDeferDelete(entry); + + if (newId) + break; + } + + if (!newId) + { + newId = PhAllocate(sizeof(DV_DISK_ID)); + CopyDiskId(newId, &adapterId); + PhMoveReference(&newId->DevicePath, DiskPath); + } + + lvItemIndex = AddListViewItemGroupId( + Context->ListViewHandle, + DiskPresent ? 0 : 1, + MAXINT, + DiskName->Buffer, + newId + ); + + if (found) + ListView_SetItemState(Context->ListViewHandle, lvItemIndex, ITEM_CHECKED, LVIS_STATEIMAGEMASK); + + DeleteDiskId(&adapterId); +} + +VOID FreeListViewDiskDriveEntries( + _In_ PDV_DISK_OPTIONS_CONTEXT Context + ) +{ + ULONG index = -1; + + while ((index = PhFindListViewItemByFlags( + Context->ListViewHandle, + index, + LVNI_ALL + )) != -1) + { + PDV_DISK_ID param; + + if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) + { + DeleteDiskId(param); + PhFree(param); + } + } +} + +VOID FindDiskDrives( + _In_ PDV_DISK_OPTIONS_CONTEXT Context + ) +{ + PPH_LIST deviceList; + HDEVINFO deviceInfoHandle; + SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) }; + SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetail; + ULONG deviceInfoLength = 0; + + if ((deviceInfoHandle = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_DISK, + NULL, + NULL, + DIGCF_DEVICEINTERFACE + )) == INVALID_HANDLE_VALUE) + { + return; + } + + deviceList = PH_AUTO(PhCreateList(1)); + + for (ULONG i = 0; SetupDiEnumDeviceInterfaces(deviceInfoHandle, NULL, &GUID_DEVINTERFACE_DISK, i, &deviceInterfaceData); i++) + { + if (SetupDiGetDeviceInterfaceDetail( + deviceInfoHandle, + &deviceInterfaceData, + 0, + 0, + &deviceInfoLength, + &deviceInfoData + ) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + continue; + } + + deviceInterfaceDetail = PhAllocate(deviceInfoLength); + deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail( + deviceInfoHandle, + &deviceInterfaceData, + deviceInterfaceDetail, + deviceInfoLength, + &deviceInfoLength, + &deviceInfoData + )) + { + HANDLE deviceHandle; + PDISK_ENUM_ENTRY diskEntry; + WCHAR diskFriendlyName[MAX_PATH] = L""; + + // This crashes on XP with error 0xC06D007F + //SetupDiGetDeviceProperty( + // deviceInfoHandle, + // &deviceInfoData, + // &DEVPKEY_Device_FriendlyName, + // &devicePropertyType, + // (PBYTE)diskFriendlyName, + // ARRAYSIZE(diskFriendlyName), + // NULL, + // 0 + // ); + + if (!SetupDiGetDeviceRegistryProperty( + deviceInfoHandle, + &deviceInfoData, + SPDRP_FRIENDLYNAME, + NULL, + (PBYTE)diskFriendlyName, + ARRAYSIZE(diskFriendlyName), + NULL + )) + { + continue; + } + + diskEntry = PhAllocate(sizeof(DISK_ENUM_ENTRY)); + memset(diskEntry, 0, sizeof(DISK_ENUM_ENTRY)); + + diskEntry->DeviceIndex = ULONG_MAX; // Note: Do not initialize to zero. + diskEntry->DeviceName = PhCreateString(diskFriendlyName); + diskEntry->DevicePath = PhCreateString(deviceInterfaceDetail->DevicePath); + + if (NT_SUCCESS(DiskDriveCreateHandle( + &deviceHandle, + diskEntry->DevicePath + ))) + { + ULONG diskIndex = ULONG_MAX; // Note: Do not initialize to zero + + if (NT_SUCCESS(DiskDriveQueryDeviceTypeAndNumber( + deviceHandle, + &diskIndex, + NULL + ))) + { + PPH_STRING diskMountPoints = PH_AUTO_T(PH_STRING, DiskDriveQueryDosMountPoints(diskIndex)); + + diskEntry->DeviceIndex = diskIndex; + diskEntry->DevicePresent = TRUE; + + if (!PhIsNullOrEmptyString(diskMountPoints)) + { + diskEntry->DeviceMountPoints = PhFormatString( + L"Disk %lu (%s) [%s]", + diskIndex, + diskMountPoints->Buffer, + diskFriendlyName + ); + } + else + { + diskEntry->DeviceMountPoints = PhFormatString( + L"Disk %lu [%s]", + diskIndex, + diskFriendlyName + ); + } + } + + NtClose(deviceHandle); + } + + PhAddItemList(deviceList, diskEntry); + } + + PhFree(deviceInterfaceDetail); + } + + SetupDiDestroyDeviceInfoList(deviceInfoHandle); + + // Sort the entries + qsort(deviceList->Items, deviceList->Count, sizeof(PVOID), DiskEntryCompareFunction); + + Context->EnumeratingDisks = TRUE; + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < deviceList->Count; i++) + { + PDISK_ENUM_ENTRY entry = deviceList->Items[i]; + + AddDiskDriveToListView( + Context, + entry->DevicePresent, + entry->DevicePath, + entry->DeviceMountPoints ? entry->DeviceMountPoints : entry->DeviceName + ); + + if (entry->DeviceMountPoints) + PhDereferenceObject(entry->DeviceMountPoints); + if (entry->DeviceName) + PhDereferenceObject(entry->DeviceName); + // Note: DevicePath is disposed by WM_DESTROY. + + PhFree(entry); + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + Context->EnumeratingDisks = FALSE; + + + // HACK: Show all unknown devices. + Context->EnumeratingDisks = TRUE; + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + ULONG index = -1; + BOOLEAN found = FALSE; + PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + while ((index = PhFindListViewItemByFlags( + Context->ListViewHandle, + index, + LVNI_ALL + )) != -1) + { + PDV_DISK_ID param; + + if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) + { + if (EquivalentDiskId(param, &entry->Id)) + { + found = TRUE; + } + } + } + + if (!found) + { + PPH_STRING description; + + if (description = PhCreateString(L"Unknown disk")) + { + AddDiskDriveToListView( + Context, + FALSE, + entry->Id.DevicePath, + description + ); + + PhDereferenceObject(description); + } + } + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + Context->EnumeratingDisks = FALSE; +} + +PPH_STRING FindDiskDeviceInstance( + _In_ PPH_STRING DevicePath + ) +{ + PPH_STRING deviceIdString = NULL; + HDEVINFO deviceInfoHandle; + SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) }; + SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetail; + ULONG deviceInfoLength = 0; + + if ((deviceInfoHandle = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_DISK, + NULL, + NULL, + DIGCF_DEVICEINTERFACE + )) == INVALID_HANDLE_VALUE) + { + return NULL; + } + + for (ULONG i = 0; SetupDiEnumDeviceInterfaces(deviceInfoHandle, NULL, &GUID_DEVINTERFACE_DISK, i, &deviceInterfaceData); i++) + { + if (SetupDiGetDeviceInterfaceDetail( + deviceInfoHandle, + &deviceInterfaceData, + 0, + 0, + &deviceInfoLength, + &deviceInfoData + ) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + continue; + } + + deviceInterfaceDetail = PhAllocate(deviceInfoLength); + deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail( + deviceInfoHandle, + &deviceInterfaceData, + deviceInterfaceDetail, + deviceInfoLength, + &deviceInfoLength, + &deviceInfoData + )) + { + if (PhEqualStringZ(deviceInterfaceDetail->DevicePath, DevicePath->Buffer, TRUE)) + { + deviceIdString = PhCreateStringEx(NULL, 0x100); + + SetupDiGetDeviceInstanceId( + deviceInfoHandle, + &deviceInfoData, + deviceIdString->Buffer, + (ULONG)deviceIdString->Length, + NULL + ); + + PhTrimToNullTerminatorString(deviceIdString); + } + } + + PhFree(deviceInterfaceDetail); + } + + SetupDiDestroyDeviceInfoList(deviceInfoHandle); + + return deviceIdString; +} + +//VOID LoadDiskDriveImages( +// _In_ PDV_DISK_OPTIONS_CONTEXT Context +// ) +//{ +// HICON smallIcon = NULL; +// +// Context->ImageList = ImageList_Create( +// GetSystemMetrics(SM_CXSMICON), +// GetSystemMetrics(SM_CYSMICON), +// ILC_COLOR32, +// 1, +// 1 +// ); +// +// // We could use SetupDiLoadClassIcon but this works. +// // Copied from HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e967-e325-11ce-bfc1-08002be10318}\\IconPath +// // The index is only valid on Vista and above. +// ExtractIconEx( +// L"%SystemRoot%\\system32\\imageres.dll", +// -32, +// NULL, +// &smallIcon, +// 1 +// ); +// +// if (smallIcon) +// { +// ImageList_AddIcon(Context->ImageList, smallIcon); +// DestroyIcon(smallIcon); +// +// // Set the imagelist only if the image was loaded. +// ListView_SetImageList( +// Context->ListViewHandle, +// Context->ImageList, +// LVSIL_SMALL +// ); +// } +//} + +INT_PTR CALLBACK DiskDriveOptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_DISK_OPTIONS_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_DISK_OPTIONS_CONTEXT)PhAllocate(sizeof(DV_DISK_OPTIONS_CONTEXT)); + memset(context, 0, sizeof(DV_DISK_OPTIONS_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_DISK_OPTIONS_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + if (context->OptionsChanged) + DiskDrivesSaveList(); + + FreeListViewDiskDriveEntries(context); + + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + // Center the property sheet. + PhCenterWindow(GetParent(hwndDlg), GetParent(GetParent(hwndDlg))); + // Hide the OK button. + ShowWindow(GetDlgItem(GetParent(hwndDlg), IDOK), SW_HIDE); + // Set the Cancel button text. + Button_SetText(GetDlgItem(GetParent(hwndDlg), IDCANCEL), L"Close"); + + context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DISKDRIVE_LISTVIEW); + PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); + ListView_SetExtendedListViewStyleEx(context->ListViewHandle, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); + PhSetControlTheme(context->ListViewHandle, L"explorer"); + PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 350, L"Disk Drives"); + PhSetExtendedListView(context->ListViewHandle); + + if (WindowsVersion >= WINDOWS_VISTA) + { + ListView_EnableGroupView(context->ListViewHandle, TRUE); + AddListViewGroup(context->ListViewHandle, 0, L"Connected"); + AddListViewGroup(context->ListViewHandle, 1, L"Disconnected"); + } + + FindDiskDrives(context); + + context->OptionsChanged = FALSE; + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + if (header->code == LVN_ITEMCHANGED) + { + LPNM_LISTVIEW listView = (LPNM_LISTVIEW)lParam; + + if (context->EnumeratingDisks) + break; + + if (listView->uChanged & LVIF_STATE) + { + switch (listView->uNewState & LVIS_STATEIMAGEMASK) + { + case 0x2000: // checked + { + PDV_DISK_ID param = (PDV_DISK_ID)listView->lParam; + + if (!FindDiskEntry(param, FALSE)) + { + PDV_DISK_ENTRY entry; + + entry = CreateDiskEntry(param); + entry->UserReference = TRUE; + } + + context->OptionsChanged = TRUE; + } + break; + case 0x1000: // unchecked + { + PDV_DISK_ID param = (PDV_DISK_ID)listView->lParam; + + FindDiskEntry(param, TRUE); + + context->OptionsChanged = TRUE; + } + break; + } + } + } + else if (header->code == NM_RCLICK) + { + PDV_DISK_ID param; + PPH_STRING deviceInstance; + + if (param = PhGetSelectedListViewItemParam(context->ListViewHandle)) + { + if (deviceInstance = FindDiskDeviceInstance(param->DevicePath)) + { + ShowDeviceMenu(hwndDlg, deviceInstance); + PhDereferenceObject(deviceInstance); + } + } + } + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/HardwareDevices/gpugraph.c b/plugins/HardwareDevices/gpugraph.c new file mode 100644 index 0000000..9dd2722 --- /dev/null +++ b/plugins/HardwareDevices/gpugraph.c @@ -0,0 +1,793 @@ +/* + * Process Hacker Extra Plugins - + * Nvidia GPU Plugin + * + * Copyright (C) 2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +#ifdef _NV_GPU_BUILD + +#define ET_GPU_PADDING 3 +static RECT NormalGraphTextMargin = { 5, 5, 5, 5 }; +static RECT NormalGraphTextPadding = { 3, 3, 3, 3 }; + +static PPH_STRING GpuName; +static HWND WindowHandle; +static HWND DetailsHandle; +static PPH_SYSINFO_SECTION Section; +static PH_LAYOUT_MANAGER LayoutManager; + +static RECT GpuGraphMargin; +static HWND GpuPanel; +static HWND GpuLabelHandle; +static HWND MemLabelHandle; +static HWND SharedLabelHandle; +static HWND BusLabelHandle; +static HWND GpuGraphHandle; +static HWND MemGraphHandle; +static HWND SharedGraphHandle; +static HWND BusGraphHandle; + +static PH_GRAPH_STATE GpuGraphState; +static PH_GRAPH_STATE MemGraphState; +static PH_GRAPH_STATE SharedGraphState; +static PH_GRAPH_STATE BusGraphState; + +PH_CIRCULAR_BUFFER_FLOAT GpuUtilizationHistory; +PH_CIRCULAR_BUFFER_ULONG GpuMemoryHistory; +PH_CIRCULAR_BUFFER_FLOAT GpuBoardHistory; +PH_CIRCULAR_BUFFER_FLOAT GpuBusHistory; + +INT_PTR CALLBACK NvGpuPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + return FALSE; +} + +VOID NvGpuCreateGraphs( + VOID + ) +{ + GpuGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(GpuGraphHandle, TRUE); + + MemGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(MemGraphHandle, TRUE); + + SharedGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(SharedGraphHandle, TRUE); + + BusGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + WindowHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(BusGraphHandle, TRUE); +} + +VOID NvGpuLayoutGraphs( + VOID + ) +{ + RECT clientRect; + RECT labelRect; + ULONG graphWidth; + ULONG graphHeight; + HDWP deferHandle; + ULONG y; + + PhLayoutManagerLayout(&LayoutManager); + + GetClientRect(WindowHandle, &clientRect); + GetClientRect(GpuLabelHandle, &labelRect); + + graphWidth = clientRect.right - GpuGraphMargin.left - GpuGraphMargin.right; + graphHeight = (clientRect.bottom - GpuGraphMargin.top - GpuGraphMargin.bottom - labelRect.bottom * 4 - ET_GPU_PADDING * 5) / 4; + + deferHandle = BeginDeferWindowPos(8); + y = GpuGraphMargin.top; + + deferHandle = DeferWindowPos( + deferHandle, + GpuLabelHandle, + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + GpuGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + MemLabelHandle, + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + MemGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + SharedLabelHandle, + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + SharedGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + graphHeight, + SWP_NOACTIVATE | SWP_NOZORDER + ); + y += graphHeight + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + BusLabelHandle, + NULL, + GpuGraphMargin.left, + y, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER + ); + y += labelRect.bottom + ET_GPU_PADDING; + + deferHandle = DeferWindowPos( + deferHandle, + BusGraphHandle, + NULL, + GpuGraphMargin.left, + y, + graphWidth, + clientRect.bottom - GpuGraphMargin.bottom - y, + SWP_NOACTIVATE | SWP_NOZORDER + ); + + EndDeferWindowPos(deferHandle); +} + +VOID NvGpuNotifyUsageGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGraphStateGetDrawInfo(&GpuGraphState, getDrawInfo, GpuUtilizationHistory.Count); + + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc = Graph_GetBufferedContext(GpuGraphHandle); + + PhMoveReference(&GpuGraphState.Text, + PhFormatString(L"%.0f%%", GpuCurrentGpuUsage * 100) + ); + + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &GpuGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + if (!GpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&GpuUtilizationHistory, GpuGraphState.Data1, drawInfo->LineDataCount); + GpuGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (GpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT gpuUsageValue; + + gpuUsageValue = PhGetItemCircularBuffer_FLOAT(&GpuUtilizationHistory, getTooltipText->Index); + + PhMoveReference(&GpuGraphState.TooltipText, PhFormatString( + L"%.0f%%\n%s", + gpuUsageValue * 100, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = GpuGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID NvGpuNotifyMemoryGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0); + PhGraphStateGetDrawInfo(&MemGraphState, getDrawInfo, GpuMemoryHistory.Count); + + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc = Graph_GetBufferedContext(MemGraphHandle); + + PhMoveReference(&MemGraphState.Text, PhFormatString( + L"%s / %s (%.2f%%)", + PhaFormatSize(UInt32x32To64(GpuCurrentMemUsage, 1024), -1)->Buffer, + PhaFormatSize(UInt32x32To64(GpuMemoryLimit, 1024), -1)->Buffer, + (FLOAT)GpuCurrentMemUsage / GpuMemoryLimit * 100 + )); + + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &MemGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + if (!MemGraphState.Valid) + { + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + MemGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&GpuMemoryHistory, i); + } + + if (GpuMemoryLimit != 0) + { + // Scale the data. + PhDivideSinglesBySingle( + MemGraphState.Data1, + (FLOAT)GpuMemoryLimit, + drawInfo->LineDataCount + ); + } + + MemGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (MemGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG usedPages; + + usedPages = PhGetItemCircularBuffer_ULONG(&GpuMemoryHistory, getTooltipText->Index); + + PhMoveReference(&MemGraphState.TooltipText, PhFormatString( + L"%s / %s (%.2f%%)\n%s", + PhaFormatSize(UInt32x32To64(usedPages, 1024), -1)->Buffer, + PhaFormatSize(UInt32x32To64(GpuMemoryLimit, 1024), -1)->Buffer, + (FLOAT)usedPages / GpuMemoryLimit * 100, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = MemGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID NvGpuNotifySharedGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGraphStateGetDrawInfo(&SharedGraphState, getDrawInfo, GpuBoardHistory.Count); + + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc = Graph_GetBufferedContext(SharedGraphHandle); + + PhMoveReference(&SharedGraphState.Text, PhFormatString( + L"%.0f%%", + (FLOAT)GpuCurrentCoreUsage * 100 + )); + + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &SharedGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + if (!SharedGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&GpuBoardHistory, SharedGraphState.Data1, drawInfo->LineDataCount); + SharedGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (SharedGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT usedPages; + + usedPages = PhGetItemCircularBuffer_FLOAT(&GpuBoardHistory, getTooltipText->Index); + + PhMoveReference(&SharedGraphState.TooltipText, PhFormatString( + L"%.0f%%\n%s", + usedPages * 100, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = SharedGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID NvGpuNotifyBusGraph( + _In_ NMHDR *Header + ) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGraphStateGetDrawInfo(&BusGraphState, getDrawInfo, GpuBusHistory.Count); + + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc = Graph_GetBufferedContext(BusGraphHandle); + + PhMoveReference(&BusGraphState.Text, PhFormatString( + L"%.0f%%", + (FLOAT)GpuCurrentBusUsage * 100 + )); + + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &BusGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + if (!BusGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&GpuBusHistory, BusGraphState.Data1, drawInfo->LineDataCount); + BusGraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (BusGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT busUsage; + + busUsage = PhGetItemCircularBuffer_FLOAT(&GpuBusHistory, getTooltipText->Index); + + PhMoveReference(&BusGraphState.TooltipText, PhFormatString( + L"%.0f%%\n%s", + busUsage * 100, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = BusGraphState.TooltipText->sr; + } + } + break; + } +} + +VOID NvGpuUpdateGraphs( + VOID + ) +{ + GpuGraphState.Valid = FALSE; + GpuGraphState.TooltipIndex = -1; + Graph_MoveGrid(GpuGraphHandle, 1); + Graph_Draw(GpuGraphHandle); + Graph_UpdateTooltip(GpuGraphHandle); + InvalidateRect(GpuGraphHandle, NULL, FALSE); + + MemGraphState.Valid = FALSE; + MemGraphState.TooltipIndex = -1; + Graph_MoveGrid(MemGraphHandle, 1); + Graph_Draw(MemGraphHandle); + Graph_UpdateTooltip(MemGraphHandle); + InvalidateRect(MemGraphHandle, NULL, FALSE); + + SharedGraphState.Valid = FALSE; + SharedGraphState.TooltipIndex = -1; + Graph_MoveGrid(SharedGraphHandle, 1); + Graph_Draw(SharedGraphHandle); + Graph_UpdateTooltip(SharedGraphHandle); + InvalidateRect(SharedGraphHandle, NULL, FALSE); + + BusGraphState.Valid = FALSE; + BusGraphState.TooltipIndex = -1; + Graph_MoveGrid(BusGraphHandle, 1); + Graph_Draw(BusGraphHandle); + Graph_UpdateTooltip(BusGraphHandle); + InvalidateRect(BusGraphHandle, NULL, FALSE); +} + +VOID NvGpuUpdatePanel( + VOID + ) +{ + SetDlgItemText(GpuPanel, IDC_CLOCK_CORE, PhaFormatString(L"%lu MHz", GpuCurrentCoreClock)->Buffer); + SetDlgItemText(GpuPanel, IDC_CLOCK_MEMORY, PhaFormatString(L"%lu MHz", GpuCurrentMemoryClock)->Buffer); + SetDlgItemText(GpuPanel, IDC_CLOCK_SHADER, PhaFormatString(L"%lu MHz", GpuCurrentShaderClock)->Buffer); + SetDlgItemText(GpuPanel, IDC_FAN_PERCENT, ((PPH_STRING)PhAutoDereferenceObject(NvGpuQueryFanSpeed()))->Buffer); + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_FAHRENHEIT)) + { + FLOAT fahrenheit = (FLOAT)(GpuCurrentCoreTemp * 1.8 + 32); + + SetDlgItemText(GpuPanel, IDC_TEMP_VALUE, PhaFormatString(L"%.1f\u00b0F", fahrenheit)->Buffer); + } + else + { + SetDlgItemText(GpuPanel, IDC_TEMP_VALUE, PhaFormatString(L"%lu\u00b0C", GpuCurrentCoreTemp)->Buffer); + } + + //SetDlgItemText(GpuPanel, IDC_TEMP_VALUE, PhaFormatString(L"%s\u00b0C", PhaFormatUInt64(GpuCurrentBoardTemp, TRUE)->Buffer)->Buffer); + SetDlgItemText(GpuPanel, IDC_VOLTAGE, PhaFormatString(L"%lu mV", GpuCurrentVoltage)->Buffer); +} + +static INT_PTR CALLBACK NvGpuDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + //PhDeleteLayoutManager(&LayoutManager); + + //PhDeleteGraphState(&GpuGraphState); + //PhDeleteGraphState(&MemGraphState); + //PhDeleteGraphState(&SharedGraphState); + //PhDeleteGraphState(&BusGraphState); + + //if (GpuGraphHandle) + // DestroyWindow(GpuGraphHandle); + //if (MemGraphHandle) + // DestroyWindow(MemGraphHandle); + //if (SharedGraphHandle) + // DestroyWindow(SharedGraphHandle); + //if (BusGraphHandle) + // DestroyWindow(BusGraphHandle); + //if (GpuPanel) + // DestroyWindow(GpuPanel); + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + WindowHandle = hwndDlg; + + GpuLabelHandle = GetDlgItem(hwndDlg, IDC_GPU_L); + MemLabelHandle = GetDlgItem(hwndDlg, IDC_MEMORY_L); + SharedLabelHandle = GetDlgItem(hwndDlg, IDC_SHARED_L); + BusLabelHandle = GetDlgItem(hwndDlg, IDC_BUS_L); + + PhInitializeGraphState(&GpuGraphState); + PhInitializeGraphState(&MemGraphState); + PhInitializeGraphState(&SharedGraphState); + PhInitializeGraphState(&BusGraphState); + + PhInitializeLayoutManager(&LayoutManager, hwndDlg); + PhAddLayoutItem(&LayoutManager, GetDlgItem(hwndDlg, IDC_GPUNAME), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&LayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + GpuGraphMargin = graphItem->Margin; + panelItem = PhAddLayoutItem(&LayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_TITLE), WM_SETFONT, (WPARAM)Section->Parameters->LargeFont, FALSE); + SendMessage(GetDlgItem(hwndDlg, IDC_GPUNAME), WM_SETFONT, (WPARAM)Section->Parameters->MediumFont, FALSE); + SetDlgItemText(hwndDlg, IDC_GPUNAME, GpuName->Buffer); + + GpuPanel = CreateDialog(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_GPU_PANEL), hwndDlg, NvGpuPanelDialogProc); + ShowWindow(GpuPanel, SW_SHOW); + PhAddLayoutItemEx(&LayoutManager, GpuPanel, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + NvGpuCreateGraphs(); + + NvGpuUpdate(); + NvGpuUpdateGraphs(); + NvGpuUpdatePanel(); + } + break; + case WM_SIZE: + NvGpuLayoutGraphs(); + break; + case WM_NOTIFY: + { + NMHDR* header = (NMHDR*)lParam; + + if (header->hwndFrom == GpuGraphHandle) + { + NvGpuNotifyUsageGraph(header); + } + else if (header->hwndFrom == MemGraphHandle) + { + NvGpuNotifyMemoryGraph(header); + } + else if (header->hwndFrom == SharedGraphHandle) + { + NvGpuNotifySharedGraph(header); + } + else if (header->hwndFrom == BusGraphHandle) + { + NvGpuNotifyBusGraph(header); + } + } + break; + case UPDATE_MSG: + { + NvGpuUpdateGraphs(); + NvGpuUpdatePanel(); + } + break; + } + + return FALSE; +} + +static BOOLEAN NvGpuSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + switch (Message) + { + case SysInfoCreate: + return TRUE; + case SysInfoDestroy: + return TRUE; + case SysInfoTick: + { + if (WindowHandle) + PostMessage(WindowHandle, UPDATE_MSG, 0, 0); + + if (DetailsHandle) + PostMessage(DetailsHandle, UPDATE_MSG, 0, 0); + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = (PPH_SYSINFO_CREATE_DIALOG)Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_GPU_DIALOG); + createDialog->DialogProc = NvGpuDialogProc; + //createDialog->Parameter = context; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, GpuUtilizationHistory.Count); + + if (!Section->GraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(&GpuUtilizationHistory, Section->GraphState.Data1, drawInfo->LineDataCount); + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + FLOAT gpuUsageValue; + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = (PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT)Parameter1; + + gpuUsageValue = PhGetItemCircularBuffer_FLOAT(&GpuUtilizationHistory, getTooltipText->Index); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"%.0f%%\n%s", + gpuUsageValue * 100, + ((PPH_STRING)PhAutoDereferenceObject(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = (PPH_SYSINFO_DRAW_PANEL)Parameter1; + + drawPanel->Title = PhCreateString(Section->Name.Buffer); + drawPanel->SubTitle = PhFormatString( + L"%.0f%%", + GpuCurrentGpuUsage * 100 + ); + } + return TRUE; + } + + return FALSE; +} + +VOID NvGpuSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers + ) +{ + PH_SYSINFO_SECTION section; + + if (!PhGetIntegerSetting(SETTING_NAME_ENABLE_GPU)) + return; + + if (!NvApiInitialized) + return; + + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + + section.Callback = NvGpuSectionCallback; + + GpuName = NvGpuQueryName(); + PhInitializeStringRef(§ion.Name, GpuName->Buffer); + + Section = Pointers->CreateSection(§ion); +} + +#endif \ No newline at end of file diff --git a/plugins/HardwareDevices/main.c b/plugins/HardwareDevices/main.c new file mode 100644 index 0000000..5431262 --- /dev/null +++ b/plugins/HardwareDevices/main.c @@ -0,0 +1,407 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +PPH_PLUGIN PluginInstance = NULL; + +PPH_OBJECT_TYPE NetAdapterEntryType = NULL; +PPH_LIST NetworkAdaptersList = NULL; +PH_QUEUED_LOCK NetworkAdaptersListLock = PH_QUEUED_LOCK_INIT; + +PPH_OBJECT_TYPE DiskDriveEntryType = NULL; +PPH_LIST DiskDrivesList = NULL; +PH_QUEUED_LOCK DiskDrivesListLock = PH_QUEUED_LOCK_INIT; + +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginUnloadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +PH_CALLBACK_REGISTRATION SystemInformationInitializingCallbackRegistration; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DiskDrivesInitialize(); + NetAdaptersInitialize(); + + DiskDrivesLoadList(); + NetAdaptersLoadList(); + +#ifdef _NV_GPU_BUILD + NvApiInitialize(); +#endif +} + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[2]; + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP; + propSheetHeader.hwndParent = (HWND)Parameter; + propSheetHeader.pszCaption = L"Hardware Devices Plugin"; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // Disk Drives + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DISKDRIVE_OPTIONS); + propSheetPage.pfnDlgProc = DiskDriveOptionsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Network Adapters + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_NETADAPTER_OPTIONS); + propSheetPage.pfnDlgProc = NetworkAdapterOptionsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + PhModalPropertySheet(&propSheetHeader); +} + +VOID NTAPI MainWindowShowingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + AddRemoveDeviceChangeCallback(); +} + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DiskDrivesUpdate(); + NetAdaptersUpdate(); + +#ifdef _NV_GPU_BUILD + NvGpuUpdate(); +#endif +} + +VOID NTAPI SystemInformationInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_SYSINFO_POINTERS pluginEntry = (PPH_PLUGIN_SYSINFO_POINTERS)Parameter; + + // Disk Drives + + PhAcquireQueuedLockShared(&DiskDrivesListLock); + + for (ULONG i = 0; i < DiskDrivesList->Count; i++) + { + PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); + + if (!entry) + continue; + + if (entry->DevicePresent) + { + DiskDriveSysInfoInitializing(pluginEntry, entry); + } + } + + PhReleaseQueuedLockShared(&DiskDrivesListLock); + + // Network Adapters + + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + PDV_NETADAPTER_ENTRY entry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!entry) + continue; + + if (entry->DevicePresent) + { + NetAdapterSysInfoInitializing(pluginEntry, entry); + } + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + + // Graphics cards +#ifdef _NV_GPU_BUILD + NvGpuSysInfoInitializing(pluginEntry); +#endif +} + +PPH_STRING TrimString( + _In_ PPH_STRING String + ) +{ + static PH_STRINGREF whitespace = PH_STRINGREF_INIT(L" \t\r\n"); + PH_STRINGREF sr = String->sr; + PhTrimStringRef(&sr, &whitespace, 0); + return PhCreateString2(&sr); +} + +INT AddListViewGroup( + _In_ HWND ListViewHandle, + _In_ INT GroupId, + _In_ PWSTR Text + ) +{ + LVGROUP group; + + group.cbSize = LVGROUP_V5_SIZE; + group.mask = LVGF_HEADER; + group.pszHeader = Text; + + if (WindowsVersion >= WINDOWS_VISTA) + { + group.cbSize = sizeof(LVGROUP); + group.mask |= LVGF_ALIGN | LVGF_STATE | LVGF_GROUPID; + group.uAlign = LVGA_HEADER_LEFT; + group.state = LVGS_COLLAPSIBLE; + group.iGroupId = GroupId; + } + + return (INT)ListView_InsertGroup(ListViewHandle, MAXINT, &group); +} + +INT AddListViewItemGroupId( + _In_ HWND ListViewHandle, + _In_ INT GroupId, + _In_ INT Index, + _In_ PWSTR Text, + _In_opt_ PVOID Param + ) +{ + LVITEM item; + + item.mask = LVIF_TEXT; + item.iItem = Index; + item.iSubItem = 0; + item.pszText = Text; + + if (WindowsVersion >= WINDOWS_VISTA) + { + item.mask |= LVIF_GROUPID; + item.iGroupId = GroupId; + } + + if (Param) + { + item.mask |= LVIF_PARAM; + item.lParam = (LPARAM)Param; + } + + return ListView_InsertItem(ListViewHandle, &item); +} + +ULONG64 RegQueryUlong64( + _In_ HANDLE KeyHandle, + _In_ PWSTR ValueName + ) +{ + ULONG64 value = 0; + PH_STRINGREF valueName; + PKEY_VALUE_PARTIAL_INFORMATION buffer; + + PhInitializeStringRef(&valueName, ValueName); + + if (NT_SUCCESS(PhQueryValueKey(KeyHandle, &valueName, KeyValuePartialInformation, &buffer))) + { + if (buffer->Type == REG_DWORD || buffer->Type == REG_QWORD) + { + value = *(ULONG64*)buffer->Data; + } + + PhFree(buffer); + } + + return value; +} + +VOID ShowDeviceMenu( + _In_ HWND ParentWindow, + _In_ PPH_STRING DeviceInstance + ) +{ + POINT cursorPos; + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + + GetCursorPos(&cursorPos); + + menu = PhCreateEMenu(); + //PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 0, L"Enable", NULL, NULL), -1); + //PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 1, L"Disable", NULL, NULL), -1); + //PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, 1, L"Properties", NULL, NULL), -1); + + selectedItem = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + cursorPos.x, + cursorPos.y + ); + + if (selectedItem && selectedItem->Id != -1) + { + switch (selectedItem->Id) + { + case 1: + { + HMODULE devMgrHandle; + + // https://msdn.microsoft.com/en-us/library/ff548181.aspx + VOID (WINAPI *DeviceProperties_RunDLL_I)( + _In_ HWND hwndStub, + _In_ HINSTANCE hAppInstance, + _In_ PWSTR lpCmdLine, + _In_ INT nCmdShow + ); + + if (devMgrHandle = LoadLibrary(L"devmgr.dll")) + { + if (DeviceProperties_RunDLL_I = (PVOID)GetProcAddress(devMgrHandle, "DeviceProperties_RunDLLW")) + { + // This will sometimes re-throw an RPC error during debugging and can be safely ignored. + DeviceProperties_RunDLL_I( + GetParent(ParentWindow), + NULL, + PhaFormatString(L"/DeviceID %s", DeviceInstance->Buffer)->Buffer, + 0 + ); + } + + FreeLibrary(devMgrHandle); + } + } + break; + } + } + + PhDestroyEMenu(menu); +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_ENABLE_NDIS, L"1" }, + { StringSettingType, SETTING_NAME_INTERFACE_LIST, L"" }, + { StringSettingType, SETTING_NAME_DISK_LIST, L"" }, +#ifdef _NV_GPU_BUILD + { IntegerSettingType, SETTING_NAME_ENABLE_GPU, L"1" }, + { IntegerSettingType, SETTING_NAME_ENABLE_FAHRENHEIT, L"0" } +#endif + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Hardware Devices"; + info->Author = L"dmex, wj32"; + info->Description = L"Plugin for monitoring hardware devices like Disk drives and Network adapters via the System Information window."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1820"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackUnload), + UnloadCallback, + NULL, + &PluginUnloadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + ProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackSystemInformationInitializing), + SystemInformationInitializingCallback, + NULL, + &SystemInformationInitializingCallbackRegistration + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/HardwareDevices/ndis.c b/plugins/HardwareDevices/ndis.c new file mode 100644 index 0000000..ebd7d57 --- /dev/null +++ b/plugins/HardwareDevices/ndis.c @@ -0,0 +1,597 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" +#include + +PVOID IphlpHandle = NULL; +_GetInterfaceDescriptionFromGuid GetInterfaceDescriptionFromGuid_I = NULL; + +NTSTATUS NetworkAdapterCreateHandle( + _Out_ PHANDLE DeviceHandle, + _In_ PPH_STRING InterfaceGuid + ) +{ + // NOTE: Do not cache this handle. The user will be unable to enable, disable or change adapter configuration. + return PhCreateFileWin32( + DeviceHandle, + PhaConcatStrings(2, L"\\\\.\\", InterfaceGuid->Buffer)->Buffer, + FILE_GENERIC_READ, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); +} + +BOOLEAN NetworkAdapterQuerySupported( + _In_ HANDLE DeviceHandle + ) +{ + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + BOOLEAN ndisQuerySupported = FALSE; + BOOLEAN adapterNameSupported = FALSE; + BOOLEAN adapterStatsSupported = FALSE; + BOOLEAN adapterLinkStateSupported = FALSE; + BOOLEAN adapterLinkSpeedSupported = FALSE; + PNDIS_OID ndisObjectIdentifiers = NULL; + + // https://msdn.microsoft.com/en-us/library/ff569642.aspx + opcode = OID_GEN_SUPPORTED_LIST; + + // TODO: 4096 objects might be too small... + ndisObjectIdentifiers = PhAllocate(PAGE_SIZE * sizeof(NDIS_OID)); + memset(ndisObjectIdentifiers, 0, PAGE_SIZE * sizeof(NDIS_OID)); + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548975.aspx + &opcode, + sizeof(NDIS_OID), + ndisObjectIdentifiers, + PAGE_SIZE * sizeof(NDIS_OID) + ))) + { + ndisQuerySupported = TRUE; + + for (ULONG i = 0; i < (ULONG)isb.Information / sizeof(NDIS_OID); i++) + { + NDIS_OID opcode = ndisObjectIdentifiers[i]; + + switch (opcode) + { + case OID_GEN_FRIENDLY_NAME: + adapterNameSupported = TRUE; + break; + case OID_GEN_STATISTICS: + adapterStatsSupported = TRUE; + break; + case OID_GEN_LINK_STATE: + adapterLinkStateSupported = TRUE; + break; + case OID_GEN_LINK_SPEED: + adapterLinkSpeedSupported = TRUE; + break; + } + } + } + + PhFree(ndisObjectIdentifiers); + + if (!adapterNameSupported) + ndisQuerySupported = FALSE; + if (!adapterStatsSupported) + ndisQuerySupported = FALSE; + if (!adapterLinkStateSupported) + ndisQuerySupported = FALSE; + if (!adapterLinkSpeedSupported) + ndisQuerySupported = FALSE; + + return ndisQuerySupported; +} + +BOOLEAN NetworkAdapterQueryNdisVersion( + _In_ HANDLE DeviceHandle, + _Out_opt_ PUINT MajorVersion, + _Out_opt_ PUINT MinorVersion + ) +{ + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + ULONG versionResult = 0; + + // https://msdn.microsoft.com/en-us/library/ff569582.aspx + opcode = OID_GEN_DRIVER_VERSION; // OID_GEN_VENDOR_DRIVER_VERSION + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &versionResult, + sizeof(versionResult) + ))) + { + if (MajorVersion) + { + *MajorVersion = HIBYTE(versionResult); + } + + if (MinorVersion) + { + *MinorVersion = LOBYTE(versionResult); + } + + return TRUE; + } + + return FALSE; +} + +PPH_STRING NetworkAdapterQueryName( + _In_ HANDLE DeviceHandle, + _In_ PPH_STRING InterfaceGuid + ) +{ + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + WCHAR adapterName[NDIS_IF_MAX_STRING_SIZE + 1] = L""; + + // https://msdn.microsoft.com/en-us/library/ff569584.aspx + opcode = OID_GEN_FRIENDLY_NAME; + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + adapterName, + sizeof(adapterName) + ))) + { + return PhCreateString(adapterName); + } + + if (!GetInterfaceDescriptionFromGuid_I) + { + if (IphlpHandle = LoadLibrary(L"iphlpapi.dll")) + { + GetInterfaceDescriptionFromGuid_I = PhGetProcedureAddress(IphlpHandle, "NhGetInterfaceDescriptionFromGuid", 0); + } + } + + // HACK: Query adapter description using undocumented function. + if (GetInterfaceDescriptionFromGuid_I) + { + GUID deviceGuid = GUID_NULL; + UNICODE_STRING guidStringUs; + + PhStringRefToUnicodeString(&InterfaceGuid->sr, &guidStringUs); + + if (NT_SUCCESS(RtlGUIDFromString(&guidStringUs, &deviceGuid))) + { + WCHAR adapterDescription[NDIS_IF_MAX_STRING_SIZE + 1] = L""; + SIZE_T adapterDescriptionLength = sizeof(adapterDescription); + + if (SUCCEEDED(GetInterfaceDescriptionFromGuid_I(&deviceGuid, adapterDescription, &adapterDescriptionLength, NULL, NULL))) + { + return PhCreateString(adapterDescription); + } + } + } + + return PhCreateString(L"Unknown Network Adapter"); +} + +NTSTATUS NetworkAdapterQueryStatistics( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_STATISTICS_INFO Info + ) +{ + NTSTATUS status; + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + NDIS_STATISTICS_INFO result; + + // https://msdn.microsoft.com/en-us/library/ff569640.aspx + opcode = OID_GEN_STATISTICS; + + memset(&result, 0, sizeof(NDIS_STATISTICS_INFO)); + result.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + result.Header.Revision = NDIS_STATISTICS_INFO_REVISION_1; + result.Header.Size = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1; + + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &result, + sizeof(result) + ); + + *Info = result; + + return status; +} + +NTSTATUS NetworkAdapterQueryLinkState( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_LINK_STATE State + ) +{ + NTSTATUS status; + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + NDIS_LINK_STATE result; + + // https://msdn.microsoft.com/en-us/library/ff569595.aspx + opcode = OID_GEN_LINK_STATE; // OID_GEN_MEDIA_CONNECT_STATUS; + + memset(&result, 0, sizeof(NDIS_LINK_STATE)); + result.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + result.Header.Revision = NDIS_LINK_STATE_REVISION_1; + result.Header.Size = NDIS_SIZEOF_LINK_STATE_REVISION_1; + + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &result, + sizeof(result) + ); + + *State = result; + + return status; +} + +BOOLEAN NetworkAdapterQueryMediaType( + _In_ HANDLE DeviceHandle, + _Out_ PNDIS_PHYSICAL_MEDIUM Medium + ) +{ + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + NDIS_PHYSICAL_MEDIUM adapterMediaType = NdisPhysicalMediumUnspecified; + + // https://msdn.microsoft.com/en-us/library/ff569622.aspx + opcode = OID_GEN_PHYSICAL_MEDIUM_EX; + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &adapterMediaType, + sizeof(adapterMediaType) + ))) + { + *Medium = adapterMediaType; + } + + if (adapterMediaType != NdisPhysicalMediumUnspecified) + return TRUE; + + // https://msdn.microsoft.com/en-us/library/ff569621.aspx + opcode = OID_GEN_PHYSICAL_MEDIUM; + adapterMediaType = NdisPhysicalMediumUnspecified; + memset(&isb, 0, sizeof(IO_STATUS_BLOCK)); + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &adapterMediaType, + sizeof(adapterMediaType) + ))) + { + *Medium = adapterMediaType; + } + + if (adapterMediaType != NdisPhysicalMediumUnspecified) + return TRUE; + + //NDIS_MEDIUM adapterMediaType = NdisMediumMax; + //opcode = OID_GEN_MEDIA_IN_USE; + + return FALSE; +} + +NTSTATUS NetworkAdapterQueryLinkSpeed( + _In_ HANDLE DeviceHandle, + _Out_ PULONG64 LinkSpeed + ) +{ + NTSTATUS status; + NDIS_OID opcode; + IO_STATUS_BLOCK isb; + NDIS_LINK_SPEED result; + + // https://msdn.microsoft.com/en-us/library/ff569593.aspx + opcode = OID_GEN_LINK_SPEED; + + memset(&result, 0, sizeof(NDIS_LINK_SPEED)); + + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &opcode, + sizeof(NDIS_OID), + &result, + sizeof(result) + ); + + *LinkSpeed = UInt32x32To64(result.XmitLinkSpeed, NDIS_UNIT_OF_MEASUREMENT); + + return status; +} + +ULONG64 NetworkAdapterQueryValue( + _In_ HANDLE DeviceHandle, + _In_ NDIS_OID OpCode + ) +{ + IO_STATUS_BLOCK isb; + ULONG64 result = 0; + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_NDIS_QUERY_GLOBAL_STATS, + &OpCode, + sizeof(NDIS_OID), + &result, + sizeof(result) + ))) + { + return result; + } + + return 0; +} + +BOOLEAN QueryInterfaceRowVista( + _In_ PDV_NETADAPTER_ID Id, + _Out_ PMIB_IF_ROW2 InterfaceRow + ) +{ + BOOLEAN result = FALSE; + MIB_IF_ROW2 interfaceRow; + + memset(&interfaceRow, 0, sizeof(MIB_IF_ROW2)); + + interfaceRow.InterfaceLuid = Id->InterfaceLuid; + interfaceRow.InterfaceIndex = Id->InterfaceIndex; + + if (GetIfEntry2) + { + if (GetIfEntry2(&interfaceRow) == NO_ERROR) + { + result = TRUE; + *InterfaceRow = interfaceRow; + } + } + + //MIB_IPINTERFACE_ROW interfaceTable; + //memset(&interfaceTable, 0, sizeof(MIB_IPINTERFACE_ROW)); + //interfaceTable.Family = AF_INET; + //interfaceTable.InterfaceLuid.Value = Context->AdapterEntry->InterfaceLuidValue; + //interfaceTable.InterfaceIndex = Context->AdapterEntry->InterfaceIndex; + //GetIpInterfaceEntry(&interfaceTable); + + return result; +} + +BOOLEAN QueryInterfaceRowXP( + _In_ PDV_NETADAPTER_ID Id, + _Out_ PMIB_IFROW InterfaceRow + ) +{ + BOOLEAN result = FALSE; + MIB_IFROW interfaceRow; + + memset(&interfaceRow, 0, sizeof(MIB_IFROW)); + + interfaceRow.dwIndex = Id->InterfaceIndex; + + if (GetIfEntry(&interfaceRow) == NO_ERROR) + { + result = TRUE; + *InterfaceRow = interfaceRow; + } + + //MIB_IPINTERFACE_ROW interfaceTable; + //memset(&interfaceTable, 0, sizeof(MIB_IPINTERFACE_ROW)); + //interfaceTable.Family = AF_INET; + //interfaceTable.InterfaceIndex = Context->AdapterEntry->InterfaceIndex; + //GetIpInterfaceEntry(&interfaceTable); + + return result; +} + + +//BOOLEAN NetworkAdapterQueryInternet( +// _Inout_ PDV_NETADAPTER_SYSINFO_CONTEXT Context, +// _In_ PPH_STRING IpAddress +// ) +//{ +// // https://technet.microsoft.com/en-us/library/cc766017.aspx +// BOOLEAN socketResult = FALSE; +// WSADATA wsadata; +// DNS_STATUS dnsQueryStatus = DNS_ERROR_RCODE_NO_ERROR; +// PDNS_RECORD dnsQueryRecords = NULL; +// +// WSAStartup(WINSOCK_VERSION, &wsadata); +// +// __try +// { +// if ((dnsQueryStatus = DnsQuery( +// L"www.msftncsi.com", +// DNS_TYPE_A, +// DNS_QUERY_NO_HOSTS_FILE | DNS_QUERY_BYPASS_CACHE, +// NULL, +// &dnsQueryRecords, +// NULL +// )) != DNS_ERROR_RCODE_NO_ERROR) +// { +// __leave; +// } +// +// for (PDNS_RECORD i = dnsQueryRecords; i != NULL; i = i->pNext) +// { +// if (i->wType == DNS_TYPE_A) +// { +// SOCKET socketHandle = INVALID_SOCKET; +// +// if ((socketHandle = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) == INVALID_SOCKET) +// continue; +// +// IN_ADDR sockAddr; +// InetPton(AF_INET, IpAddress->Buffer, &sockAddr); +// +// SOCKADDR_IN localaddr = { 0 }; +// localaddr.sin_family = AF_INET; +// localaddr.sin_addr.s_addr = sockAddr.s_addr; +// +// if (bind(socketHandle, (PSOCKADDR)&localaddr, sizeof(localaddr)) == SOCKET_ERROR) +// { +// closesocket(socketHandle); +// continue; +// } +// +// SOCKADDR_IN remoteAddr; +// remoteAddr.sin_family = AF_INET; +// remoteAddr.sin_port = htons(80); +// remoteAddr.sin_addr.s_addr = i->Data.A.IpAddress; +// +// if (WSAConnect(socketHandle, (PSOCKADDR)&remoteAddr, sizeof(remoteAddr), NULL, NULL, NULL, NULL) != SOCKET_ERROR) +// { +// socketResult = TRUE; +// closesocket(socketHandle); +// break; +// } +// +// closesocket(socketHandle); +// } +// } +// } +// __finally +// { +// if (dnsQueryRecords) +// { +// DnsFree(dnsQueryRecords, DnsFreeRecordList); +// } +// } +// +// __try +// { +// if ((dnsQueryStatus = DnsQuery( +// L"ipv6.msftncsi.com", +// DNS_TYPE_AAAA, +// DNS_QUERY_NO_HOSTS_FILE | DNS_QUERY_BYPASS_CACHE, // | DNS_QUERY_DUAL_ADDR +// NULL, +// &dnsQueryRecords, +// NULL +// )) != DNS_ERROR_RCODE_NO_ERROR) +// { +// __leave; +// } +// +// for (PDNS_RECORD i = dnsQueryRecords; i != NULL; i = i->pNext) +// { +// if (i->wType == DNS_TYPE_AAAA) +// { +// SOCKET socketHandle = INVALID_SOCKET; +// +// if ((socketHandle = WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0)) == INVALID_SOCKET) +// continue; +// +// IN6_ADDR sockAddr; +// InetPton(AF_INET6, IpAddress->Buffer, &sockAddr); +// +// SOCKADDR_IN6 remoteAddr = { 0 }; +// remoteAddr.sin6_family = AF_INET6; +// remoteAddr.sin6_port = htons(80); +// memcpy(&remoteAddr.sin6_addr.u.Byte, i->Data.AAAA.Ip6Address.IP6Byte, sizeof(i->Data.AAAA.Ip6Address.IP6Byte)); +// +// if (WSAConnect(socketHandle, (PSOCKADDR)&remoteAddr, sizeof(SOCKADDR_IN6), NULL, NULL, NULL, NULL) != SOCKET_ERROR) +// { +// socketResult = TRUE; +// closesocket(socketHandle); +// break; +// } +// +// closesocket(socketHandle); +// } +// } +// } +// __finally +// { +// if (dnsQueryRecords) +// { +// DnsFree(dnsQueryRecords, DnsFreeRecordList); +// } +// } +// +// WSACleanup(); +// +// return socketResult; +//} \ No newline at end of file diff --git a/plugins/HardwareDevices/netdetails.c b/plugins/HardwareDevices/netdetails.c new file mode 100644 index 0000000..78be88e --- /dev/null +++ b/plugins/HardwareDevices/netdetails.c @@ -0,0 +1,787 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +VOID NTAPI NetAdapterProcessesUpdatedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PDV_NETADAPTER_DETAILS_CONTEXT context = Context; + + if (context->WindowHandle) + { + PostMessage(context->WindowHandle, UPDATE_MSG, 0, 0); + } +} + +VOID NetAdapterAddListViewItemGroups( + _In_ HWND ListViewHandle + ) +{ + if (WindowsVersion >= WINDOWS_VISTA) + { + ListView_EnableGroupView(ListViewHandle, TRUE); + AddListViewGroup(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, L"Adapter"); + AddListViewGroup(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, L"Unicast"); + AddListViewGroup(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, L"Broadcast"); + AddListViewGroup(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, L"Multicast"); + AddListViewGroup(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, L"Errors"); + } + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_STATE, L"State", NULL); + //AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_CONNECTIVITY, L"Connectivity"); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_IPADDRESS, L"IP address", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_SUBNET, L"Subnet mask", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_GATEWAY, L"Default gateway", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_DNS, L"DNS", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_DOMAIN, L"Domain", NULL); + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_LINKSPEED, L"Link speed", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_SENT, L"Sent", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_RECEIVED, L"Received", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_TOTAL, L"Total", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_SENDING, L"Sending", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_RECEIVING, L"Receiving", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ADAPTER, NETADAPTER_DETAILS_INDEX_UTILIZATION, L"Utilization", NULL); + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_SENTPKTS, L"Sent packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_RECVPKTS, L"Received packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_TOTALPKTS, L"Total packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_SENT, L"Sent", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVED, L"Received", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_TOTAL, L"Total", NULL); + //AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_SENDING, L"Sending"); + //AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVING, L"Receiving"); + //AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_UNICAST, NETADAPTER_DETAILS_INDEX_UNICAST_UTILIZATION, L"Utilization"); + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_SENTPKTS, L"Sent packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_RECVPKTS, L"Received packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_TOTALPKTS, L"Total packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_SENT, L"Sent", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_RECEIVED, L"Received", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_BROADCAST, NETADAPTER_DETAILS_INDEX_BROADCAST_TOTAL, L"Total", NULL); + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_SENTPKTS, L"Sent packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_RECVPKTS, L"Received packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_TOTALPKTS, L"Total packets", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_SENT, L"Sent", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_RECEIVED, L"Received", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_MULTICAST, NETADAPTER_DETAILS_INDEX_MULTICAST_TOTAL, L"Total", NULL); + + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_SENTPKTS, L"Send errors", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_RECVPKTS, L"Receive errors", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_TOTALPKTS, L"Total errors", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_SENT, L"Send discards", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_RECEIVED, L"Receive discards", NULL); + AddListViewItemGroupId(ListViewHandle, NETADAPTER_DETAILS_CATEGORY_ERRORS, NETADAPTER_DETAILS_INDEX_ERRORS_TOTAL, L"Total discards", NULL); +} + +PVOID NetAdapterGetAddresses( + _In_ ULONG Family + ) +{ + ULONG flags; + ULONG bufferLength = 0; + PIP_ADAPTER_ADDRESSES buffer = NULL; + + flags = GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_SKIP_FRIENDLY_NAME; + + if (WindowsVersion >= WINDOWS_VISTA) + { + flags |= GAA_FLAG_INCLUDE_ALL_INTERFACES; + } + + if (GetAdaptersAddresses(Family, flags, NULL, NULL, &bufferLength) != ERROR_BUFFER_OVERFLOW) + { + return NULL; + } + + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (GetAdaptersAddresses(Family, flags, NULL, buffer, &bufferLength) != ERROR_SUCCESS) + { + PhFree(buffer); + return NULL; + } + + return buffer; +} + +VOID NetAdapterEnumerateAddresses( + _In_ PVOID AddressesBuffer, + _In_ ULONG64 InterfaceLuid, + _In_ PPH_STRING_BUILDER DomainBuffer, + _In_ PPH_STRING_BUILDER IpAddressBuffer, + _In_ PPH_STRING_BUILDER SubnetAddressBuffer, + _In_ PPH_STRING_BUILDER GatewayAddressBuffer, + _In_ PPH_STRING_BUILDER DnsAddressBuffer + ) +{ + PIP_ADAPTER_ADDRESSES addressesBuffer; + PIP_ADAPTER_UNICAST_ADDRESS unicastAddress; + PIP_ADAPTER_GATEWAY_ADDRESS gatewayAddress; + PIP_ADAPTER_DNS_SERVER_ADDRESS dnsAddress; + + for (addressesBuffer = AddressesBuffer; addressesBuffer; addressesBuffer = addressesBuffer->Next) + { + if (addressesBuffer->Luid.Value != InterfaceLuid) + continue; + + if (addressesBuffer->DnsSuffix && PhCountStringZ(addressesBuffer->DnsSuffix) > 0) + { + PhAppendFormatStringBuilder(DomainBuffer, L"%s, ", addressesBuffer->DnsSuffix); + } + + for (unicastAddress = addressesBuffer->FirstUnicastAddress; unicastAddress; unicastAddress = unicastAddress->Next) + { + if (unicastAddress->Address.lpSockaddr->sa_family == AF_INET) + { + PSOCKADDR_IN sockAddrIn = { 0 }; + IN_ADDR subnetMask = { 0 }; + WCHAR ipv4AddressString[INET_ADDRSTRLEN] = L""; + WCHAR subnetAddressString[INET_ADDRSTRLEN] = L""; + + sockAddrIn = (PSOCKADDR_IN)unicastAddress->Address.lpSockaddr; + + if (WindowsVersion >= WINDOWS_VISTA && ConvertLengthToIpv4Mask) + { + ConvertLengthToIpv4Mask(unicastAddress->OnLinkPrefixLength, &subnetMask.s_addr); + } + + if (RtlIpv4AddressToString(&sockAddrIn->sin_addr, ipv4AddressString)) + { + PhAppendFormatStringBuilder(IpAddressBuffer, L"%s, ", ipv4AddressString); + } + + if (RtlIpv4AddressToString(&subnetMask, subnetAddressString)) + { + PhAppendFormatStringBuilder(SubnetAddressBuffer, L"%s, ", subnetAddressString); + } + } + + if (unicastAddress->Address.lpSockaddr->sa_family == AF_INET6) + { + PSOCKADDR_IN6 sockAddrIn6 = { 0 }; + WCHAR ipv6AddressString[INET6_ADDRSTRLEN] = L""; + + sockAddrIn6 = (PSOCKADDR_IN6)unicastAddress->Address.lpSockaddr; + + if (RtlIpv6AddressToString(&sockAddrIn6->sin6_addr, ipv6AddressString)) + { + PhAppendFormatStringBuilder(IpAddressBuffer, L"%s, ", ipv6AddressString); + } + } + } + + for (gatewayAddress = addressesBuffer->FirstGatewayAddress; gatewayAddress; gatewayAddress = gatewayAddress->Next) + { + if (gatewayAddress->Address.lpSockaddr->sa_family == AF_INET) + { + PSOCKADDR_IN sockAddrIn = { 0 }; + WCHAR ipv4AddressString[INET_ADDRSTRLEN] = L""; + + sockAddrIn = (PSOCKADDR_IN)gatewayAddress->Address.lpSockaddr; + + if (RtlIpv4AddressToString(&sockAddrIn->sin_addr, ipv4AddressString)) + { + PhAppendFormatStringBuilder(GatewayAddressBuffer, L"%s, ", ipv4AddressString); + } + } + + if (gatewayAddress->Address.lpSockaddr->sa_family == AF_INET6) + { + PSOCKADDR_IN6 sockAddrIn6 = (PSOCKADDR_IN6)gatewayAddress->Address.lpSockaddr; + WCHAR ipv6AddressString[INET6_ADDRSTRLEN] = L""; + + if (RtlIpv6AddressToString(&sockAddrIn6->sin6_addr, ipv6AddressString)) + { + PhAppendFormatStringBuilder(GatewayAddressBuffer, L"%s, ", ipv6AddressString); + } + } + } + + for (dnsAddress = addressesBuffer->FirstDnsServerAddress; dnsAddress; dnsAddress = dnsAddress->Next) + { + if (dnsAddress->Address.lpSockaddr->sa_family == AF_INET) + { + PSOCKADDR_IN sockAddrIn = (PSOCKADDR_IN)dnsAddress->Address.lpSockaddr; + WCHAR ipv4AddressString[INET_ADDRSTRLEN] = L""; + + if (RtlIpv4AddressToString(&sockAddrIn->sin_addr, ipv4AddressString)) + { + PhAppendFormatStringBuilder(DnsAddressBuffer, L"%s, ", ipv4AddressString); + } + } + else if (dnsAddress->Address.lpSockaddr->sa_family == AF_INET6) + { + PSOCKADDR_IN6 sockAddrIn6 = (PSOCKADDR_IN6)dnsAddress->Address.lpSockaddr; + WCHAR ipv6AddressString[INET6_ADDRSTRLEN] = L""; + + if (RtlIpv6AddressToString(&sockAddrIn6->sin6_addr, ipv6AddressString)) + { + PhAppendFormatStringBuilder(DnsAddressBuffer, L"%s, ", ipv6AddressString); + } + } + } + } +} + +VOID NetAdapterLookupConfig( + _Inout_ PDV_NETADAPTER_DETAILS_CONTEXT Context + ) +{ + if (WindowsVersion >= WINDOWS_VISTA) + { + PVOID addressesBuffer = NULL; + PPH_STRING domainString = NULL; + PPH_STRING ipAddressString = NULL; + PPH_STRING subnetAddressString = NULL; + PPH_STRING gatewayAddressString = NULL; + PPH_STRING dnsAddressString = NULL; + PH_STRING_BUILDER domainBuffer; + PH_STRING_BUILDER ipAddressBuffer; + PH_STRING_BUILDER subnetAddressBuffer; + PH_STRING_BUILDER gatewayAddressBuffer; + PH_STRING_BUILDER dnsAddressBuffer; + + PhInitializeStringBuilder(&domainBuffer, 64); + PhInitializeStringBuilder(&ipAddressBuffer, 64); + PhInitializeStringBuilder(&subnetAddressBuffer, 64); + PhInitializeStringBuilder(&gatewayAddressBuffer, 64); + PhInitializeStringBuilder(&dnsAddressBuffer, 64); + + if (addressesBuffer = NetAdapterGetAddresses(AF_INET)) + { + NetAdapterEnumerateAddresses( + addressesBuffer, + Context->AdapterId.InterfaceLuid.Value, + &domainBuffer, + &ipAddressBuffer, + &subnetAddressBuffer, + &gatewayAddressBuffer, + &dnsAddressBuffer + ); + PhFree(addressesBuffer); + } + + if (addressesBuffer = NetAdapterGetAddresses(AF_INET6)) + { + NetAdapterEnumerateAddresses( + addressesBuffer, + Context->AdapterId.InterfaceLuid.Value, + &domainBuffer, + &ipAddressBuffer, + &subnetAddressBuffer, + &gatewayAddressBuffer, + &dnsAddressBuffer + ); + PhFree(addressesBuffer); + } + + if (domainBuffer.String->Length > 2) + PhRemoveEndStringBuilder(&domainBuffer, 2); + if (ipAddressBuffer.String->Length > 2) + PhRemoveEndStringBuilder(&ipAddressBuffer, 2); + if (subnetAddressBuffer.String->Length > 2) + PhRemoveEndStringBuilder(&subnetAddressBuffer, 2); + if (gatewayAddressBuffer.String->Length > 2) + PhRemoveEndStringBuilder(&gatewayAddressBuffer, 2); + if (dnsAddressBuffer.String->Length > 2) + PhRemoveEndStringBuilder(&dnsAddressBuffer, 2); + + domainString = PhFinalStringBuilderString(&domainBuffer); + ipAddressString = PhFinalStringBuilderString(&ipAddressBuffer); + subnetAddressString = PhFinalStringBuilderString(&subnetAddressBuffer); + gatewayAddressString = PhFinalStringBuilderString(&gatewayAddressBuffer); + dnsAddressString = PhFinalStringBuilderString(&dnsAddressBuffer); + + //PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_CONNECTIVITY, 1, internet ? L"Internet" : L"Local"); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_IPADDRESS, 1, ipAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_SUBNET, 1, subnetAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_GATEWAY, 1, gatewayAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_DNS, 1, dnsAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_DOMAIN, 1, domainString->Buffer); + + PhDeleteStringBuilder(&domainBuffer); + PhDeleteStringBuilder(&ipAddressBuffer); + PhDeleteStringBuilder(&subnetAddressBuffer); + PhDeleteStringBuilder(&gatewayAddressBuffer); + PhDeleteStringBuilder(&dnsAddressBuffer); + } + else + { + static PH_STRINGREF tcpIpv4KeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\"); + HANDLE keyHandle; + PPH_STRING keyNameIpV4; + + keyNameIpV4 = PhConcatStringRef2(&tcpIpv4KeyName, &Context->AdapterId.InterfaceGuid->sr); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyNameIpV4->sr, + 0 + ))) + { + PPH_STRING domainString = NULL; + PPH_STRING ipAddressString = NULL; + PPH_STRING subnetAddressString = NULL; + PPH_STRING gatewayAddressString = NULL; + + domainString = PhQueryRegistryString(keyHandle, L"DhcpDomain"); + ipAddressString = PhQueryRegistryString(keyHandle, L"DhcpIPAddress"); + subnetAddressString = PhQueryRegistryString(keyHandle, L"DhcpSubnetMask"); + gatewayAddressString = PhQueryRegistryString(keyHandle, L"DhcpDefaultGateway"); + + if (PhIsNullOrEmptyString(domainString)) + PhMoveReference(&domainString, PhQueryRegistryString(keyHandle, L"Domain")); + + if (PhIsNullOrEmptyString(ipAddressString)) + PhMoveReference(&ipAddressString, PhQueryRegistryString(keyHandle, L"IPAddress")); + + if (PhIsNullOrEmptyString(subnetAddressString)) + PhMoveReference(&subnetAddressString, PhQueryRegistryString(keyHandle, L"SubnetMask")); + + if (PhIsNullOrEmptyString(gatewayAddressString)) + PhMoveReference(&gatewayAddressString, PhQueryRegistryString(keyHandle, L"DefaultGateway")); + + //PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_CONNECTIVITY, 1, internet ? L"Internet" : L"Local"); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_IPADDRESS, 1, PhIsNullOrEmptyString(ipAddressString) ? L"" : ipAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_SUBNET, 1, PhIsNullOrEmptyString(subnetAddressString) ? L"" : subnetAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_GATEWAY, 1, PhIsNullOrEmptyString(gatewayAddressString) ? L"" : gatewayAddressString->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_DOMAIN, 1, PhIsNullOrEmptyString(domainString) ? L"" : domainString->Buffer); + + PhClearReference(&domainString); + PhClearReference(&ipAddressString); + PhClearReference(&subnetAddressString); + PhClearReference(&gatewayAddressString); + + NtClose(keyHandle); + } + + PhDereferenceObject(keyNameIpV4); + } +} + +VOID NETIOAPI_API_ NetAdapterChangeCallback( + _In_ PVOID CallerContext, + _In_opt_ PMIB_IPINTERFACE_ROW Row, + _In_ MIB_NOTIFICATION_TYPE NotificationType + ) +{ + PDV_NETADAPTER_DETAILS_CONTEXT context = CallerContext; + + if (NotificationType == MibInitialNotification) + { + NetAdapterLookupConfig(context); + } + else if (NotificationType == MibParameterNotification) + { + if (Row->InterfaceLuid.Value = context->AdapterId.InterfaceLuid.Value) + { + NetAdapterLookupConfig(context); + } + } +} + +VOID NetAdapterUpdateDetails( + _Inout_ PDV_NETADAPTER_DETAILS_CONTEXT Context + ) +{ + ULONG64 interfaceLinkSpeed = 0; + ULONG64 interfaceRcvSpeed = 0; + ULONG64 interfaceXmitSpeed = 0; + NDIS_STATISTICS_INFO interfaceStats = { 0 }; + NDIS_MEDIA_CONNECT_STATE mediaState = MediaConnectStateUnknown; + HANDLE deviceHandle = NULL; + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_NDIS)) + { + // Create the handle to the network device + if (NT_SUCCESS(NetworkAdapterCreateHandle(&deviceHandle, Context->AdapterId.InterfaceGuid))) + { + if (!Context->CheckedDeviceSupport) + { + // Check the network adapter supports the OIDs we're going to be using. + if (NetworkAdapterQuerySupported(deviceHandle)) + { + Context->DeviceSupported = TRUE; + } + + Context->CheckedDeviceSupport = TRUE; + } + + if (!Context->DeviceSupported) + { + // Device is faulty. Close the handle so we can fallback to GetIfEntry. + NtClose(deviceHandle); + deviceHandle = NULL; + } + } + } + + if (deviceHandle) + { + NDIS_LINK_STATE interfaceState; + + NetworkAdapterQueryStatistics(deviceHandle, &interfaceStats); + + if (NT_SUCCESS(NetworkAdapterQueryLinkState(deviceHandle, &interfaceState))) + { + mediaState = interfaceState.MediaConnectState; + interfaceLinkSpeed = interfaceState.XmitLinkSpeed; + } + else + { + NetworkAdapterQueryLinkSpeed(deviceHandle, &interfaceLinkSpeed); + } + + NtClose(deviceHandle); + } + else + { + if (WindowsVersion >= WINDOWS_VISTA && GetIfEntry2) + { + MIB_IF_ROW2 interfaceRow; + + if (QueryInterfaceRowVista(&Context->AdapterId, &interfaceRow)) + { + interfaceStats.ifInDiscards = interfaceRow.InDiscards; + interfaceStats.ifInErrors = interfaceRow.InErrors; + interfaceStats.ifHCInOctets = interfaceRow.InOctets; + interfaceStats.ifHCInUcastPkts = interfaceRow.InUcastPkts; + //interfaceStats.ifHCInMulticastPkts; + //interfaceStats.ifHCInBroadcastPkts; + interfaceStats.ifHCOutOctets = interfaceRow.OutOctets; + interfaceStats.ifHCOutUcastPkts = interfaceRow.OutUcastPkts; + //interfaceStats.ifHCOutMulticastPkts; + //interfaceStats.ifHCOutBroadcastPkts; + interfaceStats.ifOutErrors = interfaceRow.OutErrors; + interfaceStats.ifOutDiscards = interfaceRow.OutDiscards; + interfaceStats.ifHCInUcastOctets = interfaceRow.InUcastOctets; + interfaceStats.ifHCInMulticastOctets = interfaceRow.InMulticastOctets; + interfaceStats.ifHCInBroadcastOctets = interfaceRow.InBroadcastOctets; + interfaceStats.ifHCOutUcastOctets = interfaceRow.OutUcastOctets; + interfaceStats.ifHCOutMulticastOctets = interfaceRow.OutMulticastOctets; + interfaceStats.ifHCOutBroadcastOctets = interfaceRow.OutBroadcastOctets; + //interfaceRow.InNUcastPkts; + //interfaceRow.InUnknownProtos; + //interfaceRow.OutNUcastPkts; + //interfaceRow.OutQLen; + + mediaState = interfaceRow.MediaConnectState; + interfaceLinkSpeed = interfaceRow.TransmitLinkSpeed; + } + } + else + { + MIB_IFROW interfaceRow; + + if (QueryInterfaceRowXP(&Context->AdapterId, &interfaceRow)) + { + interfaceStats.ifInDiscards = interfaceRow.dwInDiscards; + interfaceStats.ifInErrors = interfaceRow.dwInErrors; + interfaceStats.ifHCInOctets = interfaceRow.dwInOctets; + interfaceStats.ifHCInUcastPkts = interfaceRow.dwInUcastPkts; + //interfaceStats.ifHCInMulticastPkts; + //interfaceStats.ifHCInBroadcastPkts; + interfaceStats.ifHCOutOctets = interfaceRow.dwOutOctets; + interfaceStats.ifHCOutUcastPkts = interfaceRow.dwOutUcastPkts; + //interfaceStats.ifHCOutMulticastPkts; + //interfaceStats.ifHCOutBroadcastPkts; + interfaceStats.ifOutErrors = interfaceRow.dwOutErrors; + interfaceStats.ifOutDiscards = interfaceRow.dwOutDiscards; + //interfaceStats.ifHCInUcastOctets; + //interfaceStats.ifHCInMulticastOctets; + //interfaceStats.ifHCInBroadcastOctets; + //interfaceStats.ifHCOutUcastOctets; + //interfaceStats.ifHCOutMulticastOctets; + //interfaceStats.ifHCOutBroadcastOctets; + //interfaceRow.InNUcastPkts; + //interfaceRow.InUnknownProtos; + //interfaceRow.OutNUcastPkts; + //interfaceRow.OutQLen; + + mediaState = interfaceRow.dwOperStatus == IF_OPER_STATUS_OPERATIONAL ? MediaConnectStateConnected : MediaConnectStateDisconnected; + interfaceLinkSpeed = interfaceRow.dwSpeed; + } + } + } + + interfaceRcvSpeed = interfaceStats.ifHCInOctets - Context->LastDetailsInboundValue; + interfaceXmitSpeed = interfaceStats.ifHCOutOctets - Context->LastDetailsIOutboundValue; + //interfaceRcvUnicastSpeed = interfaceStats.ifHCInUcastOctets - Context->LastDetailsInboundUnicastValue; + //interfaceXmitUnicastSpeed = interfaceStats.ifHCOutUcastOctets - Context->LastDetailsIOutboundUnicastValue; + + if (!Context->HaveFirstSample) + { + interfaceRcvSpeed = 0; + interfaceXmitSpeed = 0; + Context->HaveFirstSample = TRUE; + } + + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_STATE, 1, mediaState == MediaConnectStateConnected ? L"Connected" : L"Disconnected"); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_LINKSPEED, 1, PhaFormatString(L"%s/s", PhaFormatSize(interfaceLinkSpeed / BITS_IN_ONE_BYTE, -1)->Buffer)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_SENT, 1, PhaFormatSize(interfaceStats.ifHCOutOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_RECEIVED, 1, PhaFormatSize(interfaceStats.ifHCInOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_TOTAL, 1, PhaFormatSize(interfaceStats.ifHCInOctets + interfaceStats.ifHCOutOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_SENDING, 1, interfaceXmitSpeed != 0 ? PhaFormatString(L"%s/s", PhaFormatSize(interfaceXmitSpeed, -1)->Buffer)->Buffer : L""); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_RECEIVING, 1, interfaceRcvSpeed != 0 ? PhaFormatString(L"%s/s", PhaFormatSize(interfaceRcvSpeed, -1)->Buffer)->Buffer : L""); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UTILIZATION, 1, PhaFormatString(L"%.2f%%", (FLOAT)(interfaceRcvSpeed + interfaceXmitSpeed) / (interfaceLinkSpeed / BITS_IN_ONE_BYTE) * 100)->Buffer); + + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_SENTPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCOutUcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_RECVPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInUcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_TOTALPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInUcastPkts + interfaceStats.ifHCOutUcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_SENT, 1, PhaFormatSize(interfaceStats.ifHCOutUcastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVED, 1, PhaFormatSize(interfaceStats.ifHCInUcastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_TOTAL, 1, PhaFormatSize(interfaceStats.ifHCInUcastOctets + interfaceStats.ifHCOutUcastOctets, -1)->Buffer); + //PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_SENDING, 1, interfaceXmitUnicastSpeed != 0 ? PhaFormatString(L"%s/s", PhaFormatSize(interfaceXmitUnicastSpeed, -1)->Buffer)->Buffer : L""); + //PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_RECEIVING, 1, interfaceRcvUnicastSpeed != 0 ? PhaFormatString(L"%s/s", PhaFormatSize(interfaceRcvUnicastSpeed, -1)->Buffer)->Buffer : L""); + //PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_UNICAST_UTILIZATION, 1, PhaFormatString(L"%.2f%%", utilization2)->Buffer); + + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_SENTPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCOutBroadcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_RECVPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInBroadcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_TOTALPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInBroadcastPkts + interfaceStats.ifHCOutBroadcastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_SENT, 1, PhaFormatSize(interfaceStats.ifHCOutBroadcastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_RECEIVED, 1, PhaFormatSize(interfaceStats.ifHCInBroadcastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_BROADCAST_TOTAL, 1, PhaFormatSize(interfaceStats.ifHCInBroadcastOctets + interfaceStats.ifHCOutBroadcastOctets, -1)->Buffer); + + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_SENTPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCOutMulticastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_RECVPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInMulticastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_TOTALPKTS, 1, PhaFormatUInt64(interfaceStats.ifHCInMulticastPkts + interfaceStats.ifHCOutMulticastPkts, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_SENT, 1, PhaFormatSize(interfaceStats.ifHCOutMulticastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_RECEIVED, 1, PhaFormatSize(interfaceStats.ifHCInMulticastOctets, -1)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_MULTICAST_TOTAL, 1, PhaFormatSize(interfaceStats.ifHCInMulticastOctets + interfaceStats.ifHCOutMulticastOctets, -1)->Buffer); + + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_SENTPKTS, 1, PhaFormatUInt64(interfaceStats.ifOutErrors, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_RECVPKTS, 1, PhaFormatUInt64(interfaceStats.ifInErrors, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_TOTALPKTS, 1, PhaFormatUInt64(interfaceStats.ifInErrors + interfaceStats.ifOutErrors, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_SENT, 1, PhaFormatUInt64(interfaceStats.ifOutDiscards, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_RECEIVED, 1, PhaFormatUInt64(interfaceStats.ifInDiscards, TRUE)->Buffer); + PhSetListViewSubItem(Context->ListViewHandle, NETADAPTER_DETAILS_INDEX_ERRORS_TOTAL, 1, PhaFormatUInt64(interfaceStats.ifInDiscards + interfaceStats.ifOutDiscards, TRUE)->Buffer); + + Context->LastDetailsInboundValue = interfaceStats.ifHCInOctets; + Context->LastDetailsIOutboundValue = interfaceStats.ifHCOutOctets; + //Context->LastDetailsInboundUnicastValue = interfaceStats.ifHCInUcastOctets; + //Context->LastDetailsIOutboundUnicastValue = interfaceStats.ifHCOutUcastOctets; +} + +INT_PTR CALLBACK NetAdapterDetailsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_NETADAPTER_DETAILS_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_NETADAPTER_DETAILS_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_NETADAPTER_DETAILS_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + context->WindowHandle = hwndDlg; + context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DETAILS_LIST); + + if (context->AdapterName) + SetWindowText(hwndDlg, context->AdapterName->Buffer); + else + SetWindowText(hwndDlg, L"Unknown network adapter"); + + PhCenterWindow(hwndDlg, context->ParentHandle); + + PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); + PhSetControlTheme(context->ListViewHandle, L"explorer"); + PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 315, L"Property"); + PhAddListViewColumn(context->ListViewHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Value"); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, context->ListViewHandle, NULL, PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT); + + NetAdapterAddListViewItemGroups(context->ListViewHandle); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + NetAdapterProcessesUpdatedHandler, + context, + &context->ProcessesUpdatedRegistration + ); + + NetAdapterLookupConfig(context); + NetAdapterUpdateDetails(context); + + if (WindowsVersion >= WINDOWS_VISTA && NotifyIpInterfaceChange) + { + NotifyIpInterfaceChange( + AF_UNSPEC, + NetAdapterChangeCallback, + context, + FALSE, + &context->NotifyHandle + ); + } + } + break; + case WM_DESTROY: + { + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &context->ProcessesUpdatedRegistration + ); + + if (WindowsVersion >= WINDOWS_VISTA && CancelMibChangeNotify2 && context->NotifyHandle) + { + CancelMibChangeNotify2(context->NotifyHandle); + } + + PhDeleteLayoutManager(&context->LayoutManager); + PostQuitMessage(0); + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SHOWDIALOG: + { + if (IsMinimized(hwndDlg)) + ShowWindow(hwndDlg, SW_RESTORE); + else + ShowWindow(hwndDlg, SW_SHOW); + + SetForegroundWindow(hwndDlg); + } + break; + case UPDATE_MSG: + { + NetAdapterUpdateDetails(context); + } + break; + } + + return FALSE; +} + +VOID FreeNetAdapterDetailsContext( + _In_ PDV_NETADAPTER_DETAILS_CONTEXT Context + ) +{ + DeleteNetAdapterId(&Context->AdapterId); + PhClearReference(&Context->AdapterName); + PhFree(Context); +} + +NTSTATUS ShowNetAdapterDetailsDialogThread( + _In_ PVOID Parameter + ) +{ + PDV_NETADAPTER_DETAILS_CONTEXT context = (PDV_NETADAPTER_DETAILS_CONTEXT)Parameter; + BOOL result; + MSG message; + HWND dialogHandle; + PH_AUTO_POOL autoPool; + + PhInitializeAutoPool(&autoPool); + + dialogHandle = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_NETADAPTER_DETAILS), + NULL, + NetAdapterDetailsDlgProc, + (LPARAM)context + ); + + PostMessage(dialogHandle, WM_SHOWDIALOG, 0, 0); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!IsDialogMessage(dialogHandle, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + + FreeNetAdapterDetailsContext(context); + + return STATUS_SUCCESS; +} + +VOID ShowNetAdapterDetailsDialog( + _In_ PDV_NETADAPTER_SYSINFO_CONTEXT Context + ) +{ + HANDLE dialogThread = NULL; + PDV_NETADAPTER_DETAILS_CONTEXT context; + + context = PhAllocate(sizeof(DV_NETADAPTER_DETAILS_CONTEXT)); + memset(context, 0, sizeof(DV_NETADAPTER_DETAILS_CONTEXT)); + + context->ParentHandle = Context->WindowHandle; + PhSetReference(&context->AdapterName, Context->AdapterEntry->AdapterName); + CopyNetAdapterId(&context->AdapterId, &Context->AdapterEntry->Id); + + if (dialogThread = PhCreateThread(0, ShowNetAdapterDetailsDialogThread, context)) + NtClose(dialogThread); + else + FreeNetAdapterDetailsContext(context); +} \ No newline at end of file diff --git a/plugins/HardwareDevices/netgraph.c b/plugins/HardwareDevices/netgraph.c new file mode 100644 index 0000000..d837e0b --- /dev/null +++ b/plugins/HardwareDevices/netgraph.c @@ -0,0 +1,549 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" + +VOID NetAdapterUpdateGraphs( + _Inout_ PDV_NETADAPTER_SYSINFO_CONTEXT Context + ) +{ + Context->GraphState.Valid = FALSE; + Context->GraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->GraphHandle, 1); + Graph_Draw(Context->GraphHandle); + Graph_UpdateTooltip(Context->GraphHandle); + InvalidateRect(Context->GraphHandle, NULL, FALSE); +} + +VOID NetAdapterUpdatePanel( + _Inout_ PDV_NETADAPTER_SYSINFO_CONTEXT Context + ) +{ + ULONG64 inOctets = 0; + ULONG64 outOctets = 0; + ULONG64 linkSpeed = 0; + NDIS_MEDIA_CONNECT_STATE mediaState = MediaConnectStateUnknown; + HANDLE deviceHandle = NULL; + + if (PhGetIntegerSetting(SETTING_NAME_ENABLE_NDIS)) + { + // Create the handle to the network device + if (NT_SUCCESS(NetworkAdapterCreateHandle(&deviceHandle, Context->AdapterEntry->Id.InterfaceGuid))) + { + if (!Context->AdapterEntry->CheckedDeviceSupport) + { + // Check the network adapter supports the OIDs we're going to be using. + if (NetworkAdapterQuerySupported(deviceHandle)) + { + Context->AdapterEntry->DeviceSupported = TRUE; + } + + Context->AdapterEntry->CheckedDeviceSupport = TRUE; + } + + if (!Context->AdapterEntry->DeviceSupported) + { + // Device is faulty. Close the handle so we can fallback to GetIfEntry. + NtClose(deviceHandle); + deviceHandle = NULL; + } + } + } + + if (deviceHandle) + { + NDIS_STATISTICS_INFO interfaceStats; + NDIS_LINK_STATE interfaceState; + + if (NT_SUCCESS(NetworkAdapterQueryStatistics(deviceHandle, &interfaceStats))) + { + if (!(interfaceStats.SupportedStatistics & NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV)) + inOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_RCV); + else + inOctets = interfaceStats.ifHCInOctets; + + if (!(interfaceStats.SupportedStatistics & NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT)) + outOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_XMIT); + else + outOctets = interfaceStats.ifHCOutOctets; + } + else + { + // Note: The above code fails for some drivers that don't implement statistics (even though statistics are mandatory). + // NDIS handles these two OIDs for all miniport drivers and we can use these for those special cases. + + // https://msdn.microsoft.com/en-us/library/ff569443.aspx + inOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_RCV); + + // https://msdn.microsoft.com/en-us/library/ff569445.aspx + outOctets = NetworkAdapterQueryValue(deviceHandle, OID_GEN_BYTES_XMIT); + } + + if (NT_SUCCESS(NetworkAdapterQueryLinkState(deviceHandle, &interfaceState))) + { + mediaState = interfaceState.MediaConnectState; + linkSpeed = interfaceState.XmitLinkSpeed; + } + else + { + NetworkAdapterQueryLinkSpeed(deviceHandle, &linkSpeed); + } + + NtClose(deviceHandle); + } + else if (WindowsVersion >= WINDOWS_VISTA && GetIfEntry2) + { + MIB_IF_ROW2 interfaceRow; + + if (QueryInterfaceRowVista(&Context->AdapterEntry->Id, &interfaceRow)) + { + inOctets = interfaceRow.InOctets; + outOctets = interfaceRow.OutOctets; + mediaState = interfaceRow.MediaConnectState; + linkSpeed = interfaceRow.TransmitLinkSpeed; + } + } + else + { + MIB_IFROW interfaceRow; + + if (QueryInterfaceRowXP(&Context->AdapterEntry->Id, &interfaceRow)) + { + inOctets = interfaceRow.dwInOctets; + outOctets = interfaceRow.dwOutOctets; + mediaState = interfaceRow.dwOperStatus == IF_OPER_STATUS_OPERATIONAL ? MediaConnectStateConnected : MediaConnectStateUnknown; + linkSpeed = interfaceRow.dwSpeed; + } + } + + if (mediaState == MediaConnectStateConnected) + SetDlgItemText(Context->PanelWindowHandle, IDC_LINK_STATE, L"Connected"); + else + SetDlgItemText(Context->PanelWindowHandle, IDC_LINK_STATE, L"Disconnected"); + + SetDlgItemText(Context->PanelWindowHandle, IDC_LINK_SPEED, PhaFormatString(L"%s/s", PhaFormatSize(linkSpeed / BITS_IN_ONE_BYTE, -1)->Buffer)->Buffer); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BSENT, PhaFormatSize(outOctets, -1)->Buffer); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BRECEIVED, PhaFormatSize(inOctets, -1)->Buffer); + SetDlgItemText(Context->PanelWindowHandle, IDC_STAT_BTOTAL, PhaFormatSize(inOctets + outOctets, -1)->Buffer); +} + +VOID UpdateNetAdapterDialog( + _Inout_ PDV_NETADAPTER_SYSINFO_CONTEXT Context + ) +{ + if (Context->AdapterEntry->AdapterName) + SetDlgItemText(Context->WindowHandle, IDC_ADAPTERNAME, Context->AdapterEntry->AdapterName->Buffer); + else + SetDlgItemText(Context->WindowHandle, IDC_ADAPTERNAME, L"Unknown network adapter"); + + NetAdapterUpdateGraphs(Context); + NetAdapterUpdatePanel(Context); +} + +INT_PTR CALLBACK NetAdapterPanelDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_NETADAPTER_SYSINFO_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_NETADAPTER_SYSINFO_CONTEXT)lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_NETADAPTER_SYSINFO_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_DETAILS: + ShowNetAdapterDetailsDialog(context); + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK NetAdapterDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_NETADAPTER_SYSINFO_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_NETADAPTER_SYSINFO_CONTEXT)lParam; + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_NETADAPTER_SYSINFO_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + PhDeleteLayoutManager(&context->LayoutManager); + PhDeleteGraphState(&context->GraphState); + + if (context->GraphHandle) + DestroyWindow(context->GraphHandle); + + if (context->PanelWindowHandle) + DestroyWindow(context->PanelWindowHandle); + + RemoveProp(hwndDlg, L"Context"); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_LAYOUT_ITEM graphItem; + PPH_LAYOUT_ITEM panelItem; + + context->WindowHandle = hwndDlg; + + PhInitializeGraphState(&context->GraphState); + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ADAPTERNAME), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE); + graphItem = PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_GRAPH_LAYOUT), NULL, PH_ANCHOR_ALL); + panelItem = PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LAYOUT), NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + + SendMessage(GetDlgItem(hwndDlg, IDC_ADAPTERNAME), WM_SETFONT, (WPARAM)context->SysinfoSection->Parameters->LargeFont, FALSE); + + if (context->AdapterEntry->AdapterName) + SetDlgItemText(hwndDlg, IDC_ADAPTERNAME, context->AdapterEntry->AdapterName->Buffer); + else + SetDlgItemText(hwndDlg, IDC_ADAPTERNAME, L"Unknown network adapter"); + + context->PanelWindowHandle = CreateDialogParam(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_NETADAPTER_PANEL), hwndDlg, NetAdapterPanelDialogProc, (LPARAM)context); + ShowWindow(context->PanelWindowHandle, SW_SHOW); + PhAddLayoutItemEx(&context->LayoutManager, context->PanelWindowHandle, NULL, PH_ANCHOR_LEFT | PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM, panelItem->Margin); + + // Create the graph control. + context->GraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(context->GraphHandle, TRUE); + + PhAddLayoutItemEx(&context->LayoutManager, context->GraphHandle, NULL, PH_ANCHOR_ALL, graphItem->Margin); + + UpdateNetAdapterDialog(context); + } + break; + case WM_SIZE: + PhLayoutManagerLayout(&context->LayoutManager); + break; + case WM_NOTIFY: + { + NMHDR* header = (NMHDR*)lParam; + + if (header->hwndFrom == context->GraphHandle) + { + switch (header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + context->SysinfoSection->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + + PhGraphStateGetDrawInfo( + &context->GraphState, + getDrawInfo, + context->AdapterEntry->InboundBuffer.Count + ); + + if (!context->GraphState.Valid) + { + ULONG i; + FLOAT max = 4 * 1024; // Minimum scaling 4KB + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + context->GraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->AdapterEntry->InboundBuffer, i); + context->GraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->AdapterEntry->OutboundBuffer, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + context->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + context->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + context->GraphState.Valid = TRUE; + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (context->GraphState.TooltipIndex != getTooltipText->Index) + { + ULONG64 adapterInboundValue = PhGetItemCircularBuffer_ULONG64( + &context->AdapterEntry->InboundBuffer, + getTooltipText->Index + ); + + ULONG64 adapterOutboundValue = PhGetItemCircularBuffer_ULONG64( + &context->AdapterEntry->OutboundBuffer, + getTooltipText->Index + ); + + PhMoveReference(&context->GraphState.TooltipText, PhFormatString( + L"R: %s\nS: %s\n%s", + PhaFormatSize(adapterInboundValue, -1)->Buffer, + PhaFormatSize(adapterOutboundValue, -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + } + + getTooltipText->Text = context->GraphState.TooltipText->sr; + } + } + break; + } + } + } + break; + case UPDATE_MSG: + { + UpdateNetAdapterDialog(context); + } + break; + } + + return FALSE; +} + +BOOLEAN NetAdapterSectionCallback( + _In_ PPH_SYSINFO_SECTION Section, + _In_ PH_SYSINFO_SECTION_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2 + ) +{ + PDV_NETADAPTER_SYSINFO_CONTEXT context = (PDV_NETADAPTER_SYSINFO_CONTEXT)Section->Context; + + switch (Message) + { + case SysInfoCreate: + return TRUE; + case SysInfoDestroy: + { + PhDereferenceObject(context->AdapterEntry); + PhDereferenceObject(context->SectionName); + PhFree(context); + } + return TRUE; + case SysInfoTick: + { + if (context->WindowHandle) + PostMessage(context->WindowHandle, UPDATE_MSG, 0, 0); + } + return TRUE; + case SysInfoCreateDialog: + { + PPH_SYSINFO_CREATE_DIALOG createDialog = (PPH_SYSINFO_CREATE_DIALOG)Parameter1; + + createDialog->Instance = PluginInstance->DllBase; + createDialog->Template = MAKEINTRESOURCE(IDD_NETADAPTER_DIALOG); + createDialog->DialogProc = NetAdapterDialogProc; + createDialog->Parameter = context; + } + return TRUE; + case SysInfoGraphGetDrawInfo: + { + PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)Parameter1; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y | PH_GRAPH_LABEL_MAX_Y | PH_GRAPH_USE_LINE_2; + Section->Parameters->ColorSetupFunction(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + PhGetDrawInfoGraphBuffers(&Section->GraphState.Buffers, drawInfo, context->AdapterEntry->InboundBuffer.Count); + + if (!Section->GraphState.Valid) + { + FLOAT max = 4 * 1024; // Minimum scaling 4KB + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + FLOAT data2; + + Section->GraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->AdapterEntry->InboundBuffer, i); + Section->GraphState.Data2[i] = data2 = (FLOAT)PhGetItemCircularBuffer_ULONG64(&context->AdapterEntry->OutboundBuffer, i); + + if (max < data1 + data2) + max = data1 + data2; + } + + if (max != 0) + { + // Scale the data. + + PhDivideSinglesBySingle( + Section->GraphState.Data1, + max, + drawInfo->LineDataCount + ); + PhDivideSinglesBySingle( + Section->GraphState.Data2, + max, + drawInfo->LineDataCount + ); + } + + drawInfo->LabelYFunction = PhSiSizeLabelYFunction; + drawInfo->LabelYFunctionParameter = max; + + Section->GraphState.Valid = TRUE; + } + } + return TRUE; + case SysInfoGraphGetTooltipText: + { + PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT getTooltipText = (PPH_SYSINFO_GRAPH_GET_TOOLTIP_TEXT)Parameter1; + + ULONG64 adapterInboundValue = PhGetItemCircularBuffer_ULONG64( + &context->AdapterEntry->InboundBuffer, + getTooltipText->Index + ); + + ULONG64 adapterOutboundValue = PhGetItemCircularBuffer_ULONG64( + &context->AdapterEntry->OutboundBuffer, + getTooltipText->Index + ); + + PhMoveReference(&Section->GraphState.TooltipText, PhFormatString( + L"R: %s\nS: %s\n%s", + PhaFormatSize(adapterInboundValue, -1)->Buffer, + PhaFormatSize(adapterOutboundValue, -1)->Buffer, + ((PPH_STRING)PH_AUTO(PhGetStatisticsTimeString(NULL, getTooltipText->Index)))->Buffer + )); + + getTooltipText->Text = Section->GraphState.TooltipText->sr; + } + return TRUE; + case SysInfoGraphDrawPanel: + { + PPH_SYSINFO_DRAW_PANEL drawPanel = (PPH_SYSINFO_DRAW_PANEL)Parameter1; + + PhSetReference(&drawPanel->Title, context->AdapterEntry->AdapterName); + drawPanel->SubTitle = PhFormatString( + L"R: %s\nS: %s", + PhaFormatSize(context->AdapterEntry->InboundValue, -1)->Buffer, + PhaFormatSize(context->AdapterEntry->OutboundValue, -1)->Buffer + ); + + if (!drawPanel->Title) + drawPanel->Title = PhCreateString(L"Unknown network adapter"); + } + return TRUE; + } + + return FALSE; +} + +VOID NetAdapterSysInfoInitializing( + _In_ PPH_PLUGIN_SYSINFO_POINTERS Pointers, + _In_ _Assume_refs_(1) PDV_NETADAPTER_ENTRY AdapterEntry + ) +{ + PH_SYSINFO_SECTION section; + PDV_NETADAPTER_SYSINFO_CONTEXT context; + + context = (PDV_NETADAPTER_SYSINFO_CONTEXT)PhAllocate(sizeof(DV_NETADAPTER_SYSINFO_CONTEXT)); + memset(context, 0, sizeof(DV_NETADAPTER_SYSINFO_CONTEXT)); + memset(§ion, 0, sizeof(PH_SYSINFO_SECTION)); + + context->AdapterEntry = AdapterEntry; + context->SectionName = PhConcatStrings2(L"NetAdapter ", AdapterEntry->Id.InterfaceGuid->Buffer); + + section.Context = context; + section.Callback = NetAdapterSectionCallback; + section.Name = context->SectionName->sr; + + context->SysinfoSection = Pointers->CreateSection(§ion); +} \ No newline at end of file diff --git a/plugins/HardwareDevices/netoptions.c b/plugins/HardwareDevices/netoptions.c new file mode 100644 index 0000000..4f699dd --- /dev/null +++ b/plugins/HardwareDevices/netoptions.c @@ -0,0 +1,736 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#define INITGUID +#include "devices.h" +#include +#include + +#define ITEM_CHECKED (INDEXTOSTATEIMAGEMASK(2)) +#define ITEM_UNCHECKED (INDEXTOSTATEIMAGEMASK(1)) + +typedef struct _NET_ENUM_ENTRY +{ + BOOLEAN DevicePresent; + IF_LUID DeviceLuid; + PPH_STRING DeviceGuid; + PPH_STRING DeviceName; +} NET_ENUM_ENTRY, *PNET_ENUM_ENTRY; + +static int __cdecl AdapterEntryCompareFunction( + _In_ const void *elem1, + _In_ const void *elem2 + ) +{ + PNET_ENUM_ENTRY entry1 = *(PNET_ENUM_ENTRY *)elem1; + PNET_ENUM_ENTRY entry2 = *(PNET_ENUM_ENTRY *)elem2; + + return uint64cmp(entry1->DeviceLuid.Value, entry2->DeviceLuid.Value); +} + +VOID NetAdaptersLoadList( + VOID + ) +{ + PPH_STRING settingsString; + PH_STRINGREF remaining; + + settingsString = PhaGetStringSetting(SETTING_NAME_INTERFACE_LIST); + remaining = settingsString->sr; + + while (remaining.Length != 0) + { + ULONG64 ifindex; + ULONG64 luid64; + PH_STRINGREF part1; + PH_STRINGREF part2; + PH_STRINGREF part3; + IF_LUID ifLuid; + DV_NETADAPTER_ID id; + PDV_NETADAPTER_ENTRY entry; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, ',', &part1, &remaining); + PhSplitStringRefAtChar(&remaining, ',', &part2, &remaining); + PhSplitStringRefAtChar(&remaining, ',', &part3, &remaining); + + PhStringToInteger64(&part1, 10, &ifindex); + PhStringToInteger64(&part2, 10, &luid64); + + ifLuid.Value = luid64; + InitializeNetAdapterId(&id, (IF_INDEX)ifindex, ifLuid, PhCreateString2(&part3)); + entry = CreateNetAdapterEntry(&id); + DeleteNetAdapterId(&id); + + entry->UserReference = TRUE; + } +} + +VOID NetAdaptersSaveList( + VOID + ) +{ + PH_STRING_BUILDER stringBuilder; + PPH_STRING settingsString; + + PhInitializeStringBuilder(&stringBuilder, 260); + + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + PDV_NETADAPTER_ENTRY entry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!entry) + continue; + + if (entry->UserReference) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%lu,%I64u,%s,", + entry->Id.InterfaceIndex, // This value is UNSAFE and may change after reboot. + entry->Id.InterfaceLuid.Value, // This value is SAFE and does not change (Vista+). + entry->Id.InterfaceGuid->Buffer + ); + } + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); + PhSetStringSetting2(SETTING_NAME_INTERFACE_LIST, &settingsString->sr); +} + +BOOLEAN FindAdapterEntry( + _In_ PDV_NETADAPTER_ID Id, + _In_ BOOLEAN RemoveUserReference + ) +{ + BOOLEAN found = FALSE; + + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + PDV_NETADAPTER_ENTRY currentEntry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!currentEntry) + continue; + + found = EquivalentNetAdapterId(¤tEntry->Id, Id); + + if (found) + { + if (RemoveUserReference) + { + if (currentEntry->UserReference) + { + PhDereferenceObjectDeferDelete(currentEntry); + currentEntry->UserReference = FALSE; + } + } + + PhDereferenceObjectDeferDelete(currentEntry); + + break; + } + else + { + PhDereferenceObjectDeferDelete(currentEntry); + } + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + + return found; +} + +VOID AddNetworkAdapterToListView( + _In_ PDV_NETADAPTER_CONTEXT Context, + _In_ BOOLEAN AdapterPresent, + _In_ IF_INDEX IfIndex, + _In_ IF_LUID Luid, + _In_ PPH_STRING Guid, + _In_ PPH_STRING Description + ) +{ + DV_NETADAPTER_ID adapterId; + INT lvItemIndex; + BOOLEAN found = FALSE; + PDV_NETADAPTER_ID newId = NULL; + + InitializeNetAdapterId(&adapterId, IfIndex, Luid, NULL); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + PDV_NETADAPTER_ENTRY entry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!entry) + continue; + + if (EquivalentNetAdapterId(&entry->Id, &adapterId)) + { + newId = PhAllocate(sizeof(DV_NETADAPTER_ID)); + CopyNetAdapterId(newId, &entry->Id); + + if (entry->UserReference) + found = TRUE; + } + + PhDereferenceObjectDeferDelete(entry); + + if (newId) + break; + } + + if (!newId) + { + newId = PhAllocate(sizeof(DV_NETADAPTER_ID)); + CopyNetAdapterId(newId, &adapterId); + PhMoveReference(&newId->InterfaceGuid, Guid); + } + + lvItemIndex = AddListViewItemGroupId( + Context->ListViewHandle, + AdapterPresent ? 0 : 1, + MAXINT, + Description->Buffer, + newId + ); + + if (found) + ListView_SetItemState(Context->ListViewHandle, lvItemIndex, ITEM_CHECKED, LVIS_STATEIMAGEMASK); + + DeleteNetAdapterId(&adapterId); +} + +VOID FreeListViewAdapterEntries( + _In_ PDV_NETADAPTER_CONTEXT Context + ) +{ + ULONG index = -1; + + while ((index = PhFindListViewItemByFlags( + Context->ListViewHandle, + index, + LVNI_ALL + )) != -1) + { + PDV_NETADAPTER_ID param; + + if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) + { + DeleteNetAdapterId(param); + PhFree(param); + } + } +} + +VOID FindNetworkAdapters( + _In_ PDV_NETADAPTER_CONTEXT Context + ) +{ + if (Context->UseAlternateMethod) + { + ULONG bufferLength = 0; + PVOID buffer = NULL; + ULONG flags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + + if (WindowsVersion >= WINDOWS_VISTA) + { + flags |= GAA_FLAG_INCLUDE_ALL_INTERFACES; + } + + if (GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &bufferLength) != ERROR_BUFFER_OVERFLOW) + return; + + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (GetAdaptersAddresses(AF_UNSPEC, flags, NULL, buffer, &bufferLength) == ERROR_SUCCESS) + { + Context->EnumeratingAdapters = TRUE; + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (PIP_ADAPTER_ADDRESSES i = buffer; i; i = i->Next) + { + PPH_STRING description; + + if (description = PhCreateString(i->Description)) + { + AddNetworkAdapterToListView( + Context, + TRUE, + i->IfIndex, + i->Luid, + PhConvertMultiByteToUtf16(i->AdapterName), + description + ); + + PhDereferenceObject(description); + } + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + Context->EnumeratingAdapters = FALSE; + } + + PhFree(buffer); + } + else + { + PPH_LIST deviceList; + HDEVINFO deviceInfoHandle; + SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + + if ((deviceInfoHandle = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_NET, + NULL, + NULL, + DIGCF_DEVICEINTERFACE + )) == INVALID_HANDLE_VALUE) + { + return; + } + + deviceList = PH_AUTO(PhCreateList(1)); + + for (ULONG i = 0; SetupDiEnumDeviceInfo(deviceInfoHandle, i, &deviceInfoData); i++) + { + HANDLE keyHandle; + DEVPROPTYPE devicePropertyType; + WCHAR adapterDescription[MAX_PATH] = L""; + + // DEVPKEY_Device_DeviceDesc doesn't give us the full adapter name. + // DEVPKEY_Device_FriendlyName does give us the full adapter name but is only + // supported on Windows 8 and above. + // We use our NetworkAdapterQueryName function to query the full adapter name + // from the NDIS driver directly, if that fails then we use one of the above properties. + if (!SetupDiGetDeviceProperty( + deviceInfoHandle, + &deviceInfoData, + WindowsVersion >= WINDOWS_8 ? &DEVPKEY_Device_FriendlyName : &DEVPKEY_Device_DeviceDesc, + &devicePropertyType, + (PBYTE)adapterDescription, + ARRAYSIZE(adapterDescription), + NULL, + 0 + )) + { + continue; + } + + if (keyHandle = SetupDiOpenDevRegKey( + deviceInfoHandle, + &deviceInfoData, + DICS_FLAG_GLOBAL, + 0, + DIREG_DRV, + KEY_QUERY_VALUE + )) + { + PNET_ENUM_ENTRY adapterEntry; + HANDLE deviceHandle; + + adapterEntry = PhAllocate(sizeof(NET_ENUM_ENTRY)); + memset(adapterEntry, 0, sizeof(NET_ENUM_ENTRY)); + + adapterEntry->DeviceGuid = PhQueryRegistryString(keyHandle, L"NetCfgInstanceId"); + adapterEntry->DeviceLuid.Info.IfType = RegQueryUlong64(keyHandle, L"*IfType"); + adapterEntry->DeviceLuid.Info.NetLuidIndex = RegQueryUlong64(keyHandle, L"NetLuidIndex"); + + if (NT_SUCCESS(NetworkAdapterCreateHandle(&deviceHandle, adapterEntry->DeviceGuid))) + { + PPH_STRING adapterName; + + // Try query the full adapter name + if (adapterName = NetworkAdapterQueryName(deviceHandle, adapterEntry->DeviceGuid)) + adapterEntry->DeviceName = adapterName; + + adapterEntry->DevicePresent = TRUE; + + NtClose(deviceHandle); + } + + if (!adapterEntry->DeviceName) + adapterEntry->DeviceName = PhCreateString(adapterDescription); + + PhAddItemList(deviceList, adapterEntry); + + NtClose(keyHandle); + } + } + + SetupDiDestroyDeviceInfoList(deviceInfoHandle); + + // Sort the entries + qsort(deviceList->Items, deviceList->Count, sizeof(PVOID), AdapterEntryCompareFunction); + + Context->EnumeratingAdapters = TRUE; + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < deviceList->Count; i++) + { + PNET_ENUM_ENTRY entry = deviceList->Items[i]; + + AddNetworkAdapterToListView( + Context, + entry->DevicePresent, + 0, + entry->DeviceLuid, + entry->DeviceGuid, + entry->DeviceName + ); + + if (entry->DeviceName) + PhDereferenceObject(entry->DeviceName); + // Note: DeviceGuid is disposed by WM_DESTROY. + + PhFree(entry); + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + Context->EnumeratingAdapters = FALSE; + } + + + // HACK: Show all unknown devices. + Context->EnumeratingAdapters = TRUE; + PhAcquireQueuedLockShared(&NetworkAdaptersListLock); + + for (ULONG i = 0; i < NetworkAdaptersList->Count; i++) + { + ULONG index = -1; + BOOLEAN found = FALSE; + PDV_NETADAPTER_ENTRY entry = PhReferenceObjectSafe(NetworkAdaptersList->Items[i]); + + if (!entry) + continue; + + while ((index = PhFindListViewItemByFlags( + Context->ListViewHandle, + index, + LVNI_ALL + )) != -1) + { + PDV_NETADAPTER_ID param; + + if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) + { + if (EquivalentNetAdapterId(param, &entry->Id)) + { + found = TRUE; + } + } + } + + if (!found) + { + PPH_STRING description; + + if (description = PhCreateString(L"Unknown network adapter")) + { + AddNetworkAdapterToListView( + Context, + FALSE, + entry->Id.InterfaceIndex, + entry->Id.InterfaceLuid, + entry->Id.InterfaceGuid, + description + ); + + PhDereferenceObject(description); + } + } + + PhDereferenceObjectDeferDelete(entry); + } + + PhReleaseQueuedLockShared(&NetworkAdaptersListLock); + Context->EnumeratingAdapters = FALSE; +} + +PPH_STRING FindNetworkDeviceInstance( + _In_ PPH_STRING DevicePath + ) +{ + PPH_STRING deviceIdString = NULL; + HANDLE keyHandle; + HDEVINFO deviceInfoHandle; + SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + + if ((deviceInfoHandle = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_NET, + NULL, + NULL, + DIGCF_DEVICEINTERFACE + )) == INVALID_HANDLE_VALUE) + { + return NULL; + } + + for (ULONG i = 0; SetupDiEnumDeviceInfo(deviceInfoHandle, i, &deviceInfoData); i++) + { + if (keyHandle = SetupDiOpenDevRegKey( + deviceInfoHandle, + &deviceInfoData, + DICS_FLAG_GLOBAL, + 0, + DIREG_DRV, + KEY_QUERY_VALUE + )) + { + PPH_STRING deviceGuid; + + if (deviceGuid = PhQueryRegistryString(keyHandle, L"NetCfgInstanceId")) + { + if (PhEqualString(deviceGuid, DevicePath, TRUE)) + { + deviceIdString = PhCreateStringEx(NULL, 0x100); + + SetupDiGetDeviceInstanceId( + deviceInfoHandle, + &deviceInfoData, + deviceIdString->Buffer, + (ULONG)deviceIdString->Length, + NULL + ); + + PhTrimToNullTerminatorString(deviceIdString); + } + + PhDereferenceObject(deviceGuid); + } + + NtClose(keyHandle); + } + } + + SetupDiDestroyDeviceInfoList(deviceInfoHandle); + + return deviceIdString; +} + +//VOID LoadNetworkAdapterImages( +// _In_ PDV_NETADAPTER_CONTEXT Context +// ) +//{ +// HICON smallIcon = NULL; +// +// Context->ImageList = ImageList_Create( +// GetSystemMetrics(SM_CXSMICON), +// GetSystemMetrics(SM_CYSMICON), +// ILC_COLOR32, +// 1, +// 1 +// ); +// +// // We could use SetupDiLoadClassIcon but this works. +// // Copied from HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}\\IconPath +// // The path and index hasn't changed since Win2k. +// ExtractIconEx( +// L"%SystemRoot%\\system32\\setupapi.dll", +// -5, +// NULL, +// &smallIcon, +// 1 +// ); +// +// if (smallIcon) +// { +// ImageList_AddIcon(Context->ImageList, smallIcon); +// DestroyIcon(smallIcon); +// +// // Set the imagelist only if the image was loaded. +// ListView_SetImageList( +// Context->ListViewHandle, +// Context->ImageList, +// LVSIL_SMALL +// ); +// } +//} + +INT_PTR CALLBACK NetworkAdapterOptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PDV_NETADAPTER_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PDV_NETADAPTER_CONTEXT)PhAllocate(sizeof(DV_NETADAPTER_CONTEXT)); + memset(context, 0, sizeof(DV_NETADAPTER_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PDV_NETADAPTER_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + if (context->OptionsChanged) + NetAdaptersSaveList(); + + FreeListViewAdapterEntries(context); + + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + context->ListViewHandle = GetDlgItem(hwndDlg, IDC_NETADAPTERS_LISTVIEW); + PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); + ListView_SetExtendedListViewStyleEx(context->ListViewHandle, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); + PhSetControlTheme(context->ListViewHandle, L"explorer"); + PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 350, L"Network Adapters"); + PhSetExtendedListView(context->ListViewHandle); + + if (WindowsVersion >= WINDOWS_VISTA) + { + ListView_EnableGroupView(context->ListViewHandle, TRUE); + AddListViewGroup(context->ListViewHandle, 0, L"Connected"); + AddListViewGroup(context->ListViewHandle, 1, L"Disconnected"); + + context->UseAlternateMethod = FALSE; + } + else + { + Button_Enable(GetDlgItem(hwndDlg, IDC_SHOW_HIDDEN_ADAPTERS), FALSE); + context->UseAlternateMethod = TRUE; + } + + FindNetworkAdapters(context); + + context->OptionsChanged = FALSE; + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_SHOW_HIDDEN_ADAPTERS: + { + if (WindowsVersion >= WINDOWS_VISTA) + { + context->UseAlternateMethod = !context->UseAlternateMethod; + + if (context->UseAlternateMethod) + { + ListView_EnableGroupView(context->ListViewHandle, FALSE); + } + else + { + ListView_EnableGroupView(context->ListViewHandle, TRUE); + } + } + + FreeListViewAdapterEntries(context); + ListView_DeleteAllItems(context->ListViewHandle); + + FindNetworkAdapters(context); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + if (header->code == LVN_ITEMCHANGED) + { + LPNM_LISTVIEW listView = (LPNM_LISTVIEW)lParam; + + if (context->EnumeratingAdapters) + break; + + if (listView->uChanged & LVIF_STATE) + { + switch (listView->uNewState & LVIS_STATEIMAGEMASK) + { + case 0x2000: // checked + { + PDV_NETADAPTER_ID param = (PDV_NETADAPTER_ID)listView->lParam; + + if (!FindAdapterEntry(param, FALSE)) + { + PDV_NETADAPTER_ENTRY entry; + + entry = CreateNetAdapterEntry(param); + entry->UserReference = TRUE; + } + + context->OptionsChanged = TRUE; + } + break; + case 0x1000: // unchecked + { + PDV_NETADAPTER_ID param = (PDV_NETADAPTER_ID)listView->lParam; + + FindAdapterEntry(param, TRUE); + + context->OptionsChanged = TRUE; + } + break; + } + } + } + else if (header->code == NM_RCLICK) + { + PDV_NETADAPTER_ENTRY param; + PPH_STRING deviceInstance; + + if (param = PhGetSelectedListViewItemParam(context->ListViewHandle)) + { + if (deviceInstance = FindNetworkDeviceInstance(param->Id.InterfaceGuid)) + { + ShowDeviceMenu(hwndDlg, deviceInstance); + PhDereferenceObject(deviceInstance); + } + } + } + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/HardwareDevices/nvapi/nvapi.h b/plugins/HardwareDevices/nvapi/nvapi.h new file mode 100644 index 0000000..387b83d --- /dev/null +++ b/plugins/HardwareDevices/nvapi/nvapi.h @@ -0,0 +1,9305 @@ +#include "nvapi_lite_common.h" + + /***************************************************************************\ +|* *| +|* Copyright 2005-2010 NVIDIA Corporation. All rights reserved. *| +|* *| +|* NOTICE TO USER: *| +|* *| +|* This source code is subject to NVIDIA ownership rights under U.S. *| +|* and international Copyright laws. Users and possessors of this *| +|* source code are hereby granted a nonexclusive, royalty-free *| +|* license to use this code in individual and commercial software. *| +|* *| +|* NVIDIA MAKES NO REPRESENTATION ABOUT THE SUITABILITY OF THIS SOURCE *| +|* CODE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR *| +|* IMPLIED WARRANTY OF ANY KIND. NVIDIA DISCLAIMS ALL WARRANTIES WITH *| +|* REGARD TO THIS SOURCE CODE, INCLUDING ALL IMPLIED WARRANTIES OF *| +|* MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR *| +|* PURPOSE. IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY SPECIAL, *| +|* INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES *| +|* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN *| +|* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING *| +|* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOURCE *| +|* CODE. *| +|* *| +|* U.S. Government End Users. This source code is a "commercial item" *| +|* as that term is defined at 48 C.F.R. 2.101 (OCT 1995), consisting *| +|* of "commercial computer software" and "commercial computer software *| +|* documentation" as such terms are used in 48 C.F.R. 12.212 (SEPT 1995) *| +|* and is provided to the U.S. Government only as a commercial end item. *| +|* Consistent with 48 C.F.R.12.212 and 48 C.F.R. 227.7202-1 through *| +|* 227.7202-4 (JUNE 1995), all U.S. Government End Users acquire the *| +|* source code with only those rights set forth herein. *| +|* *| +|* Any use of this source code in individual and commercial software must *| +|* include, in the user documentation and internal comments to the code, *| +|* the above Disclaimer and U.S. Government End Users Notice. *| +|* *| +|* *| + \***************************************************************************/ + +/////////////////////////////////////////////////////////////////////////////// +// +// Date: Sep 30, 2014 +// File: nvapi.h +// +// NvAPI provides an interface to NVIDIA devices. This file contains the +// interface constants, structure definitions and function prototypes. +// +// Target Profile: developer +// Target Platform: windows +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef _NVAPI_H +#define _NVAPI_H + +#include // Make sure we have consistent structure packings + +#ifdef __cplusplus +extern "C" { +#endif + +// ==================================================== +// Universal NvAPI Definitions +// ==================================================== +#ifndef _WIN32 +#define __cdecl +#endif + + + +//! @} + +//! \ingroup nvapistatus +#define NVAPI_API_NOT_INTIALIZED NVAPI_API_NOT_INITIALIZED //!< Fix typo in error code + +//! \ingroup nvapistatus +#define NVAPI_INVALID_USER_PRIVILEDGE NVAPI_INVALID_USER_PRIVILEGE //!< Fix typo in error code + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Initialize +// +//! This function initializes the NvAPI library. +//! This must be called before calling other NvAPI_ functions. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_ERROR An error occurred during the initialization process (generic error) +//! \retval NVAPI_LIBRARYNOTFOUND Failed to load the NVAPI support library +//! \retval NVAPI_OK Initialized +//! \sa nvapistatus +//! \ingroup nvapifunctions +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_Initialize(); + +typedef NvAPI_Status (__cdecl *_NvAPI_Initialize)(VOID); +_NvAPI_Initialize NvAPI_Initialize; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Unload +// +//! DESCRIPTION: Unloads NVAPI library. This must be the last function called. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! !! This is not thread safe. In a multithreaded environment, calling NvAPI_Unload !! \n +//! !! while another thread is executing another NvAPI_XXX function, results in !! \n +//! !! undefined behaviour and might even cause the application to crash. Developers !! \n +//! !! must make sure that they are not in any other function before calling NvAPI_Unload. !! \n +//! +//! +//! Unloading NvAPI library is not supported when the library is in a resource locked state. +//! Some functions in the NvAPI library initiates an operation or allocates certain resources +//! and there are corresponding functions available, to complete the operation or free the +//! allocated resources. All such function pairs are designed to prevent unloading NvAPI library. +//! +//! For example, if NvAPI_Unload is called after NvAPI_XXX which locks a resource, it fails with +//! NVAPI_ERROR. Developers need to call the corresponding NvAPI_YYY to unlock the resources, +//! before calling NvAPI_Unload again. +//! +//! \retval ::NVAPI_ERROR One or more resources are locked and hence cannot unload NVAPI library +//! \retval ::NVAPI_OK NVAPI library unloaded +//! +//! \ingroup nvapifunctions +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_Unload(); + +typedef NvAPI_Status (__cdecl *_NvAPI_Unload)(VOID); +_NvAPI_Unload NvAPI_Unload; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetErrorMessage +// +//! This function converts an NvAPI error code into a null terminated string. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \param nr The error code to convert +//! \param szDesc The string corresponding to the error code +//! +//! \return NULL terminated string (always, never NULL) +//! \ingroup nvapifunctions +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GetErrorMessage(NvAPI_Status nr,NvAPI_ShortString szDesc); + +typedef NvAPI_Status (__cdecl *_NvAPI_GetErrorMessage)(NvAPI_Status nr, NvAPI_ShortString szDesc); +_NvAPI_GetErrorMessage NvAPI_GetErrorMessage; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetInterfaceVersionString +// +//! This function returns a string describing the version of the NvAPI library. +//! The contents of the string are human readable. Do not assume a fixed +//! format. +//! +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \param szDesc User readable string giving NvAPI version information +//! +//! \return See \ref nvapistatus for the list of possible return values. +//! \ingroup nvapifunctions +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetInterfaceVersionString(NvAPI_ShortString szDesc); + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// All display port related data types definition starts +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// This category is intentionally added before the #ifdef. The #endif should also be in the same scope +#ifndef DISPLAYPORT_STRUCTS_DEFINED +#define DISPLAYPORT_STRUCTS_DEFINED + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_LINK_RATE +{ + NV_DP_1_62GBPS = 6, + NV_DP_2_70GBPS = 0xA, + NV_DP_5_40GBPS = 0x14 +} NV_DP_LINK_RATE; + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_LANE_COUNT +{ + NV_DP_1_LANE = 1, + NV_DP_2_LANE = 2, + NV_DP_4_LANE = 4, +} NV_DP_LANE_COUNT; + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_COLOR_FORMAT +{ + NV_DP_COLOR_FORMAT_RGB = 0, + NV_DP_COLOR_FORMAT_YCbCr422, + NV_DP_COLOR_FORMAT_YCbCr444, +} NV_DP_COLOR_FORMAT; + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_COLORIMETRY +{ + NV_DP_COLORIMETRY_RGB = 0, + NV_DP_COLORIMETRY_YCbCr_ITU601, + NV_DP_COLORIMETRY_YCbCr_ITU709, +} NV_DP_COLORIMETRY; + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_DYNAMIC_RANGE +{ + NV_DP_DYNAMIC_RANGE_VESA = 0, + NV_DP_DYNAMIC_RANGE_CEA, +} NV_DP_DYNAMIC_RANGE; + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PORT_INFO. +typedef enum _NV_DP_BPC +{ + NV_DP_BPC_DEFAULT = 0, + NV_DP_BPC_6, + NV_DP_BPC_8, + NV_DP_BPC_10, + NV_DP_BPC_12, + NV_DP_BPC_16, +} NV_DP_BPC; + +#endif //#ifndef DISPLAYPORT_STRUCTS_DEFINED + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// All display port related data types definitions end +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetEDID +// +//! \fn NvAPI_GPU_GetEDID(NvPhysicalGpuHandle hPhysicalGpu, NvU32 displayOutputId, NV_EDID *pEDID) +//! This function returns the EDID data for the specified GPU handle and connection bit mask. +//! displayOutputId should have exactly 1 bit set to indicate a single display. See \ref handles. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 85 +//! +//! \retval NVAPI_INVALID_ARGUMENT pEDID is NULL; displayOutputId has 0 or > 1 bits set +//! \retval NVAPI_OK *pEDID contains valid data. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \retval NVAPI_DATA_NOT_FOUND The requested display does not contain an EDID. +// +/////////////////////////////////////////////////////////////////////////////// + + +//! \ingroup gpu +//! @{ + +#define NV_EDID_V1_DATA_SIZE 256 + +#define NV_EDID_DATA_SIZE NV_EDID_V1_DATA_SIZE + +typedef struct +{ + NvU32 version; //structure version + NvU8 EDID_Data[NV_EDID_DATA_SIZE]; +} NV_EDID_V1; + +//! Used in NvAPI_GPU_GetEDID() +typedef struct +{ + NvU32 version; //!< Structure version + NvU8 EDID_Data[NV_EDID_DATA_SIZE]; + NvU32 sizeofEDID; +} NV_EDID_V2; + +//! Used in NvAPI_GPU_GetEDID() +typedef struct +{ + NvU32 version; //!< Structure version + NvU8 EDID_Data[NV_EDID_DATA_SIZE]; + NvU32 sizeofEDID; + NvU32 edidId; //!< ID which always returned in a monotonically increasing counter. + //!< Across a split-EDID read we need to verify that all calls returned the same edidId. + //!< This counter is incremented if we get the updated EDID. + NvU32 offset; //!< Which 256-byte page of the EDID we want to read. Start at 0. + //!< If the read succeeds with edidSize > NV_EDID_DATA_SIZE, + //!< call back again with offset+256 until we have read the entire buffer +} NV_EDID_V3; + +typedef NV_EDID_V3 NV_EDID; + +#define NV_EDID_VER1 MAKE_NVAPI_VERSION(NV_EDID_V1,1) +#define NV_EDID_VER2 MAKE_NVAPI_VERSION(NV_EDID_V2,2) +#define NV_EDID_VER3 MAKE_NVAPI_VERSION(NV_EDID_V3,3) +#define NV_EDID_VER NV_EDID_VER3 + +//! @} + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_GetEDID(NvPhysicalGpuHandle hPhysicalGpu, NvU32 displayOutputId, NV_EDID *pEDID); + +//! \ingroup gpu +//! Used in NV_GPU_CONNECTOR_DATA +typedef enum _NV_GPU_CONNECTOR_TYPE +{ + NVAPI_GPU_CONNECTOR_VGA_15_PIN = 0x00000000, + NVAPI_GPU_CONNECTOR_TV_COMPOSITE = 0x00000010, + NVAPI_GPU_CONNECTOR_TV_SVIDEO = 0x00000011, + NVAPI_GPU_CONNECTOR_TV_HDTV_COMPONENT = 0x00000013, + NVAPI_GPU_CONNECTOR_TV_SCART = 0x00000014, + NVAPI_GPU_CONNECTOR_TV_COMPOSITE_SCART_ON_EIAJ4120 = 0x00000016, + NVAPI_GPU_CONNECTOR_TV_HDTV_EIAJ4120 = 0x00000017, + NVAPI_GPU_CONNECTOR_PC_POD_HDTV_YPRPB = 0x00000018, + NVAPI_GPU_CONNECTOR_PC_POD_SVIDEO = 0x00000019, + NVAPI_GPU_CONNECTOR_PC_POD_COMPOSITE = 0x0000001A, + NVAPI_GPU_CONNECTOR_DVI_I_TV_SVIDEO = 0x00000020, + NVAPI_GPU_CONNECTOR_DVI_I_TV_COMPOSITE = 0x00000021, + NVAPI_GPU_CONNECTOR_DVI_I = 0x00000030, + NVAPI_GPU_CONNECTOR_DVI_D = 0x00000031, + NVAPI_GPU_CONNECTOR_ADC = 0x00000032, + NVAPI_GPU_CONNECTOR_LFH_DVI_I_1 = 0x00000038, + NVAPI_GPU_CONNECTOR_LFH_DVI_I_2 = 0x00000039, + NVAPI_GPU_CONNECTOR_SPWG = 0x00000040, + NVAPI_GPU_CONNECTOR_OEM = 0x00000041, + NVAPI_GPU_CONNECTOR_DISPLAYPORT_EXTERNAL = 0x00000046, + NVAPI_GPU_CONNECTOR_DISPLAYPORT_INTERNAL = 0x00000047, + NVAPI_GPU_CONNECTOR_DISPLAYPORT_MINI_EXT = 0x00000048, + NVAPI_GPU_CONNECTOR_HDMI_A = 0x00000061, + NVAPI_GPU_CONNECTOR_HDMI_C_MINI = 0x00000063, + NVAPI_GPU_CONNECTOR_LFH_DISPLAYPORT_1 = 0x00000064, + NVAPI_GPU_CONNECTOR_LFH_DISPLAYPORT_2 = 0x00000065, + NVAPI_GPU_CONNECTOR_VIRTUAL_WFD = 0x00000070, + NVAPI_GPU_CONNECTOR_UNKNOWN = 0xFFFFFFFF, +} NV_GPU_CONNECTOR_TYPE; + +//////////////////////////////////////////////////////////////////////////////// +// +// NvAPI_TVOutput Information +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup tvapi +//! Used in NV_DISPLAY_TV_OUTPUT_INFO +typedef enum _NV_DISPLAY_TV_FORMAT +{ + NV_DISPLAY_TV_FORMAT_NONE = 0, + NV_DISPLAY_TV_FORMAT_SD_NTSCM = 0x00000001, + NV_DISPLAY_TV_FORMAT_SD_NTSCJ = 0x00000002, + NV_DISPLAY_TV_FORMAT_SD_PALM = 0x00000004, + NV_DISPLAY_TV_FORMAT_SD_PALBDGH = 0x00000008, + NV_DISPLAY_TV_FORMAT_SD_PALN = 0x00000010, + NV_DISPLAY_TV_FORMAT_SD_PALNC = 0x00000020, + NV_DISPLAY_TV_FORMAT_SD_576i = 0x00000100, + NV_DISPLAY_TV_FORMAT_SD_480i = 0x00000200, + NV_DISPLAY_TV_FORMAT_ED_480p = 0x00000400, + NV_DISPLAY_TV_FORMAT_ED_576p = 0x00000800, + NV_DISPLAY_TV_FORMAT_HD_720p = 0x00001000, + NV_DISPLAY_TV_FORMAT_HD_1080i = 0x00002000, + NV_DISPLAY_TV_FORMAT_HD_1080p = 0x00004000, + NV_DISPLAY_TV_FORMAT_HD_720p50 = 0x00008000, + NV_DISPLAY_TV_FORMAT_HD_1080p24 = 0x00010000, + NV_DISPLAY_TV_FORMAT_HD_1080i50 = 0x00020000, + NV_DISPLAY_TV_FORMAT_HD_1080p50 = 0x00040000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp30 = 0x00080000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp30_3840 = NV_DISPLAY_TV_FORMAT_UHD_4Kp30, + NV_DISPLAY_TV_FORMAT_UHD_4Kp25 = 0x00100000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp25_3840 = NV_DISPLAY_TV_FORMAT_UHD_4Kp25, + NV_DISPLAY_TV_FORMAT_UHD_4Kp24 = 0x00200000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp24_3840 = NV_DISPLAY_TV_FORMAT_UHD_4Kp24, + NV_DISPLAY_TV_FORMAT_UHD_4Kp24_SMPTE = 0x00400000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp50_3840 = 0x00800000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp60_3840 = 0x00900000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp30_4096 = 0x00A00000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp25_4096 = 0x00B00000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp24_4096 = 0x00C00000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp50_4096 = 0x00D00000, + NV_DISPLAY_TV_FORMAT_UHD_4Kp60_4096 = 0x00E00000, + + NV_DISPLAY_TV_FORMAT_SD_OTHER = 0x01000000, + NV_DISPLAY_TV_FORMAT_ED_OTHER = 0x02000000, + NV_DISPLAY_TV_FORMAT_HD_OTHER = 0x04000000, + + NV_DISPLAY_TV_FORMAT_ANY = 0x80000000, + +} NV_DISPLAY_TV_FORMAT; + + +//! \ingroup dispcontrol +//! @{ +#define NVAPI_MAX_VIEW_TARGET 2 +#define NVAPI_ADVANCED_MAX_VIEW_TARGET 4 + +#ifndef _NV_TARGET_VIEW_MODE_ +#define _NV_TARGET_VIEW_MODE_ + +//! Used in NvAPI_SetView(). +typedef enum _NV_TARGET_VIEW_MODE +{ + NV_VIEW_MODE_STANDARD = 0, + NV_VIEW_MODE_CLONE = 1, + NV_VIEW_MODE_HSPAN = 2, + NV_VIEW_MODE_VSPAN = 3, + NV_VIEW_MODE_DUALVIEW = 4, + NV_VIEW_MODE_MULTIVIEW = 5, +} NV_TARGET_VIEW_MODE; +#endif + +//! @} + + +// Following definitions are used in NvAPI_SetViewEx. + +//! Scaling modes - used in NvAPI_SetViewEx(). +//! \ingroup dispcontrol +typedef enum _NV_SCALING +{ + NV_SCALING_DEFAULT = 0, //!< No change + + // New Scaling Declarations + NV_SCALING_GPU_SCALING_TO_CLOSEST = 1, //!< Balanced - Full Screen + NV_SCALING_GPU_SCALING_TO_NATIVE = 2, //!< Force GPU - Full Screen + NV_SCALING_GPU_SCANOUT_TO_NATIVE = 3, //!< Force GPU - Centered\No Scaling + NV_SCALING_GPU_SCALING_TO_ASPECT_SCANOUT_TO_NATIVE = 5, //!< Force GPU - Aspect Ratio + NV_SCALING_GPU_SCALING_TO_ASPECT_SCANOUT_TO_CLOSEST = 6, //!< Balanced - Aspect Ratio + NV_SCALING_GPU_SCANOUT_TO_CLOSEST = 7, //!< Balanced - Centered\No Scaling + + // Legacy Declarations + NV_SCALING_MONITOR_SCALING = NV_SCALING_GPU_SCALING_TO_CLOSEST, + NV_SCALING_ADAPTER_SCALING = NV_SCALING_GPU_SCALING_TO_NATIVE, + NV_SCALING_CENTERED = NV_SCALING_GPU_SCANOUT_TO_NATIVE, + NV_SCALING_ASPECT_SCALING = NV_SCALING_GPU_SCALING_TO_ASPECT_SCANOUT_TO_NATIVE, + + NV_SCALING_CUSTOMIZED = 255 //!< For future use +} NV_SCALING; + +//! Rotate modes- used in NvAPI_SetViewEx(). +//! \ingroup dispcontrol +typedef enum _NV_ROTATE +{ + NV_ROTATE_0 = 0, + NV_ROTATE_90 = 1, + NV_ROTATE_180 = 2, + NV_ROTATE_270 = 3, + NV_ROTATE_IGNORED = 4, +} NV_ROTATE; + +//! Color formats- used in NvAPI_SetViewEx(). +//! \ingroup dispcontrol +#define NVFORMAT_MAKEFOURCC(ch0, ch1, ch2, ch3) \ + ((NvU32)(NvU8)(ch0) | ((NvU32)(NvU8)(ch1) << 8) | \ + ((NvU32)(NvU8)(ch2) << 16) | ((NvU32)(NvU8)(ch3) << 24 )) + + + +//! Color formats- used in NvAPI_SetViewEx(). +//! \ingroup dispcontrol +typedef enum _NV_FORMAT +{ + NV_FORMAT_UNKNOWN = 0, //!< unknown. Driver will choose one as following value. + NV_FORMAT_P8 = 41, //!< for 8bpp mode + NV_FORMAT_R5G6B5 = 23, //!< for 16bpp mode + NV_FORMAT_A8R8G8B8 = 21, //!< for 32bpp mode + NV_FORMAT_A16B16G16R16F = 113, //!< for 64bpp(floating point) mode. + +} NV_FORMAT; + +// TV standard + +typedef struct +{ + float x; //!< x-coordinate of the viewport top-left point + float y; //!< y-coordinate of the viewport top-left point + float w; //!< Width of the viewport + float h; //!< Height of the viewport +} NV_VIEWPORTF; + + + +//! \ingroup dispcontrol +//! The timing override is not supported yet; must be set to _AUTO. \n + + +typedef enum _NV_TIMING_OVERRIDE +{ + NV_TIMING_OVERRIDE_CURRENT = 0, //!< get the current timing + NV_TIMING_OVERRIDE_AUTO, //!< the timing the driver will use based the current policy + NV_TIMING_OVERRIDE_EDID, //!< EDID timing + NV_TIMING_OVERRIDE_DMT, //!< VESA DMT timing + NV_TIMING_OVERRIDE_DMT_RB, //!< VESA DMT timing with reduced blanking + NV_TIMING_OVERRIDE_CVT, //!< VESA CVT timing + NV_TIMING_OVERRIDE_CVT_RB, //!< VESA CVT timing with reduced blanking + NV_TIMING_OVERRIDE_GTF, //!< VESA GTF timing + NV_TIMING_OVERRIDE_EIA861, //!< EIA 861x pre-defined timing + NV_TIMING_OVERRIDE_ANALOG_TV, //!< analog SD/HDTV timing + NV_TIMING_OVERRIDE_CUST, //!< NV custom timings + NV_TIMING_OVERRIDE_NV_PREDEFINED, //!< NV pre-defined timing (basically the PsF timings) + NV_TIMING_OVERRIDE_NV_PSF = NV_TIMING_OVERRIDE_NV_PREDEFINED, + NV_TIMING_OVERRIDE_NV_ASPR, + NV_TIMING_OVERRIDE_SDI, //!< Override for SDI timing + + NV_TIMING_OVRRIDE_MAX, +}NV_TIMING_OVERRIDE; + + +#ifndef NV_TIMING_STRUCTS_DEFINED +#define NV_TIMING_STRUCTS_DEFINED + +//*********************** +// The Timing Structure +//*********************** +// +//! \ingroup dispcontrol +//! NVIDIA-specific timing extras \n +//! Used in NV_TIMING. +typedef struct tagNV_TIMINGEXT +{ + NvU32 flag; //!< Reserved for NVIDIA hardware-based enhancement, such as double-scan. + NvU16 rr; //!< Logical refresh rate to present + NvU32 rrx1k; //!< Physical vertical refresh rate in 0.001Hz + NvU32 aspect; //!< Display aspect ratio Hi(aspect):horizontal-aspect, Low(aspect):vertical-aspect + NvU16 rep; //!< Bit-wise pixel repetition factor: 0x1:no pixel repetition; 0x2:each pixel repeats twice horizontally,.. + NvU32 status; //!< Timing standard + NvU8 name[40]; //!< Timing name +}NV_TIMINGEXT; + + + +//! \ingroup dispcontrol +//!The very basic timing structure based on the VESA standard: +//! \code +//! |<----------------------------htotal--------------------------->| +//! ---------"active" video-------->|<-------blanking------>|<----- +//! |<-------hvisible-------->|<-hb->|<-hfp->|<-hsw->|<-hbp->|<-hb->| +//! --------- -+-------------------------+ | | | | | +//! A A | | | | | | | +//! : : | | | | | | | +//! : : | | | | | | | +//! :vertical| addressable video | | | | | | +//! : visible| | | | | | | +//! : : | | | | | | | +//! : : | | | | | | | +//! vertical V | | | | | | | +//! total --+-------------------------+ | | | | | +//! : vb border | | | | | +//! : -----------------------------------+ | | | | +//! : vfp front porch | | | | +//! : -------------------------------------------+ | | | +//! : vsw sync width | | | +//! : ---------------------------------------------------+ | | +//! : vbp back porch | | +//! : -----------------------------------------------------------+ | +//! V vb border | +//! ---------------------------------------------------------------------------+ +//! \endcode +typedef struct _NV_TIMING +{ + // VESA scan out timing parameters: + NvU16 HVisible; //!< horizontal visible + NvU16 HBorder; //!< horizontal border + NvU16 HFrontPorch; //!< horizontal front porch + NvU16 HSyncWidth; //!< horizontal sync width + NvU16 HTotal; //!< horizontal total + NvU8 HSyncPol; //!< horizontal sync polarity: 1-negative, 0-positive + + NvU16 VVisible; //!< vertical visible + NvU16 VBorder; //!< vertical border + NvU16 VFrontPorch; //!< vertical front porch + NvU16 VSyncWidth; //!< vertical sync width + NvU16 VTotal; //!< vertical total + NvU8 VSyncPol; //!< vertical sync polarity: 1-negative, 0-positive + + NvU16 interlaced; //!< 1-interlaced, 0-progressive + NvU32 pclk; //!< pixel clock in 10 kHz + + //other timing related extras + NV_TIMINGEXT etc; +}NV_TIMING; +#endif //NV_TIMING_STRUCTS_DEFINED + + +//! \addtogroup dispcontrol +//! Timing-related constants +//! @{ +#define NV_TIMING_H_SYNC_POSITIVE 0 +#define NV_TIMING_H_SYNC_NEGATIVE 1 +#define NV_TIMING_H_SYNC_DEFAULT NV_TIMING_H_SYNC_NEGATIVE +// +#define NV_TIMING_V_SYNC_POSITIVE 0 +#define NV_TIMING_V_SYNC_NEGATIVE 1 +#define NV_TIMING_V_SYNC_DEFAULT NV_TIMING_V_SYNC_POSITIVE +// +#define NV_TIMING_PROGRESSIVE 0 +#define NV_TIMING_INTERLACED 1 +#define NV_TIMING_INTERLACED_EXTRA_VBLANK_ON_FIELD2 1 +#define NV_TIMING_INTERLACED_NO_EXTRA_VBLANK_ON_FIELD2 2 +//! @} + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_SetView +// +//! \fn NvAPI_SetView(NvDisplayHandle hNvDisplay, NV_VIEW_TARGET_INFO *pTargetInfo, NV_TARGET_VIEW_MODE targetView) +//! This function lets the caller modify the target display arrangement of the selected source display handle in any nView mode. +//! It can also modify or extend the source display in Dualview mode. +//! \note Maps the selected source to the associated target Ids. +//! \note Display PATH with this API is limited to single GPU. DUALVIEW across GPUs cannot be enabled with this API. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_SetDisplayConfig. +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \since Release: 90 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. #NVAPI_DEFAULT_HANDLE is not allowed, it has to be a handle enumerated with NvAPI_EnumNVidiaDisplayHandle(). +//! \param [in] pTargetInfo Pointer to array of NV_VIEW_TARGET_INFO, specifying device properties in this view. +//! The first device entry in the array is the physical primary. +//! The device entry with the lowest source id is the desktop primary. +//! \param [in] targetCount Count of target devices specified in pTargetInfo. +//! \param [in] targetView Target view selected from NV_TARGET_VIEW_MODE. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup dispcontrol +//! Used in NvAPI_SetView() and NvAPI_GetView() +typedef struct +{ + NvU32 version; //!< (IN) structure version + NvU32 count; //!< (IN) target count + struct + { + NvU32 deviceMask; //!< (IN/OUT) Device mask + NvU32 sourceId; //!< (IN/OUT) Source ID - values will be based on the number of heads exposed per GPU. + NvU32 bPrimary:1; //!< (OUT) Indicates if this is the GPU's primary view target. This is not the desktop GDI primary. + //!< NvAPI_SetView automatically selects the first target in NV_VIEW_TARGET_INFO index 0 as the GPU's primary view. + NvU32 bInterlaced:1; //!< (IN/OUT) Indicates if the timing being used on this monitor is interlaced. + NvU32 bGDIPrimary:1; //!< (IN/OUT) Indicates if this is the desktop GDI primary. + NvU32 bForceModeSet:1;//!< (IN) Used only on Win7 and higher during a call to NvAPI_SetView(). Turns off optimization & forces OS to set supplied mode. + } target[NVAPI_MAX_VIEW_TARGET]; +} NV_VIEW_TARGET_INFO; + +//! \ingroup dispcontrol +#define NV_VIEW_TARGET_INFO_VER MAKE_NVAPI_VERSION(NV_VIEW_TARGET_INFO,2) + + +//! \ingroup dispcontrol +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_SetDisplayConfig.") +NVAPI_INTERFACE NvAPI_SetView(NvDisplayHandle hNvDisplay, NV_VIEW_TARGET_INFO *pTargetInfo, NV_TARGET_VIEW_MODE targetView); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_SetViewEx +// +//! \fn NvAPI_SetViewEx(NvDisplayHandle hNvDisplay, NV_DISPLAY_PATH_INFO *pPathInfo, NV_TARGET_VIEW_MODE displayView) +//! This function lets caller to modify the display arrangement for selected source display handle in any of the nview modes. +//! It also allows to modify or extend the source display in dualview mode. +//! \note Maps the selected source to the associated target Ids. +//! \note Display PATH with this API is limited to single GPU. DUALVIEW across GPUs cannot be enabled with this API. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_SetDisplayConfig. +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \since Release: 95 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. #NVAPI_DEFAULT_HANDLE is not allowed, it has to be a handle enumerated with +//! NvAPI_EnumNVidiaDisplayHandle(). +//! \param [in] pPathInfo Pointer to array of NV_VIEW_PATH_INFO, specifying device properties in this view. +//! The first device entry in the array is the physical primary. +//! The device entry with the lowest source id is the desktop primary. +//! \param [in] pathCount Count of paths specified in pPathInfo. +//! \param [in] displayView Display view selected from NV_TARGET_VIEW_MODE. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup dispcontrol +#define NVAPI_MAX_DISPLAY_PATH NVAPI_MAX_VIEW_TARGET + +//! \ingroup dispcontrol +#define NVAPI_ADVANCED_MAX_DISPLAY_PATH NVAPI_ADVANCED_MAX_VIEW_TARGET + + + +//! \ingroup dispcontrol +//! Used in NV_DISPLAY_PATH_INFO. +typedef struct +{ + NvU32 deviceMask; //!< (IN) Device mask + NvU32 sourceId; //!< (IN) Values will be based on the number of heads exposed per GPU(0, 1?) + NvU32 bPrimary:1; //!< (IN/OUT) Indicates if this is the GPU's primary view target. This is not the desktop GDI primary. + //!< NvAPI_SetViewEx() automatically selects the first target in NV_DISPLAY_PATH_INFO index 0 as the GPU's primary view. + NV_GPU_CONNECTOR_TYPE connector; //!< (IN) Specify connector type. For TV only. + + // source mode information + NvU32 width; //!< (IN) Width of the mode + NvU32 height; //!< (IN) Height of the mode + NvU32 depth; //!< (IN) Depth of the mode + NV_FORMAT colorFormat; //!< Color format if it needs to be specified. Not used now. + + //rotation setting of the mode + NV_ROTATE rotation; //!< (IN) Rotation setting. + + // the scaling mode + NV_SCALING scaling; //!< (IN) Scaling setting + + // Timing info + NvU32 refreshRate; //!< (IN) Refresh rate of the mode + NvU32 interlaced:1; //!< (IN) Interlaced mode flag + + NV_DISPLAY_TV_FORMAT tvFormat; //!< (IN) To choose the last TV format set this value to NV_DISPLAY_TV_FORMAT_NONE + + // Windows desktop position + NvU32 posx; //!< (IN/OUT) X-offset of this display on the Windows desktop + NvU32 posy; //!< (IN/OUT) Y-offset of this display on the Windows desktop + NvU32 bGDIPrimary:1; //!< (IN/OUT) Indicates if this is the desktop GDI primary. + + NvU32 bForceModeSet:1;//!< (IN) Used only on Win7 and higher during a call to NvAPI_SetViewEx(). Turns off optimization & forces OS to set supplied mode. + NvU32 bFocusDisplay:1;//!< (IN) If set, this display path should have the focus after the GPU topology change + NvU32 gpuId:24; //!< (IN) the physical display/target Gpu id which is the owner of the scan out (for SLI multimon, display from the slave Gpu) + +} NV_DISPLAY_PATH; + +//! \ingroup dispcontrol +//! Used in NvAPI_SetViewEx() and NvAPI_GetViewEx(). +typedef struct +{ + NvU32 version; //!< (IN) Structure version + NvU32 count; //!< (IN) Path count + NV_DISPLAY_PATH path[NVAPI_MAX_DISPLAY_PATH]; +} NV_DISPLAY_PATH_INFO_V3; + +//! \ingroup dispcontrol +//! Used in NvAPI_SetViewEx() and NvAPI_GetViewEx(). +typedef struct +{ + NvU32 version; //!< (IN) Structure version + NvU32 count; //!< (IN) Path count + NV_DISPLAY_PATH path[NVAPI_ADVANCED_MAX_DISPLAY_PATH]; +} NV_DISPLAY_PATH_INFO; + +//! \addtogroup dispcontrol +//! Macro for constructing the version fields of NV_DISPLAY_PATH_INFO +//! @{ +#define NV_DISPLAY_PATH_INFO_VER NV_DISPLAY_PATH_INFO_VER4 +#define NV_DISPLAY_PATH_INFO_VER4 MAKE_NVAPI_VERSION(NV_DISPLAY_PATH_INFO,4) +#define NV_DISPLAY_PATH_INFO_VER3 MAKE_NVAPI_VERSION(NV_DISPLAY_PATH_INFO,3) +#define NV_DISPLAY_PATH_INFO_VER2 MAKE_NVAPI_VERSION(NV_DISPLAY_PATH_INFO,2) +#define NV_DISPLAY_PATH_INFO_VER1 MAKE_NVAPI_VERSION(NV_DISPLAY_PATH_INFO,1) +//! @} + + +//! \ingroup dispcontrol +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_SetDisplayConfig.") +NVAPI_INTERFACE NvAPI_SetViewEx(NvDisplayHandle hNvDisplay, NV_DISPLAY_PATH_INFO *pPathInfo, NV_TARGET_VIEW_MODE displayView); + + + +/////////////////////////////////////////////////////////////////////////////// +// SetDisplayConfig/GetDisplayConfig +/////////////////////////////////////////////////////////////////////////////// +//! \ingroup dispcontrol + +typedef struct _NV_POSITION +{ + NvS32 x; + NvS32 y; +} NV_POSITION; + +//! \ingroup dispcontrol +typedef struct _NV_RESOLUTION +{ + NvU32 width; + NvU32 height; + NvU32 colorDepth; +} NV_RESOLUTION; + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_V1 +{ + NvU32 version; + + // Rotation and Scaling + NV_ROTATE rotation; //!< (IN) rotation setting. + NV_SCALING scaling; //!< (IN) scaling setting. + + // Refresh Rate + NvU32 refreshRate1K; //!< (IN) Non-interlaced Refresh Rate of the mode, multiplied by 1000, 0 = ignored + //!< This is the value which driver reports to the OS. + // Flags + NvU32 interlaced:1; //!< (IN) Interlaced mode flag, ignored if refreshRate == 0 + NvU32 primary:1; //!< (IN) Declares primary display in clone configuration. This is *NOT* GDI Primary. + //!< Only one target can be primary per source. If no primary is specified, the first + //!< target will automatically be primary. +#ifdef NV_PAN_AND_SCAN_DEFINED + NvU32 isPanAndScanTarget:1; //!< Whether on this target Pan and Scan is enabled or has to be enabled. Valid only + //!< when the target is part of clone topology. + NvU32 reserved:29; +#else + NvU32 reserved:30; +#endif + // TV format information + NV_GPU_CONNECTOR_TYPE connector; //!< Specify connector type. For TV only, ignored if tvFormat == NV_DISPLAY_TV_FORMAT_NONE + NV_DISPLAY_TV_FORMAT tvFormat; //!< (IN) to choose the last TV format set this value to NV_DISPLAY_TV_FORMAT_NONE + //!< In case of NvAPI_DISP_GetDisplayConfig(), this field will indicate the currently applied TV format; + //!< if no TV format is applied, this field will have NV_DISPLAY_TV_FORMAT_NONE value. + //!< In case of NvAPI_DISP_SetDisplayConfig(), this field should only be set in case of TVs; + //!< for other displays this field will be ignored and resolution & refresh rate specified in input will be used to apply the TV format. + + // Backend (raster) timing standard + NV_TIMING_OVERRIDE timingOverride; //!< Ignored if timingOverride == NV_TIMING_OVERRIDE_CURRENT + NV_TIMING timing; //!< Scan out timing, valid only if timingOverride == NV_TIMING_OVERRIDE_CUST + //!< The value NV_TIMING::NV_TIMINGEXT::rrx1k is obtained from the EDID. The driver may + //!< tweak this value for HDTV, stereo, etc., before reporting it to the OS. +} NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_V1; + +//! \ingroup dispcontrol +typedef NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_V1 NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO; + +//! \ingroup dispcontrol +#define NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER1 MAKE_NVAPI_VERSION(NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_V1,1) + +//! \ingroup dispcontrol +#define NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER1 + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1 +{ + NvU32 displayId; //!< Display ID + NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO* details; //!< May be NULL if no advanced settings are required. NULL for Non-NVIDIA Display. +} NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1; + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 +{ + NvU32 displayId; //!< Display ID + NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO* details; //!< May be NULL if no advanced settings are required + NvU32 targetId; //!< Windows CCD target ID. Must be present only for non-NVIDIA adapter, for NVIDIA adapter this parameter is ignored. +} NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2; + + +//! \ingroup dispcontrol +//! As version is not defined for this structure, we will be using version of NV_DISPLAYCONFIG_PATH_INFO +typedef NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 NV_DISPLAYCONFIG_PATH_TARGET_INFO; + + +//! \ingroup dispcontrol +typedef enum _NV_DISPLAYCONFIG_SPANNING_ORIENTATION +{ + NV_DISPLAYCONFIG_SPAN_NONE = 0, + NV_DISPLAYCONFIG_SPAN_HORIZONTAL = 1, + NV_DISPLAYCONFIG_SPAN_VERTICAL = 2, +} NV_DISPLAYCONFIG_SPANNING_ORIENTATION; + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 +{ + NV_RESOLUTION resolution; + NV_FORMAT colorFormat; //!< Ignored at present, must be NV_FORMAT_UNKNOWN (0) + NV_POSITION position; //!< Is all positions are 0 or invalid, displays will be automatically + //!< positioned from left to right with GDI Primary at 0,0, and all + //!< other displays in the order of the path array. + NV_DISPLAYCONFIG_SPANNING_ORIENTATION spanningOrientation; //!< Spanning is only supported on XP + NvU32 bGDIPrimary : 1; + NvU32 bSLIFocus : 1; + NvU32 reserved : 30; //!< Must be 0 +} NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1; + +//! \ingroup dispcontrol +//! As version is not defined for this structure we will be using version of NV_DISPLAYCONFIG_PATH_INFO +typedef NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 NV_DISPLAYCONFIG_SOURCE_MODE_INFO; + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_PATH_INFO_V1 +{ + NvU32 version; + NvU32 reserved_sourceId; //!< This field is reserved. There is ongoing debate if we need this field. + //!< Identifies sourceIds used by Windows. If all sourceIds are 0, + //!< these will be computed automatically. + NvU32 targetInfoCount; //!< Number of elements in targetInfo array + NV_DISPLAYCONFIG_PATH_TARGET_INFO_V1* targetInfo; + NV_DISPLAYCONFIG_SOURCE_MODE_INFO* sourceModeInfo; //!< May be NULL if mode info is not important +} NV_DISPLAYCONFIG_PATH_INFO_V1; + +//! \ingroup dispcontrol +//! This define is temporary and must be removed once DVS failure is fixed. +#define _NV_DISPLAYCONFIG_PATH_INFO_V2 _NV_DISPLAYCONFIG_PATH_INFO + +//! \ingroup dispcontrol +typedef struct _NV_DISPLAYCONFIG_PATH_INFO_V2 +{ + NvU32 version; + union { + NvU32 sourceId; //!< Identifies sourceId used by Windows CCD. This can be optionally set. + NvU32 reserved_sourceId; //!< Only for compatibility + }; + + NvU32 targetInfoCount; //!< Number of elements in targetInfo array + NV_DISPLAYCONFIG_PATH_TARGET_INFO* targetInfo; + NV_DISPLAYCONFIG_SOURCE_MODE_INFO* sourceModeInfo; //!< May be NULL if mode info is not important + NvU32 IsNonNVIDIAAdapter : 1; //!< True for non-NVIDIA adapter. + NvU32 reserved : 31; //!< Must be 0 + void *pOSAdapterID; //!< Used by Non-NVIDIA adapter for pointer to OS Adapter of LUID + //!< type, type casted to void *. +} NV_DISPLAYCONFIG_PATH_INFO_V2; + +//! \ingroup dispcontrol +typedef NV_DISPLAYCONFIG_PATH_INFO_V2 NV_DISPLAYCONFIG_PATH_INFO; + +//! \ingroup dispcontrol +#define NV_DISPLAYCONFIG_PATH_INFO_VER1 MAKE_NVAPI_VERSION(NV_DISPLAYCONFIG_PATH_INFO_V1,1) + +//! \ingroup dispcontrol +#define NV_DISPLAYCONFIG_PATH_INFO_VER2 MAKE_NVAPI_VERSION(NV_DISPLAYCONFIG_PATH_INFO_V2,2) + +//! \ingroup dispcontrol +#define NV_DISPLAYCONFIG_PATH_INFO_VER NV_DISPLAYCONFIG_PATH_INFO_VER2 + +//! \ingroup dispcontrol +typedef enum _NV_DISPLAYCONFIG_FLAGS +{ + NV_DISPLAYCONFIG_VALIDATE_ONLY = 0x00000001, + NV_DISPLAYCONFIG_SAVE_TO_PERSISTENCE = 0x00000002, + NV_DISPLAYCONFIG_DRIVER_RELOAD_ALLOWED = 0x00000004, //!< Driver reload is permitted if necessary + NV_DISPLAYCONFIG_FORCE_MODE_ENUMERATION = 0x00000008, //!< Refresh OS mode list. +} NV_DISPLAYCONFIG_FLAGS; + + +#define NVAPI_UNICODE_STRING_MAX 2048 +#define NVAPI_BINARY_DATA_MAX 4096 + +typedef NvU16 NvAPI_UnicodeString[NVAPI_UNICODE_STRING_MAX]; +typedef const NvU16 *NvAPI_LPCWSTR; +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetDisplayDriverVersion +//! \fn NvAPI_GetDisplayDriverVersion(NvDisplayHandle hNvDisplay, NV_DISPLAY_DRIVER_VERSION *pVersion) +//! This function returns a struct that describes aspects of the display driver +//! build. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_SYS_GetDriverAndBranchVersion. +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \param [in] hNvDisplay NVIDIA display handle. +//! \param [out] pVersion Pointer to NV_DISPLAY_DRIVER_VERSION struc +//! +//! \retval NVAPI_ERROR +//! \retval NVAPI_OK +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup driverapi +//! Used in NvAPI_GetDisplayDriverVersion() +typedef struct _NV_DISPLAY_DRIVER_VERSION +{ + NvU32 version; // Structure version + NvU32 drvVersion; + NvU32 bldChangeListNum; + NvAPI_ShortString szBuildBranchString; + NvAPI_ShortString szAdapterString; +} NV_DISPLAY_DRIVER_VERSION; + +//! \ingroup driverapi +#define NV_DISPLAY_DRIVER_VERSION_VER MAKE_NVAPI_VERSION(NV_DISPLAY_DRIVER_VERSION,1) + + +//! \ingroup driverapi +//__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_SYS_GetDriverAndBranchVersion.") +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverVersion)(NvDisplayHandle hNvDisplay, NV_DISPLAY_DRIVER_VERSION *pVersion); +_NvAPI_GetDisplayDriverVersion NvAPI_GetDisplayDriverVersion; + +//NVAPI_INTERFACE NvAPI_GetDisplayDriverVersion(NvDisplayHandle hNvDisplay, NV_DISPLAY_DRIVER_VERSION *pVersion); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_OGL_ExpertModeSet[Get] +// +//! \name NvAPI_OGL_ExpertModeSet[Get] Functions +//@{ +//! This function configures OpenGL Expert Mode, an API usage feedback and +//! advice reporting mechanism. The effects of this call are +//! applied only to the current context, and are reset to the +//! defaults when the context is destroyed. +//! +//! \note This feature is valid at runtime only when GLExpert +//! functionality has been built into the OpenGL driver +//! installed on the system. All Windows Vista OpenGL +//! drivers provided by NVIDIA have this instrumentation +//! included by default. Windows XP, however, requires a +//! special display driver available with the NVIDIA +//! PerfSDK found at developer.nvidia.com. +//! +//! \note These functions are valid only for the current OpenGL +//! context. Calling these functions prior to creating a +//! context and calling MakeCurrent with it will result +//! in errors and undefined behavior. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \param expertDetailMask Mask made up of NVAPI_OGLEXPERT_DETAIL bits, +//! this parameter specifies the detail level in +//! the feedback stream. +//! +//! \param expertReportMask Mask made up of NVAPI_OGLEXPERT_REPORT bits, +//! this parameter specifies the areas of +//! functional interest. +//! +//! \param expertOutputMask Mask made up of NVAPI_OGLEXPERT_OUTPUT bits, +//! this parameter specifies the feedback output +//! location. +//! +//! \param expertCallback Used in conjunction with OUTPUT_TO_CALLBACK, +//! this is a simple callback function the user +//! may use to obtain the feedback stream. The +//! function will be called once per fully +//! qualified feedback stream extry. +//! +//! \retval NVAPI_API_NOT_INTIALIZED NVAPI not initialized +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU found +//! \retval NVAPI_OPENGL_CONTEXT_NOT_CURRENT No NVIDIA OpenGL context +//! which supports GLExpert +//! has been made current +//! \retval NVAPI_ERROR OpenGL driver failed to load properly +//! \retval NVAPI_OK Success +// +/////////////////////////////////////////////////////////////////////////////// + +//! \addtogroup oglapi +//! @{ +#define NVAPI_OGLEXPERT_DETAIL_NONE 0x00000000 +#define NVAPI_OGLEXPERT_DETAIL_ERROR 0x00000001 +#define NVAPI_OGLEXPERT_DETAIL_SWFALLBACK 0x00000002 +#define NVAPI_OGLEXPERT_DETAIL_BASIC_INFO 0x00000004 +#define NVAPI_OGLEXPERT_DETAIL_DETAILED_INFO 0x00000008 +#define NVAPI_OGLEXPERT_DETAIL_PERFORMANCE_WARNING 0x00000010 +#define NVAPI_OGLEXPERT_DETAIL_QUALITY_WARNING 0x00000020 +#define NVAPI_OGLEXPERT_DETAIL_USAGE_WARNING 0x00000040 +#define NVAPI_OGLEXPERT_DETAIL_ALL 0xFFFFFFFF + +#define NVAPI_OGLEXPERT_REPORT_NONE 0x00000000 +#define NVAPI_OGLEXPERT_REPORT_ERROR 0x00000001 +#define NVAPI_OGLEXPERT_REPORT_SWFALLBACK 0x00000002 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_VERTEX 0x00000004 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_GEOMETRY 0x00000008 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_XFB 0x00000010 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_RASTER 0x00000020 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_FRAGMENT 0x00000040 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_ROP 0x00000080 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_FRAMEBUFFER 0x00000100 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_PIXEL 0x00000200 +#define NVAPI_OGLEXPERT_REPORT_PIPELINE_TEXTURE 0x00000400 +#define NVAPI_OGLEXPERT_REPORT_OBJECT_BUFFEROBJECT 0x00000800 +#define NVAPI_OGLEXPERT_REPORT_OBJECT_TEXTURE 0x00001000 +#define NVAPI_OGLEXPERT_REPORT_OBJECT_PROGRAM 0x00002000 +#define NVAPI_OGLEXPERT_REPORT_OBJECT_FBO 0x00004000 +#define NVAPI_OGLEXPERT_REPORT_FEATURE_SLI 0x00008000 +#define NVAPI_OGLEXPERT_REPORT_ALL 0xFFFFFFFF + + +#define NVAPI_OGLEXPERT_OUTPUT_TO_NONE 0x00000000 +#define NVAPI_OGLEXPERT_OUTPUT_TO_CONSOLE 0x00000001 +#define NVAPI_OGLEXPERT_OUTPUT_TO_DEBUGGER 0x00000004 +#define NVAPI_OGLEXPERT_OUTPUT_TO_CALLBACK 0x00000008 +#define NVAPI_OGLEXPERT_OUTPUT_TO_ALL 0xFFFFFFFF + +//! @} + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION TYPE: NVAPI_OGLEXPERT_CALLBACK +// +//! DESCRIPTION: Used in conjunction with OUTPUT_TO_CALLBACK, this is a simple +//! callback function the user may use to obtain the feedback +//! stream. The function will be called once per fully qualified +//! feedback stream entry. +//! +//! \param categoryId Contains the bit from the NVAPI_OGLEXPERT_REPORT +//! mask that corresponds to the current message +//! \param messageId Unique ID for the current message +//! \param detailLevel Contains the bit from the NVAPI_OGLEXPERT_DETAIL +//! mask that corresponds to the current message +//! \param objectId Unique ID of the object that corresponds to the +//! current message +//! \param messageStr Text string from the current message +//! +//! \ingroup oglapi +/////////////////////////////////////////////////////////////////////////////// +typedef void (* NVAPI_OGLEXPERT_CALLBACK) (unsigned int categoryId, unsigned int messageId, unsigned int detailLevel, int objectId, const char *messageStr); + + + +//! \ingroup oglapi +//! SUPPORTED OS: Windows XP and higher +//! +NVAPI_INTERFACE NvAPI_OGL_ExpertModeSet(NvU32 expertDetailLevel, + NvU32 expertReportMask, + NvU32 expertOutputMask, + NVAPI_OGLEXPERT_CALLBACK expertCallback); + +//! \addtogroup oglapi +//! SUPPORTED OS: Windows XP and higher +//! +NVAPI_INTERFACE NvAPI_OGL_ExpertModeGet(NvU32 *pExpertDetailLevel, + NvU32 *pExpertReportMask, + NvU32 *pExpertOutputMask, + NVAPI_OGLEXPERT_CALLBACK *pExpertCallback); + +//@} +/////////////////////////////////////////////////////////////////////////////// +// +//! \name NvAPI_OGL_ExpertModeDefaultsSet[Get] Functions +//! +//@{ +//! This function configures OpenGL Expert Mode global defaults. These settings +//! apply to any OpenGL application which starts up after these +//! values are applied (i.e. these settings *do not* apply to +//! currently running applications). +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \param expertDetailLevel Value which specifies the detail level in +//! the feedback stream. This is a mask made up +//! of NVAPI_OGLEXPERT_LEVEL bits. +//! +//! \param expertReportMask Mask made up of NVAPI_OGLEXPERT_REPORT bits, +//! this parameter specifies the areas of +//! functional interest. +//! +//! \param expertOutputMask Mask made up of NVAPI_OGLEXPERT_OUTPUT bits, +//! this parameter specifies the feedback output +//! location. Note that using OUTPUT_TO_CALLBACK +//! here is meaningless and has no effect, but +//! using it will not cause an error. +//! +//! \return ::NVAPI_ERROR or ::NVAPI_OK +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup oglapi +//! SUPPORTED OS: Windows XP and higher +//! +NVAPI_INTERFACE NvAPI_OGL_ExpertModeDefaultsSet(NvU32 expertDetailLevel, + NvU32 expertReportMask, + NvU32 expertOutputMask); + +//! \addtogroup oglapi +//! SUPPORTED OS: Windows XP and higher +//! +NVAPI_INTERFACE NvAPI_OGL_ExpertModeDefaultsGet(NvU32 *pExpertDetailLevel, + NvU32 *pExpertReportMask, + NvU32 *pExpertOutputMask); +//@} + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnumTCCPhysicalGPUs +// +//! This function returns an array of physical GPU handles that are in TCC Mode. +//! Each handle represents a physical GPU present in the system in TCC Mode. +//! That GPU may not be visible to the OS directly. +//! +//! The array nvGPUHandle will be filled with physical GPU handle values. The returned +//! gpuCount determines how many entries in the array are valid. +//! +//! NOTE: Handles enumerated by this API are only valid for NvAPIs that are tagged as TCC_SUPPORTED +//! If handle is passed to any other API, it will fail with NVAPI_INVALID_HANDLE +//! +//! For WDDM GPU handles please use NvAPI_EnumPhysicalGPUs() +//! +//! SUPPORTED OS: Windows Vista and higher, Mac OS X +//! +//! +//! +//! \param [out] nvGPUHandle Physical GPU array that will contain all TCC Physical GPUs +//! \param [out] pGpuCount count represent the number of valid entries in nvGPUHandle +//! +//! +//! \retval NVAPI_INVALID_ARGUMENT nvGPUHandle or pGpuCount is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_EnumTCCPhysicalGPUs( NvPhysicalGpuHandle nvGPUHandle[NVAPI_MAX_PHYSICAL_GPUS], NvU32 *pGpuCount); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnumLogicalGPUs +// +//! This function returns an array of logical GPU handles. +//! +//! Each handle represents one or more GPUs acting in concert as a single graphics device. +//! +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! The array nvGPUHandle will be filled with logical GPU handle values. The returned +//! gpuCount determines how many entries in the array are valid. +//! +//! \note All logical GPUs handles get invalidated on a GPU topology change, so the calling +//! application is required to renum the logical GPU handles to get latest physical handle +//! mapping after every GPU topology change activated by a call to NvAPI_SetGpuTopologies(). +//! +//! To detect if SLI rendering is enabled, use NvAPI_D3D_GetCurrentSLIState(). +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT nvGPUHandle or pGpuCount is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_EnumLogicalGPUs(NvLogicalGpuHandle nvGPUHandle[NVAPI_MAX_LOGICAL_GPUS], NvU32 *pGpuCount); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetPhysicalGPUsFromDisplay +// +//! This function returns an array of physical GPU handles associated with the specified display. +//! +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! The array nvGPUHandle will be filled with physical GPU handle values. The returned +//! gpuCount determines how many entries in the array are valid. +//! +//! If the display corresponds to more than one physical GPU, the first GPU returned +//! is the one with the attached active output. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hNvDisp is not valid; nvGPUHandle or pGpuCount is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND no NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GetPhysicalGPUsFromDisplay(NvDisplayHandle hNvDisp, NvPhysicalGpuHandle nvGPUHandle[NVAPI_MAX_PHYSICAL_GPUS], NvU32 *pGpuCount); + +typedef NvAPI_Status (__cdecl *_NvAPI_GetPhysicalGPUsFromDisplay)(NvDisplayHandle hNvDisp, NvPhysicalGpuHandle nvGPUHandle[NVAPI_MAX_PHYSICAL_GPUS], NvU32 *pGpuCount); +_NvAPI_GetPhysicalGPUsFromDisplay NvAPI_GetPhysicalGPUsFromDisplay; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetPhysicalGPUFromUnAttachedDisplay +// +//! This function returns a physical GPU handle associated with the specified unattached display. +//! The source GPU is a physical render GPU which renders the frame buffer but may or may not drive the scan out. +//! +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hNvUnAttachedDisp is not valid or pPhysicalGpu is NULL. +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetPhysicalGPUFromUnAttachedDisplay(NvUnAttachedDisplayHandle hNvUnAttachedDisp, NvPhysicalGpuHandle *pPhysicalGpu); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetLogicalGPUFromDisplay +// +//! This function returns the logical GPU handle associated with the specified display. +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hNvDisp is not valid; pLogicalGPU is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetLogicalGPUFromDisplay(NvDisplayHandle hNvDisp, NvLogicalGpuHandle *pLogicalGPU); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetLogicalGPUFromPhysicalGPU +// +//! This function returns the logical GPU handle associated with specified physical GPU handle. +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGPU is not valid; pLogicalGPU is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetLogicalGPUFromPhysicalGPU(NvPhysicalGpuHandle hPhysicalGPU, NvLogicalGpuHandle *pLogicalGPU); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetPhysicalGPUsFromLogicalGPU +// +//! This function returns the physical GPU handles associated with the specified logical GPU handle. +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! The array hPhysicalGPU will be filled with physical GPU handle values. The returned +//! gpuCount determines how many entries in the array are valid. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hLogicalGPU is not valid; hPhysicalGPU is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_LOGICAL_GPU_HANDLE hLogicalGPU was not a logical GPU handle +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetPhysicalGPUsFromLogicalGPU(NvLogicalGpuHandle hLogicalGPU,NvPhysicalGpuHandle hPhysicalGPU[NVAPI_MAX_PHYSICAL_GPUS], NvU32 *pGpuCount); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetGpuCoreCount +// +//! DESCRIPTION: Retrieves the total number of cores defined for a GPU. +//! Returns 0 on architectures that don't define GPU cores. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \retval ::NVAPI_INVALID_ARGUMENT pCount is NULL +//! \retval ::NVAPI_OK *pCount is set +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND no NVIDIA GPU driving a display was found +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! \retval ::NVAPI_NOT_SUPPORTED API call is not supported on current architecture +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetGpuCoreCount(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pCount); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetGpuCoreCount)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Out_ NvU32* pCount); +_NvAPI_GPU_GetGpuCoreCount NvAPI_GPU_GetGpuCoreCount; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetAllOutputs +// +//! This function returns set of all GPU-output identifiers as a bitmask. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetAllDisplayIds. +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 85 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL. +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetAllDisplayIds.") +NVAPI_INTERFACE NvAPI_GPU_GetAllOutputs(NvPhysicalGpuHandle hPhysicalGpu,NvU32 *pOutputsMask); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetConnectedOutputs +// +//! This function is the same as NvAPI_GPU_GetAllOutputs() but returns only the set of GPU output +//! identifiers that are connected to display devices. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds. +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL. +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds.") +NVAPI_INTERFACE NvAPI_GPU_GetConnectedOutputs(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pOutputsMask); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetConnectedSLIOutputs +// +//! DESCRIPTION: This function is the same as NvAPI_GPU_GetConnectedOutputs() but returns only the set of GPU-output +//! identifiers that can be selected in an SLI configuration. +//! NOTE: This function matches NvAPI_GPU_GetConnectedOutputs() +//! - On systems which are not SLI capable. +//! - If the queried GPU is not part of a valid SLI group. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds. +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 170 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE: hPhysicalGpu was not a physical GPU handle +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds.") +NVAPI_INTERFACE NvAPI_GPU_GetConnectedSLIOutputs(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pOutputsMask); + + + + +//! \ingroup gpu +typedef enum _NV_MONITOR_CONN_TYPE +{ + NV_MONITOR_CONN_TYPE_UNINITIALIZED = 0, + NV_MONITOR_CONN_TYPE_VGA, + NV_MONITOR_CONN_TYPE_COMPONENT, + NV_MONITOR_CONN_TYPE_SVIDEO, + NV_MONITOR_CONN_TYPE_HDMI, + NV_MONITOR_CONN_TYPE_DVI, + NV_MONITOR_CONN_TYPE_LVDS, + NV_MONITOR_CONN_TYPE_DP, + NV_MONITOR_CONN_TYPE_COMPOSITE, + NV_MONITOR_CONN_TYPE_UNKNOWN = -1 +} NV_MONITOR_CONN_TYPE; + + + +//! \addtogroup gpu +//! @{ +#define NV_GPU_CONNECTED_IDS_FLAG_UNCACHED NV_BIT(0) //!< Get uncached connected devices +#define NV_GPU_CONNECTED_IDS_FLAG_SLI NV_BIT(1) //!< Get devices such that those can be selected in an SLI configuration +#define NV_GPU_CONNECTED_IDS_FLAG_LIDSTATE NV_BIT(2) //!< Get devices such that to reflect the Lid State +#define NV_GPU_CONNECTED_IDS_FLAG_FAKE NV_BIT(3) //!< Get devices that includes the fake connected monitors +#define NV_GPU_CONNECTED_IDS_FLAG_EXCLUDE_MST NV_BIT(4) //!< Excludes devices that are part of the multi stream topology. + +//! @} + +//! \ingroup gpu +typedef struct _NV_GPU_DISPLAYIDS +{ + NvU32 version; + NV_MONITOR_CONN_TYPE connectorType; //!< out: vga, tv, dvi, hdmi and dp. This is reserved for future use and clients should not rely on this information. Instead get the + //!< GPU connector type from NvAPI_GPU_GetConnectorInfo/NvAPI_GPU_GetConnectorInfoEx + NvU32 displayId; //!< this is a unique identifier for each device + NvU32 isDynamic:1; //!< if bit is set then this display is part of MST topology and it's a dynamic + NvU32 isMultiStreamRootNode:1; //!< if bit is set then this displayID belongs to a multi stream enabled connector(root node). Note that when multi stream is enabled and + //!< a single multi stream capable monitor is connected to it, the monitor will share the display id with the RootNode. + //!< When there is more than one monitor connected in a multi stream topology, then the root node will have a separate displayId. + NvU32 isActive:1; //!< if bit is set then this display is being actively driven + NvU32 isCluster:1; //!< if bit is set then this display is the representative display + NvU32 isOSVisible:1; //!< if bit is set, then this display is reported to the OS + NvU32 isWFD:1; //!< if bit is set, then this display is wireless + NvU32 isConnected:1; //!< if bit is set, then this display is connected + NvU32 reserved: 22; //!< must be zero +} NV_GPU_DISPLAYIDS; + +//! \ingroup gpu +//! Macro for constructing the version field of ::_NV_GPU_DISPLAYIDS +#define NV_GPU_DISPLAYIDS_VER1 MAKE_NVAPI_VERSION(NV_GPU_DISPLAYIDS,1) + +#define NV_GPU_DISPLAYIDS_VER NV_GPU_DISPLAYIDS_VER1 + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetConnectedDisplayIds +// +//! \code +//! DESCRIPTION: Due to space limitation NvAPI_GPU_GetConnectedOutputs can return maximum 32 devices, but +//! this is no longer true for DPMST. NvAPI_GPU_GetConnectedDisplayIds will return all +//! the connected display devices in the form of displayIds for the associated hPhysicalGpu. +//! This function can accept set of flags to request cached, uncached, sli and lid to get the connected devices. +//! Default value for flags will be cached . +//! HOW TO USE: 1) for each PhysicalGpu, make a call to get the number of connected displayId's +//! using NvAPI_GPU_GetConnectedDisplayIds by passing the pDisplayIds as NULL +//! On call success: +//! 2) Allocate memory based on pDisplayIdCount then make a call NvAPI_GPU_GetConnectedDisplayIds to populate DisplayIds +//! SUPPORTED OS: Windows XP and higher +//! +//! PARAMETERS: hPhysicalGpu (IN) - GPU selection +//! flags (IN) - One or more defines from NV_GPU_CONNECTED_IDS_FLAG_* as valid flags. +//! pDisplayIds (IN/OUT) - Pointer to an NV_GPU_DISPLAYIDS struct, each entry represents a one displayID and its attributes +//! pDisplayIdCount(OUT)- Number of displayId's. +//! +//! RETURN STATUS: NVAPI_INVALID_ARGUMENT: hPhysicalGpu or pDisplayIds or pDisplayIdCount is NULL +//! NVAPI_OK: *pDisplayIds contains a set of GPU-output identifiers +//! NVAPI_NVIDIA_DEVICE_NOT_FOUND: no NVIDIA GPU driving a display was found +//! NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE: hPhysicalGpu was not a physical GPU handle +//! \endcode +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetConnectedDisplayIds(_In_ NvPhysicalGpuHandle hPhysicalGpu, __inout_ecount_part_opt(*pDisplayIdCount, *pDisplayIdCount) NV_GPU_DISPLAYIDS* pDisplayIds, _Inout_ NvU32* pDisplayIdCount, _In_ NvU32 flags); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetAllDisplayIds +// +//! DESCRIPTION: This API returns display IDs for all possible outputs on the GPU. +//! For DPMST connector, it will return display IDs for all the video sinks in the topology. \n +//! HOW TO USE: 1. The first call should be made to get the all display ID count. To get the display ID count, send in \n +//! a) hPhysicalGpu - a valid GPU handle(enumerated using NvAPI_EnumPhysicalGPUs()) as input, \n +//! b) pDisplayIds - NULL, as we just want to get the display ID count. \n +//! c) pDisplayIdCount - a valid pointer to NvU32, whose value is set to ZERO. \n +//! If all parameters are correct and this call is successful, this call will return the display ID's count. \n +//! 2. To get the display ID array, make the second call to NvAPI_GPU_GetAllDisplayIds() with \n +//! a) hPhysicalGpu - should be same value which was sent in first call, \n +//! b) pDisplayIds - pointer to the display ID array allocated by caller based on display ID count, \n +//! eg. malloc(sizeof(NV_GPU_DISPLAYIDS) * pDisplayIdCount). \n +//! c) pDisplayIdCount - a valid pointer to NvU32. This indicates for how many display IDs \n +//! the memory is allocated(pDisplayIds) by the caller. \n +//! If all parameters are correct and this call is successful, this call will return the display ID array and actual +//! display ID count (which was obtained in the first call to NvAPI_GPU_GetAllDisplayIds). If the input display ID count is +//! less than the actual display ID count, it will overwrite the input and give the pDisplayIdCount as actual count and the +//! API will return NVAPI_INSUFFICIENT_BUFFER. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \param [in] hPhysicalGpu GPU selection. +//! \param [in,out] DisplayIds Pointer to an array of NV_GPU_DISPLAYIDS structures, each entry represents one displayID +//! and its attributes. +//! \param [in,out] pDisplayIdCount As input, this parameter indicates the number of display's id's for which caller has +//! allocated the memory. As output, it will return the actual number of display IDs. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval NVAPI_INSUFFICIENT_BUFFER When the input buffer(pDisplayIds) is less than the actual number of display IDs, this API +//! will return NVAPI_INSUFFICIENT_BUFFER. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetAllDisplayIds(_In_ NvPhysicalGpuHandle hPhysicalGpu, __inout_ecount_part_opt(*pDisplayIdCount, *pDisplayIdCount) NV_GPU_DISPLAYIDS* pDisplayIds, _Inout_ NvU32* pDisplayIdCount); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetConnectedOutputsWithLidState +// +//! This function is similar to NvAPI_GPU_GetConnectedOutputs(), and returns the connected display identifiers that are connected +//! as an output mask but unlike NvAPI_GPU_GetConnectedOutputs() this API "always" reflects the Lid State in the output mask. +//! Thus if you expect the LID close state to be available in the connection mask use this API. +//! - If LID is closed then this API will remove the LID panel from the connected display identifiers. +//! - If LID is open then this API will reflect the LID panel in the connected display identifiers. +//! +//! \note This API should be used on notebook systems and on systems where the LID state is required in the connection +//! output mask. On desktop systems the returned identifiers will match NvAPI_GPU_GetConnectedOutputs(). +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds. +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 95 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds.") +NVAPI_INTERFACE NvAPI_GPU_GetConnectedOutputsWithLidState(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pOutputsMask); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetConnectedSLIOutputsWithLidState +// +//! DESCRIPTION: This function is the same as NvAPI_GPU_GetConnectedOutputsWithLidState() but returns only the set +//! of GPU-output identifiers that can be selected in an SLI configuration. With SLI disabled, +//! this function matches NvAPI_GPU_GetConnectedOutputsWithLidState(). +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds. +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 170 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_GPU_GetConnectedDisplayIds.") +NVAPI_INTERFACE NvAPI_GPU_GetConnectedSLIOutputsWithLidState(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pOutputsMask); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetSystemType +// +//! \fn NvAPI_GPU_GetSystemType(NvPhysicalGpuHandle hPhysicalGpu, NV_SYSTEM_TYPE *pSystemType) +//! This function identifies whether the GPU is a notebook GPU or a desktop GPU. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 95 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL +//! \retval NVAPI_OK *pSystemType contains the GPU system type +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE: hPhysicalGpu was not a physical GPU handle +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +//! Used in NvAPI_GPU_GetSystemType() +typedef enum _NV_SYSTEM_TYPE +{ + NV_SYSTEM_TYPE_UNKNOWN = 0, + NV_SYSTEM_TYPE_LAPTOP = 1, + NV_SYSTEM_TYPE_DESKTOP = 2, +} NV_SYSTEM_TYPE; + + + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_GetSystemType(NvPhysicalGpuHandle hPhysicalGpu, NV_SYSTEM_TYPE *pSystemType); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetActiveOutputs +// +//! This function is the same as NvAPI_GPU_GetAllOutputs but returns only the set of GPU output +//! identifiers that are actively driving display devices. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 85 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pOutputsMask is NULL. +//! \retval NVAPI_OK *pOutputsMask contains a set of GPU-output identifiers. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetActiveOutputs(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pOutputsMask); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_SetEDID +// +//! Thus function sets the EDID data for the specified GPU handle and connection bit mask. +//! displayOutputId should have exactly 1 bit set to indicate a single display. See \ref handles. +//! \note The EDID will be cached across the boot session and will be enumerated to the OS in this call. +//! To remove the EDID set sizeofEDID to zero. +//! OS and NVAPI connection status APIs will reflect the newly set or removed EDID dynamically. +//! +//! This feature will NOT be supported on the following boards: +//! - GeForce +//! - Quadro VX +//! - Tesla +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 100 +//! +//! \retval NVAPI_INVALID_ARGUMENT pEDID is NULL; displayOutputId has 0 or > 1 bits set +//! \retval NVAPI_OK *pEDID data was applied to the requested displayOutputId. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE: hPhysicalGpu was not a physical GPU handle. +//! \retval NVAPI_NOT_SUPPORTED For the above mentioned GPUs +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_SetEDID(NvPhysicalGpuHandle hPhysicalGpu, NvU32 displayOutputId, NV_EDID *pEDID); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetOutputType +// +//! \fn NvAPI_GPU_GetOutputType(NvPhysicalGpuHandle hPhysicalGpu, NvU32 outputId, NV_GPU_OUTPUT_TYPE *pOutputType) +//! This function returns the output type for a specific physical GPU handle and outputId (exactly 1 bit set - see \ref handles). +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \Version Earliest supported ForceWare version: 82.61 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu, outputId, or pOutputsMask is NULL; or outputId has > 1 bit set +//! \retval NVAPI_OK *pOutputType contains a NvGpuOutputType value +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +//! used in NvAPI_GPU_GetOutputType() +typedef enum _NV_GPU_OUTPUT_TYPE +{ + NVAPI_GPU_OUTPUT_UNKNOWN = 0, + NVAPI_GPU_OUTPUT_CRT = 1, //!< CRT display device + NVAPI_GPU_OUTPUT_DFP = 2, //!< Digital Flat Panel display device + NVAPI_GPU_OUTPUT_TV = 3, //!< TV display device +} NV_GPU_OUTPUT_TYPE; + + + + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_GetOutputType(NvPhysicalGpuHandle hPhysicalGpu, NvU32 outputId, NV_GPU_OUTPUT_TYPE *pOutputType); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_ValidateOutputCombination +// +//! This function determines if a set of GPU outputs can be active +//! simultaneously. While a GPU may have outputs, typically they cannot +//! all be active at the same time due to internal resource sharing. +//! +//! Given a physical GPU handle and a mask of candidate outputs, this call +//! will return NVAPI_OK if all of the specified outputs can be driven +//! simultaneously. It will return NVAPI_INVALID_COMBINATION if they cannot. +//! +//! Use NvAPI_GPU_GetAllOutputs() to determine which outputs are candidates. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 85 +//! +//! \retval NVAPI_OK Combination of outputs in outputsMask are valid (can be active simultaneously). +//! \retval NVAPI_INVALID_COMBINATION Combination of outputs in outputsMask are NOT valid. +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or outputsMask does not have at least 2 bits set. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_ValidateOutputCombination(NvPhysicalGpuHandle hPhysicalGpu, NvU32 outputsMask); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetFullName +// +//! This function retrieves the full GPU name as an ASCII string - for example, "Quadro FX 1400". +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \return NVAPI_ERROR or NVAPI_OK +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetFullName(NvPhysicalGpuHandle hPhysicalGpu, NvAPI_ShortString szName); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetFullName)(NvPhysicalGpuHandle hPhysicalGpu, NvAPI_ShortString szName); +_NvAPI_GPU_GetFullName NvAPI_GPU_GetFullName; + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetPCIIdentifiers +// +//! This function returns the PCI identifiers associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \param DeviceId The internal PCI device identifier for the GPU. +//! \param SubSystemId The internal PCI subsystem identifier for the GPU. +//! \param RevisionId The internal PCI device-specific revision identifier for the GPU. +//! \param ExtDeviceId The external PCI device identifier for the GPU. +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or an argument is NULL +//! \retval NVAPI_OK Arguments are populated with PCI identifiers +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetPCIIdentifiers)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Out_ NvU32* pDeviceId, _Out_ NvU32* pSubSystemId, _Out_ NvU32* pRevisionId, _Out_ NvU32* pExtDeviceId); +_NvAPI_GPU_GetPCIIdentifiers NvAPI_GPU_GetPCIIdentifiers; + + +//! \ingroup gpu +//! Used in NvAPI_GPU_GetGPUType(). +typedef enum _NV_GPU_TYPE +{ + NV_SYSTEM_TYPE_GPU_UNKNOWN = 0, + NV_SYSTEM_TYPE_IGPU = 1, //!< Integrated GPU + NV_SYSTEM_TYPE_DGPU = 2, //!< Discrete GPU +} NV_GPU_TYPE; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetGPUType +// +//! DESCRIPTION: This function returns the GPU type (integrated or discrete). +//! See ::NV_GPU_TYPE. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 173 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu +//! \retval NVAPI_OK *pGpuType contains the GPU type +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE: hPhysicalGpu was not a physical GPU handle +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetGPUType(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_GPU_TYPE *pGpuType); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetGPUType)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_GPU_TYPE *pGpuType); +_NvAPI_GPU_GetGPUType NvAPI_GPU_GetGPUType; + + +//! \ingroup gpu +//! Used in NvAPI_GPU_GetBusType() +typedef enum _NV_GPU_BUS_TYPE +{ + NVAPI_GPU_BUS_TYPE_UNDEFINED = 0, + NVAPI_GPU_BUS_TYPE_PCI = 1, + NVAPI_GPU_BUS_TYPE_AGP = 2, + NVAPI_GPU_BUS_TYPE_PCI_EXPRESS = 3, + NVAPI_GPU_BUS_TYPE_FPCI = 4, + NVAPI_GPU_BUS_TYPE_AXI = 5, +} NV_GPU_BUS_TYPE; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetBusType +// +//! This function returns the type of bus associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pBusType is NULL. +//! \retval NVAPI_OK *pBusType contains bus identifier. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetBusType(NvPhysicalGpuHandle hPhysicalGpu,NV_GPU_BUS_TYPE *pBusType); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetBusType)(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_BUS_TYPE* pBusType); +_NvAPI_GPU_GetBusType NvAPI_GPU_GetBusType; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetBusId +// +//! DESCRIPTION: Returns the ID of the bus associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 167 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pBusId is NULL. +//! \retval NVAPI_OK *pBusId contains the bus ID. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetBusId)(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pBusId); +_NvAPI_GPU_GetBusId NvAPI_GPU_GetBusId; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetBusSlotId +// +//! DESCRIPTION: Returns the ID of the bus slot associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 167 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pBusSlotId is NULL. +//! \retval NVAPI_OK *pBusSlotId contains the bus slot ID. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetBusSlotId)(NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pBusSlotId); +_NvAPI_GPU_GetBusSlotId NvAPI_GPU_GetBusSlotId; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetIRQ +// +//! This function returns the interrupt number associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pIRQ is NULL. +//! \retval NVAPI_OK *pIRQ contains interrupt number. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetIRQ)(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pIRQ); +_NvAPI_GPU_GetIRQ NvAPI_GPU_GetIRQ; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetVbiosRevision +// +//! This function returns the revision of the video BIOS associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pBiosRevision is NULL. +//! \retval NVAPI_OK *pBiosRevision contains revision number. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetVbiosRevision(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pBiosRevision); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetVbiosOEMRevision +// +//! This function returns the OEM revision of the video BIOS associated with this GPU. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu or pBiosRevision is NULL +//! \retval NVAPI_OK *pBiosRevision contains revision number +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetVbiosOEMRevision(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pBiosRevision); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetVbiosVersionString +// +//! This function returns the full video BIOS version string in the form of xx.xx.xx.xx.yy where +//! - xx numbers come from NvAPI_GPU_GetVbiosRevision() and +//! - yy comes from NvAPI_GPU_GetVbiosOEMRevision(). +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT hPhysicalGpu is NULL. +//! \retval NVAPI_OK szBiosRevision contains version string. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetVbiosVersionString(NvPhysicalGpuHandle hPhysicalGpu, NvAPI_ShortString szBiosRevision); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetVbiosVersionString)(NvPhysicalGpuHandle hPhysicalGpu, NvAPI_ShortString szBiosRevision); +_NvAPI_GPU_GetVbiosVersionString NvAPI_GPU_GetVbiosVersionString; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetAGPAperture +// +//! This function returns the AGP aperture in megabytes. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT pSize is NULL. +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetAGPAperture(NvPhysicalGpuHandle hPhysicalGpu,NvU32 *pSize); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetCurrentAGPRate +// +//! This function returns the current AGP Rate (0 = AGP not present, 1 = 1x, 2 = 2x, etc.). +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT pRate is NULL. +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetCurrentAGPRate(NvPhysicalGpuHandle hPhysicalGpu,NvU32 *pRate); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetCurrentPCIEDownstreamWidth +// +//! This function returns the number of PCIE lanes being used for the PCIE interface +//! downstream from the GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT pWidth is NULL. +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetCurrentPCIEDownstreamWidth(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pWidth); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetPhysicalFrameBufferSize +// +//! This function returns the physical size of framebuffer in KB. This does NOT include any +//! system RAM that may be dedicated for use by the GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT pSize is NULL +//! \retval NVAPI_OK Call successful +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetPhysicalFrameBufferSize(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pSize); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetVirtualFrameBufferSize +// +//! This function returns the virtual size of framebuffer in KB. This includes the physical RAM plus any +//! system RAM that has been dedicated for use by the GPU. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 90 +//! +//! \retval NVAPI_INVALID_ARGUMENT pSize is NULL. +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu was not a physical GPU handle. +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetVirtualFrameBufferSize(NvPhysicalGpuHandle hPhysicalGpu, NvU32* pSize); + + + +//! \ingroup gpu +typedef struct _NV_BOARD_INFO +{ + NvU32 version; //!< structure version + NvU8 BoardNum[16]; //!< Board Serial Number +}NV_BOARD_INFO_V1; + +//! \ingroup gpu +typedef NV_BOARD_INFO_V1 NV_BOARD_INFO; +//! \ingroup gpu +#define NV_BOARD_INFO_VER1 MAKE_NVAPI_VERSION(NV_BOARD_INFO_V1,1) +//! \ingroup gpu +#define NV_BOARD_INFO_VER NV_BOARD_INFO_VER1 + +//! SUPPORTED OS: Windows XP and higher +//! +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetBoardInfo +// +//! DESCRIPTION: This API Retrieves the Board information (a unique GPU Board Serial Number) stored in the InfoROM. +//! +//! \param [in] hPhysicalGpu Physical GPU Handle. +//! \param [in,out] NV_BOARD_INFO Board Information. +//! +//! TCC_SUPPORTED +//! +//! \retval ::NVAPI_OK completed request +//! \retval ::NVAPI_ERROR miscellaneous error occurred +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE handle passed is not a physical GPU handle +//! \retval ::NVAPI_API_NOT_INTIALIZED NVAPI not initialized +//! \retval ::NVAPI_INVALID_POINTER pBoardInfo is NULL +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION the version of the INFO struct is not supported +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetBoardInfo)(NvPhysicalGpuHandle hPhysicalGpu, NV_BOARD_INFO *pBoardInfo); +_NvAPI_GPU_GetBoardInfo NvAPI_GPU_GetBoardInfo; + + +/////////////////////////////////////////////////////////////////////////////// +// +// GPU Clock Control +// +// These APIs allow the user to get and set individual clock domains +// on a per-GPU basis. +// +/////////////////////////////////////////////////////////////////////////////// + + +//! \ingroup gpuclock +//! @{ +#define NVAPI_MAX_GPU_CLOCKS 32 +#define NVAPI_MAX_GPU_PUBLIC_CLOCKS 32 +#define NVAPI_MAX_GPU_PERF_CLOCKS 32 +#define NVAPI_MAX_GPU_PERF_VOLTAGES 16 +#define NVAPI_MAX_GPU_PERF_PSTATES 16 +//! @} + +//! \ingroup gpuclock +typedef enum _NV_GPU_PUBLIC_CLOCK_ID +{ + NVAPI_GPU_PUBLIC_CLOCK_GRAPHICS = 0, + NVAPI_GPU_PUBLIC_CLOCK_MEMORY = 4, + NVAPI_GPU_PUBLIC_CLOCK_PROCESSOR = 7, + NVAPI_GPU_PUBLIC_CLOCK_UNDEFINED = NVAPI_MAX_GPU_PUBLIC_CLOCKS, +} NV_GPU_PUBLIC_CLOCK_ID; + + +//! \ingroup gpuclock +typedef enum _NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID +{ + NVAPI_GPU_PERF_VOLTAGE_INFO_DOMAIN_CORE = 0, + NVAPI_GPU_PERF_VOLTAGE_INFO_DOMAIN_UNDEFINED = NVAPI_MAX_GPU_PERF_VOLTAGES, +} NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID; + + + +//! \ingroup gpuclock +//! Used in NvAPI_GPU_GetAllClockFrequencies() +typedef struct _NV_GPU_CLOCK_FREQUENCIES_V1 +{ + NvU32 version; //!< Structure version + NvU32 reserved; //!< These bits are reserved for future use. + struct + { + NvU32 bIsPresent:1; //!< Set if this domain is present on this GPU + NvU32 reserved:31; //!< These bits are reserved for future use. + NvU32 frequency; //!< Clock frequency (kHz) + }domain[NVAPI_MAX_GPU_PUBLIC_CLOCKS]; +} NV_GPU_CLOCK_FREQUENCIES_V1; + +//! \ingroup gpuclock +//! Used in NvAPI_GPU_GetAllClockFrequencies() +typedef enum _NV_GPU_CLOCK_FREQUENCIES_CLOCK_TYPE +{ + NV_GPU_CLOCK_FREQUENCIES_CURRENT_FREQ = 0, + NV_GPU_CLOCK_FREQUENCIES_BASE_CLOCK = 1, + NV_GPU_CLOCK_FREQUENCIES_BOOST_CLOCK = 2, + NV_GPU_CLOCK_FREQUENCIES_CLOCK_TYPE_NUM = 3 +} NV_GPU_CLOCK_FREQUENCIES_CLOCK_TYPE; + +//! \ingroup gpuclock +//! Used in NvAPI_GPU_GetAllClockFrequencies() +typedef struct +{ + NvU32 version; //!< Structure version + NvU32 ClockType:2; //!< One of NV_GPU_CLOCK_FREQUENCIES_CLOCK_TYPE. Used to specify the type of clock to be returned. + NvU32 reserved:22; //!< These bits are reserved for future use. Must be set to 0. + NvU32 reserved1:8; //!< These bits are reserved. + struct + { + NvU32 bIsPresent:1; //!< Set if this domain is present on this GPU + NvU32 reserved:31; //!< These bits are reserved for future use. + NvU32 frequency; //!< Clock frequency (kHz) + }domain[NVAPI_MAX_GPU_PUBLIC_CLOCKS]; +} NV_GPU_CLOCK_FREQUENCIES_V2; + +//! \ingroup gpuclock +//! Used in NvAPI_GPU_GetAllClockFrequencies() +typedef NV_GPU_CLOCK_FREQUENCIES_V2 NV_GPU_CLOCK_FREQUENCIES; + +//! \addtogroup gpuclock +//! @{ +#define NV_GPU_CLOCK_FREQUENCIES_VER_1 MAKE_NVAPI_VERSION(NV_GPU_CLOCK_FREQUENCIES_V1,1) +#define NV_GPU_CLOCK_FREQUENCIES_VER_2 MAKE_NVAPI_VERSION(NV_GPU_CLOCK_FREQUENCIES_V2,2) +#define NV_GPU_CLOCK_FREQUENCIES_VER NV_GPU_CLOCK_FREQUENCIES_VER_2 +//! @} + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetAllClockFrequencies +// +//! This function retrieves the NV_GPU_CLOCK_FREQUENCIES structure for the specified physical GPU. +//! +//! For each clock domain: +//! - bIsPresent is set for each domain that is present on the GPU +//! - frequency is the domain's clock freq in kHz +//! +//! Each domain's info is indexed in the array. For example: +//! clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_MEMORY] holds the info for the MEMORY domain. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 295 +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, +//! they are listed below. +//! \retval NVAPI_INVALID_ARGUMENT pClkFreqs is NULL. +//! \ingroup gpuclock +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetAllClockFrequencies(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_GPU_CLOCK_FREQUENCIES* pClkFreqs); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetAllClockFrequencies)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_GPU_CLOCK_FREQUENCIES* pClkFreqs); +_NvAPI_GPU_GetAllClockFrequencies NvAPI_GPU_GetAllClockFrequencies; + +//! \addtogroup gpupstate +//! @{ + +typedef enum _NV_GPU_PERF_PSTATE_ID +{ + NVAPI_GPU_PERF_PSTATE_P0 = 0, + NVAPI_GPU_PERF_PSTATE_P1, + NVAPI_GPU_PERF_PSTATE_P2, + NVAPI_GPU_PERF_PSTATE_P3, + NVAPI_GPU_PERF_PSTATE_P4, + NVAPI_GPU_PERF_PSTATE_P5, + NVAPI_GPU_PERF_PSTATE_P6, + NVAPI_GPU_PERF_PSTATE_P7, + NVAPI_GPU_PERF_PSTATE_P8, + NVAPI_GPU_PERF_PSTATE_P9, + NVAPI_GPU_PERF_PSTATE_P10, + NVAPI_GPU_PERF_PSTATE_P11, + NVAPI_GPU_PERF_PSTATE_P12, + NVAPI_GPU_PERF_PSTATE_P13, + NVAPI_GPU_PERF_PSTATE_P14, + NVAPI_GPU_PERF_PSTATE_P15, + NVAPI_GPU_PERF_PSTATE_UNDEFINED = NVAPI_MAX_GPU_PERF_PSTATES, + NVAPI_GPU_PERF_PSTATE_ALL, + +} NV_GPU_PERF_PSTATE_ID; + +//! @} + + + +//! \ingroup gpupstate +//! Used in NvAPI_GPU_GetPstatesInfoEx() +typedef struct _NV_GPU_PERF_PSTATES_INFO_V1 +{ + NvU32 version; + NvU32 flags; //!< - bit 0 indicates if perfmon is enabled or not + //!< - bit 1 indicates if dynamic Pstate is capable or not + //!< - bit 2 indicates if dynamic Pstate is enable or not + //!< - all other bits must be set to 0 + NvU32 numPstates; //!< The number of available p-states + NvU32 numClocks; //!< The number of clock domains supported by each P-State + struct + { + NV_GPU_PERF_PSTATE_ID pstateId; //!< ID of the p-state. + NvU32 flags; //!< - bit 0 indicates if the PCIE limit is GEN1 or GEN2 + //!< - bit 1 indicates if the Pstate is overclocked or not + //!< - bit 2 indicates if the Pstate is overclockable or not + //!< - all other bits must be set to 0 + struct + { + NV_GPU_PUBLIC_CLOCK_ID domainId; //!< ID of the clock domain + NvU32 flags; //!< Reserved. Must be set to 0 + NvU32 freq; //!< Clock frequency in kHz + + } clocks[NVAPI_MAX_GPU_PERF_CLOCKS]; + } pstates[NVAPI_MAX_GPU_PERF_PSTATES]; + +} NV_GPU_PERF_PSTATES_INFO_V1; + + +//! \ingroup gpupstate +typedef struct _NV_GPU_PERF_PSTATES_INFO_V2 +{ + NvU32 version; + NvU32 flags; //!< - bit 0 indicates if perfmon is enabled or not + //!< - bit 1 indicates if dynamic Pstate is capable or not + //!< - bit 2 indicates if dynamic Pstate is enable or not + //!< - all other bits must be set to 0 + NvU32 numPstates; //!< The number of available p-states + NvU32 numClocks; //!< The number of clock domains supported by each P-State + NvU32 numVoltages; + struct + { + NV_GPU_PERF_PSTATE_ID pstateId; //!< ID of the p-state. + NvU32 flags; //!< - bit 0 indicates if the PCIE limit is GEN1 or GEN2 + //!< - bit 1 indicates if the Pstate is overclocked or not + //!< - bit 2 indicates if the Pstate is overclockable or not + //!< - all other bits must be set to 0 + struct + { + NV_GPU_PUBLIC_CLOCK_ID domainId; + NvU32 flags; //!< bit 0 indicates if this clock is overclockable + //!< all other bits must be set to 0 + NvU32 freq; + + } clocks[NVAPI_MAX_GPU_PERF_CLOCKS]; + struct + { + NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID domainId; //!< ID of the voltage domain, containing flags and mvolt info + NvU32 flags; //!< Reserved for future use. Must be set to 0 + NvU32 mvolt; //!< Voltage in mV + + } voltages[NVAPI_MAX_GPU_PERF_VOLTAGES]; + + } pstates[NVAPI_MAX_GPU_PERF_PSTATES]; //!< Valid index range is 0 to numVoltages-1 + +} NV_GPU_PERF_PSTATES_INFO_V2; + +//! \ingroup gpupstate +typedef NV_GPU_PERF_PSTATES_INFO_V2 NV_GPU_PERF_PSTATES_INFO; + + +//! \ingroup gpupstate +//! @{ + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES_INFO_V1 +#define NV_GPU_PERF_PSTATES_INFO_VER1 MAKE_NVAPI_VERSION(NV_GPU_PERF_PSTATES_INFO_V1,1) + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES_INFO_V2 +#define NV_GPU_PERF_PSTATES_INFO_VER2 MAKE_NVAPI_VERSION(NV_GPU_PERF_PSTATES_INFO_V2,2) + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES_INFO +#define NV_GPU_PERF_PSTATES_INFO_VER NV_GPU_PERF_PSTATES_INFO_VER2 + +//! @} + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetPstatesInfoEx +// +//! DESCRIPTION: This API retrieves all performance states (P-States) information. This is the same as +//! NvAPI_GPU_GetPstatesInfo(), but supports an input flag for various options. +//! +//! P-States are GPU active/executing performance capability and power consumption states. +//! +//! P-States ranges from P0 to P15, with P0 being the highest performance/power state, and +//! P15 being the lowest performance/power state. Each P-State, if available, maps to a +//! performance level. Not all P-States are available on a given system. The definitions +//! of each P-State are currently as follows: \n +//! - P0/P1 - Maximum 3D performance +//! - P2/P3 - Balanced 3D performance-power +//! - P8 - Basic HD video playback +//! - P10 - DVD playback +//! - P12 - Minimum idle power consumption +//! +//! \deprecated Do not use this function - it is deprecated in release 304. Instead, use NvAPI_GPU_GetPstates20. +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \param [in] hPhysicalGPU GPU selection. +//! \param [out] pPerfPstatesInfo P-States information retrieved, as detailed below: \n +//! - flags is reserved for future use. +//! - numPstates is the number of available P-States +//! - numClocks is the number of clock domains supported by each P-State +//! - pstates has valid index range from 0 to numPstates - 1 +//! - pstates[i].pstateId is the ID of the P-State, +//! containing the following info: +//! - pstates[i].flags containing the following info: +//! - bit 0 indicates if the PCIE limit is GEN1 or GEN2 +//! - bit 1 indicates if the Pstate is overclocked or not +//! - bit 2 indicates if the Pstate is overclockable or not +//! - pstates[i].clocks has valid index range from 0 to numClocks -1 +//! - pstates[i].clocks[j].domainId is the public ID of the clock domain, +//! containing the following info: +//! - pstates[i].clocks[j].flags containing the following info: +//! bit 0 indicates if the clock domain is overclockable or not +//! - pstates[i].clocks[j].freq is the clock frequency in kHz +//! - pstates[i].voltages has a valid index range from 0 to numVoltages - 1 +//! - pstates[i].voltages[j].domainId is the ID of the voltage domain, +//! containing the following info: +//! - pstates[i].voltages[j].flags is reserved for future use. +//! - pstates[i].voltages[j].mvolt is the voltage in mV +//! inputFlags(IN) - This can be used to select various options: +//! - if bit 0 is set, pPerfPstatesInfo would contain the default settings +//! instead of the current, possibily overclocked settings. +//! - if bit 1 is set, pPerfPstatesInfo would contain the maximum clock +//! frequencies instead of the nominal frequencies. +//! - if bit 2 is set, pPerfPstatesInfo would contain the minimum clock +//! frequencies instead of the nominal frequencies. +//! - all other bits must be set to 0. +//! +//! \retval ::NVAPI_OK Completed request +//! \retval ::NVAPI_ERROR Miscellaneous error occurred +//! \retval ::NVAPI_HANDLE_INVALIDATED Handle passed has been invalidated (see user guide) +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE Handle passed is not a physical GPU handle +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the NV_GPU_PERF_PSTATES struct is not supported +//! +//! \ingroup gpupstate +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 304. Instead, use NvAPI_GPU_GetPstates20.") +NVAPI_INTERFACE NvAPI_GPU_GetPstatesInfoEx(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_PERF_PSTATES_INFO *pPerfPstatesInfo, NvU32 inputFlags); + + +//! \addtogroup gpupstate +//! @{ + +#define NVAPI_MAX_GPU_PSTATE20_PSTATES 16 +#define NVAPI_MAX_GPU_PSTATE20_CLOCKS 8 +#define NVAPI_MAX_GPU_PSTATE20_BASE_VOLTAGES 4 + +//! Used to identify clock type +typedef enum _NV_GPU_PERF_PSTATE20_CLOCK_TYPE_ID +{ + //! Clock domains that use single frequency value within given pstate + NVAPI_GPU_PERF_PSTATE20_CLOCK_TYPE_SINGLE = 0, + + //! Clock domains that allow range of frequency values within given pstate + NVAPI_GPU_PERF_PSTATE20_CLOCK_TYPE_RANGE, +} NV_GPU_PERF_PSTATE20_CLOCK_TYPE_ID; + +//! Used to describe both voltage and frequency deltas +typedef struct _NV_GPU_PERF_PSTATES20_PARAM_DELTA +{ + //! Value of parameter delta (in respective units [kHz, uV]) + NvS32 value; + + struct + { + //! Min value allowed for parameter delta (in respective units [kHz, uV]) + NvS32 min; + + //! Max value allowed for parameter delta (in respective units [kHz, uV]) + NvS32 max; + } valueRange; +} NV_GPU_PERF_PSTATES20_PARAM_DELTA; + +//! Used to describe single clock entry +typedef struct _NV_GPU_PSTATE20_CLOCK_ENTRY_V1 +{ + //! ID of the clock domain + NV_GPU_PUBLIC_CLOCK_ID domainId; + + //! Clock type ID + NV_GPU_PERF_PSTATE20_CLOCK_TYPE_ID typeId; + NvU32 bIsEditable : 1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved : 31; + + //! Current frequency delta from nominal settings in (kHz) + NV_GPU_PERF_PSTATES20_PARAM_DELTA freqDelta_kHz; + + //! Clock domain type dependant information + union + { + struct + { + //! Clock frequency within given pstate in (kHz) + NvU32 freq_kHz; + } single; + + struct + { + //! Min clock frequency within given pstate in (kHz) + NvU32 minFreq_kHz; + + //! Max clock frequency within given pstate in (kHz) + NvU32 maxFreq_kHz; + + //! Voltage domain ID and value range in (uV) required for this clock + NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID domainId; + NvU32 minVoltage_uV; + NvU32 maxVoltage_uV; + } range; + } data; +} NV_GPU_PSTATE20_CLOCK_ENTRY_V1; + +//! Used to describe single base voltage entry +typedef struct _NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1 +{ + //! ID of the voltage domain + NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID domainId; + NvU32 bIsEditable:1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved:31; + + //! Current base voltage settings in [uV] + NvU32 volt_uV; + + NV_GPU_PERF_PSTATES20_PARAM_DELTA voltDelta_uV; // Current base voltage delta from nominal settings in [uV] +} NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1; + +//! Used in NvAPI_GPU_GetPstates20() interface call. + +typedef struct _NV_GPU_PERF_PSTATES20_INFO_V1 +{ + //! Version info of the structure (NV_GPU_PERF_PSTATES20_INFO_VER) + NvU32 version; + + NvU32 bIsEditable:1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved:31; + + //! Number of populated pstates + NvU32 numPstates; + + //! Number of populated clocks (per pstate) + NvU32 numClocks; + + //! Number of populated base voltages (per pstate) + NvU32 numBaseVoltages; + + //! Performance state (P-State) settings + //! Valid index range is 0 to numPstates-1 + struct + { + //! ID of the P-State + NV_GPU_PERF_PSTATE_ID pstateId; + + NvU32 bIsEditable:1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved:31; + + //! Array of clock entries + //! Valid index range is 0 to numClocks-1 + NV_GPU_PSTATE20_CLOCK_ENTRY_V1 clocks[NVAPI_MAX_GPU_PSTATE20_CLOCKS]; + + //! Array of baseVoltage entries + //! Valid index range is 0 to numBaseVoltages-1 + NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1 baseVoltages[NVAPI_MAX_GPU_PSTATE20_BASE_VOLTAGES]; + } pstates[NVAPI_MAX_GPU_PSTATE20_PSTATES]; +} NV_GPU_PERF_PSTATES20_INFO_V1; + +//! Used in NvAPI_GPU_GetPstates20() interface call. + +typedef struct _NV_GPU_PERF_PSTATES20_INFO_V2 +{ + //! Version info of the structure (NV_GPU_PERF_PSTATES20_INFO_VER) + NvU32 version; + + NvU32 bIsEditable : 1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved : 31; + + //! Number of populated pstates + NvU32 numPstates; + + //! Number of populated clocks (per pstate) + NvU32 numClocks; + + //! Number of populated base voltages (per pstate) + NvU32 numBaseVoltages; + + //! Performance state (P-State) settings + //! Valid index range is 0 to numPstates-1 + struct + { + //! ID of the P-State + NV_GPU_PERF_PSTATE_ID pstateId; + + NvU32 bIsEditable : 1; + + //! These bits are reserved for future use (must be always 0) + NvU32 reserved : 31; + + //! Array of clock entries + //! Valid index range is 0 to numClocks-1 + NV_GPU_PSTATE20_CLOCK_ENTRY_V1 clocks[NVAPI_MAX_GPU_PSTATE20_CLOCKS]; + + //! Array of baseVoltage entries + //! Valid index range is 0 to numBaseVoltages-1 + NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1 baseVoltages[NVAPI_MAX_GPU_PSTATE20_BASE_VOLTAGES]; + } pstates[NVAPI_MAX_GPU_PSTATE20_PSTATES]; + + //! OV settings - Please refer to NVIDIA over-volting recommendation to understand impact of this functionality + //! Valid index range is 0 to numVoltages-1 + struct + { + //! Number of populated voltages + NvU32 numVoltages; + + //! Array of voltage entries + //! Valid index range is 0 to numVoltages-1 + NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1 voltages[NVAPI_MAX_GPU_PSTATE20_BASE_VOLTAGES]; + } ov; +} NV_GPU_PERF_PSTATES20_INFO_V2; + +typedef NV_GPU_PERF_PSTATES20_INFO_V2 NV_GPU_PERF_PSTATES20_INFO; + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES20_INFO_V1 +#define NV_GPU_PERF_PSTATES20_INFO_VER1 MAKE_NVAPI_VERSION(NV_GPU_PERF_PSTATES20_INFO_V1,1) + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES20_INFO_V2 +#define NV_GPU_PERF_PSTATES20_INFO_VER2 MAKE_NVAPI_VERSION(NV_GPU_PERF_PSTATES20_INFO_V2,2) + +//! Macro for constructing the version field of NV_GPU_PERF_PSTATES20_INFO +#define NV_GPU_PERF_PSTATES20_INFO_VER NV_GPU_PERF_PSTATES20_INFO_VER2 + +//! @} + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetPstates20 +// +//! DESCRIPTION: This API retrieves all performance states (P-States) 2.0 information. +//! +//! P-States are GPU active/executing performance capability states. +//! They range from P0 to P15, with P0 being the highest performance state, +//! and P15 being the lowest performance state. Each P-State, if available, +//! maps to a performance level. Not all P-States are available on a given system. +//! The definition of each P-States are currently as follow: +//! - P0/P1 - Maximum 3D performance +//! - P2/P3 - Balanced 3D performance-power +//! - P8 - Basic HD video playback +//! - P10 - DVD playback +//! - P12 - Minimum idle power consumption +//! +//! TCC_SUPPORTED +//! +//! \since Release: 295 +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \param [in] hPhysicalGPU GPU selection +//! \param [out] pPstatesInfo P-States information retrieved, as documented in declaration above +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, +//! they are listed below. +//! +//! \ingroup gpupstate +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetPstates20(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_GPU_PERF_PSTATES20_INFO* pPstatesInfo); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetPstates20)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_GPU_PERF_PSTATES20_INFO* pPstatesInfo); +_NvAPI_GPU_GetPstates20 NvAPI_GPU_GetPstates20; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetCurrentPstate +// +//! DESCRIPTION: This function retrieves the current performance state (P-State). +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 165 +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGPU GPU selection +//! \param [out] pCurrentPstate The ID of the current P-State of the GPU - see \ref NV_GPU_PERF_PSTATES. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred. +//! \retval NVAPI_HANDLE_INVALIDATED Handle passed has been invalidated (see user guide). +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE Handle passed is not a physical GPU handle. +//! \retval NVAPI_NOT_SUPPORTED P-States is not supported on this setup. +//! +//! \ingroup gpupstate +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetCurrentPstate(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_PERF_PSTATE_ID *pCurrentPstate); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetCurrentPstate)(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_PERF_PSTATE_ID *pCurrentPstate); +_NvAPI_GPU_GetCurrentPstate NvAPI_GPU_GetCurrentPstate; + + +//! \ingroup gpupstate +#define NVAPI_MAX_GPU_UTILIZATIONS 8 + + + +//! \ingroup gpupstate +//! Used in NvAPI_GPU_GetDynamicPstatesInfoEx(). +typedef struct _NV_GPU_DYNAMIC_PSTATES_INFO_EX +{ + NvU32 version; //!< Structure version + NvU32 flags; //!< bit 0 indicates if the dynamic Pstate is enabled or not + struct + { + NvU32 bIsPresent:1; //!< Set if this utilization domain is present on this GPU + NvU32 percentage; //!< Percentage of time where the domain is considered busy in the last 1 second interval + } utilization[NVAPI_MAX_GPU_UTILIZATIONS]; +} NV_GPU_DYNAMIC_PSTATES_INFO_EX; + +//! \ingroup gpupstate +//! Macro for constructing the version field of NV_GPU_DYNAMIC_PSTATES_INFO_EX +#define NV_GPU_DYNAMIC_PSTATES_INFO_EX_VER MAKE_NVAPI_VERSION(NV_GPU_DYNAMIC_PSTATES_INFO_EX,1) + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetDynamicPstatesInfoEx +// +//! DESCRIPTION: This API retrieves the NV_GPU_DYNAMIC_PSTATES_INFO_EX structure for the specified physical GPU. +//! Each domain's info is indexed in the array. For example: +//! - pDynamicPstatesInfo->utilization[NVAPI_GPU_UTILIZATION_DOMAIN_GPU] holds the info for the GPU domain. \p +//! There are currently 4 domains for which GPU utilization and dynamic P-State thresholds can be retrieved: +//! graphic engine (GPU), frame buffer (FB), video engine (VID), and bus interface (BUS). +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 185 +//! +//! \retval ::NVAPI_OK +//! \retval ::NVAPI_ERROR +//! \retval ::NVAPI_INVALID_ARGUMENT pDynamicPstatesInfo is NULL +//! \retval ::NVAPI_HANDLE_INVALIDATED +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the INFO struct is not supported +//! +//! \ingroup gpupstate +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetDynamicPstatesInfoEx(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_DYNAMIC_PSTATES_INFO_EX *pDynamicPstatesInfoEx); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetDynamicPstatesInfoEx)(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_DYNAMIC_PSTATES_INFO_EX* pDynamicPstatesInfoEx); +_NvAPI_GPU_GetDynamicPstatesInfoEx NvAPI_GPU_GetDynamicPstatesInfoEx; + +/////////////////////////////////////////////////////////////////////////////////// +// Thermal API +// Provides ability to get temperature levels from the various thermal sensors associated with the GPU + +//! \ingroup gputhermal +#define NVAPI_MAX_THERMAL_SENSORS_PER_GPU 3 + +//! \ingroup gputhermal +//! Used in NV_GPU_THERMAL_SETTINGS +typedef enum _NV_THERMAL_TARGET +{ + NVAPI_THERMAL_TARGET_NONE = 0, + NVAPI_THERMAL_TARGET_GPU = 1, //!< GPU core temperature requires NvPhysicalGpuHandle + NVAPI_THERMAL_TARGET_MEMORY = 2, //!< GPU memory temperature requires NvPhysicalGpuHandle + NVAPI_THERMAL_TARGET_POWER_SUPPLY = 4, //!< GPU power supply temperature requires NvPhysicalGpuHandle + NVAPI_THERMAL_TARGET_BOARD = 8, //!< GPU board ambient temperature requires NvPhysicalGpuHandle + NVAPI_THERMAL_TARGET_VCD_BOARD = 9, //!< Visual Computing Device Board temperature requires NvVisualComputingDeviceHandle + NVAPI_THERMAL_TARGET_VCD_INLET = 10, //!< Visual Computing Device Inlet temperature requires NvVisualComputingDeviceHandle + NVAPI_THERMAL_TARGET_VCD_OUTLET = 11, //!< Visual Computing Device Outlet temperature requires NvVisualComputingDeviceHandle + + NVAPI_THERMAL_TARGET_ALL = 15, + NVAPI_THERMAL_TARGET_UNKNOWN = -1, +} NV_THERMAL_TARGET; + +//! \ingroup gputhermal +//! Used in NV_GPU_THERMAL_SETTINGS +typedef enum _NV_THERMAL_CONTROLLER +{ + NVAPI_THERMAL_CONTROLLER_NONE = 0, + NVAPI_THERMAL_CONTROLLER_GPU_INTERNAL, + NVAPI_THERMAL_CONTROLLER_ADM1032, + NVAPI_THERMAL_CONTROLLER_MAX6649, + NVAPI_THERMAL_CONTROLLER_MAX1617, + NVAPI_THERMAL_CONTROLLER_LM99, + NVAPI_THERMAL_CONTROLLER_LM89, + NVAPI_THERMAL_CONTROLLER_LM64, + NVAPI_THERMAL_CONTROLLER_ADT7473, + NVAPI_THERMAL_CONTROLLER_SBMAX6649, + NVAPI_THERMAL_CONTROLLER_VBIOSEVT, + NVAPI_THERMAL_CONTROLLER_OS, + NVAPI_THERMAL_CONTROLLER_UNKNOWN = -1, +} NV_THERMAL_CONTROLLER; + +//! \ingroup gputhermal +//! Used in NvAPI_GPU_GetThermalSettings() +typedef struct _NV_GPU_THERMAL_SETTINGS_V1 +{ + NvU32 version; //!< structure version + NvU32 count; //!< number of associated thermal sensors + struct + { + NV_THERMAL_CONTROLLER controller; //!< internal, ADM1032, MAX6649... + NvU32 defaultMinTemp; //!< The min default temperature value of the thermal sensor in degree Celsius + NvU32 defaultMaxTemp; //!< The max default temperature value of the thermal sensor in degree Celsius + NvU32 currentTemp; //!< The current temperature value of the thermal sensor in degree Celsius + NV_THERMAL_TARGET target; //!< Thermal sensor targeted @ GPU, memory, chipset, powersupply, Visual Computing Device, etc. + } sensor[NVAPI_MAX_THERMAL_SENSORS_PER_GPU]; + +} NV_GPU_THERMAL_SETTINGS_V1; + +//! \ingroup gputhermal +typedef struct _NV_GPU_THERMAL_SETTINGS_V2 +{ + NvU32 version; //!< structure version + NvU32 count; //!< number of associated thermal sensors + struct + { + NV_THERMAL_CONTROLLER controller; //!< internal, ADM1032, MAX6649... + NvS32 defaultMinTemp; //!< Minimum default temperature value of the thermal sensor in degree Celsius + NvS32 defaultMaxTemp; //!< Maximum default temperature value of the thermal sensor in degree Celsius + NvS32 currentTemp; //!< Current temperature value of the thermal sensor in degree Celsius + NV_THERMAL_TARGET target; //!< Thermal sensor targeted - GPU, memory, chipset, powersupply, Visual Computing Device, etc + } sensor[NVAPI_MAX_THERMAL_SENSORS_PER_GPU]; + +} NV_GPU_THERMAL_SETTINGS_V2; + +//! \ingroup gputhermal +typedef NV_GPU_THERMAL_SETTINGS_V2 NV_GPU_THERMAL_SETTINGS; + +//! \ingroup gputhermal +//! @{ + +//! Macro for constructing the version field of NV_GPU_THERMAL_SETTINGS_V1 +#define NV_GPU_THERMAL_SETTINGS_VER_1 MAKE_NVAPI_VERSION(NV_GPU_THERMAL_SETTINGS_V1,1) + +//! Macro for constructing the version field of NV_GPU_THERMAL_SETTINGS_V2 +#define NV_GPU_THERMAL_SETTINGS_VER_2 MAKE_NVAPI_VERSION(NV_GPU_THERMAL_SETTINGS_V2,2) + +//! Macro for constructing the version field of NV_GPU_THERMAL_SETTINGS +#define NV_GPU_THERMAL_SETTINGS_VER NV_GPU_THERMAL_SETTINGS_VER_2 +//! @} + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetThermalSettings +// +//! This function retrieves the thermal information of all thermal sensors or specific thermal sensor associated with the selected GPU. +//! Thermal sensors are indexed 0 to NVAPI_MAX_THERMAL_SENSORS_PER_GPU-1. +//! +//! - To retrieve specific thermal sensor info, set the sensorIndex to the required thermal sensor index. +//! - To retrieve info for all sensors, set sensorIndex to NVAPI_THERMAL_TARGET_ALL. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 85 +//! +//! \param [in] hPhysicalGPU GPU selection. +//! \param [in] sensorIndex Explicit thermal sensor index selection. +//! \param [out] pThermalSettings Array of thermal settings. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred. +//! \retval NVAPI_INVALID_ARGUMENT pThermalInfo is NULL. +//! \retval NVAPI_HANDLE_INVALIDATED Handle passed has been invalidated (see user guide). +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE Handle passed is not a physical GPU handle. +//! \retval NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the INFO struct is not supported. +//! \ingroup gputhermal +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetThermalSettings(NvPhysicalGpuHandle hPhysicalGpu, NvU32 sensorIndex, NV_GPU_THERMAL_SETTINGS *pThermalSettings); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetThermalSettings)(NvPhysicalGpuHandle hPhysicalGpu, NvU32 sensorIndex, NV_GPU_THERMAL_SETTINGS *pThermalSettings); +_NvAPI_GPU_GetThermalSettings NvAPI_GPU_GetThermalSettings; + + +/////////////////////////////////////////////////////////////////////////////////// +// I2C API +// Provides ability to read or write data using I2C protocol. +// These APIs allow I2C access only to DDC monitors + + +//! \addtogroup i2capi +//! @{ +#define NVAPI_MAX_SIZEOF_I2C_DATA_BUFFER 4096 +#define NVAPI_MAX_SIZEOF_I2C_REG_ADDRESS 4 +#define NVAPI_DISPLAY_DEVICE_MASK_MAX 24 +#define NVAPI_I2C_SPEED_DEPRECATED 0xFFFF + +typedef enum _NV_I2C_SPEED +{ + NVAPI_I2C_SPEED_DEFAULT, //!< Set i2cSpeedKhz to I2C_SPEED_DEFAULT if default I2C speed is to be chosen, ie.use the current frequency setting. + NVAPI_I2C_SPEED_3KHZ, + NVAPI_I2C_SPEED_10KHZ, + NVAPI_I2C_SPEED_33KHZ, + NVAPI_I2C_SPEED_100KHZ, + NVAPI_I2C_SPEED_200KHZ, + NVAPI_I2C_SPEED_400KHZ, +} NV_I2C_SPEED; + +//! Used in NvAPI_I2CRead() and NvAPI_I2CWrite() +typedef struct +{ + NvU32 version; //!< The structure version. + NvU32 displayMask; //!< The Display Mask of the concerned display. + NvU8 bIsDDCPort; //!< This flag indicates either the DDC port (TRUE) or the communication port + //!< (FALSE) of the concerned display. + NvU8 i2cDevAddress; //!< The address of the I2C slave. The address should be shifted left by one. For + //!< example, the I2C address 0x50, often used for reading EDIDs, would be stored + //!< here as 0xA0. This matches the position within the byte sent by the master, as + //!< the last bit is reserved to specify the read or write direction. + NvU8* pbI2cRegAddress; //!< The I2C target register address. May be NULL, which indicates no register + //!< address should be sent. + NvU32 regAddrSize; //!< The size in bytes of target register address. If pbI2cRegAddress is NULL, this + //!< field must be 0. + NvU8* pbData; //!< The buffer of data which is to be read or written (depending on the command). + NvU32 cbSize; //!< The size of the data buffer, pbData, to be read or written. + NvU32 i2cSpeed; //!< The target speed of the transaction (between 28Kbps to 40Kbps; not guaranteed). +} NV_I2C_INFO_V1; + +//! Used in NvAPI_I2CRead() and NvAPI_I2CWrite() +typedef struct +{ + NvU32 version; //!< The structure version. + NvU32 displayMask; //!< The Display Mask of the concerned display. + NvU8 bIsDDCPort; //!< This flag indicates either the DDC port (TRUE) or the communication port + //!< (FALSE) of the concerned display. + NvU8 i2cDevAddress; //!< The address of the I2C slave. The address should be shifted left by one. For + //!< example, the I2C address 0x50, often used for reading EDIDs, would be stored + //!< here as 0xA0. This matches the position within the byte sent by the master, as + //!< the last bit is reserved to specify the read or write direction. + NvU8* pbI2cRegAddress; //!< The I2C target register address. May be NULL, which indicates no register + //!< address should be sent. + NvU32 regAddrSize; //!< The size in bytes of target register address. If pbI2cRegAddress is NULL, this + //!< field must be 0. + NvU8* pbData; //!< The buffer of data which is to be read or written (depending on the command). + NvU32 cbSize; //!< The size of the data buffer, pbData, to be read or written. + NvU32 i2cSpeed; //!< Deprecated, Must be set to NVAPI_I2C_SPEED_DEPRECATED. + NV_I2C_SPEED i2cSpeedKhz; //!< The target speed of the transaction in (kHz) (Chosen from the enum NV_I2C_SPEED). +} NV_I2C_INFO_V2; + +//! Used in NvAPI_I2CRead() and NvAPI_I2CWrite() +typedef struct +{ + NvU32 version; //!< The structure version. + NvU32 displayMask; //!< The Display Mask of the concerned display. + NvU8 bIsDDCPort; //!< This flag indicates either the DDC port (TRUE) or the communication port + //!< (FALSE) of the concerned display. + NvU8 i2cDevAddress; //!< The address of the I2C slave. The address should be shifted left by one. For + //!< example, the I2C address 0x50, often used for reading EDIDs, would be stored + //!< here as 0xA0. This matches the position within the byte sent by the master, as + //!< the last bit is reserved to specify the read or write direction. + NvU8* pbI2cRegAddress; //!< The I2C target register address. May be NULL, which indicates no register + //!< address should be sent. + NvU32 regAddrSize; //!< The size in bytes of target register address. If pbI2cRegAddress is NULL, this + //!< field must be 0. + NvU8* pbData; //!< The buffer of data which is to be read or written (depending on the command). + NvU32 cbSize; //!< The size of the data buffer, pbData, to be read or written. + NvU32 i2cSpeed; //!< Deprecated, Must be set to NVAPI_I2C_SPEED_DEPRECATED. + NV_I2C_SPEED i2cSpeedKhz; //!< The target speed of the transaction in (kHz) (Chosen from the enum NV_I2C_SPEED). + NvU8 portId; //!< The portid on which device is connected (remember to set bIsPortIdSet if this value is set) + //!< Optional for pre-Kepler + NvU32 bIsPortIdSet; //!< set this flag on if and only if portid value is set +} NV_I2C_INFO_V3; + +typedef NV_I2C_INFO_V3 NV_I2C_INFO; + +#define NV_I2C_INFO_VER3 MAKE_NVAPI_VERSION(NV_I2C_INFO_V3,3) +#define NV_I2C_INFO_VER2 MAKE_NVAPI_VERSION(NV_I2C_INFO_V2,2) +#define NV_I2C_INFO_VER1 MAKE_NVAPI_VERSION(NV_I2C_INFO_V1,1) + +#define NV_I2C_INFO_VER NV_I2C_INFO_VER3 +//! @} + +/***********************************************************************************/ + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_I2CRead +// +//! This function reads the data buffer from the I2C port. +//! The I2C request must be for a DDC port: pI2cInfo->bIsDDCPort = 1. +//! +//! A data buffer size larger than 16 bytes may be rejected if a register address is specified. In such a case, +//! NVAPI_ARGUMENT_EXCEED_MAX_SIZE would be returned. +//! +//! If a register address is specified (i.e. regAddrSize is positive), then the transaction will be performed in +//! the combined format described in the I2C specification. The register address will be written, followed by +//! reading into the data buffer. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 85 +//! +//! \param [in] hPhysicalGPU GPU selection. +//! \param [out] NV_I2C_INFO *pI2cInfo The I2C data input structure +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred. +//! \retval NVAPI_HANDLE_INVALIDATED Handle passed has been invalidated (see user guide). +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE Handle passed is not a physical GPU handle. +//! \retval NVAPI_INCOMPATIBLE_STRUCT_VERSION Structure version is not supported. +//! \retval NVAPI_INVALID_ARGUMENT - argument does not meet specified requirements +//! \retval NVAPI_ARGUMENT_EXCEED_MAX_SIZE - an argument exceeds the maximum +//! +//! \ingroup i2capi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_I2CRead(NvPhysicalGpuHandle hPhysicalGpu, NV_I2C_INFO *pI2cInfo); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_I2CWrite +// +//! This function writes the data buffer to the I2C port. +//! +//! The I2C request must be for a DDC port: pI2cInfo->bIsDDCPort = 1. +//! +//! A data buffer size larger than 16 bytes may be rejected if a register address is specified. In such a case, +//! NVAPI_ARGUMENT_EXCEED_MAX_SIZE would be returned. +//! +//! If a register address is specified (i.e. regAddrSize is positive), then the register address will be written +//! and the data buffer will immediately follow without a restart. +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \since Release: 85 +//! +//! \param [in] hPhysicalGPU GPU selection. +//! \param [in] pI2cInfo The I2C data input structure +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred. +//! \retval NVAPI_HANDLE_INVALIDATED Handle passed has been invalidated (see user guide). +//! \retval NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE Handle passed is not a physical GPU handle. +//! \retval NVAPI_INCOMPATIBLE_STRUCT_VERSION Structure version is not supported. +//! \retval NVAPI_INVALID_ARGUMENT Argument does not meet specified requirements +//! \retval NVAPI_ARGUMENT_EXCEED_MAX_SIZE Argument exceeds the maximum +//! +//! \ingroup i2capi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_I2CWrite(NvPhysicalGpuHandle hPhysicalGpu, NV_I2C_INFO *pI2cInfo); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_WorkstationFeatureSetup +// +//! \fn NvAPI_GPU_WorkstationFeatureSetup(NvPhysicalGpuHandle hPhysicalGpu, NvU32 featureEnableMask, NvU32 featureDisableMask) +//! DESCRIPTION: This API configures the driver for a set of workstation features. +//! The driver can allocate the memory resources accordingly. +//! +//! SUPPORTED OS: Windows 7 +//! +//! +//! \param [in] hPhysicalGpu Physical GPU Handle of the display adapter to be configured. GPU handles may be retrieved +//! using NvAPI_EnumPhysicalGPUs. A value of NULL is permitted and applies the same operation +//! to all GPU handles enumerated by NvAPI_EnumPhysicalGPUs. +//! \param [in] featureEnableMask Mask of features the caller requests to enable for use +//! \param [in] featureDisableMask Mask of features the caller requests to disable +//! +//! As a general rule, features in the enable and disable masks are expected to be disjoint, although the disable +//! mask has precedence and a feature flagged in both masks will be disabled. +//! +//! \retval ::NVAPI_OK configuration request succeeded +//! \retval ::NVAPI_ERROR configuration request failed +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu is not a physical GPU handle. +//! \retval ::NVAPI_GPU_WORKSTATION_FEATURE_INCOMPLETE requested feature set does not have all resources allocated for completeness. +//! \retval ::NVAPI_NO_IMPLEMENTATION only implemented for Win7 +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +typedef enum _NVAPI_GPU_WORKSTATION_FEATURE_MASK +{ + NVAPI_GPU_WORKSTATION_FEATURE_MASK_SWAPGROUP = 0x00000001, + NVAPI_GPU_WORKSTATION_FEATURE_MASK_STEREO = 0x00000010, + NVAPI_GPU_WORKSTATION_FEATURE_MASK_WARPING = 0x00000100, + NVAPI_GPU_WORKSTATION_FEATURE_MASK_PIXINTENSITY = 0x00000200, + NVAPI_GPU_WORKSTATION_FEATURE_MASK_GRAYSCALE = 0x00000400, + NVAPI_GPU_WORKSTATION_FEATURE_MASK_BPC10 = 0x00001000 +} NVAPI_GPU_WORKSTATION_FEATURE_MASK; + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_WorkstationFeatureSetup(_In_ NvPhysicalGpuHandle hPhysicalGpu, _In_ NvU32 featureEnableMask, _In_ NvU32 featureDisableMask); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_WorkstationFeatureQuery +// +//! DESCRIPTION: This API queries the current set of workstation features. +//! +//! SUPPORTED OS: Windows 7 +//! +//! +//! \param [in] hPhysicalGpu Physical GPU Handle of the display adapter to be configured. GPU handles may be retrieved +//! using NvAPI_EnumPhysicalGPUs. +//! \param [out] pConfiguredFeatureMask Mask of features requested for use by client drivers +//! \param [out] pConsistentFeatureMask Mask of features that have all resources allocated for completeness. +//! +//! \retval ::NVAPI_OK configuration request succeeded +//! \retval ::NVAPI_ERROR configuration request failed +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE hPhysicalGpu is not a physical GPU handle. +//! \retval ::NVAPI_NO_IMPLEMENTATION only implemented for Win7 +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_WorkstationFeatureQuery(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Out_opt_ NvU32 *pConfiguredFeatureMask, _Out_opt_ NvU32 *pConsistentFeatureMask); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetHDCPSupportStatus +// +//! \fn NvAPI_GPU_GetHDCPSupportStatus(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_GET_HDCP_SUPPORT_STATUS *pGetHDCPSupportStatus) +//! DESCRIPTION: This function returns a GPU's HDCP support status. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 175 +//! +//! \retval ::NVAPI_OK +//! \retval ::NVAPI_ERROR +//! \retval ::NVAPI_INVALID_ARGUMENT +//! \retval ::NVAPI_HANDLE_INVALIDATED +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION +// +//////////////////////////////////////////////////////////////////////////////// + + +//! \addtogroup gpu +//! @{ + + +//! HDCP fuse states - used in NV_GPU_GET_HDCP_SUPPORT_STATUS +typedef enum _NV_GPU_HDCP_FUSE_STATE +{ + NV_GPU_HDCP_FUSE_STATE_UNKNOWN = 0, + NV_GPU_HDCP_FUSE_STATE_DISABLED = 1, + NV_GPU_HDCP_FUSE_STATE_ENABLED = 2, +} NV_GPU_HDCP_FUSE_STATE; + + +//! HDCP key sources - used in NV_GPU_GET_HDCP_SUPPORT_STATUS +typedef enum _NV_GPU_HDCP_KEY_SOURCE +{ + NV_GPU_HDCP_KEY_SOURCE_UNKNOWN = 0, + NV_GPU_HDCP_KEY_SOURCE_NONE = 1, + NV_GPU_HDCP_KEY_SOURCE_CRYPTO_ROM = 2, + NV_GPU_HDCP_KEY_SOURCE_SBIOS = 3, + NV_GPU_HDCP_KEY_SOURCE_I2C_ROM = 4, + NV_GPU_HDCP_KEY_SOURCE_FUSES = 5, +} NV_GPU_HDCP_KEY_SOURCE; + + +//! HDCP key source states - used in NV_GPU_GET_HDCP_SUPPORT_STATUS +typedef enum _NV_GPU_HDCP_KEY_SOURCE_STATE +{ + NV_GPU_HDCP_KEY_SOURCE_STATE_UNKNOWN = 0, + NV_GPU_HDCP_KEY_SOURCE_STATE_ABSENT = 1, + NV_GPU_HDCP_KEY_SOURCE_STATE_PRESENT = 2, +} NV_GPU_HDCP_KEY_SOURCE_STATE; + + +//! HDPC support status - used in NvAPI_GPU_GetHDCPSupportStatus() +typedef struct +{ + NvU32 version; //! Structure version constucted by macro #NV_GPU_GET_HDCP_SUPPORT_STATUS + NV_GPU_HDCP_FUSE_STATE hdcpFuseState; //! GPU's HDCP fuse state + NV_GPU_HDCP_KEY_SOURCE hdcpKeySource; //! GPU's HDCP key source + NV_GPU_HDCP_KEY_SOURCE_STATE hdcpKeySourceState; //! GPU's HDCP key source state +} NV_GPU_GET_HDCP_SUPPORT_STATUS; + + +//! Macro for constructing the version for structure NV_GPU_GET_HDCP_SUPPORT_STATUS +#define NV_GPU_GET_HDCP_SUPPORT_STATUS_VER MAKE_NVAPI_VERSION(NV_GPU_GET_HDCP_SUPPORT_STATUS,1) + + +//! @} + + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_GetHDCPSupportStatus(NvPhysicalGpuHandle hPhysicalGpu, NV_GPU_GET_HDCP_SUPPORT_STATUS *pGetHDCPSupportStatus); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetTachReading +// +//! DESCRIPTION: This API retrieves the fan speed tachometer reading for the specified physical GPU. +//! +//! HOW TO USE: +//! - NvU32 Value = 0; +//! - ret = NvAPI_GPU_GetTachReading(hPhysicalGpu, &Value); +//! - On call success: +//! - Value contains the tachometer reading +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu GPU selection. +//! \param [out] pValue Pointer to a variable to get the tachometer reading +//! +//! \retval ::NVAPI_OK - completed request +//! \retval ::NVAPI_ERROR - miscellaneous error occurred +//! \retval ::NVAPI_NOT_SUPPORTED - functionality not supported +//! \retval ::NVAPI_API_NOT_INTIALIZED - nvapi not initialized +//! \retval ::NVAPI_INVALID_ARGUMENT - invalid argument passed +//! \retval ::NVAPI_HANDLE_INVALIDATED - handle passed has been invalidated (see user guide) +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE - handle passed is not a physical GPU handle +//! +//! \ingroup gpucooler +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetTachReading(NvPhysicalGpuHandle hPhysicalGPU, NvU32 *pValue); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetTachReading)(NvPhysicalGpuHandle hPhysicalGPU, NvU32 *pValue); +_NvAPI_GPU_GetTachReading NvAPI_GPU_GetTachReading; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetECCStatusInfo +// +//! \fn NvAPI_GPU_GetECCStatusInfo(NvPhysicalGpuHandle hPhysicalGpu, +//! NV_GPU_ECC_STATUS_INFO *pECCStatusInfo); +//! DESCRIPTION: This function returns ECC memory status information. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu A handle identifying the physical GPU for which ECC +//! status information is to be retrieved. +//! \param [out] pECCStatusInfo A pointer to an ECC status structure. +//! +//! \retval ::NVAPI_OK The request was completed successfully. +//! \retval ::NVAPI_ERROR An unknown error occurred. +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE The provided GPU handle is not a physical GPU handle. +//! \retval ::NVAPI_INVALID_HANDLE The provided GPU handle is invalid. +//! \retval ::NVAPI_HANDLE_INVALIDATED The provided GPU handle is no longer valid. +//! \retval ::NVAPI_INVALID_POINTER An invalid argument pointer was provided. +//! \retval ::NVAPI_NOT_SUPPORTED The request is not supported. +//! \retval ::NVAPI_API_NOT_INTIALIZED NvAPI was not yet initialized. +// +/////////////////////////////////////////////////////////////////////////////// + +//! \addtogroup gpuecc +//! Used in NV_GPU_ECC_STATUS_INFO. +typedef enum _NV_ECC_CONFIGURATION +{ + NV_ECC_CONFIGURATION_NOT_SUPPORTED = 0, + NV_ECC_CONFIGURATION_DEFERRED, //!< Changes require a POST to take effect + NV_ECC_CONFIGURATION_IMMEDIATE, //!< Changes can optionally be made to take effect immediately +} NV_ECC_CONFIGURATION; + +//! \ingroup gpuecc +//! Used in NvAPI_GPU_GetECCStatusInfo(). +typedef struct _NV_GPU_ECC_STATUS_INFO +{ + NvU32 version; //!< Structure version + NvU32 isSupported : 1; //!< ECC memory feature support + NV_ECC_CONFIGURATION configurationOptions; //!< Supported ECC memory feature configuration options + NvU32 isEnabled : 1; //!< Active ECC memory setting +} NV_GPU_ECC_STATUS_INFO; + +//! \ingroup gpuecc +//! Macro for constructing the version field of NV_GPU_ECC_STATUS_INFO +#define NV_GPU_ECC_STATUS_INFO_VER MAKE_NVAPI_VERSION(NV_GPU_ECC_STATUS_INFO, 1) + +//! \ingroup gpuecc +NVAPI_INTERFACE NvAPI_GPU_GetECCStatusInfo(NvPhysicalGpuHandle hPhysicalGpu, + NV_GPU_ECC_STATUS_INFO *pECCStatusInfo); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetECCErrorInfo +// +//! \fn NvAPI_GPU_GetECCErrorInfo(NvPhysicalGpuHandle hPhysicalGpu, +//! NV_GPU_ECC_ERROR_INFO *pECCErrorInfo); +//! +//! DESCRIPTION: This function returns ECC memory error information. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu A handle identifying the physical GPU for +//! which ECC error information is to be +//! retrieved. +//! \param [out] pECCErrorInfo A pointer to an ECC error structure. +//! +//! \retval ::NVAPI_OK The request was completed successfully. +//! \retval ::NVAPI_ERROR An unknown error occurred. +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE The provided GPU handle is not a physical GPU handle. +//! \retval ::NVAPI_INVALID_ARGUMENT incorrect param value +//! \retval ::NVAPI_INVALID_POINTER An invalid argument pointer was provided. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION structure version is not supported, initialize to NV_GPU_ECC_ERROR_INFO_VER. +//! \retval ::NVAPI_HANDLE_INVALIDATED The provided GPU handle is no longer valid. +//! \retval ::NVAPI_NOT_SUPPORTED The request is not supported. +//! \retval ::NVAPI_API_NOT_INTIALIZED NvAPI was not yet initialized. +// +/////////////////////////////////////////////////////////////////////////////// + + +//! \ingroup gpuecc +//! Used in NvAPI_GPU_GetECCErrorInfo()/ +typedef struct _NV_GPU_ECC_ERROR_INFO +{ + NvU32 version; //!< Structure version + struct { + NvU64 singleBitErrors; //!< Number of single-bit ECC errors detected since last boot + NvU64 doubleBitErrors; //!< Number of double-bit ECC errors detected since last boot + } current; + struct { + NvU64 singleBitErrors; //!< Number of single-bit ECC errors detected since last counter reset + NvU64 doubleBitErrors; //!< Number of double-bit ECC errors detected since last counter reset + } aggregate; +} NV_GPU_ECC_ERROR_INFO; + +//! \ingroup gpuecc +//! Macro for constructing the version field of NV_GPU_ECC_ERROR_INFO +#define NV_GPU_ECC_ERROR_INFO_VER MAKE_NVAPI_VERSION(NV_GPU_ECC_ERROR_INFO, 1) + +//! \ingroup gpuecc +NVAPI_INTERFACE NvAPI_GPU_GetECCErrorInfo(NvPhysicalGpuHandle hPhysicalGpu, + NV_GPU_ECC_ERROR_INFO *pECCErrorInfo); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_ResetECCErrorInfo +// +//! DESCRIPTION: This function resets ECC memory error counters. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu A handle identifying the physical GPU for +//! which ECC error information is to be +//! cleared. +//! \param [in] bResetCurrent Reset the current ECC error counters. +//! \param [in] bResetAggregate Reset the aggregate ECC error counters. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval ::NVAPI_INVALID_USER_PRIVILEGE - The caller does not have administrative privileges +//! +//! \ingroup gpuecc +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_ResetECCErrorInfo(NvPhysicalGpuHandle hPhysicalGpu, NvU8 bResetCurrent, + NvU8 bResetAggregate); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetECCConfigurationInfo +// +//! \fn NvAPI_GPU_GetECCConfigurationInfo(NvPhysicalGpuHandle hPhysicalGpu, +//! NV_GPU_ECC_CONFIGURATION_INFO *pECCConfigurationInfo); +//! DESCRIPTION: This function returns ECC memory configuration information. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu A handle identifying the physical GPU for +//! which ECC configuration information +//! is to be retrieved. +//! \param [out] pECCConfigurationInfo A pointer to an ECC +//! configuration structure. +//! +//! \retval ::NVAPI_OK The request was completed successfully. +//! \retval ::NVAPI_ERROR An unknown error occurred. +//! \retval ::NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE The provided GPU handle is not a physical GPU handle. +//! \retval ::NVAPI_INVALID_HANDLE The provided GPU handle is invalid. +//! \retval ::NVAPI_HANDLE_INVALIDATED The provided GPU handle is no longer valid. +//! \retval ::NVAPI_INVALID_POINTER An invalid argument pointer was provided. +//! \retval ::NVAPI_NOT_SUPPORTED The request is not supported. +//! \retval ::NVAPI_API_NOT_INTIALIZED NvAPI was not yet initialized. +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpuecc +//! Used in NvAPI_GPU_GetECCConfigurationInfo(). +typedef struct _NV_GPU_ECC_CONFIGURATION_INFO +{ + NvU32 version; //! Structure version + NvU32 isEnabled : 1; //! Current ECC configuration stored in non-volatile memory + NvU32 isEnabledByDefault : 1; //! Factory default ECC configuration (static) +} NV_GPU_ECC_CONFIGURATION_INFO; + +//! \ingroup gpuecc +//! Macro for consstructing the verion field of NV_GPU_ECC_CONFIGURATION_INFO +#define NV_GPU_ECC_CONFIGURATION_INFO_VER MAKE_NVAPI_VERSION(NV_GPU_ECC_CONFIGURATION_INFO,1) + +//! \ingroup gpuecc +NVAPI_INTERFACE NvAPI_GPU_GetECCConfigurationInfo(NvPhysicalGpuHandle hPhysicalGpu, + NV_GPU_ECC_CONFIGURATION_INFO *pECCConfigurationInfo); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_SetECCConfiguration +// +//! DESCRIPTION: This function updates the ECC memory configuration setting. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \param [in] hPhysicalGpu A handle identifying the physical GPU for +//! which to update the ECC configuration +//! setting. +//! \param [in] bEnable The new ECC configuration setting. +//! \param [in] bEnableImmediately Request that the new setting take effect immediately. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval ::NVAPI_INVALID_CONFIGURATION - Possibly SLI is enabled. Disable SLI and retry. +//! \retval ::NVAPI_INVALID_USER_PRIVILEGE - The caller does not have administrative privileges +//! +//! \ingroup gpuecc +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_SetECCConfiguration(NvPhysicalGpuHandle hPhysicalGpu, NvU8 bEnable, + NvU8 bEnableImmediately); + + + +//! \ingroup gpu +typedef struct +{ + NvU32 version; //!< version of this structure + NvU32 width; //!< width of the input texture + NvU32 height; //!< height of the input texture + float* blendingTexture; //!< array of floating values building an intensity RGB texture +} NV_SCANOUT_INTENSITY_DATA_V1; + +//! \ingroup gpu +typedef struct +{ + NvU32 version; //!< version of this structure + NvU32 width; //!< width of the input texture + NvU32 height; //!< height of the input texture + float* blendingTexture; //!< array of floating values building an intensity RGB texture + float* offsetTexture; //!< array of floating values building an offset texture + NvU32 offsetTexChannels; //!< number of channels per pixel in the offset texture +} NV_SCANOUT_INTENSITY_DATA_V2; + +typedef NV_SCANOUT_INTENSITY_DATA_V2 NV_SCANOUT_INTENSITY_DATA; + +//! \ingroup gpu +#define NV_SCANOUT_INTENSITY_DATA_VER1 MAKE_NVAPI_VERSION(NV_SCANOUT_INTENSITY_DATA_V1, 1) +#define NV_SCANOUT_INTENSITY_DATA_VER2 MAKE_NVAPI_VERSION(NV_SCANOUT_INTENSITY_DATA_V2, 2) +#define NV_SCANOUT_INTENSITY_DATA_VER NV_SCANOUT_INTENSITY_DATA_VER2 + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_SetScanoutIntensity +// +//! DESCRIPTION: This API enables and sets up per-pixel intensity feature on the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \param [in] displayId combined physical display and GPU identifier of the display to apply the intensity control. +//! \param [in] scanoutIntensityData the intensity texture info. +//! \param [out] pbSticky(OUT) indicates whether the settings will be kept over a reboot. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input parameters. +//! \retval ::NVAPI_API_NOT_INITIALIZED NvAPI not initialized. +//! \retval ::NVAPI_NOT_SUPPORTED Interface not supported by the driver used, or only supported on selected GPUs +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input data. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION NV_SCANOUT_INTENSITY_DATA structure version mismatch. +//! \retval ::NVAPI_OK Feature enabled. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_SetScanoutIntensity(NvU32 displayId, NV_SCANOUT_INTENSITY_DATA* scanoutIntensityData, int *pbSticky); + + +//! \ingroup gpu +typedef struct _NV_SCANOUT_INTENSITY_STATE_DATA +{ + NvU32 version; //!< version of this structure + NvU32 bEnabled; //!< intensity is enabled or not +} NV_SCANOUT_INTENSITY_STATE_DATA; + +//! \ingroup gpu +#define NV_SCANOUT_INTENSITY_STATE_VER MAKE_NVAPI_VERSION(NV_SCANOUT_INTENSITY_STATE_DATA, 1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_GetScanoutIntensityState +// +//! DESCRIPTION: This API queries current state of the intensity feature on the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \param [in] displayId combined physical display and GPU identifier of the display to query the configuration. +//! \param [in,out] scanoutIntensityStateData intensity state data. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input parameters. +//! \retval ::NVAPI_API_NOT_INITIALIZED NvAPI not initialized. +//! \retval ::NVAPI_NOT_SUPPORTED Interface not supported by the driver used, or only supported on selected GPUs. +//! \retval ::NVAPI_OK Feature enabled. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetScanoutIntensityState(_In_ NvU32 displayId, _Inout_ NV_SCANOUT_INTENSITY_STATE_DATA* scanoutIntensityStateData); + + +//! \ingroup gpu +typedef enum +{ + NV_GPU_WARPING_VERTICE_FORMAT_TRIANGLESTRIP_XYUVRQ = 0, + NV_GPU_WARPING_VERTICE_FORMAT_TRIANGLES_XYUVRQ = 1, +} NV_GPU_WARPING_VERTICE_FORMAT; + +//! \ingroup gpu +typedef struct +{ + NvU32 version; //!< version of this structure + float* vertices; //!< width of the input texture + NV_GPU_WARPING_VERTICE_FORMAT vertexFormat; //!< format of the input vertices + int numVertices; //!< number of the input vertices + NvSBox* textureRect; //!< rectangle in desktop coordinates describing the source area for the warping +} NV_SCANOUT_WARPING_DATA; + +//! \ingroup gpu +#define NV_SCANOUT_WARPING_VER MAKE_NVAPI_VERSION(NV_SCANOUT_WARPING_DATA, 1) + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_SetScanoutWarping +// +//! DESCRIPTION: This API enables and sets up the warping feature on the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \param [in] displayId Combined physical display and GPU identifier of the display to apply the intensity control +//! \param [in] scanoutWarpingData The warping data info +//! \param [out] pbSticky Indicates whether the settings will be kept over a reboot. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input parameters. +//! \retval ::NVAPI_API_NOT_INITIALIZED NvAPI not initialized. +//! \retval ::NVAPI_NOT_SUPPORTED Interface not supported by the driver used, or only supported on selected GPUs +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input data. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION NV_SCANOUT_INTENSITY_DATA structure version mismatch. +//! \retval ::NVAPI_OK Feature enabled. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// + +NVAPI_INTERFACE NvAPI_GPU_SetScanoutWarping(NvU32 displayId, NV_SCANOUT_WARPING_DATA* scanoutWarpingData, int* piMaxNumVertices, int* pbSticky); + + +//! \ingroup gpu +typedef struct _NV_SCANOUT_WARPING_STATE_DATA +{ + NvU32 version; //!< version of this structure + NvU32 bEnabled; //!< warping is enabled or not +} NV_SCANOUT_WARPING_STATE_DATA; + +//! \ingroup gpu +#define NV_SCANOUT_WARPING_STATE_VER MAKE_NVAPI_VERSION(NV_SCANOUT_WARPING_STATE_DATA, 1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_GetScanoutWarpingState +// +//! DESCRIPTION: This API queries current state of the warping feature on the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \param [in] displayId combined physical display and GPU identifier of the display to query the configuration. +//! \param [in,out] scanoutWarpingStateData warping state data. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input parameters. +//! \retval ::NVAPI_API_NOT_INITIALIZED NvAPI not initialized. +//! \retval ::NVAPI_NOT_SUPPORTED Interface not supported by the driver used, or only supported on selected GPUs. +//! \retval ::NVAPI_OK Feature enabled. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetScanoutWarpingState(_In_ NvU32 displayId, _Inout_ NV_SCANOUT_WARPING_STATE_DATA* scanoutWarpingStateData); + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_GetScanoutConfiguration +// +//! DESCRIPTION: This API queries the desktop and scanout portion of the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \param [in] displayId combined physical display and GPU identifier of the display to query the configuration. +//! \param [in,out] desktopRect desktop area of the display in desktop coordinates. +//! \param [in,out] scanoutRect scanout area of the display relative to desktopRect. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Invalid input parameters. +//! \retval ::NVAPI_API_NOT_INITIALIZED NvAPI not initialized. +//! \retval ::NVAPI_NOT_SUPPORTED Interface not supported by the driver used, or only supported on selected GPUs. +//! \retval ::NVAPI_OK Feature enabled. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetScanoutConfiguration(NvU32 displayId, NvSBox* desktopRect, NvSBox* scanoutRect); + + + +//! \ingroup gpu +//! Used in NvAPI_GPU_GetScanoutConfigurationEx(). +typedef struct _NV_SCANOUT_INFORMATION +{ + NvU32 version; //!< Structure version, needs to be initialized with NV_SCANOUT_INFORMATION_VER. + + NvSBox sourceDesktopRect; //!< Operating system display device rect in desktop coordinates displayId is scanning out from. + NvSBox sourceViewportRect; //!< Area inside the sourceDesktopRect which is scanned out to the display. + NvSBox targetViewportRect; //!< Area inside the rect described by targetDisplayWidth/Height sourceViewportRect is scanned out to. + NvU32 targetDisplayWidth; //!< Horizontal size of the active resolution scanned out to the display. + NvU32 targetDisplayHeight; //!< Vertical size of the active resolution scanned out to the display. + NvU32 cloneImportance; //!< If targets are cloned views of the sourceDesktopRect the cloned targets have an imporantce assigned (0:primary,1 secondary,...). + NV_ROTATE sourceToTargetRotation; //!< Rotation performed between the sourceViewportRect and the targetViewportRect. +} NV_SCANOUT_INFORMATION; + +#define NV_SCANOUT_INFORMATION_VER MAKE_NVAPI_VERSION(NV_SCANOUT_INFORMATION,1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GPU_GetScanoutConfigurationEx +// +//! DESCRIPTION: This API queries the desktop and scanout portion of the specified display. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! \since Release: 331 +//! +//! \param [in] displayId combined physical display and GPU identifier of the display to query the configuration. +//! \param [in,out] pScanoutInformation desktop area to displayId mapping information. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GPU_GetScanoutConfigurationEx(_In_ NvU32 displayId, _Inout_ NV_SCANOUT_INFORMATION *pScanoutInformation); + + +//! Used in NvAPI_GPU_GetPerfDecreaseInfo. +//! Bit masks for knowing the exact reason for performance decrease +typedef enum _NVAPI_GPU_PERF_DECREASE +{ + NV_GPU_PERF_DECREASE_NONE = 0, //!< No Slowdown detected + NV_GPU_PERF_DECREASE_REASON_THERMAL_PROTECTION = 0x00000001, //!< Thermal slowdown/shutdown/POR thermal protection + NV_GPU_PERF_DECREASE_REASON_POWER_CONTROL = 0x00000002, //!< Power capping / pstate cap + NV_GPU_PERF_DECREASE_REASON_AC_BATT = 0x00000004, //!< AC->BATT event + NV_GPU_PERF_DECREASE_REASON_API_TRIGGERED = 0x00000008, //!< API triggered slowdown + NV_GPU_PERF_DECREASE_REASON_INSUFFICIENT_POWER = 0x00000010, //!< Power connector missing + NV_GPU_PERF_DECREASE_REASON_UNKNOWN = 0x80000000, //!< Unknown reason +} NVAPI_GPU_PERF_DECREASE; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetPerfDecreaseInfo +// +//! DESCRIPTION: This function retrieves - in NvU32 variable - reasons for the current performance decrease. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! \param [in] hPhysicalGPU (IN) - GPU for which performance decrease is to be evaluated. +//! \param [out] pPerfDecrInfo (OUT) - Pointer to a NvU32 variable containing performance decrease info +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_GPU_GetPerfDecreaseInfo(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NvU32 *pPerfDecrInfo); + +typedef __success(return == NVAPI_OK) NvAPI_Status (__cdecl *_NvAPI_GPU_GetPerfDecreaseInfo)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NVAPI_GPU_PERF_DECREASE* pPerfDecrInfo); +_NvAPI_GPU_GetPerfDecreaseInfo NvAPI_GPU_GetPerfDecreaseInfo; + + +//! \ingroup gpu +typedef enum _NV_GPU_ILLUMINATION_ATTRIB +{ + NV_GPU_IA_LOGO_BRIGHTNESS = 0, + NV_GPU_IA_SLI_BRIGHTNESS = 1, +} NV_GPU_ILLUMINATION_ATTRIB; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_QueryIlluminationSupport +// +//! \fn NvAPI_GPU_QueryIlluminationSupport(_Inout_ NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM *pIlluminationSupportInfo) +//! DESCRIPTION: This function reports if the specified illumination attribute is supported. +//! +//! \note Only a single GPU can manage an given attribute on a given HW element, +//! regardless of how many are attatched. I.E. only one GPU will be used to control +//! the brightness of the LED on an SLI bridge, regardless of how many are physicaly attached. +//! You should enumerate thru the GPUs with this call to determine which GPU is managing the attribute. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! \since Version: 300.05 +//! +//! \param [in] hPhysicalGpu Physical GPU handle +//! \param Attribute An enumeration value specifying the Illumination attribute to be querried +//! \param [out] pSupported A boolean indicating if the attribute is supported. +//! +//! \return See \ref nvapistatus for the list of possible return values. +// +////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +typedef struct _NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_V1 { + + // IN + NvU32 version; //!< Version of this structure + NvPhysicalGpuHandle hPhysicalGpu; //!< The handle of the GPU that you are checking for the specified attribute. + //!< note that this is the GPU that is managing the attribute. + //!< Only a single GPU can manage an given attribute on a given HW element, + //!< regardless of how many are attatched. + //!< I.E. only one GPU will be used to control the brightness of the LED on an SLI bridge, + //!< regardless of how many are physicaly attached. + //!< You enumerate thru the GPUs with this call to determine which GPU is managing the attribute. + NV_GPU_ILLUMINATION_ATTRIB Attribute; //!< An enumeration value specifying the Illumination attribute to be querried. + //!< refer to enum \ref NV_GPU_ILLUMINATION_ATTRIB. + + // OUT + NvU32 bSupported; //!< A boolean indicating if the attribute is supported. + +} NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_V1; + +//! \ingroup gpu +typedef NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_V1 NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM; +//! \ingroup gpu +#define NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_VER_1 MAKE_NVAPI_VERSION(NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_V1,1) +//! \ingroup gpu +#define NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_VER NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM_VER_1 + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_QueryIlluminationSupport(_Inout_ NV_GPU_QUERY_ILLUMINATION_SUPPORT_PARM *pIlluminationSupportInfo); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetIllumination +// +//! \fn NvAPI_GPU_GetIllumination(NV_GPU_GET_ILLUMINATION_PARM *pIlluminationInfo) +//! DESCRIPTION: This function reports value of the specified illumination attribute. +//! +//! \note Only a single GPU can manage an given attribute on a given HW element, +//! regardless of how many are attatched. I.E. only one GPU will be used to control +//! the brightness of the LED on an SLI bridge, regardless of how many are physicaly attached. +//! You should enumerate thru the GPUs with the \ref NvAPI_GPU_QueryIlluminationSupport call to +//! determine which GPU is managing the attribute. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! \since Version: 300.05 +//! +//! \param [in] hPhysicalGpu Physical GPU handle +//! \param Attribute An enumeration value specifying the Illumination attribute to be querried +//! \param [out] Value A ULONG containing the current value for the specified attribute. +//! This is specified as a percentage of the full range of the attribute +//! (0-100; 0 = off, 100 = full brightness) +//! +//! \return See \ref nvapistatus for the list of possible return values. Return values of special interest are: +//! NVAPI_INVALID_ARGUMENT The specified attibute is not known to the driver. +//! NVAPI_NOT_SUPPORTED: The specified attribute is not supported on the specified GPU +// +////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +typedef struct _NV_GPU_GET_ILLUMINATION_PARM_V1 { + + // IN + NvU32 version; //!< Version of this structure + NvPhysicalGpuHandle hPhysicalGpu; //!< The handle of the GPU that you are checking for the specified attribute. + //!< Note that this is the GPU that is managing the attribute. + //!< Only a single GPU can manage an given attribute on a given HW element, + //!< regardless of how many are attatched. + //!< I.E. only one GPU will be used to control the brightness of the LED on an SLI bridge, + //!< regardless of how many are physicaly attached. + //!< You enumerate thru the GPUs with this call to determine which GPU is managing the attribute. + NV_GPU_ILLUMINATION_ATTRIB Attribute; //!< An enumeration value specifying the Illumination attribute to be querried. + //!< refer to enum \ref NV_GPU_ILLUMINATION_ATTRIB. + + // OUT + NvU32 Value; //!< A ULONG that will contain the current value of the specified attribute. + //! This is specified as a percentage of the full range of the attribute + //! (0-100; 0 = off, 100 = full brightness) + +} NV_GPU_GET_ILLUMINATION_PARM_V1; + +//! \ingroup gpu +typedef NV_GPU_GET_ILLUMINATION_PARM_V1 NV_GPU_GET_ILLUMINATION_PARM; +//! \ingroup gpu +#define NV_GPU_GET_ILLUMINATION_PARM_VER_1 MAKE_NVAPI_VERSION(NV_GPU_GET_ILLUMINATION_PARM_V1,1) +//! \ingroup gpu +#define NV_GPU_GET_ILLUMINATION_PARM_VER NV_GPU_GET_ILLUMINATION_PARM_VER_1 + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_GetIllumination(NV_GPU_GET_ILLUMINATION_PARM *pIlluminationInfo); + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_SetIllumination +// +//! \fn NvAPI_GPU_SetIllumination(NV_GPU_SET_ILLUMINATION_PARM *pIlluminationInfo) +//! DESCRIPTION: This function sets the value of the specified illumination attribute. +//! +//! \note Only a single GPU can manage an given attribute on a given HW element, +//! regardless of how many are attatched. I.E. only one GPU will be used to control +//! the brightness of the LED on an SLI bridge, regardless of how many are physicaly attached. +//! You should enumerate thru the GPUs with the \ref NvAPI_GPU_QueryIlluminationSupport call to +//! determine which GPU is managing the attribute. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! \since Version: 300.05 +//! +//! \param [in] hPhysicalGpu Physical GPU handle +//! \param Attribute An enumeration value specifying the Illumination attribute to be set +//! \param Value The new value for the specified attribute. +//! This should be specified as a percentage of the full range of the attribute +//! (0-100; 0 = off, 100 = full brightness) +//! If a value is specified outside this range, NVAPI_INVALID_ARGUMENT will be returned. +//! +//! \return See \ref nvapistatus for the list of possible return values. Return values of special interest are: +//! NVAPI_INVALID_ARGUMENT The specified attibute is not known to the driver, or the specified value is out of range. +//! NVAPI_NOT_SUPPORTED The specified attribute is not supported on the specified GPU. +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup gpu +typedef struct _NV_GPU_SET_ILLUMINATION_PARM_V1 { + + // IN + NvU32 version; //!< Version of this structure + NvPhysicalGpuHandle hPhysicalGpu; //!< The handle of the GPU that you are checking for the specified attribute. + //!< Note that this is the GPU that is managing the attribute. + //!< Only a single GPU can manage an given attribute on a given HW element, + //!< regardless of how many are attatched. + //!< I.E. only one GPU will be used to control the brightness of the LED on an SLI bridge, + //!< regardless of how many are physicaly attached. + //!< You enumerate thru the GPUs with this call to determine which GPU is managing the attribute. + NV_GPU_ILLUMINATION_ATTRIB Attribute; //!< An enumeration value specifying the Illumination attribute to be querried. + //!< refer to enum \ref NV_GPU_ILLUMINATION_ATTRIB. + NvU32 Value; //!< A ULONG containing the new value for the specified attribute. + //!< This should be specified as a percentage of the full range of the attribute + //!< (0-100; 0 = off, 100 = full brightness) + //!< If a value is specified outside this range, NVAPI_INVALID_ARGUMENT will be returned. + + // OUT + +} NV_GPU_SET_ILLUMINATION_PARM_V1; + +//! \ingroup gpu +typedef NV_GPU_SET_ILLUMINATION_PARM_V1 NV_GPU_SET_ILLUMINATION_PARM; +//! \ingroup gpu +#define NV_GPU_SET_ILLUMINATION_PARM_VER_1 MAKE_NVAPI_VERSION(NV_GPU_SET_ILLUMINATION_PARM_V1,1) +//! \ingroup gpu +#define NV_GPU_SET_ILLUMINATION_PARM_VER NV_GPU_SET_ILLUMINATION_PARM_VER_1 + +//! \ingroup gpu +NVAPI_INTERFACE NvAPI_GPU_SetIllumination(NV_GPU_SET_ILLUMINATION_PARM *pIlluminationInfo); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnumNvidiaDisplayHandle +// +//! This function returns the handle of the NVIDIA display specified by the enum +//! index (thisEnum). The client should keep enumerating until it +//! returns NVAPI_END_ENUMERATION. +//! +//! Note: Display handles can get invalidated on a modeset, so the calling applications need to +//! renum the handles after every modeset. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \param [in] thisEnum The index of the NVIDIA display. +//! \param [out] pNvDispHandle Pointer to the NVIDIA display handle. +//! +//! \retval NVAPI_INVALID_ARGUMENT Either the handle pointer is NULL or enum index too big +//! \retval NVAPI_OK Return a valid NvDisplayHandle based on the enum index +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device found in the system +//! \retval NVAPI_END_ENUMERATION No more display device to enumerate +//! \ingroup disphandle +/////////////////////////////////////////////////////////////////////////////// +//NVAPI_INTERFACE NvAPI_EnumNvidiaDisplayHandle(NvU32 thisEnum, NvDisplayHandle *pNvDispHandle); + +typedef NvAPI_Status (__cdecl *_NvAPI_EnumNvidiaDisplayHandle)(NvU32 thisEnum, NvDisplayHandle *pNvDispHandle); +_NvAPI_EnumNvidiaDisplayHandle NvAPI_EnumNvidiaDisplayHandle; + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnumNvidiaUnAttachedDisplayHandle +// +//! This function returns the handle of the NVIDIA unattached display specified by the enum +//! index (thisEnum). The client should keep enumerating until it +//! returns error. +//! Note: Display handles can get invalidated on a modeset, so the calling applications need to +//! renum the handles after every modeset. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \param [in] thisEnum The index of the NVIDIA display. +//! \param [out] pNvUnAttachedDispHandle Pointer to the NVIDIA display handle of the unattached display. +//! +//! \retval NVAPI_INVALID_ARGUMENT Either the handle pointer is NULL or enum index too big +//! \retval NVAPI_OK Return a valid NvDisplayHandle based on the enum index +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device found in the system +//! \retval NVAPI_END_ENUMERATION No more display device to enumerate. +//! \ingroup disphandle +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_EnumNvidiaUnAttachedDisplayHandle(NvU32 thisEnum, NvUnAttachedDisplayHandle *pNvUnAttachedDispHandle); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_CreateDisplayFromUnAttachedDisplay +// +//! This function converts the unattached display handle to an active attached display handle. +//! +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT hNvUnAttachedDisp is not valid or pNvDisplay is NULL. +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_CreateDisplayFromUnAttachedDisplay(NvUnAttachedDisplayHandle hNvUnAttachedDisp, NvDisplayHandle *pNvDisplay); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetAssociatedNVidiaDisplayHandle +// +//! This function returns the handle of the NVIDIA display that is associated +//! with the given display "name" (such as "\\.\DISPLAY1"). +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT Either argument is NULL +//! \retval NVAPI_OK *pNvDispHandle is now valid +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device maps to that display name +//! \ingroup disphandle +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetAssociatedNvidiaDisplayHandle(const char *szDisplayName, NvDisplayHandle *pNvDispHandle); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_DISP_GetAssociatedUnAttachedNvidiaDisplayHandle +// +//! DESCRIPTION: This function returns the handle of an unattached NVIDIA display that is +//! associated with the given display name (such as "\\DISPLAY1"). +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! \retval ::NVAPI_INVALID_ARGUMENT Either argument is NULL. +//! \retval ::NVAPI_OK *pNvUnAttachedDispHandle is now valid. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device maps to that display name. +//! +//! \ingroup disphandle +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_GetAssociatedUnAttachedNvidiaDisplayHandle(const char *szDisplayName, NvUnAttachedDisplayHandle *pNvUnAttachedDispHandle); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetAssociatedNVidiaDisplayName +// +//! For a given NVIDIA display handle, this function returns a string (such as "\\.\DISPLAY1") to identify the display. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT Either argument is NULL +//! \retval NVAPI_OK *pNvDispHandle is now valid +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device maps to that display name +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetAssociatedNvidiaDisplayName(NvDisplayHandle NvDispHandle, NvAPI_ShortString szDisplayName); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetUnAttachedAssociatedDisplayName +// +//! This function returns the display name given, for example, "\\DISPLAY1", using the unattached NVIDIA display handle +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 95 +//! +//! \retval NVAPI_INVALID_ARGUMENT Either argument is NULL +//! \retval NVAPI_OK *pNvDispHandle is now valid +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA device maps to that display name +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetUnAttachedAssociatedDisplayName(NvUnAttachedDisplayHandle hNvUnAttachedDisp, NvAPI_ShortString szDisplayName); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnableHWCursor +// +//! This function enables hardware cursor support +//! +//! SUPPORTED OS: Windows XP +//! +//! +//! +//! \since Release: 80 +//! +//! \return NVAPI_ERROR or NVAPI_OK +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_EnableHWCursor(NvDisplayHandle hNvDisplay); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_DisableHWCursor +// +//! This function disables hardware cursor support +//! +//! SUPPORTED OS: Windows XP +//! +//! +//! \since Release: 80 +//! +//! \return NVAPI_ERROR or NVAPI_OK +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DisableHWCursor(NvDisplayHandle hNvDisplay); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetVBlankCounter +// +//! This function gets the V-blank counter +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 80 +//! +//! \return NVAPI_ERROR or NVAPI_OK +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetVBlankCounter(NvDisplayHandle hNvDisplay, NvU32 *pCounter); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_SetRefreshRateOverride +// +//! This function overrides the refresh rate on the given display/outputsMask. +//! The new refresh rate can be applied right away in this API call or deferred to be applied with the +//! next OS modeset. The override is good for only one modeset (regardless whether it's deferred or immediate). +//! +//! +//! SUPPORTED OS: Windows XP +//! +//! +//! \since Release: 80 +//! +//! \param [in] hNvDisplay The NVIDIA display handle. It can be NVAPI_DEFAULT_HANDLE or a handle +//! enumerated from NvAPI_EnumNVidiaDisplayHandle(). +//! \param [in] outputsMask A set of bits that identify all target outputs which are associated with the NVIDIA +//! display handle to apply the refresh rate override. When SLI is enabled, the +//! outputsMask only applies to the GPU that is driving the display output. +//! \param [in] refreshRate The override value. "0.0" means cancel the override. +//! \param [in] bSetDeferred +//! - "0": Apply the refresh rate override immediately in this API call.\p +//! - "1": Apply refresh rate at the next OS modeset. +//! +//! \retval NVAPI_INVALID_ARGUMENT hNvDisplay or outputsMask is invalid +//! \retval NVAPI_OK The refresh rate override is correct set +//! \retval NVAPI_ERROR The operation failed +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_SetRefreshRateOverride(NvDisplayHandle hNvDisplay, NvU32 outputsMask, float refreshRate, NvU32 bSetDeferred); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GetAssociatedDisplayOutputId +// +//! This function gets the active outputId associated with the display handle. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 90 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. It can be NVAPI_DEFAULT_HANDLE or a handle enumerated from NvAPI_EnumNVidiaDisplayHandle(). +//! \param [out] outputId The active display output ID associated with the selected display handle hNvDisplay. +//! The outputid will have only one bit set. In the case of Clone or Span mode, this will indicate the +//! display outputId of the primary display that the GPU is driving. See \ref handles. +//! +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_EXPECTED_DISPLAY_HANDLE hNvDisplay is not a valid display handle. +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetAssociatedDisplayOutputId(NvDisplayHandle hNvDisplay, NvU32 *pOutputId); + + +//! \ingroup dispcontrol +//! Used in NvAPI_GetDisplayPortInfo(). +typedef struct +{ + NvU32 version; //!< Structure version + NvU32 dpcd_ver; //!< DPCD version of the monitor + NV_DP_LINK_RATE maxLinkRate; //!< Maximum supported link rate + NV_DP_LANE_COUNT maxLaneCount; //!< Maximum supported lane count + NV_DP_LINK_RATE curLinkRate; //!< Current link rate + NV_DP_LANE_COUNT curLaneCount; //!< Current lane count + NV_DP_COLOR_FORMAT colorFormat; //!< Current color format + NV_DP_DYNAMIC_RANGE dynamicRange; //!< Dynamic range + NV_DP_COLORIMETRY colorimetry; //!< Ignored in RGB space + NV_DP_BPC bpc; //!< Current bit-per-component; + NvU32 isDp : 1; //!< If the monitor is driven by a DisplayPort + NvU32 isInternalDp : 1; //!< If the monitor is driven by an NV Dp transmitter + NvU32 isColorCtrlSupported : 1; //!< If the color format change is supported + NvU32 is6BPCSupported : 1; //!< If 6 bpc is supported + NvU32 is8BPCSupported : 1; //!< If 8 bpc is supported + NvU32 is10BPCSupported : 1; //!< If 10 bpc is supported + NvU32 is12BPCSupported : 1; //!< If 12 bpc is supported + NvU32 is16BPCSupported : 1; //!< If 16 bpc is supported + NvU32 isYCrCb422Supported : 1; //!< If YCrCb422 is supported + NvU32 isYCrCb444Supported : 1; //!< If YCrCb444 is supported + + } NV_DISPLAY_PORT_INFO; + +//! Macro for constructing the version field of NV_DISPLAY_PORT_INFO. +#define NV_DISPLAY_PORT_INFO_VER MAKE_NVAPI_VERSION(NV_DISPLAY_PORT_INFO,1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GetDisplayPortInfo +// +//! \fn NvAPI_GetDisplayPortInfo(_In_opt_ NvDisplayHandle hNvDisplay, _In_ NvU32 outputId, _Inout_ NV_DISPLAY_PORT_INFO *pInfo) +//! DESCRIPTION: This function returns the current DisplayPort-related information on the specified device (monitor). +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 165 +//! +//! \param [in] hvDisplay NVIDIA Display selection. It can be NVAPI_DEFAULT_HANDLE or a handle enumerated from NvAPI_EnumNVidiaDisplayHandle(). +//! This parameter is ignored when the outputId is a NvAPI displayId. +//! \param [in] outputId This can either be the connection bit mask or the NvAPI displayId. When the legacy connection bit mask is passed, +//! it should have exactly 1 bit set to indicate a single display. If it's "0" then the default outputId from +//! NvAPI_GetAssociatedDisplayOutputId() will be used. See \ref handles. +//! \param [out] pInfo The DisplayPort information +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +// +/////////////////////////////////////////////////////////////////////////////// +//! \ingroup dispcontrol +NVAPI_INTERFACE NvAPI_GetDisplayPortInfo(_In_opt_ NvDisplayHandle hNvDisplay, _In_ NvU32 outputId, _Inout_ NV_DISPLAY_PORT_INFO *pInfo); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_SetDisplayPort +// +//! \fn NvAPI_SetDisplayPort(NvDisplayHandle hNvDisplay, NvU32 outputId, NV_DISPLAY_PORT_CONFIG *pCfg) +//! DESCRIPTION: This function sets up DisplayPort-related configurations. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 165 +//! +//! \param [in] hNvDisplay NVIDIA display handle. It can be NVAPI_DEFAULT_HANDLE or a handle enumerated from +//! NvAPI_EnumNVidiaDisplayHandle(). +//! \param [in] outputId This display output ID, when it's "0" it means the default outputId generated from the return of +//! NvAPI_GetAssociatedDisplayOutputId(). See \ref handles. +//! \param [in] pCfg The display port config structure. If pCfg is NULL, it means to use the driver's default value to setup. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter +/////////////////////////////////////////////////////////////////////////////// + + +//! \ingroup dispcontrol +//! DisplayPort configuration settings - used in NvAPI_SetDisplayPort(). +typedef struct _NV_DISPLAY_PORT_CONFIG +{ + NvU32 version; //!< Structure version - 2 is the latest + NV_DP_LINK_RATE linkRate; //!< Link rate + NV_DP_LANE_COUNT laneCount; //!< Lane count + NV_DP_COLOR_FORMAT colorFormat; //!< Color format to set + NV_DP_DYNAMIC_RANGE dynamicRange; //!< Dynamic range + NV_DP_COLORIMETRY colorimetry; //!< Ignored in RGB space + NV_DP_BPC bpc; //!< Bit-per-component + NvU32 isHPD : 1; //!< If the control panel is making this call due to HPD + NvU32 isSetDeferred : 1; //!< Requires an OS modeset to finalize the setup if set + NvU32 isChromaLpfOff : 1; //!< Force the chroma low_pass_filter to be off + NvU32 isDitherOff : 1; //!< Force to turn off dither + NvU32 testLinkTrain : 1; //!< If testing mode, skip validation + NvU32 testColorChange : 1; //!< If testing mode, skip validation + +} NV_DISPLAY_PORT_CONFIG; + +//! \addtogroup dispcontrol +//! @{ +//! Macro for constructing the version field of NV_DISPLAY_PORT_CONFIG +#define NV_DISPLAY_PORT_CONFIG_VER MAKE_NVAPI_VERSION(NV_DISPLAY_PORT_CONFIG,2) +//! Macro for constructing the version field of NV_DISPLAY_PORT_CONFIG +#define NV_DISPLAY_PORT_CONFIG_VER_1 MAKE_NVAPI_VERSION(NV_DISPLAY_PORT_CONFIG,1) +//! Macro for constructing the version field of NV_DISPLAY_PORT_CONFIG +#define NV_DISPLAY_PORT_CONFIG_VER_2 MAKE_NVAPI_VERSION(NV_DISPLAY_PORT_CONFIG,2) +//! @} + + +//! \ingroup dispcontrol +NVAPI_INTERFACE NvAPI_SetDisplayPort(NvDisplayHandle hNvDisplay, NvU32 outputId, NV_DISPLAY_PORT_CONFIG *pCfg); + + + + +//! \ingroup dispcontrol +//! Used in NvAPI_GetHDMISupportInfo(). +typedef struct _NV_HDMI_SUPPORT_INFO_V1 +{ + NvU32 version; //!< Structure version + + NvU32 isGpuHDMICapable : 1; //!< If the GPU can handle HDMI + NvU32 isMonUnderscanCapable : 1; //!< If the monitor supports underscan + NvU32 isMonBasicAudioCapable : 1; //!< If the monitor supports basic audio + NvU32 isMonYCbCr444Capable : 1; //!< If YCbCr 4:4:4 is supported + NvU32 isMonYCbCr422Capable : 1; //!< If YCbCr 4:2:2 is supported + NvU32 isMonxvYCC601Capable : 1; //!< If xvYCC 601 is supported + NvU32 isMonxvYCC709Capable : 1; //!< If xvYCC 709 is supported + NvU32 isMonHDMI : 1; //!< If the monitor is HDMI (with IEEE's HDMI registry ID) + NvU32 reserved : 24; //!< Reserved. + + NvU32 EDID861ExtRev; //!< Revision number of the EDID 861 extension + } NV_HDMI_SUPPORT_INFO_V1; + +typedef struct _NV_HDMI_SUPPORT_INFO_V2 +{ + NvU32 version; //!< Structure version + + NvU32 isGpuHDMICapable : 1; //!< If the GPU can handle HDMI + NvU32 isMonUnderscanCapable : 1; //!< If the monitor supports underscan + NvU32 isMonBasicAudioCapable : 1; //!< If the monitor supports basic audio + NvU32 isMonYCbCr444Capable : 1; //!< If YCbCr 4:4:4 is supported + NvU32 isMonYCbCr422Capable : 1; //!< If YCbCr 4:2:2 is supported + NvU32 isMonxvYCC601Capable : 1; //!< If xvYCC extended colorimetry 601 is supported + NvU32 isMonxvYCC709Capable : 1; //!< If xvYCC extended colorimetry 709 is supported + NvU32 isMonHDMI : 1; //!< If the monitor is HDMI (with IEEE's HDMI registry ID) + NvU32 isMonsYCC601Capable : 1; //!< if sYCC601 extended colorimetry is supported + NvU32 isMonAdobeYCC601Capable : 1; //!< if AdobeYCC601 extended colorimetry is supported + NvU32 isMonAdobeRGBCapable : 1; //!< if AdobeRGB extended colorimetry is supported + NvU32 reserved : 21; //!< Reserved. + + NvU32 EDID861ExtRev; //!< Revision number of the EDID 861 extension + } NV_HDMI_SUPPORT_INFO_V2; + +#define NV_HDMI_SUPPORT_INFO_VER1 MAKE_NVAPI_VERSION(NV_HDMI_SUPPORT_INFO_V1, 1) +#define NV_HDMI_SUPPORT_INFO_VER2 MAKE_NVAPI_VERSION(NV_HDMI_SUPPORT_INFO_V2, 2) + + + +#ifndef NV_HDMI_SUPPORT_INFO_VER + +typedef NV_HDMI_SUPPORT_INFO_V2 NV_HDMI_SUPPORT_INFO; +#define NV_HDMI_SUPPORT_INFO_VER NV_HDMI_SUPPORT_INFO_VER2 + +#endif + + +//! SUPPORTED OS: Windows Vista and higher +//! +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GetHDMISupportInfo +// +//! \fn NvAPI_GetHDMISupportInfo(_In_opt_ NvDisplayHandle hNvDisplay, _In_ NvU32 outputId, _Inout_ NV_HDMI_SUPPORT_INFO *pInfo) +//! This API returns the current infoframe data on the specified device(monitor). +//! +//! \since Release: 95 +//! +//! \param [in] hvDisplay NVIDIA Display selection. It can be NVAPI_DEFAULT_HANDLE or a handle enumerated from NvAPI_EnumNVidiaDisplayHandle(). +//! This parameter is ignored when the outputId is a NvAPI displayId. +//! \param [in] outputId This can either be the connection bit mask or the NvAPI displayId. When the legacy connection bit mask is passed, +//! it should have exactly 1 bit set to indicate a single display. If it's "0" then the default outputId from +//! NvAPI_GetAssociatedDisplayOutputId() will be used. See \ref handles. +//! \param [out] pInfo The monitor and GPU's HDMI support info +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +/////////////////////////////////////////////////////////////////////////////// + + +//! \ingroup dispcontrol +NVAPI_INTERFACE NvAPI_GetHDMISupportInfo(_In_opt_ NvDisplayHandle hNvDisplay, _In_ NvU32 outputId, _Inout_ NV_HDMI_SUPPORT_INFO *pInfo); + + +//! \ingroup dispcontrol + +typedef enum +{ + NV_INFOFRAME_CMD_GET_DEFAULT = 0, //!< Returns the fields in the infoframe with values set by the manufacturer - NVIDIA/OEM. + NV_INFOFRAME_CMD_RESET, //!< Sets the fields in the infoframe to auto, and infoframe to the default infoframe for use in a set. + NV_INFOFRAME_CMD_GET, //!< Get the current infoframe state. + NV_INFOFRAME_CMD_SET, //!< Set the current infoframe state (flushed to the monitor), the values are one time and do not persist. + NV_INFOFRAME_CMD_GET_OVERRIDE, //!< Get the override infoframe state, non-override fields will be set to value = AUTO, overridden fields will have the current override values. + NV_INFOFRAME_CMD_SET_OVERRIDE, //!< Set the override infoframe state, non-override fields will be set to value = AUTO, other values indicate override; persist across modeset/reboot + NV_INFOFRAME_CMD_GET_PROPERTY, //!< get properties associated with infoframe (each of the infoframe type will have properties) + NV_INFOFRAME_CMD_SET_PROPERTY, //!< set properties associated with infoframe +} NV_INFOFRAME_CMD; + + +typedef enum +{ + NV_INFOFRAME_PROPERTY_MODE_AUTO = 0, //!< Driver determines whether to send infoframes. + NV_INFOFRAME_PROPERTY_MODE_ENABLE, //!< Driver always sends infoframe. + NV_INFOFRAME_PROPERTY_MODE_DISABLE, //!< Driver never sends infoframe. + NV_INFOFRAME_PROPERTY_MODE_ALLOW_OVERRIDE, //!< Driver only sends infoframe when client requests it via infoframe escape call. +} NV_INFOFRAME_PROPERTY_MODE; + + +//! Returns whether the current monitor is in blacklist or force this monitor to be in blacklist. +typedef enum +{ + NV_INFOFRAME_PROPERTY_BLACKLIST_FALSE = 0, + NV_INFOFRAME_PROPERTY_BLACKLIST_TRUE, +} NV_INFOFRAME_PROPERTY_BLACKLIST; + +typedef struct +{ + NvU32 mode : 4; + NvU32 blackList : 2; + NvU32 reserved : 10; + NvU32 version : 8; + NvU32 length : 8; +} NV_INFOFRAME_PROPERTY; + +//! Byte1 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO_NODATA = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO_OVERSCAN, + NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO_UNDERSCAN, + NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO_FUTURE, + NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_SCANINFO; + + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA_NOT_PRESENT = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA_VERTICAL_PRESENT, + NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA_HORIZONTAL_PRESENT, + NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA_BOTH_PRESENT, + NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_BARDATA; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_AFI_ABSENT = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_AFI_PRESENT, + NV_INFOFRAME_FIELD_VALUE_AVI_AFI_AUTO = 3 +} NV_INFOFRAME_FIELD_VALUE_AVI_ACTIVEFORMATINFO; + + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT_RGB = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT_YCbCr422, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT_YCbCr444, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT_FUTURE, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_COLORFORMAT; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_F17_FALSE = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_F17_TRUE, + NV_INFOFRAME_FIELD_VALUE_AVI_F17_AUTO = 3 +} NV_INFOFRAME_FIELD_VALUE_AVI_F17; + +//! Byte2 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_NO_AFD = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE01, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE02, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE03, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_LETTERBOX_GT16x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE05, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE06, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE07, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_EQUAL_CODEDFRAME = 8, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_CENTER_4x3, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_CENTER_16x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_CENTER_14x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_RESERVE12, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_4x3_ON_14x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_16x9_ON_14x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_16x9_ON_4x3, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION_AUTO = 31, +} NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOACTIVEPORTION; + + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME_NO_DATA = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME_4x3, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME_16x9, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME_FUTURE, + NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_ASPECTRATIOCODEDFRAME; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY_NO_DATA = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY_SMPTE_170M, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY_ITUR_BT709, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY_USE_EXTENDED_COLORIMETRY, + NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_COLORIMETRY; + +//! Byte 3 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING_NO_DATA = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING_HORIZONTAL, + NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING_VERTICAL, + NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING_BOTH, + NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_NONUNIFORMPICTURESCALING; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION_DEFAULT = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION_LIMITED_RANGE, + NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION_FULL_RANGE, + NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION_RESERVED, + NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_RGBQUANTIZATION; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_XVYCC601 = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_XVYCC709, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_SYCC601, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_ADOBEYCC601, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_ADOBERGB, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_RESERVED05, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_RESERVED06, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_RESERVED07, + NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY_AUTO = 15 +} NV_INFOFRAME_FIELD_VALUE_AVI_EXTENDEDCOLORIMETRY; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_ITC_VIDEO_CONTENT = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_ITC_ITCONTENT, + NV_INFOFRAME_FIELD_VALUE_AVI_ITC_AUTO = 3 +} NV_INFOFRAME_FIELD_VALUE_AVI_ITC; + +//! Byte 4 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_NONE = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X02, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X03, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X04, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X05, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X06, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X07, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X08, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X09, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_X10, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED10, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED11, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED12, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED13, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED14, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_RESERVED15, + NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION_AUTO = 31 +} NV_INFOFRAME_FIELD_VALUE_AVI_PIXELREPETITION; + + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE_GRAPHICS = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE_PHOTO, + NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE_CINEMA, + NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE_GAME, + NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_CONTENTTYPE; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION_LIMITED_RANGE = 0, + NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION_FULL_RANGE, + NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION_RESERVED02, + NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION_RESERVED03, + NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AVI_YCCQUANTIZATION; + +//! Adding an Auto bit to each field +typedef struct +{ + NvU32 vic : 8; + NvU32 pixelRepeat : 5; + NvU32 colorSpace : 3; + NvU32 colorimetry : 3; + NvU32 extendedColorimetry : 4; + NvU32 rgbQuantizationRange : 3; + NvU32 yccQuantizationRange : 3; + NvU32 itContent : 2; + NvU32 contentTypes : 3; + NvU32 scanInfo : 3; + NvU32 activeFormatInfoPresent : 2; + NvU32 activeFormatAspectRatio : 5; + NvU32 picAspectRatio : 3; + NvU32 nonuniformScaling : 3; + NvU32 barInfo : 3; + NvU32 top_bar : 17; + NvU32 bottom_bar : 17; + NvU32 left_bar : 17; + NvU32 right_bar : 17; + NvU32 Future17 : 2; + NvU32 Future47 : 2; +} NV_INFOFRAME_VIDEO; + +//! Byte 1 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_IN_HEADER = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_2, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_3, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_4, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_5, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_6, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_7, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_8, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT_AUTO = 15 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELCOUNT; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_IN_HEADER = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_PCM, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_AC3, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_MPEG1, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_MP3, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_MPEG2, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_AACLC, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_DTS, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_ATRAC, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_DSD, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_EAC3, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_DTSHD, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_MLP, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_DST, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_WMAPRO, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_USE_CODING_EXTENSION_TYPE, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE_AUTO = 31 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGTYPE; + +//! Byte 2 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE_IN_HEADER = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE_16BITS, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE_20BITS, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE_24BITS, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLESIZE; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_IN_HEADER = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_32000HZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_44100HZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_48000HZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_88200KHZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_96000KHZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_176400KHZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_192000KHZ, + NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY_AUTO = 15 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_SAMPLEFREQUENCY; + + + +//! Byte 3 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_USE_CODING_TYPE = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_HEAAC, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_HEAACV2, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_MPEGSURROUND, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE04, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE05, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE06, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE07, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE08, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE09, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE10, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE11, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE12, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE13, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE14, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE15, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE16, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE17, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE18, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE19, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE20, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE21, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE22, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE23, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE24, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE25, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE26, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE27, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE28, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE29, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE30, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_RESERVE31, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE_AUTO = 63 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_CODINGEXTENSIONTYPE; + + +//! Byte 4 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_X_X_X_FR_FL =0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_X_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_X_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_X_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_RC_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_RC_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_RC_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_X_RC_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_X_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_RC_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_RC_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_RC_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_RC_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_RRC_RLC_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_RRC_RLC_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_RRC_RLC_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_RRC_RLC_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_X_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_X_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_X_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_X_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_RC_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_RC_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_RC_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_X_RC_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRC_FLC_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_FCH_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_X_FCH_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_X_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_X_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRH_FLH_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRH_FLH_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRW_FLW_RR_RL_X_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRW_FLW_RR_RL_X_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_RC_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_RC_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FCH_RC_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FCH_RC_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_FCH_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_TC_FCH_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRH_FLH_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRH_FLH_RR_RL_FC_LFE_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRW_FLW_RR_RL_FC_X_FR_FL, + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_FRW_FLW_RR_RL_FC_LFE_FR_FL = 0X31, + // all other values should default to auto + NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION_AUTO = 0x1FF +} NV_INFOFRAME_FIELD_VALUE_AUDIO_CHANNELALLOCATION; + +//! Byte 5 related +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL_NO_DATA = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL_0DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL_PLUS10DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL_RESERVED03, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL_AUTO = 7 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_LFEPLAYBACKLEVEL; + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_0DB = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_1DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_2DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_3DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_4DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_5DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_6DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_7DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_8DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_9DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_10DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_11DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_12DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_13DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_14DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_15DB, + NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES_AUTO = 31 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_LEVELSHIFTVALUES; + + +typedef enum +{ + NV_INFOFRAME_FIELD_VALUE_AUDIO_DOWNMIX_PERMITTED = 0, + NV_INFOFRAME_FIELD_VALUE_AUDIO_DOWNMIX_PROHIBITED, + NV_INFOFRAME_FIELD_VALUE_AUDIO_DOWNMIX_AUTO = 3 +} NV_INFOFRAME_FIELD_VALUE_AUDIO_DOWNMIX; + +typedef struct +{ + NvU32 codingType : 5; + NvU32 codingExtensionType : 6; + NvU32 sampleSize : 3; + NvU32 sampleRate : 4; + NvU32 channelCount : 4; + NvU32 speakerPlacement : 9; + NvU32 downmixInhibit : 2; + NvU32 lfePlaybackLevel : 3; + NvU32 levelShift : 5; + NvU32 Future12 : 2; + NvU32 Future2x : 4; + NvU32 Future3x : 4; + NvU32 Future52 : 2; + NvU32 Future6 : 9; + NvU32 Future7 : 9; + NvU32 Future8 : 9; + NvU32 Future9 : 9; + NvU32 Future10 : 9; +} NV_INFOFRAME_AUDIO; + +typedef struct +{ + NvU32 version; //!< version of this structure + NvU16 size; //!< size of this structure + NvU8 cmd; //!< The actions to perform from NV_INFOFRAME_CMD + NvU8 type; //!< type of infoframe + + union + { + NV_INFOFRAME_PROPERTY property; //!< This is NVIDIA-specific and corresponds to the property cmds and associated infoframe. + NV_INFOFRAME_AUDIO audio; + NV_INFOFRAME_VIDEO video; + } infoframe; +} NV_INFOFRAME_DATA; + +//! Macro for constructing the version field of ::NV_INFOFRAME_DATA +#define NV_INFOFRAME_DATA_VER MAKE_NVAPI_VERSION(NV_INFOFRAME_DATA,1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_Disp_InfoFrameControl +// +//! DESCRIPTION: This API controls the InfoFrame values. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in] displayId Monitor Identifier +//! \param [in,out] pInfoframeData Contains data corresponding to InfoFrame +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Disp_InfoFrameControl(_In_ NvU32 displayId, _Inout_ NV_INFOFRAME_DATA *pInfoframeData); + + + + + + +//! \ingroup dispcontrol +//! @{ +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_Disp_ColorControl +// +//! \fn NvAPI_Disp_ColorControl(NvU32 displayId, NV_COLOR_DATA *pColorData) +//! DESCRIPTION: This API controls the Color values. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in] displayId Monitor Identifier +//! \param [in,out] pColorData Contains data corresponding to color information +//! +//! \return RETURN STATUS: +//! ::NVAPI_OK, +//! ::NVAPI_ERROR, +//! ::NVAPI_INVALID_ARGUMENT +// +/////////////////////////////////////////////////////////////////////////////// + +typedef enum +{ + NV_COLOR_CMD_GET = 1, + NV_COLOR_CMD_SET, + NV_COLOR_CMD_IS_SUPPORTED_COLOR, + NV_COLOR_CMD_GET_DEFAULT +} NV_COLOR_CMD; + +//! See Table 14 of CEA-861E. Not all of this is supported by the GPU. +typedef enum +{ + NV_COLOR_FORMAT_RGB = 0, + NV_COLOR_FORMAT_YUV422, + NV_COLOR_FORMAT_YUV444, + NV_COLOR_FORMAT_DEFAULT = 0xFE, + NV_COLOR_FORMAT_AUTO = 0xFF +} NV_COLOR_FORMAT; + + + +typedef enum +{ + NV_COLOR_COLORIMETRY_RGB = 0, + NV_COLOR_COLORIMETRY_YCC601, + NV_COLOR_COLORIMETRY_YCC709, + NV_COLOR_COLORIMETRY_XVYCC601, + NV_COLOR_COLORIMETRY_XVYCC709, + NV_COLOR_COLORIMETRY_SYCC601, + NV_COLOR_COLORIMETRY_ADOBEYCC601, + NV_COLOR_COLORIMETRY_ADOBERGB, + NV_COLOR_COLORIMETRY_DEFAULT = 0xFE, + NV_COLOR_COLORIMETRY_AUTO = 0xFF +} NV_COLOR_COLORIMETRY; + +typedef struct +{ + NvU32 version; //!< Version of this structure + NvU16 size; //!< Size of this structure + NvU8 cmd; + struct + { + NvU8 colorFormat; + NvU8 colorimetry; + } data; +} NV_COLOR_DATA; + +NVAPI_INTERFACE NvAPI_Disp_ColorControl(NvU32 displayId, NV_COLOR_DATA *pColorData); + +//! Macro for constructing the version field of ::NV_COLOR_DATA +#define NV_COLOR_DATA_VER MAKE_NVAPI_VERSION(NV_COLOR_DATA,1) + +//! @} + +//! \ingroup dispcontrol +//! Used in NvAPI_DISP_GetTiming(). +typedef struct +{ + NvU32 isInterlaced : 4; //!< To retrieve interlaced/progressive timing + NvU32 reserved0 : 12; + union + { + NvU32 tvFormat : 8; //!< The actual analog HD/SDTV format. Used when the timing type is + //! NV_TIMING_OVERRIDE_ANALOG_TV and width==height==rr==0. + NvU32 ceaId : 8; //!< The EIA/CEA 861B/D predefined short timing descriptor ID. + //! Used when the timing type is NV_TIMING_OVERRIDE_EIA861 + //! and width==height==rr==0. + NvU32 nvPsfId : 8; //!< The NV predefined PsF format Id. + //! Used when the timing type is NV_TIMING_OVERRIDE_NV_PREDEFINED. + }; + NvU32 scaling : 8; //!< Define preferred scaling +}NV_TIMING_FLAG; + +//! \ingroup dispcontrol +//! Used in NvAPI_DISP_GetTiming(). +typedef struct _NV_TIMING_INPUT +{ + NvU32 version; //!< (IN) structure version + + NvU32 width; //!< Visible horizontal size + NvU32 height; //!< Visible vertical size + float rr; //!< Timing refresh rate + + NV_TIMING_FLAG flag; //!< Flag containing additional info for timing calculation. + + NV_TIMING_OVERRIDE type; //!< Timing type(formula) to use for calculating the timing +}NV_TIMING_INPUT; + +#define NV_TIMING_INPUT_VER MAKE_NVAPI_VERSION(NV_TIMING_INPUT,1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_GetTiming +// +//! DESCRIPTION: This function calculates the timing from the visible width/height/refresh-rate and timing type info. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 313 +//! +//! +//! \param [in] displayId Display ID of the display. +//! \param [in] timingInput Inputs used for calculating the timing. +//! \param [out] pTiming Pointer to the NV_TIMING structure. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_GetTiming( _In_ NvU32 displayId,_In_ NV_TIMING_INPUT *timingInput, _Out_ NV_TIMING *pTiming); + + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_GetMonitorCapabilities +// +//! \fn NvAPI_DISP_GetMonitorCapabilities(NvU32 displayId, NV_MONITOR_CAPABILITIES *pMonitorCapabilities) +//! DESCRIPTION: This API returns the Monitor capabilities +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in] displayId Monitor Identifier +//! \param [out] pMonitorCapabilities The monitor support info +//! +//! \return ::NVAPI_OK, +//! ::NVAPI_ERROR, +//! ::NVAPI_INVALID_ARGUMENT +// +/////////////////////////////////////////////////////////////////////////////// + +//! \ingroup dispcontrol +//! @{ + + +//! HDMI-related and extended CAPs +typedef enum +{ + // hdmi related caps + NV_MONITOR_CAPS_TYPE_HDMI_VSDB = 0x1000, + NV_MONITOR_CAPS_TYPE_HDMI_VCDB = 0x1001, +} NV_MONITOR_CAPS_TYPE; + + + +typedef struct _NV_MONITOR_CAPS_VCDB +{ + NvU8 quantizationRangeYcc : 1; + NvU8 quantizationRangeRgb : 1; + NvU8 scanInfoPreferredVideoFormat : 2; + NvU8 scanInfoITVideoFormats : 2; + NvU8 scanInfoCEVideoFormats : 2; +} NV_MONITOR_CAPS_VCDB; + + +//! See NvAPI_DISP_GetMonitorCapabilities(). +typedef struct _NV_MONITOR_CAPS_VSDB +{ + // byte 1 + NvU8 sourcePhysicalAddressB : 4; //!< Byte 1 + NvU8 sourcePhysicalAddressA : 4; //!< Byte 1 + // byte 2 + NvU8 sourcePhysicalAddressD : 4; //!< Byte 2 + NvU8 sourcePhysicalAddressC : 4; //!< Byte 2 + // byte 3 + NvU8 supportDualDviOperation : 1; //!< Byte 3 + NvU8 reserved6 : 2; //!< Byte 3 + NvU8 supportDeepColorYCbCr444 : 1; //!< Byte 3 + NvU8 supportDeepColor30bits : 1; //!< Byte 3 + NvU8 supportDeepColor36bits : 1; //!< Byte 3 + NvU8 supportDeepColor48bits : 1; //!< Byte 3 + NvU8 supportAI : 1; //!< Byte 3 + // byte 4 + NvU8 maxTmdsClock; //!< Bye 4 + // byte 5 + NvU8 cnc0SupportGraphicsTextContent : 1; //!< Byte 5 + NvU8 cnc1SupportPhotoContent : 1; //!< Byte 5 + NvU8 cnc2SupportCinemaContent : 1; //!< Byte 5 + NvU8 cnc3SupportGameContent : 1; //!< Byte 5 + NvU8 reserved8 : 1; //!< Byte 5 + NvU8 hasVicEntries : 1; //!< Byte 5 + NvU8 hasInterlacedLatencyField : 1; //!< Byte 5 + NvU8 hasLatencyField : 1; //!< Byte 5 + // byte 6 + NvU8 videoLatency; //!< Byte 6 + // byte 7 + NvU8 audioLatency; //!< Byte 7 + // byte 8 + NvU8 interlacedVideoLatency; //!< Byte 8 + // byte 9 + NvU8 interlacedAudioLatency; //!< Byte 9 + // byte 10 + NvU8 reserved13 : 7; //!< Byte 10 + NvU8 has3dEntries : 1; //!< Byte 10 + // byte 11 + NvU8 hdmi3dLength : 5; //!< Byte 11 + NvU8 hdmiVicLength : 3; //!< Byte 11 + // Remaining bytes + NvU8 hdmi_vic[7]; //!< Keeping maximum length for 3 bits + NvU8 hdmi_3d[31]; //!< Keeping maximum length for 5 bits +} NV_MONITOR_CAPS_VSDB; + + +//! See NvAPI_DISP_GetMonitorCapabilities(). +typedef struct _NV_MONITOR_CAPABILITIES +{ + NvU32 version; + NvU16 size; + NvU32 infoType; + NvU32 connectorType; //!< Out: VGA, TV, DVI, HDMI, DP + NvU8 bIsValidInfo : 1; //!< Boolean : Returns invalid if requested info is not present such as VCDB not present + union { + NV_MONITOR_CAPS_VSDB vsdb; + NV_MONITOR_CAPS_VCDB vcdb; + } data; +} NV_MONITOR_CAPABILITIES; + +//! Macro for constructing the version field of ::NV_MONITOR_CAPABILITIES +#define NV_MONITOR_CAPABILITIES_VER MAKE_NVAPI_VERSION(NV_MONITOR_CAPABILITIES,1) + +//! @} + +//! SUPPORTED OS: Windows Vista and higher +//! +//! \ingroup dispcontrol +NVAPI_INTERFACE NvAPI_DISP_GetMonitorCapabilities(_In_ NvU32 displayId, _Inout_ NV_MONITOR_CAPABILITIES *pMonitorCapabilities); + +//! \ingroup dispcontrol +typedef struct _NV_MONITOR_COLOR_DATA +{ + NvU32 version; +// We are only supporting DP monitors for now. We need to extend this to HDMI panels as well + NV_DP_COLOR_FORMAT colorFormat; //!< One of the supported color formats + NV_DP_BPC backendBitDepths; //!< One of the supported bit depths +} NV_MONITOR_COLOR_CAPS_V1; + +typedef NV_MONITOR_COLOR_CAPS_V1 NV_MONITOR_COLOR_CAPS; + +//! \ingroup dispcontrol +#define NV_MONITOR_COLOR_CAPS_VER1 MAKE_NVAPI_VERSION(NV_MONITOR_COLOR_CAPS_V1,1) +#define NV_MONITOR_COLOR_CAPS_VER NV_MONITOR_COLOR_CAPS_VER1 + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_GetMonitorColorCapabilities +// +//! DESCRIPTION: This API returns all the color formats and bit depth values supported by a given DP monitor. +//! +//! USAGE: Sequence of calls which caller should make to get the information. +//! 1. First call NvAPI_DISP_GetMonitorColorCapabilities() with pMonitorColorCapabilities as NULL to get the count. +//! 2. Allocate memory for color caps(NV_MONITOR_COLOR_CAPS) array. +//! 3. Call NvAPI_DISP_GetMonitorColorCapabilities() again with the pointer to the memory allocated to get all the +//! color capabilities. +//! +//! Note : +//! 1. pColorCapsCount should never be NULL, else the API will fail with NVAPI_INVALID_ARGUMENT. +//! 2. *pColorCapsCount returned from the API will always be the actual count in any/every call. +//! 3. Memory size to be allocated should be (*pColorCapsCount * sizeof(NV_MONITOR_COLOR_CAPS)). +//! 4. If the memory allocated is less than what is required to return all the timings, this API will return the +//! amount of information which can fit in user provided buffer and API will return NVAPI_INSUFFICIENT_BUFFER. +//! 5. If the caller specifies a greater value for *pColorCapsCount in second call to NvAPI_DISP_GetMonitorColorCapabilities() +//! than what was returned from first call, the API will return only the actual number of elements in the color +//! capabilities array and the extra buffer will remain unused. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in] displayId Monitor Identifier +//! \param [in, out] pMonitorColorCapabilities The monitor color capabilities information +//! \param [in, out] pColorCapsCount - During input, the number of elements allocated for the pMonitorColorCapabilities pointer +//! - During output, the actual number of color data elements the monitor supports +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval NVAPI_INSUFFICIENT_BUFFER The input buffer size is not sufficient to hold the total contents. In this case +//! *pColorCapsCount will hold the required amount of elements. +//! \retval NVAPI_INVALID_DISPLAY_ID The input monitor is either not connected or is not a DP panel. +//! +//! \ingroup dispcontrol +//! +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_GetMonitorColorCapabilities(_In_ NvU32 displayId, __inout_ecount_part_opt(*pColorCapsCount, *pColorCapsCount) NV_MONITOR_COLOR_CAPS *pMonitorColorCapabilities, _Inout_ NvU32 *pColorCapsCount); + +//! \ingroup dispcontrol +//! Used in NvAPI_DISP_EnumCustomDisplay() and NvAPI_DISP_TryCustomDisplay(). +typedef struct +{ + NvU32 version; + + // the source mode information + NvU32 width; //!< Source surface(source mode) width + NvU32 height; //!< Source surface(source mode) height + NvU32 depth; //!< Source surface color depth."0" means all 8/16/32bpp + NV_FORMAT colorFormat; //!< Color format (optional) + + NV_VIEWPORTF srcPartition; //!< For multimon support, should be set to (0,0,1.0,1.0) for now. + + float xRatio; //!< Horizontal scaling ratio + float yRatio; //!< Vertical scaling ratio + + NV_TIMING timing; //!< Timing used to program TMDS/DAC/LVDS/HDMI/TVEncoder, etc. + NvU32 hwModeSetOnly : 1; //!< If set, it means a hardware modeset without OS update + +}NV_CUSTOM_DISPLAY; + +//! \ingroup dispcontrol +//! Used in NV_CUSTOM_DISPLAY. +#define NV_CUSTOM_DISPLAY_VER MAKE_NVAPI_VERSION(NV_CUSTOM_DISPLAY,1) + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_EnumCustomDisplay +// +//! DESCRIPTION: This API enumerates the custom timing specified by the enum index. +//! The client should keep enumerating until it returns NVAPI_END_ENUMERATION. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] displayId Dispaly ID of the display. +//! \param [in] index Enum index +//! \param [inout] pCustDisp Pointer to the NV_CUSTOM_DISPLAY structure +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! \retval NVAPI_INVALID_DISPLAY_ID: Custom Timing is not supported on the Display, whose display id is passed +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_EnumCustomDisplay( _In_ NvU32 displayId, _In_ NvU32 index, _Inout_ NV_CUSTOM_DISPLAY *pCustDisp); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_TryCustomDisplay +// +//! DESCRIPTION: This API is used to set up a custom display without saving the configuration on multiple displays. +//! +//! \note +//! All the members of srcPartition, present in NV_CUSTOM_DISPLAY structure, should have their range in (0.0,1.0). +//! In clone mode the timings can applied to both the target monitors but only one target at a time. \n +//! For the secondary target the applied timings works under the following conditions: +//! - If the secondary monitor EDID supports the selected timing, OR +//! - If the selected custom timings can be scaled by the secondary monitor for the selected source resolution on the primary, OR +//! - If the selected custom timings matches the existing source resolution on the primary. +//! Setting up a custom display on non-active but connected monitors is supported only for Win7 and above. +//! +//! SUPPORTED OS: Windows XP, Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! +//! \param [in] pDisplayIds Array of the target display Dispaly IDs - See \ref handles. +//! \param [in] count Total number of the incoming Display IDs and corresponding NV_CUSTOM_DISPLAY structure. This is for the multi-head support. +//! \param [in] pCustDisp Pointer to the NV_CUSTOM_DISPLAY structure array. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! \retval NVAPI_INVALID_DISPLAY_ID: Custom Timing is not supported on the Display, whose display id is passed +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_TryCustomDisplay( __in_ecount(count) NvU32 *pDisplayIds, _In_ NvU32 count, __in_ecount(count) NV_CUSTOM_DISPLAY *pCustDisp); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_DeleteCustomDisplay +// +//! DESCRIPTION: This function deletes the custom display configuration, specified from the registry for all the displays whose display IDs are passed. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 313 +//! +//! +//! \param [in] pDisplayIds Array of Dispaly IDs on which custom display configuration is to be saved. +//! \param [in] count Total number of the incoming Dispaly IDs. This is for the multi-head support. +//! \param [in] pCustDisp Pointer to the NV_CUSTOM_DISPLAY structure +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! \retval NVAPI_INVALID_DISPLAY_ID: Custom Timing is not supported on the Display, whose display id is passed +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_DeleteCustomDisplay( __in_ecount(count) NvU32 *pDisplayIds, _In_ NvU32 count, _In_ NV_CUSTOM_DISPLAY *pCustDisp); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_SaveCustomDisplay +// +//! DESCRIPTION: This function saves the current hardware display configuration on the specified Display IDs as a custom display configuration. +//! This function should be called right after NvAPI_DISP_TryCustomDisplay() to save the custom display from the current +//! hardware context. This function will not do anything if the custom display configuration is not tested on the hardware. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 313 +//! +//! +//! \param [in] pDisplayIds Array of Dispaly IDs on which custom display configuration is to be saved. +//! \param [in] count Total number of the incoming Dispaly IDs. This is for the multi-head support. +//! \param [in] isThisOutputIdOnly If set, the saved custom display will only be applied on the monitor with the same outputId (see \ref handles). +//! \param [in] isThisMonitorIdOnly If set, the saved custom display will only be applied on the monitor with the same EDID ID or +//! the same TV connector in case of analog TV. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! \retval NVAPI_INVALID_DISPLAY_ID: Custom Timing is not supported on the Display, whose display id is passed +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_SaveCustomDisplay( __in_ecount(count) NvU32 *pDisplayIds, _In_ NvU32 count, _In_ NvU32 isThisOutputIdOnly, _In_ NvU32 isThisMonitorIdOnly); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_RevertCustomDisplayTrial +// +//! DESCRIPTION: This API is used to restore the display configuration, that was changed by calling NvAPI_DISP_TryCustomDisplay(). This function +//! must be called only after a custom display configuration is tested on the hardware, using NvAPI_DISP_TryCustomDisplay(), +//! otherwise no action is taken. On Vista, NvAPI_DISP_RevertCustomDisplayTrial should be called with an active display that +//! was affected during the NvAPI_DISP_TryCustomDisplay() call, per GPU. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \since Release: 313 +//! +//! +//! \param [in] pDisplayIds Pointer to display Id, of an active display. +//! \param [in] count Total number of incoming Display IDs. For future use only. Currently it is expected to be passed as 1. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_RevertCustomDisplayTrial( __in_ecount(count) NvU32* pDisplayIds, _In_ NvU32 count); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GetView +// +//! This API lets caller retrieve the target display arrangement for selected source display handle. +//! \note Display PATH with this API is limited to single GPU. DUALVIEW across GPUs will be returned as STANDARD VIEW. +//! Use NvAPI_SYS_GetDisplayTopologies() to query views across GPUs. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_GetDisplayConfig. +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \since Release: 85 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. It can be #NVAPI_DEFAULT_HANDLE or a handle enumerated from +//! NvAPI_EnumNVidiaDisplayHandle(). +//! \param [out] pTargets User allocated storage to retrieve an array of NV_VIEW_TARGET_INFO. Can be NULL to retrieve +//! the targetCount. +//! \param [in,out] targetMaskCount Count of target device mask specified in pTargetMask. +//! \param [out] targetView Target view selected from NV_TARGET_VIEW_MODE. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_GetDisplayConfig.") +NVAPI_INTERFACE NvAPI_GetView(NvDisplayHandle hNvDisplay, NV_VIEW_TARGET_INFO *pTargets, NvU32 *pTargetMaskCount, NV_TARGET_VIEW_MODE *pTargetView); + + + + + + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GetViewEx +// +//! DESCRIPTION: This API lets caller retrieve the target display arrangement for selected source display handle. +//! \note Display PATH with this API is limited to single GPU. DUALVIEW across GPUs will be returned as STANDARD VIEW. +//! Use NvAPI_SYS_GetDisplayTopologies() to query views across GPUs. +//! +//! \deprecated Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_GetDisplayConfig. +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \since Release: 165 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. #NVAPI_DEFAULT_HANDLE is not allowed, it has to be a handle enumerated with +//! NvAPI_EnumNVidiaDisplayHandle(). +//! \param [in,out] pPathInfo Count field should be set to NVAPI_MAX_DISPLAY_PATH. Can be NULL to retrieve just the pathCount. +//! \param [in,out] pPathCount Number of elements in array pPathInfo->path. +//! \param [out] pTargetViewMode Display view selected from NV_TARGET_VIEW_MODE. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_API_NOT_INTIALIZED NVAPI not initialized +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +//! \retval NVAPI_EXPECTED_DISPLAY_HANDLE hNvDisplay is not a valid display handle. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +__nvapi_deprecated_function("Do not use this function - it is deprecated in release 290. Instead, use NvAPI_DISP_GetDisplayConfig.") +NVAPI_INTERFACE NvAPI_GetViewEx(NvDisplayHandle hNvDisplay, NV_DISPLAY_PATH_INFO *pPathInfo, NvU32 *pPathCount, NV_TARGET_VIEW_MODE *pTargetViewMode); + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_GetSupportedViews +// +//! This API lets caller enumerate all the supported NVIDIA display views - nView and Dualview modes. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 85 +//! +//! \param [in] hNvDisplay NVIDIA Display selection. It can be #NVAPI_DEFAULT_HANDLE or a handle enumerated from +//! NvAPI_EnumNVidiaDisplayHandle(). +//! \param [out] pTargetViews Array of supported views. Can be NULL to retrieve the pViewCount first. +//! \param [in,out] pViewCount Count of supported views. +//! +//! \retval NVAPI_OK Completed request +//! \retval NVAPI_ERROR Miscellaneous error occurred +//! \retval NVAPI_INVALID_ARGUMENT Invalid input parameter. +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GetSupportedViews(NvDisplayHandle hNvDisplay, NV_TARGET_VIEW_MODE *pTargetViews, NvU32 *pViewCount); + + +//! SUPPORTED OS: Windows XP and higher +//! +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_DISP_GetDisplayIdByDisplayName +// +//! DESCRIPTION: This API retrieves the Display Id of a given display by +//! display name. The display must be active to retrieve the +//! displayId. In the case of clone mode or Surround gaming, +//! the primary or top-left display will be returned. +//! +//! \param [in] displayName Name of display (Eg: "\\DISPLAY1" to +//! retrieve the displayId for. +//! \param [out] displayId Display ID of the requested display. +//! +//! retval ::NVAPI_OK: Capabilties have been returned. +//! retval ::NVAPI_INVALID_ARGUMENT: One or more args passed in are invalid. +//! retval ::NVAPI_API_NOT_INTIALIZED: The NvAPI API needs to be initialized first +//! retval ::NVAPI_NO_IMPLEMENTATION: This entrypoint not available +//! retval ::NVAPI_ERROR: Miscellaneous error occurred +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_GetDisplayIdByDisplayName(const char *displayName, NvU32* displayId); + + + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_GetDisplayConfig +// +//! DESCRIPTION: This API lets caller retrieve the current global display +//! configuration. +//! USAGE: The caller might have to call this three times to fetch all the required configuration details as follows: +//! First Pass: Caller should Call NvAPI_DISP_GetDisplayConfig() with pathInfo set to NULL to fetch pathInfoCount. +//! Second Pass: Allocate memory for pathInfo with respect to the number of pathInfoCount(from First Pass) to fetch +//! targetInfoCount. If sourceModeInfo is needed allocate memory or it can be initialized to NULL. +//! Third Pass(Optional, only required if target information is required): Allocate memory for targetInfo with respect +//! to number of targetInfoCount(from Second Pass). +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in,out] pathInfoCount Number of elements in pathInfo array, returns number of valid topologies, this cannot be null. +//! \param [in,out] pathInfo Array of path information +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. If there are return error codes with +//! specific meaning for this API, they are listed below. +//! +//! \retval NVAPI_INVALID_ARGUMENT - Invalid input parameter. Following can be the reason for this return value: +//! -# pathInfoCount is NULL. +//! -# *pathInfoCount is 0 and pathInfo is not NULL. +//! -# *pathInfoCount is not 0 and pathInfo is NULL. +//! \retval NVAPI_DEVICE_BUSY - ModeSet has not yet completed. Please wait and call it again. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_GetDisplayConfig(_Inout_ NvU32 *pathInfoCount, __out_ecount_full_opt(*pathInfoCount) NV_DISPLAYCONFIG_PATH_INFO *pathInfo); + + + + +/////////////////////////////////////////////////////////////////////////////// +// FUNCTION NAME: NvAPI_DISP_SetDisplayConfig +// +// +//! DESCRIPTION: This API lets caller apply a global display configuration +//! across multiple GPUs. +//! +//! If all sourceIds are zero, then NvAPI will pick up sourceId's based on the following criteria : +//! - If user provides sourceModeInfo then we are trying to assign 0th sourceId always to GDIPrimary. +//! This is needed since active windows always moves along with 0th sourceId. +//! - For rest of the paths, we are incrementally assigning the sourceId per adapter basis. +//! - If user doesn't provide sourceModeInfo then NVAPI just picks up some default sourceId's in incremental order. +//! Note : NVAPI will not intelligently choose the sourceIDs for any configs that does not need a modeset. +//! +//! SUPPORTED OS: Windows Vista and higher +//! +//! +//! \param [in] pathInfoCount Number of supplied elements in pathInfo +//! \param [in] pathInfo Array of path information +//! \param [in] flags Flags for applying settings +//! +//! \retval ::NVAPI_OK - completed request +//! \retval ::NVAPI_API_NOT_INTIALIZED - NVAPI not initialized +//! \retval ::NVAPI_ERROR - miscellaneous error occurred +//! \retval ::NVAPI_INVALID_ARGUMENT - Invalid input parameter. +//! +//! \ingroup dispcontrol +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_DISP_SetDisplayConfig(_In_ NvU32 pathInfoCount, __in_ecount(pathInfoCount) NV_DISPLAYCONFIG_PATH_INFO* pathInfo, _In_ NvU32 flags); + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +// MOSAIC allows a multi display target output scanout on a single source. +// +// SAMPLE of MOSAIC 1x4 topo with 8 pixel horizontal overlap +// +//+-------------------------++-------------------------++-------------------------++-------------------------+ +//| || || || | +//| || || || | +//| || || || | +//| DVI1 || DVI2 || DVI3 || DVI4 | +//| || || || | +//| || || || | +//| || || || | +//| || || || | +//+-------------------------++-------------------------++-------------------------++-------------------------+ + + +//! \addtogroup mosaicapi +//! @{ + +#define NVAPI_MAX_MOSAIC_DISPLAY_ROWS 8 +#define NVAPI_MAX_MOSAIC_DISPLAY_COLUMNS 8 +// +// These bits are used to describe the validity of a topo. +// +#define NV_MOSAIC_TOPO_VALIDITY_VALID 0x00000000 //!< The topology is valid +#define NV_MOSAIC_TOPO_VALIDITY_MISSING_GPU 0x00000001 //!< Not enough SLI GPUs were found to fill the entire + //! topology. hPhysicalGPU will be 0 for these. +#define NV_MOSAIC_TOPO_VALIDITY_MISSING_DISPLAY 0x00000002 //!< Not enough displays were found to fill the entire + //! topology. displayOutputId will be 0 for these. +#define NV_MOSAIC_TOPO_VALIDITY_MIXED_DISPLAY_TYPES 0x00000004 //!< The topoogy is only possible with displays of the same + //! NV_GPU_OUTPUT_TYPE. Check displayOutputIds to make + //! sure they are all CRTs, or all DFPs. + + +// +//! This structure defines the topology details. +typedef struct +{ + NvU32 version; //!< Version of this structure + NvLogicalGpuHandle hLogicalGPU; //!< Logical GPU for this topology + NvU32 validityMask; //!< 0 means topology is valid with the current hardware. + //! If not 0, inspect bits against NV_MOSAIC_TOPO_VALIDITY_*. + NvU32 rowCount; //!< Number of displays in a row + NvU32 colCount; //!< Number of displays in a column + + struct + { + NvPhysicalGpuHandle hPhysicalGPU; //!< Physical GPU to be used in the topology (0 if GPU missing) + NvU32 displayOutputId; //!< Connected display target (0 if no display connected) + NvS32 overlapX; //!< Pixels of overlap on left of target: (+overlap, -gap) + NvS32 overlapY; //!< Pixels of overlap on top of target: (+overlap, -gap) + + } gpuLayout[NVAPI_MAX_MOSAIC_DISPLAY_ROWS][NVAPI_MAX_MOSAIC_DISPLAY_COLUMNS]; + +} NV_MOSAIC_TOPO_DETAILS; + +//! Macro for constructing te vesion field of NV_MOSAIC_TOPO_DETAILS +#define NVAPI_MOSAIC_TOPO_DETAILS_VER MAKE_NVAPI_VERSION(NV_MOSAIC_TOPO_DETAILS,1) + + +// +//! These values refer to the different types of Mosaic topologies that are possible. When +//! getting the supported Mosaic topologies, you can specify one of these types to narrow down +//! the returned list to only those that match the given type. +typedef enum +{ + NV_MOSAIC_TOPO_TYPE_ALL, //!< All mosaic topologies + NV_MOSAIC_TOPO_TYPE_BASIC, //!< Basic Mosaic topologies + NV_MOSAIC_TOPO_TYPE_PASSIVE_STEREO, //!< Passive Stereo topologies + NV_MOSAIC_TOPO_TYPE_SCALED_CLONE, //!< Not supported at this time + NV_MOSAIC_TOPO_TYPE_PASSIVE_STEREO_SCALED_CLONE, //!< Not supported at this time + NV_MOSAIC_TOPO_TYPE_MAX, //!< Always leave this at end of the enum +} NV_MOSAIC_TOPO_TYPE; + + +// +//! This is a complete list of supported Mosaic topologies. +//! +//! Using a "Basic" topology combines multiple monitors to create a single desktop. +//! +//! Using a "Passive" topology combines multiples monitors to create a passive stereo desktop. +//! In passive stereo, two identical topologies combine - one topology is used for the right eye and the other identical //! topology (targeting different displays) is used for the left eye. \n +//! NOTE: common\inc\nvEscDef.h shadows a couple PASSIVE_STEREO enums. If this +//! enum list changes and effects the value of NV_MOSAIC_TOPO_BEGIN_PASSIVE_STEREO +//! please update the corresponding value in nvEscDef.h +typedef enum +{ + NV_MOSAIC_TOPO_NONE, + + // 'BASIC' topos start here + // + // The result of using one of these Mosaic topos is that multiple monitors + // will combine to create a single desktop. + // + NV_MOSAIC_TOPO_BEGIN_BASIC, + NV_MOSAIC_TOPO_1x2_BASIC = NV_MOSAIC_TOPO_BEGIN_BASIC, + NV_MOSAIC_TOPO_2x1_BASIC, + NV_MOSAIC_TOPO_1x3_BASIC, + NV_MOSAIC_TOPO_3x1_BASIC, + NV_MOSAIC_TOPO_1x4_BASIC, + NV_MOSAIC_TOPO_4x1_BASIC, + NV_MOSAIC_TOPO_2x2_BASIC, + NV_MOSAIC_TOPO_2x3_BASIC, + NV_MOSAIC_TOPO_2x4_BASIC, + NV_MOSAIC_TOPO_3x2_BASIC, + NV_MOSAIC_TOPO_4x2_BASIC, + NV_MOSAIC_TOPO_1x5_BASIC, + NV_MOSAIC_TOPO_1x6_BASIC, + NV_MOSAIC_TOPO_7x1_BASIC, + + // Add padding for 10 more entries. 6 will be enough room to specify every + // possible topology with 8 or fewer displays, so this gives us a little + // extra should we need it. + NV_MOSAIC_TOPO_END_BASIC = NV_MOSAIC_TOPO_7x1_BASIC + 9, + + // 'PASSIVE_STEREO' topos start here + // + // The result of using one of these Mosaic topos is that multiple monitors + // will combine to create a single PASSIVE STEREO desktop. What this means is + // that there will be two topos that combine to create the overall desktop. + // One topo will be used for the left eye, and the other topo (of the + // same rows x cols), will be used for the right eye. The difference between + // the two topos is that different GPUs and displays will be used. + // + NV_MOSAIC_TOPO_BEGIN_PASSIVE_STEREO, // value shadowed in nvEscDef.h + NV_MOSAIC_TOPO_1x2_PASSIVE_STEREO = NV_MOSAIC_TOPO_BEGIN_PASSIVE_STEREO, + NV_MOSAIC_TOPO_2x1_PASSIVE_STEREO, + NV_MOSAIC_TOPO_1x3_PASSIVE_STEREO, + NV_MOSAIC_TOPO_3x1_PASSIVE_STEREO, + NV_MOSAIC_TOPO_1x4_PASSIVE_STEREO, + NV_MOSAIC_TOPO_4x1_PASSIVE_STEREO, + NV_MOSAIC_TOPO_2x2_PASSIVE_STEREO, + NV_MOSAIC_TOPO_END_PASSIVE_STEREO = NV_MOSAIC_TOPO_2x2_PASSIVE_STEREO + 4, + + + // + // Total number of topos. Always leave this at the end of the enumeration. + // + NV_MOSAIC_TOPO_MAX //! Total number of topologies. + +} NV_MOSAIC_TOPO; + + +// +//! This is a "topology brief" structure. It tells you what you need to know about +//! a topology at a high level. A list of these is returned when you query for the +//! supported Mosaic information. +//! +//! If you need more detailed information about the topology, call +//! NvAPI_Mosaic_GetTopoGroup() with the topology value from this structure. +typedef struct +{ + NvU32 version; //!< Version of this structure + NV_MOSAIC_TOPO topo; //!< The topology + NvU32 enabled; //!< 1 if topo is enabled, else 0 + NvU32 isPossible; //!< 1 if topo *can* be enabled, else 0 + +} NV_MOSAIC_TOPO_BRIEF; + +//! Macro for constructing the version field of NV_MOSAIC_TOPO_BRIEF +#define NVAPI_MOSAIC_TOPO_BRIEF_VER MAKE_NVAPI_VERSION(NV_MOSAIC_TOPO_BRIEF,1) + + +// +//! Basic per-display settings that are used in setting/getting the Mosaic mode +typedef struct +{ + NvU32 version; //!< Version of this structure + NvU32 width; //!< Per-display width + NvU32 height; //!< Per-display height + NvU32 bpp; //!< Bits per pixel + NvU32 freq; //!< Display frequency +} NV_MOSAIC_DISPLAY_SETTING; + +//! Macro for constructing the version field of NV_MOSAIC_DISPLAY_SETTING +#define NVAPI_MOSAIC_DISPLAY_SETTING_VER MAKE_NVAPI_VERSION(NV_MOSAIC_DISPLAY_SETTING,1) + + +// +// Set a reasonable max number of display settings to support +// so arrays are bound. +// +#define NV_MOSAIC_DISPLAY_SETTINGS_MAX 40 //!< Set a reasonable maximum number of display settings to support + //! so arrays are bound. + + +// +//! This structure is used to contain a list of supported Mosaic topologies +//! along with the display settings that can be used. +typedef struct +{ + NvU32 version; //!< Version of this structure + NvU32 topoBriefsCount; //!< Number of topologies in below array + NV_MOSAIC_TOPO_BRIEF topoBriefs[NV_MOSAIC_TOPO_MAX]; //!< List of supported topologies with only brief details + NvU32 displaySettingsCount; //!< Number of display settings in below array + NV_MOSAIC_DISPLAY_SETTING displaySettings[NV_MOSAIC_DISPLAY_SETTINGS_MAX]; //!< List of per display settings possible + +} NV_MOSAIC_SUPPORTED_TOPO_INFO; + +//! Macro forconstructing the version field of NV_MOSAIC_SUPPORTED_TOPO_INFO +#define NVAPI_MOSAIC_SUPPORTED_TOPO_INFO_VER MAKE_NVAPI_VERSION(NV_MOSAIC_SUPPORTED_TOPO_INFO,1) + + +// +// Indices to use to access the topos array within the mosaic topology +#define NV_MOSAIC_TOPO_IDX_DEFAULT 0 + +#define NV_MOSAIC_TOPO_IDX_LEFT_EYE 0 +#define NV_MOSAIC_TOPO_IDX_RIGHT_EYE 1 +#define NV_MOSAIC_TOPO_NUM_EYES 2 + + +// +//! This defines the maximum number of topos that can be in a topo group. +//! At this time, it is set to 2 because our largest topo group (passive +//! stereo) only needs 2 topos (left eye and right eye). +//! +//! If a new topo group with more than 2 topos is added above, then this +//! number will also have to be incremented. +#define NV_MOSAIC_MAX_TOPO_PER_TOPO_GROUP 2 + + +// +//! This structure defines a group of topologies that work together to create one +//! overall layout. All of the supported topologies are represented with this +//! structure. +//! +//! For example, a 'Passive Stereo' topology would be represented with this +//! structure, and would have separate topology details for the left and right eyes. +//! The count would be 2. A 'Basic' topology is also represented by this structure, +//! with a count of 1. +//! +//! The structure is primarily used internally, but is exposed to applications in a +//! read-only fashion because there are some details in it that might be useful +//! (like the number of rows/cols, or connected display information). A user can +//! get the filled-in structure by calling NvAPI_Mosaic_GetTopoGroup(). +//! +//! You can then look at the detailed values within the structure. There are no +//! entrypoints which take this structure as input (effectively making it read-only). +typedef struct +{ + NvU32 version; //!< Version of this structure + NV_MOSAIC_TOPO_BRIEF brief; //!< The brief details of this topo + NvU32 count; //!< Number of topos in array below + NV_MOSAIC_TOPO_DETAILS topos[NV_MOSAIC_MAX_TOPO_PER_TOPO_GROUP]; + +} NV_MOSAIC_TOPO_GROUP; + +//! Macro for constructing the version field of NV_MOSAIC_TOPO_GROUP +#define NVAPI_MOSAIC_TOPO_GROUP_VER MAKE_NVAPI_VERSION(NV_MOSAIC_TOPO_GROUP,1) + +//! @} + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Mosaic_GetSupportedTopoInfo +// +//! DESCRIPTION: This API returns information on the topologies and display resolutions +//! supported by Mosaic mode. +//! +//! NOTE: Not all topologies returned can be set immediately. +//! See 'OUT' Notes below. +//! +//! Once you get the list of supported topologies, you can call +//! NvAPI_Mosaic_GetTopoGroup() with one of the Mosaic topologies if you need +//! more information about it. +//! +//! 'IN' Notes: pSupportedTopoInfo->version must be set before calling this function. +//! If the specified version is not supported by this implementation, +//! an error will be returned (NVAPI_INCOMPATIBLE_STRUCT_VERSION). +//! +//! 'OUT' Notes: Some of the topologies returned might not be valid for one reason or +//! another. It could be due to mismatched or missing displays. It +//! could also be because the required number of GPUs is not found. +//! At a high level, you can see if the topology is valid and can be enabled +//! by looking at the pSupportedTopoInfo->topoBriefs[xxx].isPossible flag. +//! If this is true, the topology can be enabled. If it +//! is false, you can find out why it cannot be enabled by getting the +//! details of the topology via NvAPI_Mosaic_GetTopoGroup(). From there, +//! look at the validityMask of the individual topologies. The bits can +//! be tested against the NV_MOSAIC_TOPO_VALIDITY_* bits. +//! +//! It is possible for this function to return NVAPI_OK with no topologies +//! listed in the return structure. If this is the case, it means that +//! the current hardware DOES support Mosaic, but with the given configuration +//! no valid topologies were found. This most likely means that SLI was not +//! enabled for the hardware. Once enabled, you should see valid topologies +//! returned from this function. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! +//! \param [in,out] pSupportedTopoInfo Information about what topologies and display resolutions +//! are supported for Mosaic. +//! \param [in] type The type of topologies the caller is interested in +//! getting. See NV_MOSAIC_TOPO_TYPE for possible values. +//! +//! \retval ::NVAPI_OK No errors in returning supported topologies. +//! \retval ::NVAPI_NOT_SUPPORTED Mosaic is not supported with the existing hardware. +//! \retval ::NVAPI_INVALID_ARGUMENT One or more arguments passed in are invalid. +//! \retval ::NVAPI_API_NOT_INTIALIZED The NvAPI API needs to be initialized first. +//! \retval ::NVAPI_NO_IMPLEMENTATION This entrypoint not available. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the structure passed in is not +// compatible with this entry point. +//! \retval ::NVAPI_ERROR: Miscellaneous error occurred. +//! +//! \ingroup mosaicapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Mosaic_GetSupportedTopoInfo(NV_MOSAIC_SUPPORTED_TOPO_INFO *pSupportedTopoInfo, NV_MOSAIC_TOPO_TYPE type); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Mosaic_GetTopoGroup +// +//! DESCRIPTION: This API returns a structure filled with the details +//! of the specified Mosaic topology. +//! +//! If the pTopoBrief passed in matches the current topology, +//! then information in the brief and group structures +//! will reflect what is current. Thus the brief would have +//! the current 'enable' status, and the group would have the +//! current overlap values. If there is no match, then the +//! returned brief has an 'enable' status of FALSE (since it +//! is obviously not enabled), and the overlap values will be 0. +//! +//! 'IN' Notes: pTopoGroup->version must be set before calling this function. +//! If the specified version is not supported by this implementation, +//! an error will be returned (NVAPI_INCOMPATIBLE_STRUCT_VERSION). +//! +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! \param [in] pTopoBrief The topology for getting the details +//! This must be one of the topology briefs +//! returned from NvAPI_Mosaic_GetSupportedTopoInfo(). +//! \param [in,out] pTopoGroup The topology details matching the brief +//! +//! \retval ::NVAPI_OK Details were retrieved successfully. +//! \retval ::NVAPI_NOT_SUPPORTED Mosaic is not supported with the existing hardware. +//! \retval ::NVAPI_INVALID_ARGUMENT One or more argumentss passed in are invalid. +//! \retval ::NVAPI_API_NOT_INTIALIZED The NvAPI API needs to be initialized first. +//! \retval ::NVAPI_NO_IMPLEMENTATION This entrypoint not available. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the structure passed in is not +// compatible with this entry point. +//! \retval ::NVAPI_ERROR: Miscellaneous error occurred. +//! +//! \ingroup mosaicapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Mosaic_GetTopoGroup(NV_MOSAIC_TOPO_BRIEF *pTopoBrief, NV_MOSAIC_TOPO_GROUP *pTopoGroup); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Mosaic_GetOverlapLimits +// +//! DESCRIPTION: This API returns the X and Y overlap limits required if +//! the given Mosaic topology and display settings are to be used. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! \param [in] pTopoBrief The topology for getting limits +//! This must be one of the topo briefs +//! returned from NvAPI_Mosaic_GetSupportedTopoInfo(). +//! \param [in] pDisplaySetting The display settings for getting the limits. +//! This must be one of the settings +//! returned from NvAPI_Mosaic_GetSupportedTopoInfo(). +//! \param [out] pMinOverlapX X overlap minimum +//! \param [out] pMaxOverlapX X overlap maximum +//! \param [out] pMinOverlapY Y overlap minimum +//! \param [out] pMaxOverlapY Y overlap maximum +//! +//! \retval ::NVAPI_OK Details were retrieved successfully. +//! \retval ::NVAPI_NOT_SUPPORTED Mosaic is not supported with the existing hardware. +//! \retval ::NVAPI_INVALID_ARGUMENT One or more argumentss passed in are invalid. +//! \retval ::NVAPI_API_NOT_INTIALIZED The NvAPI API needs to be initialized first. +//! \retval ::NVAPI_NO_IMPLEMENTATION This entrypoint not available. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the structure passed in is not +//! compatible with this entry point. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup mosaicapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Mosaic_GetOverlapLimits(NV_MOSAIC_TOPO_BRIEF *pTopoBrief, NV_MOSAIC_DISPLAY_SETTING *pDisplaySetting, NvS32 *pMinOverlapX, NvS32 *pMaxOverlapX, NvS32 *pMinOverlapY, NvS32 *pMaxOverlapY); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Mosaic_SetCurrentTopo +// +//! DESCRIPTION: This API sets the Mosaic topology and performs a mode switch +//! using the given display settings. +//! +//! If NVAPI_OK is returned, the current Mosaic topology was set +//! correctly. Any other status returned means the +//! topology was not set, and remains what it was before this +//! function was called. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! \param [in] pTopoBrief The topology to set. This must be one of the topologies returned from +//! NvAPI_Mosaic_GetSupportedTopoInfo(), and it must have an isPossible value of 1. +//! \param [in] pDisplaySetting The per display settings to be used in the Mosaic mode. This must be one of the +//! settings returned from NvAPI_Mosaic_GetSupportedTopoInfo(). +//! \param [in] overlapX The pixel overlap to use between horizontal displays (use positive a number for +//! overlap, or a negative number to create a gap.) If the overlap is out of bounds +//! for what is possible given the topo and display setting, the overlap will be clamped. +//! \param [in] overlapY The pixel overlap to use between vertical displays (use positive a number for +//! overlap, or a negative number to create a gap.) If the overlap is out of bounds for +//! what is possible given the topo and display setting, the overlap will be clamped. +//! \param [in] enable If 1, the topology being set will also be enabled, meaning that the mode set will +//! occur. \n +//! If 0, you don't want to be in Mosaic mode right now, but want to set the current +//! Mosaic topology so you can enable it later with NvAPI_Mosaic_EnableCurrentTopo(). +//! +//! \retval ::NVAPI_OK The Mosaic topology was set. +//! \retval ::NVAPI_NOT_SUPPORTED Mosaic is not supported with the existing hardware. +//! \retval ::NVAPI_INVALID_ARGUMENT One or more argumentss passed in are invalid. +//! \retval ::NVAPI_TOPO_NOT_POSSIBLE The topology passed in is not currently possible. +//! \retval ::NVAPI_API_NOT_INTIALIZED The NvAPI API needs to be initialized first. +//! \retval ::NVAPI_NO_IMPLEMENTATION This entrypoint not available. +//! \retval ::NVAPI_INCOMPATIBLE_STRUCT_VERSION The version of the structure passed in is not +//! compatible with this entrypoint. +//! \retval ::NVAPI_MODE_CHANGE_FAILED There was an error changing the display mode. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup mosaicapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Mosaic_SetCurrentTopo(NV_MOSAIC_TOPO_BRIEF *pTopoBrief, NV_MOSAIC_DISPLAY_SETTING *pDisplaySetting, NvS32 overlapX, NvS32 overlapY, NvU32 enable); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_Mosaic_GetCurrentTopo +// +//! DESCRIPTION: This API returns information for the current Mosaic topology. +//! This includes topology, display settings, and overlap values. +//! +//! You can call NvAPI_Mosaic_GetTopoGroup() with the topology +//! if you require more information. +//! +//! If there isn't a current topology, then pTopoBrief->topo will +//! be NV_MOSAIC_TOPO_NONE. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 185 +//! +//! \param [out] pTopoBrief The current Mosaic topology +//! \param [out] pDisplaySetting The current per-display settings +//! \param [out] pOverlapX The pixel overlap between horizontal displays +//! \param [out] pOverlapY The pixel overlap between vertical displays +//! +//! \retval ::NVAPI_OK Success getting current info. +//! \retval ::NVAPI_NOT_SUPPORTED Mosaic is not supported with the existing hardware. +//! \retval ::NVAPI_INVALID_ARGUMENT One or more argumentss passed in are invalid. +//! \retval ::NVAPI_API_NOT_INTIALIZED The NvAPI API needs to be initialized first. +//! \retval ::NVAPI_NO_IMPLEMENTATION This entry point not available. +//! \retval ::NVAPI_ERROR Miscellaneous error occurred. +//! +//! \ingroup mosaicapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_Mosaic_GetCurrentTopo(NV_MOSAIC_TOPO_BRIEF *pTopoBrief, NV_MOSAIC_DISPLAY_SETTING *pDisplaySetting, NvS32 *pOverlapX, NvS32 *pOverlapY); + + +#define NVAPI_MAX_GSYNC_DEVICES 4 + + +// Sync Display APIs + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_EnumSyncDevices +// +//! DESCRIPTION: This API returns an array of Sync device handles. A Sync device handle represents a +//! single Sync device on the system. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [out] nvGSyncHandles- The caller provides an array of handles, which must contain at least +//! NVAPI_MAX_GSYNC_DEVICES elements. The API will zero out the entire array and then fill in one +//! or more handles. If an error occurs, the array is invalid. +//! \param [out] *gsyncCount- The caller provides the storage space. NvAPI_GSync_EnumSyncDevices +//! sets *gsyncCount to indicate how many of the elements in the nvGSyncHandles[] array are valid. +//! If an error occurs, *gsyncCount will be set to zero. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT nvGSyncHandles or gsyncCount is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_EnumSyncDevices(_Out_ NvGSyncDeviceHandle nvGSyncHandles[NVAPI_MAX_GSYNC_DEVICES], _Out_ NvU32 *gsyncCount); + + + +// GSync boardId values +#define NVAPI_GSYNC_BOARD_ID_P358 856 //!< GSync board ID 0x358, see NV_GSYNC_CAPABILITIES +#define NVAPI_GSYNC_BOARD_ID_P2060 8288 //!< GSync board ID 0x2060, see NV_GSYNC_CAPABILITIES + + +//! Used in NvAPI_GSync_QueryCapabilities(). +typedef struct _NV_GSYNC_CAPABILITIES +{ + NvU32 version; //!< Version of the structure + NvU32 boardId; //!< Board ID + NvU32 revision; //!< FPGA Revision + NvU32 capFlags; //!< Capabilities of the Sync board. Reserved for future use +} NV_GSYNC_CAPABILITIES; + + + +//! \ingroup gsyncapi +//! Macro for constructing the version field of NV_GSYNC_CAPABILITIES. +#define NV_GSYNC_CAPABILITIES_VER MAKE_NVAPI_VERSION(NV_GSYNC_CAPABILITIES,1) + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_QueryCapabilities +// +//! DESCRIPTION: This API returns the capabilities of the Sync device. +//! +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice- The handle for a Sync device for which the capabilities will be queried. +//! \param [inout] *pNvGSyncCapabilities- The caller provides the storage space. NvAPI_GSync_QueryCapabilities() sets +//! *pNvGSyncCapabilities to the version and capabilities details of the Sync device +//! If an error occurs, *pNvGSyncCapabilities will be set to NULL. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_QueryCapabilities(_In_ NvGSyncDeviceHandle hNvGSyncDevice, _Inout_ NV_GSYNC_CAPABILITIES *pNvGSyncCapabilities); + + + +//! Connector values for a GPU. Used in NV_GSYNC_GPU. +typedef enum _NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR +{ + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR_NONE = 0, + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR_PRIMARY = 1, + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR_SECONDARY = 2, + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR_TERTIARY = 3, + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR_QUARTERNARY = 4, +} NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR; + +//! Display sync states. Used in NV_GSYNC_DISPLAY. +typedef enum _NVAPI_GSYNC_DISPLAY_SYNC_STATE +{ + NVAPI_GSYNC_DISPLAY_SYNC_STATE_UNSYNCED = 0, + NVAPI_GSYNC_DISPLAY_SYNC_STATE_SLAVE = 1, + NVAPI_GSYNC_DISPLAY_SYNC_STATE_MASTER = 2, +} NVAPI_GSYNC_DISPLAY_SYNC_STATE; + +typedef struct _NV_GSYNC_GPU +{ + NvU32 version; //!< Version of the structure + NvPhysicalGpuHandle hPhysicalGpu; //!< GPU handle + NVAPI_GSYNC_GPU_TOPOLOGY_CONNECTOR connector; //!< Indicates which connector on the device the GPU is connected to. + NvPhysicalGpuHandle hProxyPhysicalGpu; //!< GPU through which hPhysicalGpu is connected to the Sync device (if not directly connected) + //!< - this is NULL otherwise + NvU32 isSynced : 1; //!< Whether this GPU is sync'd or not. + NvU32 reserved : 31; //!< Should be set to ZERO +} NV_GSYNC_GPU; + +typedef struct _NV_GSYNC_DISPLAY +{ + NvU32 version; //!< Version of the structure + NvU32 displayId; //!< display identifier for displays.The GPU to which it is connected, can be retireved from NvAPI_SYS_GetPhysicalGpuFromDisplayId + NvU32 isMasterable : 1; //!< Can this display be the master? (Read only) + NvU32 reserved : 31; //!< Should be set to ZERO + NVAPI_GSYNC_DISPLAY_SYNC_STATE syncState; //!< Is this display slave/master + //!< (Retrieved with topology or set by caller for enable/disable sync) +} NV_GSYNC_DISPLAY; + +#define NV_GSYNC_DISPLAY_VER MAKE_NVAPI_VERSION(NV_GSYNC_DISPLAY,1) +#define NV_GSYNC_GPU_VER MAKE_NVAPI_VERSION(NV_GSYNC_GPU,1) + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_GetTopology +// +//! DESCRIPTION: This API returns the topology for the specified Sync device. +//! +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice- The caller provides the handle for a Sync device for which the topology will be queried. +//! \param [in, out] gsyncGpuCount- It returns number of GPUs connected to Sync device +//! \param [in, out] gsyncGPUs- It returns info about GPUs connected to Sync device +//! \param [in, out] gsyncDisplayCount- It returns number of active displays that belongs to Sync device +//! \param [in, out] gsyncDisplays- It returns info about all active displays that belongs to Sync device +//! +//! HOW TO USE: 1) make a call to get the number of GPUs connected OR displays synced through Sync device +//! by passing the gsyncGPUs OR gsyncDisplays as NULL respectively. Both gsyncGpuCount and gsyncDisplayCount can be retrieved in same call by passing +//! both gsyncGPUs and gsyncDisplays as NULL +//! On call success: +//! 2) Allocate memory based on gsyncGpuCount(for gsyncGPUs) and/or gsyncDisplayCount(for gsyncDisplays) then make a call to populate gsyncGPUs and/or gsyncDisplays respectively. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! \retval ::NVAPI_INSUFFICIENT_BUFFER When the actual number of GPUs/displays in the topology exceed the number of elements allocated for SyncGPUs/SyncDisplays respectively. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_GetTopology(_In_ NvGSyncDeviceHandle hNvGSyncDevice, __inout_opt NvU32 *gsyncGpuCount, __inout_ecount_part_opt(*gsyncGpuCount, *gsyncGpuCount) NV_GSYNC_GPU *gsyncGPUs, + __inout_opt NvU32 *gsyncDisplayCount, __inout_ecount_part_opt(*gsyncDisplayCount, *gsyncDisplayCount) NV_GSYNC_DISPLAY *gsyncDisplays); + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_SetSyncStateSettings +// +//! DESCRIPTION: Sets a new sync state for the displays in system. +//! +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] gsyncDisplayCount- The number of displays in gsyncDisplays. +//! \param [in] pGsyncDisplays- The caller provides the structure containing all displays that need to be synchronized in the system. +//! The displays that are not part of pGsyncDisplays, will be un-synchronized. +//! \param [in] flags- Reserved for future use. +//! +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT If the display topology or count not valid. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! \retval ::NVAPI_INVALID_SYNC_TOPOLOGY 1.If any mosaic grid is partial. +//! 2.If timing(HVisible/VVisible/refreshRate) applied of any display is different. +//! 3.If There is a across GPU mosaic grid in system and that is not a part of pGsyncDisplays. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_SetSyncStateSettings(_In_ NvU32 gsyncDisplayCount, __in_ecount(gsyncDisplayCount) NV_GSYNC_DISPLAY *pGsyncDisplays, _In_ NvU32 flags); + + +//! \ingroup gsyncapi + +//! Source signal edge to be used for output pulse. See NV_GSYNC_CONTROL_PARAMS. +typedef enum _NVAPI_GSYNC_POLARITY +{ + NVAPI_GSYNC_POLARITY_RISING_EDGE = 0, + NVAPI_GSYNC_POLARITY_FALLING_EDGE = 1, + NVAPI_GSYNC_POLARITY_BOTH_EDGES = 2, +} NVAPI_GSYNC_POLARITY; + +//! Used in NV_GSYNC_CONTROL_PARAMS. +typedef enum _NVAPI_GSYNC_VIDEO_MODE +{ + NVAPI_GSYNC_VIDEO_MODE_NONE = 0, + NVAPI_GSYNC_VIDEO_MODE_TTL = 1, + NVAPI_GSYNC_VIDEO_MODE_NTSCPALSECAM = 2, + NVAPI_GSYNC_VIDEO_MODE_HDTV = 3, + NVAPI_GSYNC_VIDEO_MODE_COMPOSITE = 4, +} NVAPI_GSYNC_VIDEO_MODE; + +//! Used in NV_GSYNC_CONTROL_PARAMS. +typedef enum _NVAPI_GSYNC_SYNC_SOURCE +{ + NVAPI_GSYNC_SYNC_SOURCE_VSYNC = 0, + NVAPI_GSYNC_SYNC_SOURCE_HOUSESYNC = 1, +} NVAPI_GSYNC_SYNC_SOURCE; + +//! Used in NV_GSYNC_CONTROL_PARAMS. +typedef struct _NV_GSYNC_DELAY +{ + NvU32 version; //!< Version of the structure + NvU32 numLines; //!< delay to be induced in number of horizontal lines. + NvU32 numPixels; //!< delay to be induced in number of pixels. + NvU32 maxLines; //!< maximum number of lines supported at current display mode to induce delay. Updated by NvAPI_GSync_GetControlParameters(). Read only. + NvU32 minPixels; //!< minimum number of pixels required at current display mode to induce delay. Updated by NvAPI_GSync_GetControlParameters(). Read only. +} NV_GSYNC_DELAY; + +#define NV_GSYNC_DELAY_VER MAKE_NVAPI_VERSION(NV_GSYNC_DELAY,1) + +//! Used in NvAPI_GSync_GetControlParameters() and NvAPI_GSync_SetControlParameters(). +typedef struct _NV_GSYNC_CONTROL_PARAMS +{ + NvU32 version; //!< Version of the structure + NVAPI_GSYNC_POLARITY polarity; //!< Leading edge / Falling edge / both + NVAPI_GSYNC_VIDEO_MODE vmode; //!< None, TTL, NTSCPALSECAM, HDTV + NvU32 interval; //!< Number of pulses to wait between framelock signal generation + NVAPI_GSYNC_SYNC_SOURCE source; //!< VSync/House sync + NvU32 interlaceMode:1; //!< interlace mode for a Sync device + NvU32 reserved:31; //!< should be set zero + NV_GSYNC_DELAY syncSkew; //!< The time delay between the frame sync signal and the GPUs signal. + NV_GSYNC_DELAY startupDelay; //!< Sync start delay for master. +} NV_GSYNC_CONTROL_PARAMS; + +#define NV_GSYNC_CONTROL_PARAMS_VER MAKE_NVAPI_VERSION(NV_GSYNC_CONTROL_PARAMS,1) + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_GetControlParameters +// +//! DESCRIPTION: This API queries for sync control parameters as defined in NV_GSYNC_CONTROL_PARAMS. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice- The caller provides the handle of the Sync device for which to get parameters +//! \param [inout] *pGsyncControls- The caller provides the storage space. NvAPI_GSync_GetControlParameters() populates *pGsyncControls with values. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_GetControlParameters(_In_ NvGSyncDeviceHandle hNvGSyncDevice, _Inout_ NV_GSYNC_CONTROL_PARAMS *pGsyncControls); + + + +////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_SetControlParameters +// +//! DESCRIPTION: This API sets control parameters as defined in NV_SYNC_CONTROL_PARAMS. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice- The caller provides the handle of the Sync device for which to get parameters +//! \param [inout] *pGsyncControls- The caller provides NV_GSYNC_CONTROL_PARAMS. skew and startDelay will be updated to the applied values. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any Sync Device. +//! \retval ::NVAPI_SYNC_MASTER_NOT_FOUND Control Parameters can only be set if there is a Sync Master enabled on the Gsync card. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_SetControlParameters(_In_ NvGSyncDeviceHandle hNvGSyncDevice, _Inout_ NV_GSYNC_CONTROL_PARAMS *pGsyncControls); + + + + +//! Used in NvAPI_GSync_AdjustSyncDelay() +typedef enum _NVAPI_GSYNC_DELAY_TYPE +{ + NVAPI_GSYNC_DELAY_TYPE_UNKNOWN = 0, + NVAPI_GSYNC_DELAY_TYPE_SYNC_SKEW = 1, + NVAPI_GSYNC_DELAY_TYPE_STARTUP = 2 +} NVAPI_GSYNC_DELAY_TYPE; + +////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_AdjustSyncDelay +// +//! DESCRIPTION: This API adjusts the skew and startDelay to the closest possible values. Use this API before calling NvAPI_GSync_SetControlParameters for skew or startDelay. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 319 +//! +//! \param [in] hNvGSyncDevice- The caller provides the handle of the Sync device for which to get parameters +//! \param [in] delayType- Specifies whether the delay is syncSkew or startupDelay. +//! \param [inout] *pGsyncDelay- The caller provides NV_GSYNC_DELAY. skew and startDelay will be adjusted and updated to the closest values. +//! \param [out] *syncSteps- This parameter is optional. It returns the sync delay in unit steps. If 0, it means either the NV_GSYNC_DELAY::numPixels is less than NV_GSYNC_DELAY::minPixels or NV_GSYNC_DELAY::numOfLines exceeds the NV_GSYNC_DELAY::maxLines. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_AdjustSyncDelay(_In_ NvGSyncDeviceHandle hNvGSyncDevice, _In_ NVAPI_GSYNC_DELAY_TYPE delayType, _Inout_ NV_GSYNC_DELAY *pGsyncDelay, _Out_opt_ NvU32* syncSteps); + + + +//! Used in NvAPI_GSync_GetSyncStatus(). +typedef struct _NV_GSYNC_STATUS +{ + NvU32 version; //!< Version of the structure + NvU32 bIsSynced; //!< Is timing in sync? + NvU32 bIsStereoSynced; //!< Does the phase of the timing signal from the GPU = the phase of the master sync signal? + NvU32 bIsSyncSignalAvailable; //!< Is the sync signal available? +} NV_GSYNC_STATUS; + +//! Macro for constructing the version field for NV_GSYNC_STATUS. +#define NV_GSYNC_STATUS_VER MAKE_NVAPI_VERSION(NV_GSYNC_STATUS,1) + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_GetSyncStatus +// +//! DESCRIPTION: This API queries the sync status of a GPU - timing, stereosync and sync signal availability. +//! +//! SUPPORTED OS: Windows 7 and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice- Handle of the Sync device +//! \param [in] hPhysicalGpu- GPU to be queried for sync status. +//! \param [out] *status- The caller provides the storage space. NvAPI_GSync_GetSyncStatus() populates *status with +//! values - timing, stereosync and signal availability. On error, *status is set to NULL. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL / SyncTarget is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any G-Sync Device. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_GetSyncStatus(_In_ NvGSyncDeviceHandle hNvGSyncDevice, _In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_GSYNC_STATUS *status); + + +//! \ingroup gsyncapi + +#define NVAPI_MAX_RJ45_PER_GSYNC 2 + +//! Used in NV_GSYNC_STATUS_PARAMS. +typedef enum _NVAPI_GSYNC_RJ45_IO +{ + NVAPI_GSYNC_RJ45_OUTPUT = 0, + NVAPI_GSYNC_RJ45_INPUT = 1, + NVAPI_GSYNC_RJ45_UNUSED = 2 //!< This field is used to notify that the framelock is not actually present. + +} NVAPI_GSYNC_RJ45_IO; + +//! \ingroup gsyncapi +//! Used in NvAPI_GSync_GetStatusParameters(). +typedef struct _NV_GSYNC_STATUS_PARAMS +{ + NvU32 version; + NvU32 refreshRate; //!< The refresh rate + NVAPI_GSYNC_RJ45_IO RJ45_IO[NVAPI_MAX_RJ45_PER_GSYNC]; //!< Configured as input / output + NvU32 RJ45_Ethernet[NVAPI_MAX_RJ45_PER_GSYNC]; //!< Connected to ethernet hub? [ERRONEOUSLY CONNECTED!] + NvU32 houseSyncIncoming; //!< Incoming house sync frequency in Hz + NvU32 bHouseSync; //!< Is house sync connected? +} NV_GSYNC_STATUS_PARAMS; + + +//! \ingroup gsyncapi +//! Macro for constructing the version field of NV_GSYNC_STATUS_PARAMS +#define NV_GSYNC_STATUS_PARAMS_VER MAKE_NVAPI_VERSION(NV_GSYNC_STATUS_PARAMS,1) + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GSync_GetStatusParameters +// +//! DESCRIPTION: This API queries for sync status parameters as defined in NV_GSYNC_STATUS_PARAMS. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \since Release: 313 +//! +//! \param [in] hNvGSyncDevice The caller provides the handle of the GSync device for which to get parameters +//! \param [out] *pStatusParams The caller provides the storage space. NvAPI_GSync_GetStatusParameters populates *pStatusParams with +//! values. +//! +//! \return This API can return any of the error codes enumerated in #NvAPI_Status. +//! If there are return error codes with specific meaning for this API, they are listed below. +//! \retval ::NVAPI_INVALID_ARGUMENT hNvGSyncDevice is NULL / pStatusParams is NULL. +//! \retval ::NVAPI_NVIDIA_DEVICE_NOT_FOUND The queried Graphics system does not have any GSync Device. +//! +//! \ingroup gsyncapi +/////////////////////////////////////////////////////////////////////////////// +NVAPI_INTERFACE NvAPI_GSync_GetStatusParameters(NvGSyncDeviceHandle hNvGSyncDevice, NV_GSYNC_STATUS_PARAMS *pStatusParams); + +//! @} + +//! SUPPORTED OS: Windows Vista and higher +//! + +///////////////////////////////////////////////////////////////////////// +// Video Input Output (VIO) API +///////////////////////////////////////////////////////////////////////// + +//! \ingroup vidio +//! Unique identifier for VIO owner (process identifier or NVVIOOWNERID_NONE) +typedef NvU32 NVVIOOWNERID; + + +//! \addtogroup vidio +//! @{ + + +#define NVVIOOWNERID_NONE 0 //!< Unregistered ownerId + + +//! Owner type for device +typedef enum _NVVIOOWNERTYPE +{ + NVVIOOWNERTYPE_NONE , //!< No owner for the device + NVVIOOWNERTYPE_APPLICATION , //!< Application owns the device + NVVIOOWNERTYPE_DESKTOP , //!< Desktop transparent mode owns the device (not applicable for video input) +}NVVIOOWNERTYPE; + +// Access rights for NvAPI_VIO_Open() + +//! Read access (not applicable for video output) +#define NVVIO_O_READ 0x00000000 + +//! Write exclusive access (not applicable for video input) +#define NVVIO_O_WRITE_EXCLUSIVE 0x00010001 + +//! +#define NVVIO_VALID_ACCESSRIGHTS (NVVIO_O_READ | \ + NVVIO_O_WRITE_EXCLUSIVE ) + + +//! VIO_DATA.ulOwnerID high-bit is set only if device has been initialized by VIOAPI +//! examined at NvAPI_GetCapabilities|NvAPI_VIO_Open to determine if settings need to be applied from registry or POR state read +#define NVVIO_OWNERID_INITIALIZED 0x80000000 + +//! VIO_DATA.ulOwnerID next-bit is set only if device is currently in exclusive write access mode from NvAPI_VIO_Open() +#define NVVIO_OWNERID_EXCLUSIVE 0x40000000 + +//! VIO_DATA.ulOwnerID lower bits are: +//! NVGVOOWNERTYPE_xxx enumerations indicating use context +#define NVVIO_OWNERID_TYPEMASK 0x0FFFFFFF //!< mask for NVVIOOWNERTYPE_xxx + + +//! @} + +//--------------------------------------------------------------------- +// Enumerations +//--------------------------------------------------------------------- + + +//! \addtogroup vidio +//! @{ + +//! Video signal format and resolution +typedef enum _NVVIOSIGNALFORMAT +{ + NVVIOSIGNALFORMAT_NONE, //!< Invalid signal format + NVVIOSIGNALFORMAT_487I_59_94_SMPTE259_NTSC, //!< 01 487i 59.94Hz (SMPTE259) NTSC + NVVIOSIGNALFORMAT_576I_50_00_SMPTE259_PAL, //!< 02 576i 50.00Hz (SMPTE259) PAL + NVVIOSIGNALFORMAT_1035I_60_00_SMPTE260, //!< 03 1035i 60.00Hz (SMPTE260) + NVVIOSIGNALFORMAT_1035I_59_94_SMPTE260, //!< 04 1035i 59.94Hz (SMPTE260) + NVVIOSIGNALFORMAT_1080I_50_00_SMPTE295, //!< 05 1080i 50.00Hz (SMPTE295) + NVVIOSIGNALFORMAT_1080I_60_00_SMPTE274, //!< 06 1080i 60.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080I_59_94_SMPTE274, //!< 07 1080i 59.94Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080I_50_00_SMPTE274, //!< 08 1080i 50.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080P_30_00_SMPTE274, //!< 09 1080p 30.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080P_29_97_SMPTE274, //!< 10 1080p 29.97Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080P_25_00_SMPTE274, //!< 11 1080p 25.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080P_24_00_SMPTE274, //!< 12 1080p 24.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080P_23_976_SMPTE274, //!< 13 1080p 23.976Hz (SMPTE274) + NVVIOSIGNALFORMAT_720P_60_00_SMPTE296, //!< 14 720p 60.00Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_59_94_SMPTE296, //!< 15 720p 59.94Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_50_00_SMPTE296, //!< 16 720p 50.00Hz (SMPTE296) + NVVIOSIGNALFORMAT_1080I_48_00_SMPTE274, //!< 17 1080I 48.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080I_47_96_SMPTE274, //!< 18 1080I 47.96Hz (SMPTE274) + NVVIOSIGNALFORMAT_720P_30_00_SMPTE296, //!< 19 720p 30.00Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_29_97_SMPTE296, //!< 20 720p 29.97Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_25_00_SMPTE296, //!< 21 720p 25.00Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_24_00_SMPTE296, //!< 22 720p 24.00Hz (SMPTE296) + NVVIOSIGNALFORMAT_720P_23_98_SMPTE296, //!< 23 720p 23.98Hz (SMPTE296) + NVVIOSIGNALFORMAT_2048P_30_00_SMPTE372, //!< 24 2048p 30.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048P_29_97_SMPTE372, //!< 25 2048p 29.97Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048I_60_00_SMPTE372, //!< 26 2048i 60.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048I_59_94_SMPTE372, //!< 27 2048i 59.94Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048P_25_00_SMPTE372, //!< 28 2048p 25.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048I_50_00_SMPTE372, //!< 29 2048i 50.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048P_24_00_SMPTE372, //!< 30 2048p 24.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048P_23_98_SMPTE372, //!< 31 2048p 23.98Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048I_48_00_SMPTE372, //!< 32 2048i 48.00Hz (SMPTE372) + NVVIOSIGNALFORMAT_2048I_47_96_SMPTE372, //!< 33 2048i 47.96Hz (SMPTE372) + + NVVIOSIGNALFORMAT_1080PSF_25_00_SMPTE274, //!< 34 1080PsF 25.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080PSF_29_97_SMPTE274, //!< 35 1080PsF 29.97Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080PSF_30_00_SMPTE274, //!< 36 1080PsF 30.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080PSF_24_00_SMPTE274, //!< 37 1080PsF 24.00Hz (SMPTE274) + NVVIOSIGNALFORMAT_1080PSF_23_98_SMPTE274, //!< 38 1080PsF 23.98Hz (SMPTE274) + + NVVIOSIGNALFORMAT_1080P_50_00_SMPTE274_3G_LEVEL_A, //!< 39 1080P 50.00Hz (SMPTE274) 3G Level A + NVVIOSIGNALFORMAT_1080P_59_94_SMPTE274_3G_LEVEL_A, //!< 40 1080P 59.94Hz (SMPTE274) 3G Level A + NVVIOSIGNALFORMAT_1080P_60_00_SMPTE274_3G_LEVEL_A, //!< 41 1080P 60.00Hz (SMPTE274) 3G Level A + + NVVIOSIGNALFORMAT_1080P_60_00_SMPTE274_3G_LEVEL_B, //!< 42 1080p 60.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_1080I_60_00_SMPTE274_3G_LEVEL_B, //!< 43 1080i 60.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048I_60_00_SMPTE372_3G_LEVEL_B, //!< 44 2048i 60.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_50_00_SMPTE274_3G_LEVEL_B, //!< 45 1080p 50.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_1080I_50_00_SMPTE274_3G_LEVEL_B, //!< 46 1080i 50.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048I_50_00_SMPTE372_3G_LEVEL_B, //!< 47 2048i 50.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_30_00_SMPTE274_3G_LEVEL_B, //!< 48 1080p 30.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048P_30_00_SMPTE372_3G_LEVEL_B, //!< 49 2048p 30.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_25_00_SMPTE274_3G_LEVEL_B, //!< 50 1080p 25.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048P_25_00_SMPTE372_3G_LEVEL_B, //!< 51 2048p 25.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_24_00_SMPTE274_3G_LEVEL_B, //!< 52 1080p 24.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048P_24_00_SMPTE372_3G_LEVEL_B, //!< 53 2048p 24.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080I_48_00_SMPTE274_3G_LEVEL_B, //!< 54 1080i 48.00Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048I_48_00_SMPTE372_3G_LEVEL_B, //!< 55 2048i 48.00Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_59_94_SMPTE274_3G_LEVEL_B, //!< 56 1080p 59.94Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_1080I_59_94_SMPTE274_3G_LEVEL_B, //!< 57 1080i 59.94Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048I_59_94_SMPTE372_3G_LEVEL_B, //!< 58 2048i 59.94Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_29_97_SMPTE274_3G_LEVEL_B, //!< 59 1080p 29.97Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048P_29_97_SMPTE372_3G_LEVEL_B, //!< 60 2048p 29.97Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080P_23_98_SMPTE274_3G_LEVEL_B, //!< 61 1080p 29.98Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048P_23_98_SMPTE372_3G_LEVEL_B, //!< 62 2048p 29.98Hz (SMPTE372) 3G Level B + NVVIOSIGNALFORMAT_1080I_47_96_SMPTE274_3G_LEVEL_B, //!< 63 1080i 47.96Hz (SMPTE274) 3G Level B + NVVIOSIGNALFORMAT_2048I_47_96_SMPTE372_3G_LEVEL_B, //!< 64 2048i 47.96Hz (SMPTE372) 3G Level B + + NVVIOSIGNALFORMAT_END //!< 65 To indicate end of signal format list + +}NVVIOSIGNALFORMAT; + +//! SMPTE standards format +typedef enum _NVVIOVIDEOSTANDARD +{ + NVVIOVIDEOSTANDARD_SMPTE259 , //!< SMPTE259 + NVVIOVIDEOSTANDARD_SMPTE260 , //!< SMPTE260 + NVVIOVIDEOSTANDARD_SMPTE274 , //!< SMPTE274 + NVVIOVIDEOSTANDARD_SMPTE295 , //!< SMPTE295 + NVVIOVIDEOSTANDARD_SMPTE296 , //!< SMPTE296 + NVVIOVIDEOSTANDARD_SMPTE372 , //!< SMPTE372 +}NVVIOVIDEOSTANDARD; + +//! HD or SD video type +typedef enum _NVVIOVIDEOTYPE +{ + NVVIOVIDEOTYPE_SD , //!< Standard-definition (SD) + NVVIOVIDEOTYPE_HD , //!< High-definition (HD) +}NVVIOVIDEOTYPE; + +//! Interlace mode +typedef enum _NVVIOINTERLACEMODE +{ + NVVIOINTERLACEMODE_PROGRESSIVE , //!< Progressive (p) + NVVIOINTERLACEMODE_INTERLACE , //!< Interlace (i) + NVVIOINTERLACEMODE_PSF , //!< Progressive Segment Frame (psf) +}NVVIOINTERLACEMODE; + +//! Video data format +typedef enum _NVVIODATAFORMAT +{ + NVVIODATAFORMAT_UNKNOWN = -1 , //!< Invalid DataFormat + NVVIODATAFORMAT_R8G8B8_TO_YCRCB444 , //!< R8:G8:B8 => YCrCb (4:4:4) + NVVIODATAFORMAT_R8G8B8A8_TO_YCRCBA4444 , //!< R8:G8:B8:A8 => YCrCbA (4:4:4:4) + NVVIODATAFORMAT_R8G8B8Z10_TO_YCRCBZ4444 , //!< R8:G8:B8:Z10 => YCrCbZ (4:4:4:4) + NVVIODATAFORMAT_R8G8B8_TO_YCRCB422 , //!< R8:G8:B8 => YCrCb (4:2:2) + NVVIODATAFORMAT_R8G8B8A8_TO_YCRCBA4224 , //!< R8:G8:B8:A8 => YCrCbA (4:2:2:4) + NVVIODATAFORMAT_R8G8B8Z10_TO_YCRCBZ4224 , //!< R8:G8:B8:Z10 => YCrCbZ (4:2:2:4) + NVVIODATAFORMAT_X8X8X8_444_PASSTHRU , //!< R8:G8:B8 => RGB (4:4:4) + NVVIODATAFORMAT_X8X8X8A8_4444_PASSTHRU , //!< R8:G8:B8:A8 => RGBA (4:4:4:4) + NVVIODATAFORMAT_X8X8X8Z10_4444_PASSTHRU , //!< R8:G8:B8:Z10 => RGBZ (4:4:4:4) + NVVIODATAFORMAT_X10X10X10_444_PASSTHRU , //!< Y10:CR10:CB10 => YCrCb (4:4:4) + NVVIODATAFORMAT_X10X8X8_444_PASSTHRU , //!< Y10:CR8:CB8 => YCrCb (4:4:4) + NVVIODATAFORMAT_X10X8X8A10_4444_PASSTHRU , //!< Y10:CR8:CB8:A10 => YCrCbA (4:4:4:4) + NVVIODATAFORMAT_X10X8X8Z10_4444_PASSTHRU , //!< Y10:CR8:CB8:Z10 => YCrCbZ (4:4:4:4) + NVVIODATAFORMAT_DUAL_R8G8B8_TO_DUAL_YCRCB422 , //!< R8:G8:B8 + R8:G8:B8 => YCrCb (4:2:2 + 4:2:2) + NVVIODATAFORMAT_DUAL_X8X8X8_TO_DUAL_422_PASSTHRU , //!< Y8:CR8:CB8 + Y8:CR8:CB8 => YCrCb (4:2:2 + 4:2:2) + NVVIODATAFORMAT_R10G10B10_TO_YCRCB422 , //!< R10:G10:B10 => YCrCb (4:2:2) + NVVIODATAFORMAT_R10G10B10_TO_YCRCB444 , //!< R10:G10:B10 => YCrCb (4:4:4) + NVVIODATAFORMAT_X12X12X12_444_PASSTHRU , //!< X12:X12:X12 => XXX (4:4:4) + NVVIODATAFORMAT_X12X12X12_422_PASSTHRU , //!< X12:X12:X12 => XXX (4:2:2) + NVVIODATAFORMAT_Y10CR10CB10_TO_YCRCB422 , //!< Y10:CR10:CB10 => YCrCb (4:2:2) + NVVIODATAFORMAT_Y8CR8CB8_TO_YCRCB422 , //!< Y8:CR8:CB8 => YCrCb (4:2:2) + NVVIODATAFORMAT_Y10CR8CB8A10_TO_YCRCBA4224 , //!< Y10:CR8:CB8:A10 => YCrCbA (4:2:2:4) + NVVIODATAFORMAT_R10G10B10_TO_RGB444 , //!< R10:G10:B10 => RGB (4:4:4) + NVVIODATAFORMAT_R12G12B12_TO_YCRCB444 , //!< R12:G12:B12 => YCrCb (4:4:4) + NVVIODATAFORMAT_R12G12B12_TO_YCRCB422 , //!< R12:G12:B12 => YCrCb (4:2:2) +}NVVIODATAFORMAT; + +//! Video output area +typedef enum _NVVIOOUTPUTAREA +{ + NVVIOOUTPUTAREA_FULLSIZE , //!< Output to entire video resolution (full size) + NVVIOOUTPUTAREA_SAFEACTION , //!< Output to centered 90% of video resolution (safe action) + NVVIOOUTPUTAREA_SAFETITLE , //!< Output to centered 80% of video resolution (safe title) +}NVVIOOUTPUTAREA; + +//! Synchronization source +typedef enum _NVVIOSYNCSOURCE +{ + NVVIOSYNCSOURCE_SDISYNC , //!< SDI Sync (Digital input) + NVVIOSYNCSOURCE_COMPSYNC , //!< COMP Sync (Composite input) +}NVVIOSYNCSOURCE; + +//! Composite synchronization type +typedef enum _NVVIOCOMPSYNCTYPE +{ + NVVIOCOMPSYNCTYPE_AUTO , //!< Auto-detect + NVVIOCOMPSYNCTYPE_BILEVEL , //!< Bi-level signal + NVVIOCOMPSYNCTYPE_TRILEVEL , //!< Tri-level signal +}NVVIOCOMPSYNCTYPE; + +//! Video input output status +typedef enum _NVVIOINPUTOUTPUTSTATUS +{ + NVINPUTOUTPUTSTATUS_OFF , //!< Not in use + NVINPUTOUTPUTSTATUS_ERROR , //!< Error detected + NVINPUTOUTPUTSTATUS_SDI_SD , //!< SDI (standard-definition) + NVINPUTOUTPUTSTATUS_SDI_HD , //!< SDI (high-definition) +}NVVIOINPUTOUTPUTSTATUS; + +//! Synchronization input status +typedef enum _NVVIOSYNCSTATUS +{ + NVVIOSYNCSTATUS_OFF , //!< Sync not detected + NVVIOSYNCSTATUS_ERROR , //!< Error detected + NVVIOSYNCSTATUS_SYNCLOSS , //!< Genlock in use, format mismatch with output + NVVIOSYNCSTATUS_COMPOSITE , //!< Composite sync + NVVIOSYNCSTATUS_SDI_SD , //!< SDI sync (standard-definition) + NVVIOSYNCSTATUS_SDI_HD , //!< SDI sync (high-definition) +}NVVIOSYNCSTATUS; + +//! Video Capture Status +typedef enum _NVVIOCAPTURESTATUS +{ + NVVIOSTATUS_STOPPED , //!< Sync not detected + NVVIOSTATUS_RUNNING , //!< Error detected + NVVIOSTATUS_ERROR , //!< Genlock in use, format mismatch with output +}NVVIOCAPTURESTATUS; + +//! Video Capture Status +typedef enum _NVVIOSTATUSTYPE +{ + NVVIOSTATUSTYPE_IN , //!< Input Status + NVVIOSTATUSTYPE_OUT , //!< Output Status +}NVVIOSTATUSTYPE; + + +//! Assumption, maximum 4 SDI input and 4 SDI output cards supported on a system +#define NVAPI_MAX_VIO_DEVICES 8 + +//! 4 physical jacks supported on each SDI input card. +#define NVAPI_MAX_VIO_JACKS 4 + + +//! Each physical jack an on SDI input card can have +//! two "channels" in the case of "3G" VideoFormats, as specified +//! by SMPTE 425; for non-3G VideoFormats, only the first channel within +//! a physical jack is valid. +#define NVAPI_MAX_VIO_CHANNELS_PER_JACK 2 + +//! 4 Streams, 1 per physical jack +#define NVAPI_MAX_VIO_STREAMS 4 + +#define NVAPI_MIN_VIO_STREAMS 1 + +//! SDI input supports a max of 2 links per stream +#define NVAPI_MAX_VIO_LINKS_PER_STREAM 2 + + +#define NVAPI_MAX_FRAMELOCK_MAPPING_MODES 20 + +//! Min number of capture images +#define NVAPI_GVI_MIN_RAW_CAPTURE_IMAGES 1 + +//! Max number of capture images +#define NVAPI_GVI_MAX_RAW_CAPTURE_IMAGES 32 + +//! Default number of capture images +#define NVAPI_GVI_DEFAULT_RAW_CAPTURE_IMAGES 5 + + + +// Data Signal notification events. These need a event handler in RM. +// Register/Unregister and PopEvent NVAPI's are already available. + +//! Device configuration +typedef enum _NVVIOCONFIGTYPE +{ + NVVIOCONFIGTYPE_IN , //!< Input Status + NVVIOCONFIGTYPE_OUT , //!< Output Status +}NVVIOCONFIGTYPE; + +typedef enum _NVVIOCOLORSPACE +{ + NVVIOCOLORSPACE_UNKNOWN, + NVVIOCOLORSPACE_YCBCR, + NVVIOCOLORSPACE_YCBCRA, + NVVIOCOLORSPACE_YCBCRD, + NVVIOCOLORSPACE_GBR, + NVVIOCOLORSPACE_GBRA, + NVVIOCOLORSPACE_GBRD, +} NVVIOCOLORSPACE; + +//! Component sampling +typedef enum _NVVIOCOMPONENTSAMPLING +{ + NVVIOCOMPONENTSAMPLING_UNKNOWN, + NVVIOCOMPONENTSAMPLING_4444, + NVVIOCOMPONENTSAMPLING_4224, + NVVIOCOMPONENTSAMPLING_444, + NVVIOCOMPONENTSAMPLING_422 +} NVVIOCOMPONENTSAMPLING; + +typedef enum _NVVIOBITSPERCOMPONENT +{ + NVVIOBITSPERCOMPONENT_UNKNOWN, + NVVIOBITSPERCOMPONENT_8, + NVVIOBITSPERCOMPONENT_10, + NVVIOBITSPERCOMPONENT_12, +} NVVIOBITSPERCOMPONENT; + +typedef enum _NVVIOLINKID +{ + NVVIOLINKID_UNKNOWN, + NVVIOLINKID_A, + NVVIOLINKID_B, + NVVIOLINKID_C, + NVVIOLINKID_D +} NVVIOLINKID; + + +typedef enum _NVVIOANCPARITYCOMPUTATION +{ + NVVIOANCPARITYCOMPUTATION_AUTO, + NVVIOANCPARITYCOMPUTATION_ON, + NVVIOANCPARITYCOMPUTATION_OFF +} NVVIOANCPARITYCOMPUTATION; + + + +//! @} + + +//--------------------------------------------------------------------- +// Structures +//--------------------------------------------------------------------- + +//! \addtogroup vidio +//! @{ + + +//! Supports Serial Digital Interface (SDI) output +#define NVVIOCAPS_VIDOUT_SDI 0x00000001 + +//! Supports Internal timing source +#define NVVIOCAPS_SYNC_INTERNAL 0x00000100 + +//! Supports Genlock timing source +#define NVVIOCAPS_SYNC_GENLOCK 0x00000200 + +//! Supports Serial Digital Interface (SDI) synchronization input +#define NVVIOCAPS_SYNCSRC_SDI 0x00001000 + +//! Supports Composite synchronization input +#define NVVIOCAPS_SYNCSRC_COMP 0x00002000 + +//! Supports Desktop transparent mode +#define NVVIOCAPS_OUTPUTMODE_DESKTOP 0x00010000 + +//! Supports OpenGL application mode +#define NVVIOCAPS_OUTPUTMODE_OPENGL 0x00020000 + +//! Supports Serial Digital Interface (SDI) input +#define NVVIOCAPS_VIDIN_SDI 0x00100000 + +//! Supports Packed ANC +#define NVVIOCAPS_PACKED_ANC_SUPPORTED 0x00200000 + +//! Supports ANC audio blanking +#define NVVIOCAPS_AUDIO_BLANKING_SUPPORTED 0x00400000 + +//! SDI-class interface: SDI output with two genlock inputs +#define NVVIOCLASS_SDI 0x00000001 + +//! Device capabilities +typedef struct _NVVIOCAPS +{ + NvU32 version; //!< Structure version + NvAPI_String adapterName; //!< Graphics adapter name + NvU32 adapterClass; //!< Graphics adapter classes (NVVIOCLASS_SDI mask) + NvU32 adapterCaps; //!< Graphics adapter capabilities (NVVIOCAPS_* mask) + NvU32 dipSwitch; //!< On-board DIP switch settings bits + NvU32 dipSwitchReserved; //!< On-board DIP switch settings reserved bits + NvU32 boardID; //!< Board ID + //! Driver version + struct // + { + NvU32 majorVersion; //!< Major version. For GVI, majorVersion contains MajorVersion(HIWORD) And MinorVersion(LOWORD) + NvU32 minorVersion; //!< Minor version. For GVI, minorVersion contains Revison(HIWORD) And Build(LOWORD) + } driver; // + //! Firmware version + struct + { + NvU32 majorVersion; //!< Major version. In version 2, for both GVI and GVO, majorVersion contains MajorVersion(HIWORD) And MinorVersion(LOWORD) + NvU32 minorVersion; //!< Minor version. In version 2, for both GVI and GVO, minorVersion contains Revison(HIWORD) And Build(LOWORD) + } firmWare; // + NVVIOOWNERID ownerId; //!< Unique identifier for owner of video output (NVVIOOWNERID_INVALID if free running) + NVVIOOWNERTYPE ownerType; //!< Owner type (OpenGL application or Desktop mode) +} NVVIOCAPS; + +//! Macro for constructing the version field of NVVIOCAPS +#define NVVIOCAPS_VER1 MAKE_NVAPI_VERSION(NVVIOCAPS,1) +#define NVVIOCAPS_VER2 MAKE_NVAPI_VERSION(NVVIOCAPS,2) +#define NVVIOCAPS_VER NVVIOCAPS_VER2 + +//! Input channel status +typedef struct _NVVIOCHANNELSTATUS +{ + NvU32 smpte352; //!< 4-byte SMPTE 352 video payload identifier + NVVIOSIGNALFORMAT signalFormat; //!< Signal format + NVVIOBITSPERCOMPONENT bitsPerComponent; //!< Bits per component + NVVIOCOMPONENTSAMPLING samplingFormat; //!< Sampling format + NVVIOCOLORSPACE colorSpace; //!< Color space + NVVIOLINKID linkID; //!< Link ID +} NVVIOCHANNELSTATUS; + +//! Input device status +typedef struct _NVVIOINPUTSTATUS +{ + NVVIOCHANNELSTATUS vidIn[NVAPI_MAX_VIO_JACKS][NVAPI_MAX_VIO_CHANNELS_PER_JACK]; //!< Video input status per channel within a jack + NVVIOCAPTURESTATUS captureStatus; //!< status of video capture +} NVVIOINPUTSTATUS; + +//! Output device status +typedef struct _NVVIOOUTPUTSTATUS +{ + NVVIOINPUTOUTPUTSTATUS vid1Out; //!< Video 1 output status + NVVIOINPUTOUTPUTSTATUS vid2Out; //!< Video 2 output status + NVVIOSYNCSTATUS sdiSyncIn; //!< SDI sync input status + NVVIOSYNCSTATUS compSyncIn; //!< Composite sync input status + NvU32 syncEnable; //!< Sync enable (TRUE if using syncSource) + NVVIOSYNCSOURCE syncSource; //!< Sync source + NVVIOSIGNALFORMAT syncFormat; //!< Sync format + NvU32 frameLockEnable; //!< Framelock enable flag + NvU32 outputVideoLocked; //!< Output locked status + NvU32 dataIntegrityCheckErrorCount; //!< Data integrity check error count + NvU32 dataIntegrityCheckEnabled; //!< Data integrity check status enabled + NvU32 dataIntegrityCheckFailed; //!< Data integrity check status failed + NvU32 uSyncSourceLocked; //!< genlocked to framelocked to ref signal + NvU32 uPowerOn; //!< TRUE: indicates there is sufficient power +} NVVIOOUTPUTSTATUS; + +//! Video device status. +typedef struct _NVVIOSTATUS +{ + NvU32 version; //!< Structure version + NVVIOSTATUSTYPE nvvioStatusType; //!< Input or Output status + union + { + NVVIOINPUTSTATUS inStatus; //!< Input device status + NVVIOOUTPUTSTATUS outStatus; //!< Output device status + }vioStatus; +} NVVIOSTATUS; + +//! Macro for constructingthe version field of NVVIOSTATUS +#define NVVIOSTATUS_VER MAKE_NVAPI_VERSION(NVVIOSTATUS,1) + +//! Output region +typedef struct _NVVIOOUTPUTREGION +{ + NvU32 x; //!< Horizontal origin in pixels + NvU32 y; //!< Vertical origin in pixels + NvU32 width; //!< Width of region in pixels + NvU32 height; //!< Height of region in pixels +} NVVIOOUTPUTREGION; + +//! Gamma ramp (8-bit index) +typedef struct _NVVIOGAMMARAMP8 +{ + NvU16 uRed[256]; //!< Red channel gamma ramp (8-bit index, 16-bit values) + NvU16 uGreen[256]; //!< Green channel gamma ramp (8-bit index, 16-bit values) + NvU16 uBlue[256]; //!< Blue channel gamma ramp (8-bit index, 16-bit values) +} NVVIOGAMMARAMP8; + +//! Gamma ramp (10-bit index) +typedef struct _NVVIOGAMMARAMP10 +{ + NvU16 uRed[1024]; //!< Red channel gamma ramp (10-bit index, 16-bit values) + NvU16 uGreen[1024]; //!< Green channel gamma ramp (10-bit index, 16-bit values) + NvU16 uBlue[1024]; //!< Blue channel gamma ramp (10-bit index, 16-bit values) +} NVVIOGAMMARAMP10; + + +//! Sync delay +typedef struct _NVVIOSYNCDELAY +{ + NvU32 version; //!< Structure version + NvU32 horizontalDelay; //!< Horizontal delay in pixels + NvU32 verticalDelay; //!< Vertical delay in lines +} NVVIOSYNCDELAY; + +//! Macro for constructing the version field of NVVIOSYNCDELAY +#define NVVIOSYNCDELAY_VER MAKE_NVAPI_VERSION(NVVIOSYNCDELAY,1) + + +//! Video mode information +typedef struct _NVVIOVIDEOMODE +{ + NvU32 horizontalPixels; //!< Horizontal resolution (in pixels) + NvU32 verticalLines; //!< Vertical resolution for frame (in lines) + float fFrameRate; //!< Frame rate + NVVIOINTERLACEMODE interlaceMode; //!< Interlace mode + NVVIOVIDEOSTANDARD videoStandard; //!< SMPTE standards format + NVVIOVIDEOTYPE videoType; //!< HD or SD signal classification +} NVVIOVIDEOMODE; + +//! Signal format details +typedef struct _NVVIOSIGNALFORMATDETAIL +{ + NVVIOSIGNALFORMAT signalFormat; //!< Signal format enumerated value + NVVIOVIDEOMODE videoMode; //!< Video mode for signal format +}NVVIOSIGNALFORMATDETAIL; + + +//! R8:G8:B8 +#define NVVIOBUFFERFORMAT_R8G8B8 0x00000001 + +//! R8:G8:B8:Z24 +#define NVVIOBUFFERFORMAT_R8G8B8Z24 0x00000002 + +//! R8:G8:B8:A8 +#define NVVIOBUFFERFORMAT_R8G8B8A8 0x00000004 + +//! R8:G8:B8:A8:Z24 +#define NVVIOBUFFERFORMAT_R8G8B8A8Z24 0x00000008 + +//! R16FP:G16FP:B16FP +#define NVVIOBUFFERFORMAT_R16FPG16FPB16FP 0x00000010 + +//! R16FP:G16FP:B16FP:Z24 +#define NVVIOBUFFERFORMAT_R16FPG16FPB16FPZ24 0x00000020 + +//! R16FP:G16FP:B16FP:A16FP +#define NVVIOBUFFERFORMAT_R16FPG16FPB16FPA16FP 0x00000040 + +//! R16FP:G16FP:B16FP:A16FP:Z24 +#define NVVIOBUFFERFORMAT_R16FPG16FPB16FPA16FPZ24 0x00000080 + + + +//! Data format details +typedef struct _NVVIODATAFORMATDETAIL +{ + NVVIODATAFORMAT dataFormat; //!< Data format enumerated value + NvU32 vioCaps; //!< Data format capabilities (NVVIOCAPS_* mask) +}NVVIODATAFORMATDETAIL; + +//! Colorspace conversion +typedef struct _NVVIOCOLORCONVERSION +{ + NvU32 version; //!< Structure version + float colorMatrix[3][3]; //!< Output[n] = + float colorOffset[3]; //!< Input[0] * colorMatrix[n][0] + + float colorScale[3]; //!< Input[1] * colorMatrix[n][1] + + //!< Input[2] * colorMatrix[n][2] + + //!< OutputRange * colorOffset[n] + //!< where OutputRange is the standard magnitude of + //!< Output[n][n] and colorMatrix and colorOffset + //!< values are within the range -1.0 to +1.0 + NvU32 compositeSafe; //!< compositeSafe constrains luminance range when using composite output +} NVVIOCOLORCONVERSION; + +//! macro for constructing the version field of _NVVIOCOLORCONVERSION. +#define NVVIOCOLORCONVERSION_VER MAKE_NVAPI_VERSION(NVVIOCOLORCONVERSION,1) + +//! Gamma correction +typedef struct _NVVIOGAMMACORRECTION +{ + NvU32 version; //!< Structure version + NvU32 vioGammaCorrectionType; //!< Gamma correction type (8-bit or 10-bit) + //! Gamma correction: + union + { + NVVIOGAMMARAMP8 gammaRamp8; //!< Gamma ramp (8-bit index, 16-bit values) + NVVIOGAMMARAMP10 gammaRamp10; //!< Gamma ramp (10-bit index, 16-bit values) + }gammaRamp; + float fGammaValueR; //!< Red Gamma value within gamma ranges. 0.5 - 6.0 + float fGammaValueG; //!< Green Gamma value within gamma ranges. 0.5 - 6.0 + float fGammaValueB; //!< Blue Gamma value within gamma ranges. 0.5 - 6.0 +} NVVIOGAMMACORRECTION; + +//! Macro for constructing thevesion field of _NVVIOGAMMACORRECTION +#define NVVIOGAMMACORRECTION_VER MAKE_NVAPI_VERSION(NVVIOGAMMACORRECTION,1) + +//! Maximum number of ranges per channel +#define MAX_NUM_COMPOSITE_RANGE 2 + + +typedef struct _NVVIOCOMPOSITERANGE +{ + NvU32 uRange; + NvU32 uEnabled; + NvU32 uMin; + NvU32 uMax; +} NVVIOCOMPOSITERANGE; + + + +// Device configuration (fields masks indicating NVVIOCONFIG fields to use for NvAPI_VIO_GetConfig/NvAPI_VIO_SetConfig() ) +// +#define NVVIOCONFIG_SIGNALFORMAT 0x00000001 //!< fields: signalFormat +#define NVVIOCONFIG_DATAFORMAT 0x00000002 //!< fields: dataFormat +#define NVVIOCONFIG_OUTPUTREGION 0x00000004 //!< fields: outputRegion +#define NVVIOCONFIG_OUTPUTAREA 0x00000008 //!< fields: outputArea +#define NVVIOCONFIG_COLORCONVERSION 0x00000010 //!< fields: colorConversion +#define NVVIOCONFIG_GAMMACORRECTION 0x00000020 //!< fields: gammaCorrection +#define NVVIOCONFIG_SYNCSOURCEENABLE 0x00000040 //!< fields: syncSource and syncEnable +#define NVVIOCONFIG_SYNCDELAY 0x00000080 //!< fields: syncDelay +#define NVVIOCONFIG_COMPOSITESYNCTYPE 0x00000100 //!< fields: compositeSyncType +#define NVVIOCONFIG_FRAMELOCKENABLE 0x00000200 //!< fields: EnableFramelock +#define NVVIOCONFIG_422FILTER 0x00000400 //!< fields: bEnable422Filter +#define NVVIOCONFIG_COMPOSITETERMINATE 0x00000800 //!< fields: bCompositeTerminate (Not supported on Quadro FX 4000 SDI) +#define NVVIOCONFIG_DATAINTEGRITYCHECK 0x00001000 //!< fields: bEnableDataIntegrityCheck (Not supported on Quadro FX 4000 SDI) +#define NVVIOCONFIG_CSCOVERRIDE 0x00002000 //!< fields: colorConversion override +#define NVVIOCONFIG_FLIPQUEUELENGTH 0x00004000 //!< fields: flipqueuelength control +#define NVVIOCONFIG_ANCTIMECODEGENERATION 0x00008000 //!< fields: bEnableANCTimeCodeGeneration +#define NVVIOCONFIG_COMPOSITE 0x00010000 //!< fields: bEnableComposite +#define NVVIOCONFIG_ALPHAKEYCOMPOSITE 0x00020000 //!< fields: bEnableAlphaKeyComposite +#define NVVIOCONFIG_COMPOSITE_Y 0x00040000 //!< fields: compRange +#define NVVIOCONFIG_COMPOSITE_CR 0x00080000 //!< fields: compRange +#define NVVIOCONFIG_COMPOSITE_CB 0x00100000 //!< fields: compRange +#define NVVIOCONFIG_FULL_COLOR_RANGE 0x00200000 //!< fields: bEnableFullColorRange +#define NVVIOCONFIG_RGB_DATA 0x00400000 //!< fields: bEnableRGBData +#define NVVIOCONFIG_RESERVED_SDIOUTPUTENABLE 0x00800000 //!< fields: bEnableSDIOutput +#define NVVIOCONFIG_STREAMS 0x01000000 //!< fields: streams +#define NVVIOCONFIG_ANC_PARITY_COMPUTATION 0x02000000 //!< fields: ancParityComputation +#define NVVIOCONFIG_ANC_AUDIO_REPEAT 0x04000000 //!< fields: enableAudioBlanking + + +// Don't forget to update NVVIOCONFIG_VALIDFIELDS in nvapi.spec when NVVIOCONFIG_ALLFIELDS changes. +#define NVVIOCONFIG_ALLFIELDS ( NVVIOCONFIG_SIGNALFORMAT | \ + NVVIOCONFIG_DATAFORMAT | \ + NVVIOCONFIG_OUTPUTREGION | \ + NVVIOCONFIG_OUTPUTAREA | \ + NVVIOCONFIG_COLORCONVERSION | \ + NVVIOCONFIG_GAMMACORRECTION | \ + NVVIOCONFIG_SYNCSOURCEENABLE | \ + NVVIOCONFIG_SYNCDELAY | \ + NVVIOCONFIG_COMPOSITESYNCTYPE | \ + NVVIOCONFIG_FRAMELOCKENABLE | \ + NVVIOCONFIG_422FILTER | \ + NVVIOCONFIG_COMPOSITETERMINATE | \ + NVVIOCONFIG_DATAINTEGRITYCHECK | \ + NVVIOCONFIG_CSCOVERRIDE | \ + NVVIOCONFIG_FLIPQUEUELENGTH | \ + NVVIOCONFIG_ANCTIMECODEGENERATION | \ + NVVIOCONFIG_COMPOSITE | \ + NVVIOCONFIG_ALPHAKEYCOMPOSITE | \ + NVVIOCONFIG_COMPOSITE_Y | \ + NVVIOCONFIG_COMPOSITE_CR | \ + NVVIOCONFIG_COMPOSITE_CB | \ + NVVIOCONFIG_FULL_COLOR_RANGE | \ + NVVIOCONFIG_RGB_DATA | \ + NVVIOCONFIG_RESERVED_SDIOUTPUTENABLE | \ + NVVIOCONFIG_STREAMS | \ + NVVIOCONFIG_ANC_PARITY_COMPUTATION | \ + NVVIOCONFIG_ANC_AUDIO_REPEAT ) + +#define NVVIOCONFIG_VALIDFIELDS ( NVVIOCONFIG_SIGNALFORMAT | \ + NVVIOCONFIG_DATAFORMAT | \ + NVVIOCONFIG_OUTPUTREGION | \ + NVVIOCONFIG_OUTPUTAREA | \ + NVVIOCONFIG_COLORCONVERSION | \ + NVVIOCONFIG_GAMMACORRECTION | \ + NVVIOCONFIG_SYNCSOURCEENABLE | \ + NVVIOCONFIG_SYNCDELAY | \ + NVVIOCONFIG_COMPOSITESYNCTYPE | \ + NVVIOCONFIG_FRAMELOCKENABLE | \ + NVVIOCONFIG_RESERVED_SDIOUTPUTENABLE | \ + NVVIOCONFIG_422FILTER | \ + NVVIOCONFIG_COMPOSITETERMINATE | \ + NVVIOCONFIG_DATAINTEGRITYCHECK | \ + NVVIOCONFIG_CSCOVERRIDE | \ + NVVIOCONFIG_FLIPQUEUELENGTH | \ + NVVIOCONFIG_ANCTIMECODEGENERATION | \ + NVVIOCONFIG_COMPOSITE | \ + NVVIOCONFIG_ALPHAKEYCOMPOSITE | \ + NVVIOCONFIG_COMPOSITE_Y | \ + NVVIOCONFIG_COMPOSITE_CR | \ + NVVIOCONFIG_COMPOSITE_CB | \ + NVVIOCONFIG_FULL_COLOR_RANGE | \ + NVVIOCONFIG_RGB_DATA | \ + NVVIOCONFIG_RESERVED_SDIOUTPUTENABLE | \ + NVVIOCONFIG_STREAMS | \ + NVVIOCONFIG_ANC_PARITY_COMPUTATION | \ + NVVIOCONFIG_ANC_AUDIO_REPEAT) + +#define NVVIOCONFIG_DRIVERFIELDS ( NVVIOCONFIG_OUTPUTREGION | \ + NVVIOCONFIG_OUTPUTAREA | \ + NVVIOCONFIG_COLORCONVERSION | \ + NVVIOCONFIG_FLIPQUEUELENGTH) + +#define NVVIOCONFIG_GAMMAFIELDS ( NVVIOCONFIG_GAMMACORRECTION ) + +#define NVVIOCONFIG_RMCTRLFIELDS ( NVVIOCONFIG_SIGNALFORMAT | \ + NVVIOCONFIG_DATAFORMAT | \ + NVVIOCONFIG_SYNCSOURCEENABLE | \ + NVVIOCONFIG_COMPOSITESYNCTYPE | \ + NVVIOCONFIG_FRAMELOCKENABLE | \ + NVVIOCONFIG_422FILTER | \ + NVVIOCONFIG_COMPOSITETERMINATE | \ + NVVIOCONFIG_DATAINTEGRITYCHECK | \ + NVVIOCONFIG_COMPOSITE | \ + NVVIOCONFIG_ALPHAKEYCOMPOSITE | \ + NVVIOCONFIG_COMPOSITE_Y | \ + NVVIOCONFIG_COMPOSITE_CR | \ + NVVIOCONFIG_COMPOSITE_CB) + +#define NVVIOCONFIG_RMSKEWFIELDS ( NVVIOCONFIG_SYNCDELAY ) + +#define NVVIOCONFIG_ALLOWSDIRUNNING_FIELDS ( NVVIOCONFIG_DATAINTEGRITYCHECK | \ + NVVIOCONFIG_SYNCDELAY | \ + NVVIOCONFIG_CSCOVERRIDE | \ + NVVIOCONFIG_ANCTIMECODEGENERATION | \ + NVVIOCONFIG_COMPOSITE | \ + NVVIOCONFIG_ALPHAKEYCOMPOSITE | \ + NVVIOCONFIG_COMPOSITE_Y | \ + NVVIOCONFIG_COMPOSITE_CR | \ + NVVIOCONFIG_COMPOSITE_CB | \ + NVVIOCONFIG_ANC_PARITY_COMPUTATION) + + + #define NVVIOCONFIG_RMMODESET_FIELDS ( NVVIOCONFIG_SIGNALFORMAT | \ + NVVIOCONFIG_DATAFORMAT | \ + NVVIOCONFIG_SYNCSOURCEENABLE | \ + NVVIOCONFIG_FRAMELOCKENABLE | \ + NVVIOCONFIG_COMPOSITESYNCTYPE | \ + NVVIOCONFIG_ANC_AUDIO_REPEAT) + + +//! Output device configuration +// No members can be deleted from below structure. Only add new members at the +// end of the structure. +typedef struct _NVVIOOUTPUTCONFIG_V1 +{ + NVVIOSIGNALFORMAT signalFormat; //!< Signal format for video output + NVVIODATAFORMAT dataFormat; //!< Data format for video output + NVVIOOUTPUTREGION outputRegion; //!< Region for video output (Desktop mode) + NVVIOOUTPUTAREA outputArea; //!< Usable resolution for video output (safe area) + NVVIOCOLORCONVERSION colorConversion; //!< Color conversion. + NVVIOGAMMACORRECTION gammaCorrection; + NvU32 syncEnable; //!< Sync enable (TRUE to use syncSource) + NVVIOSYNCSOURCE syncSource; //!< Sync source + NVVIOSYNCDELAY syncDelay; //!< Sync delay + NVVIOCOMPSYNCTYPE compositeSyncType; //!< Composite sync type + NvU32 frameLockEnable; //!< Flag indicating whether framelock was on/off + NvU32 psfSignalFormat; //!< Indicates whether contained format is PSF Signal format + NvU32 enable422Filter; //!< Enables/Disables 4:2:2 filter + NvU32 compositeTerminate; //!< Composite termination + NvU32 enableDataIntegrityCheck; //!< Enable data integrity check: true - enable, false - disable + NvU32 cscOverride; //!< Use provided CSC color matrix to overwrite + NvU32 flipQueueLength; //!< Number of buffers used for the internal flipqueue + NvU32 enableANCTimeCodeGeneration; //!< Enable SDI ANC time code generation + NvU32 enableComposite; //!< Enable composite + NvU32 enableAlphaKeyComposite; //!< Enable Alpha key composite + NVVIOCOMPOSITERANGE compRange; //!< Composite ranges + NvU8 reservedData[256]; //!< Inicates last stored SDI output state TRUE-ON / FALSE-OFF + NvU32 enableFullColorRange; //!< Flag indicating Full Color Range + NvU32 enableRGBData; //!< Indicates data is in RGB format +} NVVIOOUTPUTCONFIG_V1; + +typedef struct _NVVIOOUTPUTCONFIG_V2 +{ + NVVIOSIGNALFORMAT signalFormat; //!< Signal format for video output + NVVIODATAFORMAT dataFormat; //!< Data format for video output + NVVIOOUTPUTREGION outputRegion; //!< Region for video output (Desktop mode) + NVVIOOUTPUTAREA outputArea; //!< Usable resolution for video output (safe area) + NVVIOCOLORCONVERSION colorConversion; //!< Color conversion. + NVVIOGAMMACORRECTION gammaCorrection; + NvU32 syncEnable; //!< Sync enable (TRUE to use syncSource) + NVVIOSYNCSOURCE syncSource; //!< Sync source + NVVIOSYNCDELAY syncDelay; //!< Sync delay + NVVIOCOMPSYNCTYPE compositeSyncType; //!< Composite sync type + NvU32 frameLockEnable; //!< Flag indicating whether framelock was on/off + NvU32 psfSignalFormat; //!< Indicates whether contained format is PSF Signal format + NvU32 enable422Filter; //!< Enables/Disables 4:2:2 filter + NvU32 compositeTerminate; //!< Composite termination + NvU32 enableDataIntegrityCheck; //!< Enable data integrity check: true - enable, false - disable + NvU32 cscOverride; //!< Use provided CSC color matrix to overwrite + NvU32 flipQueueLength; //!< Number of buffers used for the internal flip queue + NvU32 enableANCTimeCodeGeneration; //!< Enable SDI ANC time code generation + NvU32 enableComposite; //!< Enable composite + NvU32 enableAlphaKeyComposite; //!< Enable Alpha key composite + NVVIOCOMPOSITERANGE compRange; //!< Composite ranges + NvU8 reservedData[256]; //!< Indicates last stored SDI output state TRUE-ON / FALSE-OFF + NvU32 enableFullColorRange; //!< Flag indicating Full Color Range + NvU32 enableRGBData; //!< Indicates data is in RGB format + NVVIOANCPARITYCOMPUTATION ancParityComputation; //!< Enable HW ANC parity bit computation (auto/on/off) +} NVVIOOUTPUTCONFIG_V2; + +typedef struct _NVVIOOUTPUTCONFIG_V3 +{ + NVVIOSIGNALFORMAT signalFormat; //!< Signal format for video output + NVVIODATAFORMAT dataFormat; //!< Data format for video output + NVVIOOUTPUTREGION outputRegion; //!< Region for video output (Desktop mode) + NVVIOOUTPUTAREA outputArea; //!< Usable resolution for video output (safe area) + NVVIOCOLORCONVERSION colorConversion; //!< Color conversion. + NVVIOGAMMACORRECTION gammaCorrection; + NvU32 syncEnable; //!< Sync enable (TRUE to use syncSource) + NVVIOSYNCSOURCE syncSource; //!< Sync source + NVVIOSYNCDELAY syncDelay; //!< Sync delay + NVVIOCOMPSYNCTYPE compositeSyncType; //!< Composite sync type + NvU32 frameLockEnable; //!< Flag indicating whether framelock was on/off + NvU32 psfSignalFormat; //!< Indicates whether contained format is PSF Signal format + NvU32 enable422Filter; //!< Enables/Disables 4:2:2 filter + NvU32 compositeTerminate; //!< Composite termination + NvU32 enableDataIntegrityCheck; //!< Enable data integrity check: true - enable, false - disable + NvU32 cscOverride; //!< Use provided CSC color matrix to overwrite + NvU32 flipQueueLength; //!< Number of buffers used for the internal flip queue + NvU32 enableANCTimeCodeGeneration; //!< Enable SDI ANC time code generation + NvU32 enableComposite; //!< Enable composite + NvU32 enableAlphaKeyComposite; //!< Enable Alpha key composite + NVVIOCOMPOSITERANGE compRange; //!< Composite ranges + NvU8 reservedData[256]; //!< Indicates last stored SDI output state TRUE-ON / FALSE-OFF + NvU32 enableFullColorRange; //!< Flag indicating Full Color Range + NvU32 enableRGBData; //!< Indicates data is in RGB format + NVVIOANCPARITYCOMPUTATION ancParityComputation; //!< Enable HW ANC parity bit computation (auto/on/off) + NvU32 enableAudioBlanking; //!< Enable HANC audio blanking on repeat frames +} NVVIOOUTPUTCONFIG_V3; + +//! Stream configuration +typedef struct _NVVIOSTREAM +{ + NvU32 bitsPerComponent; //!< Bits per component + NVVIOCOMPONENTSAMPLING sampling; //!< Sampling + NvU32 expansionEnable; //!< Enable/disable 4:2:2->4:4:4 expansion + NvU32 numLinks; //!< Number of active links + struct + { + NvU32 jack; //!< This stream's link[i] will use the specified (0-based) channel within the + NvU32 channel; //!< specified (0-based) jack + } links[NVAPI_MAX_VIO_LINKS_PER_STREAM]; +} NVVIOSTREAM; + +//! Input device configuration +typedef struct _NVVIOINPUTCONFIG +{ + NvU32 numRawCaptureImages; //!< numRawCaptureImages is the number of frames to keep in the capture queue. + //!< must be between NVAPI_GVI_MIN_RAW_CAPTURE_IMAGES and NVAPI_GVI_MAX_RAW_CAPTURE_IMAGES, + NVVIOSIGNALFORMAT signalFormat; //!< Signal format. + //!< Please note that both numRawCaptureImages and signalFormat should be set together. + NvU32 numStreams; //!< Number of active streams. + NVVIOSTREAM streams[NVAPI_MAX_VIO_STREAMS]; //!< Stream configurations + NvU32 bTestMode; //!< This attribute controls the GVI test mode. + //!< Possible values 0/1. When testmode enabled, the + //!< GVI device will generate fake data as quickly as possible. +} NVVIOINPUTCONFIG; + +typedef struct _NVVIOCONFIG_V1 +{ + NvU32 version; //!< Structure version + NvU32 fields; //!< Caller sets to NVVIOCONFIG_* mask for fields to use + NVVIOCONFIGTYPE nvvioConfigType; //!< Input or Output configuration + union + { + NVVIOINPUTCONFIG inConfig; //!< Input device configuration + NVVIOOUTPUTCONFIG_V1 outConfig; //!< Output device configuration + }vioConfig; +} NVVIOCONFIG_V1; + + +typedef struct _NVVIOCONFIG_V2 +{ + NvU32 version; //!< Structure version + NvU32 fields; //!< Caller sets to NVVIOCONFIG_* mask for fields to use + NVVIOCONFIGTYPE nvvioConfigType; //!< Input or Output configuration + union + { + NVVIOINPUTCONFIG inConfig; //!< Input device configuration + NVVIOOUTPUTCONFIG_V2 outConfig; //!< Output device configuration + }vioConfig; +} NVVIOCONFIG_V2; + +typedef struct _NVVIOCONFIG_V3 +{ + NvU32 version; //!< Structure version + NvU32 fields; //!< Caller sets to NVVIOCONFIG_* mask for fields to use + NVVIOCONFIGTYPE nvvioConfigType; //!< Input or Output configuration + union + { + NVVIOINPUTCONFIG inConfig; //!< Input device configuration + NVVIOOUTPUTCONFIG_V3 outConfig; //!< Output device configuration + }vioConfig; +} NVVIOCONFIG_V3; +typedef NVVIOOUTPUTCONFIG_V3 NVVIOOUTPUTCONFIG; +typedef NVVIOCONFIG_V3 NVVIOCONFIG; + +#define NVVIOCONFIG_VER1 MAKE_NVAPI_VERSION(NVVIOCONFIG_V1,1) +#define NVVIOCONFIG_VER2 MAKE_NVAPI_VERSION(NVVIOCONFIG_V2,2) +#define NVVIOCONFIG_VER3 MAKE_NVAPI_VERSION(NVVIOCONFIG_V3,3) +#define NVVIOCONFIG_VER NVVIOCONFIG_VER3 + + +typedef struct +{ + NvPhysicalGpuHandle hPhysicalGpu; //!< Handle to Physical GPU (This could be NULL for GVI device if its not binded) + NvVioHandle hVioHandle; //! + +#endif // _NVAPI_H \ No newline at end of file diff --git a/plugins/HardwareDevices/nvapi/nvapi_lite_common.h b/plugins/HardwareDevices/nvapi/nvapi_lite_common.h new file mode 100644 index 0000000..a6e6bc2 --- /dev/null +++ b/plugins/HardwareDevices/nvapi/nvapi_lite_common.h @@ -0,0 +1,429 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if (defined(WIN32) || defined(_WIN32)) && defined(_MSC_VER) && (_MSC_VER > 1399) && !defined(NVAPI_INTERNAL) && !defined(NVAPI_DEPRECATED_OLD) +#ifndef __nvapi_deprecated_function +#define __nvapi_deprecated_function(message) __declspec(deprecated(message)) +#endif +#ifndef __nvapi_deprecated_datatype +#define __nvapi_deprecated_datatype(FirstRelease) __declspec(deprecated("Do not use this data type - it is deprecated in release " #FirstRelease ".")) +#endif +#else +#ifndef __nvapi_deprecated_function +#define __nvapi_deprecated_function(message) +#endif +#ifndef __nvapi_deprecated_datatype +#define __nvapi_deprecated_datatype(FirstRelease) +#endif +#endif + +/* 64-bit types for compilers that support them, plus some obsolete variants */ +#if defined(__GNUC__) || defined(__arm) || defined(__IAR_SYSTEMS_ICC__) || defined(__ghs__) || defined(_WIN64) +typedef unsigned long long NvU64; /* 0 to 18446744073709551615 */ +typedef long long NvS64; /* -9223372036854775808 to 9223372036854775807 */ +#else +typedef unsigned __int64 NvU64; /* 0 to 18446744073709551615 */ +typedef __int64 NvS64; /* -9223372036854775808 to 9223372036854775807 */ +#endif + +// mac os 32-bit still needs this +#if (defined(macintosh) || defined(__APPLE__)) && !defined(__LP64__) +typedef signed long NvS32; /* -2147483648 to 2147483647 */ +#else +typedef signed int NvS32; /* -2147483648 to 2147483647 */ +#endif + +// mac os 32-bit still needs this +#if ( (defined(macintosh) && defined(__LP64__) && (__NVAPI_RESERVED0__)) || (!defined(macintosh) && defined(__NVAPI_RESERVED0__)) ) +typedef unsigned int NvU32; /* 0 to 4294967295 */ +#else +typedef unsigned long NvU32; /* 0 to 4294967295 */ +#endif + +typedef signed short NvS16; +typedef unsigned short NvU16; +typedef unsigned char NvU8; +typedef signed char NvS8; + +typedef struct _NV_RECT +{ + NvU32 left; + NvU32 top; + NvU32 right; + NvU32 bottom; +} NV_RECT; + +#define NVAPI_INTERFACE extern __success(return == NVAPI_OK) NvAPI_Status __cdecl + +#define NV_DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name + +//! \addtogroup nvapihandles +//! NVAPI Handles - These handles are retrieved from various calls and passed in to others in NvAPI +//! These are meant to be opaque types. Do not assume they correspond to indices, HDCs, +//! display indexes or anything else. +//! +//! Most handles remain valid until a display re-configuration (display mode set) or GPU +//! reconfiguration (going into or out of SLI modes) occurs. If NVAPI_HANDLE_INVALIDATED +//! is received by an app, it should discard all handles, and re-enumerate them. +//! @{ +NV_DECLARE_HANDLE(NvDisplayHandle); //!< Display Device driven by NVIDIA GPU(s) (an attached display) +NV_DECLARE_HANDLE(NvMonitorHandle); //!< Monitor handle +NV_DECLARE_HANDLE(NvUnAttachedDisplayHandle); //!< Unattached Display Device driven by NVIDIA GPU(s) +NV_DECLARE_HANDLE(NvLogicalGpuHandle); //!< One or more physical GPUs acting in concert (SLI) +NV_DECLARE_HANDLE(NvPhysicalGpuHandle); //!< A single physical GPU +NV_DECLARE_HANDLE(NvEventHandle); //!< A handle to an event registration instance +NV_DECLARE_HANDLE(NvVisualComputingDeviceHandle); //!< A handle to a Visual Computing Device +NV_DECLARE_HANDLE(NvHICHandle); //!< A handle to a Host Interface Card +NV_DECLARE_HANDLE(NvGSyncDeviceHandle); //!< A handle to a Sync device +NV_DECLARE_HANDLE(NvVioHandle); //!< A handle to an SDI device +NV_DECLARE_HANDLE(NvTransitionHandle); //!< A handle to address a single transition request +NV_DECLARE_HANDLE(NvAudioHandle); //!< NVIDIA HD Audio Device +NV_DECLARE_HANDLE(Nv3DVPContextHandle); //!< A handle for a 3D Vision Pro (3DVP) context +NV_DECLARE_HANDLE(Nv3DVPTransceiverHandle); //!< A handle for a 3DVP RF transceiver +NV_DECLARE_HANDLE(Nv3DVPGlassesHandle); //!< A handle for a pair of 3DVP RF shutter glasses +NV_DECLARE_HANDLE(NvSourceHandle); //!< Unique source handle on the system +NV_DECLARE_HANDLE(NvTargetHandle); //!< Unique target handle on the system +//! @} + +//! \ingroup nvapihandles +//! @{ +#define NVAPI_DEFAULT_HANDLE 0 +#define NV_BIT(x) (1 << (x)) +//! @} + +//! \addtogroup nvapitypes +//! @{ +#define NVAPI_GENERIC_STRING_MAX 4096 +#define NVAPI_LONG_STRING_MAX 256 +#define NVAPI_SHORT_STRING_MAX 64 + +typedef struct _NvSBox +{ + NvS32 sX; + NvS32 sY; + NvS32 sWidth; + NvS32 sHeight; +} NvSBox; + +#ifndef NvGUID_Defined +#define NvGUID_Defined + +typedef struct _NvGUID +{ + NvU32 data1; + NvU16 data2; + NvU16 data3; + NvU8 data4[8]; +} NvGUID, NvLUID; + +#endif //#ifndef NvGUID_Defined + +#define NVAPI_MAX_PHYSICAL_GPUS 64 +#define NVAPI_MAX_PHYSICAL_BRIDGES 100 +#define NVAPI_PHYSICAL_GPUS 32 +#define NVAPI_MAX_LOGICAL_GPUS 64 +#define NVAPI_MAX_AVAILABLE_GPU_TOPOLOGIES 256 +#define NVAPI_MAX_AVAILABLE_SLI_GROUPS 256 +#define NVAPI_MAX_GPU_TOPOLOGIES NVAPI_MAX_PHYSICAL_GPUS +#define NVAPI_MAX_GPU_PER_TOPOLOGY 8 +#define NVAPI_MAX_DISPLAY_HEADS 2 +#define NVAPI_ADVANCED_DISPLAY_HEADS 4 +#define NVAPI_MAX_DISPLAYS NVAPI_PHYSICAL_GPUS * NVAPI_ADVANCED_DISPLAY_HEADS +#define NVAPI_MAX_ACPI_IDS 16 +#define NVAPI_MAX_VIEW_MODES 8 +#define NV_MAX_HEADS 4 //!< Maximum heads, each with NVAPI_DESKTOP_RES resolution +#define NVAPI_MAX_HEADS_PER_GPU 32 + +#define NV_MAX_HEADS 4 //!< Maximum number of heads, each with #NVAPI_DESKTOP_RES resolution +#define NV_MAX_VID_STREAMS 4 //!< Maximum number of input video streams, each with a #NVAPI_VIDEO_SRC_INFO +#define NV_MAX_VID_PROFILES 4 //!< Maximum number of output video profiles supported + +#define NVAPI_SYSTEM_MAX_DISPLAYS NVAPI_MAX_PHYSICAL_GPUS * NV_MAX_HEADS + +#define NVAPI_SYSTEM_MAX_HWBCS 128 +#define NVAPI_SYSTEM_HWBC_INVALID_ID 0xffffffff +#define NVAPI_MAX_AUDIO_DEVICES 16 + + +typedef char NvAPI_String[NVAPI_GENERIC_STRING_MAX]; +typedef char NvAPI_LongString[NVAPI_LONG_STRING_MAX]; +typedef char NvAPI_ShortString[NVAPI_SHORT_STRING_MAX]; +//! @} + + +// ========================================================================================= +//! NvAPI Version Definition \n +//! Maintain per structure specific version define using the MAKE_NVAPI_VERSION macro. \n +//! Usage: #define NV_GENLOCK_STATUS_VER MAKE_NVAPI_VERSION(NV_GENLOCK_STATUS, 1) +//! \ingroup nvapitypes +// ========================================================================================= +#define MAKE_NVAPI_VERSION(typeName,ver) (NvU32)(sizeof(typeName) | ((ver) << 16)) + +//! \ingroup nvapitypes +#define GET_NVAPI_VERSION(ver) (NvU32)((ver)>>16) + +//! \ingroup nvapitypes +#define GET_NVAPI_SIZE(ver) (NvU32)((ver) & 0xffff) + + +// ==================================================== +//! NvAPI Status Values +//! All NvAPI functions return one of these codes. +//! \ingroup nvapistatus +// ==================================================== + +typedef enum _NvAPI_Status +{ + NVAPI_OK = 0, //!< Success. Request is completed. + NVAPI_ERROR = -1, //!< Generic error + NVAPI_LIBRARY_NOT_FOUND = -2, //!< NVAPI support library cannot be loaded. + NVAPI_NO_IMPLEMENTATION = -3, //!< not implemented in current driver installation + NVAPI_API_NOT_INITIALIZED = -4, //!< NvAPI_Initialize has not been called (successfully) + NVAPI_INVALID_ARGUMENT = -5, //!< The argument/parameter value is not valid or NULL. + NVAPI_NVIDIA_DEVICE_NOT_FOUND = -6, //!< No NVIDIA display driver, or NVIDIA GPU driving a display, was found. + NVAPI_END_ENUMERATION = -7, //!< No more items to enumerate + NVAPI_INVALID_HANDLE = -8, //!< Invalid handle + NVAPI_INCOMPATIBLE_STRUCT_VERSION = -9, //!< An argument's structure version is not supported + NVAPI_HANDLE_INVALIDATED = -10, //!< The handle is no longer valid (likely due to GPU or display re-configuration) + NVAPI_OPENGL_CONTEXT_NOT_CURRENT = -11, //!< No NVIDIA OpenGL context is current (but needs to be) + NVAPI_INVALID_POINTER = -14, //!< An invalid pointer, usually NULL, was passed as a parameter + NVAPI_NO_GL_EXPERT = -12, //!< OpenGL Expert is not supported by the current drivers + NVAPI_INSTRUMENTATION_DISABLED = -13, //!< OpenGL Expert is supported, but driver instrumentation is currently disabled + NVAPI_NO_GL_NSIGHT = -15, //!< OpenGL does not support Nsight + + NVAPI_EXPECTED_LOGICAL_GPU_HANDLE = -100, //!< Expected a logical GPU handle for one or more parameters + NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE = -101, //!< Expected a physical GPU handle for one or more parameters + NVAPI_EXPECTED_DISPLAY_HANDLE = -102, //!< Expected an NV display handle for one or more parameters + NVAPI_INVALID_COMBINATION = -103, //!< The combination of parameters is not valid. + NVAPI_NOT_SUPPORTED = -104, //!< Requested feature is not supported in the selected GPU + NVAPI_PORTID_NOT_FOUND = -105, //!< No port ID was found for the I2C transaction + NVAPI_EXPECTED_UNATTACHED_DISPLAY_HANDLE = -106, //!< Expected an unattached display handle as one of the input parameters. + NVAPI_INVALID_PERF_LEVEL = -107, //!< Invalid perf level + NVAPI_DEVICE_BUSY = -108, //!< Device is busy; request not fulfilled + NVAPI_NV_PERSIST_FILE_NOT_FOUND = -109, //!< NV persist file is not found + NVAPI_PERSIST_DATA_NOT_FOUND = -110, //!< NV persist data is not found + NVAPI_EXPECTED_TV_DISPLAY = -111, //!< Expected a TV output display + NVAPI_EXPECTED_TV_DISPLAY_ON_DCONNECTOR = -112, //!< Expected a TV output on the D Connector - HDTV_EIAJ4120. + NVAPI_NO_ACTIVE_SLI_TOPOLOGY = -113, //!< SLI is not active on this device. + NVAPI_SLI_RENDERING_MODE_NOTALLOWED = -114, //!< Setup of SLI rendering mode is not possible right now. + NVAPI_EXPECTED_DIGITAL_FLAT_PANEL = -115, //!< Expected a digital flat panel. + NVAPI_ARGUMENT_EXCEED_MAX_SIZE = -116, //!< Argument exceeds the expected size. + NVAPI_DEVICE_SWITCHING_NOT_ALLOWED = -117, //!< Inhibit is ON due to one of the flags in NV_GPU_DISPLAY_CHANGE_INHIBIT or SLI active. + NVAPI_TESTING_CLOCKS_NOT_SUPPORTED = -118, //!< Testing of clocks is not supported. + NVAPI_UNKNOWN_UNDERSCAN_CONFIG = -119, //!< The specified underscan config is from an unknown source (e.g. INF) + NVAPI_TIMEOUT_RECONFIGURING_GPU_TOPO = -120, //!< Timeout while reconfiguring GPUs + NVAPI_DATA_NOT_FOUND = -121, //!< Requested data was not found + NVAPI_EXPECTED_ANALOG_DISPLAY = -122, //!< Expected an analog display + NVAPI_NO_VIDLINK = -123, //!< No SLI video bridge is present + NVAPI_REQUIRES_REBOOT = -124, //!< NVAPI requires a reboot for the settings to take effect + NVAPI_INVALID_HYBRID_MODE = -125, //!< The function is not supported with the current Hybrid mode. + NVAPI_MIXED_TARGET_TYPES = -126, //!< The target types are not all the same + NVAPI_SYSWOW64_NOT_SUPPORTED = -127, //!< The function is not supported from 32-bit on a 64-bit system. + NVAPI_IMPLICIT_SET_GPU_TOPOLOGY_CHANGE_NOT_ALLOWED = -128, //!< There is no implicit GPU topology active. Use NVAPI_SetHybridMode to change topology. + NVAPI_REQUEST_USER_TO_CLOSE_NON_MIGRATABLE_APPS = -129, //!< Prompt the user to close all non-migratable applications. + NVAPI_OUT_OF_MEMORY = -130, //!< Could not allocate sufficient memory to complete the call. + NVAPI_WAS_STILL_DRAWING = -131, //!< The previous operation that is transferring information to or from this surface is incomplete. + NVAPI_FILE_NOT_FOUND = -132, //!< The file was not found. + NVAPI_TOO_MANY_UNIQUE_STATE_OBJECTS = -133, //!< There are too many unique instances of a particular type of state object. + NVAPI_INVALID_CALL = -134, //!< The method call is invalid. For example, a method's parameter may not be a valid pointer. + NVAPI_D3D10_1_LIBRARY_NOT_FOUND = -135, //!< d3d10_1.dll cannot be loaded. + NVAPI_FUNCTION_NOT_FOUND = -136, //!< Couldn't find the function in the loaded DLL. + NVAPI_INVALID_USER_PRIVILEGE = -137, //!< Current User is not Admin. + NVAPI_EXPECTED_NON_PRIMARY_DISPLAY_HANDLE = -138, //!< The handle corresponds to GDIPrimary. + NVAPI_EXPECTED_COMPUTE_GPU_HANDLE = -139, //!< Setting Physx GPU requires that the GPU is compute-capable. + NVAPI_STEREO_NOT_INITIALIZED = -140, //!< The Stereo part of NVAPI failed to initialize completely. Check if the stereo driver is installed. + NVAPI_STEREO_REGISTRY_ACCESS_FAILED = -141, //!< Access to stereo-related registry keys or values has failed. + NVAPI_STEREO_REGISTRY_PROFILE_TYPE_NOT_SUPPORTED = -142, //!< The given registry profile type is not supported. + NVAPI_STEREO_REGISTRY_VALUE_NOT_SUPPORTED = -143, //!< The given registry value is not supported. + NVAPI_STEREO_NOT_ENABLED = -144, //!< Stereo is not enabled and the function needed it to execute completely. + NVAPI_STEREO_NOT_TURNED_ON = -145, //!< Stereo is not turned on and the function needed it to execute completely. + NVAPI_STEREO_INVALID_DEVICE_INTERFACE = -146, //!< Invalid device interface. + NVAPI_STEREO_PARAMETER_OUT_OF_RANGE = -147, //!< Separation percentage or JPEG image capture quality is out of [0-100] range. + NVAPI_STEREO_FRUSTUM_ADJUST_MODE_NOT_SUPPORTED = -148, //!< The given frustum adjust mode is not supported. + NVAPI_TOPO_NOT_POSSIBLE = -149, //!< The mosaic topology is not possible given the current state of the hardware. + NVAPI_MODE_CHANGE_FAILED = -150, //!< An attempt to do a display resolution mode change has failed. + NVAPI_D3D11_LIBRARY_NOT_FOUND = -151, //!< d3d11.dll/d3d11_beta.dll cannot be loaded. + NVAPI_INVALID_ADDRESS = -152, //!< Address is outside of valid range. + NVAPI_STRING_TOO_SMALL = -153, //!< The pre-allocated string is too small to hold the result. + NVAPI_MATCHING_DEVICE_NOT_FOUND = -154, //!< The input does not match any of the available devices. + NVAPI_DRIVER_RUNNING = -155, //!< Driver is running. + NVAPI_DRIVER_NOTRUNNING = -156, //!< Driver is not running. + NVAPI_ERROR_DRIVER_RELOAD_REQUIRED = -157, //!< A driver reload is required to apply these settings. + NVAPI_SET_NOT_ALLOWED = -158, //!< Intended setting is not allowed. + NVAPI_ADVANCED_DISPLAY_TOPOLOGY_REQUIRED = -159, //!< Information can't be returned due to "advanced display topology". + NVAPI_SETTING_NOT_FOUND = -160, //!< Setting is not found. + NVAPI_SETTING_SIZE_TOO_LARGE = -161, //!< Setting size is too large. + NVAPI_TOO_MANY_SETTINGS_IN_PROFILE = -162, //!< There are too many settings for a profile. + NVAPI_PROFILE_NOT_FOUND = -163, //!< Profile is not found. + NVAPI_PROFILE_NAME_IN_USE = -164, //!< Profile name is duplicated. + NVAPI_PROFILE_NAME_EMPTY = -165, //!< Profile name is empty. + NVAPI_EXECUTABLE_NOT_FOUND = -166, //!< Application not found in the Profile. + NVAPI_EXECUTABLE_ALREADY_IN_USE = -167, //!< Application already exists in the other profile. + NVAPI_DATATYPE_MISMATCH = -168, //!< Data Type mismatch + NVAPI_PROFILE_REMOVED = -169, //!< The profile passed as parameter has been removed and is no longer valid. + NVAPI_UNREGISTERED_RESOURCE = -170, //!< An unregistered resource was passed as a parameter. + NVAPI_ID_OUT_OF_RANGE = -171, //!< The DisplayId corresponds to a display which is not within the normal outputId range. + NVAPI_DISPLAYCONFIG_VALIDATION_FAILED = -172, //!< Display topology is not valid so the driver cannot do a mode set on this configuration. + NVAPI_DPMST_CHANGED = -173, //!< Display Port Multi-Stream topology has been changed. + NVAPI_INSUFFICIENT_BUFFER = -174, //!< Input buffer is insufficient to hold the contents. + NVAPI_ACCESS_DENIED = -175, //!< No access to the caller. + NVAPI_MOSAIC_NOT_ACTIVE = -176, //!< The requested action cannot be performed without Mosaic being enabled. + NVAPI_SHARE_RESOURCE_RELOCATED = -177, //!< The surface is relocated away from video memory. + NVAPI_REQUEST_USER_TO_DISABLE_DWM = -178, //!< The user should disable DWM before calling NvAPI. + NVAPI_D3D_DEVICE_LOST = -179, //!< D3D device status is D3DERR_DEVICELOST or D3DERR_DEVICENOTRESET - the user has to reset the device. + NVAPI_INVALID_CONFIGURATION = -180, //!< The requested action cannot be performed in the current state. + NVAPI_STEREO_HANDSHAKE_NOT_DONE = -181, //!< Call failed as stereo handshake not completed. + NVAPI_EXECUTABLE_PATH_IS_AMBIGUOUS = -182, //!< The path provided was too short to determine the correct NVDRS_APPLICATION + NVAPI_DEFAULT_STEREO_PROFILE_IS_NOT_DEFINED = -183, //!< Default stereo profile is not currently defined + NVAPI_DEFAULT_STEREO_PROFILE_DOES_NOT_EXIST = -184, //!< Default stereo profile does not exist + NVAPI_CLUSTER_ALREADY_EXISTS = -185, //!< A cluster is already defined with the given configuration. + NVAPI_DPMST_DISPLAY_ID_EXPECTED = -186, //!< The input display id is not that of a multi stream enabled connector or a display device in a multi stream topology + NVAPI_INVALID_DISPLAY_ID = -187, //!< The input display id is not valid or the monitor associated to it does not support the current operation + NVAPI_STREAM_IS_OUT_OF_SYNC = -188, //!< While playing secure audio stream, stream goes out of sync + NVAPI_INCOMPATIBLE_AUDIO_DRIVER = -189, //!< Older audio driver version than required + NVAPI_VALUE_ALREADY_SET = -190, //!< Value already set, setting again not allowed. + NVAPI_TIMEOUT = -191, //!< Requested operation timed out + NVAPI_GPU_WORKSTATION_FEATURE_INCOMPLETE = -192, //!< The requested workstation feature set has incomplete driver internal allocation resources + NVAPI_STEREO_INIT_ACTIVATION_NOT_DONE = -193, //!< Call failed because InitActivation was not called. + NVAPI_SYNC_NOT_ACTIVE = -194, //!< The requested action cannot be performed without Sync being enabled. + NVAPI_SYNC_MASTER_NOT_FOUND = -195, //!< The requested action cannot be performed without Sync Master being enabled. + NVAPI_INVALID_SYNC_TOPOLOGY = -196, //!< Invalid displays passed in the NV_GSYNC_DISPLAY pointer. + NVAPI_ECID_SIGN_ALGO_UNSUPPORTED = -197, //!< The specified signing algorithm is not supported. Either an incorrect value was entered or the current installed driver/hardware does not support the input value. + NVAPI_ECID_KEY_VERIFICATION_FAILED = -198, //!< The encrypted public key verification has failed. +} NvAPI_Status; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_SYS_GetDriverAndBranchVersion +// +//! DESCRIPTION: This API returns display driver version and driver-branch string. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! \param [out] pDriverVersion Contains the driver version after successful return. +//! \param [out] szBuildBranchString Contains the driver-branch string after successful return. +//! +//! \retval ::NVAPI_INVALID_ARGUMENT: either pDriverVersion is NULL or enum index too big +//! \retval ::NVAPI_OK - completed request +//! \retval ::NVAPI_API_NOT_INTIALIZED - NVAPI not initialized +//! \retval ::NVAPI_ERROR - miscellaneous error occurred +//! +//! \ingroup driverapi +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_SYS_GetDriverAndBranchVersion)(_Out_ NvU32* pDriverVersion, _Out_ NvAPI_ShortString szBuildBranchString); +_NvAPI_SYS_GetDriverAndBranchVersion NvAPI_SYS_GetDriverAndBranchVersion; + + +//! \ingroup driverapi +//! Used in NvAPI_GPU_GetMemoryInfo(). +typedef struct _NV_DISPLAY_DRIVER_MEMORY_INFO_V1 +{ + NvU32 version; //!< Version info + NvU32 dedicatedVideoMemory; //!< Size(in kb) of the physical framebuffer. + NvU32 availableDedicatedVideoMemory; //!< Size(in kb) of the available physical framebuffer for allocating video memory surfaces. + NvU32 systemVideoMemory; //!< Size(in kb) of system memory the driver allocates at load time. + NvU32 sharedSystemMemory; //!< Size(in kb) of shared system memory that driver is allowed to commit for surfaces across all allocations. +} NV_DISPLAY_DRIVER_MEMORY_INFO_V1; + + +//! \ingroup driverapi +//! Used in NvAPI_GPU_GetMemoryInfo(). +typedef struct _NV_DISPLAY_DRIVER_MEMORY_INFO_V2 +{ + NvU32 version; //!< Version info + NvU32 dedicatedVideoMemory; //!< Size(in kb) of the physical framebuffer. + NvU32 availableDedicatedVideoMemory; //!< Size(in kb) of the available physical framebuffer for allocating video memory surfaces. + NvU32 systemVideoMemory; //!< Size(in kb) of system memory the driver allocates at load time. + NvU32 sharedSystemMemory; //!< Size(in kb) of shared system memory that driver is allowed to commit for surfaces across all allocations. + NvU32 curAvailableDedicatedVideoMemory; //!< Size(in kb) of the current available physical framebuffer for allocating video memory surfaces. +} NV_DISPLAY_DRIVER_MEMORY_INFO_V2; + + +//! \ingroup driverapi +typedef NV_DISPLAY_DRIVER_MEMORY_INFO_V2 NV_DISPLAY_DRIVER_MEMORY_INFO; + +//! \ingroup driverapi +//! Macro for constructing the version field of NV_DISPLAY_DRIVER_MEMORY_INFO_V1 +#define NV_DISPLAY_DRIVER_MEMORY_INFO_VER_1 MAKE_NVAPI_VERSION(NV_DISPLAY_DRIVER_MEMORY_INFO_V1, 1) + +//! \ingroup driverapi +//! Macro for constructing the version field of NV_DISPLAY_DRIVER_MEMORY_INFO_V2 +#define NV_DISPLAY_DRIVER_MEMORY_INFO_VER_2 MAKE_NVAPI_VERSION(NV_DISPLAY_DRIVER_MEMORY_INFO_V2, 2) + +//! \ingroup driverapi +#define NV_DISPLAY_DRIVER_MEMORY_INFO_VER NV_DISPLAY_DRIVER_MEMORY_INFO_VER_2 + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetMemoryInfo +// +//! DESCRIPTION: This function retrieves the available driver memory footprint for the specified GPU. +//! +//! SUPPORTED OS: Windows XP and higher +//! +//! +//! TCC_SUPPORTED +//! +//! \since Release: 177 +//! +//! \param [in] hPhysicalGpu Handle of the physical GPU for which the memory information is to be extracted. +//! \param [out] pMemoryInfo The memory footprint available in the driver. See NV_DISPLAY_DRIVER_MEMORY_INFO. +//! +//! \retval NVAPI_INVALID_ARGUMENT pMemoryInfo is NULL. +//! \retval NVAPI_OK Call successful. +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found. +//! \retval NVAPI_INCOMPATIBLE_STRUCT_VERSION NV_DISPLAY_DRIVER_MEMORY_INFO structure version mismatch. +//! +//! \ingroup driverapi +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetMemoryInfo)(NvDisplayHandle hNvDisplay, NV_DISPLAY_DRIVER_MEMORY_INFO *pMemoryInfo); +_NvAPI_GPU_GetMemoryInfo NvAPI_GPU_GetMemoryInfo; + + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_EnumPhysicalGPUs +// +//! This function returns an array of physical GPU handles. +//! Each handle represents a physical GPU present in the system. +//! That GPU may be part of an SLI configuration, or may not be visible to the OS directly. +//! +//! At least one GPU must be present in the system and running an NVIDIA display driver. +//! +//! The array nvGPUHandle will be filled with physical GPU handle values. The returned +//! gpuCount determines how many entries in the array are valid. +//! +//! \note In drivers older than 105.00, all physical GPU handles get invalidated on a +//! modeset. So the calling applications need to renum the handles after every modeset.\n +//! With drivers 105.00 and up, all physical GPU handles are constant. +//! Physical GPU handles are constant as long as the GPUs are not physically moved and +//! the SBIOS VGA order is unchanged. +//! +//! For GPU handles in TCC MODE please use NvAPI_EnumTCCPhysicalGPUs() +//! +//! SUPPORTED OS: Windows XP and higher, Mac OS X +//! +//! +//! \par Introduced in +//! \since Release: 80 +//! +//! \retval NVAPI_INVALID_ARGUMENT nvGPUHandle or pGpuCount is NULL +//! \retval NVAPI_OK One or more handles were returned +//! \retval NVAPI_NVIDIA_DEVICE_NOT_FOUND No NVIDIA GPU driving a display was found +//! \ingroup gpu +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_EnumPhysicalGPUs)(NvPhysicalGpuHandle nvGPUHandle[NVAPI_MAX_PHYSICAL_GPUS], NvU32* pGpuCount); +_NvAPI_EnumPhysicalGPUs NvAPI_EnumPhysicalGPUs; + +#ifdef __cplusplus +} +#endif + +#include \ No newline at end of file diff --git a/plugins/HardwareDevices/nvidia.c b/plugins/HardwareDevices/nvidia.c new file mode 100644 index 0000000..1a6412e --- /dev/null +++ b/plugins/HardwareDevices/nvidia.c @@ -0,0 +1,1035 @@ +/* + * Process Hacker Extra Plugins - + * Nvidia GPU Plugin + * + * Copyright (C) 2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#define INITGUID +#include "devices.h" +#include "nvapi\nvapi.h" +#include "nvidia.h" + +#ifdef _NV_GPU_BUILD + +BOOLEAN NvApiInitialized = FALSE; +static PVOID NvApiLibrary = NULL; +static PPH_LIST NvGpuPhysicalHandleList = NULL; +static PPH_LIST NvGpuDisplayHandleList = NULL; +static NvU32 GpuArchType = 0; + +ULONG GpuMemoryLimit = 0; +FLOAT GpuCurrentGpuUsage = 0.0f; +FLOAT GpuCurrentCoreUsage = 0.0f; +FLOAT GpuCurrentBusUsage = 0.0f; +ULONG GpuCurrentMemUsage = 0; +ULONG GpuCurrentMemSharedUsage = 0; +ULONG GpuCurrentCoreTemp = 0; +ULONG GpuCurrentBoardTemp = 0; +ULONG GpuCurrentCoreClock = 0; +ULONG GpuCurrentMemoryClock = 0; +ULONG GpuCurrentShaderClock = 0; +ULONG GpuCurrentVoltage = 0; +//NVAPI_GPU_PERF_DECREASE GpuPerfDecreaseReason = NV_GPU_PERF_DECREASE_NONE; + +static VOID NvGpuEnumPhysicalHandles(VOID) +{ + NvU32 gpuCount = 0; + NvPhysicalGpuHandle gpuHandles[NVAPI_MAX_PHYSICAL_GPUS]; + + memset(gpuHandles, 0, sizeof(gpuHandles)); + + if (NvAPI_EnumPhysicalGPUs(gpuHandles, &gpuCount) == NVAPI_OK) + { + PhAddItemsList(NvGpuPhysicalHandleList, gpuHandles, gpuCount); + } +} + +static VOID NvGpuEnumDisplayHandles(VOID) +{ + for (NvU32 i = 0; i < NVAPI_MAX_DISPLAYS; i++) + { + NvDisplayHandle displayHandle; + + if (NvAPI_EnumNvidiaDisplayHandle(i, &displayHandle) == NVAPI_END_ENUMERATION) + { + break; + } + + PhAddItemList(NvGpuDisplayHandleList, displayHandle); + } +} + +VOID NvApiInitialize(VOID) +{ + ULONG sampleCount; + + if (!PhGetIntegerSetting(SETTING_NAME_ENABLE_GPU)) + return; + + sampleCount = PhGetIntegerSetting(L"SampleCount"); + PhInitializeCircularBuffer_FLOAT(&GpuUtilizationHistory, sampleCount); + PhInitializeCircularBuffer_ULONG(&GpuMemoryHistory, sampleCount); + PhInitializeCircularBuffer_FLOAT(&GpuBoardHistory, sampleCount); + PhInitializeCircularBuffer_FLOAT(&GpuBusHistory, sampleCount); + + NvGpuPhysicalHandleList = PhCreateList(1); + NvGpuDisplayHandleList = PhCreateList(1); + +#ifdef _M_IX86 + if (!(NvApiLibrary = LoadLibrary(L"nvapi.dll"))) + return; +#else + if (!(NvApiLibrary = LoadLibrary(L"nvapi64.dll"))) + return; +#endif + + // Retrieve the NvAPI_QueryInterface function address + if (!(NvAPI_QueryInterface = PhGetProcedureAddress(NvApiLibrary, "nvapi_QueryInterface", 0))) + return; + + // Initialization functions + if (!(NvAPI_Initialize = NvAPI_QueryInterface(0x150E828UL))) + return; + if (!(NvAPI_Unload = NvAPI_QueryInterface(0xD22BDD7EUL))) + return; + + // Error functions + NvAPI_GetErrorMessage = NvAPI_QueryInterface(0x6C2D048CUL); + + // Handle functions + NvAPI_EnumPhysicalGPUs = NvAPI_QueryInterface(0xE5AC921FUL); + NvAPI_EnumNvidiaDisplayHandle = NvAPI_QueryInterface(0x9ABDD40DUL); + + // Information functions + NvAPI_SYS_GetDriverAndBranchVersion = NvAPI_QueryInterface(0x2926AAADUL); + NvAPI_GPU_GetFullName = NvAPI_QueryInterface(0xCEEE8E9FUL); + + // Query functions + NvAPI_GPU_GetMemoryInfo = NvAPI_QueryInterface(0x774AA982UL); + NvAPI_GPU_GetThermalSettings = NvAPI_QueryInterface(0xE3640A56UL); + NvAPI_GPU_GetCoolerSettings = NvAPI_QueryInterface(0xDA141340UL); + NvAPI_GPU_GetPerfDecreaseInfo = NvAPI_QueryInterface(0x7F7F4600UL); + NvAPI_GPU_GetTachReading = NvAPI_QueryInterface(0x5F608315UL); + NvAPI_GPU_GetAllClockFrequencies = NvAPI_QueryInterface(0xDCB616C3UL); + + // Undocumented functions below + + NvAPI_GPU_GetUsages = NvAPI_QueryInterface(0x189A1FDFUL); + NvAPI_GPU_GetAllClocks = NvAPI_QueryInterface(0x1BD69F49UL); + NvAPI_GPU_GetVoltageDomainsStatus = NvAPI_QueryInterface(0xC16C7E2CUL); + + NvAPI_GPU_GetPerfClocks = NvAPI_QueryInterface(0x1EA54A3B); + NvAPI_GPU_GetVoltages = NvAPI_QueryInterface(0x7D656244); + NvAPI_GPU_QueryActiveApps = NvAPI_QueryInterface(0x65B1C5F5); + NvAPI_GPU_GetShaderPipeCount = NvAPI_QueryInterface(0x63E2F56F); + NvAPI_GPU_GetShaderSubPipeCount = NvAPI_QueryInterface(0x0BE17923); + NvAPI_GPU_GetRamBusWidth = NvAPI_QueryInterface(0x7975C581); + NvAPI_GPU_GetRamBankCount = NvAPI_QueryInterface(0x17073A3CUL); + NvAPI_GPU_GetRamType = NvAPI_QueryInterface(0x57F7CAACUL); + NvAPI_GPU_GetRamMaker = NvAPI_QueryInterface(0x42AEA16AUL); + NvAPI_GPU_GetFoundry = NvAPI_QueryInterface(0x5D857A00UL); + //NvAPI_GetDisplayDriverMemoryInfo = NvAPI_QueryInterface(0x774AA982); + //NvAPI_GetPhysicalGPUsFromDisplay = NvAPI_QueryInterface(0x34EF9506); + NvAPI_GetDisplayDriverVersion = NvAPI_QueryInterface(0xF951A4D1UL); + NvAPI_GetDisplayDriverRegistryPath = NvAPI_QueryInterface(0x0E24CEEEUL); + //NvAPI_RestartDisplayDriver = NvAPI_QueryInterface(0xB4B26B65UL); + //NvAPI_GPU_GetBoardInfo = NvAPI_QueryInterface(0x22D54523); + //NvAPI_GPU_GetBusType = NvAPI_QueryInterface(0x1BB18724); + //NvAPI_GPU_GetIRQ = NvAPI_QueryInterface(0xE4715417); + NvAPI_GPU_GetVbiosVersionString = NvAPI_QueryInterface(0xA561FD7DUL); + NvAPI_GPU_GetShortName = NvAPI_QueryInterface(0xD988F0F3UL); + NvAPI_GPU_GetArchInfo = NvAPI_QueryInterface(0xD8265D24UL); + NvAPI_GPU_GetPCIIdentifiers = NvAPI_QueryInterface(0x2DDFB66EUL); + NvAPI_GPU_GetPartitionCount = NvAPI_QueryInterface(0x86F05D7AUL); + NvAPI_GPU_GetGpuCoreCount = NvAPI_QueryInterface(0xC7026A87UL); + NvAPI_GPU_GetPCIEInfo = NvAPI_QueryInterface(0xE3795199UL); + NvAPI_GPU_GetFBWidthAndLocation = NvAPI_QueryInterface(0x11104158UL); + NvAPI_GPU_ClientPowerTopologyGetStatus = NvAPI_QueryInterface(0x0EDCF624EUL); + NvAPI_GetDisplayDriverBuildTitle = NvAPI_QueryInterface(0x7562E947); + NvAPI_GetDisplayDriverCompileType = NvAPI_QueryInterface(0x988AEA78); + NvAPI_GetDisplayDriverSecurityLevel = NvAPI_QueryInterface(0x9D772BBA); + NvAPI_GPU_GetVPECount = NvAPI_QueryInterface(0xD8CBF37B); + NvAPI_GPU_GetTargetID = NvAPI_QueryInterface(0x35B5FD2F); + //NvAPI_GPU_GetExtendedMinorRevision = NvAPI_QueryInterface(0x025F17421); + //NvAPI_GPU_GetSerialNumber = NvAPI_QueryInterface(0x14B83A5F); + + if (NvAPI_Initialize() == NVAPI_OK) + { + NvGpuEnumPhysicalHandles(); + NvGpuEnumDisplayHandles(); + + NvApiInitialized = TRUE; + } +} + +BOOLEAN DestroyNvApi(VOID) +{ + NvApiInitialized = FALSE; + + if (NvGpuDisplayHandleList) + PhDereferenceObject(NvGpuDisplayHandleList); + + if (NvGpuPhysicalHandleList) + PhDereferenceObject(NvGpuPhysicalHandleList); + + if (NvAPI_Unload) + NvAPI_Unload(); + + if (NvApiLibrary) + FreeLibrary(NvApiLibrary); + + return TRUE; +} + +PPH_STRING NvGpuQueryDriverVersion(VOID) +{ + if (NvApiInitialized && NvAPI_SYS_GetDriverAndBranchVersion) + { + NvU32 driverVersion = 0; + NvAPI_ShortString driverAndBranchString = ""; + + if (NvAPI_SYS_GetDriverAndBranchVersion(&driverVersion, driverAndBranchString) == NVAPI_OK) + { + return PhFormatString( + L"%lu.%lu [%hs]", + driverVersion / 100, + driverVersion % 100, + driverAndBranchString + ); + } + } + + if (NvApiInitialized && NvAPI_GetDisplayDriverVersion) + { + NV_DISPLAY_DRIVER_VERSION nvDisplayDriverVersion = { NV_DISPLAY_DRIVER_VERSION_VER }; + + if (NvAPI_GetDisplayDriverVersion(NvGpuDisplayHandleList->Items[0], &nvDisplayDriverVersion) == NVAPI_OK) + { + return PhFormatString( + L"%lu.%lu [%hs]", + nvDisplayDriverVersion.drvVersion / 100, + nvDisplayDriverVersion.drvVersion % 100, + nvDisplayDriverVersion.szBuildBranchString + ); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryVbiosVersionString(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetVbiosVersionString) + { + NvAPI_ShortString biosRevision = ""; + + if (NvAPI_GPU_GetVbiosVersionString(NvGpuPhysicalHandleList->Items[0], biosRevision) == NVAPI_OK) + { + return PhConvertMultiByteToUtf16(biosRevision); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryName(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetFullName) + { + NvAPI_ShortString nvNameAnsiString = ""; + + if (NvAPI_GPU_GetFullName(NvGpuPhysicalHandleList->Items[0], nvNameAnsiString) == NVAPI_OK) + { + return PhConvertMultiByteToUtf16(nvNameAnsiString); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryShortName(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetShortName) + { + NvAPI_ShortString nvShortNameAnsiString = ""; + + if (NvAPI_GPU_GetShortName(NvGpuPhysicalHandleList->Items[0], nvShortNameAnsiString) == NVAPI_OK) + { + return PhConvertMultiByteToUtf16(nvShortNameAnsiString); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryRevision(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetArchInfo) + { + NV_ARCH_INFO nvArchInfo = { NV_ARCH_INFO_VER }; + + if (NvAPI_GPU_GetArchInfo( + NvGpuPhysicalHandleList->Items[0], + &nvArchInfo + ) == NVAPI_OK) + { + GpuArchType = nvArchInfo.unknown[0]; + + return PhFormatString(L"%02X", nvArchInfo.unknown[2]); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryRamType(VOID) +{ + if (NvApiInitialized) + { + PPH_STRING ramTypeString = NULL; + PPH_STRING ramMakerString = NULL; + NV_RAM_TYPE nvRamType = NV_RAM_TYPE_NONE; + NV_RAM_MAKER nvRamMaker = NV_RAM_MAKER_NONE; + + if (NvAPI_GPU_GetRamType) + { + NvAPI_GPU_GetRamType(NvGpuPhysicalHandleList->Items[0], &nvRamType); + } + + if (NvAPI_GPU_GetRamMaker) + { + NvAPI_GPU_GetRamMaker(NvGpuPhysicalHandleList->Items[0], &nvRamMaker); + } + + switch (nvRamType) + { + case NV_RAM_TYPE_SDRAM: + ramTypeString = PhaCreateString(L"SDRAM"); + break; + case NV_RAM_TYPE_DDR1: + ramTypeString = PhaCreateString(L"DDR1"); + break; + case NV_RAM_TYPE_DDR2: + ramTypeString = PhaCreateString(L"DDR2"); + break; + case NV_RAM_TYPE_GDDR2: + ramTypeString = PhaCreateString(L"GDDR2"); + break; + case NV_RAM_TYPE_GDDR3: + ramTypeString = PhaCreateString(L"GDDR3"); + break; + case NV_RAM_TYPE_GDDR4: + ramTypeString = PhaCreateString(L"GDDR4"); + break; + case NV_RAM_TYPE_DDR3: + ramTypeString = PhaCreateString(L"DDR3"); + break; + case NV_RAM_TYPE_GDDR5: + ramTypeString = PhaCreateString(L"GDDR5"); + break; + case NV_RAM_TYPE_LPDDR2: + ramTypeString = PhaCreateString(L"LPDDR2"); + break; + default: + ramTypeString = PhaFormatString(L"Unknown: %lu", nvRamType); + break; + } + + switch (nvRamMaker) + { + case NV_RAM_MAKER_SAMSUNG: + ramMakerString = PhaCreateString(L"Samsung"); + break; + case NV_RAM_MAKER_QIMONDA: + ramMakerString = PhaCreateString(L"Qimonda"); + break; + case NV_RAM_MAKER_ELPIDA: + ramMakerString = PhaCreateString(L"Elpida"); + break; + case NV_RAM_MAKER_ETRON: + ramMakerString = PhaCreateString(L"Etron"); + break; + case NV_RAM_MAKER_NANYA: + ramMakerString = PhaCreateString(L"Nanya"); + break; + case NV_RAM_MAKER_HYNIX: + ramMakerString = PhaCreateString(L"Hynix"); + break; + case NV_RAM_MAKER_MOSEL: + ramMakerString = PhaCreateString(L"Mosel"); + break; + case NV_RAM_MAKER_WINBOND: + ramMakerString = PhaCreateString(L"Winbond"); + break; + case NV_RAM_MAKER_ELITE: + ramMakerString = PhaCreateString(L"Elite"); + break; + case NV_RAM_MAKER_MICRON: + ramMakerString = PhaCreateString(L"Micron"); + break; + default: + ramMakerString = PhaFormatString(L"Unknown: %lu", nvRamMaker); + break; + } + + return PhFormatString(L"%s (%s)", ramTypeString->Buffer, ramMakerString->Buffer); + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryFoundry(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetFoundry) + { + NV_FOUNDRY nvFoundryType = NV_FOUNDRY_NONE; + + if (NvAPI_GPU_GetFoundry( + NvGpuPhysicalHandleList->Items[0], + &nvFoundryType + ) == NVAPI_OK) + { + switch (nvFoundryType) + { + case NV_FOUNDRY_TSMC: + return PhCreateString(L"Taiwan Semiconductor Manufacturing Company (TSMC)"); + case NV_FOUNDRY_UMC: + return PhCreateString(L"United Microelectronics Corporation (UMC)"); + case NV_FOUNDRY_IBM: + return PhCreateString(L"IBM Microelectronics"); + case NV_FOUNDRY_SMIC: + return PhCreateString(L"Semiconductor Manufacturing International Corporation (SMIC)"); + case NV_FOUNDRY_CSM: + return PhCreateString(L"Chartered Semiconductor Manufacturing (CSM)"); + case NV_FOUNDRY_TOSHIBA: + return PhCreateString(L"Toshiba Corporation"); + default: + return PhFormatString(L"Unknown: %lu", nvFoundryType); + } + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryDeviceId(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetPCIIdentifiers) + { + NvU32 pDeviceId = 0; + NvU32 pSubSystemId = 0; + NvU32 pRevisionId = 0; + NvU32 pExtDeviceId = 0; + + if (NvAPI_GPU_GetPCIIdentifiers( + NvGpuPhysicalHandleList->Items[0], + &pDeviceId, + &pSubSystemId, + &pRevisionId, + &pExtDeviceId + ) == NVAPI_OK) + { + return PhFormatString(L"%04X - %04X", pDeviceId & 65535, pDeviceId >> 16); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryRopsCount(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetPartitionCount) + { + NvU32 pCount = 0; + + if (NvAPI_GPU_GetPartitionCount(NvGpuPhysicalHandleList->Items[0], &pCount) == NVAPI_OK) + { + if (GpuArchType >= 0x120) + { + return PhFormatString(L"%lu", pCount * 16); + } + else if (GpuArchType >= 0x0c0) + { + return PhFormatString(L"%lu", pCount * 8); + } + else + { + return PhFormatString(L"%lu", pCount * 4); + } + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryShaderCount(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetGpuCoreCount) + { + NvU32 pCount = 0; + + if (NvAPI_GPU_GetGpuCoreCount(NvGpuPhysicalHandleList->Items[0], &pCount) == NVAPI_OK) + { + return PhFormatString(L"%lu Unified", pCount); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryPciInfo(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetPCIEInfo) + { + NV_PCIE_INFO pciInfo = { NV_PCIE_INFO_VER }; + + if (NvAPI_GPU_GetPCIEInfo(NvGpuPhysicalHandleList->Items[0], &pciInfo) == NVAPI_OK) + { + return PhFormatString(L"%lu @ %lu %lu", + pciInfo.info[1].unknown1, + pciInfo.info[0].unknown5, + pciInfo.info[0].unknown6 + ); + } + } + + return PhCreateString(L"N/A"); +} + +PPH_STRING NvGpuQueryBusWidth(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_GetFBWidthAndLocation) + { + NvU32 pWidth = 0; + NvU32 pLocation = 0; + + if (NvAPI_GPU_GetFBWidthAndLocation(NvGpuPhysicalHandleList->Items[0], &pWidth, &pLocation) == NVAPI_OK) + { + return PhFormatString(L"%lu Bit", pWidth); + } + } + + return PhCreateString(L"N/A"); +} + + +PPH_STRING NvGpuQueryPcbValue(VOID) +{ + if (NvApiInitialized && NvAPI_GPU_ClientPowerTopologyGetStatus) + { + NV_POWER_TOPOLOGY_STATUS nvPowerTopologyStatus = { NV_POWER_TOPOLOGY_STATUS_VER }; + + if (NvAPI_GPU_ClientPowerTopologyGetStatus(NvGpuPhysicalHandleList->Items[0], &nvPowerTopologyStatus) == NVAPI_OK) + { + for (NvU32 i = 0; i < nvPowerTopologyStatus.count; i++) + { + NV_POWER_TOPOLOGY_2 powerTopology = nvPowerTopologyStatus.unknown[i]; + + if (powerTopology.flags == NV_POWER_TOPOLOGY_FLAG_ENABLED) + { + return PhFormatString(L"PMU: %.2f%%", (FLOAT)powerTopology.unknown.unknown2 / 1000); + } + } + } + } + + return PhCreateString(L"N/A"); +} + + +PPH_STRING NvGpuQueryDriverSettings(VOID) +{ + if (NvApiInitialized && NvAPI_GetDisplayDriverRegistryPath) + { + NvAPI_LongString nvKeyPathAnsiString = ""; + + if (NvAPI_GetDisplayDriverRegistryPath(NvGpuDisplayHandleList->Items[0], nvKeyPathAnsiString) == NVAPI_OK) + { + HANDLE keyHandle; + PPH_STRING keyPath; + + keyPath = PhConvertMultiByteToUtf16(nvKeyPathAnsiString); + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyPath->sr, + 0 + ))) + { + PPH_STRING driverDateString = NULL;// PhQueryRegistryString(keyHandle, L"DriverDate"); + PPH_STRING driverVersionString = PhQueryRegistryString(keyHandle, L"DriverVersion"); + + UNICODE_STRING valueName; + PKEY_VALUE_PARTIAL_INFORMATION buffer = NULL; + ULONG bufferSize; + + RtlInitUnicodeString(&valueName, L"DriverDateData"); + + if (NtQueryValueKey( + keyHandle, + &valueName, + KeyValuePartialInformation, + NULL, + 0, + &bufferSize + ) == STATUS_BUFFER_TOO_SMALL) + { + buffer = PhAllocate(bufferSize); + + if (NT_SUCCESS(NtQueryValueKey( + keyHandle, + &valueName, + KeyValuePartialInformation, + buffer, + bufferSize, + &bufferSize + ))) + { + if (buffer->Type == REG_BINARY && buffer->DataLength == sizeof(FILETIME)) + { + SYSTEMTIME systemTime; + SYSTEMTIME localTime; + + FileTimeToSystemTime((const FILETIME*)buffer->Data, &systemTime); + SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localTime); + + driverDateString = PhFormatDate(&localTime, NULL); + } + } + + PhFree(buffer); + } + + NtClose(keyHandle); + PhDereferenceObject(keyPath); + PhAutoDereferenceObject(driverVersionString); + + if (driverDateString) + { + PhAutoDereferenceObject(driverDateString); + return PhFormatString(L"%s [%s]", driverVersionString->Buffer, driverDateString->Buffer); + } + else + { + return PhFormatString(L"%s", driverVersionString->Buffer); + } + } + + PhDereferenceObject(keyPath); + } + } + + return PhCreateString(L"N/A"); +} + + + +BOOLEAN NvGpuDriverIsWHQL(VOID) +{ + BOOLEAN nvGpuDriverIsWHQL = FALSE; + //NvAPI_LongString nvNameAnsiString = ""; + + //HANDLE keyHandle; + ////HANDLE keySettingsHandle; + + //PPH_STRING keyPath; + //PPH_STRING matchingDeviceIdString; + ////PPH_STRING keySettingsPath; + //PPH_STRING keyServicePath; + + //WCHAR displayInstancePath[MAX_PATH] = L""; + + //HDEVINFO deviceInfoHandle = INVALID_HANDLE_VALUE; + //SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) }; + //SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + //PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetail = NULL; + //ULONG deviceInfoLength = 0; + + //__try + //{ + // if (!NvApiInitialized) + // __leave; + + // if (!NvAPI_GetDisplayDriverRegistryPath) + // __leave; + + // if (NvAPI_GetDisplayDriverRegistryPath(NvGpuDisplayHandleList->Items[0], nvNameAnsiString) != NVAPI_OK) + // __leave; + + // keyPath = PhConvertMultiByteToUtf16(nvNameAnsiString); + + // if (!NT_SUCCESS(PhOpenKey( + // &keyHandle, + // KEY_READ, + // PH_KEY_LOCAL_MACHINE, + // &keyPath->sr, + // 0 + // ))) + // { + // __leave; + // } + + // matchingDeviceIdString = PhQueryRegistryString(keyHandle, L"MatchingDeviceId"); + + //keySettingsPath = PhConcatStrings2(keyPath->Buffer, L"\\VolatileSettings"); + + //if (NT_SUCCESS(PhOpenKey( + // &keySettingsHandle, + // KEY_READ, + // PH_KEY_LOCAL_MACHINE, + // &keySettingsPath->sr, + // 0 + // ))) + //{ + // GUID settingsKey = GUID_DEVINTERFACE_DISPLAY_ADAPTER; + // PPH_STRING guidString = PhFormatGuid(&settingsKey); + // + // ULONG dwType = REG_BINARY; + // LONG length = MAX_PATH; + // + // if (RegQueryValueEx( + // keySettingsHandle, + // guidString->Buffer, + // 0, + // &dwType, + // (PBYTE)displayInstancePath, + // &length + // ) != ERROR_SUCCESS) + // { + // //__leave; + // } + // + // NtClose(keySettingsHandle); + // PhDereferenceObject(guidString); + //} + + // if ((deviceInfoHandle = SetupDiGetClassDevs( + // &GUID_DEVINTERFACE_DISPLAY_ADAPTER, + // NULL, + // NULL, + // DIGCF_PRESENT | DIGCF_DEVICEINTERFACE + // )) == INVALID_HANDLE_VALUE) + // { + // __leave; + // } + + // for (ULONG i = 0; i < 1000; i++) + // { + // ULONG devicePropertyLength = 0; + // DEVPROPTYPE devicePropertyType = 0; + // HANDLE keyServiceHandle; + // WCHAR matchingDeviceId[MAX_PATH] = L""; + // WCHAR deviceServiceName[MAX_PATH] = L""; + + // if (!SetupDiEnumDeviceInterfaces(deviceInfoHandle, 0, &GUID_DEVINTERFACE_DISPLAY_ADAPTER, i, &deviceInterfaceData)) + // break; + + // if (SetupDiGetDeviceInterfaceDetail( + // deviceInfoHandle, + // &deviceInterfaceData, + // 0, + // 0, + // &deviceInfoLength, + // &deviceInfoData + // ) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + // { + // continue; + // } + + // deviceInterfaceDetail = PhAllocate(deviceInfoLength); + // deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // if (!SetupDiGetDeviceInterfaceDetail( + // deviceInfoHandle, + // &deviceInterfaceData, + // deviceInterfaceDetail, + // deviceInfoLength, + // &deviceInfoLength, + // &deviceInfoData + // )) + // { + // continue; + // } + + // if (!SetupDiGetDeviceProperty( + // deviceInfoHandle, + // &deviceInfoData, + // &DEVPKEY_Device_MatchingDeviceId, + // &devicePropertyType, + // (PBYTE)&matchingDeviceId, + // sizeof(matchingDeviceId), + // &devicePropertyLength, + // 0 + // )) + // { + // continue; + // } + + + // //if (PhEqualStringZ(deviceInterfaceDetail->DevicePath, displayInstancePath, TRUE)) + // if (!PhEqualStringZ(matchingDeviceId, matchingDeviceIdString->Buffer, TRUE)) + // continue; + + // if (!SetupDiGetDeviceProperty( + // deviceInfoHandle, + // &deviceInfoData, + // &DEVPKEY_Device_Service, + // &devicePropertyType, + // (PBYTE)&deviceServiceName, + // sizeof(deviceServiceName), + // &devicePropertyLength, + // 0 + // )) + // { + // continue; + // } + + // keyServicePath = PhConcatStrings2(L"System\\CurrentControlSet\\Services\\", deviceServiceName); + + // if (NT_SUCCESS(PhOpenKey( + // &keyServiceHandle, + // KEY_READ, + // PH_KEY_LOCAL_MACHINE, + // &keyServicePath->sr, + // 0 + // ))) + // { + // PPH_STRING driverNtPathString = NULL; + // PPH_STRING driverDosPathString = NULL; + + // if (driverNtPathString = PhQueryRegistryString(keyServiceHandle, L"ImagePath")) + // { + // driverDosPathString = PhGetFileName(driverNtPathString); + // PhDereferenceObject(driverNtPathString); + // } + + // if (driverDosPathString) + // { + // PPH_STRING fileSignerName = NULL; + // //PH_MAPPED_IMAGE fileMappedImage; + // // + // //if (NT_SUCCESS(PhLoadMappedImage(driverDosPathString->Buffer, NULL, TRUE, &fileMappedImage))) + // //{ + // // LARGE_INTEGER time; + // // SYSTEMTIME systemTime; + // // PPH_STRING string; + // // + // // RtlSecondsSince1970ToTime(fileMappedImage.NtHeaders->FileHeader.TimeDateStamp, &time); + // // PhLargeIntegerToLocalSystemTime(&systemTime, &time); + // // + // // string = PhFormatDateTime(&systemTime); + // // //SetDlgItemText(hwndDlg, IDC_TIMESTAMP, string->Buffer); + // // PhDereferenceObject(string); + // // + // // PhUnloadMappedImage(&fileMappedImage); + // //} + + // if (PhVerifyFile(driverDosPathString->Buffer, &fileSignerName) == VrTrusted) + // { + // if (PhEqualString2(fileSignerName, L"Microsoft Windows Hardware Compatibility Publisher", TRUE)) + // { + // nvGpuDriverIsWHQL = TRUE; + // } + // } + + // if (fileSignerName) + // PhDereferenceObject(fileSignerName); + + // PhDereferenceObject(driverDosPathString); + // } + + // NtClose(keyServiceHandle); + // } + // } + //} + //__finally + //{ + // if (deviceInfoHandle != INVALID_HANDLE_VALUE) + // { + // SetupDiDestroyDeviceInfoList(deviceInfoHandle); + // } + + // if (keyHandle) + // { + // NtClose(keyHandle); + // } + + // if (deviceInterfaceDetail) + // { + // PhFree(deviceInterfaceDetail); + // } + + // if (keyPath) + // { + // PhDereferenceObject(keyPath); + // } + //} + + return nvGpuDriverIsWHQL; +} + +PPH_STRING NvGpuQueryFanSpeed(VOID) +{ + NvU32 tachValue = 0; + NV_GPU_COOLER_SETTINGS coolerInfo = { NV_GPU_COOLER_SETTINGS_VER }; + + if (NvApiInitialized) + { + if (NvAPI_GPU_GetTachReading(NvGpuPhysicalHandleList->Items[0], &tachValue) == NVAPI_OK) + { + if (NvAPI_GPU_GetCoolerSettings(NvGpuPhysicalHandleList->Items[0], NVAPI_COOLER_TARGET_ALL, &coolerInfo) == NVAPI_OK) + { + return PhFormatString(L"%lu RPM (%lu%%)", tachValue, coolerInfo.cooler[0].currentLevel); + } + + return PhFormatString(L"%lu RPM", tachValue); + } + else + { + if (NvAPI_GPU_GetCoolerSettings(NvGpuPhysicalHandleList->Items[0], NVAPI_COOLER_TARGET_ALL, &coolerInfo) == NVAPI_OK) + { + return PhFormatString(L"%lu%%", coolerInfo.cooler[0].currentLevel); + } + } + } + + return PhCreateString(L"N/A"); +} + + + + +VOID NvGpuUpdate(VOID) +{ + NV_USAGES_INFO usagesInfo = { NV_USAGES_INFO_VER }; + NV_GPU_THERMAL_SETTINGS thermalSettings = { NV_GPU_THERMAL_SETTINGS_VER }; + NV_GPU_CLOCK_FREQUENCIES clkFreqs = { NV_GPU_CLOCK_FREQUENCIES_VER }; + NV_CLOCKS_INFO clocksInfo = { NV_CLOCKS_INFO_VER }; + NV_VOLTAGE_DOMAINS voltageDomains = { NV_VOLTAGE_DOMAIN_INFO_VER }; + ULONG totalMemory = 0; + ULONG sharedMemory = 0; + ULONG freeMemory = 0; + ULONG usedMemory = 0; + + if (!NvApiInitialized) + return; + + for (ULONG i = 0; i < NvGpuDisplayHandleList->Count; i++) + { + NvDisplayHandle nvDisplayHandle; + NV_DISPLAY_DRIVER_MEMORY_INFO memoryInfo = { NV_DISPLAY_DRIVER_MEMORY_INFO_VER }; + + nvDisplayHandle = NvGpuDisplayHandleList->Items[i]; + + if (NvAPI_GPU_GetMemoryInfo(nvDisplayHandle, &memoryInfo) == NVAPI_OK) + { + totalMemory += memoryInfo.dedicatedVideoMemory; + sharedMemory += memoryInfo.sharedSystemMemory; + freeMemory += memoryInfo.curAvailableDedicatedVideoMemory; + usedMemory += totalMemory - freeMemory; + } + } + + GpuMemoryLimit = totalMemory; + GpuCurrentMemUsage = usedMemory; + GpuCurrentMemSharedUsage = sharedMemory; + + if (NvAPI_GPU_GetUsages(NvGpuPhysicalHandleList->Items[0], &usagesInfo) == NVAPI_OK) + { + GpuCurrentGpuUsage = (FLOAT)usagesInfo.usages[2] / 100; + GpuCurrentCoreUsage = (FLOAT)usagesInfo.usages[6] / 100; + GpuCurrentBusUsage = (FLOAT)usagesInfo.usages[14] / 100; + } + + if (NvAPI_GPU_GetThermalSettings(NvGpuPhysicalHandleList->Items[0], NVAPI_THERMAL_TARGET_ALL, &thermalSettings) == NVAPI_OK) + { + GpuCurrentCoreTemp = thermalSettings.sensor[0].currentTemp; + GpuCurrentBoardTemp = thermalSettings.sensor[1].currentTemp; + } + + if (NvAPI_GPU_GetAllClockFrequencies(NvGpuPhysicalHandleList->Items[0], &clkFreqs) == NVAPI_OK) + { + //if (clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_GRAPHICS].bIsPresent) + GpuCurrentCoreClock = clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_GRAPHICS].frequency / 1000; + + //if (clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_MEMORY].bIsPresent) + GpuCurrentMemoryClock = clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_MEMORY].frequency / 1000; + + //if (clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_PROCESSOR].bIsPresent) + GpuCurrentShaderClock = clkFreqs.domain[NVAPI_GPU_PUBLIC_CLOCK_PROCESSOR].frequency / 1000; + } + + if (NvAPI_GPU_GetAllClocks(NvGpuPhysicalHandleList->Items[0], &clocksInfo) == NVAPI_OK) + { + if (GpuCurrentCoreClock == 0) + GpuCurrentCoreClock = clocksInfo.clocks[0] / 1000; + + if (GpuCurrentMemoryClock == 0) + GpuCurrentMemoryClock = clocksInfo.clocks[1] / 1000; + + if (GpuCurrentShaderClock == 0) + GpuCurrentShaderClock = clocksInfo.clocks[2] / 1000; + + if (clocksInfo.clocks[30] != 0) + { + if (GpuCurrentCoreClock == 0) + GpuCurrentCoreClock = (ULONG)(clocksInfo.clocks[30] * 0.0005f); + + if (GpuCurrentShaderClock == 0) + GpuCurrentShaderClock = (ULONG)(clocksInfo.clocks[30] * 0.001f); + } + } + + if (NvAPI_GPU_GetVoltageDomainsStatus(NvGpuPhysicalHandleList->Items[0], &voltageDomains) == NVAPI_OK) + { + GpuCurrentVoltage = voltageDomains.domain[0].mvolt / 1000; + + //for (NvU32 i = 0; i < voltageDomains.max; i++) + //{ + // if (voltageDomains.domain[i].domainId == NVAPI_GPU_PERF_VOLTAGE_INFO_DOMAIN_CORE) + // { + // OutputDebugString(PhaFormatString(L"Voltage: [%lu] %lu\r\n", i, voltageDomains.domain[0].mvolt / 1000))->Buffer); + // } + //} + } + + //if (NvAPI_GPU_GetPerfDecreaseInfo(NvGpuPhysicalHandleList->Items[0], &GpuPerfDecreaseReason) != NVAPI_OK) + //{ + // GpuPerfDecreaseReason = NV_GPU_PERF_DECREASE_REASON_UNKNOWN; + //} + + //NvU32 totalApps = 0; + //NV_ACTIVE_APPS activeApps[NVAPI_MAX_PROCESSES] = { NV_ACTIVE_APPS_INFO_VER }; + //NvAPI_GPU_QueryActiveApps(NvGpuPhysicalHandleList->Items[0], activeApps, &totalApps); + + //NV_VOLTAGES voltages = { NV_VOLTAGES_INFO_VER }; + //NvAPI_GPU_GetVoltages(NvGpuPhysicalHandleList->Items[0], &voltages); + + //Nv120 clockInfo = { NV_PERF_CLOCKS_INFO_VER }; + //NvAPI_GPU_GetPerfClocks(NvGpuPhysicalHandleList->Items[0], 0, &clockInfo); + + PhAddItemCircularBuffer_FLOAT(&GpuUtilizationHistory, GpuCurrentGpuUsage); + PhAddItemCircularBuffer_ULONG(&GpuMemoryHistory, GpuCurrentMemUsage); + PhAddItemCircularBuffer_FLOAT(&GpuBoardHistory, GpuCurrentCoreUsage); + PhAddItemCircularBuffer_FLOAT(&GpuBusHistory, GpuCurrentBusUsage); +} + +#endif \ No newline at end of file diff --git a/plugins/HardwareDevices/nvidia.h b/plugins/HardwareDevices/nvidia.h new file mode 100644 index 0000000..1044282 --- /dev/null +++ b/plugins/HardwareDevices/nvidia.h @@ -0,0 +1,617 @@ +/* + * Process Hacker Extra Plugins - + * Nvidia GPU Plugin + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" +#include + +// These structures and types have been gathered from nvapi leaks and symbols, as well as reverse engineering and guessing. + +// rev +#define NVAPI_MAX_USAGES_PER_GPU 0x21 +#define NVAPI_MAX_CLOCKS_PER_GPU 0x120 +#define NVAPI_MAX_COOLERS_PER_GPU 0x3 +#define NVAPI_MIN_COOLER_LEVEL 0x0 +#define NVAPI_MAX_COOLER_LEVEL 0x64 +#define NVAPI_MAX_COOLER_LEVELS 0x18 +#define NVAPI_MAX_PROCESSES 0x80 + +// rev +typedef PVOID (__cdecl *_NvAPI_QueryInterface)(_In_ NvU32 FunctionOffset); +_NvAPI_QueryInterface NvAPI_QueryInterface; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetShaderPipeCount)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pShaderPipeCount); +_NvAPI_GPU_GetShaderPipeCount NvAPI_GPU_GetShaderPipeCount; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetShaderSubPipeCount)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pShaderSubPipeCount); +_NvAPI_GPU_GetShaderSubPipeCount NvAPI_GPU_GetShaderSubPipeCount; + + +// rev +typedef enum _NV_RAM_TYPE +{ + NV_RAM_TYPE_NONE, + NV_RAM_TYPE_SDRAM, + NV_RAM_TYPE_DDR1, + NV_RAM_TYPE_DDR2, + NV_RAM_TYPE_GDDR2, + NV_RAM_TYPE_GDDR3, + NV_RAM_TYPE_GDDR4, + NV_RAM_TYPE_DDR3, + NV_RAM_TYPE_GDDR5, + NV_RAM_TYPE_LPDDR2 +} NV_RAM_TYPE; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetRamType)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NV_RAM_TYPE* pRamType); +_NvAPI_GPU_GetRamType NvAPI_GPU_GetRamType; + + +// rev +typedef enum _NV_RAM_MAKER +{ + NV_RAM_MAKER_NONE, + NV_RAM_MAKER_SAMSUNG, + NV_RAM_MAKER_QIMONDA, + NV_RAM_MAKER_ELPIDA, + NV_RAM_MAKER_ETRON, + NV_RAM_MAKER_NANYA, + NV_RAM_MAKER_HYNIX, + NV_RAM_MAKER_MOSEL, + NV_RAM_MAKER_WINBOND, + NV_RAM_MAKER_ELITE, + NV_RAM_MAKER_MICRON +} NV_RAM_MAKER; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetRamType)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NV_RAM_MAKER* pRamMaker); +_NvAPI_GPU_GetRamType NvAPI_GPU_GetRamMaker; + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_GetRamBusWidth)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pRamBusWidth); +_NvAPI_GPU_GetRamBusWidth NvAPI_GPU_GetRamBusWidth; + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_GetRamBankCount)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pRamBankCount); +_NvAPI_GPU_GetRamBankCount NvAPI_GPU_GetRamBankCount; + + + +typedef enum _NV_FOUNDRY +{ + NV_FOUNDRY_NONE, + NV_FOUNDRY_TSMC, + NV_FOUNDRY_UMC, + NV_FOUNDRY_IBM, + NV_FOUNDRY_SMIC, + NV_FOUNDRY_CSM, + NV_FOUNDRY_TOSHIBA +} NV_FOUNDRY; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetFoundry)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NV_FOUNDRY* pFoundry); +_NvAPI_GPU_GetFoundry NvAPI_GPU_GetFoundry; + + + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetFBWidthAndLocation)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pWidth, NvU32* pLocation); +_NvAPI_GPU_GetFBWidthAndLocation NvAPI_GPU_GetFBWidthAndLocation; + + +// rev (This has a different offset than the NvAPI_GPU_GetMemoryInfo function despite both returning the same struct). +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverMemoryInfo)(_In_ NvDisplayHandle NvDispHandle, _Inout_ NV_DISPLAY_DRIVER_MEMORY_INFO* pMemoryInfo); +_NvAPI_GetDisplayDriverMemoryInfo NvAPI_GetDisplayDriverMemoryInfo; + + + +// rev +typedef enum _NV_COOLER_TYPE +{ + NVAPI_COOLER_TYPE_NONE = 0, + NVAPI_COOLER_TYPE_FAN, + NVAPI_COOLER_TYPE_WATER, + NVAPI_COOLER_TYPE_LIQUID_NO2, +} NV_COOLER_TYPE; + +// rev +typedef enum _NV_COOLER_CONTROLLER +{ + NVAPI_COOLER_CONTROLLER_NONE = 0, + NVAPI_COOLER_CONTROLLER_ADI, + NVAPI_COOLER_CONTROLLER_INTERNAL, +} NV_COOLER_CONTROLLER; + +// rev +typedef enum _NV_COOLER_POLICY +{ + NVAPI_COOLER_POLICY_NONE = 0, + NVAPI_COOLER_POLICY_MANUAL, // Manual adjustment of cooler level. Gets applied right away independent of temperature or performance level. + NVAPI_COOLER_POLICY_PERF, // GPU performance controls the cooler level. + NVAPI_COOLER_POLICY_TEMPERATURE_DISCRETE = 4, // Discrete thermal levels control the cooler level. + NVAPI_COOLER_POLICY_TEMPERATURE_CONTINUOUS = 8, // Cooler level adjusted at continuous thermal levels. + NVAPI_COOLER_POLICY_HYBRID, // Hybrid of performance and temperature levels. +} NV_COOLER_POLICY; + +// rev +typedef enum _NV_COOLER_TARGET +{ + NVAPI_COOLER_TARGET_NONE = 0, + NVAPI_COOLER_TARGET_GPU, + NVAPI_COOLER_TARGET_MEMORY, + NVAPI_COOLER_TARGET_POWER_SUPPLY = 4, + NVAPI_COOLER_TARGET_ALL = 7 // This cooler cools all of the components related to its target gpu. +} NV_COOLER_TARGET; + +// rev +typedef enum _NV_COOLER_CONTROL +{ + NVAPI_COOLER_CONTROL_NONE = 0, + NVAPI_COOLER_CONTROL_TOGGLE, // ON/OFF + NVAPI_COOLER_CONTROL_VARIABLE, // Suppports variable control. +} NV_COOLER_CONTROL; + +// rev +typedef enum _NV_COOLER_ACTIVITY_LEVEL +{ + NVAPI_INACTIVE = 0, // inactive or unsupported + NVAPI_ACTIVE = 1, // active and spinning in case of fan +} NV_COOLER_ACTIVITY_LEVEL; + +// rev +typedef struct _NV_GPU_COOLER_SETTINGS +{ + NvU32 version; // structure version + NvU32 count; // number of associated coolers with the selected GPU + struct + { + NV_COOLER_TYPE type; // type of cooler - FAN, WATER, LIQUID_NO2... + NV_COOLER_CONTROLLER controller; // internal, ADI... + NvU32 defaultMinLevel; // the min default value % of the cooler + NvU32 defaultMaxLevel; // the max default value % of the cooler + NvU32 currentMinLevel; // the current allowed min value % of the cooler + NvU32 currentMaxLevel; // the current allowed max value % of the cooler + NvU32 currentLevel; // the current value % of the cooler + NV_COOLER_POLICY defaultPolicy; // cooler control policy - auto-perf, auto-thermal, manual, hybrid... + NV_COOLER_POLICY currentPolicy; // cooler control policy - auto-perf, auto-thermal, manual, hybrid... + NV_COOLER_TARGET target; // cooling target - GPU, memory, chipset, powersupply, canoas... + NV_COOLER_CONTROL controlType; // toggle or variable + NV_COOLER_ACTIVITY_LEVEL active; // is the cooler active - fan spinning... + } cooler[NVAPI_MAX_COOLERS_PER_GPU]; +} NV_GPU_COOLER_SETTINGS, *PNV_GPU_COOLER_SETTINGS; + +#define NV_GPU_COOLER_SETTINGS_VER MAKE_NVAPI_VERSION(NV_GPU_COOLER_SETTINGS, 1) + +// rev +typedef struct _NV_GPU_SETCOOLER_LEVEL +{ + NvU32 version; //structure version + struct + { + NvU32 currentLevel; // the new value % of the cooler + NV_COOLER_POLICY currentPolicy; // the new cooler control policy - auto-perf, auto-thermal, manual, hybrid... + } cooler[NVAPI_MAX_COOLERS_PER_GPU]; +} NV_GPU_SETCOOLER_LEVEL; + +#define NV_GPU_SETCOOLER_LEVEL_VER MAKE_NVAPI_VERSION(NV_GPU_SETCOOLER_LEVEL, 1) + +// rev +typedef struct _NV_GPU_COOLER_POLICY_TABLE +{ + NvU32 version; //structure version + NV_COOLER_POLICY policy; //selected policy to update the cooler levels for, example NVAPI_COOLER_POLICY_PERF + struct + { + NvU32 levelId; // level indicator for a policy + NvU32 currentLevel; // new cooler level for the selected policy level indicator. + NvU32 defaultLevel; // default cooler level for the selected policy level indicator. + } policyCoolerLevel[NVAPI_MAX_COOLER_LEVELS]; +} NV_GPU_COOLER_POLICY_TABLE; + +#define NV_GPU_COOLER_POLICY_TABLE_VER MAKE_NVAPI_VERSION(NV_GPU_COOLER_POLICY_TABLE, 1) + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetCoolerSettings +// +// DESCRIPTION: Retrieves the cooler information of all coolers or a specific cooler associated with the selected GPU. +// Coolers are indexed 0 to NVAPI_MAX_COOLERS_PER_GPU-1. +// To retrieve specific cooler info set the coolerIndex to the appropriate cooler index. +// To retrieve info for all cooler set coolerIndex to NVAPI_COOLER_TARGET_ALL. +// +// PARAMETERS : hPhysicalGPU(IN) - GPU selection. +// coolerIndex(IN) - Explict cooler index selection. +// pCoolerInfo(OUT) - Array of cooler settings. +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetCoolerSettings)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 coolerIndex, NV_GPU_COOLER_SETTINGS* pCoolerInfo); +_NvAPI_GPU_GetCoolerSettings NvAPI_GPU_GetCoolerSettings; + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_SetCoolerLevels +// +// DESCRIPTION: Set the cooler levels for all coolers or a specific cooler associated with the selected GPU. +// Coolers are indexed 0 to NVAPI_MAX_COOLERS_PER_GPU-1. Every cooler level with non-zero currentpolicy gets applied. +// The new level should be in the range of minlevel and maxlevel retrieved from GetCoolerSettings API or between +// and NVAPI_MIN_COOLER_LEVEL to MAX_COOLER_LEVEL. +// To set level for a specific cooler set the coolerIndex to the appropriate cooler index. +// To set level for all coolers set coolerIndex to NVAPI_COOLER_TARGET_ALL. +// NOTE: To lock the fan speed independent of the temperature or performance changes set the cooler currentPolicy to +// NVAPI_COOLER_POLICY_MANUAL else set it to the current policy retrieved from the GetCoolerSettings API. +// PARAMETERS: hPhysicalGPU(IN) - GPU selection. +// coolerIndex(IN) - Explict cooler index selection. +// pCoolerLevels(IN) - Updated cooler level and cooler policy. +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_SetCoolerLevels)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 coolerIndex, NV_GPU_SETCOOLER_LEVEL *pCoolerLevels); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_RestoreCoolerSettings +// +// DESCRIPTION: Restore the modified cooler settings to NVIDIA defaults. +// +// PARAMETERS: hPhysicalGPU(IN) - GPU selection. +// pCoolerIndex(IN) - Array containing absolute cooler indexes to restore. Pass NULL restore all coolers. +// CoolerCount - Number of coolers to restore. +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_RestoreCoolerSettings)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pCoolerIndex, NvU32 coolerCount); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_GetCoolerPolicyTable +// +// DESCRIPTION: Retrieves the table of cooler and policy levels for the selected policy. Supported only for NVAPI_COOLER_POLICY_PERF. +// +// PARAMETERS: hPhysicalGPU(IN) - GPU selection. +// coolerIndex(IN) - cooler index selection. +// pCoolerTable(OUT) - Table of policy levels and associated cooler levels. +// count(OUT) - Count of the number of valid levels for the selected policy. +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetCoolerPolicyTable)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 coolerIndex, NV_GPU_COOLER_POLICY_TABLE *pCoolerTable, NvU32 *count); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_SetCoolerPolicyTable +// +// DESCRIPTION: Restore the modified cooler settings to NVIDIA defaults. Supported only for NVAPI_COOLER_POLICY_PERF. +// +// PARAMETERS: hPhysicalGPU(IN) - GPU selection. +// coolerIndex(IN) - cooler index selection. +// pCoolerTable(IN) - Updated table of policy levels and associated cooler levels. Every non-zero policy level gets updated. +// count(IN) - Number of valid levels in the policy table. +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_SetCoolerPolicyTable)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 coolerIndex, NV_GPU_COOLER_POLICY_TABLE *pCoolerTable, NvU32 count); + +/////////////////////////////////////////////////////////////////////////////// +// +// FUNCTION NAME: NvAPI_GPU_RestoreCoolerPolicyTable +// +// DESCRIPTION: Restores the perf table policy levels to the defaults. +// +// PARAMETERS: hPhysicalGPU(IN) - GPU selection. +// coolerIndex(IN) - cooler index selection. +// pCoolerIndex(IN) - Array containing absolute cooler indexes to restore. Pass NULL restore all coolers. +// coolerCount - Number of coolers to restore. +// policy - restore for the selected policy +// +/////////////////////////////////////////////////////////////////////////////// +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_RestoreCoolerPolicyTable)(_In_ NvPhysicalGpuHandle hPhysicalGpu, NvU32 *pCoolerIndex, NvU32 coolerCount, NV_COOLER_POLICY policy); + + + +// rev - NvAPI_GPU_GetUsages +typedef struct _NV_USAGES_INFO +{ + NvU32 version; //!< Structure version + NvU32 usages[NVAPI_MAX_USAGES_PER_GPU]; +} NV_USAGES_INFO; + +#define NV_USAGES_INFO_VER MAKE_NVAPI_VERSION(NV_USAGES_INFO, 1) + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetUsages)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_USAGES_INFO* pUsagesInfo); +_NvAPI_GPU_GetUsages NvAPI_GPU_GetUsages; + + + +// rev - NvAPI_GPU_GetAllClocks +typedef struct _NV_CLOCKS_INFO +{ + NvU32 version; //!< Structure version + NvU32 clocks[NVAPI_MAX_CLOCKS_PER_GPU]; +} NV_CLOCKS_INFO; + +#define NV_CLOCKS_INFO_VER MAKE_NVAPI_VERSION(NV_CLOCKS_INFO, 2) + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetAllClocks)(_In_ NvPhysicalGpuHandle hPhysicalGpu, _Inout_ NV_CLOCKS_INFO* pClocksInfo); +_NvAPI_GPU_GetAllClocks NvAPI_GPU_GetAllClocks; + + + +// rev - NvAPI_GPU_GetVoltageDomainsStatus +typedef struct _NV_VOLTAGE_DOMAINS +{ + NvU32 version; //!< Structure version + NvU32 flags; //!< Reserved for future use. Must be set to 0 + NvU32 max; + struct + { + NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID domainId; //!< ID of the voltage domain + NvU32 mvolt; //!< Voltage in mV + } domain[NVAPI_MAX_GPU_PERF_VOLTAGES]; +} NV_VOLTAGE_DOMAINS; + +#define NV_VOLTAGE_DOMAIN_INFO_VER MAKE_NVAPI_VERSION(NV_VOLTAGE_DOMAINS, 1) + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_GetVoltageDomainsStatus)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_VOLTAGE_DOMAINS* pVoltageDomainsStatus); +_NvAPI_GPU_GetVoltageDomainsStatus NvAPI_GPU_GetVoltageDomainsStatus; + + + +// rev - NvAPI_GPU_GetVoltages +typedef struct _NV_VOLTAGES_INFO +{ + NV_GPU_PERF_VOLTAGE_INFO_DOMAIN_ID domainId; //!< ID of the voltage domain + NvU32 unknown1; + NvU32 max; + + struct + { + NvU32 unknown2; + NvU32 mvolt; //!< Voltage in mV + } info[128]; +} NV_VOLTAGES_INFO; + +// rev +typedef struct _NV_VOLTAGES +{ + NvU32 version; //!< Structure version + NvU32 flags; //!< Reserved for future use. Must be set to 0 + NvU32 max; + NV_VOLTAGES_INFO voltages[NVAPI_MAX_GPU_PERF_VOLTAGES]; +} NV_VOLTAGES; + +#define NV_VOLTAGES_INFO_VER MAKE_NVAPI_VERSION(NV_VOLTAGES, 1) + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_GetVoltages)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_VOLTAGES* pPerfVoltages); +_NvAPI_GPU_GetVoltages NvAPI_GPU_GetVoltages; + + + +// rev - NvAPI_GPU_GetPerfClocks +typedef struct _NV_PERF_CLOCKS_UNKNOWN_2 +{ + NvU32 unknown1; + NvU32 unknown2; + NvU32 unknown3; + NvU32 unknown4; + NvU32 unknown5; + NvU32 unknown6; + NvU32 unknown7; +} NV_PERF_CLOCKS_UNKNOWN_2; + +// rev +typedef struct _NV_PERF_CLOCKS_UNKNOWN_1 +{ + NvU32 unknown1; + NvU32 unknown2; + NV_PERF_CLOCKS_UNKNOWN_2 unknown3[32]; +} NV_PERF_CLOCKS_UNKNOWN_1; + +// rev +typedef struct _NV_PERF_CLOCKS +{ + NvU32 version; //!< Structure version + NvU32 unknown1; + NvU32 unknown2; + NvU32 unknown3; + NvU32 unknown4; + NV_PERF_CLOCKS_UNKNOWN_1 unknown5[12]; +} NV_PERF_CLOCKS; + +#define NV_PERF_CLOCKS_INFO_VER MAKE_NVAPI_VERSION(NV_PERF_CLOCKS, 1) + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_GetPerfClocks)(_In_ NvPhysicalGpuHandle hPhysicalGPU, INT i, _Inout_ NV_PERF_CLOCKS* pPerfClocks); +_NvAPI_GPU_GetPerfClocks NvAPI_GPU_GetPerfClocks; + + + +// rev - NvAPI_GPU_QueryActiveApps +typedef struct _NV_ACTIVE_APP +{ + NvU32 version; //!< Structure version + NvU32 processPID; + NvAPI_LongString processName; +} NV_ACTIVE_APP; + +#define NV_ACTIVE_APPS_INFO_VER MAKE_NVAPI_VERSION(NV_ACTIVE_APP, 2) + +// rev +typedef NvAPI_Status(__cdecl *_NvAPI_GPU_QueryActiveApps)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_ACTIVE_APP pActiveApps[NVAPI_MAX_PROCESSES], _Inout_ NvU32* pTotal); +_NvAPI_GPU_QueryActiveApps NvAPI_GPU_QueryActiveApps; + + + +// rev - NvAPI_GPU_GetPowerMizerInfo +typedef enum _PowerSourceInfo +{ + PowerSourceInfo_Unknown1 = 1, + PowerSourceInfo_Unknown2, + PowerSourceInfo_Unknown3 = 8738 +} PowerSourceInfo; + +// rev +typedef enum _SelectSource +{ + SelectSource_Unknown1 = 1, + SelectSource_Unknown2, + SelectSource_Unknown3 +} SelectSource; + +// rev +typedef enum _LevelInfo +{ + LevelInfo_Unknown1 = 1, + LevelInfo_Unknown2, + LevelInfo_Unknown3, + LevelInfo_Unknown4, + LevelInfo_Unknown5, + LevelInfo_Unknown6, + LevelInfo_Unknown7 +} LevelInfo; + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetPowerMizerInfo)(_In_ NvPhysicalGpuHandle hPhysicalGPU, PowerSourceInfo powerSourceInfo, SelectSource select, LevelInfo* pLevelInfo); +_NvAPI_GPU_GetPowerMizerInfo NvAPI_GPU_GetPowerMizerInfo; + + + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverRegistryPath)(_In_ NvDisplayHandle hNvDisplay, _Inout_ NvAPI_LongString szDriverRegistryPath); +_NvAPI_GetDisplayDriverRegistryPath NvAPI_GetDisplayDriverRegistryPath; + + + +// rev +//typedef NvAPI_Status (__cdecl *_NvAPI_RestartDisplayDriver)(_In_ NvU32 NvDriverIndex); +//_NvAPI_RestartDisplayDriver NvAPI_RestartDisplayDriver; + + +typedef enum _NV_POWER_TOPOLOGY_FLAGS +{ + NV_POWER_TOPOLOGY_FLAG_DISABLED, // Unsure if name is correct + NV_POWER_TOPOLOGY_FLAG_ENABLED // Unsure if name is correct +} NV_POWER_TOPOLOGY_FLAGS; + + +typedef struct _NV_POWER_TOPOLOGY_1 +{ + NvU32 unknown1; + NvU32 unknown2; + NvU32 unknown3; + + // return this.unknown1 & 1u; + // return (this.unknown1 & 4294967294u) / 2u; +} NV_POWER_TOPOLOGY_1; + + +typedef struct _NV_POWER_TOPOLOGY_2 +{ + NV_POWER_TOPOLOGY_FLAGS flags; + NV_POWER_TOPOLOGY_1 unknown; +} NV_POWER_TOPOLOGY_2; + +typedef struct _NV_POWER_TOPOLOGY_STATUS +{ + NvU32 version; //!< Structure version + NvU32 count; + + NV_POWER_TOPOLOGY_2 unknown[4]; +} NV_POWER_TOPOLOGY_STATUS; + +#define NV_POWER_TOPOLOGY_STATUS_VER MAKE_NVAPI_VERSION(NV_POWER_TOPOLOGY_STATUS, 1) + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_ClientPowerTopologyGetStatus)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_POWER_TOPOLOGY_STATUS* pClientPowerTopologyStatus); +_NvAPI_GPU_ClientPowerTopologyGetStatus NvAPI_GPU_ClientPowerTopologyGetStatus; + + + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetShortName)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NvAPI_ShortString szName); +_NvAPI_GPU_GetShortName NvAPI_GPU_GetShortName; + + +// rev +typedef struct _NV_ARCH_INFO +{ + NvU32 version; //!< Structure version + NvU32 unknown[3]; +} NV_ARCH_INFO; + +#define NV_ARCH_INFO_VER MAKE_NVAPI_VERSION(NV_ARCH_INFO, 2) + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetArchInfo)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_ARCH_INFO* pArchInfo); +_NvAPI_GPU_GetArchInfo NvAPI_GPU_GetArchInfo; + + + +// rev +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetPartitionCount)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Out_ NvU32* pCount); +_NvAPI_GPU_GetPartitionCount NvAPI_GPU_GetPartitionCount; + + + + +typedef struct _NV_PCIE_INFO_UNKNOWN +{ + NvU32 unknown0; + NvU32 unknown1; + NvU32 unknown2; + NvU32 unknown3; + NvU32 unknown4; + NvU32 unknown5; + NvU32 unknown6; + NvU32 unknown7; +} NV_PCIE_INFO_UNKNOWN; + +typedef struct _NV_PCIE_INFO +{ + NvU32 version; //!< Structure version + NV_PCIE_INFO_UNKNOWN info[5]; +} NV_PCIE_INFO; + +#define NV_PCIE_INFO_VER MAKE_NVAPI_VERSION(NV_PCIE_INFO, 2) + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetPCIEInfo)(_In_ NvPhysicalGpuHandle hPhysicalGPU, _Inout_ NV_PCIE_INFO* pPciInfo); +_NvAPI_GPU_GetPCIEInfo NvAPI_GPU_GetPCIEInfo; + +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverBuildTitle)(_In_ NvDisplayHandle hNvDisplay, NvAPI_ShortString pDriverBuildTitle); +_NvAPI_GetDisplayDriverBuildTitle NvAPI_GetDisplayDriverBuildTitle; + +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverCompileType)(_In_ NvDisplayHandle hNvDisplay, NvU32* pDriverCompileType); +_NvAPI_GetDisplayDriverCompileType NvAPI_GetDisplayDriverCompileType; + +typedef NvAPI_Status (__cdecl *_NvAPI_GetDisplayDriverSecurityLevel)(_In_ NvDisplayHandle hNvDisplay, NvU32* pDriverSecurityLevel); +_NvAPI_GetDisplayDriverSecurityLevel NvAPI_GetDisplayDriverSecurityLevel; + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetVPECount)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pVPECount); +_NvAPI_GPU_GetVPECount NvAPI_GPU_GetVPECount; + +//typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetExtendedMinorRevision)(_In_ NvPhysicalGpuHandle hPhysicalGPU, NvU32* pRamBankCount); +//typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetSerialNumber)(_In_ NvPhysicalGpuHandle hPhysicalGPU, PBYTE pRamBankCount); + +typedef NvAPI_Status (__cdecl *_NvAPI_GPU_GetTargetID)(_In_ NvPhysicalGpuHandle hPhysicalGPU, PBYTE pRamBankCount); +_NvAPI_GPU_GetTargetID NvAPI_GPU_GetTargetID; + + +#include \ No newline at end of file diff --git a/plugins/HardwareDevices/resource.h b/plugins/HardwareDevices/resource.h new file mode 100644 index 0000000..14036c4 --- /dev/null +++ b/plugins/HardwareDevices/resource.h @@ -0,0 +1,61 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by HardwareDevices.rc +// +#define IDD_NETADAPTER_OPTIONS 101 +#define IDD_NETADAPTER_DIALOG 102 +#define IDD_NETADAPTER_PANEL 104 +#define IDD_NETADAPTER_DETAILS 105 +#define IDD_DISKDRIVE_DETAILS_FILESYSTEM 109 +#define IDD_DISKDRIVE_DETAILS_GENERAL 111 +#define IDD_GPU_DIALOG 1077 +#define IDD_DISKDRIVE_OPTIONS 1013 +#define IDD_DISKDRIVE_DIALOG 1014 +#define IDD_DISKDRIVE_PANEL 1016 +#define IDD_DISKDRIVE_DETAILS_SMART 1017 +#define IDC_GRAPH_LAYOUT 103 +#define IDC_NETADAPTERS_LISTVIEW 1001 +#define IDC_LINK_SPEED 1002 +#define IDC_ADAPTERNAME 1003 +#define IDC_LAYOUT 1004 +#define IDC_LINK_STATE 1005 +#define IDC_STAT_BSENT 1006 +#define IDC_STAT_BRECEIVED 1007 +#define IDC_STAT_BTOTAL 1008 +#define IDC_DETAILS 1010 +#define IDC_DETAILS_LIST 1011 +#define IDC_SHOW_HIDDEN_ADAPTERS 1012 +#define IDC_EDIT1 1015 +#define IDC_DISKDRIVE_LISTVIEW 1017 +#define IDC_DESCRIPTION 1017 +#define IDC_DISKMOUNTPATH 1018 +#define IDC_TITLE 1019 +#define IDC_STAT_BREAD 1020 +#define IDC_STAT_BWRITE 1021 +#define IDC_STAT_ACTIVE 1023 +#define IDC_STAT_RESPONSETIME 1024 +#define IDC_STAT_QUEUELENGTH 1025 +#define IDC_DISKNAME 1076 +#define IDC_GPUNAME 1078 +#define IDC_GPU_L 1079 +#define IDC_MEMORY_L 1080 +#define IDC_SHARED_L 1081 +#define IDC_BUS_L 1082 +#define IDD_GPU_PANEL 1083 +#define IDC_CLOCK_CORE 211 +#define IDC_CLOCK_MEMORY 212 +#define IDC_FAN_PERCENT 213 +#define IDC_TEMP_VALUE 214 +#define IDC_CLOCK_SHADER 215 +#define IDC_VOLTAGE 217 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 114 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1020 +#define _APS_NEXT_SYMED_VALUE 106 +#endif +#endif diff --git a/plugins/HardwareDevices/storage.c b/plugins/HardwareDevices/storage.c new file mode 100644 index 0000000..919f67c --- /dev/null +++ b/plugins/HardwareDevices/storage.c @@ -0,0 +1,1570 @@ +/* + * Process Hacker Plugins - + * Hardware Devices Plugin + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "devices.h" +#include + +// NOTE: Functions in this file can be used on disks, volumes, and partitions, +// even if they appear to only support one type, they can be used to query different +// information from other types. +// TODO: Come up with a better naming scheme to identify these multi-purpose functions. + +NTSTATUS DiskDriveCreateHandle( + _Out_ PHANDLE DeviceHandle, + _In_ PPH_STRING DevicePath + ) +{ + // Some examples of paths that can be used to open the disk device for statistics: + // \PhysicalDrive1 + // \X: + // X:\ + // \HarddiskVolume1 + // \Harddisk1Partition1 + // \Harddisk1\Partition1 + // \Volume{a978c827-cf64-44b4-b09a-57a55ef7f49f} + // IOCTL_MOUNTMGR_QUERY_POINTS (used by FindFirstVolume and FindFirstVolumeMountPoint) + // HKEY_LOCAL_MACHINE\\SYSTEM\\MountedDevices (contains the DosDevice and path used by the SetupAPI with DetailData->DevicePath) + // Other methods?? + + return PhCreateFileWin32( + DeviceHandle, + DevicePath->Buffer, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT // FILE_RANDOM_ACCESS + ); +} + +ULONG DiskDriveQueryDeviceMap( + VOID + ) +{ +#ifndef _WIN64 + PROCESS_DEVICEMAP_INFORMATION deviceMapInfo; +#else + PROCESS_DEVICEMAP_INFORMATION_EX deviceMapInfo; +#endif + + memset(&deviceMapInfo, 0, sizeof(deviceMapInfo)); + + if (NT_SUCCESS(NtQueryInformationProcess( + NtCurrentProcess(), + ProcessDeviceMap, + &deviceMapInfo, + sizeof(deviceMapInfo), + NULL + ))) + { + return deviceMapInfo.Query.DriveMap; + } + else + { + return GetLogicalDrives(); + } +} + +PPH_STRING DiskDriveQueryDosMountPoints( + _In_ ULONG DeviceNumber + ) +{ + ULONG driveMask; + WCHAR deviceNameBuffer[7] = L"\\\\.\\?:"; + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, MAX_PATH); + + driveMask = DiskDriveQueryDeviceMap(); + + // NOTE: This isn't the best way of doing this but it works. + for (INT i = 0; i < 0x1A; i++) + { + if (driveMask & (0x1 << i)) + { + HANDLE deviceHandle; + + deviceNameBuffer[4] = (WCHAR)('A' + i); + + if (NT_SUCCESS(PhCreateFileWin32( + &deviceHandle, + deviceNameBuffer, + FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + ULONG deviceNumber = ULONG_MAX; // Note: Do not initialize to zero. + DEVICE_TYPE deviceType = 0; + + if (NT_SUCCESS(DiskDriveQueryDeviceTypeAndNumber( + deviceHandle, + &deviceNumber, + &deviceType + ))) + { + // BUG: Device numbers are re-used on seperate device controllers and this + // causes drive letters to be assigned to disks at those same indexes. + // For now, just filter CD_ROM devices but we may need to be a lot more strict and + // only allow devices of type FILE_DEVICE_DISK to be scanned for mount points. + if (deviceNumber == DeviceNumber && deviceType != FILE_DEVICE_CD_ROM) + { + PhAppendFormatStringBuilder(&stringBuilder, L"%c: ", deviceNameBuffer[4]); + } + } + + NtClose(deviceHandle); + } + } + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} + +PPH_LIST DiskDriveQueryMountPointHandles( + _In_ ULONG DeviceNumber + ) +{ + ULONG driveMask; + PPH_LIST deviceList; + WCHAR deviceNameBuffer[7] = L"\\\\.\\?:"; + + driveMask = DiskDriveQueryDeviceMap(); + deviceList = PhCreateList(2); + + // NOTE: This isn't the best way of doing this but it works. + for (INT i = 0; i < 0x1A; i++) + { + if (driveMask & (0x1 << i)) + { + HANDLE deviceHandle; + + deviceNameBuffer[4] = (WCHAR)('A' + i); + + if (NT_SUCCESS(PhCreateFileWin32( + &deviceHandle, + deviceNameBuffer, + PhGetOwnTokenAttributes().Elevated ? FILE_GENERIC_READ : FILE_READ_ATTRIBUTES | FILE_TRAVERSE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + ULONG deviceNumber = ULONG_MAX; // Note: Do not initialize to zero. + DEVICE_TYPE deviceType = 0; + + if (NT_SUCCESS(DiskDriveQueryDeviceTypeAndNumber( + deviceHandle, + &deviceNumber, + &deviceType + ))) + { + // BUG: Device numbers are re-used on seperate device controllers and this + // causes drive letters to be assigned to disks at those same indexes. + // For now, just filter CD_ROM devices but we may need to be a lot more strict and + // only allow devices of type FILE_DEVICE_DISK to be scanned for mount points. + if (deviceNumber == DeviceNumber && deviceType != FILE_DEVICE_CD_ROM) + { + PDISK_HANDLE_ENTRY entry = PhAllocate(sizeof(DISK_HANDLE_ENTRY)); + memset(entry, 0, sizeof(DISK_HANDLE_ENTRY)); + + entry->DeviceLetter = deviceNameBuffer[4]; + entry->DeviceHandle = deviceHandle; + + PhAddItemList(deviceList, entry); + } + } + } + } + } + + return deviceList; +} + + +BOOLEAN DiskDriveQueryAdapterInformation( + _In_ HANDLE DeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + STORAGE_PROPERTY_QUERY query; + PSTORAGE_ADAPTER_DESCRIPTOR buffer; + + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageAdapterProperty; + + bufferLength = sizeof(STORAGE_ADAPTER_DESCRIPTOR); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + bufferLength = buffer->Size; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PSTORAGE_ADAPTER_DESCRIPTOR storageDescriptor = (PSTORAGE_ADAPTER_DESCRIPTOR)buffer; + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + +BOOLEAN DiskDriveQueryCache( + _In_ HANDLE DeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + STORAGE_PROPERTY_QUERY query; + PSTORAGE_WRITE_CACHE_PROPERTY buffer; + + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageDeviceWriteCacheProperty; + + bufferLength = sizeof(STORAGE_WRITE_CACHE_PROPERTY); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + bufferLength = buffer->Size; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PSTORAGE_WRITE_CACHE_PROPERTY storageDescriptor = (PSTORAGE_WRITE_CACHE_PROPERTY)buffer; + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + +BOOLEAN DiskDriveQueryPower( + _In_ HANDLE DeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + STORAGE_PROPERTY_QUERY query; + PDEVICE_POWER_DESCRIPTOR buffer; + + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageDevicePowerProperty; + + bufferLength = sizeof(DEVICE_POWER_DESCRIPTOR); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + bufferLength = buffer->Size; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PDEVICE_POWER_DESCRIPTOR storageDescriptor = (PDEVICE_POWER_DESCRIPTOR)buffer; + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + +BOOLEAN DiskDriveQueryTemperature( + _In_ HANDLE DeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + STORAGE_PROPERTY_QUERY query; + PSTORAGE_TEMPERATURE_DATA_DESCRIPTOR buffer; + + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageAdapterTemperatureProperty; // StorageDeviceTemperatureProperty + + bufferLength = sizeof(STORAGE_TEMPERATURE_DATA_DESCRIPTOR); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + bufferLength = buffer->Size; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PSTORAGE_TEMPERATURE_DATA_DESCRIPTOR storageDescriptor = (PSTORAGE_TEMPERATURE_DATA_DESCRIPTOR)buffer; + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + +BOOLEAN DiskDriveQueryDeviceInformation( + _In_ HANDLE DeviceHandle, + _Out_opt_ PPH_STRING* DiskVendor, + _Out_opt_ PPH_STRING* DiskModel, + _Out_opt_ PPH_STRING* DiskRevision, + _Out_opt_ PPH_STRING* DiskSerial + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + STORAGE_PROPERTY_QUERY query; + PSTORAGE_DESCRIPTOR_HEADER buffer; + + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageDeviceProperty; + + bufferLength = sizeof(STORAGE_DESCRIPTOR_HEADER); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + bufferLength = buffer->Size; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(query), + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = (PSTORAGE_DEVICE_DESCRIPTOR)buffer; + + // TODO: Use the following fields: + // STORAGE_BUS_TYPE BusType; + // DWORD RawPropertiesLength; + // BYTE RawDeviceProperties[1]; + + if (DiskVendor && deviceDescriptor->VendorIdOffset != 0) + { + PPH_STRING diskVendor; + + diskVendor = PH_AUTO(PhConvertMultiByteToUtf16((PBYTE)deviceDescriptor + deviceDescriptor->VendorIdOffset)); + + *DiskVendor = TrimString(diskVendor); + } + + if (DiskModel && deviceDescriptor->ProductIdOffset != 0) + { + PPH_STRING diskModel; + + diskModel = PH_AUTO(PhConvertMultiByteToUtf16((PBYTE)deviceDescriptor + deviceDescriptor->ProductIdOffset)); + + *DiskModel = TrimString(diskModel); + } + + if (DiskRevision && deviceDescriptor->ProductRevisionOffset != 0) + { + PPH_STRING diskRevision; + + diskRevision = PH_AUTO(PhConvertMultiByteToUtf16((PBYTE)deviceDescriptor + deviceDescriptor->ProductRevisionOffset)); + + *DiskRevision = TrimString(diskRevision); + } + + if (DiskSerial && deviceDescriptor->SerialNumberOffset != 0) + { + PPH_STRING diskSerial; + + diskSerial = PH_AUTO(PhConvertMultiByteToUtf16((PBYTE)deviceDescriptor + deviceDescriptor->SerialNumberOffset)); + + *DiskSerial = TrimString(diskSerial); + } + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + +NTSTATUS DiskDriveQueryDeviceTypeAndNumber( + _In_ HANDLE DeviceHandle, + _Out_opt_ PULONG DeviceNumber, + _Out_opt_ DEVICE_TYPE* DeviceType + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + STORAGE_DEVICE_NUMBER result; + + memset(&result, 0, sizeof(STORAGE_DEVICE_NUMBER)); + + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_GET_DEVICE_NUMBER, // https://msdn.microsoft.com/en-us/library/bb968800.aspx + NULL, + 0, + &result, + sizeof(result) + ); + + if (NT_SUCCESS(status)) + { + if (DeviceType) + { + *DeviceType = result.DeviceType; + } + + if (DeviceNumber) + { + *DeviceNumber = result.DeviceNumber; + } + } + + return status; +} + +NTSTATUS DiskDriveQueryStatistics( + _In_ HANDLE DeviceHandle, + _Out_ PDISK_PERFORMANCE Info + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + DISK_PERFORMANCE result; + + memset(&result, 0, sizeof(DISK_PERFORMANCE)); + + // TODO: IOCTL_DISK_GET_PERFORMANCE_INFO from ntdddisk.h + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_DISK_PERFORMANCE, // https://msdn.microsoft.com/en-us/library/aa365183.aspx + NULL, + 0, + &result, + sizeof(result) + ); + + if (NT_SUCCESS(status)) + { + *Info = result; + } + + return status; +} + +PPH_STRING DiskDriveQueryGeometry( + _In_ HANDLE DeviceHandle + ) +{ + IO_STATUS_BLOCK isb; + DISK_GEOMETRY result; + + memset(&result, 0, sizeof(DISK_GEOMETRY)); + + // Note: MSDN states the IOCTL_DISK_GET_DRIVE_GEOMETRY query is obsolete. + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_DISK_GET_DRIVE_GEOMETRY, // https://msdn.microsoft.com/en-us/library/aa365169.aspx + NULL, + 0, + &result, + sizeof(result) + ))) + { + // TODO: This doesn't return total capacity like Task Manager. + return PhFormatSize(result.Cylinders.QuadPart * result.TracksPerCylinder * result.SectorsPerTrack * result.BytesPerSector, -1); + } + + return PhReferenceEmptyString(); +} + +BOOLEAN DiskDriveQueryImminentFailure( + _In_ HANDLE DeviceHandle, + _Out_ PPH_LIST* DiskSmartAttributes + ) +{ + IO_STATUS_BLOCK isb; + STORAGE_PREDICT_FAILURE storagePredictFailure; + + memset(&storagePredictFailure, 0, sizeof(STORAGE_PREDICT_FAILURE)); + + // * IOCTL_STORAGE_PREDICT_FAILURE returns an opaque 512-byte vendor-specific information block, which + // in all cases contains SMART attribute information (2 bytes header + 12 bytes each attribute). + // * This works without admin rights but doesn't support other features like logs and self-tests. + // * It works for (S)ATA devices but not for USB. + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_PREDICT_FAILURE, // https://msdn.microsoft.com/en-us/library/ff560587.aspx + NULL, + 0, + &storagePredictFailure, + sizeof(storagePredictFailure) + ))) + { + PPH_LIST diskAttributeList; + //USHORT structureVersion = (USHORT)(storagePredictFailure.VendorSpecific[0] * 256 + storagePredictFailure.VendorSpecific[1]); + //USHORT majorVersion = HIBYTE(structureVersion); + //USHORT minorVersion = LOBYTE(structureVersion); + //TODO: include storagePredictFailure.PredictFailure; + + diskAttributeList = PhCreateList(30); + + //An attribute is a one-byte value ranging from 1 to 253 (FDh). + //The initial default value is 100 (64h). + //The value and the intertretation of the value are vendor-specific. + //Attribute values are read-only to the host. + //A device may report up to 30 attributes to the host. + //Values of 00h, FEh and FFh are invalid. + //When attribute values are updated by the device depends on the specific attribute. Some are updated as + //the disk operates, some are only updated during SMART self-tests, or at special events like power-on or + //unloading the heads of a disk drive, etc + + //Each attribute may have an associated threshhold. When the value exceeds the threshhold, the attribute + //triggers a SMART ‘threshhold exceeded’ event. This event indicates that either the disk is expected to fail + //in less than 24 hours or it has exceeded its design or usage lifetime. + //When an attribute value is greater than or equal to the threshhold, the threshhold is considered to be + //exceeded. A flag is set indicating that failure is likely. + //There is no standard way for a host to read or change attribute threshholds. + //See the SMART(RETURN STATUS) command for information about how a device reports that a + //threshhold has been exceeded. + // TODO: Query Threshholds. + + for (UCHAR i = 0; i < 30; ++i) + { + PSMART_ATTRIBUTE attribute = (PSMART_ATTRIBUTE)(storagePredictFailure.VendorSpecific + i * sizeof(SMART_ATTRIBUTE) + SMART_HEADER_SIZE); + + // Attribute values 0x00, 0xFE, 0xFF are invalid. + // There is no requirement that attributes be in any particular order. + if ( + attribute->Id != 0x00 && + attribute->Id != 0xFE && + attribute->Id != 0xFF + ) + { + PSMART_ATTRIBUTES info = PhAllocate(sizeof(SMART_ATTRIBUTES)); + memset(info, 0, sizeof(SMART_ATTRIBUTES)); + + info->AttributeId = attribute->Id; + info->CurrentValue = attribute->CurrentValue; + info->WorstValue = attribute->WorstValue; + + // TODO: These flag offsets might be off-by-one. + info->Advisory = (attribute->Flags & 0x1) == 0x0; + info->FailureImminent = (attribute->Flags & 0x1) == 0x1; + info->OnlineDataCollection = (attribute->Flags & 0x2) == 0x2; + info->Performance = (attribute->Flags & 0x3) == 0x3; + info->ErrorRate = (attribute->Flags & 0x4) == 0x4; + info->EventCount = (attribute->Flags & 0x5) == 0x5; + info->SelfPreserving = (attribute->Flags & 0x6) == 0x6; + + info->RawValue = MAKELONG( + MAKEWORD(attribute->RawValue[0], attribute->RawValue[1]), + MAKEWORD(attribute->RawValue[2], attribute->RawValue[3]) + ); + // Missing 2 raw values. + + PhAddItemList(diskAttributeList, info); + } + } + + *DiskSmartAttributes = diskAttributeList; + + return TRUE; + } + + return FALSE; +} + +// requires admin +NTSTATUS DiskDriveQueryCacheInformation( + _In_ HANDLE DeviceHandle + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK isb; + DISK_CACHE_INFORMATION result; + + memset(&result, 0, sizeof(DISK_CACHE_INFORMATION)); + + status = NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_DISK_GET_CACHE_INFORMATION, + NULL, + 0, + &result, + sizeof(result) + ); + + return status; +} + +BOOLEAN DiskDriveQueryAttributes( + _In_ HANDLE DeviceHandle + ) +{ + IO_STATUS_BLOCK isb; + GET_DISK_ATTRIBUTES result; + + memset(&result, 0, sizeof(GET_DISK_ATTRIBUTES)); + + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_DISK_GET_DISK_ATTRIBUTES, + NULL, + 0, + &result, + sizeof(result) + ))) + { + return TRUE; + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryLength( + _In_ HANDLE DeviceHandle, + _Out_ ULONG64* Length + ) +{ + IO_STATUS_BLOCK isb; + GET_LENGTH_INFORMATION result; + + memset(&result, 0, sizeof(GET_LENGTH_INFORMATION)); + + // This gives us the full disk size but requires FILE_GENERIC_READ. + if (NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_DISK_GET_LENGTH_INFO, + NULL, + 0, + &result, + sizeof(result) + ))) + { + *Length = result.Length.QuadPart; + return TRUE; + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryBcProperties( + _In_ HANDLE DeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + PSTORAGE_GET_BC_PROPERTIES_OUTPUT buffer; + + bufferLength = sizeof(STORAGE_GET_BC_PROPERTIES_OUTPUT); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtDeviceIoControlFile( + DeviceHandle, + NULL, + NULL, + NULL, + &isb, + IOCTL_STORAGE_GET_BC_PROPERTIES, + NULL, + 0, + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + PSTORAGE_GET_BC_PROPERTIES_OUTPUT storageDescriptor = (PSTORAGE_GET_BC_PROPERTIES_OUTPUT)buffer; + + if (buffer) + { + PhFree(buffer); + } + + return TRUE; +} + + +BOOLEAN DiskDriveQueryFileSystemInfo( + _In_ HANDLE DosDeviceHandle, + _Out_ USHORT* FileSystemType, + _Out_ PVOID* FileSystemStatistics + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + PFILESYSTEM_STATISTICS buffer; + + bufferLength = sizeof(FILESYSTEM_STATISTICS); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_FILESYSTEM_GET_STATISTICS, // FSCTL_FILESYSTEM_GET_STATISTICS_EX + NULL, + 0, + buffer, + bufferLength + ))) + { + PhFree(buffer); + return FALSE; + } + + switch (buffer->FileSystemType) + { + case FILESYSTEM_STATISTICS_TYPE_NTFS: + case FILESYSTEM_STATISTICS_TYPE_REFS: // ReFS uses the same statistics as NTFS. + { + bufferLength = sizeof(NTFS_FILESYSTEM_STATISTICS) * 64 * (ULONG)PhSystemBasicInformation.NumberOfProcessors; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + } + break; + case FILESYSTEM_STATISTICS_TYPE_FAT: + { + bufferLength = sizeof(FAT_FILESYSTEM_STATISTICS) * 64 * (ULONG)PhSystemBasicInformation.NumberOfProcessors; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + } + break; + case FILESYSTEM_STATISTICS_TYPE_EXFAT: + { + bufferLength = sizeof(EXFAT_FILESYSTEM_STATISTICS) * 64 * (ULONG)PhSystemBasicInformation.NumberOfProcessors; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + } + break; + } + + if (NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_FILESYSTEM_GET_STATISTICS, + NULL, + 0, + buffer, + bufferLength + ))) + { + *FileSystemType = buffer->FileSystemType; + *FileSystemStatistics = buffer; + return TRUE; + } + + PhFree(buffer); + return FALSE; +} + +BOOLEAN DiskDriveQueryNtfsVolumeInfo( + _In_ HANDLE DosDeviceHandle, + _Out_ PNTFS_VOLUME_INFO VolumeInfo + ) +{ + IO_STATUS_BLOCK isb; + NTFS_VOLUME_INFO result; + + memset(&result, 0, sizeof(NTFS_VOLUME_INFO)); + + if (NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_GET_NTFS_VOLUME_DATA, + NULL, + 0, + &result, + sizeof(result) + ))) + { + *VolumeInfo = result; + return TRUE; + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryRefsVolumeInfo( + _In_ HANDLE DosDeviceHandle, + _Out_ PREFS_VOLUME_DATA_BUFFER VolumeInfo + ) +{ + IO_STATUS_BLOCK isb; + REFS_VOLUME_DATA_BUFFER result; + + memset(&result, 0, sizeof(REFS_VOLUME_DATA_BUFFER)); + + if (NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_GET_REFS_VOLUME_DATA, // FSCTL_QUERY_REFS_VOLUME_COUNTER_INFO + NULL, + 0, + &result, + sizeof(result) + ))) + { + *VolumeInfo = result; + return TRUE; + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryTxfsVolumeInfo( + _In_ HANDLE DosDeviceHandle + ) +{ + ULONG bufferLength; + IO_STATUS_BLOCK isb; + PTXFS_LIST_TRANSACTIONS buffer; + + bufferLength = sizeof(TXFS_LIST_TRANSACTIONS); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + //HANDLE deviceHandle = CreateFile( + // L"C:\\", + // GENERIC_READ, + // FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + // NULL, + // OPEN_EXISTING, + // FILE_FLAG_BACKUP_SEMANTICS, + // NULL + // ); + + NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_TXFS_LIST_TRANSACTIONS, + NULL, + 0, + buffer, + bufferLength + ); + + bufferLength = (ULONG)buffer->BufferSizeRequired; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_TXFS_LIST_TRANSACTIONS, + NULL, + 0, + buffer, + bufferLength + ); + + for (ULONG i = 0; i < buffer->NumberOfTransactions; i++) + { + PTXFS_LIST_TRANSACTIONS_ENTRY entry = (PTXFS_LIST_TRANSACTIONS_ENTRY)(buffer + i * sizeof(TXFS_LIST_TRANSACTIONS)); + //PPH_STRING txGuid = PhFormatGuid(&entry->TransactionId); + //entry->TransactionState; + //Resource Manager Identifier : 17DC1CDD-9C6C-11E5-BBC2-F5C37BC15998 + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryBootSectorFsCount( + _In_ HANDLE DosDeviceHandle + ) +{ + IO_STATUS_BLOCK isb; + BOOT_AREA_INFO result; + + memset(&result, 0, sizeof(BOOT_AREA_INFO)); + + if (!NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_GET_BOOT_AREA_INFO, + NULL, + 0, + &result, + sizeof(result) + ))) + { + return FALSE; + } + + // Doesn't work for some unknown reason. + //NtDeviceIoControlFile( + // DosDeviceHandle, + // NULL, + // NULL, + // NULL, + // &isb, + // FSCTL_ALLOW_EXTENDED_DASD_IO, + // NULL, + // 0, + // &result, + // sizeof(result) + // ); + + for (ULONG i = 0; i < result.BootSectorCount; i++) + { + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd442654.aspx + typedef struct _FILE_SYSTEM_RECOGNITION_STRUCTURE + { + UCHAR Jmp[3]; + UCHAR FsName[8]; + UCHAR MustBeZero[5]; + ULONG Identifier; + USHORT Length; + USHORT Checksum; + } FILE_SYSTEM_RECOGNITION_STRUCTURE; + + +#include + typedef struct _BOOT_SECTOR + { + UCHAR Jmp[3]; + UCHAR Format[8]; + USHORT BytesPerSector; + UCHAR SectorsPerCluster; + USHORT ReservedSectorCount; + UCHAR Mbz1; + USHORT Mbz2; + USHORT Reserved1; + UCHAR MediaType; + USHORT Mbz3; + USHORT SectorsPerTrack; + USHORT NumberOfHeads; + ULONG PartitionOffset; + ULONG Reserved2[2]; + ULONGLONG TotalSectors; + ULONGLONG MftStartLcn; + ULONGLONG Mft2StartLcn; + ULONG ClustersPerFileRecord; + ULONG ClustersPerIndexBlock; + ULONGLONG VolumeSerialNumber; + UCHAR Code[0x1AE]; // 430 + USHORT BootSignature; + } BOOT_SECTOR, *PBOOT_SECTOR; +#include + + + BOOT_SECTOR sector; + + memset(§or, 0, sizeof(BOOT_SECTOR)); + + // There are 2 boot sectors on NTFS partitions, we can't access the second one. + // To read or write to the last few sectors of a volume (where the second boot sector is located), + // you must call FSCTL_ALLOW_EXTENDED_DASD_IO, which instructs the file system to not perform any boundary checks, + // which doesn't work for some reason. + if (!NT_SUCCESS(NtReadFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + §or, + sizeof(BOOT_SECTOR), + &result.BootSectors[i].Offset, + NULL + ))) + { + + } + } + + return FALSE; +} + +BOOLEAN DiskDriveQueryVolumeDirty( + _In_ HANDLE DosDeviceHandle, + _Out_ PBOOLEAN IsDirty + ) +{ + ULONG result; + IO_STATUS_BLOCK isb; + + memset(&result, 0, sizeof(ULONG)); + + if (NT_SUCCESS(NtFsControlFile( + DosDeviceHandle, + NULL, + NULL, + NULL, + &isb, + FSCTL_IS_VOLUME_DIRTY, + NULL, + 0, + &result, + sizeof(result) + ))) + { + if (result & VOLUME_IS_DIRTY) + { + *IsDirty = TRUE; + } + else + { + *IsDirty = FALSE; + } + + return TRUE; + } + + return FALSE; +} + + + +NTSTATUS DiskDriveQueryVolumeInformation( + _In_ HANDLE DosDeviceHandle, + _Out_ PFILE_FS_VOLUME_INFORMATION* VolumeInfo + ) +{ + NTSTATUS status; + ULONG bufferLength; + IO_STATUS_BLOCK isb; + PFILE_FS_VOLUME_INFORMATION buffer; + + bufferLength = sizeof(FILE_FS_VOLUME_INFORMATION); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + status = NtQueryVolumeInformationFile( + DosDeviceHandle, + &isb, + buffer, + bufferLength, + FileFsVolumeInformation + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + bufferLength = sizeof(FILE_FS_VOLUME_INFORMATION) + buffer->VolumeLabelLength; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + status = NtQueryVolumeInformationFile( + DosDeviceHandle, + &isb, + buffer, + bufferLength, + FileFsVolumeInformation + ); + } + + if (NT_SUCCESS(status)) + { + *VolumeInfo = buffer; + return status; + } + + PhFree(buffer); + return status; +} + +NTSTATUS DiskDriveQueryVolumeAttributes( + _In_ HANDLE DosDeviceHandle, + _Out_ PFILE_FS_ATTRIBUTE_INFORMATION* AttributeInfo + ) +{ + NTSTATUS status; + ULONG bufferLength; + IO_STATUS_BLOCK isb; + PFILE_FS_ATTRIBUTE_INFORMATION buffer; + + bufferLength = sizeof(FILE_FS_ATTRIBUTE_INFORMATION); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + status = NtQueryVolumeInformationFile( + DosDeviceHandle, + &isb, + buffer, + bufferLength, + FileFsAttributeInformation + ); + + if (status == STATUS_BUFFER_OVERFLOW) + { + bufferLength = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + buffer->FileSystemNameLength; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + status = NtQueryVolumeInformationFile( + DosDeviceHandle, + &isb, + buffer, + bufferLength, + FileFsAttributeInformation + ); + } + + if (NT_SUCCESS(status)) + { + *AttributeInfo = buffer; + return status; + } + + PhFree(buffer); + return status; +} + +BOOLEAN DiskDriveQueryVolumeFreeSpace( + _In_ HANDLE DosDeviceHandle, + _Out_ ULONG64* TotalLength, + _Out_ ULONG64* FreeLength + ) +{ + IO_STATUS_BLOCK isb; + FILE_FS_FULL_SIZE_INFORMATION result; + + memset(&result, 0, sizeof(FILE_FS_FULL_SIZE_INFORMATION)); + + if (NT_SUCCESS(NtQueryVolumeInformationFile( + DosDeviceHandle, + &isb, + &result, + sizeof(FILE_FS_FULL_SIZE_INFORMATION), + FileFsFullSizeInformation + ))) + { + *TotalLength = result.TotalAllocationUnits.QuadPart * result.SectorsPerAllocationUnit * result.BytesPerSector; + *FreeLength = result.ActualAvailableAllocationUnits.QuadPart * result.SectorsPerAllocationUnit * result.BytesPerSector; + return TRUE; + } + + return FALSE; +} + +BOOLEAN DiskDriveFlushCache( + _In_ HANDLE DosDeviceHandle + ) +{ + IO_STATUS_BLOCK isb; + + // NtFlushBuffersFile + // + // If a volume handle is specified: + // - Write all modified data for all files on the volume from the Windows + // in-memory cache. + // - Commit all pending metadata changes for all files on the volume from + // the Windows in-memory cache. + // - Send a SYNC command to the underlying storage device to commit all + // written data in the devices cache to persistent storage. + // + + if (NT_SUCCESS(NtFlushBuffersFile(DosDeviceHandle, &isb))) + return TRUE; + + return FALSE; +} + +PWSTR SmartAttributeGetText( + _In_ SMART_ATTRIBUTE_ID AttributeId + ) +{ + // from https://en.wikipedia.org/wiki/S.M.A.R.T + + switch (AttributeId) + { + case SMART_ATTRIBUTE_ID_READ_ERROR_RATE: // Critical + return L"Raw Read Error Rate"; + case SMART_ATTRIBUTE_ID_THROUGHPUT_PERFORMANCE: + return L"Throughput Performance"; + case SMART_ATTRIBUTE_ID_SPIN_UP_TIME: + return L"Spin Up Time"; + case SMART_ATTRIBUTE_ID_START_STOP_COUNT: + return L"Start/Stop Count"; + case SMART_ATTRIBUTE_ID_REALLOCATED_SECTORS_COUNT: // Critical + return L"Reallocated Sectors Count"; + case SMART_ATTRIBUTE_ID_READ_CHANNEL_MARGIN: + return L"Read Channel Margin"; + case SMART_ATTRIBUTE_ID_SEEK_ERROR_RATE: + return L"Seek Error Rate"; + case SMART_ATTRIBUTE_ID_SEEK_TIME_PERFORMANCE: + return L"Seek Time Performance"; + case SMART_ATTRIBUTE_ID_POWER_ON_HOURS: + return L"Power-On Hours"; + case SMART_ATTRIBUTE_ID_SPIN_RETRY_COUNT: + return L"Spin Retry Count"; + case SMART_ATTRIBUTE_ID_CALIBRATION_RETRY_COUNT: + return L"Recalibration Retries"; + case SMART_ATTRIBUTE_ID_POWER_CYCLE_COUNT: + return L"Device Power Cycle Count"; + case SMART_ATTRIBUTE_ID_SOFT_READ_ERROR_RATE: + return L"Soft Read Error Rate"; + case SMART_ATTRIBUTE_ID_SATA_DOWNSHIFT_ERROR_COUNT: + return L"Sata Downshift Error Count"; + case SMART_ATTRIBUTE_ID_END_TO_END_ERROR: + return L"End To End Error"; + case SMART_ATTRIBUTE_ID_HEAD_STABILITY: + return L"Head Stability"; + case SMART_ATTRIBUTE_ID_INDUCED_OP_VIBRATION_DETECTION: + return L"Induced Op Vibration Detection"; + case SMART_ATTRIBUTE_ID_REPORTED_UNCORRECTABLE_ERRORS: + return L"Reported Uncorrectable Errors"; + case SMART_ATTRIBUTE_ID_COMMAND_TIMEOUT: + return L"Command Timeout"; + case SMART_ATTRIBUTE_ID_HIGH_FLY_WRITES: + return L"High Fly Writes"; + case SMART_ATTRIBUTE_ID_TEMPERATURE_DIFFERENCE_FROM_100: + return L"Airflow Temperature"; + case SMART_ATTRIBUTE_ID_GSENSE_ERROR_RATE: + return L"GSense Error Rate"; + case SMART_ATTRIBUTE_ID_POWER_OFF_RETRACT_COUNT: + return L"Power-off Retract Count"; + case SMART_ATTRIBUTE_ID_LOAD_CYCLE_COUNT: + return L"Load/Unload Cycle Count"; + case SMART_ATTRIBUTE_ID_TEMPERATURE: + return L"Temperature"; + case SMART_ATTRIBUTE_ID_HARDWARE_ECC_RECOVERED: + return L"Hardware ECC Recovered"; + case SMART_ATTRIBUTE_ID_REALLOCATION_EVENT_COUNT: // Critical + return L"Reallocation Event Count"; + case SMART_ATTRIBUTE_ID_CURRENT_PENDING_SECTOR_COUNT: // Critical + return L"Current Pending Sector Count"; + case SMART_ATTRIBUTE_ID_UNCORRECTABLE_SECTOR_COUNT: // Critical + return L"Uncorrectable Sector Count"; + case SMART_ATTRIBUTE_ID_ULTRADMA_CRC_ERROR_COUNT: + return L"UltraDMA CRC Error Count"; + case SMART_ATTRIBUTE_ID_MULTI_ZONE_ERROR_RATE: + return L"Write Error Rate"; + case SMART_ATTRIBUTE_ID_OFFTRACK_SOFT_READ_ERROR_RATE: + return L"Soft read error rate"; + case SMART_ATTRIBUTE_ID_DATA_ADDRESS_MARK_ERRORS: + return L"Data Address Mark errors"; + case SMART_ATTRIBUTE_ID_RUN_OUT_CANCEL: + return L"Run out cancel"; + case SMART_ATTRIBUTE_ID_SOFT_ECC_CORRECTION: + return L"Soft ECC correction"; + case SMART_ATTRIBUTE_ID_THERMAL_ASPERITY_RATE_TAR: + return L"Thermal asperity rate (TAR)"; + case SMART_ATTRIBUTE_ID_FLYING_HEIGHT: + return L"Flying height"; + case SMART_ATTRIBUTE_ID_SPIN_HIGH_CURRENT: + return L"Spin high current"; + case SMART_ATTRIBUTE_ID_SPIN_BUZZ: + return L"Spin buzz"; + case SMART_ATTRIBUTE_ID_OFFLINE_SEEK_PERFORMANCE: + return L"Offline seek performance"; + case SMART_ATTRIBUTE_ID_VIBRATION_DURING_WRITE: + return L"Vibration During Write"; + case SMART_ATTRIBUTE_ID_SHOCK_DURING_WRITE: + return L"Shock During Write"; + case SMART_ATTRIBUTE_ID_DISK_SHIFT: // Critical + return L"Disk Shift"; + case SMART_ATTRIBUTE_ID_GSENSE_ERROR_RATE_ALT: + return L"G-Sense Error Rate (Alt)"; + case SMART_ATTRIBUTE_ID_LOADED_HOURS: + return L"Loaded Hours"; + case SMART_ATTRIBUTE_ID_LOAD_UNLOAD_RETRY_COUNT: + return L"Load/Unload Retry Count"; + case SMART_ATTRIBUTE_ID_LOAD_FRICTION: + return L"Load Friction"; + case SMART_ATTRIBUTE_ID_LOAD_UNLOAD_CYCLE_COUNT: + return L"Load Unload Cycle Count"; + case SMART_ATTRIBUTE_ID_LOAD_IN_TIME: + return L"Load-in Time"; + case SMART_ATTRIBUTE_ID_TORQUE_AMPLIFICATION_COUNT: + return L"Torque Amplification Count"; + case SMART_ATTRIBUTE_ID_POWER_OFF_RETTRACT_CYCLE: + return L"Power-Off Retract Count"; + case SMART_ATTRIBUTE_ID_GMR_HEAD_AMPLITUDE: + return L"GMR Head Amplitude"; + case SMART_ATTRIBUTE_ID_DRIVE_TEMPERATURE: + return L"Temperature"; + case SMART_ATTRIBUTE_ID_HEAD_FLYING_HOURS: // Transfer Error Rate (Fujitsu) + return L"Head Flying Hours"; + case SMART_ATTRIBUTE_ID_TOTAL_LBA_WRITTEN: + return L"Total LBAs Written"; + case SMART_ATTRIBUTE_ID_TOTAL_LBA_READ: + return L"Total LBAs Read"; + case SMART_ATTRIBUTE_ID_READ_ERROR_RETY_RATE: + return L"Read Error Retry Rate"; + case SMART_ATTRIBUTE_ID_FREE_FALL_PROTECTION: + return L"Free Fall Protection"; + } + + return L"BUG BUG BUG"; +} + +PWSTR SmartAttributeGetDescription( + _In_ SMART_ATTRIBUTE_ID AttributeId + ) +{ + // from https://en.wikipedia.org/wiki/S.M.A.R.T + switch (AttributeId) + { + case SMART_ATTRIBUTE_ID_READ_ERROR_RATE: + return L"Lower raw value is better.\r\nVendor specific raw value. Stores data related to the rate of hardware read errors that occurred when reading data from a disk surface. The raw value has different structure for different vendors and is often not meaningful as a decimal number."; + case SMART_ATTRIBUTE_ID_THROUGHPUT_PERFORMANCE: + return L"Higher raw value is better.\r\nOverall (general) throughput performance of a hard disk drive. If the value of this attribute is decreasing there is a high probability that there is a problem with the disk."; + case SMART_ATTRIBUTE_ID_SPIN_UP_TIME: + return L"Lower raw value is better.\r\nAverage time of spindle spin up (from zero RPM to fully operational [milliseconds])."; + case SMART_ATTRIBUTE_ID_START_STOP_COUNT: + return L"A tally of spindle start/stop cycles.\r\nThe spindle turns on, and hence the count is increased, both when the hard disk is turned on after having before been turned entirely off (disconnected from power source) and when the hard disk returns from having previously been put to sleep mode."; + case SMART_ATTRIBUTE_ID_REALLOCATED_SECTORS_COUNT: + return L"Lower raw value is better.\r\nCount of reallocated sectors. When the hard drive finds a read/write/verification error, it marks that sector as \"reallocated\" and transfers data to a special reserved area (spare area). This process is also known as remapping, and reallocated sectors are called \"remaps\". The raw value normally represents a count of the bad sectors that have been found and remapped. Thus, the higher the attribute value, the more sectors the drive has had to reallocate. This allows a drive with bad sectors to continue operation; however, a drive which has had any reallocations at all is significantly more likely to fail in the near future. While primarily used as a metric of the life expectancy of the drive, this number also affects performance. As the count of reallocated sectors increases, the read/write speed tends to become worse because the drive head is forced to seek to the reserved area whenever a remap is accessed. If sequential access speed is critical, the remapped sectors can be manually marked as bad blocks in the file system in order to prevent their use."; + case SMART_ATTRIBUTE_ID_READ_CHANNEL_MARGIN: + return L"Margin of a channel while reading data.\r\nThe function of this attribute is not specified."; + case SMART_ATTRIBUTE_ID_SEEK_ERROR_RATE: + return L"Vendor specific raw value.\r\nRate of seek errors of the magnetic heads. If there is a partial failure in the mechanical positioning system, then seek errors will arise. Such a failure may be due to numerous factors, such as damage to a servo, or thermal widening of the hard disk. The raw value has different structure for different vendors and is often not meaningful as a decimal number."; + case SMART_ATTRIBUTE_ID_SEEK_TIME_PERFORMANCE: + return L"Average performance of seek operations of the magnetic heads.\r\nIf this attribute is decreasing, it is a sign of problems in the mechanical subsystem."; + case SMART_ATTRIBUTE_ID_POWER_ON_HOURS: + return L"Count of hours in power-on state.\r\nThe raw value of this attribute shows total count of hours (or minutes, or seconds, depending on manufacturer) in power-on state.\r\nBy default, the total expected lifetime of a hard disk in perfect condition is defined as 5 years(running every day and night on all days).This is equal to 1825 days in 24 / 7 mode or 43800 hours.\r\nOn some pre-2005 drives, this raw value may advance erratically and/or \"wrap around\" (reset to zero periodically)."; + case SMART_ATTRIBUTE_ID_SPIN_RETRY_COUNT: + return L"Count of retry of spin start attempts.\r\nThis attribute stores a total count of the spin start attempts to reach the fully operational speed (under the condition that the first attempt was unsuccessful). An increase of this attribute value is a sign of problems in the hard disk mechanical subsystem."; + case SMART_ATTRIBUTE_ID_CALIBRATION_RETRY_COUNT: + return L"This attribute indicates the count that recalibration was requested (under the condition that the first attempt was unsuccessful). An increase of this attribute value is a sign of problems in the hard disk mechanical subsystem."; + case SMART_ATTRIBUTE_ID_POWER_CYCLE_COUNT: + return L"This attribute indicates the count of full hard disk power on/off cycles."; + case SMART_ATTRIBUTE_ID_SOFT_READ_ERROR_RATE: + return L"Uncorrected read errors reported to the operating system."; + } + + //TODO: Include more descriptions.. + + return L""; +} \ No newline at end of file diff --git a/plugins/NetworkTools/CHANGELOG.txt b/plugins/NetworkTools/CHANGELOG.txt new file mode 100644 index 0000000..ea060c6 --- /dev/null +++ b/plugins/NetworkTools/CHANGELOG.txt @@ -0,0 +1,24 @@ +1.6 + * Added PathPing support + +1.5 + * Improved ping graph scaling + +1.4 + * Fixed whois scrolling issue + * Fixed ping threading issues + * Fixed various memory leaks + +1.3 + * Added plugin options for ping/tracert/whois + * Added native pint/tracert/whois support + * Updated UI + +1.2 + * Fixed encoding bug + +1.1 + * Fixed handle leak + +1.0 + * Initial release diff --git a/plugins/NetworkTools/NetworkTools.rc b/plugins/NetworkTools/NetworkTools.rc new file mode 100644 index 0000000..eaa0513 --- /dev/null +++ b/plugins/NetworkTools/NetworkTools.rc @@ -0,0 +1,196 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,6,0,0 + PRODUCTVERSION 1,6,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "dmex" + VALUE "FileDescription", "Network Tools plugin for Process Hacker" + VALUE "FileVersion", "1.6" + VALUE "InternalName", "NetworkTools" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "NetworkTools.dll" + VALUE "ProductName", "Network Tools plugin for Process Hacker" + VALUE "ProductVersion", "1.6" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OUTPUT DIALOGEX 0, 0, 319, 183 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Network Tools" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_NETOUTPUTEDIT,7,7,305,152,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | NOT WS_BORDER | WS_VSCROLL,WS_EX_CLIENTEDGE + CONTROL "More information",IDC_MORE_INFO,"SysLink",NOT WS_VISIBLE | WS_TABSTOP,7,163,77,12 + PUSHBUTTON "Close",IDOK,263,162,50,14 +END + +IDD_OPTIONS DIALOGEX 0, 0, 215, 79 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Timeout (milliseconds):",IDC_STATIC,9,7,73,8 + EDITTEXT IDC_MAXTIMEOUTTEXT,7,17,79,14,ES_AUTOHSCROLL | ES_NUMBER + DEFPUSHBUTTON "OK",IDOK,104,58,50,14 + PUSHBUTTON "Cancel",IDCANCEL,158,58,50,14 +END + +IDD_PINGDIALOG DIALOGEX 0, 0, 269, 131 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Dialog" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Pinging X with X bytes of data:",IDC_MAINTEXT,7,7,255,16 + LTEXT "PingGraphLayout",IDC_PING_LAYOUT,7,26,255,27,NOT WS_VISIBLE | WS_BORDER + GROUPBOX "Ping statistics",IDC_ICMP_PANEL,7,56,254,52,0,WS_EX_TRANSPARENT + LTEXT "Average: 0ms",IDC_ICMP_AVG,13,69,73,8 + LTEXT "Pings sent: 0",IDC_PINGS_SENT,92,69,88,8 + LTEXT "Bad hashes: 0",IDC_BAD_HASH,186,69,72,8 + LTEXT "Minimum: 0ms",IDC_ICMP_MIN,13,81,73,8 + LTEXT "Pings lost: 0 (0%)",IDC_PINGS_LOST,92,81,88,8 + LTEXT "Anon replies: 0",IDC_ANON_ADDR,186,81,72,8 + LTEXT "Maximum: 0ms",IDC_ICMP_MAX,13,93,73,8 + DEFPUSHBUTTON "Close",IDOK,212,110,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OUTPUT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 312 + TOPMARGIN, 7 + BOTTOMMARGIN, 176 + END + + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 208 + TOPMARGIN, 7 + BOTTOMMARGIN, 72 + END + + IDD_PINGDIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 262 + TOPMARGIN, 7 + BOTTOMMARGIN, 124 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OUTPUT AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/NetworkTools/NetworkTools.vcxproj b/plugins/NetworkTools/NetworkTools.vcxproj new file mode 100644 index 0000000..f264710 --- /dev/null +++ b/plugins/NetworkTools/NetworkTools.vcxproj @@ -0,0 +1,109 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E} + NetworkTools + Win32Proj + NetworkTools + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + iphlpapi.lib;winhttp.lib;ws2_32.lib;%(AdditionalDependencies) + iphlpapi.dll;winhttp.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;winhttp.lib;ws2_32.lib;%(AdditionalDependencies) + iphlpapi.dll;winhttp.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;winhttp.lib;ws2_32.lib;%(AdditionalDependencies) + iphlpapi.dll;winhttp.dll;%(DelayLoadDLLs) + + + + + iphlpapi.lib;winhttp.lib;ws2_32.lib;%(AdditionalDependencies) + iphlpapi.dll;winhttp.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/NetworkTools/NetworkTools.vcxproj.filters b/plugins/NetworkTools/NetworkTools.vcxproj.filters new file mode 100644 index 0000000..c7d8450 --- /dev/null +++ b/plugins/NetworkTools/NetworkTools.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/plugins/NetworkTools/main.c b/plugins/NetworkTools/main.c new file mode 100644 index 0000000..eb168ff --- /dev/null +++ b/plugins/NetworkTools/main.c @@ -0,0 +1,164 @@ +/* + * Process Hacker Network Tools - + * main program + * + * Copyright (C) 2010-2011 wj32 + * Copyright (C) 2013 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" + +PPH_PLUGIN PluginInstance; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +PH_CALLBACK_REGISTRATION NetworkMenuInitializingCallbackRegistration; + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + (HWND)Parameter, + OptionsDlgProc + ); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = (PPH_PLUGIN_MENU_ITEM)Parameter; + PPH_NETWORK_ITEM networkItem = (PPH_NETWORK_ITEM)menuItem->Context; + + switch (menuItem->Id) + { + case NETWORK_ACTION_PING: + PerformNetworkAction(NETWORK_ACTION_PING, networkItem); + break; + case NETWORK_ACTION_TRACEROUTE: + PerformNetworkAction(NETWORK_ACTION_TRACEROUTE, networkItem); + break; + case NETWORK_ACTION_WHOIS: + PerformNetworkAction(NETWORK_ACTION_WHOIS, networkItem); + break; + case NETWORK_ACTION_PATHPING: + PerformNetworkAction(NETWORK_ACTION_PATHPING, networkItem); + break; + } +} + +VOID NTAPI NetworkMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = (PPH_PLUGIN_MENU_INFORMATION)Parameter; + PPH_NETWORK_ITEM networkItem; + PPH_EMENU_ITEM toolsMenu; + PPH_EMENU_ITEM closeMenuItem; + + if (menuInfo->u.Network.NumberOfNetworkItems == 1) + networkItem = menuInfo->u.Network.NetworkItems[0]; + else + networkItem = NULL; + + // Create the Tools menu. + toolsMenu = PhPluginCreateEMenuItem(PluginInstance, 0, 0, L"Tools", NULL); + PhInsertEMenuItem(toolsMenu, PhPluginCreateEMenuItem(PluginInstance, 0, NETWORK_ACTION_PING, L"Ping", networkItem), -1); + PhInsertEMenuItem(toolsMenu, PhPluginCreateEMenuItem(PluginInstance, 0, NETWORK_ACTION_TRACEROUTE, L"Traceroute", networkItem), -1); + PhInsertEMenuItem(toolsMenu, PhPluginCreateEMenuItem(PluginInstance, 0, NETWORK_ACTION_WHOIS, L"Whois", networkItem), -1); + PhInsertEMenuItem(toolsMenu, PhPluginCreateEMenuItem(PluginInstance, 0, NETWORK_ACTION_PATHPING, L"PathPing", networkItem), -1); + + // Insert the Tools menu into the network menu. + closeMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Close", 0); + PhInsertEMenuItem(menuInfo->Menu, toolsMenu, closeMenuItem ? PhIndexOfEMenuItem(menuInfo->Menu, closeMenuItem) : 1); + + toolsMenu->Flags |= PH_EMENU_DISABLED; + + if (networkItem) + { + if (!PhIsNullIpAddress(&networkItem->RemoteEndpoint.Address)) + { + toolsMenu->Flags &= ~PH_EMENU_DISABLED; + } + } +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { IntegerPairSettingType, SETTING_NAME_TRACERT_WINDOW_POSITION, L"0,0" }, + { ScalableIntegerPairSettingType, SETTING_NAME_TRACERT_WINDOW_SIZE, L"@96|600,365" }, + { IntegerPairSettingType, SETTING_NAME_PING_WINDOW_POSITION, L"0,0" }, + { ScalableIntegerPairSettingType, SETTING_NAME_PING_WINDOW_SIZE, L"@96|420,250" }, + { IntegerSettingType, SETTING_NAME_PING_TIMEOUT, L"3e8" } // 1000 timeout. + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Network Tools"; + info->Author = L"dmex, wj32"; + info->Description = L"Provides ping, traceroute and whois for network connections."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1117"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackNetworkMenuInitializing), + NetworkMenuInitializingCallback, + NULL, + &NetworkMenuInitializingCallbackRegistration + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/NetworkTools/nettools.h b/plugins/NetworkTools/nettools.h new file mode 100644 index 0000000..f6b5af3 --- /dev/null +++ b/plugins/NetworkTools/nettools.h @@ -0,0 +1,145 @@ +/* + * Process Hacker ToolStatus - + * toolstatus header + * + * Copyright (C) 2010-2013 wj32 + * Copyright (C) 2012-2013 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef NETTOOLS_H +#define NETTOOLS_H + +#define CINTERFACE +#define COBJMACROS +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "resource.h" + +#define PLUGIN_NAME L"ProcessHacker.NetworkTools" +#define SETTING_NAME_TRACERT_WINDOW_POSITION (PLUGIN_NAME L".WindowPosition") +#define SETTING_NAME_TRACERT_WINDOW_SIZE (PLUGIN_NAME L".WindowSize") +#define SETTING_NAME_PING_WINDOW_POSITION (PLUGIN_NAME L".PingWindowPosition") +#define SETTING_NAME_PING_WINDOW_SIZE (PLUGIN_NAME L".PingWindowSize") +#define SETTING_NAME_PING_TIMEOUT (PLUGIN_NAME L".PingMaxTimeout") + +// ICMP Packet Length: (msdn: IcmpSendEcho2/Icmp6SendEcho2) +// The buffer must be large enough to hold at least one ICMP_ECHO_REPLY or ICMPV6_ECHO_REPLY structure +// + the number of bytes of data specified in the RequestSize parameter. +// This buffer should also be large enough to also hold 8 more bytes of data (the size of an ICMP error message) +// + space for an IO_STATUS_BLOCK structure. +#define ICMP_BUFFER_SIZE(EchoReplyLength, Buffer) (ULONG)(((EchoReplyLength) + (Buffer)->Length) + 8 + sizeof(IO_STATUS_BLOCK)) + +extern PPH_PLUGIN PluginInstance; + +typedef enum _PH_NETWORK_ACTION +{ + NETWORK_ACTION_PING, + NETWORK_ACTION_TRACEROUTE, + NETWORK_ACTION_WHOIS, + NETWORK_ACTION_FINISH, + NETWORK_ACTION_PATHPING +} PH_NETWORK_ACTION; + +// output +#define NTM_RECEIVEDTRACE (WM_APP + NETWORK_ACTION_TRACEROUTE) +#define NTM_RECEIVEDWHOIS (WM_APP + NETWORK_ACTION_WHOIS) +#define NTM_RECEIVEDFINISH (WM_APP + NETWORK_ACTION_FINISH) + +typedef struct _NETWORK_OUTPUT_CONTEXT +{ + PH_NETWORK_ACTION Action; + PH_LAYOUT_MANAGER LayoutManager; + PH_WORK_QUEUE PingWorkQueue; + PH_GRAPH_STATE PingGraphState; + PH_CIRCULAR_BUFFER_ULONG PingHistory; + PH_CALLBACK_REGISTRATION ProcessesUpdatedRegistration; + + HWND WindowHandle; + HWND ParentHandle; + HWND StatusHandle; + HWND PingGraphHandle; + HWND OutputHandle; + HANDLE PipeReadHandle; + HANDLE ProcessHandle; + HFONT FontHandle; + HICON IconHandle; + + ULONG CurrentPingMs; + ULONG MaxPingTimeout; + ULONG HashFailCount; + ULONG UnknownAddrCount; + ULONG PingMinMs; + ULONG PingMaxMs; + ULONG PingSentCount; + ULONG PingRecvCount; + ULONG PingLossCount; + + PPH_NETWORK_ITEM NetworkItem; + PH_IP_ADDRESS IpAddress; + WCHAR IpAddressString[INET6_ADDRSTRLEN]; +} NETWORK_OUTPUT_CONTEXT, *PNETWORK_OUTPUT_CONTEXT; + +NTSTATUS PhNetworkPingDialogThreadStart( + _In_ PVOID Parameter + ); + +VOID PerformNetworkAction( + _In_ PH_NETWORK_ACTION Action, + _In_ PPH_NETWORK_ITEM NetworkItem + ); + +NTSTATUS NetworkPingThreadStart( + _In_ PVOID Parameter + ); + +NTSTATUS NetworkTracertThreadStart( + _In_ PVOID Parameter + ); + +NTSTATUS NetworkWhoisThreadStart( + _In_ PVOID Parameter + ); + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +INT_PTR CALLBACK NetworkOutputDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#endif diff --git a/plugins/NetworkTools/options.c b/plugins/NetworkTools/options.c new file mode 100644 index 0000000..c4aa9f0 --- /dev/null +++ b/plugins/NetworkTools/options.c @@ -0,0 +1,62 @@ +/* + * Process Hacker Network Tools - + * options dialog + * + * Copyright (C) 2010-2013 wj32 + * Copyright (C) 2012-2013 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetDlgItemInt(hwndDlg, IDC_MAXTIMEOUTTEXT, PhGetIntegerSetting(SETTING_NAME_PING_TIMEOUT), FALSE); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + ULONG maxPingTimeout = GetDlgItemInt(hwndDlg, IDC_MAXTIMEOUTTEXT, NULL, FALSE); + + PhSetIntegerSetting(SETTING_NAME_PING_TIMEOUT, maxPingTimeout); + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/NetworkTools/output.c b/plugins/NetworkTools/output.c new file mode 100644 index 0000000..562bae0 --- /dev/null +++ b/plugins/NetworkTools/output.c @@ -0,0 +1,423 @@ +/* + * Process Hacker Network Tools - + * output dialog + * + * Copyright (C) 2010-2015 wj32 + * Copyright (C) 2012-2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +INT_PTR CALLBACK NetworkOutputDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PNETWORK_OUTPUT_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PNETWORK_OUTPUT_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PNETWORK_OUTPUT_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + { + PhSaveWindowPlacementToSetting(SETTING_NAME_TRACERT_WINDOW_POSITION, SETTING_NAME_TRACERT_WINDOW_SIZE, hwndDlg); + PhDeleteLayoutManager(&context->LayoutManager); + + if (context->ProcessHandle) + { + // Terminate the child process. + PhTerminateProcess(context->ProcessHandle, STATUS_SUCCESS); + + // Close the child process handle. + NtClose(context->ProcessHandle); + } + + // Close the pipe handle. + if (context->PipeReadHandle) + NtClose(context->PipeReadHandle); + + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + + PostQuitMessage(0); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PH_RECTANGLE windowRectangle; + + context->WindowHandle = hwndDlg; + context->OutputHandle = GetDlgItem(hwndDlg, IDC_NETOUTPUTEDIT); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, context->OutputHandle, NULL, PH_ANCHOR_ALL); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_MORE_INFO), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT); + + windowRectangle.Position = PhGetIntegerPairSetting(SETTING_NAME_TRACERT_WINDOW_POSITION); + windowRectangle.Size = PhGetScalableIntegerPairSetting(SETTING_NAME_TRACERT_WINDOW_SIZE, TRUE).Pair; + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 190; + rect.bottom = 120; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + // Check for first-run default position. + if (windowRectangle.Position.X == 0 || windowRectangle.Position.Y == 0) + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + } + else + { + PhLoadWindowPlacementFromSetting(SETTING_NAME_TRACERT_WINDOW_POSITION, SETTING_NAME_TRACERT_WINDOW_SIZE, hwndDlg); + } + + if (context->IpAddress.Type == PH_IPV4_NETWORK_TYPE) + { + RtlIpv4AddressToString(&context->IpAddress.InAddr, context->IpAddressString); + } + else + { + RtlIpv6AddressToString(&context->IpAddress.In6Addr, context->IpAddressString); + } + + switch (context->Action) + { + case NETWORK_ACTION_TRACEROUTE: + { + HANDLE dialogThread = INVALID_HANDLE_VALUE; + + Static_SetText(context->WindowHandle, + PhaFormatString(L"Tracing route to %s...", context->IpAddressString)->Buffer + ); + + if (dialogThread = PhCreateThread(0, NetworkTracertThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + break; + case NETWORK_ACTION_WHOIS: + { + HANDLE dialogThread = INVALID_HANDLE_VALUE; + + Static_SetText(context->WindowHandle, + PhaFormatString(L"Whois %s...", context->IpAddressString)->Buffer + ); + + ShowWindow(GetDlgItem(hwndDlg, IDC_MORE_INFO), SW_SHOW); + + if (dialogThread = PhCreateThread(0, NetworkWhoisThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + break; + case NETWORK_ACTION_PATHPING: + { + HANDLE dialogThread = INVALID_HANDLE_VALUE; + + Static_SetText(context->WindowHandle, + PhaFormatString(L"Pathing route to %s...", context->IpAddressString)->Buffer + ); + + if (dialogThread = PhCreateThread(0, NetworkTracertThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + break; + } + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + } + } + break; + case WM_SIZE: + PhLayoutManagerLayout(&context->LayoutManager); + break; + case WM_SIZING: + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + break; + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + HWND hwndChild = (HWND)lParam; + + // Check if old graph colors are enabled. + if (!PhGetIntegerSetting(L"GraphColorMode")) + break; + + // Check for our edit control and change the color. + if (hwndChild == context->OutputHandle) + { + // Set a transparent background for the control backcolor. + //SetBkMode(hDC, TRANSPARENT); + + // Set the Edit control background. + SetBkColor(hDC, RGB(0x0, 0x0, 0x0)); + + // Set text color as the Green PH graph text color. + SetTextColor(hDC, RGB(124, 252, 0)); + + // Set a black control backcolor. + return (INT_PTR)GetStockBrush(BLACK_BRUSH); + } + } + break; + case WM_NOTIFY: + { + switch (((LPNMHDR)lParam)->code) + { + case NM_CLICK: + case NM_RETURN: + { + PNMLINK syslink = (PNMLINK)lParam; + + if (syslink->hdr.idFrom == IDC_MORE_INFO) + { + PhShellExecute( + PhMainWndHandle, + PhaConcatStrings2(L"http://wq.apnic.net/apnic-bin/whois.pl?searchtext=", context->IpAddressString)->Buffer, + NULL + ); + } + } + break; + } + } + break; + case NTM_RECEIVEDTRACE: + { + OEM_STRING inputString; + UNICODE_STRING convertedString; + PH_STRING_BUILDER receivedString; + + if (wParam != 0) + { + inputString.Buffer = (PCHAR)lParam; + inputString.Length = (USHORT)wParam; + + if (NT_SUCCESS(RtlOemStringToUnicodeString(&convertedString, &inputString, TRUE))) + { + PPH_STRING windowText = NULL; + + PhInitializeStringBuilder(&receivedString, PAGE_SIZE); + + // Get the current output text. + windowText = PhGetWindowText(context->OutputHandle); + + // Append the current output text to the New string. + if (!PhIsNullOrEmptyString(windowText)) + PhAppendStringBuilder(&receivedString, &windowText->sr); + + PhAppendFormatStringBuilder(&receivedString, L"%s", convertedString.Buffer); + + // Remove leading newlines. + if (receivedString.String->Length >= 2 * 2 && + receivedString.String->Buffer[0] == '\r' && + receivedString.String->Buffer[1] == '\n') + { + PhRemoveStringBuilder(&receivedString, 0, 2); + } + + SetWindowText(context->OutputHandle, receivedString.String->Buffer); + SendMessage( + context->OutputHandle, + EM_SETSEL, + receivedString.String->Length / 2 - 1, + receivedString.String->Length / 2 - 1 + ); + SendMessage(context->OutputHandle, WM_VSCROLL, SB_BOTTOM, 0); + + PhDereferenceObject(windowText); + PhDeleteStringBuilder(&receivedString); + RtlFreeUnicodeString(&convertedString); + } + } + } + break; + case NTM_RECEIVEDWHOIS: + { + OEM_STRING inputString; + UNICODE_STRING convertedString; + PH_STRING_BUILDER receivedString; + + if (lParam != 0) + { + inputString.Buffer = (PCHAR)lParam; + inputString.Length = (USHORT)wParam; + + if (NT_SUCCESS(RtlOemStringToUnicodeString(&convertedString, &inputString, TRUE))) + { + USHORT i; + + PhInitializeStringBuilder(&receivedString, PAGE_SIZE); + + // Convert carriage returns. + for (i = 0; i < convertedString.Length; i++) + { + if (convertedString.Buffer[i] == '\n') + { + PhAppendStringBuilder2(&receivedString, L"\r\n"); + } + else + { + PhAppendCharStringBuilder(&receivedString, convertedString.Buffer[i]); + } + } + + // Remove leading newlines. + if (receivedString.String->Length >= 2 * 2 && + receivedString.String->Buffer[0] == '\r' && + receivedString.String->Buffer[1] == '\n') + { + PhRemoveStringBuilder(&receivedString, 0, 2); + } + + SetWindowText(context->OutputHandle, receivedString.String->Buffer); + SendMessage( + context->OutputHandle, + EM_SETSEL, + receivedString.String->Length / 2 - 1, + receivedString.String->Length / 2 - 1 + ); + SendMessage(context->OutputHandle, WM_VSCROLL, SB_TOP, 0); + + PhDeleteStringBuilder(&receivedString); + RtlFreeUnicodeString(&convertedString); + } + + PhFree((PVOID)lParam); + } + } + break; + case NTM_RECEIVEDFINISH: + { + PPH_STRING windowText = PhGetWindowText(context->WindowHandle); + + if (windowText) + { + Static_SetText( + context->WindowHandle, + PhaFormatString(L"%s Finished.", windowText->Buffer)->Buffer + ); + PhDereferenceObject(windowText); + } + } + break; + } + + return FALSE; +} + +NTSTATUS PhNetworkOutputDialogThreadStart( + _In_ PVOID Parameter + ) +{ + BOOL result; + MSG message; + HWND windowHandle; + PH_AUTO_POOL autoPool; + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Parameter; + + PhInitializeAutoPool(&autoPool); + + windowHandle = CreateDialogParam( + (HINSTANCE)PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OUTPUT), + PhMainWndHandle, + NetworkOutputDlgProc, + (LPARAM)Parameter + ); + + ShowWindow(windowHandle, SW_SHOW); + SetForegroundWindow(windowHandle); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!IsDialogMessage(context->WindowHandle, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +VOID PerformNetworkAction( + _In_ PH_NETWORK_ACTION Action, + _In_ PPH_NETWORK_ITEM NetworkItem + ) +{ + HANDLE dialogThread = INVALID_HANDLE_VALUE; + PNETWORK_OUTPUT_CONTEXT context; + + context = (PNETWORK_OUTPUT_CONTEXT)PhAllocate(sizeof(NETWORK_OUTPUT_CONTEXT)); + memset(context, 0, sizeof(NETWORK_OUTPUT_CONTEXT)); + + context->Action = Action; + context->NetworkItem = NetworkItem; + context->IpAddress = NetworkItem->RemoteEndpoint.Address; + + if (context->Action == NETWORK_ACTION_PING) + { + if (dialogThread = PhCreateThread(0, PhNetworkPingDialogThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + else + { + if (dialogThread = PhCreateThread(0, PhNetworkOutputDialogThreadStart, (PVOID)context)) + NtClose(dialogThread); + } +} \ No newline at end of file diff --git a/plugins/NetworkTools/ping.c b/plugins/NetworkTools/ping.c new file mode 100644 index 0000000..b6eff06 --- /dev/null +++ b/plugins/NetworkTools/ping.c @@ -0,0 +1,757 @@ +/* + * Process Hacker Network Tools - + * Ping dialog + * + * Copyright (C) 2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" + +#define WM_PING_UPDATE (WM_APP + 151) + +static RECT NormalGraphTextMargin = { 5, 5, 5, 5 }; +static RECT NormalGraphTextPadding = { 3, 3, 3, 3 }; + +HFONT InitializeFont( + _In_ HWND hwnd + ) +{ + LOGFONT logFont; + + // Create the font handle + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + HDC hdc; + + if (hdc = GetDC(hwnd)) + { + HFONT fontHandle = CreateFont( + -MulDiv(-15, GetDeviceCaps(hdc, LOGPIXELSY), 72), + 0, + 0, + 0, + FW_MEDIUM, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY | ANTIALIASED_QUALITY, + DEFAULT_PITCH, + logFont.lfFaceName + ); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)fontHandle, TRUE); + + ReleaseDC(hwnd, hdc); + + return fontHandle; + } + } + + return NULL; +} + +VOID NetworkPingUpdateGraph( + _In_ PNETWORK_OUTPUT_CONTEXT Context + ) +{ + Context->PingGraphState.Valid = FALSE; + Context->PingGraphState.TooltipIndex = -1; + Graph_MoveGrid(Context->PingGraphHandle, 1); + Graph_Draw(Context->PingGraphHandle); + Graph_UpdateTooltip(Context->PingGraphHandle); + InvalidateRect(Context->PingGraphHandle, NULL, FALSE); +} + +/** + * Creates a Ansi string using format specifiers. + * + * \param Format The format-control string. + * \param ArgPtr A pointer to the list of arguments. + */ +PPH_BYTES FormatAnsiString_V( + _In_ _Printf_format_string_ PSTR Format, + _In_ va_list ArgPtr + ) +{ + PPH_BYTES string; + int length; + + length = _vscprintf(Format, ArgPtr); + + if (length == -1) + return NULL; + + string = PhCreateBytesEx(NULL, length * sizeof(CHAR)); + + _vsnprintf( + string->Buffer, + length, + Format, ArgPtr + ); + + return string; +} + +/** + * Creates a Ansi string using format specifiers. + * + * \param Format The format-control string. + */ +PPH_BYTES FormatAnsiString( + _In_ _Printf_format_string_ PSTR Format, + ... + ) +{ + va_list argptr; + + va_start(argptr, Format); + + return FormatAnsiString_V(Format, argptr); +} + +NTSTATUS NetworkPingThreadStart( + _In_ PVOID Parameter + ) +{ + HANDLE icmpHandle = INVALID_HANDLE_VALUE; + ULONG icmpCurrentPingMs = 0; + ULONG icmpCurrentPingTtl = 0; + ULONG icmpReplyCount = 0; + ULONG icmpReplyLength = 0; + PVOID icmpReplyBuffer = NULL; + PPH_STRING phVersion = NULL; + PPH_BYTES icmpEchoBuffer = NULL; + IP_OPTION_INFORMATION pingOptions = + { + 255, // Time To Live + 0, // Type Of Service + IP_FLAG_DF, // IP header flags + 0 // Size of options data + }; + + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Parameter; + + __try + { + // Query PH version. + if ((phVersion = PhGetPhVersion()) == NULL) + __leave; + + // Create ICMP echo buffer. + if ((icmpEchoBuffer = FormatAnsiString("processhacker_%S_0x0D06F00D_x1", phVersion->Buffer)) == NULL) + __leave; + + if (context->IpAddress.Type == PH_IPV6_NETWORK_TYPE) + { + SOCKADDR_IN6 icmp6LocalAddr = { 0 }; + SOCKADDR_IN6 icmp6RemoteAddr = { 0 }; + PICMPV6_ECHO_REPLY icmp6ReplyStruct = NULL; + + // Create ICMPv6 handle. + if ((icmpHandle = Icmp6CreateFile()) == INVALID_HANDLE_VALUE) + __leave; + + // Set Local IPv6-ANY address. + icmp6LocalAddr.sin6_addr = in6addr_any; + icmp6LocalAddr.sin6_family = AF_INET6; + + // Set Remote IPv6 address. + icmp6RemoteAddr.sin6_addr = context->IpAddress.In6Addr; + icmp6RemoteAddr.sin6_port = _byteswap_ushort((USHORT)context->NetworkItem->RemoteEndpoint.Port); + + // Allocate ICMPv6 message. + icmpReplyLength = ICMP_BUFFER_SIZE(sizeof(ICMPV6_ECHO_REPLY), icmpEchoBuffer); + icmpReplyBuffer = PhAllocate(icmpReplyLength); + memset(icmpReplyBuffer, 0, icmpReplyLength); + + InterlockedIncrement(&context->PingSentCount); + + // Send ICMPv6 ping... + icmpReplyCount = Icmp6SendEcho2( + icmpHandle, + NULL, + NULL, + NULL, + &icmp6LocalAddr, + &icmp6RemoteAddr, + icmpEchoBuffer->Buffer, + (USHORT)icmpEchoBuffer->Length, + &pingOptions, + icmpReplyBuffer, + icmpReplyLength, + context->MaxPingTimeout + ); + + icmp6ReplyStruct = (PICMPV6_ECHO_REPLY)icmpReplyBuffer; + if (icmpReplyCount > 0 && icmp6ReplyStruct) + { + BOOLEAN icmpPacketSignature = FALSE; + + if (icmp6ReplyStruct->Status != IP_SUCCESS) + { + InterlockedIncrement(&context->PingLossCount); + } + + if (_memicmp( + icmp6ReplyStruct->Address.sin6_addr, + context->IpAddress.In6Addr.u.Word, + sizeof(icmp6ReplyStruct->Address.sin6_addr) + ) != 0) + { + InterlockedIncrement(&context->UnknownAddrCount); + } + + //if (icmp6ReplyStruct->DataSize == icmpEchoBuffer->MaximumLength) + //{ + // icmpPacketSignature = (_memicmp( + // icmpEchoBuffer->Buffer, + // icmp6ReplyStruct->Data, + // icmp6ReplyStruct->DataSize + // ) == 0); + //} + + //if (icmpPacketSignature != TRUE) + //{ + // InterlockedIncrement(&context->HashFailCount); + //} + + icmpCurrentPingMs = icmp6ReplyStruct->RoundTripTime; + //icmpCurrentPingTtl = icmp6ReplyStruct->Options.Ttl; + } + else + { + InterlockedIncrement(&context->PingLossCount); + } + } + else + { + IPAddr icmpLocalAddr = 0; + IPAddr icmpRemoteAddr = 0; + PICMP_ECHO_REPLY icmpReplyStruct = NULL; + + // Create ICMPv4 handle. + if ((icmpHandle = IcmpCreateFile()) == INVALID_HANDLE_VALUE) + __leave; + + // Set Local IPv4-ANY address. + icmpLocalAddr = in4addr_any.s_addr; + + // Set Remote IPv4 address. + icmpRemoteAddr = context->IpAddress.InAddr.s_addr; + + // Allocate ICMPv4 message. + icmpReplyLength = ICMP_BUFFER_SIZE(sizeof(ICMP_ECHO_REPLY), icmpEchoBuffer); + icmpReplyBuffer = PhAllocate(icmpReplyLength); + memset(icmpReplyBuffer, 0, icmpReplyLength); + + InterlockedIncrement(&context->PingSentCount); + + // Send ICMPv4 ping... + //if (WindowsVersion > WINDOWS_VISTA) + //{ + // // Vista SP1 and up we can specify the source address: + // icmpReplyCount = IcmpSendEcho2Ex( + // icmpHandle, + // NULL, + // NULL, + // NULL, + // icmpLocalAddr, + // icmpRemoteAddr, + // icmpEchoBuffer->Buffer, + // icmpEchoBuffer->MaximumLength, + // &pingOptions, + // icmpReplyBuffer, + // icmpReplyLength, + // context->MaxPingTimeout + // ); + //} + + icmpReplyCount = IcmpSendEcho2( + icmpHandle, + NULL, + NULL, + NULL, + icmpRemoteAddr, + icmpEchoBuffer->Buffer, + (USHORT)icmpEchoBuffer->Length, + &pingOptions, + icmpReplyBuffer, + icmpReplyLength, + context->MaxPingTimeout + ); + + icmpReplyStruct = (PICMP_ECHO_REPLY)icmpReplyBuffer; + + if (icmpReplyStruct && icmpReplyCount > 0) + { + BOOLEAN icmpPacketSignature = FALSE; + + if (icmpReplyStruct->Status != IP_SUCCESS) + { + InterlockedIncrement(&context->PingLossCount); + } + + if (icmpReplyStruct->Address != context->IpAddress.InAddr.s_addr) + { + InterlockedIncrement(&context->UnknownAddrCount); + } + + if (icmpReplyStruct->DataSize == icmpEchoBuffer->Length) + { + icmpPacketSignature = (_memicmp( + icmpEchoBuffer->Buffer, + icmpReplyStruct->Data, + icmpReplyStruct->DataSize + ) == 0); + } + + icmpCurrentPingMs = icmpReplyStruct->RoundTripTime; + icmpCurrentPingTtl = icmpReplyStruct->Options.Ttl; + + if (!icmpPacketSignature) + { + InterlockedIncrement(&context->HashFailCount); + } + } + else + { + InterlockedIncrement(&context->PingLossCount); + } + } + + InterlockedIncrement(&context->PingRecvCount); + + if (context->PingMinMs == 0 || icmpCurrentPingMs < context->PingMinMs) + context->PingMinMs = icmpCurrentPingMs; + if (icmpCurrentPingMs > context->PingMaxMs) + context->PingMaxMs = icmpCurrentPingMs; + + context->CurrentPingMs = icmpCurrentPingMs; + + PhAddItemCircularBuffer_ULONG(&context->PingHistory, icmpCurrentPingMs); + } + __finally + { + if (phVersion) + { + PhDereferenceObject(phVersion); + } + + if (icmpEchoBuffer) + { + PhDereferenceObject(icmpEchoBuffer); + } + + if (icmpHandle != INVALID_HANDLE_VALUE) + { + IcmpCloseHandle(icmpHandle); + } + + if (icmpReplyBuffer) + { + PhFree(icmpReplyBuffer); + } + } + + PostMessage(context->WindowHandle, WM_PING_UPDATE, 0, 0); + + return STATUS_SUCCESS; +} + +VOID NTAPI NetworkPingUpdateHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Context; + + // Queue up the next ping into our work queue... + PhQueueItemWorkQueue( + &context->PingWorkQueue, + NetworkPingThreadStart, + (PVOID)context + ); +} + +INT_PTR CALLBACK NetworkPingWndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PNETWORK_OUTPUT_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PNETWORK_OUTPUT_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PNETWORK_OUTPUT_CONTEXT)GetProp(hwndDlg, L"Context"); + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PH_RECTANGLE windowRectangle; + PPH_LAYOUT_ITEM panelItem; + + // We have already set the group boxes to have WS_EX_TRANSPARENT to fix + // the drawing issue that arises when using WS_CLIPCHILDREN. However + // in removing the flicker from the graphs the group boxes will now flicker. + // It's a good tradeoff since no one stares at the group boxes. + PhSetWindowStyle(hwndDlg, WS_CLIPCHILDREN, WS_CLIPCHILDREN); + + context->WindowHandle = hwndDlg; + context->ParentHandle = GetParent(hwndDlg); + context->StatusHandle = GetDlgItem(hwndDlg, IDC_MAINTEXT); + context->MaxPingTimeout = PhGetIntegerSetting(SETTING_NAME_PING_TIMEOUT); + + windowRectangle.Position = PhGetIntegerPairSetting(SETTING_NAME_PING_WINDOW_POSITION); + windowRectangle.Size = PhGetScalableIntegerPairSetting(SETTING_NAME_PING_WINDOW_SIZE, TRUE).Pair; + + // Create the font handle. + context->FontHandle = InitializeFont(context->StatusHandle); + + // Create the graph control. + context->PingGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 3, + 3, + hwndDlg, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(context->PingGraphHandle, TRUE); + + // Load the Process Hacker icon. + context->IconHandle = (HICON)LoadImage( + NtCurrentPeb()->ImageBaseAddress, + MAKEINTRESOURCE(PHAPP_IDI_PROCESSHACKER), + IMAGE_ICON, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON), + LR_SHARED + ); + // Set window icon. + if (context->IconHandle) + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)context->IconHandle); + + // Initialize the WorkQueue with a maximum of 20 threads (fix pinging slow-links with a high interval update). + PhInitializeWorkQueue(&context->PingWorkQueue, 0, 20, 5000); + PhInitializeGraphState(&context->PingGraphState); + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhInitializeCircularBuffer_ULONG(&context->PingHistory, PhGetIntegerSetting(L"SampleCount")); + + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ICMP_PANEL), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ICMP_AVG), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ICMP_MIN), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ICMP_MAX), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_PINGS_SENT), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_PINGS_LOST), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_BAD_HASH), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_ANON_ADDR), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_LEFT); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDOK), NULL, PH_ANCHOR_BOTTOM | PH_ANCHOR_RIGHT); + panelItem = PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_PING_LAYOUT), NULL, PH_ANCHOR_ALL); + PhAddLayoutItemEx(&context->LayoutManager, context->PingGraphHandle, NULL, PH_ANCHOR_ALL, panelItem->Margin); + + // Load window settings. + if (windowRectangle.Position.X == 0 || windowRectangle.Position.Y == 0) + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + else + { + PhLoadWindowPlacementFromSetting(SETTING_NAME_PING_WINDOW_POSITION, SETTING_NAME_PING_WINDOW_SIZE, hwndDlg); + } + + // Initialize window layout. + PhLayoutManagerLayout(&context->LayoutManager); + + // Convert IP Address to string format. + if (context->IpAddress.Type == PH_IPV4_NETWORK_TYPE) + { + RtlIpv4AddressToString(&context->IpAddress.InAddr, context->IpAddressString); + } + else + { + RtlIpv6AddressToString(&context->IpAddress.In6Addr, context->IpAddressString); + } + + SetWindowText(hwndDlg, PhaFormatString(L"Ping %s", context->IpAddressString)->Buffer); + SetWindowText(context->StatusHandle, PhaFormatString(L"Pinging %s with 32 bytes of data:", context->IpAddressString)->Buffer); + + PhRegisterCallback( + &PhProcessesUpdatedEvent, + NetworkPingUpdateHandler, + context, + &context->ProcessesUpdatedRegistration + ); + } + return TRUE; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + } + } + break; + case WM_DESTROY: + { + PhUnregisterCallback( + &PhProcessesUpdatedEvent, + &context->ProcessesUpdatedRegistration + ); + + PhSaveWindowPlacementToSetting( + SETTING_NAME_PING_WINDOW_POSITION, + SETTING_NAME_PING_WINDOW_SIZE, + hwndDlg + ); + + if (context->PingGraphHandle) + DestroyWindow(context->PingGraphHandle); + + if (context->IconHandle) + DestroyIcon(context->IconHandle); + + if (context->FontHandle) + DeleteObject(context->FontHandle); + + PhDeleteWorkQueue(&context->PingWorkQueue); + PhDeleteGraphState(&context->PingGraphState); + PhDeleteLayoutManager(&context->LayoutManager); + + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + + PostQuitMessage(0); + } + break; + case WM_SIZE: + PhLayoutManagerLayout(&context->LayoutManager); + break; + case WM_SIZING: + PhResizingMinimumSize((PRECT)lParam, wParam, 420, 250); + break; + case WM_CTLCOLORBTN: + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + HWND hwndChild = (HWND)lParam; + + // Check for our static label and change the color. + if (GetDlgCtrlID(hwndChild) == IDC_MAINTEXT) + { + SetTextColor(hDC, RGB(19, 112, 171)); + } + + // Set a transparent background for the control backcolor. + SetBkMode(hDC, TRANSPARENT); + + // set window background color. + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + } + break; + case WM_PING_UPDATE: + { + ULONG i = 0; + ULONG maxGraphHeight = 0; + ULONG pingAvgValue = 0; + + NetworkPingUpdateGraph(context); + + for (i = 0; i < context->PingHistory.Count; i++) + { + maxGraphHeight = maxGraphHeight + PhGetItemCircularBuffer_ULONG(&context->PingHistory, i); + pingAvgValue = maxGraphHeight / context->PingHistory.Count; + } + + SetDlgItemText(hwndDlg, IDC_ICMP_AVG, PhaFormatString( + L"Average: %lums", pingAvgValue)->Buffer); + SetDlgItemText(hwndDlg, IDC_ICMP_MIN, PhaFormatString( + L"Minimum: %lums", context->PingMinMs)->Buffer); + SetDlgItemText(hwndDlg, IDC_ICMP_MAX, PhaFormatString( + L"Maximum: %lums", context->PingMaxMs)->Buffer); + + SetDlgItemText(hwndDlg, IDC_PINGS_SENT, PhaFormatString( + L"Pings sent: %lu", context->PingSentCount)->Buffer); + SetDlgItemText(hwndDlg, IDC_PINGS_LOST, PhaFormatString( + L"Pings lost: %lu (%.0f%%)", context->PingLossCount, + ((FLOAT)context->PingLossCount / context->PingSentCount * 100) + )->Buffer); + + SetDlgItemText(hwndDlg, IDC_BAD_HASH, PhaFormatString( + L"Bad hashes: %lu", context->HashFailCount)->Buffer); + SetDlgItemText(hwndDlg, IDC_ANON_ADDR, PhaFormatString( + L"Anon replies: %lu", context->UnknownAddrCount)->Buffer); + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case GCN_GETDRAWINFO: + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + if (header->hwndFrom == context->PingGraphHandle) + { + if (PhGetIntegerSetting(L"GraphShowText")) + { + HDC hdc = Graph_GetBufferedContext(context->PingGraphHandle); + + PhMoveReference(&context->PingGraphState.Text, + PhFormatString(L"Ping: %lums", context->CurrentPingMs) + ); + + SelectObject(hdc, PhApplicationFont); + PhSetGraphText(hdc, drawInfo, &context->PingGraphState.Text->sr, + &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT); + } + else + { + drawInfo->Text.Buffer = NULL; + } + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0); + PhGraphStateGetDrawInfo(&context->PingGraphState, getDrawInfo, context->PingHistory.Count); + + if (!context->PingGraphState.Valid) + { + ULONG i; + FLOAT max = 0; + + for (i = 0; i < drawInfo->LineDataCount; i++) + { + FLOAT data1; + + context->PingGraphState.Data1[i] = data1 = (FLOAT)PhGetItemCircularBuffer_ULONG(&context->PingHistory, i); + + if (max < data1) + max = data1; + } + + // Minimum scaling of timeout (1000ms default). + if (max < (FLOAT)context->MaxPingTimeout) + max = (FLOAT)context->MaxPingTimeout; + + // Scale the data. + PhDivideSinglesBySingle( + context->PingGraphState.Data1, + max, + drawInfo->LineDataCount + ); + + context->PingGraphState.Valid = TRUE; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)lParam; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (header->hwndFrom == context->PingGraphHandle) + { + if (context->PingGraphState.TooltipIndex != getTooltipText->Index) + { + ULONG pingMs = PhGetItemCircularBuffer_ULONG(&context->PingHistory, getTooltipText->Index); + + PhMoveReference(&context->PingGraphState.TooltipText, + PhFormatString(L"Ping: %lums", pingMs) + ); + } + + getTooltipText->Text = context->PingGraphState.TooltipText->sr; + } + } + } + break; + } + } + break; + } + + return FALSE; +} + +NTSTATUS PhNetworkPingDialogThreadStart( + _In_ PVOID Parameter + ) +{ + BOOL result; + MSG message; + HWND windowHandle; + PH_AUTO_POOL autoPool; + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Parameter; + + PhInitializeAutoPool(&autoPool); + + windowHandle = CreateDialogParam( + (HINSTANCE)PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_PINGDIALOG), + PhMainWndHandle, + NetworkPingWndProc, + (LPARAM)Parameter + ); + + ShowWindow(windowHandle, SW_SHOW); + SetForegroundWindow(windowHandle); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!IsDialogMessage(windowHandle, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/plugins/NetworkTools/resource.h b/plugins/NetworkTools/resource.h new file mode 100644 index 0000000..154ba6c --- /dev/null +++ b/plugins/NetworkTools/resource.h @@ -0,0 +1,31 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by NetworkTools.rc +// +#define IDD_OUTPUT 101 +#define IDD_PINGDIALOG 102 +#define IDD_OPTIONS 103 +#define IDC_MAXTIMEOUTTEXT 1008 +#define IDC_NETOUTPUTEDIT 1009 +#define IDC_ICMP_PANEL 1011 +#define IDC_PINGS_LOST 1012 +#define IDC_ICMP_MIN 1013 +#define IDC_ICMP_MAX 1014 +#define IDC_ICMP_AVG 1015 +#define IDC_MAINTEXT 1016 +#define IDC_ANON_ADDR 1017 +#define IDC_MORE_INFO 1019 +#define IDC_PINGS_SENT 1020 +#define IDC_PING_LAYOUT 1021 +#define IDC_BAD_HASH 1022 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1022 +#define _APS_NEXT_SYMED_VALUE 104 +#endif +#endif diff --git a/plugins/NetworkTools/tracert.c b/plugins/NetworkTools/tracert.c new file mode 100644 index 0000000..334094d --- /dev/null +++ b/plugins/NetworkTools/tracert.c @@ -0,0 +1,158 @@ +/* + * Process Hacker Network Tools - + * Tracert dialog + * + * Copyright (C) 2013 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" + +NTSTATUS StdOutNetworkTracertThreadStart( + _In_ PVOID Parameter + ) +{ + NTSTATUS status; + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Parameter; + IO_STATUS_BLOCK isb; + UCHAR buffer[PAGE_SIZE]; + + while (TRUE) + { + status = NtReadFile( + context->PipeReadHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + sizeof(buffer), + NULL, + NULL + ); + + if (!NT_SUCCESS(status)) + break; + + SendMessage(context->WindowHandle, NTM_RECEIVEDTRACE, (WPARAM)isb.Information, (LPARAM)buffer); + } + + SendMessage(context->WindowHandle, NTM_RECEIVEDFINISH, 0, 0); + + return STATUS_SUCCESS; +} + +NTSTATUS NetworkTracertThreadStart( + _In_ PVOID Parameter + ) +{ + HANDLE pipeWriteHandle = INVALID_HANDLE_VALUE; + PNETWORK_OUTPUT_CONTEXT context = (PNETWORK_OUTPUT_CONTEXT)Parameter; + + if (CreatePipe(&context->PipeReadHandle, &pipeWriteHandle, NULL, 0)) + { + HANDLE threadHandle = NULL; + STARTUPINFO startupInfo = { sizeof(startupInfo) }; + OBJECT_HANDLE_FLAG_INFORMATION flagInfo; + PPH_STRING command = NULL; + + startupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startupInfo.hStdOutput = pipeWriteHandle; + startupInfo.hStdError = pipeWriteHandle; + startupInfo.wShowWindow = SW_HIDE; + + switch (context->Action) + { + case NETWORK_ACTION_TRACEROUTE: + { + if (PhGetIntegerSetting(L"EnableNetworkResolve")) + { + command = PhFormatString( + L"%s\\system32\\tracert.exe %s", + USER_SHARED_DATA->NtSystemRoot, + context->IpAddressString + ); + } + else + { + // Disable hostname lookup. + command = PhFormatString( + L"%s\\system32\\tracert.exe -d %s", + USER_SHARED_DATA->NtSystemRoot, + context->IpAddressString + ); + } + } + break; + case NETWORK_ACTION_PATHPING: + { + if (PhGetIntegerSetting(L"EnableNetworkResolve")) + { + command = PhFormatString( + L"%s\\system32\\pathping.exe %s", + USER_SHARED_DATA->NtSystemRoot, + context->IpAddressString + ); + } + else + { + // Disable hostname lookup. + command = PhFormatString( + L"%s\\system32\\pathping.exe -n %s", + USER_SHARED_DATA->NtSystemRoot, + context->IpAddressString + ); + } + } + break; + } + + // Allow the write handle to be inherited. + flagInfo.Inherit = TRUE; + flagInfo.ProtectFromClose = FALSE; + + NtSetInformationObject( + pipeWriteHandle, + ObjectHandleFlagInformation, + &flagInfo, + sizeof(OBJECT_HANDLE_FLAG_INFORMATION) + ); + + PhCreateProcessWin32Ex( + NULL, + command->Buffer, + NULL, + NULL, + &startupInfo, + PH_CREATE_PROCESS_INHERIT_HANDLES, + NULL, + NULL, + &context->ProcessHandle, + NULL + ); + + // Essential; when the process exits, the last instance of the pipe will be disconnected and our thread will exit. + NtClose(pipeWriteHandle); + + // Create a thread which will wait for output and display it. + if (threadHandle = PhCreateThread(0, StdOutNetworkTracertThreadStart, context)) + NtClose(threadHandle); + } + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/plugins/NetworkTools/whois.c b/plugins/NetworkTools/whois.c new file mode 100644 index 0000000..8c74273 --- /dev/null +++ b/plugins/NetworkTools/whois.c @@ -0,0 +1,216 @@ +/* + * Process Hacker Network Tools - + * Whois dialog + * + * Copyright (C) 2013 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "nettools.h" +#include +#include + +BOOLEAN ReadRequestString( + _In_ HINTERNET Handle, + _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data, + _Out_ ULONG *DataLength + ) +{ + PSTR data; + ULONG allocatedLength; + ULONG dataLength; + ULONG returnLength; + BYTE buffer[PAGE_SIZE]; + + allocatedLength = sizeof(buffer); + data = (PSTR)PhAllocate(allocatedLength); + dataLength = 0; + + // Zero the buffer + memset(buffer, 0, PAGE_SIZE); + + while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Copy the returned buffer into our pointer + memcpy(data + dataLength, buffer, returnLength); + // Zero the returned buffer for the next loop + //memset(buffer, 0, returnLength); + + dataLength += returnLength; + } + + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + data[dataLength] = 0; + + *DataLength = dataLength; + *Data = data; + + return TRUE; +} + +NTSTATUS NetworkWhoisThreadStart( + _In_ PVOID Parameter + ) +{ + BOOLEAN isSuccess = FALSE; + ULONG xmlLength = 0; + PSTR xmlBuffer = NULL; + PPH_STRING phVersion = NULL; + PPH_STRING userAgent = NULL; + PPH_STRING whoisHttpGetString = NULL; + HINTERNET connectionHandle = NULL; + HINTERNET requestHandle = NULL; + HINTERNET sessionHandle = NULL; + PNETWORK_OUTPUT_CONTEXT context = NULL; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; + + //4.4.3. IP Addresses and Networks + // https://www.arin.net/resources/whoisrws/whois_api.html + //TODO: use REF string from /rest/ip/ lookup for querying the IP network: "/rest/net/NET-74-125-0-0-1?showDetails=true" + // or use CIDR string from /rest/ip/ lookup for querying the IP network: "/rest/cidr/216.34.181.0/24?showDetails=true + //WinHttpAddRequestHeaders(requestHandle, L"application/arin.whoisrws-v1+xml", -1L, 0); + + __try + { + // Query thread context. + if ((context = (PNETWORK_OUTPUT_CONTEXT)Parameter) == NULL) + __leave; + + // Query PH version. + if ((phVersion = PhGetPhVersion()) == NULL) + __leave; + + // Create a user agent string. + if ((userAgent = PhConcatStrings2(L"Process Hacker ", phVersion->Buffer)) == NULL) + __leave; + + // Query the current system proxy + WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); + + // Open the HTTP session with the system proxy configuration if available + if (!(sessionHandle = WinHttpOpen( + userAgent->Buffer, + proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + proxyConfig.lpszProxy, + proxyConfig.lpszProxyBypass, + 0 + ))) + { + __leave; + } + + if (WindowsVersion >= WINDOWS_8_1) + { + // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. + ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + + WinHttpSetOption( + sessionHandle, + WINHTTP_OPTION_DECOMPRESSION, + &httpFlags, + sizeof(ULONG) + ); + } + + if (!(connectionHandle = WinHttpConnect( + sessionHandle, + L"whois.arin.net", + INTERNET_DEFAULT_HTTP_PORT, + 0 + ))) + { + __leave; + } + + if (!(whoisHttpGetString = PhFormatString(L"/rest/ip/%s", context->IpAddressString))) + __leave; + + if (!(requestHandle = WinHttpOpenRequest( + connectionHandle, + NULL, + whoisHttpGetString->Buffer, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH + ))) + { + __leave; + } + + if (!WinHttpAddRequestHeaders(requestHandle, L"Accept: text/plain", -1L, 0)) + __leave; + + if (!WinHttpSendRequest( + requestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0 + )) + { + __leave; + } + + if (!WinHttpReceiveResponse(requestHandle, NULL)) + __leave; + + if (!ReadRequestString(requestHandle, &xmlBuffer, &xmlLength)) + __leave; + + PostMessage(context->WindowHandle, NTM_RECEIVEDWHOIS, (WPARAM)xmlLength, (LPARAM)xmlBuffer); + PostMessage(context->WindowHandle, NTM_RECEIVEDFINISH, 0, 0); + + isSuccess = TRUE; + } + __finally + { + if (phVersion) + PhDereferenceObject(phVersion); + + if (userAgent) + PhDereferenceObject(userAgent); + + if (whoisHttpGetString) + PhDereferenceObject(whoisHttpGetString); + + if (requestHandle) + WinHttpCloseHandle(requestHandle); + + if (connectionHandle) + WinHttpCloseHandle(connectionHandle); + + if (sessionHandle) + WinHttpCloseHandle(sessionHandle); + } + + return STATUS_SUCCESS; +} \ No newline at end of file diff --git a/plugins/OnlineChecks/CHANGELOG.txt b/plugins/OnlineChecks/CHANGELOG.txt new file mode 100644 index 0000000..f5a252b --- /dev/null +++ b/plugins/OnlineChecks/CHANGELOG.txt @@ -0,0 +1,25 @@ +1.7 + * Fixed virusscan.jotti.org uploader + +1.6 + * Updated VirusTotal executable limit to 128 MB + +1.5 + * Added CIMA hash checking + * Added file analyzed prompt + +1.4 + * Added upload progress + * Updated UI + +1.3 + * Updated VirusTotal uploader and added hash checking + +1.2 + * Added Comodo Instant Malware Analysis + +1.1 + * Updated VirusTotal uploader + +1.0 + * Initial release diff --git a/plugins/OnlineChecks/OnlineChecks.rc b/plugins/OnlineChecks/OnlineChecks.rc new file mode 100644 index 0000000..4e32b9c --- /dev/null +++ b/plugins/OnlineChecks/OnlineChecks.rc @@ -0,0 +1,147 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,7,0,0 + PRODUCTVERSION 1,7,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "dmex" + VALUE "FileDescription", "Online Checks plugin for Process Hacker" + VALUE "FileVersion", "1.7" + VALUE "InternalName", "OnlineChecks" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "OnlineChecks.dll" + VALUE "ProductName", "Online Checks plugin for Process Hacker" + VALUE "ProductVersion", "1.7" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROGRESS DIALOGEX 0, 0, 241, 58 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Uploading To" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Message",IDC_MESSAGE,7,5,227,12 + CONTROL "",IDC_PROGRESS1,"msctls_progress32",0x0,7,23,227,9 + LTEXT "Connecting...",IDC_STATUS,7,39,172,11 + PUSHBUTTON "Yes",IDYES,133,37,50,14,NOT WS_VISIBLE + DEFPUSHBUTTON "Cancel",IDNO,185,37,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PROGRESS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 234 + TOPMARGIN, 5 + BOTTOMMARGIN, 51 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_PROGRESS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/OnlineChecks/OnlineChecks.vcxproj b/plugins/OnlineChecks/OnlineChecks.vcxproj new file mode 100644 index 0000000..c941b50 --- /dev/null +++ b/plugins/OnlineChecks/OnlineChecks.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {79D24223-1122-40A9-BC8F-46A2089FE089} + OnlineChecks + Win32Proj + OnlineChecks + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/OnlineChecks/OnlineChecks.vcxproj.filters b/plugins/OnlineChecks/OnlineChecks.vcxproj.filters new file mode 100644 index 0000000..58acfac --- /dev/null +++ b/plugins/OnlineChecks/OnlineChecks.vcxproj.filters @@ -0,0 +1,137 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {ba7a7cdc-f79a-40a8-8452-8ddee51948c8} + + + {dbc03cac-8d30-4163-b768-f9485dc26673} + + + + + Source Files + + + Source Files + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files\json-c + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + Header Files\json-c + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/plugins/OnlineChecks/json-c/AUTHORS b/plugins/OnlineChecks/json-c/AUTHORS new file mode 100644 index 0000000..b389989 --- /dev/null +++ b/plugins/OnlineChecks/json-c/AUTHORS @@ -0,0 +1,5 @@ +Michael Clark +Jehiah Czebotar +Eric Haszlakiewicz +C. Watford (christopher.watford@gmail.com) + diff --git a/plugins/OnlineChecks/json-c/COPYING b/plugins/OnlineChecks/json-c/COPYING new file mode 100644 index 0000000..740d125 --- /dev/null +++ b/plugins/OnlineChecks/json-c/COPYING @@ -0,0 +1,42 @@ + +Copyright (c) 2009-2012 Eric Haszlakiewicz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------------------------------------------------------- + +Copyright (c) 2004, 2005 Metaparadigm Pte Ltd + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/OnlineChecks/json-c/ChangeLog b/plugins/OnlineChecks/json-c/ChangeLog new file mode 100644 index 0000000..451b8f6 --- /dev/null +++ b/plugins/OnlineChecks/json-c/ChangeLog @@ -0,0 +1,214 @@ + +0.12 + + * Address security issues: + * CVE-2013-6371: hash collision denial of service + * CVE-2013-6370: buffer overflow if size_t is larger than int + + * Avoid potential overflow in json_object_get_double + + * Eliminate the mc_abort() function and MC_ABORT macro. + + * Make the json_tokener_errors array local. It has been deprecated for + a while, and json_tokener_error_desc() should be used instead. + + * change the floating point output format to %.17g so values with + more than 6 digits show up in the output. + + * Remove the old libjson.so name compatibility support. The library is + only created as libjson-c.so now and headers are only installed + into the ${prefix}/json-c directory. + + * When supported by the linker, add the -Bsymbolic-functions flag. + + * Various changes to fix the build on MSVC. + + * Make strict mode more strict: + * number must not start with 0 + * no single-quote strings + * no comments + * trailing char not allowed + * only allow lowercase literals + + * Added a json_object_new_double_s() convenience function to allow + an exact string representation of a double to be specified when + creating the object and use it in json_tokener_parse_ex() so + a re-serialized object more exactly matches the input. + + * Add support NaN and Infinity + + +0.11 + + * IMPORTANT: the name of the library has changed to libjson-c.so and + the header files are now in include/json-c. + The pkgconfig name has also changed from json to json-c. + You should change your build to use appropriate -I and -l options. + A compatibility shim is in place so builds using the old name will + continue to work, but that will be removed in the next release. + * Maximum recursion depth is now a runtime option. + json_tokener_new() is provided for compatibility. + json_tokener_new_ex(depth) + * Include json_object_iterator.h in the installed headers. + * Add support for building on Android. + * Rewrite json_object_object_add to replace just the value if the key already exists so keys remain valid. + * Make it safe to delete keys while iterating with the json_object_object_foreach macro. + * Add a json_set_serializer() function to allow the string output of a json_object to be customized. + * Make float parsing locale independent. + * Add a json_tokener_set_flags() function and a JSON_TOKENER_STRICT flag. + * Enable -Werror when building. + * speed improvements to parsing 64-bit integers on systems with working sscanf + * Add a json_object_object_length function. + * Fix a bug (buffer overrun) when expanding arrays to more than 64 entries. + +0.10 + + * Add a json_object_to_json_string_ext() function to allow output to be + formatted in a more human readable form. + * Add json_object_object_get_ex(), a NULL-safe get object method, to be able + to distinguish between a key not present and the value being NULL. + * Add an alternative iterator implementation, see json_object_iterator.h + * Make json_object_iter public to enable external use of the + json_object_object_foreachC macro. + * Add a printbuf_memset() function to provide an effecient way to set and + append things like whitespace indentation. + * Adjust json_object_is_type and json_object_get_type so they return + json_type_null for NULL objects and handle NULL passed to + json_objct_object_get(). + * Rename boolean type to json_bool. + * Fix various compile issues for Visual Studio and MinGW. + * Allow json_tokener_parse_ex() to be re-used to parse multiple object. + Also, fix some parsing issues with capitalized hexadecimal numbers and + number in E notation. + * Add json_tokener_get_error() and json_tokener_error_desc() to better + encapsulate the process of retrieving errors while parsing. + * Various improvements to the documentation of many functions. + * Add new json_object_array_sort() function. + * Fix a bug in json_object_get_int(), which would incorrectly return 0 + when called on a string type object. + Eric Haszlakiewicz + * Add a json_type_to_name() function. + Eric Haszlakiewicz + * Add a json_tokener_parse_verbose() function. + Jehiah Czebotar + * Improve support for null bytes within JSON strings. + Jehiah Czebotar + * Fix file descriptor leak if memory allocation fails in json_util + Zachary Blair, zack_blair at hotmail dot com + * Add int64 support. Two new functions json_object_net_int64 and + json_object_get_int64. Binary compatibility preserved. + Eric Haszlakiewicz, EHASZLA at transunion com + Rui Miguel Silva Seabra, rms at 1407 dot org + * Fix subtle bug in linkhash where lookup could hang after all slots + were filled then successively freed. + Spotted by Jean-Marc Naud, j dash m at newtraxtech dot com + * Make json_object_from_file take const char *filename + Spotted by Vikram Raj V, vsagar at attinteractive dot com + * Add handling of surrogate pairs (json_tokener.c, test4.c, Makefile.am) + Brent Miller, bdmiller at yahoo dash inc dot com + * Correction to comment describing printbuf_memappend in printbuf.h + Brent Miller, bdmiller at yahoo dash inc dot com + +0.9 + * Add README.html README-WIN32.html config.h.win32 to Makefile.am + Michael Clark, + * Add const qualifier to the json_tokener_parse functions + Eric Haszlakiewicz, EHASZLA at transunion dot com + * Rename min and max so we can never clash with C or C++ std library + Ian Atha, thatha at yahoo dash inc dot com + * Fix any noticeable spelling or grammar errors. + * Make sure every va_start has a va_end. + * Check all pointers for validity. + Erik Hovland, erik at hovland dot org + * Fix json_object_get_boolean to return false for empty string + Spotted by Vitaly Kruglikov, Vitaly dot Kruglikov at palm dot com + * optimizations to json_tokener_parse_ex(), printbuf_memappend() + Brent Miller, bdmiller at yahoo dash inc dot com + * Disable REFCOUNT_DEBUG by default in json_object.c + * Don't use this as a variable, so we can compile with a C++ compiler + * Add casts from void* to type of assignment when using malloc + * Add #ifdef __cplusplus guards to all of the headers + * Add typedefs for json_object, json_tokener, array_list, printbuf, lh_table + Michael Clark, + * Null pointer dereference fix. Fix json_object_get_boolean strlen test + to not return TRUE for zero length string. Remove redundant includes. + Erik Hovland, erik at hovland dot org + * Fixed warning reported by adding -Wstrict-prototypes + -Wold-style-definition to the compilatin flags. + Dotan Barak, dotanba at gmail dot com + * Add const correctness to public interfaces + Gerard Krol, g dot c dot krol at student dot tudelft dot nl + +0.8 + * Add va_end for every va_start + Dotan Barak, dotanba at gmail dot com + * Add macros to enable compiling out debug code + Geoffrey Young, geoff at modperlcookbook dot org + * Fix bug with use of capital E in numbers with exponents + Mateusz Loskot, mateusz at loskot dot net + * Add stddef.h include + * Patch allows for json-c compile with -Werror and not fail due to + -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations + Geoffrey Young, geoff at modperlcookbook dot org + +0.7 + * Add escaping of backslash to json output + * Add escaping of foward slash on tokenizing and output + * Changes to internal tokenizer from using recursion to + using a depth state structure to allow incremental parsing + +0.6 + * Fix bug in escaping of control characters + Johan Björklund, johbjo09 at kth dot se + * Remove include "config.h" from headers (should only + be included from .c files) + Michael Clark + +0.5 + * Make headers C++ compatible by change *this to *obj + * Add ifdef C++ extern "C" to headers + * Use simpler definition of min and max in bits.h + Larry Lansing, llansing at fuzzynerd dot com + + * Remove automake 1.6 requirement + * Move autogen commands into autogen.sh. Update README + * Remove error pointer special case for Windows + * Change license from LGPL to MIT + Michael Clark + +0.4 + * Fix additional error case in object parsing + * Add back sign reversal in nested object parse as error pointer + value is negative, while error value is positive. + Michael Clark + +0.3 + * fix pointer arithmetic bug for error pointer check in is_error() macro + * fix type passed to printbuf_memappend in json_tokener + * update autotools bootstrap instructions in README + Michael Clark + +0.2 + * printbuf.c - C. Watford (christopher.watford@gmail.com) + Added a Win32/Win64 compliant implementation of vasprintf + * debug.c - C. Watford (christopher.watford@gmail.com) + Removed usage of vsyslog on Win32/Win64 systems, needs to be handled + by a configure script + * json_object.c - C. Watford (christopher.watford@gmail.com) + Added scope operator to wrap usage of json_object_object_foreach, this + needs to be rethought to be more ANSI C friendly + * json_object.h - C. Watford (christopher.watford@gmail.com) + Added Microsoft C friendly version of json_object_object_foreach + * json_tokener.c - C. Watford (christopher.watford@gmail.com) + Added a Win32/Win64 compliant implementation of strndup + * json_util.c - C. Watford (christopher.watford@gmail.com) + Added cast and mask to suffice size_t v. unsigned int conversion + correctness + * json_tokener.c - sign reversal issue on error info for nested object parse + spotted by Johan Björklund (johbjo09 at kth.se) + * json_object.c - escape " in json_escape_str + * Change to automake and libtool to build shared and static library + Michael Clark + +0.1 + * initial release diff --git a/plugins/OnlineChecks/json-c/arraylist.c b/plugins/OnlineChecks/json-c/arraylist.c new file mode 100644 index 0000000..97f2c92 --- /dev/null +++ b/plugins/OnlineChecks/json-c/arraylist.c @@ -0,0 +1,101 @@ +/* + * $Id: arraylist.c,v 1.4 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include "config.h" + +#ifdef STDC_HEADERS +# include +# include +#endif /* STDC_HEADERS */ + +#if defined(HAVE_STRINGS_H) && !defined(_STRING_H) && !defined(__USE_BSD) +# include +#endif /* HAVE_STRINGS_H */ + +#include "bits.h" +#include "arraylist.h" + +struct array_list* +array_list_new(array_list_free_fn *free_fn) +{ + struct array_list *arr; + + arr = (struct array_list*)calloc(1, sizeof(struct array_list)); + if(!arr) return NULL; + arr->size = ARRAY_LIST_DEFAULT_SIZE; + arr->length = 0; + arr->free_fn = free_fn; + if(!(arr->array = (void**)calloc(sizeof(void*), arr->size))) { + free(arr); + return NULL; + } + return arr; +} + +extern void +array_list_free(struct array_list *arr) +{ + int i; + for(i = 0; i < arr->length; i++) + if(arr->array[i]) arr->free_fn(arr->array[i]); + free(arr->array); + free(arr); +} + +void* +array_list_get_idx(struct array_list *arr, int i) +{ + if(i >= arr->length) return NULL; + return arr->array[i]; +} + +static int array_list_expand_internal(struct array_list *arr, int max) +{ + void *t; + int new_size; + + if(max < arr->size) return 0; + new_size = json_max(arr->size << 1, max); + if(!(t = realloc(arr->array, new_size*sizeof(void*)))) return -1; + arr->array = (void**)t; + (void)memset(arr->array + arr->size, 0, (new_size-arr->size)*sizeof(void*)); + arr->size = new_size; + return 0; +} + +int +array_list_put_idx(struct array_list *arr, int idx, void *data) +{ + if(array_list_expand_internal(arr, idx+1)) return -1; + if(arr->array[idx]) arr->free_fn(arr->array[idx]); + arr->array[idx] = data; + if(arr->length <= idx) arr->length = idx + 1; + return 0; +} + +int +array_list_add(struct array_list *arr, void *data) +{ + return array_list_put_idx(arr, arr->length, data); +} + +void +array_list_sort(struct array_list *arr, int(__cdecl* sort_fn)(const void *, const void *)) +{ + qsort(arr->array, arr->length, sizeof(arr->array[0]), + (int (__cdecl*)(const void *, const void *))sort_fn); +} + +int +array_list_length(struct array_list *arr) +{ + return arr->length; +} diff --git a/plugins/OnlineChecks/json-c/arraylist.h b/plugins/OnlineChecks/json-c/arraylist.h new file mode 100644 index 0000000..089be0b --- /dev/null +++ b/plugins/OnlineChecks/json-c/arraylist.h @@ -0,0 +1,56 @@ +/* + * $Id: arraylist.h,v 1.4 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _arraylist_h_ +#define _arraylist_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARRAY_LIST_DEFAULT_SIZE 32 + +typedef void (array_list_free_fn) (void *data); + +struct array_list +{ + void **array; + int length; + int size; + array_list_free_fn *free_fn; +}; + +extern struct array_list* +array_list_new(array_list_free_fn *free_fn); + +extern void +array_list_free(struct array_list *al); + +extern void* +array_list_get_idx(struct array_list *al, int i); + +extern int +array_list_put_idx(struct array_list *al, int i, void *data); + +extern int +array_list_add(struct array_list *al, void *data); + +extern int +array_list_length(struct array_list *al); + +extern void +array_list_sort(struct array_list *arr, int(__cdecl* compar)(const void *, const void *)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/bits.h b/plugins/OnlineChecks/json-c/bits.h new file mode 100644 index 0000000..c8cbbc8 --- /dev/null +++ b/plugins/OnlineChecks/json-c/bits.h @@ -0,0 +1,28 @@ +/* + * $Id: bits.h,v 1.10 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _bits_h_ +#define _bits_h_ + +#ifndef json_min +#define json_min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef json_max +#define json_max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define hexdigit(x) (((x) <= '9') ? (x) - '0' : ((x) & 7) + 9) +#define error_ptr(error) ((void*)error) +#define error_description(error) (json_tokener_errors[error]) +#define is_error(ptr) (ptr == NULL) + +#endif diff --git a/plugins/OnlineChecks/json-c/config.h b/plugins/OnlineChecks/json-c/config.h new file mode 100644 index 0000000..df46f0a --- /dev/null +++ b/plugins/OnlineChecks/json-c/config.h @@ -0,0 +1,89 @@ +#define PACKAGE_STRING "JSON C Library 0.12-20140410" +#define PACKAGE_BUGREPORT "json-c@googlegroups.com" +#define PACKAGE_NAME "JSON C Library" +#define PACKAGE_TARNAME "json-c" +#define PACKAGE_VERSION "0.12-20140410" + + +#define HAVE_SETLOCALE 1 +#define HAVE_LOCALE_H 1 + +#define HAVE_DECL_NAN 1 +#define HAVE_DECL_INFINITY 1 +//#define HAVE_DECL__ISNAN 1 +//#define HAVE_DECL__FINITE 1 + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +/* #undef HAVE_DOPRNT */ + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#define HAVE_MALLOC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `open' function. */ +#define HAVE_OPEN 1 + +/* Define to 1 if your system has a GNU libc compatible `realloc' function, + and to 0 otherwise. */ +#define HAVE_REALLOC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRNDUP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the `strerror' function. */ +#define HAVE_STRERROR 1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vprintf' function. */ +#undef HAVE_VPRINTF + +/* Define to 1 if you have the `vsyslog' function. */ +#undef HAVE_VSYSLOG + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + diff --git a/plugins/OnlineChecks/json-c/config.h.in b/plugins/OnlineChecks/json-c/config.h.in new file mode 100644 index 0000000..0dcab1a --- /dev/null +++ b/plugins/OnlineChecks/json-c/config.h.in @@ -0,0 +1,174 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Enable RDRANR Hardware RNG Hash Seed */ +#undef ENABLE_RDRAND + +/* Define if .gnu.warning accepts long strings. */ +#undef HAS_GNU_WARNING_LONG + +/* Define to 1 if you have the declaration of `INFINITY', and to 0 if you + don't. */ +#undef HAVE_DECL_INFINITY + +/* Define to 1 if you have the declaration of `isinf', and to 0 if you don't. + */ +#undef HAVE_DECL_ISINF + +/* Define to 1 if you have the declaration of `isnan', and to 0 if you don't. + */ +#undef HAVE_DECL_ISNAN + +/* Define to 1 if you have the declaration of `nan', and to 0 if you don't. */ +#undef HAVE_DECL_NAN + +/* Define to 1 if you have the declaration of `_finite', and to 0 if you + don't. */ +#undef HAVE_DECL__FINITE + +/* Define to 1 if you have the declaration of `_isnan', and to 0 if you don't. + */ +#undef HAVE_DECL__ISNAN + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +#undef HAVE_DOPRNT + +/* Define to 1 if you have the header file. */ +#undef HAVE_ENDIAN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#undef HAVE_MALLOC + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `open' function. */ +#undef HAVE_OPEN + +/* Define to 1 if your system has a GNU libc compatible `realloc' function, + and to 0 otherwise. */ +#undef HAVE_REALLOC + +/* Define to 1 if you have the `setlocale' function. */ +#undef HAVE_SETLOCALE + +/* Define to 1 if you have the `snprintf' function. */ +#undef HAVE_SNPRINTF + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strcasecmp' function. */ +#undef HAVE_STRCASECMP + +/* Define to 1 if you have the `strdup' function. */ +#undef HAVE_STRDUP + +/* Define to 1 if you have the `strerror' function. */ +#undef HAVE_STRERROR + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strncasecmp' function. */ +#undef HAVE_STRNCASECMP + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_CDEFS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vasprintf' function. */ +#undef HAVE_VASPRINTF + +/* Define to 1 if you have the `vprintf' function. */ +#undef HAVE_VPRINTF + +/* Define to 1 if you have the `vsnprintf' function. */ +#undef HAVE_VSNPRINTF + +/* Define to 1 if you have the `vsyslog' function. */ +#undef HAVE_VSYSLOG + +/* Public define for json_inttypes.h */ +#undef JSON_C_HAVE_INTTYPES_H + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to rpl_malloc if the replacement function should be used. */ +#undef malloc + +/* Define to rpl_realloc if the replacement function should be used. */ +#undef realloc + +/* Define to `unsigned int' if does not define. */ +#undef size_t diff --git a/plugins/OnlineChecks/json-c/debug.c b/plugins/OnlineChecks/json-c/debug.c new file mode 100644 index 0000000..9dff781 --- /dev/null +++ b/plugins/OnlineChecks/json-c/debug.c @@ -0,0 +1,83 @@ +/* + * $Id: debug.c,v 1.5 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include "config.h" + +#include +#include +#include +#include + +#if HAVE_SYSLOG_H +# include +#endif /* HAVE_SYSLOG_H */ + +#if HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ + +#if HAVE_SYS_PARAM_H +#include +#endif /* HAVE_SYS_PARAM_H */ + +#include "debug.h" + +static int _syslog = 0; +static int _debug = 0; + +void mc_set_debug(int debug) { _debug = debug; } +int mc_get_debug(void) { return _debug; } + +extern void mc_set_syslog(int syslog) +{ + _syslog = syslog; +} + +void mc_debug(const char *msg, ...) +{ + va_list ap; + if(_debug) { + va_start(ap, msg); +#if HAVE_VSYSLOG + if(_syslog) { + vsyslog(LOG_DEBUG, msg, ap); + } else +#endif + vprintf(msg, ap); + va_end(ap); + } +} + +void mc_error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); +#if HAVE_VSYSLOG + if(_syslog) { + vsyslog(LOG_ERR, msg, ap); + } else +#endif + vfprintf(stderr, msg, ap); + va_end(ap); +} + +void mc_info(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); +#if HAVE_VSYSLOG + if(_syslog) { + vsyslog(LOG_INFO, msg, ap); + } else +#endif + vfprintf(stderr, msg, ap); + va_end(ap); +} diff --git a/plugins/OnlineChecks/json-c/debug.h b/plugins/OnlineChecks/json-c/debug.h new file mode 100644 index 0000000..80ca3e4 --- /dev/null +++ b/plugins/OnlineChecks/json-c/debug.h @@ -0,0 +1,71 @@ +/* + * $Id: debug.h,v 1.5 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern void mc_set_debug(int debug); +extern int mc_get_debug(void); + +extern void mc_set_syslog(int syslog); + +extern void mc_debug(const char *msg, ...); +extern void mc_error(const char *msg, ...); +extern void mc_info(const char *msg, ...); + +#ifndef __STRING +#define __STRING(x) #x +#endif + +#ifndef PARSER_BROKEN_FIXED + +#define JASSERT(cond) do {} while(0) + +#else + +#define JASSERT(cond) do { \ + if (!(cond)) { \ + mc_error("cjson assert failure %s:%d : cond \"" __STRING(cond) "failed\n", __FILE__, __LINE__); \ + *(int *)0 = 1;\ + abort(); \ + }\ + } while(0) + +#endif + +#define MC_ERROR(x, ...) mc_error(x, ##__VA_ARGS__) + +#ifdef MC_MAINTAINER_MODE +#define MC_SET_DEBUG(x) mc_set_debug(x) +#define MC_GET_DEBUG() mc_get_debug() +#define MC_SET_SYSLOG(x) mc_set_syslog(x) +#define MC_DEBUG(x, ...) mc_debug(x, ##__VA_ARGS__) +#define MC_INFO(x, ...) mc_info(x, ##__VA_ARGS__) +#else +#define MC_SET_DEBUG(x) if (0) mc_set_debug(x) +#define MC_GET_DEBUG() (0) +#define MC_SET_SYSLOG(x) if (0) mc_set_syslog(x) +#define MC_DEBUG(x, ...) if (0) mc_debug(x, ##__VA_ARGS__) +#define MC_INFO(x, ...) if (0) mc_info(x, ##__VA_ARGS__) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json.h b/plugins/OnlineChecks/json-c/json.h new file mode 100644 index 0000000..4339b20 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json.h @@ -0,0 +1,34 @@ +/* + * $Id: json.h,v 1.6 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _json_h_ +#define _json_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bits.h" +#include "debug.h" +#include "linkhash.h" +#include "arraylist.h" +#include "json_util.h" +#include "json_object.h" +#include "json_tokener.h" +#include "json_object_iterator.h" +#include "json_c_version.h" + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json_c_version.c b/plugins/OnlineChecks/json-c/json_c_version.c new file mode 100644 index 0000000..13eb188 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_c_version.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Eric Haszlakiewicz + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + */ +#include "config.h" + +#include "json_c_version.h" + +const char *json_c_version(void) +{ + return JSON_C_VERSION; +} + +int json_c_version_num(void) +{ + return JSON_C_VERSION_NUM; +} + diff --git a/plugins/OnlineChecks/json-c/json_c_version.h b/plugins/OnlineChecks/json-c/json_c_version.h new file mode 100644 index 0000000..eed98a4 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_c_version.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Eric Haszlakiewicz + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + */ + +#ifndef _json_c_version_h_ +#define _json_c_version_h_ + +#define JSON_C_MAJOR_VERSION 0 +#define JSON_C_MINOR_VERSION 12 +#define JSON_C_MICRO_VERSION 0 +#define JSON_C_VERSION_NUM ((JSON_C_MAJOR_VERSION << 16) | \ + (JSON_C_MINOR_VERSION << 8) | \ + JSON_C_MICRO_VERSION) +#define JSON_C_VERSION "0.12" + +const char *json_c_version(void); /* Returns JSON_C_VERSION */ +int json_c_version_num(void); /* Returns JSON_C_VERSION_NUM */ + +#endif diff --git a/plugins/OnlineChecks/json-c/json_config.h b/plugins/OnlineChecks/json-c/json_config.h new file mode 100644 index 0000000..405fda2 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_config.h @@ -0,0 +1,3 @@ + +/* Define to 1 if you have the header file. */ +#define JSON_C_HAVE_INTTYPES_H 1 diff --git a/plugins/OnlineChecks/json-c/json_inttypes.h b/plugins/OnlineChecks/json-c/json_inttypes.h new file mode 100644 index 0000000..9de8d24 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_inttypes.h @@ -0,0 +1,28 @@ + +#ifndef _json_inttypes_h_ +#define _json_inttypes_h_ + +#include "json_config.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1700 + +/* Anything less than Visual Studio C++ 10 is missing stdint.h and inttypes.h */ +typedef __int32 int32_t; +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX ((int32_t)_I32_MAX) +typedef __int64 int64_t; +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX ((int64_t)_I64_MAX) +#define PRId64 "I64d" +#define SCNd64 "I64d" + +#else + +#ifdef JSON_C_HAVE_INTTYPES_H +#include +#endif +/* inttypes.h includes stdint.h */ + +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json_object.c b/plugins/OnlineChecks/json-c/json_object.c new file mode 100644 index 0000000..57f3f0d --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_object.c @@ -0,0 +1,860 @@ +/* + * $Id: json_object.c,v 1.17 2006/07/25 03:24:50 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "printbuf.h" +#include "linkhash.h" +#include "arraylist.h" +#include "json_inttypes.h" +#include "json_object.h" +#include "json_object_private.h" +#include "json_util.h" +#include "math_compat.h" + +#if !defined(HAVE_STRDUP) && defined(_MSC_VER) + /* MSC has the version as _strdup */ +# define strdup _strdup +#elif !defined(HAVE_STRDUP) +# error You do not have strdup on your system. +#endif /* HAVE_STRDUP */ + +#if !defined(HAVE_SNPRINTF) && defined(_MSC_VER) + /* MSC has the version as _snprintf */ +# define snprintf _snprintf +#elif !defined(HAVE_SNPRINTF) +# error You do not have snprintf on your system. +#endif /* HAVE_SNPRINTF */ + +// Don't define this. It's not thread-safe. +/* #define REFCOUNT_DEBUG 1 */ + +const char *json_number_chars = "0123456789.+-eE"; +const char *json_hex_chars = "0123456789abcdefABCDEF"; + +static void json_object_generic_delete(struct json_object* jso); +static struct json_object* json_object_new(enum json_type o_type); + +static json_object_to_json_string_fn json_object_object_to_json_string; +static json_object_to_json_string_fn json_object_boolean_to_json_string; +static json_object_to_json_string_fn json_object_int_to_json_string; +static json_object_to_json_string_fn json_object_double_to_json_string; +static json_object_to_json_string_fn json_object_string_to_json_string; +static json_object_to_json_string_fn json_object_array_to_json_string; + + +/* ref count debugging */ + +#ifdef REFCOUNT_DEBUG + +static struct lh_table *json_object_table; + +static void json_object_init(void) __attribute__ ((constructor)); +static void json_object_init(void) { + MC_DEBUG("json_object_init: creating object table\n"); + json_object_table = lh_kptr_table_new(128, "json_object_table", NULL); +} + +static void json_object_fini(void) __attribute__ ((destructor)); +static void json_object_fini(void) { + struct lh_entry *ent; + if(MC_GET_DEBUG()) { + if (json_object_table->count) { + MC_DEBUG("json_object_fini: %d referenced objects at exit\n", + json_object_table->count); + lh_foreach(json_object_table, ent) { + struct json_object* obj = (struct json_object*)ent->v; + MC_DEBUG("\t%s:%p\n", json_type_to_name(obj->o_type), obj); + } + } + } + MC_DEBUG("json_object_fini: freeing object table\n"); + lh_table_free(json_object_table); +} +#endif /* REFCOUNT_DEBUG */ + + +/* string escaping */ + +static int json_escape_str(struct printbuf *pb, char *str, size_t len) +{ + int pos = 0, start_offset = 0; + unsigned char c; + while (len--) { + c = str[pos]; + switch(c) { + case '\b': + case '\n': + case '\r': + case '\t': + case '\f': + case '"': + case '\\': + case '/': + if(pos - start_offset > 0) + printbuf_memappend(pb, str + start_offset, pos - start_offset); + if(c == '\b') printbuf_memappend(pb, "\\b", 2); + else if(c == '\n') printbuf_memappend(pb, "\\n", 2); + else if(c == '\r') printbuf_memappend(pb, "\\r", 2); + else if(c == '\t') printbuf_memappend(pb, "\\t", 2); + else if(c == '\f') printbuf_memappend(pb, "\\f", 2); + else if(c == '"') printbuf_memappend(pb, "\\\"", 2); + else if(c == '\\') printbuf_memappend(pb, "\\\\", 2); + else if(c == '/') printbuf_memappend(pb, "\\/", 2); + start_offset = ++pos; + break; + default: + if(c < ' ') { + if(pos - start_offset > 0) + printbuf_memappend(pb, str + start_offset, pos - start_offset); + sprintbuf(pb, "\\u00%c%c", + json_hex_chars[c >> 4], + json_hex_chars[c & 0xf]); + start_offset = ++pos; + } else pos++; + } + } + if(pos - start_offset > 0) + printbuf_memappend(pb, str + start_offset, pos - start_offset); + return 0; +} + + +/* reference counting */ + +extern struct json_object* json_object_get(struct json_object *jso) +{ + if(jso) { + jso->_ref_count++; + } + return jso; +} + +int json_object_put(struct json_object *jso) +{ + if(jso) + { + jso->_ref_count--; + if(!jso->_ref_count) + { + if (jso->_user_delete) + jso->_user_delete(jso, jso->_userdata); + jso->_delete(jso); + return 1; + } + } + return 0; +} + + +/* generic object construction and destruction parts */ + +static void json_object_generic_delete(struct json_object* jso) +{ +#ifdef REFCOUNT_DEBUG + MC_DEBUG("json_object_delete_%s: %p\n", + json_type_to_name(jso->o_type), jso); + lh_table_delete(json_object_table, jso); +#endif /* REFCOUNT_DEBUG */ + printbuf_free(jso->_pb); + free(jso); +} + +static struct json_object* json_object_new(enum json_type o_type) +{ + struct json_object *jso; + + jso = (struct json_object*)calloc(sizeof(struct json_object), 1); + if(!jso) return NULL; + jso->o_type = o_type; + jso->_ref_count = 1; + jso->_delete = &json_object_generic_delete; +#ifdef REFCOUNT_DEBUG + lh_table_insert(json_object_table, jso, jso); + MC_DEBUG("json_object_new_%s: %p\n", json_type_to_name(jso->o_type), jso); +#endif /* REFCOUNT_DEBUG */ + return jso; +} + + +/* type checking functions */ + +int json_object_is_type(struct json_object *jso, enum json_type type) +{ + if (!jso) + return (type == json_type_null); + return (jso->o_type == type); +} + +enum json_type json_object_get_type(struct json_object *jso) +{ + if (!jso) + return json_type_null; + return jso->o_type; +} + +/* set a custom conversion to string */ + +void json_object_set_serializer(json_object *jso, + json_object_to_json_string_fn to_string_func, + void *userdata, + json_object_delete_fn *user_delete) +{ + // First, clean up any previously existing user info + if (jso->_user_delete) + { + jso->_user_delete(jso, jso->_userdata); + } + jso->_userdata = NULL; + jso->_user_delete = NULL; + + if (to_string_func == NULL) + { + // Reset to the standard serialization function + switch(jso->o_type) + { + case json_type_null: + jso->_to_json_string = NULL; + break; + case json_type_boolean: + jso->_to_json_string = &json_object_boolean_to_json_string; + break; + case json_type_double: + jso->_to_json_string = &json_object_double_to_json_string; + break; + case json_type_int: + jso->_to_json_string = &json_object_int_to_json_string; + break; + case json_type_object: + jso->_to_json_string = &json_object_object_to_json_string; + break; + case json_type_array: + jso->_to_json_string = &json_object_array_to_json_string; + break; + case json_type_string: + jso->_to_json_string = &json_object_string_to_json_string; + break; + } + return; + } + + jso->_to_json_string = to_string_func; + jso->_userdata = userdata; + jso->_user_delete = user_delete; +} + + +/* extended conversion to string */ + +char* json_object_to_json_string_ext(struct json_object *jso, int flags) +{ + if (!jso) + return "null"; + + if ((!jso->_pb) && !(jso->_pb = printbuf_new())) + return NULL; + + printbuf_reset(jso->_pb); + + if(jso->_to_json_string(jso, jso->_pb, 0, flags) < 0) + return NULL; + + return jso->_pb->buf; +} + +/* backwards-compatible conversion to string */ + +char* json_object_to_json_string(struct json_object *jso) +{ + return json_object_to_json_string_ext(jso, JSON_C_TO_STRING_SPACED); +} + +static void indent(struct printbuf *pb, int level, int flags) +{ + if (flags & JSON_C_TO_STRING_PRETTY) + { + printbuf_memset(pb, -1, ' ', level * 2); + } +} + +/* json_object_object */ + +static int json_object_object_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + int had_children = 0; + struct json_object_iter iter; + + sprintbuf(pb, "{" /*}*/); + if (flags & JSON_C_TO_STRING_PRETTY) + sprintbuf(pb, "\n"); + json_object_object_foreachC(jso, iter) + { + if (had_children) + { + sprintbuf(pb, ","); + if (flags & JSON_C_TO_STRING_PRETTY) + sprintbuf(pb, "\n"); + } + had_children = 1; + if (flags & JSON_C_TO_STRING_SPACED) + sprintbuf(pb, " "); + indent(pb, level+1, flags); + sprintbuf(pb, "\""); + json_escape_str(pb, iter.key, strlen(iter.key)); + if (flags & JSON_C_TO_STRING_SPACED) + sprintbuf(pb, "\": "); + else + sprintbuf(pb, "\":"); + if(iter.val == NULL) + sprintbuf(pb, "null"); + else + iter.val->_to_json_string(iter.val, pb, level+1,flags); + } + if (flags & JSON_C_TO_STRING_PRETTY) + { + if (had_children) + sprintbuf(pb, "\n"); + indent(pb,level,flags); + } + if (flags & JSON_C_TO_STRING_SPACED) + return sprintbuf(pb, /*{*/ " }"); + else + return sprintbuf(pb, /*{*/ "}"); +} + + +static void json_object_lh_entry_free(struct lh_entry *ent) +{ + free(ent->k); + json_object_put((struct json_object*)ent->v); +} + +static void json_object_object_delete(struct json_object* jso) +{ + lh_table_free(jso->o.c_object); + json_object_generic_delete(jso); +} + +struct json_object* json_object_new_object(void) +{ + struct json_object* jso = json_object_new(json_type_object); + + if (!jso) + { + return NULL; + } + + jso->_delete = &json_object_object_delete; + jso->_to_json_string = &json_object_object_to_json_string; + jso->o.c_object = lh_kchar_table_new(JSON_OBJECT_DEF_HASH_ENTRIES, NULL, &json_object_lh_entry_free); + + return jso; +} + +struct lh_table* json_object_get_object(struct json_object *jso) +{ + if(!jso) return NULL; + switch(jso->o_type) { + case json_type_object: + return jso->o.c_object; + default: + return NULL; + } +} + +void json_object_object_add(struct json_object* jso, const char *key, + struct json_object *val) +{ + // We lookup the entry and replace the value, rather than just deleting + // and re-adding it, so the existing key remains valid. + json_object *existing_value = NULL; + struct lh_entry *existing_entry; + existing_entry = lh_table_lookup_entry(jso->o.c_object, (void*)key); + if (!existing_entry) + { + lh_table_insert(jso->o.c_object, strdup(key), val); + return; + } + existing_value = (void *)existing_entry->v; + if (existing_value) + json_object_put(existing_value); + existing_entry->v = val; +} + +int json_object_object_length(struct json_object *jso) +{ + return lh_table_length(jso->o.c_object); +} + +struct json_object* json_object_object_get(struct json_object* jso, const char *key) +{ + struct json_object *result = NULL; + json_object_object_get_ex(jso, key, &result); + return result; +} + +json_bool json_object_object_get_ex(struct json_object* jso, const char *key, struct json_object **value) +{ + if (value != NULL) + *value = NULL; + + if (NULL == jso) + return FALSE; + + switch(jso->o_type) + { + case json_type_object: + return lh_table_lookup_ex(jso->o.c_object, (void*)key, (void**)value); + default: + if (value != NULL) + *value = NULL; + return FALSE; + } +} + +void json_object_object_del(struct json_object* jso, const char *key) +{ + lh_table_delete(jso->o.c_object, key); +} + + +/* json_object_boolean */ + +static int json_object_boolean_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + if(jso->o.c_boolean) return sprintbuf(pb, "true"); + else return sprintbuf(pb, "false"); +} + +struct json_object* json_object_new_boolean(json_bool b) +{ + struct json_object *jso = json_object_new(json_type_boolean); + if(!jso) return NULL; + jso->_to_json_string = &json_object_boolean_to_json_string; + jso->o.c_boolean = b; + return jso; +} + +json_bool json_object_get_boolean(struct json_object *jso) +{ + if(!jso) return FALSE; + switch(jso->o_type) { + case json_type_boolean: + return jso->o.c_boolean; + case json_type_int: + return (jso->o.c_int64 != 0); + case json_type_double: + return (jso->o.c_double != 0); + case json_type_string: + return (jso->o.c_string.len != 0); + default: + return FALSE; + } +} + + +/* json_object_int */ + +static int json_object_int_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + return sprintbuf(pb, "%"PRId64, jso->o.c_int64); +} + +struct json_object* json_object_new_int(int32_t i) +{ + struct json_object *jso = json_object_new(json_type_int); + if(!jso) return NULL; + jso->_to_json_string = &json_object_int_to_json_string; + jso->o.c_int64 = i; + return jso; +} + +int32_t json_object_get_int(struct json_object *jso) +{ + int64_t cint64; + enum json_type o_type; + + if(!jso) return 0; + + o_type = jso->o_type; + cint64 = jso->o.c_int64; + + if (o_type == json_type_string) + { + /* + * Parse strings into 64-bit numbers, then use the + * 64-to-32-bit number handling below. + */ + if (json_parse_int64(jso->o.c_string.str, &cint64) != 0) + return 0; /* whoops, it didn't work. */ + o_type = json_type_int; + } + + switch(o_type) { + case json_type_int: + /* Make sure we return the correct values for out of range numbers. */ + if (cint64 <= INT32_MIN) + return INT32_MIN; + else if (cint64 >= INT32_MAX) + return INT32_MAX; + else + return (int32_t)cint64; + case json_type_double: + return (int32_t)jso->o.c_double; + case json_type_boolean: + return jso->o.c_boolean; + default: + return 0; + } +} + +struct json_object* json_object_new_int64(int64_t i) +{ + struct json_object *jso = json_object_new(json_type_int); + if(!jso) return NULL; + jso->_to_json_string = &json_object_int_to_json_string; + jso->o.c_int64 = i; + return jso; +} + +int64_t json_object_get_int64(struct json_object *jso) +{ + int64_t cint; + + if(!jso) return 0; + switch(jso->o_type) { + case json_type_int: + return jso->o.c_int64; + case json_type_double: + return (int64_t)jso->o.c_double; + case json_type_boolean: + return jso->o.c_boolean; + case json_type_string: + if (json_parse_int64(jso->o.c_string.str, &cint) == 0) return cint; + default: + return 0; + } +} + + +/* json_object_double */ + +static int json_object_double_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + char buf[128], *p, *q; + size_t size; + /* Although JSON RFC does not support + NaN or Infinity as numeric values + ECMA 262 section 9.8.1 defines + how to handle these cases as strings */ + if(isnan(jso->o.c_double)) + size = _snprintf_s(buf, sizeof(buf), _TRUNCATE, "NaN"); + else if(isinf(jso->o.c_double)) + if(jso->o.c_double > 0) + size = _snprintf_s(buf, sizeof(buf), _TRUNCATE, "Infinity"); + else + size = _snprintf_s(buf, sizeof(buf), _TRUNCATE, "-Infinity"); + else + size = _snprintf_s(buf, sizeof(buf), _TRUNCATE, "%.17g", jso->o.c_double); + + p = strchr(buf, ','); + if (p) { + *p = '.'; + } else { + p = strchr(buf, '.'); + } + if (p && (flags & JSON_C_TO_STRING_NOZERO)) { + /* last useful digit, always keep 1 zero */ + p++; + for (q=p ; *q ; q++) { + if (*q!='0') p=q; + } + /* drop trailing zeroes */ + *(++p) = 0; + size = p-buf; + } + printbuf_memappend(pb, buf, size); + return (int)size; +} + +struct json_object* json_object_new_double(double d) +{ + struct json_object *jso = json_object_new(json_type_double); + if (!jso) + return NULL; + jso->_to_json_string = &json_object_double_to_json_string; + jso->o.c_double = d; + return jso; +} + +struct json_object* json_object_new_double_s(double d, const char *ds) +{ + struct json_object *jso = json_object_new_double(d); + if (!jso) + return NULL; + + json_object_set_serializer(jso, json_object_userdata_to_json_string, strdup(ds), json_object_free_userdata); + return jso; +} + +int json_object_userdata_to_json_string(struct json_object *jso, struct printbuf *pb, int level, int flags) +{ + size_t userdata_len = strlen(jso->_userdata); + printbuf_memappend(pb, jso->_userdata, userdata_len); + return (int)userdata_len; +} + +void json_object_free_userdata(struct json_object *jso, void *userdata) +{ + free(userdata); +} + +double json_object_get_double(struct json_object *jso) +{ + double cdouble; + char *errPtr = NULL; + + if(!jso) return 0.0; + switch(jso->o_type) { + case json_type_double: + return jso->o.c_double; + case json_type_int: + return (double)jso->o.c_int64; + case json_type_boolean: + return jso->o.c_boolean; + case json_type_string: + errno = 0; + cdouble = strtod(jso->o.c_string.str,&errPtr); + + /* if conversion stopped at the first character, return 0.0 */ + if (errPtr == jso->o.c_string.str) + return 0.0; + + /* + * Check that the conversion terminated on something sensible + * + * For example, { "pay" : 123AB } would parse as 123. + */ + if (*errPtr != '\0') + return 0.0; + + /* + * If strtod encounters a string which would exceed the + * capacity of a double, it returns +/- HUGE_VAL and sets + * errno to ERANGE. But +/- HUGE_VAL is also a valid result + * from a conversion, so we need to check errno. + * + * Underflow also sets errno to ERANGE, but it returns 0 in + * that case, which is what we will return anyway. + * + * See CERT guideline ERR30-C + */ + if ((HUGE_VAL == cdouble || -HUGE_VAL == cdouble) && + (ERANGE == errno)) + cdouble = 0.0; + return cdouble; + default: + return 0.0; + } +} + + +/* json_object_string */ + +static int json_object_string_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + sprintbuf(pb, "\""); + json_escape_str(pb, jso->o.c_string.str, jso->o.c_string.len); + sprintbuf(pb, "\""); + return 0; +} + +static void json_object_string_delete(struct json_object* jso) +{ + free(jso->o.c_string.str); + json_object_generic_delete(jso); +} + +struct json_object* json_object_new_string(const char *s) +{ + struct json_object *jso = json_object_new(json_type_string); + if(!jso) return NULL; + jso->_delete = &json_object_string_delete; + jso->_to_json_string = &json_object_string_to_json_string; + jso->o.c_string.str = strdup(s); + jso->o.c_string.len = (int)strlen(s); + return jso; +} + +struct json_object* json_object_new_string_len(const char *s, size_t len) +{ + struct json_object *jso = json_object_new(json_type_string); + if(!jso) return NULL; + jso->_delete = &json_object_string_delete; + jso->_to_json_string = &json_object_string_to_json_string; + jso->o.c_string.str = (char*)malloc(len + 1); + memcpy(jso->o.c_string.str, (void *)s, len); + jso->o.c_string.str[len] = '\0'; + jso->o.c_string.len = len; + return jso; +} + +char* json_object_get_string(struct json_object *jso) +{ + if (!jso) + return NULL; + switch (jso->o_type) + { + case json_type_string: + return jso->o.c_string.str; + default: + return json_object_to_json_string(jso); + } +} + +int json_object_get_string_len(struct json_object *jso) { + if(!jso) return 0; + switch(jso->o_type) { + case json_type_string: + return (int)jso->o.c_string.len; + default: + return 0; + } +} + + +/* json_object_array */ + +static int json_object_array_to_json_string(struct json_object* jso, + struct printbuf *pb, + int level, + int flags) +{ + int had_children = 0; + int ii; + sprintbuf(pb, "["); + if (flags & JSON_C_TO_STRING_PRETTY) + sprintbuf(pb, "\n"); + for(ii=0; ii < json_object_array_length(jso); ii++) + { + struct json_object *val; + if (had_children) + { + sprintbuf(pb, ","); + if (flags & JSON_C_TO_STRING_PRETTY) + sprintbuf(pb, "\n"); + } + had_children = 1; + if (flags & JSON_C_TO_STRING_SPACED) + sprintbuf(pb, " "); + indent(pb, level + 1, flags); + val = json_object_array_get_idx(jso, ii); + if(val == NULL) + sprintbuf(pb, "null"); + else + val->_to_json_string(val, pb, level+1, flags); + } + if (flags & JSON_C_TO_STRING_PRETTY) + { + if (had_children) + sprintbuf(pb, "\n"); + indent(pb,level,flags); + } + + if (flags & JSON_C_TO_STRING_SPACED) + return sprintbuf(pb, " ]"); + else + return sprintbuf(pb, "]"); +} + +static void json_object_array_entry_free(void *data) +{ + json_object_put((struct json_object*)data); +} + +static void json_object_array_delete(struct json_object* jso) +{ + array_list_free(jso->o.c_array); + json_object_generic_delete(jso); +} + +struct json_object* json_object_new_array(void) +{ + struct json_object *jso = json_object_new(json_type_array); + if(!jso) return NULL; + jso->_delete = &json_object_array_delete; + jso->_to_json_string = &json_object_array_to_json_string; + jso->o.c_array = array_list_new(&json_object_array_entry_free); + return jso; +} + +struct array_list* json_object_get_array(struct json_object *jso) +{ + if(!jso) return NULL; + switch(jso->o_type) { + case json_type_array: + return jso->o.c_array; + default: + return NULL; + } +} + +void json_object_array_sort(struct json_object *jso, int(__cdecl* sort_fn)(const void *, const void *)) +{ + array_list_sort(jso->o.c_array, sort_fn); +} + +int json_object_array_length(struct json_object *jso) +{ + return array_list_length(jso->o.c_array); +} + +int json_object_array_add(struct json_object *jso,struct json_object *val) +{ + return array_list_add(jso->o.c_array, val); +} + +int json_object_array_put_idx(struct json_object *jso, int idx, + struct json_object *val) +{ + return array_list_put_idx(jso->o.c_array, idx, val); +} + +struct json_object* json_object_array_get_idx(struct json_object *jso, + int idx) +{ + return (struct json_object*)array_list_get_idx(jso->o.c_array, idx); +} + diff --git a/plugins/OnlineChecks/json-c/json_object.h b/plugins/OnlineChecks/json-c/json_object.h new file mode 100644 index 0000000..c649ab7 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_object.h @@ -0,0 +1,611 @@ +/* + * $Id: json_object.h,v 1.12 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _json_object_h_ +#define _json_object_h_ + +#ifdef __GNUC__ +#define THIS_FUNCTION_IS_DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define THIS_FUNCTION_IS_DEPRECATED(func) __declspec(deprecated) func +#else +#define THIS_FUNCTION_IS_DEPRECATED(func) func +#endif + +#include "json_inttypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define JSON_OBJECT_DEF_HASH_ENTRIES 16 + +/** + * A flag for the json_object_to_json_string_ext() and + * json_object_to_file_ext() functions which causes the output + * to have no extra whitespace or formatting applied. + */ +#define JSON_C_TO_STRING_PLAIN 0 +/** + * A flag for the json_object_to_json_string_ext() and + * json_object_to_file_ext() functions which causes the output to have + * minimal whitespace inserted to make things slightly more readable. + */ +#define JSON_C_TO_STRING_SPACED (1<<0) +/** + * A flag for the json_object_to_json_string_ext() and + * json_object_to_file_ext() functions which causes + * the output to be formatted. + * + * See the "Two Space Tab" option at http://jsonformatter.curiousconcept.com/ + * for an example of the format. + */ +#define JSON_C_TO_STRING_PRETTY (1<<1) +/** + * A flag to drop trailing zero for float values + */ +#define JSON_C_TO_STRING_NOZERO (1<<2) + +#undef FALSE +#define FALSE ((json_bool)0) + +#undef TRUE +#define TRUE ((json_bool)1) + +extern const char *json_number_chars; +extern const char *json_hex_chars; + +/* CAW: added for ANSI C iteration correctness */ +struct json_object_iter +{ + char *key; + struct json_object *val; + struct lh_entry *entry; +}; + +/* forward structure definitions */ + +typedef int json_bool; +typedef struct printbuf printbuf; +typedef struct lh_table lh_table; +typedef struct array_list array_list; +typedef struct json_object json_object, *json_object_ptr; +typedef struct json_object_iter json_object_iter; +typedef struct json_tokener json_tokener; + +/** + * Type of custom user delete functions. See json_object_set_serializer. + */ +typedef void (json_object_delete_fn)(struct json_object *jso, void *userdata); + +/** + * Type of a custom serialization function. See json_object_set_serializer. + */ +typedef int (json_object_to_json_string_fn)(struct json_object *jso, + struct printbuf *pb, + int level, + int flags); + +/* supported object types */ + +typedef enum json_type { + /* If you change this, be sure to update json_type_to_name() too */ + json_type_null, + json_type_boolean, + json_type_double, + json_type_int, + json_type_object, + json_type_array, + json_type_string, +} json_type; + +/* reference counting functions */ + +/** + * Increment the reference count of json_object, thereby grabbing shared + * ownership of obj. + * + * @param obj the json_object instance + */ +extern struct json_object* json_object_get(struct json_object *obj); + +/** + * Decrement the reference count of json_object and free if it reaches zero. + * You must have ownership of obj prior to doing this or you will cause an + * imbalance in the reference count. + * + * @param obj the json_object instance + * @returns 1 if the object was freed. + */ +int json_object_put(struct json_object *obj); + +/** + * Check if the json_object is of a given type + * @param obj the json_object instance + * @param type one of: + json_type_null (i.e. obj == NULL), + json_type_boolean, + json_type_double, + json_type_int, + json_type_object, + json_type_array, + json_type_string, + */ +extern int json_object_is_type(struct json_object *obj, enum json_type type); + +/** + * Get the type of the json_object. See also json_type_to_name() to turn this + * into a string suitable, for instance, for logging. + * + * @param obj the json_object instance + * @returns type being one of: + json_type_null (i.e. obj == NULL), + json_type_boolean, + json_type_double, + json_type_int, + json_type_object, + json_type_array, + json_type_string, + */ +extern enum json_type json_object_get_type(struct json_object *obj); + + +/** Stringify object to json format. + * Equivalent to json_object_to_json_string_ext(obj, JSON_C_TO_STRING_SPACED) + * @param obj the json_object instance + * @returns a string in JSON format + */ +extern char* json_object_to_json_string(struct json_object *obj); + +/** Stringify object to json format + * @param obj the json_object instance + * @param flags formatting options, see JSON_C_TO_STRING_PRETTY and other constants + * @returns a string in JSON format + */ +extern char* json_object_to_json_string_ext(struct json_object *obj, int flags); + +/** + * Set a custom serialization function to be used when this particular object + * is converted to a string by json_object_to_json_string. + * + * If a custom serializer is already set on this object, any existing + * user_delete function is called before the new one is set. + * + * If to_string_func is NULL, the other parameters are ignored + * and the default behaviour is reset. + * + * The userdata parameter is optional and may be passed as NULL. If provided, + * it is passed to to_string_func as-is. This parameter may be NULL even + * if user_delete is non-NULL. + * + * The user_delete parameter is optional and may be passed as NULL, even if + * the userdata parameter is non-NULL. It will be called just before the + * json_object is deleted, after it's reference count goes to zero + * (see json_object_put()). + * If this is not provided, it is up to the caller to free the userdata at + * an appropriate time. (i.e. after the json_object is deleted) + * + * @param jso the object to customize + * @param to_string_func the custom serialization function + * @param userdata an optional opaque cookie + * @param user_delete an optional function from freeing userdata + */ +extern void json_object_set_serializer(json_object *jso, + json_object_to_json_string_fn to_string_func, + void *userdata, + json_object_delete_fn *user_delete); + +/** + * Simply call free on the userdata pointer. + * Can be used with json_object_set_serializer(). + * + * @param jso unused + * @param userdata the pointer that is passed to free(). + */ +json_object_delete_fn json_object_free_userdata; + +/** + * Copy the jso->_userdata string over to pb as-is. + * Can be used with json_object_set_serializer(). + * + * @param jso The object whose _userdata is used. + * @param pb The destination buffer. + * @param level Ignored. + * @param flags Ignored. + */ +json_object_to_json_string_fn json_object_userdata_to_json_string; + + +/* object type methods */ + +/** Create a new empty object with a reference count of 1. The caller of + * this object initially has sole ownership. Remember, when using + * json_object_object_add or json_object_array_put_idx, ownership will + * transfer to the object/array. Call json_object_get if you want to maintain + * shared ownership or also add this object as a child of multiple objects or + * arrays. Any ownerships you acquired but did not transfer must be released + * through json_object_put. + * + * @returns a json_object of type json_type_object + */ +extern struct json_object* json_object_new_object(void); + +/** Get the hashtable of a json_object of type json_type_object + * @param obj the json_object instance + * @returns a linkhash + */ +extern struct lh_table* json_object_get_object(struct json_object *obj); + +/** Get the size of an object in terms of the number of fields it has. + * @param obj the json_object whose length to return + */ +extern int json_object_object_length(struct json_object* obj); + +/** Add an object field to a json_object of type json_type_object + * + * The reference count will *not* be incremented. This is to make adding + * fields to objects in code more compact. If you want to retain a reference + * to an added object, independent of the lifetime of obj, you must wrap the + * passed object with json_object_get. + * + * Upon calling this, the ownership of val transfers to obj. Thus you must + * make sure that you do in fact have ownership over this object. For instance, + * json_object_new_object will give you ownership until you transfer it, + * whereas json_object_object_get does not. + * + * @param obj the json_object instance + * @param key the object field name (a private copy will be duplicated) + * @param val a json_object or NULL member to associate with the given field + */ +extern void json_object_object_add(struct json_object* obj, const char *key, + struct json_object *val); + +/** Get the json_object associate with a given object field + * + * *No* reference counts will be changed. There is no need to manually adjust + * reference counts through the json_object_put/json_object_get methods unless + * you need to have the child (value) reference maintain a different lifetime + * than the owning parent (obj). Ownership of the returned value is retained + * by obj (do not do json_object_put unless you have done a json_object_get). + * If you delete the value from obj (json_object_object_del) and wish to access + * the returned reference afterwards, make sure you have first gotten shared + * ownership through json_object_get (& don't forget to do a json_object_put + * or transfer ownership to prevent a memory leak). + * + * @param obj the json_object instance + * @param key the object field name + * @returns the json_object associated with the given field name + * @deprecated Please use json_object_object_get_ex + */ +THIS_FUNCTION_IS_DEPRECATED(extern struct json_object* json_object_object_get(struct json_object* obj, + const char *key)); + +/** Get the json_object associated with a given object field. + * + * This returns true if the key is found, false in all other cases (including + * if obj isn't a json_type_object). + * + * *No* reference counts will be changed. There is no need to manually adjust + * reference counts through the json_object_put/json_object_get methods unless + * you need to have the child (value) reference maintain a different lifetime + * than the owning parent (obj). Ownership of value is retained by obj. + * + * @param obj the json_object instance + * @param key the object field name + * @param value a pointer where to store a reference to the json_object + * associated with the given field name. + * + * It is safe to pass a NULL value. + * @returns whether or not the key exists + */ +extern json_bool json_object_object_get_ex(struct json_object* obj, + const char *key, + struct json_object **value); + +/** Delete the given json_object field + * + * The reference count will be decremented for the deleted object. If there + * are no more owners of the value represented by this key, then the value is + * freed. Otherwise, the reference to the value will remain in memory. + * + * @param obj the json_object instance + * @param key the object field name + */ +extern void json_object_object_del(struct json_object* obj, const char *key); + +/** + * Iterate through all keys and values of an object. + * + * Adding keys to the object while iterating is NOT allowed. + * + * Deleting an existing key, or replacing an existing key with a + * new value IS allowed. + * + * @param obj the json_object instance + * @param key the local name for the char* key variable defined in the body + * @param val the local name for the json_object* object variable defined in + * the body + */ +#if defined(__GNUC__) && !defined(__STRICT_ANSI__) && __STDC_VERSION__ >= 199901L + +# define json_object_object_foreach(obj,key,val) \ + char *key; \ + struct json_object *val __attribute__((__unused__)); \ + for(struct lh_entry *entry ## key = json_object_get_object(obj)->head, *entry_next ## key = NULL; \ + ({ if(entry ## key) { \ + key = (char*)entry ## key->k; \ + val = (struct json_object*)entry ## key->v; \ + entry_next ## key = entry ## key->next; \ + } ; entry ## key; }); \ + entry ## key = entry_next ## key ) + +#else /* ANSI C or MSC */ + +# define json_object_object_foreach(obj,key,val) \ + char *key;\ + struct json_object *val; \ + struct lh_entry *entry ## key; \ + struct lh_entry *entry_next ## key = NULL; \ + for(entry ## key = json_object_get_object(obj)->head; \ + (entry ## key ? ( \ + key = (char*)entry ## key->k, \ + val = (struct json_object*)entry ## key->v, \ + entry_next ## key = entry ## key->next, \ + entry ## key) : 0); \ + entry ## key = entry_next ## key) + +#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) && __STDC_VERSION__ >= 199901L */ + +/** Iterate through all keys and values of an object (ANSI C Safe) + * @param obj the json_object instance + * @param iter the object iterator + */ +#define json_object_object_foreachC(obj,iter) \ + for(iter.entry = json_object_get_object(obj)->head; (iter.entry ? (iter.key = (char*)iter.entry->k, iter.val = (struct json_object*)iter.entry->v, iter.entry) : 0); iter.entry = iter.entry->next) + +/* Array type methods */ + +/** Create a new empty json_object of type json_type_array + * @returns a json_object of type json_type_array + */ +extern struct json_object* json_object_new_array(void); + +/** Get the arraylist of a json_object of type json_type_array + * @param obj the json_object instance + * @returns an arraylist + */ +extern struct array_list* json_object_get_array(struct json_object *obj); + +/** Get the length of a json_object of type json_type_array + * @param obj the json_object instance + * @returns an int + */ +extern int json_object_array_length(struct json_object *obj); + +/** Sorts the elements of jso of type json_type_array +* +* Pointers to the json_object pointers will be passed as the two arguments +* to @sort_fn +* +* @param obj the json_object instance +* @param sort_fn a sorting function +*/ +extern void json_object_array_sort(struct json_object *jso, int(__cdecl* sort_fn)(const void *, const void *)); + +/** Add an element to the end of a json_object of type json_type_array + * + * The reference count will *not* be incremented. This is to make adding + * fields to objects in code more compact. If you want to retain a reference + * to an added object you must wrap the passed object with json_object_get + * + * @param obj the json_object instance + * @param val the json_object to be added + */ +extern int json_object_array_add(struct json_object *obj, + struct json_object *val); + +/** Insert or replace an element at a specified index in an array (a json_object of type json_type_array) + * + * The reference count will *not* be incremented. This is to make adding + * fields to objects in code more compact. If you want to retain a reference + * to an added object you must wrap the passed object with json_object_get + * + * The reference count of a replaced object will be decremented. + * + * The array size will be automatically be expanded to the size of the + * index if the index is larger than the current size. + * + * @param obj the json_object instance + * @param idx the index to insert the element at + * @param val the json_object to be added + */ +extern int json_object_array_put_idx(struct json_object *obj, int idx, + struct json_object *val); + +/** Get the element at specificed index of the array (a json_object of type json_type_array) + * @param obj the json_object instance + * @param idx the index to get the element at + * @returns the json_object at the specified index (or NULL) + */ +extern struct json_object* json_object_array_get_idx(struct json_object *obj, + int idx); + +/* json_bool type methods */ + +/** Create a new empty json_object of type json_type_boolean + * @param b a json_bool TRUE or FALSE (0 or 1) + * @returns a json_object of type json_type_boolean + */ +extern struct json_object* json_object_new_boolean(json_bool b); + +/** Get the json_bool value of a json_object + * + * The type is coerced to a json_bool if the passed object is not a json_bool. + * integer and double objects will return FALSE if there value is zero + * or TRUE otherwise. If the passed object is a string it will return + * TRUE if it has a non zero length. If any other object type is passed + * TRUE will be returned if the object is not NULL. + * + * @param obj the json_object instance + * @returns a json_bool + */ +extern json_bool json_object_get_boolean(struct json_object *obj); + + +/* int type methods */ + +/** Create a new empty json_object of type json_type_int + * Note that values are stored as 64-bit values internally. + * To ensure the full range is maintained, use json_object_new_int64 instead. + * @param i the integer + * @returns a json_object of type json_type_int + */ +extern struct json_object* json_object_new_int(int32_t i); + + +/** Create a new empty json_object of type json_type_int + * @param i the integer + * @returns a json_object of type json_type_int + */ +extern struct json_object* json_object_new_int64(int64_t i); + + +/** Get the int value of a json_object + * + * The type is coerced to a int if the passed object is not a int. + * double objects will return their integer conversion. Strings will be + * parsed as an integer. If no conversion exists then 0 is returned + * and errno is set to EINVAL. null is equivalent to 0 (no error values set) + * + * Note that integers are stored internally as 64-bit values. + * If the value of too big or too small to fit into 32-bit, INT32_MAX or + * INT32_MIN are returned, respectively. + * + * @param obj the json_object instance + * @returns an int + */ +extern int32_t json_object_get_int(struct json_object *obj); + +/** Get the int value of a json_object + * + * The type is coerced to a int64 if the passed object is not a int64. + * double objects will return their int64 conversion. Strings will be + * parsed as an int64. If no conversion exists then 0 is returned. + * + * NOTE: Set errno to 0 directly before a call to this function to determine + * whether or not conversion was successful (it does not clear the value for + * you). + * + * @param obj the json_object instance + * @returns an int64 + */ +extern int64_t json_object_get_int64(struct json_object *obj); + + +/* double type methods */ + +/** Create a new empty json_object of type json_type_double + * @param d the double + * @returns a json_object of type json_type_double + */ +extern struct json_object* json_object_new_double(double d); + +/** + * Create a new json_object of type json_type_double, using + * the exact serialized representation of the value. + * + * This allows for numbers that would otherwise get displayed + * inefficiently (e.g. 12.3 => "12.300000000000001") to be + * serialized with the more convenient form. + * + * Note: this is used by json_tokener_parse_ex() to allow for + * an exact re-serialization of a parsed object. + * + * An equivalent sequence of calls is: + * @code + * jso = json_object_new_double(d); + * json_object_set_serializer(d, json_object_userdata_to_json_string, + * strdup(ds), json_object_free_userdata) + * @endcode + * + * @param d the numeric value of the double. + * @param ds the string representation of the double. This will be copied. + */ +extern struct json_object* json_object_new_double_s(double d, const char *ds); + +/** Get the double floating point value of a json_object + * + * The type is coerced to a double if the passed object is not a double. + * integer objects will return their double conversion. Strings will be + * parsed as a double. If no conversion exists then 0.0 is returned and + * errno is set to EINVAL. null is equivalent to 0 (no error values set) + * + * If the value is too big to fit in a double, then the value is set to + * the closest infinity with errno set to ERANGE. If strings cannot be + * converted to their double value, then EINVAL is set & NaN is returned. + * + * Arrays of length 0 are interpreted as 0 (with no error flags set). + * Arrays of length 1 are effectively cast to the equivalent object and + * converted using the above rules. All other arrays set the error to + * EINVAL & return NaN. + * + * NOTE: Set errno to 0 directly before a call to this function to + * determine whether or not conversion was successful (it does not clear + * the value for you). + * + * @param obj the json_object instance + * @returns a double floating point number + */ +extern double json_object_get_double(struct json_object *obj); + + +/* string type methods */ + +/** Create a new empty json_object of type json_type_string + * + * A copy of the string is made and the memory is managed by the json_object + * + * @param s the string + * @returns a json_object of type json_type_string + */ +extern struct json_object* json_object_new_string(const char *s); + +extern struct json_object* json_object_new_string_len(const char *s, size_t len); + +/** Get the string value of a json_object + * + * If the passed object is not of type json_type_string then the JSON + * representation of the object is returned. + * + * The returned string memory is managed by the json_object and will + * be freed when the reference count of the json_object drops to zero. + * + * @param obj the json_object instance + * @returns a string + */ +extern char* json_object_get_string(struct json_object *obj); + +/** Get the string length of a json_object + * + * If the passed object is not of type json_type_string then zero + * will be returned. + * + * @param obj the json_object instance + * @returns int + */ +extern int json_object_get_string_len(struct json_object *obj); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json_object_iterator.c b/plugins/OnlineChecks/json-c/json_object_iterator.c new file mode 100644 index 0000000..7066649 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_object_iterator.c @@ -0,0 +1,168 @@ +/** +******************************************************************************* +* @file json_object_iterator.c +* +* Copyright (c) 2009-2012 Hewlett-Packard Development Company, L.P. +* +* This library is free software; you can redistribute it and/or modify +* it under the terms of the MIT license. See COPYING for details. +* +* @brief json-c forces clients to use its private data +* structures for JSON Object iteration. This API +* implementation corrects that by abstracting the +* private json-c details. +* +******************************************************************************* +*/ + +#include + +#include "json.h" +#include "json_object_private.h" + +#include "json_object_iterator.h" + +/** + * How It Works + * + * For each JSON Object, json-c maintains a linked list of zero + * or more lh_entry (link-hash entry) structures inside the + * Object's link-hash table (lh_table). + * + * Each lh_entry structure on the JSON Object's linked list + * represents a single name/value pair. The "next" field of the + * last lh_entry in the list is set to NULL, which terminates + * the list. + * + * We represent a valid iterator that refers to an actual + * name/value pair via a pointer to the pair's lh_entry + * structure set as the iterator's opaque_ field. + * + * We follow json-c's current pair list representation by + * representing a valid "end" iterator (one that refers past the + * last pair) with a NULL value in the iterator's opaque_ field. + * + * A JSON Object without any pairs in it will have the "head" + * field of its lh_table structure set to NULL. For such an + * object, json_object_iter_begin will return an iterator with + * the opaque_ field set to NULL, which is equivalent to the + * "end" iterator. + * + * When iterating, we simply update the iterator's opaque_ field + * to point to the next lh_entry structure in the linked list. + * opaque_ will become NULL once we iterate past the last pair + * in the list, which makes the iterator equivalent to the "end" + * iterator. + */ + +/// Our current representation of the "end" iterator; +/// +/// @note May not always be NULL +static const void* kObjectEndIterValue = NULL; + +/** + * **************************************************************************** + */ +struct json_object_iterator +json_object_iter_begin(struct json_object* obj) +{ + struct json_object_iterator iter; + struct lh_table* pTable; + + /// @note json_object_get_object will return NULL if passed NULL + /// or a non-json_type_object instance + pTable = json_object_get_object(obj); + JASSERT(NULL != pTable); + + /// @note For a pair-less Object, head is NULL, which matches our + /// definition of the "end" iterator + iter.opaque_ = pTable->head; + return iter; +} + +/** + * **************************************************************************** + */ +struct json_object_iterator +json_object_iter_end(const struct json_object* obj) +{ + struct json_object_iterator iter; + + JASSERT(NULL != obj); + JASSERT(json_object_is_type(obj, json_type_object)); + + iter.opaque_ = kObjectEndIterValue; + + return iter; +} + +/** + * **************************************************************************** + */ +void +json_object_iter_next(struct json_object_iterator* iter) +{ + JASSERT(NULL != iter); + JASSERT(kObjectEndIterValue != iter->opaque_); + + iter->opaque_ = ((struct lh_entry *)iter->opaque_)->next; +} + + +/** + * **************************************************************************** + */ +const char* +json_object_iter_peek_name(const struct json_object_iterator* iter) +{ + JASSERT(NULL != iter); + JASSERT(kObjectEndIterValue != iter->opaque_); + + return (const char*)(((struct lh_entry *)iter->opaque_)->k); +} + + +/** + * **************************************************************************** + */ +struct json_object* +json_object_iter_peek_value(const struct json_object_iterator* iter) +{ + JASSERT(NULL != iter); + JASSERT(kObjectEndIterValue != iter->opaque_); + + return (struct json_object*)(((struct lh_entry *)iter->opaque_)->v); +} + + +/** + * **************************************************************************** + */ +json_bool +json_object_iter_equal(const struct json_object_iterator* iter1, + const struct json_object_iterator* iter2) +{ + JASSERT(NULL != iter1); + JASSERT(NULL != iter2); + + return (iter1->opaque_ == iter2->opaque_); +} + + +/** + * **************************************************************************** + */ +struct json_object_iterator +json_object_iter_init_default(void) +{ + struct json_object_iterator iter; + + /** + * @note Make this a negative, invalid value, such that + * accidental access to it would likely be trapped by the + * hardware as an invalid address. + */ + iter.opaque_ = NULL; + + return iter; +} diff --git a/plugins/OnlineChecks/json-c/json_object_iterator.h b/plugins/OnlineChecks/json-c/json_object_iterator.h new file mode 100644 index 0000000..44c9fb2 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_object_iterator.h @@ -0,0 +1,239 @@ +/** +******************************************************************************* +* @file json_object_iterator.h +* +* Copyright (c) 2009-2012 Hewlett-Packard Development Company, L.P. +* +* This library is free software; you can redistribute it and/or modify +* it under the terms of the MIT license. See COPYING for details. +* +* @brief json-c forces clients to use its private data +* structures for JSON Object iteration. This API +* corrects that by abstracting the private json-c +* details. +* +* API attributes:
+* * Thread-safe: NO
+* * Reentrant: NO +* +******************************************************************************* +*/ + + +#ifndef JSON_OBJECT_ITERATOR_H +#define JSON_OBJECT_ITERATOR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Forward declaration for the opaque iterator information. + */ +struct json_object_iter_info_; + +/** + * The opaque iterator that references a name/value pair within + * a JSON Object instance or the "end" iterator value. + */ +struct json_object_iterator { + const void* opaque_; +}; + + +/** + * forward declaration of json-c's JSON value instance structure + */ +struct json_object; + + +/** + * Initializes an iterator structure to a "default" value that + * is convenient for initializing an iterator variable to a + * default state (e.g., initialization list in a class' + * constructor). + * + * @code + * struct json_object_iterator iter = json_object_iter_init_default(); + * MyClass() : iter_(json_object_iter_init_default()) + * @endcode + * + * @note The initialized value doesn't reference any specific + * pair, is considered an invalid iterator, and MUST NOT + * be passed to any json-c API that expects a valid + * iterator. + * + * @note User and internal code MUST NOT make any assumptions + * about and dependencies on the value of the "default" + * iterator value. + * + * @return json_object_iterator + */ +struct json_object_iterator +json_object_iter_init_default(void); + +/** Retrieves an iterator to the first pair of the JSON Object. + * + * @warning Any modification of the underlying pair invalidates all + * iterators to that pair. + * + * @param obj JSON Object instance (MUST be of type json_object) + * + * @return json_object_iterator If the JSON Object has at + * least one pair, on return, the iterator refers + * to the first pair. If the JSON Object doesn't + * have any pairs, the returned iterator is + * equivalent to the "end" iterator for the same + * JSON Object instance. + * + * @code + * struct json_object_iterator it; + * struct json_object_iterator itEnd; + * struct json_object* obj; + * + * obj = json_tokener_parse("{'first':'george', 'age':100}"); + * it = json_object_iter_begin(obj); + * itEnd = json_object_iter_end(obj); + * + * while (!json_object_iter_equal(&it, &itEnd)) { + * printf("%s\n", + * json_object_iter_peek_name(&it)); + * json_object_iter_next(&it); + * } + * + * @endcode + */ +struct json_object_iterator +json_object_iter_begin(struct json_object* obj); + +/** Retrieves the iterator that represents the position beyond the + * last pair of the given JSON Object instance. + * + * @warning Do NOT write code that assumes that the "end" + * iterator value is NULL, even if it is so in a + * particular instance of the implementation. + * + * @note The reason we do not (and MUST NOT) provide + * "json_object_iter_is_end(json_object_iterator* iter)" + * type of API is because it would limit the underlying + * representation of name/value containment (or force us + * to add additional, otherwise unnecessary, fields to + * the iterator structure). The "end" iterator and the + * equality test method, on the other hand, permit us to + * cleanly abstract pretty much any reasonable underlying + * representation without burdening the iterator + * structure with unnecessary data. + * + * @note For performance reasons, memorize the "end" iterator prior + * to any loop. + * + * @param obj JSON Object instance (MUST be of type json_object) + * + * @return json_object_iterator On return, the iterator refers + * to the "end" of the Object instance's pairs + * (i.e., NOT the last pair, but "beyond the last + * pair" value) + */ +struct json_object_iterator +json_object_iter_end(const struct json_object* obj); + +/** Returns an iterator to the next pair, if any + * + * @warning Any modification of the underlying pair + * invalidates all iterators to that pair. + * + * @param iter [IN/OUT] Pointer to iterator that references a + * name/value pair; MUST be a valid, non-end iterator. + * WARNING: bad things will happen if invalid or "end" + * iterator is passed. Upon return will contain the + * reference to the next pair if there is one; if there + * are no more pairs, will contain the "end" iterator + * value, which may be compared against the return value + * of json_object_iter_end() for the same JSON Object + * instance. + */ +void +json_object_iter_next(struct json_object_iterator* iter); + + +/** Returns a const pointer to the name of the pair referenced + * by the given iterator. + * + * @param iter pointer to iterator that references a name/value + * pair; MUST be a valid, non-end iterator. + * + * @warning bad things will happen if an invalid or + * "end" iterator is passed. + * + * @return const char* Pointer to the name of the referenced + * name/value pair. The name memory belongs to the + * name/value pair, will be freed when the pair is + * deleted or modified, and MUST NOT be modified or + * freed by the user. + */ +const char* +json_object_iter_peek_name(const struct json_object_iterator* iter); + + +/** Returns a pointer to the json-c instance representing the + * value of the referenced name/value pair, without altering + * the instance's reference count. + * + * @param iter pointer to iterator that references a name/value + * pair; MUST be a valid, non-end iterator. + * + * @warning bad things will happen if invalid or + * "end" iterator is passed. + * + * @return struct json_object* Pointer to the json-c value + * instance of the referenced name/value pair; the + * value's reference count is not changed by this + * function: if you plan to hold on to this json-c node, + * take a look at json_object_get() and + * json_object_put(). IMPORTANT: json-c API represents + * the JSON Null value as a NULL json_object instance + * pointer. + */ +struct json_object* +json_object_iter_peek_value(const struct json_object_iterator* iter); + + +/** Tests two iterators for equality. Typically used to test + * for end of iteration by comparing an iterator to the + * corresponding "end" iterator (that was derived from the same + * JSON Object instance). + * + * @note The reason we do not (and MUST NOT) provide + * "json_object_iter_is_end(json_object_iterator* iter)" + * type of API is because it would limit the underlying + * representation of name/value containment (or force us + * to add additional, otherwise unnecessary, fields to + * the iterator structure). The equality test method, on + * the other hand, permits us to cleanly abstract pretty + * much any reasonable underlying representation. + * + * @param iter1 Pointer to first valid, non-NULL iterator + * @param iter2 POinter to second valid, non-NULL iterator + * + * @warning if a NULL iterator pointer or an uninitialized + * or invalid iterator, or iterators derived from + * different JSON Object instances are passed, bad things + * will happen! + * + * @return json_bool non-zero if iterators are equal (i.e., both + * reference the same name/value pair or are both at + * "end"); zero if they are not equal. + */ +json_bool +json_object_iter_equal(const struct json_object_iterator* iter1, + const struct json_object_iterator* iter2); + + +#ifdef __cplusplus +} +#endif + + +#endif /* JSON_OBJECT_ITERATOR_H */ diff --git a/plugins/OnlineChecks/json-c/json_object_private.h b/plugins/OnlineChecks/json-c/json_object_private.h new file mode 100644 index 0000000..deff7e8 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_object_private.h @@ -0,0 +1,47 @@ +/* + * $Id: json_object_private.h,v 1.4 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _json_object_private_h_ +#define _json_object_private_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (json_object_private_delete_fn)(struct json_object *o); + +struct json_object +{ + enum json_type o_type; + json_object_private_delete_fn *_delete; + json_object_to_json_string_fn *_to_json_string; + int _ref_count; + struct printbuf *_pb; + union data { + json_bool c_boolean; + double c_double; + int64_t c_int64; + struct lh_table *c_object; + struct array_list *c_array; + struct { + char *str; + size_t len; + } c_string; + } o; + json_object_delete_fn *_user_delete; + void *_userdata; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json_tokener.c b/plugins/OnlineChecks/json-c/json_tokener.c new file mode 100644 index 0000000..2959cbd --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_tokener.c @@ -0,0 +1,888 @@ +/* + * $Id: json_tokener.c,v 1.20 2006/07/25 03:24:50 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + * + * Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. + * The copyrights to the contents of this file are licensed under the MIT License + * (http://www.opensource.org/licenses/mit-license.php) + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "bits.h" +#include "debug.h" +#include "printbuf.h" +#include "arraylist.h" +#include "json_inttypes.h" +#include "json_object.h" +#include "json_tokener.h" +#include "json_util.h" + +#ifdef HAVE_LOCALE_H +#include +#endif /* HAVE_LOCALE_H */ + +#if !HAVE_STRDUP && defined(_MSC_VER) + /* MSC has the version as _strdup */ +# define strdup _strdup +#elif !HAVE_STRDUP +# error You do not have strdup on your system. +#endif /* HAVE_STRDUP */ + +#if !HAVE_STRNCASECMP && defined(_MSC_VER) + /* MSC has the version as _strnicmp */ +# define strncasecmp _strnicmp +#elif !HAVE_STRNCASECMP +# error You do not have strncasecmp on your system. +#endif /* HAVE_STRNCASECMP */ + +/* Use C99 NAN by default; if not available, nan("") should work too. */ +#ifndef NAN +#define NAN nan("") +#endif /* !NAN */ + +static const char json_null_str[] = "null"; +static const int json_null_str_len = sizeof(json_null_str) - 1; +static const char json_inf_str[] = "Infinity"; +static const int json_inf_str_len = sizeof(json_inf_str) - 1; +static const char json_nan_str[] = "NaN"; +static const int json_nan_str_len = sizeof(json_nan_str) - 1; +static const char json_true_str[] = "true"; +static const int json_true_str_len = sizeof(json_true_str) - 1; +static const char json_false_str[] = "false"; +static const int json_false_str_len = sizeof(json_false_str) - 1; + +static const char* json_tokener_errors[] = { + "success", + "continue", + "nesting too deep", + "unexpected end of data", + "unexpected character", + "null expected", + "boolean expected", + "number expected", + "array value separator ',' expected", + "quoted object property name expected", + "object property name separator ':' expected", + "object value separator ',' expected", + "invalid string sequence", + "expected comment", + "buffer size overflow" +}; + +const char *json_tokener_error_desc(enum json_tokener_error jerr) +{ + int jerr_int = (int)jerr; + if (jerr_int < 0 || jerr_int >= (int)(sizeof(json_tokener_errors) / sizeof(json_tokener_errors[0]))) + return "Unknown error, invalid json_tokener_error value passed to json_tokener_error_desc()"; + return json_tokener_errors[jerr]; +} + +enum json_tokener_error json_tokener_get_error(json_tokener *tok) +{ + return tok->err; +} + +/* Stuff for decoding unicode sequences */ +#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800) +#define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00) +#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000) +static unsigned char utf8_replacement_char[3] = { 0xEF, 0xBF, 0xBD }; + +struct json_tokener* json_tokener_new_ex(int depth) +{ + struct json_tokener *tok; + + tok = (struct json_tokener*)calloc(1, sizeof(struct json_tokener)); + if (!tok) return NULL; + tok->stack = (struct json_tokener_srec *)calloc(depth, sizeof(struct json_tokener_srec)); + if (!tok->stack) { + free(tok); + return NULL; + } + tok->pb = printbuf_new(); + tok->max_depth = depth; + json_tokener_reset(tok); + return tok; +} + +struct json_tokener* json_tokener_new(void) +{ + return json_tokener_new_ex(JSON_TOKENER_DEFAULT_DEPTH); +} + +void json_tokener_free(struct json_tokener *tok) +{ + json_tokener_reset(tok); + if (tok->pb) printbuf_free(tok->pb); + if (tok->stack) free(tok->stack); + free(tok); +} + +static void json_tokener_reset_level(struct json_tokener *tok, int depth) +{ + tok->stack[depth].state = json_tokener_state_eatws; + tok->stack[depth].saved_state = json_tokener_state_start; + json_object_put(tok->stack[depth].current); + tok->stack[depth].current = NULL; + free(tok->stack[depth].obj_field_name); + tok->stack[depth].obj_field_name = NULL; +} + +void json_tokener_reset(struct json_tokener *tok) +{ + int i; + if (!tok) + return; + + for(i = tok->depth; i >= 0; i--) + json_tokener_reset_level(tok, i); + tok->depth = 0; + tok->err = json_tokener_success; +} + +struct json_object* json_tokener_parse(const char *str) +{ + enum json_tokener_error jerr_ignored; + struct json_object* obj; + obj = json_tokener_parse_verbose(str, &jerr_ignored); + return obj; +} + +struct json_object* json_tokener_parse_verbose(const char *str, enum json_tokener_error *error) +{ + struct json_tokener* tok; + struct json_object* obj; + + tok = json_tokener_new(); + if (!tok) + return NULL; + obj = json_tokener_parse_ex(tok, str, -1); + *error = tok->err; + if(tok->err != json_tokener_success) { + if (obj != NULL) + json_object_put(obj); + obj = NULL; + } + + json_tokener_free(tok); + return obj; +} + +#define state tok->stack[tok->depth].state +#define saved_state tok->stack[tok->depth].saved_state +#define current tok->stack[tok->depth].current +#define obj_field_name tok->stack[tok->depth].obj_field_name + +/* Optimization: + * json_tokener_parse_ex() consumed a lot of CPU in its main loop, + * iterating character-by character. A large performance boost is + * achieved by using tighter loops to locally handle units such as + * comments and strings. Loops that handle an entire token within + * their scope also gather entire strings and pass them to + * printbuf_memappend() in a single call, rather than calling + * printbuf_memappend() one char at a time. + * + * PEEK_CHAR() and ADVANCE_CHAR() macros are used for code that is + * common to both the main loop and the tighter loops. + */ + +/* PEEK_CHAR(dest, tok) macro: + * Peeks at the current char and stores it in dest. + * Returns 1 on success, sets tok->err and returns 0 if no more chars. + * Implicit inputs: str, len vars + */ +#define PEEK_CHAR(dest, tok) \ + (((tok)->char_offset == len) ? \ + (((tok)->depth == 0 && state == json_tokener_state_eatws && saved_state == json_tokener_state_finish) ? \ + (((tok)->err = json_tokener_success), 0) \ + : \ + (((tok)->err = json_tokener_continue), 0) \ + ) : \ + (((dest) = *str), 1) \ + ) + +/* ADVANCE_CHAR() macro: + * Incrementes str & tok->char_offset. + * For convenience of existing conditionals, returns the old value of c (0 on eof) + * Implicit inputs: c var + */ +#define ADVANCE_CHAR(str, tok) \ + ( ++(str), ((tok)->char_offset)++, c) + + +/* End optimization macro defs */ + + +struct json_object* json_tokener_parse_ex(struct json_tokener *tok, + const char *str, int len) +{ + struct json_object *obj = NULL; + char c = '\1'; +#ifdef HAVE_SETLOCALE + char *oldlocale=NULL, *tmplocale; + + tmplocale = setlocale(LC_NUMERIC, NULL); + if (tmplocale) oldlocale = strdup(tmplocale); + setlocale(LC_NUMERIC, "C"); +#endif + + tok->char_offset = 0; + tok->err = json_tokener_success; + + /* this interface is presently not 64-bit clean due to the int len argument + and the internal printbuf interface that takes 32-bit int len arguments + so the function limits the maximum string size to INT32_MAX (2GB). + If the function is called with len == -1 then strlen is called to check + the string length is less than INT32_MAX (2GB) */ + if ((len < -1) || (len == -1 && strlen(str) > INT32_MAX)) { + tok->err = json_tokener_error_size; + return NULL; + } + + while (PEEK_CHAR(c, tok)) { + + redo_char: + switch(state) { + + case json_tokener_state_eatws: + /* Advance until we change state */ + while (isspace((int)c)) { + if ((!ADVANCE_CHAR(str, tok)) || (!PEEK_CHAR(c, tok))) + goto out; + } + if(c == '/' && !(tok->flags & JSON_TOKENER_STRICT)) { + printbuf_reset(tok->pb); + printbuf_memappend_fast(tok->pb, &c, 1); + state = json_tokener_state_comment_start; + } else { + state = saved_state; + goto redo_char; + } + break; + + case json_tokener_state_start: + switch(c) { + case '{': + state = json_tokener_state_eatws; + saved_state = json_tokener_state_object_field_start; + current = json_object_new_object(); + break; + case '[': + state = json_tokener_state_eatws; + saved_state = json_tokener_state_array; + current = json_object_new_array(); + break; + case 'I': + case 'i': + state = json_tokener_state_inf; + printbuf_reset(tok->pb); + tok->st_pos = 0; + goto redo_char; + case 'N': + case 'n': + state = json_tokener_state_null; // or NaN + printbuf_reset(tok->pb); + tok->st_pos = 0; + goto redo_char; + case '\'': + if (tok->flags & JSON_TOKENER_STRICT) { + /* in STRICT mode only double-quote are allowed */ + tok->err = json_tokener_error_parse_unexpected; + goto out; + } + case '"': + state = json_tokener_state_string; + printbuf_reset(tok->pb); + tok->quote_char = c; + break; + case 'T': + case 't': + case 'F': + case 'f': + state = json_tokener_state_boolean; + printbuf_reset(tok->pb); + tok->st_pos = 0; + goto redo_char; +#if defined(__GNUC__) + case '0' ... '9': +#else + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': +#endif + case '-': + state = json_tokener_state_number; + printbuf_reset(tok->pb); + tok->is_double = 0; + goto redo_char; + default: + tok->err = json_tokener_error_parse_unexpected; + goto out; + } + break; + + case json_tokener_state_finish: + if(tok->depth == 0) goto out; + obj = json_object_get(current); + json_tokener_reset_level(tok, tok->depth); + tok->depth--; + goto redo_char; + + case json_tokener_state_inf: /* aka starts with 'i' */ + { + int size; + int size_inf; + int is_negative = 0; + + printbuf_memappend_fast(tok->pb, &c, 1); + size = json_min(tok->st_pos+1, json_null_str_len); + size_inf = json_min(tok->st_pos+1, json_inf_str_len); + char *infbuf = tok->pb->buf; + if (*infbuf == '-') + { + infbuf++; + is_negative = 1; + } + if ((!(tok->flags & JSON_TOKENER_STRICT) && + strncasecmp(json_inf_str, infbuf, size_inf) == 0) || + (strncmp(json_inf_str, infbuf, size_inf) == 0) + ) + { + if (tok->st_pos == json_inf_str_len) + { + current = json_object_new_double(is_negative ? -INFINITY : INFINITY); + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + } else { + tok->err = json_tokener_error_parse_unexpected; + goto out; + } + tok->st_pos++; + } + break; + case json_tokener_state_null: /* aka starts with 'n' */ + { + int size; + int size_nan; + printbuf_memappend_fast(tok->pb, &c, 1); + size = json_min(tok->st_pos+1, json_null_str_len); + size_nan = json_min(tok->st_pos+1, json_nan_str_len); + if((!(tok->flags & JSON_TOKENER_STRICT) && + strncasecmp(json_null_str, tok->pb->buf, size) == 0) + || (strncmp(json_null_str, tok->pb->buf, size) == 0) + ) { + if (tok->st_pos == json_null_str_len) { + current = NULL; + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + } + else if ((!(tok->flags & JSON_TOKENER_STRICT) && + strncasecmp(json_nan_str, tok->pb->buf, size_nan) == 0) || + (strncmp(json_nan_str, tok->pb->buf, size_nan) == 0) + ) + { + if (tok->st_pos == json_nan_str_len) + { + current = json_object_new_double(NAN); + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + } else { + tok->err = json_tokener_error_parse_null; + goto out; + } + tok->st_pos++; + } + break; + + case json_tokener_state_comment_start: + if(c == '*') { + state = json_tokener_state_comment; + } else if(c == '/') { + state = json_tokener_state_comment_eol; + } else { + tok->err = json_tokener_error_parse_comment; + goto out; + } + printbuf_memappend_fast(tok->pb, &c, 1); + break; + + case json_tokener_state_comment: + { + /* Advance until we change state */ + const char *case_start = str; + while(c != '*') { + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + goto out; + } + } + printbuf_memappend_fast(tok->pb, case_start, 1+str-case_start); + state = json_tokener_state_comment_end; + } + break; + + case json_tokener_state_comment_eol: + { + /* Advance until we change state */ + const char *case_start = str; + while(c != '\n') { + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + goto out; + } + } + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + MC_DEBUG("json_tokener_comment: %s\n", tok->pb->buf); + state = json_tokener_state_eatws; + } + break; + + case json_tokener_state_comment_end: + printbuf_memappend_fast(tok->pb, &c, 1); + if(c == '/') { + MC_DEBUG("json_tokener_comment: %s\n", tok->pb->buf); + state = json_tokener_state_eatws; + } else { + state = json_tokener_state_comment; + } + break; + + case json_tokener_state_string: + { + /* Advance until we change state */ + const char *case_start = str; + while(1) { + if(c == tok->quote_char) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + current = json_object_new_string_len(tok->pb->buf, tok->pb->bpos); + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + break; + } else if(c == '\\') { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + saved_state = json_tokener_state_string; + state = json_tokener_state_string_escape; + break; + } + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + goto out; + } + } + } + break; + + case json_tokener_state_string_escape: + switch(c) { + case '"': + case '\\': + case '/': + printbuf_memappend_fast(tok->pb, &c, 1); + state = saved_state; + break; + case 'b': + case 'n': + case 'r': + case 't': + case 'f': + if(c == 'b') printbuf_memappend_fast(tok->pb, "\b", 1); + else if(c == 'n') printbuf_memappend_fast(tok->pb, "\n", 1); + else if(c == 'r') printbuf_memappend_fast(tok->pb, "\r", 1); + else if(c == 't') printbuf_memappend_fast(tok->pb, "\t", 1); + else if(c == 'f') printbuf_memappend_fast(tok->pb, "\f", 1); + state = saved_state; + break; + case 'u': + tok->ucs_char = 0; + tok->st_pos = 0; + state = json_tokener_state_escape_unicode; + break; + default: + tok->err = json_tokener_error_parse_string; + goto out; + } + break; + + case json_tokener_state_escape_unicode: + { + unsigned int got_hi_surrogate = 0; + + /* Handle a 4-byte sequence, or two sequences if a surrogate pair */ + while(1) { + if(strchr(json_hex_chars, c)) { + tok->ucs_char += ((unsigned int)hexdigit(c) << ((3-tok->st_pos++)*4)); + if(tok->st_pos == 4) { + unsigned char unescaped_utf[4]; + + if (got_hi_surrogate) { + if (IS_LOW_SURROGATE(tok->ucs_char)) { + /* Recalculate the ucs_char, then fall thru to process normally */ + tok->ucs_char = DECODE_SURROGATE_PAIR(got_hi_surrogate, tok->ucs_char); + } else { + /* Hi surrogate was not followed by a low surrogate */ + /* Replace the hi and process the rest normally */ + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + } + got_hi_surrogate = 0; + } + + if (tok->ucs_char < 0x80) { + unescaped_utf[0] = tok->ucs_char; + printbuf_memappend_fast(tok->pb, (char*)unescaped_utf, 1); + } else if (tok->ucs_char < 0x800) { + unescaped_utf[0] = 0xc0 | (tok->ucs_char >> 6); + unescaped_utf[1] = 0x80 | (tok->ucs_char & 0x3f); + printbuf_memappend_fast(tok->pb, (char*)unescaped_utf, 2); + } else if (IS_HIGH_SURROGATE(tok->ucs_char)) { + /* Got a high surrogate. Remember it and look for the + * the beginning of another sequence, which should be the + * low surrogate. + */ + got_hi_surrogate = tok->ucs_char; + /* Not at end, and the next two chars should be "\u" */ + if ((tok->char_offset+1 != len) && + (tok->char_offset+2 != len) && + (str[1] == '\\') && + (str[2] == 'u')) + { + /* Advance through the 16 bit surrogate, and move on to the + * next sequence. The next step is to process the following + * characters. + */ + if( !ADVANCE_CHAR(str, tok) || !ADVANCE_CHAR(str, tok) ) { + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + } + /* Advance to the first char of the next sequence and + * continue processing with the next sequence. + */ + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + goto out; + } + tok->ucs_char = 0; + tok->st_pos = 0; + continue; /* other json_tokener_state_escape_unicode */ + } else { + /* Got a high surrogate without another sequence following + * it. Put a replacement char in for the hi surrogate + * and pretend we finished. + */ + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + } + } else if (IS_LOW_SURROGATE(tok->ucs_char)) { + /* Got a low surrogate not preceded by a high */ + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + } else if (tok->ucs_char < 0x10000) { + unescaped_utf[0] = 0xe0 | (tok->ucs_char >> 12); + unescaped_utf[1] = 0x80 | ((tok->ucs_char >> 6) & 0x3f); + unescaped_utf[2] = 0x80 | (tok->ucs_char & 0x3f); + printbuf_memappend_fast(tok->pb, (char*)unescaped_utf, 3); + } else if (tok->ucs_char < 0x110000) { + unescaped_utf[0] = 0xf0 | ((tok->ucs_char >> 18) & 0x07); + unescaped_utf[1] = 0x80 | ((tok->ucs_char >> 12) & 0x3f); + unescaped_utf[2] = 0x80 | ((tok->ucs_char >> 6) & 0x3f); + unescaped_utf[3] = 0x80 | (tok->ucs_char & 0x3f); + printbuf_memappend_fast(tok->pb, (char*)unescaped_utf, 4); + } else { + /* Don't know what we got--insert the replacement char */ + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + } + state = saved_state; + break; + } + } else { + tok->err = json_tokener_error_parse_string; + goto out; + } + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + if (got_hi_surrogate) /* Clean up any pending chars */ + printbuf_memappend_fast(tok->pb, (char*)utf8_replacement_char, 3); + goto out; + } + } + } + break; + + case json_tokener_state_boolean: + { + int size1, size2; + printbuf_memappend_fast(tok->pb, &c, 1); + size1 = json_min(tok->st_pos+1, json_true_str_len); + size2 = json_min(tok->st_pos+1, json_false_str_len); + if((!(tok->flags & JSON_TOKENER_STRICT) && + strncasecmp(json_true_str, tok->pb->buf, size1) == 0) + || (strncmp(json_true_str, tok->pb->buf, size1) == 0) + ) { + if(tok->st_pos == json_true_str_len) { + current = json_object_new_boolean(1); + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + } else if((!(tok->flags & JSON_TOKENER_STRICT) && + strncasecmp(json_false_str, tok->pb->buf, size2) == 0) + || (strncmp(json_false_str, tok->pb->buf, size2) == 0)) { + if(tok->st_pos == json_false_str_len) { + current = json_object_new_boolean(0); + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + } else { + tok->err = json_tokener_error_parse_boolean; + goto out; + } + tok->st_pos++; + } + break; + + case json_tokener_state_number: + { + /* Advance until we change state */ + const char *case_start = str; + int case_len=0; + while(c && strchr(json_number_chars, c)) { + ++case_len; + if(c == '.' || c == 'e' || c == 'E') + tok->is_double = 1; + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, case_start, case_len); + goto out; + } + } + if (case_len>0) + printbuf_memappend_fast(tok->pb, case_start, case_len); + + // Check for -Infinity + if (tok->pb->buf[0] == '-' && case_len == 1 && + (c == 'i' || c == 'I')) + { + state = json_tokener_state_inf; + goto redo_char; + } + } + { + int64_t num64; + double numd; + if (!tok->is_double && json_parse_int64(tok->pb->buf, &num64) == 0) { + if (num64 && tok->pb->buf[0]=='0' && (tok->flags & JSON_TOKENER_STRICT)) { + /* in strict mode, number must not start with 0 */ + tok->err = json_tokener_error_parse_number; + goto out; + } + current = json_object_new_int64(num64); + } + else if(tok->is_double && json_parse_double(tok->pb->buf, &numd) == 0) + { + current = json_object_new_double_s(numd, tok->pb->buf); + } else { + tok->err = json_tokener_error_parse_number; + goto out; + } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + goto redo_char; + } + break; + + case json_tokener_state_array_after_sep: + case json_tokener_state_array: + if(c == ']') { + if (state == json_tokener_state_array_after_sep && + (tok->flags & JSON_TOKENER_STRICT)) + { + tok->err = json_tokener_error_parse_unexpected; + goto out; + } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + } else { + if(tok->depth >= tok->max_depth-1) { + tok->err = json_tokener_error_depth; + goto out; + } + state = json_tokener_state_array_add; + tok->depth++; + json_tokener_reset_level(tok, tok->depth); + goto redo_char; + } + break; + + case json_tokener_state_array_add: + json_object_array_add(current, obj); + saved_state = json_tokener_state_array_sep; + state = json_tokener_state_eatws; + goto redo_char; + + case json_tokener_state_array_sep: + if(c == ']') { + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + } else if(c == ',') { + saved_state = json_tokener_state_array_after_sep; + state = json_tokener_state_eatws; + } else { + tok->err = json_tokener_error_parse_array; + goto out; + } + break; + + case json_tokener_state_object_field_start: + case json_tokener_state_object_field_start_after_sep: + if(c == '}') { + if (state == json_tokener_state_object_field_start_after_sep && + (tok->flags & JSON_TOKENER_STRICT)) + { + tok->err = json_tokener_error_parse_unexpected; + goto out; + } + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + } else if (c == '"' || c == '\'') { + tok->quote_char = c; + printbuf_reset(tok->pb); + state = json_tokener_state_object_field; + } else { + tok->err = json_tokener_error_parse_object_key_name; + goto out; + } + break; + + case json_tokener_state_object_field: + { + /* Advance until we change state */ + const char *case_start = str; + while(1) { + if(c == tok->quote_char) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + obj_field_name = strdup(tok->pb->buf); + saved_state = json_tokener_state_object_field_end; + state = json_tokener_state_eatws; + break; + } else if(c == '\\') { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + saved_state = json_tokener_state_object_field; + state = json_tokener_state_string_escape; + break; + } + if (!ADVANCE_CHAR(str, tok) || !PEEK_CHAR(c, tok)) { + printbuf_memappend_fast(tok->pb, case_start, str-case_start); + goto out; + } + } + } + break; + + case json_tokener_state_object_field_end: + if(c == ':') { + saved_state = json_tokener_state_object_value; + state = json_tokener_state_eatws; + } else { + tok->err = json_tokener_error_parse_object_key_sep; + goto out; + } + break; + + case json_tokener_state_object_value: + if(tok->depth >= tok->max_depth-1) { + tok->err = json_tokener_error_depth; + goto out; + } + state = json_tokener_state_object_value_add; + tok->depth++; + json_tokener_reset_level(tok, tok->depth); + goto redo_char; + + case json_tokener_state_object_value_add: + json_object_object_add(current, obj_field_name, obj); + free(obj_field_name); + obj_field_name = NULL; + saved_state = json_tokener_state_object_sep; + state = json_tokener_state_eatws; + goto redo_char; + + case json_tokener_state_object_sep: + if(c == '}') { + saved_state = json_tokener_state_finish; + state = json_tokener_state_eatws; + } else if(c == ',') { + saved_state = json_tokener_state_object_field_start_after_sep; + state = json_tokener_state_eatws; + } else { + tok->err = json_tokener_error_parse_object_value_sep; + goto out; + } + break; + + } + if (!ADVANCE_CHAR(str, tok)) + goto out; + } /* while(POP_CHAR) */ + + out: + if (c && + (state == json_tokener_state_finish) && + (tok->depth == 0) && + (tok->flags & JSON_TOKENER_STRICT)) { + /* unexpected char after JSON data */ + tok->err = json_tokener_error_parse_unexpected; + } + if (!c) { /* We hit an eof char (0) */ + if(state != json_tokener_state_finish && + saved_state != json_tokener_state_finish) + tok->err = json_tokener_error_parse_eof; + } + +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, oldlocale); + if (oldlocale) free(oldlocale); +#endif + + if (tok->err == json_tokener_success) + { + json_object *ret = json_object_get(current); + int ii; + + /* Partially reset, so we parse additional objects on subsequent calls. */ + for(ii = tok->depth; ii >= 0; ii--) + json_tokener_reset_level(tok, ii); + return ret; + } + + MC_DEBUG("json_tokener_parse_ex: error %s at offset %d\n", + json_tokener_errors[tok->err], tok->char_offset); + return NULL; +} + +void json_tokener_set_flags(struct json_tokener *tok, int flags) +{ + tok->flags = flags; +} diff --git a/plugins/OnlineChecks/json-c/json_tokener.h b/plugins/OnlineChecks/json-c/json_tokener.h new file mode 100644 index 0000000..a72d2bd --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_tokener.h @@ -0,0 +1,208 @@ +/* + * $Id: json_tokener.h,v 1.10 2006/07/25 03:24:50 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _json_tokener_h_ +#define _json_tokener_h_ + +#include +#include "json_object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum json_tokener_error { + json_tokener_success, + json_tokener_continue, + json_tokener_error_depth, + json_tokener_error_parse_eof, + json_tokener_error_parse_unexpected, + json_tokener_error_parse_null, + json_tokener_error_parse_boolean, + json_tokener_error_parse_number, + json_tokener_error_parse_array, + json_tokener_error_parse_object_key_name, + json_tokener_error_parse_object_key_sep, + json_tokener_error_parse_object_value_sep, + json_tokener_error_parse_string, + json_tokener_error_parse_comment, + json_tokener_error_size +}; + +enum json_tokener_state { + json_tokener_state_eatws, + json_tokener_state_start, + json_tokener_state_finish, + json_tokener_state_null, + json_tokener_state_comment_start, + json_tokener_state_comment, + json_tokener_state_comment_eol, + json_tokener_state_comment_end, + json_tokener_state_string, + json_tokener_state_string_escape, + json_tokener_state_escape_unicode, + json_tokener_state_boolean, + json_tokener_state_number, + json_tokener_state_array, + json_tokener_state_array_add, + json_tokener_state_array_sep, + json_tokener_state_object_field_start, + json_tokener_state_object_field, + json_tokener_state_object_field_end, + json_tokener_state_object_value, + json_tokener_state_object_value_add, + json_tokener_state_object_sep, + json_tokener_state_array_after_sep, + json_tokener_state_object_field_start_after_sep, + json_tokener_state_inf +}; + +struct json_tokener_srec +{ + enum json_tokener_state state, saved_state; + struct json_object *obj; + struct json_object *current; + char *obj_field_name; +}; + +#define JSON_TOKENER_DEFAULT_DEPTH 32 + +struct json_tokener +{ + char *str; + struct printbuf *pb; + int max_depth, depth, is_double, st_pos, char_offset; + enum json_tokener_error err; + unsigned int ucs_char; + char quote_char; + struct json_tokener_srec *stack; + int flags; +}; + +/** + * Be strict when parsing JSON input. Use caution with + * this flag as what is considered valid may become more + * restrictive from one release to the next, causing your + * code to fail on previously working input. + * + * This flag is not set by default. + * + * @see json_tokener_set_flags() + */ +#define JSON_TOKENER_STRICT 0x01 + +/** + * Given an error previously returned by json_tokener_get_error(), + * return a human readable description of the error. + * + * @return a generic error message is returned if an invalid error value is provided. + */ +const char *json_tokener_error_desc(enum json_tokener_error jerr); + +/** + * Retrieve the error caused by the last call to json_tokener_parse_ex(), + * or json_tokener_success if there is no error. + * + * When parsing a JSON string in pieces, if the tokener is in the middle + * of parsing this will return json_tokener_continue. + * + * See also json_tokener_error_desc(). + */ +enum json_tokener_error json_tokener_get_error(struct json_tokener *tok); + +extern struct json_tokener* json_tokener_new(void); +extern struct json_tokener* json_tokener_new_ex(int depth); +extern void json_tokener_free(struct json_tokener *tok); +extern void json_tokener_reset(struct json_tokener *tok); +extern struct json_object* json_tokener_parse(const char *str); +extern struct json_object* json_tokener_parse_verbose(const char *str, enum json_tokener_error *error); + +/** + * Set flags that control how parsing will be done. + */ +extern void json_tokener_set_flags(struct json_tokener *tok, int flags); + +/** + * Parse a string and return a non-NULL json_object if a valid JSON value + * is found. The string does not need to be a JSON object or array; + * it can also be a string, number or boolean value. + * + * A partial JSON string can be parsed. If the parsing is incomplete, + * NULL will be returned and json_tokener_get_error() will be return + * json_tokener_continue. + * json_tokener_parse_ex() can then be called with additional bytes in str + * to continue the parsing. + * + * If json_tokener_parse_ex() returns NULL and the error anything other than + * json_tokener_continue, a fatal error has occurred and parsing must be + * halted. Then tok object must not be re-used until json_tokener_reset() is + * called. + * + * When a valid JSON value is parsed, a non-NULL json_object will be + * returned. Also, json_tokener_get_error() will return json_tokener_success. + * Be sure to check the type with json_object_is_type() or + * json_object_get_type() before using the object. + * + * @b XXX this shouldn't use internal fields: + * Trailing characters after the parsed value do not automatically cause an + * error. It is up to the caller to decide whether to treat this as an + * error or to handle the additional characters, perhaps by parsing another + * json value starting from that point. + * + * Extra characters can be detected by comparing the tok->char_offset against + * the length of the last len parameter passed in. + * + * The tokener does \b not maintain an internal buffer so the caller is + * responsible for calling json_tokener_parse_ex with an appropriate str + * parameter starting with the extra characters. + * + * This interface is presently not 64-bit clean due to the int len argument + * so the function limits the maximum string size to INT32_MAX (2GB). + * If the function is called with len == -1 then strlen is called to check + * the string length is less than INT32_MAX (2GB) + * + * Example: + * @code +json_object *jobj = NULL; +const char *mystring = NULL; +int stringlen = 0; +enum json_tokener_error jerr; +do { + mystring = ... // get JSON string, e.g. read from file, etc... + stringlen = strlen(mystring); + jobj = json_tokener_parse_ex(tok, mystring, stringlen); +} while ((jerr = json_tokener_get_error(tok)) == json_tokener_continue); +if (jerr != json_tokener_success) +{ + fprintf(stderr, "Error: %s\n", json_tokener_error_desc(jerr)); + // Handle errors, as appropriate for your application. +} +if (tok->char_offset < stringlen) // XXX shouldn't access internal fields +{ + // Handle extra characters after parsed object as desired. + // e.g. issue an error, parse another object from that point, etc... +} +// Success, use jobj here. + +@endcode + * + * @param tok a json_tokener previously allocated with json_tokener_new() + * @param str an string with any valid JSON expression, or portion of. This does not need to be null terminated. + * @param len the length of str + */ +extern struct json_object* json_tokener_parse_ex(struct json_tokener *tok, + const char *str, int len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/json_util.c b/plugins/OnlineChecks/json-c/json_util.c new file mode 100644 index 0000000..fda5d1e --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_util.c @@ -0,0 +1,301 @@ +/* + * $Id: json_util.c,v 1.4 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include "config.h" +#undef realloc + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_TYPES_H +#include +#endif /* HAVE_SYS_TYPES_H */ + +#ifdef HAVE_SYS_STAT_H +#include +#endif /* HAVE_SYS_STAT_H */ + +#ifdef HAVE_FCNTL_H +#include +#endif /* HAVE_FCNTL_H */ + +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include +# include +#endif /* defined(_WIN32) */ + +#if !defined(HAVE_OPEN) && defined(_WIN32) +# define open _open +#endif + +#if !defined(HAVE_SNPRINTF) && defined(_MSC_VER) + /* MSC has the version as _snprintf */ +# define snprintf _snprintf +#elif !defined(HAVE_SNPRINTF) +# error You do not have snprintf on your system. +#endif /* HAVE_SNPRINTF */ + +#include "bits.h" +#include "debug.h" +#include "printbuf.h" +#include "json_inttypes.h" +#include "json_object.h" +#include "json_tokener.h" +#include "json_util.h" + +static int sscanf_is_broken = 0; +static int sscanf_is_broken_testdone = 0; +static void sscanf_is_broken_test(void); + +struct json_object* json_object_from_file(const char *filename) +{ + //struct printbuf *pb; + //struct json_object *obj; + //char buf[JSON_FILE_BUF_SIZE]; + //int fd, ret; + + //if((fd = open(filename, O_RDONLY)) < 0) { + // MC_ERROR("json_object_from_file: error opening file %s: %s\n", + // filename, strerror(errno)); + // return NULL; + //} + //if(!(pb = printbuf_new())) { + // close(fd); + // MC_ERROR("json_object_from_file: printbuf_new failed\n"); + // return NULL; + //} + //while((ret = read(fd, buf, JSON_FILE_BUF_SIZE)) > 0) { + // printbuf_memappend(pb, buf, ret); + //} + //close(fd); + //if(ret < 0) { + // MC_ERROR("json_object_from_file: error reading file %s: %s\n", + // filename, strerror(errno)); + // printbuf_free(pb); + // return NULL; + //} + //obj = json_tokener_parse(pb->buf); + //printbuf_free(pb); + return NULL;//obj; +} + +/* extended "format and write to file" function */ + +int json_object_to_file_ext(const char *filename, struct json_object *obj, int flags) +{ + //const char *json_str; + //int fd, ret; + //unsigned int wpos, wsize; + + //if(!obj) { + // MC_ERROR("json_object_to_file: object is null\n"); + // return -1; + //} + + //if((fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) { + // MC_ERROR("json_object_to_file: error opening file %s: %s\n", + // filename, strerror(errno)); + // return -1; + //} + + //if(!(json_str = json_object_to_json_string_ext(obj,flags))) { + // close(fd); + // return -1; + //} + + //wsize = (unsigned int)(strlen(json_str) & UINT_MAX); /* CAW: probably unnecessary, but the most 64bit safe */ + //wpos = 0; + //while(wpos < wsize) { + // if((ret = write(fd, json_str + wpos, wsize-wpos)) < 0) { + // close(fd); + // MC_ERROR("json_object_to_file: error writing file %s: %s\n", + // filename, strerror(errno)); + // return -1; + // } + + // /* because of the above check for ret < 0, we can safely cast and add */ + // wpos += (unsigned int)ret; + //} + + //close(fd); + return 0; +} + +// backwards compatible "format and write to file" function + +int json_object_to_file(const char *filename, struct json_object *obj) +{ + return json_object_to_file_ext(filename, obj, JSON_C_TO_STRING_PLAIN); +} + +int json_parse_double(const char *buf, double *retval) +{ + return (sscanf_s(buf, "%lf", retval)==1 ? 0 : 1); +} + +/* + * Not all implementations of sscanf actually work properly. + * Check whether the one we're currently using does, and if + * it's broken, enable the workaround code. + */ +static void sscanf_is_broken_test() +{ + int64_t num64; + int ret_errno, is_int64_min, ret_errno2, is_int64_max; + + sscanf_s(" -01234567890123456789012345", "%" SCNd64, &num64); + ret_errno = errno; + is_int64_min = (num64 == INT64_MIN); + + sscanf_s(" 01234567890123456789012345", "%" SCNd64, &num64); + ret_errno2 = errno; + is_int64_max = (num64 == INT64_MAX); + + if (ret_errno != ERANGE || !is_int64_min || + ret_errno2 != ERANGE || !is_int64_max) + { + MC_DEBUG("sscanf_is_broken_test failed, enabling workaround code\n"); + sscanf_is_broken = 1; + } +} + +int json_parse_int64(const char *buf, int64_t *retval) +{ + int64_t num64; + const char *buf_sig_digits; + int orig_has_neg; + int saved_errno; + + if (!sscanf_is_broken_testdone) + { + sscanf_is_broken_test(); + sscanf_is_broken_testdone = 1; + } + + // Skip leading spaces + while (isspace((int)*buf) && *buf) + buf++; + + errno = 0; // sscanf won't always set errno, so initialize + + if (sscanf_s(buf, "%" SCNd64, &num64) != 1) + { + MC_DEBUG("Failed to parse, sscanf != 1\n"); + return 1; + } + + saved_errno = errno; + buf_sig_digits = buf; + orig_has_neg = 0; + if (*buf_sig_digits == '-') + { + buf_sig_digits++; + orig_has_neg = 1; + } + + // Not all sscanf implementations actually work + if (sscanf_is_broken && saved_errno != ERANGE) + { + char buf_cmp[100]; + char *buf_cmp_start = buf_cmp; + int recheck_has_neg = 0; + int buf_cmp_len; + + // Skip leading zeros, but keep at least one digit + while (buf_sig_digits[0] == '0' && buf_sig_digits[1] != '\0') + buf_sig_digits++; + if (num64 == 0) // assume all sscanf impl's will parse -0 to 0 + orig_has_neg = 0; // "-0" is the same as just plain "0" + + _snprintf_s(buf_cmp_start, sizeof(buf_cmp), _TRUNCATE, "%" PRId64, num64); + if (*buf_cmp_start == '-') + { + recheck_has_neg = 1; + buf_cmp_start++; + } + // No need to skip leading spaces or zeros here. + + buf_cmp_len = (int)strlen(buf_cmp_start); + /** + * If the sign is different, or + * some of the digits are different, or + * there is another digit present in the original string + * then we have NOT successfully parsed the value. + */ + if (orig_has_neg != recheck_has_neg || + strncmp(buf_sig_digits, buf_cmp_start, strlen(buf_cmp_start)) != 0 || + ((int)strlen(buf_sig_digits) != buf_cmp_len && + isdigit((int)buf_sig_digits[buf_cmp_len]) + ) + ) + { + saved_errno = ERANGE; + } + } + + // Not all sscanf impl's set the value properly when out of range. + // Always do this, even for properly functioning implementations, + // since it shouldn't slow things down much. + if (saved_errno == ERANGE) + { + if (orig_has_neg) + num64 = INT64_MIN; + else + num64 = INT64_MAX; + } + *retval = num64; + return 0; +} + +#ifndef HAVE_REALLOC +void* rpl_realloc(void* p, size_t n) +{ + if (n == 0) + n = 1; + if (p == 0) + return malloc(n); + return realloc(p, n); +} +#endif + +#define NELEM(a) (sizeof(a) / sizeof(a[0])) +static const char* json_type_name[] = { + /* If you change this, be sure to update the enum json_type definition too */ + "null", + "boolean", + "double", + "int", + "object", + "array", + "string", +}; + +const char *json_type_to_name(enum json_type o_type) +{ + int o_type_int = (int)o_type; + if (o_type_int < 0 || o_type_int >= (int)NELEM(json_type_name)) + { + MC_ERROR("json_type_to_name: type %d is out of range [0,%d]\n", o_type, NELEM(json_type_name)); + return NULL; + } + return json_type_name[o_type]; +} + diff --git a/plugins/OnlineChecks/json-c/json_util.h b/plugins/OnlineChecks/json-c/json_util.h new file mode 100644 index 0000000..1005e58 --- /dev/null +++ b/plugins/OnlineChecks/json-c/json_util.h @@ -0,0 +1,41 @@ +/* + * $Id: json_util.h,v 1.4 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _json_util_h_ +#define _json_util_h_ + +#include "json_object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define JSON_FILE_BUF_SIZE 4096 + +/* utility functions */ +extern struct json_object* json_object_from_file(const char *filename); +extern int json_object_to_file(const char *filename, struct json_object *obj); +extern int json_object_to_file_ext(const char *filename, struct json_object *obj, int flags); +extern int json_parse_int64(const char *buf, int64_t *retval); +extern int json_parse_double(const char *buf, double *retval); + + +/** + * Return a string describing the type of the object. + * e.g. "int", or "object", etc... + */ +extern const char *json_type_to_name(enum json_type o_type); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/libjson.c b/plugins/OnlineChecks/json-c/libjson.c new file mode 100644 index 0000000..5284fd0 --- /dev/null +++ b/plugins/OnlineChecks/json-c/libjson.c @@ -0,0 +1,26 @@ + +/* dummy source file for compatibility purposes */ + +#if defined(HAVE_CDEFS_H) +#include +#endif + +#ifndef __warn_references + +#if defined(__GNUC__) && defined (HAS_GNU_WARNING_LONG) + +#define __warn_references(sym,msg) \ + __asm__(".section .gnu" #sym ",\n\t.ascii \"" msg "\"\n\t.text"); + +#else +#define __warn_references(sym,msg) /* nothing */ +#endif + +#endif + +#include "json_object.h" + +__warn_references(json_object_get, "Warning: please link against libjson-c instead of libjson"); + +/* __asm__(".section .gnu.warning." __STRING(sym) \ + " ; .ascii \"" msg "\" ; .text") */ diff --git a/plugins/OnlineChecks/json-c/linkhash.c b/plugins/OnlineChecks/json-c/linkhash.c new file mode 100644 index 0000000..50de485 --- /dev/null +++ b/plugins/OnlineChecks/json-c/linkhash.c @@ -0,0 +1,604 @@ +/* + * $Id: linkhash.c,v 1.4 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_ENDIAN_H +# include /* attempt to define endianness */ +#endif + +#include "random_seed.h" +#include "linkhash.h" + +void lh_abort(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + exit(1); +} + +unsigned long lh_ptr_hash(const void *k) +{ + /* CAW: refactored to be 64bit nice */ + return (unsigned long)((((ptrdiff_t)k * LH_PRIME) >> 4) & ULONG_MAX); +} + +int lh_ptr_equal(const void *k1, const void *k2) +{ + return (k1 == k2); +} + +/* + * hashlittle from lookup3.c, by Bob Jenkins, May 2006, Public Domain. + * http://burtleburtle.net/bob/c/lookup3.c + * minor modifications to make functions static so no symbols are exported + * minor mofifications to compile with -Werror + */ + +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +------------------------------------------------------------------------------- +*/ + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#else +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 0 +#endif + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + const uint8_t *k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} + +unsigned long lh_char_hash(const void *k) +{ + static volatile int random_seed = -1; + + if (random_seed == -1) { + int seed; + /* we can't use -1 as it is the unitialized sentinel */ + while ((seed = json_c_get_random_seed()) == -1); +#if defined __GNUC__ + __sync_val_compare_and_swap(&random_seed, -1, seed); +#elif defined _MSC_VER + InterlockedCompareExchange(&random_seed, seed, -1); +#else +#warning "racy random seed initializtion if used by multiple threads" + random_seed = seed; /* potentially racy */ +#endif + } + + return hashlittle((const char*)k, strlen((const char*)k), random_seed); +} + +int lh_char_equal(const void *k1, const void *k2) +{ + return (strcmp((const char*)k1, (const char*)k2) == 0); +} + +struct lh_table* lh_table_new(int size, const char *name, + lh_entry_free_fn *free_fn, + lh_hash_fn *hash_fn, + lh_equal_fn *equal_fn) +{ + int i; + struct lh_table *t; + + t = (struct lh_table*)calloc(1, sizeof(struct lh_table)); + if(!t) lh_abort("lh_table_new: calloc failed\n"); + t->count = 0; + t->size = size; + t->name = name; + t->table = (struct lh_entry*)calloc(size, sizeof(struct lh_entry)); + if(!t->table) lh_abort("lh_table_new: calloc failed\n"); + t->free_fn = free_fn; + t->hash_fn = hash_fn; + t->equal_fn = equal_fn; + for(i = 0; i < size; i++) t->table[i].k = LH_EMPTY; + return t; +} + +struct lh_table* lh_kchar_table_new(int size, const char *name, + lh_entry_free_fn *free_fn) +{ + return lh_table_new(size, name, free_fn, lh_char_hash, lh_char_equal); +} + +struct lh_table* lh_kptr_table_new(int size, const char *name, + lh_entry_free_fn *free_fn) +{ + return lh_table_new(size, name, free_fn, lh_ptr_hash, lh_ptr_equal); +} + +void lh_table_resize(struct lh_table *t, int new_size) +{ + struct lh_table *new_t; + struct lh_entry *ent; + + new_t = lh_table_new(new_size, t->name, NULL, t->hash_fn, t->equal_fn); + ent = t->head; + while(ent) { + lh_table_insert(new_t, ent->k, ent->v); + ent = ent->next; + } + free(t->table); + t->table = new_t->table; + t->size = new_size; + t->head = new_t->head; + t->tail = new_t->tail; + t->resizes++; + free(new_t); +} + +void lh_table_free(struct lh_table *t) +{ + struct lh_entry *c; + for(c = t->head; c != NULL; c = c->next) { + if(t->free_fn) { + t->free_fn(c); + } + } + free(t->table); + free(t); +} + + +int lh_table_insert(struct lh_table *t, void *k, const void *v) +{ + unsigned long h, n; + + t->inserts++; + if(t->count >= t->size * LH_LOAD_FACTOR) lh_table_resize(t, t->size * 2); + + h = t->hash_fn(k); + n = h % t->size; + + while( 1 ) { + if(t->table[n].k == LH_EMPTY || t->table[n].k == LH_FREED) break; + t->collisions++; + if ((int)++n == t->size) n = 0; + } + + t->table[n].k = k; + t->table[n].v = v; + t->count++; + + if(t->head == NULL) { + t->head = t->tail = &t->table[n]; + t->table[n].next = t->table[n].prev = NULL; + } else { + t->tail->next = &t->table[n]; + t->table[n].prev = t->tail; + t->table[n].next = NULL; + t->tail = &t->table[n]; + } + + return 0; +} + + +struct lh_entry* lh_table_lookup_entry(struct lh_table *t, const void *k) +{ + unsigned long h = t->hash_fn(k); + unsigned long n = h % t->size; + int count = 0; + + t->lookups++; + while( count < t->size ) { + if(t->table[n].k == LH_EMPTY) return NULL; + if(t->table[n].k != LH_FREED && + t->equal_fn(t->table[n].k, k)) return &t->table[n]; + if ((int)++n == t->size) n = 0; + count++; + } + return NULL; +} + + +const void* lh_table_lookup(struct lh_table *t, const void *k) +{ + void *result; + lh_table_lookup_ex(t, k, &result); + return result; +} + +json_bool lh_table_lookup_ex(struct lh_table* t, const void* k, void **v) +{ + struct lh_entry *e = lh_table_lookup_entry(t, k); + if (e != NULL) { + if (v != NULL) *v = (void *)e->v; + return TRUE; /* key found */ + } + if (v != NULL) *v = NULL; + return FALSE; /* key not found */ +} + +int lh_table_delete_entry(struct lh_table *t, struct lh_entry *e) +{ + ptrdiff_t n = (ptrdiff_t)(e - t->table); /* CAW: fixed to be 64bit nice, still need the crazy negative case... */ + + /* CAW: this is bad, really bad, maybe stack goes other direction on this machine... */ + if(n < 0) { return -2; } + + if(t->table[n].k == LH_EMPTY || t->table[n].k == LH_FREED) return -1; + t->count--; + if(t->free_fn) t->free_fn(e); + t->table[n].v = NULL; + t->table[n].k = LH_FREED; + if(t->tail == &t->table[n] && t->head == &t->table[n]) { + t->head = t->tail = NULL; + } else if (t->head == &t->table[n]) { + t->head->next->prev = NULL; + t->head = t->head->next; + } else if (t->tail == &t->table[n]) { + t->tail->prev->next = NULL; + t->tail = t->tail->prev; + } else { + t->table[n].prev->next = t->table[n].next; + t->table[n].next->prev = t->table[n].prev; + } + t->table[n].next = t->table[n].prev = NULL; + return 0; +} + + +int lh_table_delete(struct lh_table *t, const void *k) +{ + struct lh_entry *e = lh_table_lookup_entry(t, k); + if(!e) return -1; + return lh_table_delete_entry(t, e); +} + +int lh_table_length(struct lh_table *t) +{ + return t->count; +} diff --git a/plugins/OnlineChecks/json-c/linkhash.h b/plugins/OnlineChecks/json-c/linkhash.h new file mode 100644 index 0000000..950d09f --- /dev/null +++ b/plugins/OnlineChecks/json-c/linkhash.h @@ -0,0 +1,292 @@ +/* + * $Id: linkhash.h,v 1.6 2006/01/30 23:07:57 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * Copyright (c) 2009 Hewlett-Packard Development Company, L.P. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef _linkhash_h_ +#define _linkhash_h_ + +#include "json_object.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * golden prime used in hash functions + */ +#define LH_PRIME 0x9e370001UL + +/** + * The fraction of filled hash buckets until an insert will cause the table + * to be resized. + * This can range from just above 0 up to 1.0. + */ +#define LH_LOAD_FACTOR 0.66 + +/** + * sentinel pointer value for empty slots + */ +#define LH_EMPTY (void*)-1 + +/** + * sentinel pointer value for freed slots + */ +#define LH_FREED (void*)-2 + +struct lh_entry; + +/** + * callback function prototypes + */ +typedef void (lh_entry_free_fn) (struct lh_entry *e); +/** + * callback function prototypes + */ +typedef unsigned long (lh_hash_fn) (const void *k); +/** + * callback function prototypes + */ +typedef int (lh_equal_fn) (const void *k1, const void *k2); + +/** + * An entry in the hash table + */ +struct lh_entry { + /** + * The key. + */ + void *k; + /** + * The value. + */ + const void *v; + /** + * The next entry + */ + struct lh_entry *next; + /** + * The previous entry. + */ + struct lh_entry *prev; +}; + + +/** + * The hash table structure. + */ +struct lh_table { + /** + * Size of our hash. + */ + int size; + /** + * Numbers of entries. + */ + int count; + + /** + * Number of collisions. + */ + int collisions; + + /** + * Number of resizes. + */ + int resizes; + + /** + * Number of lookups. + */ + int lookups; + + /** + * Number of inserts. + */ + int inserts; + + /** + * Number of deletes. + */ + int deletes; + + /** + * Name of the hash table. + */ + const char *name; + + /** + * The first entry. + */ + struct lh_entry *head; + + /** + * The last entry. + */ + struct lh_entry *tail; + + struct lh_entry *table; + + /** + * A pointer onto the function responsible for freeing an entry. + */ + lh_entry_free_fn *free_fn; + lh_hash_fn *hash_fn; + lh_equal_fn *equal_fn; +}; + + +/** + * Pre-defined hash and equality functions + */ +extern unsigned long lh_ptr_hash(const void *k); +extern int lh_ptr_equal(const void *k1, const void *k2); + +extern unsigned long lh_char_hash(const void *k); +extern int lh_char_equal(const void *k1, const void *k2); + + +/** + * Convenience list iterator. + */ +#define lh_foreach(table, entry) \ +for(entry = table->head; entry; entry = entry->next) + +/** + * lh_foreach_safe allows calling of deletion routine while iterating. + */ +#define lh_foreach_safe(table, entry, tmp) \ +for(entry = table->head; entry && ((tmp = entry->next) || 1); entry = tmp) + + + +/** + * Create a new linkhash table. + * @param size initial table size. The table is automatically resized + * although this incurs a performance penalty. + * @param name the table name. + * @param free_fn callback function used to free memory for entries + * when lh_table_free or lh_table_delete is called. + * If NULL is provided, then memory for keys and values + * must be freed by the caller. + * @param hash_fn function used to hash keys. 2 standard ones are defined: + * lh_ptr_hash and lh_char_hash for hashing pointer values + * and C strings respectively. + * @param equal_fn comparison function to compare keys. 2 standard ones defined: + * lh_ptr_hash and lh_char_hash for comparing pointer values + * and C strings respectively. + * @return a pointer onto the linkhash table. + */ +extern struct lh_table* lh_table_new(int size, const char *name, + lh_entry_free_fn *free_fn, + lh_hash_fn *hash_fn, + lh_equal_fn *equal_fn); + +/** + * Convenience function to create a new linkhash + * table with char keys. + * @param size initial table size. + * @param name table name. + * @param free_fn callback function used to free memory for entries. + * @return a pointer onto the linkhash table. + */ +extern struct lh_table* lh_kchar_table_new(int size, const char *name, + lh_entry_free_fn *free_fn); + + +/** + * Convenience function to create a new linkhash + * table with ptr keys. + * @param size initial table size. + * @param name table name. + * @param free_fn callback function used to free memory for entries. + * @return a pointer onto the linkhash table. + */ +extern struct lh_table* lh_kptr_table_new(int size, const char *name, + lh_entry_free_fn *free_fn); + + +/** + * Free a linkhash table. + * If a callback free function is provided then it is called for all + * entries in the table. + * @param t table to free. + */ +extern void lh_table_free(struct lh_table *t); + + +/** + * Insert a record into the table. + * @param t the table to insert into. + * @param k a pointer to the key to insert. + * @param v a pointer to the value to insert. + */ +extern int lh_table_insert(struct lh_table *t, void *k, const void *v); + + +/** + * Lookup a record into the table. + * @param t the table to lookup + * @param k a pointer to the key to lookup + * @return a pointer to the record structure of the value or NULL if it does not exist. + */ +extern struct lh_entry* lh_table_lookup_entry(struct lh_table *t, const void *k); + +/** + * Lookup a record into the table + * @param t the table to lookup + * @param k a pointer to the key to lookup + * @return a pointer to the found value or NULL if it does not exist. + * @deprecated Use lh_table_lookup_ex instead. + */ +THIS_FUNCTION_IS_DEPRECATED(extern const void* lh_table_lookup(struct lh_table *t, const void *k)); + +/** + * Lookup a record in the table + * @param t the table to lookup + * @param k a pointer to the key to lookup + * @param v a pointer to a where to store the found value (set to NULL if it doesn't exist). + * @return whether or not the key was found + */ +extern json_bool lh_table_lookup_ex(struct lh_table *t, const void *k, void **v); + +/** + * Delete a record from the table. + * If a callback free function is provided then it is called for the + * for the item being deleted. + * @param t the table to delete from. + * @param e a pointer to the entry to delete. + * @return 0 if the item was deleted. + * @return -1 if it was not found. + */ +extern int lh_table_delete_entry(struct lh_table *t, struct lh_entry *e); + + +/** + * Delete a record from the table. + * If a callback free function is provided then it is called for the + * for the item being deleted. + * @param t the table to delete from. + * @param k a pointer to the key to delete. + * @return 0 if the item was deleted. + * @return -1 if it was not found. + */ +extern int lh_table_delete(struct lh_table *t, const void *k); + +extern int lh_table_length(struct lh_table *t); + +void lh_abort(const char *msg, ...); +void lh_table_resize(struct lh_table *t, int new_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/math_compat.h b/plugins/OnlineChecks/json-c/math_compat.h new file mode 100644 index 0000000..f40b8fa --- /dev/null +++ b/plugins/OnlineChecks/json-c/math_compat.h @@ -0,0 +1,28 @@ +#ifndef __math_compat_h +#define __math_compat_h + +/* Define isnan and isinf on Windows/MSVC */ + +#ifndef HAVE_DECL_ISNAN +# ifdef HAVE_DECL__ISNAN +#include +#define isnan(x) _isnan(x) +# endif +#endif + +#ifndef HAVE_DECL_ISINF +# ifdef HAVE_DECL__FINITE +#include +#define isinf(x) (!_finite(x)) +# endif +#endif + +#ifndef HAVE_DECL_NAN +#error This platform does not have nan() +#endif + +#ifndef HAVE_DECL_INFINITY +#error This platform does not have INFINITY +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/printbuf.c b/plugins/OnlineChecks/json-c/printbuf.c new file mode 100644 index 0000000..94d41b0 --- /dev/null +++ b/plugins/OnlineChecks/json-c/printbuf.c @@ -0,0 +1,194 @@ +/* + * $Id: printbuf.c,v 1.5 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + * + * Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. + * The copyrights to the contents of this file are licensed under the MIT License + * (http://www.opensource.org/licenses/mit-license.php) + */ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_STDARG_H +# include +#else /* !HAVE_STDARG_H */ +# error Not enough var arg support! +#endif /* HAVE_STDARG_H */ + +#include "bits.h" +#include "debug.h" +#include "printbuf.h" + +static int printbuf_extend(struct printbuf *p, size_t min_size); + +struct printbuf* printbuf_new(void) +{ + struct printbuf *p; + + p = (struct printbuf*)calloc(1, sizeof(struct printbuf)); + if(!p) return NULL; + p->size = 32; + p->bpos = 0; + if(!(p->buf = (char*)malloc(p->size))) { + free(p); + return NULL; + } + return p; +} + + +/** + * Extend the buffer p so it has a size of at least min_size. + * + * If the current size is large enough, nothing is changed. + * + * Note: this does not check the available space! The caller + * is responsible for performing those calculations. + */ +static int printbuf_extend(struct printbuf *p, size_t min_size) +{ + char *t; + size_t new_size; + + if (p->size >= min_size) + return 0; + + new_size = json_max(p->size * 2, min_size + 8); +#ifdef PRINTBUF_DEBUG + MC_DEBUG("printbuf_memappend: realloc " + "bpos=%d min_size=%d old_size=%d new_size=%d\n", + p->bpos, min_size, p->size, new_size); +#endif /* PRINTBUF_DEBUG */ + if(!(t = (char*)realloc(p->buf, new_size))) + return -1; + p->size = new_size; + p->buf = t; + return 0; +} + +size_t printbuf_memappend(struct printbuf *p, const char *buf, size_t size) +{ + if (p->size <= p->bpos + size + 1) { + if (printbuf_extend(p, p->bpos + size + 1) < 0) + return -1; + } + memcpy(p->buf + p->bpos, buf, size); + p->bpos += size; + p->buf[p->bpos]= '\0'; + return size; +} + +int printbuf_memset(struct printbuf *pb, size_t offset, int charvalue, size_t len) +{ + size_t size_needed; + + if (offset == -1) + offset = pb->bpos; + size_needed = offset + len; + if (pb->size < size_needed) + { + if (printbuf_extend(pb, size_needed) < 0) + return -1; + } + + memset(pb->buf + offset, charvalue, len); + if (pb->bpos < size_needed) + pb->bpos = size_needed; + + return 0; +} + +#if !defined(HAVE_VSNPRINTF) && defined(_MSC_VER) +# define vsnprintf _vsnprintf +#elif !defined(HAVE_VSNPRINTF) /* !HAVE_VSNPRINTF */ +# error Need vsnprintf! +#endif /* !HAVE_VSNPRINTF && defined(_WIN32) */ + +#if !defined(HAVE_VASPRINTF) +/* CAW: compliant version of vasprintf */ +static int vasprintf(char **buf, const char *fmt, va_list ap) +{ +#ifndef _WIN32 + static char _T_emptybuffer = '\0'; +#endif /* !defined(_WIN32) */ + int chars; + char *b; + + if(!buf) { return -1; } + +#ifdef _WIN32 + chars = _vscprintf(fmt, ap)+1; +#else /* !defined(_WIN32) */ + /* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite + our buffer like on some 64bit sun systems.... but hey, its time to move on */ + chars = vsnprintf(&_T_emptybuffer, 0, fmt, ap)+1; + if(chars < 0) { chars *= -1; } /* CAW: old glibc versions have this problem */ +#endif /* defined(_WIN32) */ + + b = (char*)malloc(sizeof(char) * chars); + if(!b) { return -1; } + + if ((chars = vsprintf_s(b, sizeof(char), fmt, ap)) < 0) + { + free(b); + } + else + { + *buf = b; + } + + return chars; +} +#endif /* !HAVE_VASPRINTF */ + +int sprintbuf(struct printbuf *p, const char *msg, ...) +{ + va_list ap; + char *t; + int size; + char buf[128]; + + /* user stack buffer first */ + va_start(ap, msg); + size = _vsnprintf_s(buf, sizeof(buf), _TRUNCATE, msg, ap); + va_end(ap); + /* if string is greater than stack buffer, then use dynamic string + with vasprintf. Note: some implementation of vsnprintf return -1 + if output is truncated whereas some return the number of bytes that + would have been written - this code handles both cases. */ + if(size == -1 || size > 127) { + va_start(ap, msg); + if((size = vasprintf(&t, msg, ap)) < 0) { va_end(ap); return -1; } + va_end(ap); + printbuf_memappend(p, t, size); + free(t); + return size; + } else { + printbuf_memappend(p, buf, size); + return size; + } +} + +void printbuf_reset(struct printbuf *p) +{ + p->buf[0] = '\0'; + p->bpos = 0; +} + +void printbuf_free(struct printbuf *p) +{ + if(p) { + free(p->buf); + free(p); + } +} diff --git a/plugins/OnlineChecks/json-c/printbuf.h b/plugins/OnlineChecks/json-c/printbuf.h new file mode 100644 index 0000000..ed003b6 --- /dev/null +++ b/plugins/OnlineChecks/json-c/printbuf.h @@ -0,0 +1,81 @@ +/* + * $Id: printbuf.h,v 1.4 2006/01/26 02:16:28 mclark Exp $ + * + * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + * + * Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. + * The copyrights to the contents of this file are licensed under the MIT License + * (http://www.opensource.org/licenses/mit-license.php) + */ + +#ifndef _printbuf_h_ +#define _printbuf_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct printbuf { + char *buf; + size_t bpos; + size_t size; +}; + +extern struct printbuf* +printbuf_new(void); + +/* As an optimization, printbuf_memappend_fast is defined as a macro + * that handles copying data if the buffer is large enough; otherwise + * it invokes printbuf_memappend_real() which performs the heavy + * lifting of realloc()ing the buffer and copying data. + * Your code should not use printbuf_memappend directly--use + * printbuf_memappend_fast instead. + */ +extern size_t printbuf_memappend(struct printbuf *p, const char *buf, size_t size); + +__inline void printbuf_memappend_fast(struct printbuf *p, const char *bufptr, size_t bufsize) +{ + if ((p->size - p->bpos) > bufsize) + { + memcpy(p->buf + p->bpos, (bufptr), bufsize); + p->bpos += (int)bufsize; + p->buf[p->bpos] = '\0'; + } + else + { + printbuf_memappend(p, (bufptr), bufsize); + } +} + +#define printbuf_length(p) ((p)->bpos) + +/** + * Set len bytes of the buffer to charvalue, starting at offset offset. + * Similar to calling memset(x, charvalue, len); + * + * The memory allocated for the buffer is extended as necessary. + * + * If offset is -1, this starts at the end of the current data in the buffer. + */ +extern int +printbuf_memset(struct printbuf *pb, size_t offset, int charvalue, size_t len); + +extern int +sprintbuf(struct printbuf *p, const char *msg, ...); + +extern void +printbuf_reset(struct printbuf *p); + +extern void +printbuf_free(struct printbuf *p); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/json-c/random_seed.c b/plugins/OnlineChecks/json-c/random_seed.c new file mode 100644 index 0000000..ece374e --- /dev/null +++ b/plugins/OnlineChecks/json-c/random_seed.c @@ -0,0 +1,238 @@ +/* + * random_seed.c + * + * Copyright (c) 2013 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#include +#include "config.h" + +#define DEBUG_SEED(s) + + +#if defined ENABLE_RDRAND + +/* cpuid */ + +#if defined __GNUC__ && (defined __i386__ || defined __x86_64__) +#define HAS_X86_CPUID 1 + +static void do_cpuid(int regs[], int h) +{ + __asm__ __volatile__( +#if defined __x86_64__ + "pushq %%rbx;\n" +#else + "pushl %%ebx;\n" +#endif + "cpuid;\n" +#if defined __x86_64__ + "popq %%rbx;\n" +#else + "popl %%ebx;\n" +#endif + : "=a"(regs[0]), [ebx] "=r"(regs[1]), "=c"(regs[2]), "=d"(regs[3]) + : "a"(h)); +} + +#elif defined _MSC_VER + +#define HAS_X86_CPUID 1 +#define do_cpuid __cpuid + +#endif + +/* has_rdrand */ + +#if HAS_X86_CPUID + +static int has_rdrand() +{ + // CPUID.01H:ECX.RDRAND[bit 30] == 1 + int regs[4]; + do_cpuid(regs, 1); + return (regs[2] & (1 << 30)) != 0; +} + +#endif + +/* get_rdrand_seed - GCC x86 and X64 */ + +#if defined __GNUC__ && (defined __i386__ || defined __x86_64__) + +#define HAVE_RDRAND 1 + +static int get_rdrand_seed() +{ + DEBUG_SEED("get_rdrand_seed"); + int _eax; + // rdrand eax + __asm__ __volatile__("1: .byte 0x0F\n" + " .byte 0xC7\n" + " .byte 0xF0\n" + " jnc 1b;\n" + : "=a" (_eax)); + return _eax; +} + +#endif + +#if defined _MSC_VER + +#if _MSC_VER >= 1700 +#define HAVE_RDRAND 1 + +/* get_rdrand_seed - Visual Studio 2012 and above */ + +static int get_rdrand_seed() +{ + DEBUG_SEED("get_rdrand_seed"); + int r; + while (_rdrand32_step(&r) == 0); + return r; +} + +#elif defined _M_IX86 +#define HAVE_RDRAND 1 + +/* get_rdrand_seed - Visual Studio 2010 and below - x86 only */ + +static int get_rdrand_seed() +{ + DEBUG_SEED("get_rdrand_seed"); + int _eax; +retry: + // rdrand eax + __asm _emit 0x0F __asm _emit 0xC7 __asm _emit 0xF0 + __asm jnc retry + __asm mov _eax, eax + return _eax; +} + +#endif +#endif + +#endif /* defined ENABLE_RDRAND */ + + +/* has_dev_urandom */ + +#if defined (__APPLE__) || defined(__unix__) || defined(__linux__) + +#include +#include +#include +#include +#include +#include + +#define HAVE_DEV_RANDOM 1 + +static const char *dev_random_file = "/dev/urandom"; + +static int has_dev_urandom() +{ + struct stat buf; + if (stat(dev_random_file, &buf)) { + return 0; + } + return ((buf.st_mode & S_IFCHR) != 0); +} + + +/* get_dev_random_seed */ + +static int get_dev_random_seed() +{ + DEBUG_SEED("get_dev_random_seed"); + + int fd = open(dev_random_file, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "error opening %s: %s", dev_random_file, strerror(errno)); + exit(1); + } + + int r; + ssize_t nread = read(fd, &r, sizeof(r)); + if (nread != sizeof(r)) { + fprintf(stderr, "error read %s: %s", dev_random_file, strerror(errno)); + exit(1); + } + else if (nread != sizeof(r)) { + fprintf(stderr, "error short read %s", dev_random_file); + exit(1); + } + close(fd); + return r; +} + +#endif + + +/* get_cryptgenrandom_seed */ + +#ifdef _WIN32 + +#define HAVE_CRYPTGENRANDOM 1 + +#include + +static int get_cryptgenrandom_seed() +{ + DEBUG_SEED("get_cryptgenrandom_seed"); + + HCRYPTPROV hProvider = 0; + int r; + + if (!CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + fprintf(stderr, "error CryptAcquireContextW"); + exit(1); + } + + if (!CryptGenRandom(hProvider, sizeof(r), (BYTE*)&r)) + { + fprintf(stderr, "error CryptGenRandom"); + exit(1); + } + + CryptReleaseContext(hProvider, 0); + + return r; +} + +#endif + + +/* get_time_seed */ + +#include + +static int get_time_seed() +{ + DEBUG_SEED("get_time_seed"); + + return (int)time(NULL) * 433494437; +} + + +/* json_c_get_random_seed */ + +int json_c_get_random_seed() +{ +#if HAVE_RDRAND + if (has_rdrand()) return get_rdrand_seed(); +#endif +#if HAVE_DEV_RANDOM + if (has_dev_urandom()) return get_dev_random_seed(); +#endif +#if HAVE_CRYPTGENRANDOM + return get_cryptgenrandom_seed(); +#endif + return get_time_seed(); +} diff --git a/plugins/OnlineChecks/json-c/random_seed.h b/plugins/OnlineChecks/json-c/random_seed.h new file mode 100644 index 0000000..7362d67 --- /dev/null +++ b/plugins/OnlineChecks/json-c/random_seed.h @@ -0,0 +1,25 @@ +/* + * random_seed.h + * + * Copyright (c) 2013 Metaparadigm Pte. Ltd. + * Michael Clark + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See COPYING for details. + * + */ + +#ifndef seed_h +#define seed_h + +#ifdef __cplusplus +extern "C" { +#endif + +extern int json_c_get_random_seed(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/OnlineChecks/main.c b/plugins/OnlineChecks/main.c new file mode 100644 index 0000000..1cf3072 --- /dev/null +++ b/plugins/OnlineChecks/main.c @@ -0,0 +1,206 @@ +/* + * Process Hacker Online Checks - + * Main Program + * + * Copyright (C) 2010-2013 wj32 + * Copyright (C) 2012-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "onlnchk.h" + +PPH_PLUGIN PluginInstance; +static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ModuleMenuInitializingCallbackRegistration; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + PPH_STRING fileName; + + switch (menuItem->Id) + { + case ID_SENDTO_SERVICE1: + fileName = menuItem->Context; + UploadToOnlineService(fileName, UPLOAD_SERVICE_VIRUSTOTAL); + break; + case ID_SENDTO_SERVICE2: + fileName = menuItem->Context; + UploadToOnlineService(fileName, UPLOAD_SERVICE_JOTTI); + break; + case ID_SENDTO_SERVICE3: + fileName = menuItem->Context; + UploadToOnlineService(fileName, UPLOAD_SERVICE_CIMA); + break; + } +} + +PPH_EMENU_ITEM CreateSendToMenu( + _In_ PPH_EMENU_ITEM Parent, + _In_ PWSTR InsertAfter, + _In_ PPH_STRING FileName + ) +{ + PPH_EMENU_ITEM sendToMenu; + PPH_EMENU_ITEM menuItem; + ULONG insertIndex; + + // Create the Send To menu. + sendToMenu = PhPluginCreateEMenuItem(PluginInstance, 0, 0, L"Send to", NULL); + PhInsertEMenuItem(sendToMenu, PhPluginCreateEMenuItem(PluginInstance, 0, ID_SENDTO_SERVICE1, L"virustotal.com", FileName), -1); + PhInsertEMenuItem(sendToMenu, PhPluginCreateEMenuItem(PluginInstance, 0, ID_SENDTO_SERVICE2, L"virusscan.jotti.org", FileName), -1); + PhInsertEMenuItem(sendToMenu, PhPluginCreateEMenuItem(PluginInstance, 0, ID_SENDTO_SERVICE3, L"camas.comodo.com", FileName), -1); + + menuItem = PhFindEMenuItem(Parent, PH_EMENU_FIND_STARTSWITH, InsertAfter, 0); + + if (menuItem) + insertIndex = PhIndexOfEMenuItem(Parent, menuItem); + else + insertIndex = -1; + + PhInsertEMenuItem(Parent, sendToMenu, insertIndex + 1); + + return sendToMenu; +} + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem; + PPH_EMENU_ITEM sendToMenu; + + if (menuInfo->u.Process.NumberOfProcesses == 1) + processItem = menuInfo->u.Process.Processes[0]; + else + processItem = NULL; + + sendToMenu = CreateSendToMenu(menuInfo->Menu, L"Search online", processItem ? processItem->FileName : NULL); + + // Only enable the Send To menu if there is exactly one process selected and it has a file name. + + if (!processItem || !processItem->FileName) + { + sendToMenu->Flags |= PH_EMENU_DISABLED; + } +} + +VOID NTAPI ModuleMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_MODULE_ITEM moduleItem; + PPH_EMENU_ITEM sendToMenu; + + if (menuInfo->u.Module.NumberOfModules == 1) + moduleItem = menuInfo->u.Module.Modules[0]; + else + moduleItem = NULL; + + sendToMenu = CreateSendToMenu(menuInfo->Menu, L"Search online", moduleItem ? moduleItem->FileName : NULL); + + if (!moduleItem) + { + sendToMenu->Flags |= PH_EMENU_DISABLED; + } +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Online Checks"; + info->Author = L"dmex, wj32"; + info->Description = L"Allows files to be checked with online services."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1118"; + info->HasOptions = FALSE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + ProcessMenuInitializingCallback, + NULL, + &ProcessMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackModuleMenuInitializing), + ModuleMenuInitializingCallback, + NULL, + &ModuleMenuInitializingCallbackRegistration + ); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/OnlineChecks/onlnchk.h b/plugins/OnlineChecks/onlnchk.h new file mode 100644 index 0000000..119a8bb --- /dev/null +++ b/plugins/OnlineChecks/onlnchk.h @@ -0,0 +1,103 @@ +/* + * Process Hacker Online Checks - + * Main Headers + * + * Copyright (C) 2010-2013 wj32 + * Copyright (C) 2012-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef ONLNCHK_H +#define ONLNCHK_H + +#define CINTERFACE +#define COBJMACROS +#include +#include +#include + +#include "sha256.h" +#include "resource.h" + +#define PLUGIN_NAME L"ProcessHacker.OnlineChecks" + +#define HASH_SHA1 1 +#define HASH_SHA256 2 + +#define UM_EXISTS (WM_USER + 1) +#define UM_LAUNCH (WM_USER + 2) +#define UM_ERROR (WM_USER + 3) + +#define Control_Visible(hWnd, visible) \ + ShowWindow(hWnd, visible ? SW_SHOW : SW_HIDE); + +typedef enum _PH_UPLOAD_SERVICE_STATE +{ + PhUploadServiceDefault = 0, + PhUploadServiceChecking, + PhUploadServiceViewReport, + PhUploadServiceUploading, + PhUploadServiceLaunching, + PhUploadServiceMaximum +} PH_UPLOAD_SERVICE_STATE; + +typedef struct _SERVICE_INFO +{ + ULONG Id; + PWSTR HostName; + INTERNET_PORT HostPort; + ULONG HostFlags; + PWSTR UploadObjectName; + PWSTR FileNameFieldName; +} SERVICE_INFO, *PSERVICE_INFO; + +typedef struct _UPLOAD_CONTEXT +{ + ULONG Service; + HWND DialogHandle; + HWND MessageHandle; + HWND StatusHandle; + HWND ProgressHandle; + HFONT MessageFont; + HINTERNET HttpHandle; + + ULONG ErrorCode; + ULONG TotalFileLength; + + PH_UPLOAD_SERVICE_STATE UploadServiceState; + + PPH_STRING FileName; + PPH_STRING BaseFileName; + PPH_STRING WindowFileName; + PPH_STRING ObjectName; + PPH_STRING LaunchCommand; +} UPLOAD_CONTEXT, *PUPLOAD_CONTEXT; + +// main +extern PPH_PLUGIN PluginInstance; + +// upload +#define UPLOAD_SERVICE_VIRUSTOTAL 101 +#define UPLOAD_SERVICE_JOTTI 102 +#define UPLOAD_SERVICE_CIMA 103 + +VOID UploadToOnlineService( + _In_ PPH_STRING FileName, + _In_ ULONG Service + ); + +#endif diff --git a/plugins/OnlineChecks/resource.h b/plugins/OnlineChecks/resource.h new file mode 100644 index 0000000..38e5c26 --- /dev/null +++ b/plugins/OnlineChecks/resource.h @@ -0,0 +1,27 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by OnlineChecks.rc +// +#define ID_SENDTO_SERVICE1 101 +#define IDD_PROGRESS 101 +#define ID_SENDTO_SERVICE2 102 +#define ID_SENDTO_SERVICE3 103 +#define ID_SENDTO_SERVICE4 104 +#define ID_SENDTO_SERVICE5 105 +#define ID_SENDTO_SERVICE6 106 +#define ID_SENDTO_SERVICE7 107 +#define ID_SENDTO_SERVICE8 108 +#define IDC_MESSAGE 1002 +#define IDC_PROGRESS1 1003 +#define IDC_STATUS 1004 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 109 +#endif +#endif diff --git a/plugins/OnlineChecks/sha256.c b/plugins/OnlineChecks/sha256.c new file mode 100644 index 0000000..9f80de7 --- /dev/null +++ b/plugins/OnlineChecks/sha256.c @@ -0,0 +1,369 @@ +/* + * FIPS-180-2 compliant SHA-256 implementation + * + * Copyright (C) 2001-2003 Christophe Devine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include "sha256.h" + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32) (b)[(i) ] << 24 ) \ + | ( (uint32) (b)[(i) + 1] << 16 ) \ + | ( (uint32) (b)[(i) + 2] << 8 ) \ + | ( (uint32) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uint8) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uint8) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uint8) ( (n) ); \ +} + +void sha256_starts( sha256_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +void sha256_process( sha256_context *ctx, uint8 data[64] ) +{ + uint32 temp1, temp2, W[64]; + uint32 A, B, C, D, E, F, G, H; + + GET_UINT32( W[0], data, 0 ); + GET_UINT32( W[1], data, 4 ); + GET_UINT32( W[2], data, 8 ); + GET_UINT32( W[3], data, 12 ); + GET_UINT32( W[4], data, 16 ); + GET_UINT32( W[5], data, 20 ); + GET_UINT32( W[6], data, 24 ); + GET_UINT32( W[7], data, 28 ); + GET_UINT32( W[8], data, 32 ); + GET_UINT32( W[9], data, 36 ); + GET_UINT32( W[10], data, 40 ); + GET_UINT32( W[11], data, 44 ); + GET_UINT32( W[12], data, 48 ); + GET_UINT32( W[13], data, 52 ); + GET_UINT32( W[14], data, 56 ); + GET_UINT32( W[15], data, 60 ); + +#define SHR(x,n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x,n) (SHR(x,n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x,18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x,17) ^ ROTR(x,19) ^ SHR(x,10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x,13) ^ ROTR(x,22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x,11) ^ ROTR(x,25)) + +#define F0(x,y,z) ((x & y) | (z & (x | y))) +#define F1(x,y,z) (z ^ (x & (y ^ z))) + +#define R(t) \ +( \ + W[t] = S1(W[t - 2]) + W[t - 7] + \ + S0(W[t - 15]) + W[t - 16] \ +) + +#define P(a,b,c,d,e,f,g,h,x,K) \ +{ \ + temp1 = h + S3(e) + F1(e,f,g) + K + x; \ + temp2 = S2(a) + F0(a,b,c); \ + d += temp1; h = temp1 + temp2; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + E = ctx->state[4]; + F = ctx->state[5]; + G = ctx->state[6]; + H = ctx->state[7]; + + P( A, B, C, D, E, F, G, H, W[ 0], 0x428A2F98 ); + P( H, A, B, C, D, E, F, G, W[ 1], 0x71374491 ); + P( G, H, A, B, C, D, E, F, W[ 2], 0xB5C0FBCF ); + P( F, G, H, A, B, C, D, E, W[ 3], 0xE9B5DBA5 ); + P( E, F, G, H, A, B, C, D, W[ 4], 0x3956C25B ); + P( D, E, F, G, H, A, B, C, W[ 5], 0x59F111F1 ); + P( C, D, E, F, G, H, A, B, W[ 6], 0x923F82A4 ); + P( B, C, D, E, F, G, H, A, W[ 7], 0xAB1C5ED5 ); + P( A, B, C, D, E, F, G, H, W[ 8], 0xD807AA98 ); + P( H, A, B, C, D, E, F, G, W[ 9], 0x12835B01 ); + P( G, H, A, B, C, D, E, F, W[10], 0x243185BE ); + P( F, G, H, A, B, C, D, E, W[11], 0x550C7DC3 ); + P( E, F, G, H, A, B, C, D, W[12], 0x72BE5D74 ); + P( D, E, F, G, H, A, B, C, W[13], 0x80DEB1FE ); + P( C, D, E, F, G, H, A, B, W[14], 0x9BDC06A7 ); + P( B, C, D, E, F, G, H, A, W[15], 0xC19BF174 ); + P( A, B, C, D, E, F, G, H, R(16), 0xE49B69C1 ); + P( H, A, B, C, D, E, F, G, R(17), 0xEFBE4786 ); + P( G, H, A, B, C, D, E, F, R(18), 0x0FC19DC6 ); + P( F, G, H, A, B, C, D, E, R(19), 0x240CA1CC ); + P( E, F, G, H, A, B, C, D, R(20), 0x2DE92C6F ); + P( D, E, F, G, H, A, B, C, R(21), 0x4A7484AA ); + P( C, D, E, F, G, H, A, B, R(22), 0x5CB0A9DC ); + P( B, C, D, E, F, G, H, A, R(23), 0x76F988DA ); + P( A, B, C, D, E, F, G, H, R(24), 0x983E5152 ); + P( H, A, B, C, D, E, F, G, R(25), 0xA831C66D ); + P( G, H, A, B, C, D, E, F, R(26), 0xB00327C8 ); + P( F, G, H, A, B, C, D, E, R(27), 0xBF597FC7 ); + P( E, F, G, H, A, B, C, D, R(28), 0xC6E00BF3 ); + P( D, E, F, G, H, A, B, C, R(29), 0xD5A79147 ); + P( C, D, E, F, G, H, A, B, R(30), 0x06CA6351 ); + P( B, C, D, E, F, G, H, A, R(31), 0x14292967 ); + P( A, B, C, D, E, F, G, H, R(32), 0x27B70A85 ); + P( H, A, B, C, D, E, F, G, R(33), 0x2E1B2138 ); + P( G, H, A, B, C, D, E, F, R(34), 0x4D2C6DFC ); + P( F, G, H, A, B, C, D, E, R(35), 0x53380D13 ); + P( E, F, G, H, A, B, C, D, R(36), 0x650A7354 ); + P( D, E, F, G, H, A, B, C, R(37), 0x766A0ABB ); + P( C, D, E, F, G, H, A, B, R(38), 0x81C2C92E ); + P( B, C, D, E, F, G, H, A, R(39), 0x92722C85 ); + P( A, B, C, D, E, F, G, H, R(40), 0xA2BFE8A1 ); + P( H, A, B, C, D, E, F, G, R(41), 0xA81A664B ); + P( G, H, A, B, C, D, E, F, R(42), 0xC24B8B70 ); + P( F, G, H, A, B, C, D, E, R(43), 0xC76C51A3 ); + P( E, F, G, H, A, B, C, D, R(44), 0xD192E819 ); + P( D, E, F, G, H, A, B, C, R(45), 0xD6990624 ); + P( C, D, E, F, G, H, A, B, R(46), 0xF40E3585 ); + P( B, C, D, E, F, G, H, A, R(47), 0x106AA070 ); + P( A, B, C, D, E, F, G, H, R(48), 0x19A4C116 ); + P( H, A, B, C, D, E, F, G, R(49), 0x1E376C08 ); + P( G, H, A, B, C, D, E, F, R(50), 0x2748774C ); + P( F, G, H, A, B, C, D, E, R(51), 0x34B0BCB5 ); + P( E, F, G, H, A, B, C, D, R(52), 0x391C0CB3 ); + P( D, E, F, G, H, A, B, C, R(53), 0x4ED8AA4A ); + P( C, D, E, F, G, H, A, B, R(54), 0x5B9CCA4F ); + P( B, C, D, E, F, G, H, A, R(55), 0x682E6FF3 ); + P( A, B, C, D, E, F, G, H, R(56), 0x748F82EE ); + P( H, A, B, C, D, E, F, G, R(57), 0x78A5636F ); + P( G, H, A, B, C, D, E, F, R(58), 0x84C87814 ); + P( F, G, H, A, B, C, D, E, R(59), 0x8CC70208 ); + P( E, F, G, H, A, B, C, D, R(60), 0x90BEFFFA ); + P( D, E, F, G, H, A, B, C, R(61), 0xA4506CEB ); + P( C, D, E, F, G, H, A, B, R(62), 0xBEF9A3F7 ); + P( B, C, D, E, F, G, H, A, R(63), 0xC67178F2 ); + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; + ctx->state[4] += E; + ctx->state[5] += F; + ctx->state[6] += G; + ctx->state[7] += H; +} + +void sha256_update( sha256_context *ctx, uint8 *input, uint32 length ) +{ + uint32 left, fill; + + if( ! length ) return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += length; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < length ) + ctx->total[1]++; + + if( left && length >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + sha256_process( ctx, ctx->buffer ); + length -= fill; + input += fill; + left = 0; + } + + while( length >= 64 ) + { + sha256_process( ctx, input ); + length -= 64; + input += 64; + } + + if( length ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, length ); + } +} + +static uint8 sha256_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void sha256_finish( sha256_context *ctx, uint8 digest[32] ) +{ + uint32 last, padn; + uint32 high, low; + uint8 msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32( high, msglen, 0 ); + PUT_UINT32( low, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + sha256_update( ctx, sha256_padding, padn ); + sha256_update( ctx, msglen, 8 ); + + PUT_UINT32( ctx->state[0], digest, 0 ); + PUT_UINT32( ctx->state[1], digest, 4 ); + PUT_UINT32( ctx->state[2], digest, 8 ); + PUT_UINT32( ctx->state[3], digest, 12 ); + PUT_UINT32( ctx->state[4], digest, 16 ); + PUT_UINT32( ctx->state[5], digest, 20 ); + PUT_UINT32( ctx->state[6], digest, 24 ); + PUT_UINT32( ctx->state[7], digest, 28 ); +} + +#ifdef TEST + +#include +#include + +/* + * those are the standard FIPS-180-2 test vectors + */ + +static char *msg[] = +{ + "abc", + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + NULL +}; + +static char *val[] = +{ + "ba7816bf8f01cfea414140de5dae2223" \ + "b00361a396177a9cb410ff61f20015ad", + "248d6a61d20638b8e5c026930c3e6039" \ + "a33ce45964ff2167f6ecedd419db06c1", + "cdc76e5c9914fb9281a1c7e284d73e67" \ + "f1809a48a497200e046d39ccc7112cd0" +}; + +int main( int argc, char *argv[] ) +{ + FILE *f; + int i, j; + char output[65]; + sha256_context ctx; + unsigned char buf[1000]; + unsigned char sha256sum[32]; + + if( argc < 2 ) + { + printf( "\n SHA-256 Validation Tests:\n\n" ); + + for( i = 0; i < 3; i++ ) + { + printf( " Test %d ", i + 1 ); + + sha256_starts( &ctx ); + + if( i < 2 ) + { + sha256_update( &ctx, (uint8 *) msg[i], + strlen( msg[i] ) ); + } + else + { + memset( buf, 'a', 1000 ); + + for( j = 0; j < 1000; j++ ) + { + sha256_update( &ctx, (uint8 *) buf, 1000 ); + } + } + + sha256_finish( &ctx, sha256sum ); + + for( j = 0; j < 32; j++ ) + { + sprintf( output + j * 2, "%02x", sha256sum[j] ); + } + + if( memcmp( output, val[i], 64 ) ) + { + printf( "failed!\n" ); + return( 1 ); + } + + printf( "passed.\n" ); + } + + printf( "\n" ); + } + else + { + if( ! ( f = fopen( argv[1], "rb" ) ) ) + { + perror( "fopen" ); + return( 1 ); + } + + sha256_starts( &ctx ); + + while( ( i = fread( buf, 1, sizeof( buf ), f ) ) > 0 ) + { + sha256_update( &ctx, buf, i ); + } + + sha256_finish( &ctx, sha256sum ); + + for( j = 0; j < 32; j++ ) + { + printf( "%02x", sha256sum[j] ); + } + + printf( " %s\n", argv[1] ); + } + + return( 0 ); +} + +#endif diff --git a/plugins/OnlineChecks/sha256.h b/plugins/OnlineChecks/sha256.h new file mode 100644 index 0000000..cf883a2 --- /dev/null +++ b/plugins/OnlineChecks/sha256.h @@ -0,0 +1,25 @@ +#ifndef _SHA256_H +#define _SHA256_H + +#ifndef uint8 +#define uint8 unsigned char +#endif + +#ifndef uint32 +#define uint32 unsigned long int +#endif + +typedef struct +{ + uint32 total[2]; + uint32 state[8]; + uint8 buffer[64]; +} +sha256_context; + +void sha256_starts( sha256_context *ctx ); +void sha256_update( sha256_context *ctx, uint8 *input, uint32 length ); +void sha256_finish( sha256_context *ctx, uint8 digest[32] ); + +#endif /* sha256.h */ + diff --git a/plugins/OnlineChecks/upload.c b/plugins/OnlineChecks/upload.c new file mode 100644 index 0000000..b273f08 --- /dev/null +++ b/plugins/OnlineChecks/upload.c @@ -0,0 +1,1382 @@ + /* + * Process Hacker Online Checks - + * Uploader Window + * + * Copyright (C) 2010-2013 wj32 + * Copyright (C) 2012-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "onlnchk.h" +#include "json-c/json.h" + +static SERVICE_INFO UploadServiceInfo[] = +{ + { UPLOAD_SERVICE_VIRUSTOTAL, L"www.virustotal.com", INTERNET_DEFAULT_HTTPS_PORT, WINHTTP_FLAG_SECURE, L"???", L"file" }, + { UPLOAD_SERVICE_JOTTI, L"virusscan.jotti.org", INTERNET_DEFAULT_HTTPS_PORT, WINHTTP_FLAG_SECURE, L"/en-US/submit-file?isAjax=true", L"sample-file[]" }, + { UPLOAD_SERVICE_CIMA, L"camas.comodo.com", INTERNET_DEFAULT_HTTP_PORT, 0, L"/cgi-bin/submit", L"file" } +}; + +json_object_ptr json_get_object(json_object_ptr rootObj, const char* key) +{ + json_object_ptr returnObj; + + if (json_object_object_get_ex(rootObj, key, &returnObj)) + { + return returnObj; + } + + return NULL; +} + +HFONT InitializeFont( + _In_ HWND hwnd + ) +{ + LOGFONT logFont; + + // Create the font handle + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + HDC hdc; + + if (hdc = GetDC(hwnd)) + { + HFONT fontHandle = CreateFont( + -MulDiv(-14, GetDeviceCaps(hdc, LOGPIXELSY), 72), + 0, + 0, + 0, + FW_MEDIUM, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY | ANTIALIASED_QUALITY, + DEFAULT_PITCH, + logFont.lfFaceName + ); + + SendMessage(hwnd, WM_SETFONT, (WPARAM)fontHandle, TRUE); + + ReleaseDC(hwnd, hdc); + + return fontHandle; + } + } + + return NULL; +} + +BOOL ReadRequestString( + _In_ HINTERNET Handle, + _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data, + _Out_ ULONG *DataLength + ) +{ + BYTE buffer[PAGE_SIZE]; + PSTR data; + ULONG allocatedLength; + ULONG dataLength; + ULONG returnLength; + + allocatedLength = sizeof(buffer); + data = (PSTR)PhAllocate(allocatedLength); + dataLength = 0; + + // Zero the buffer + memset(buffer, 0, PAGE_SIZE); + + while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Copy the returned buffer into our pointer + memcpy(data + dataLength, buffer, returnLength); + // Zero the returned buffer for the next loop + //memset(buffer, 0, returnLength); + + dataLength += returnLength; + } + + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + data[dataLength] = 0; + + *DataLength = dataLength; + *Data = data; + + return TRUE; +} + +VOID RaiseUploadError( + _In_ PUPLOAD_CONTEXT Context, + _In_ PWSTR Error, + _In_ ULONG ErrorCode + ) +{ + if (Context->DialogHandle) + { + PostMessage( + Context->DialogHandle, + UM_ERROR, + 0, + (LPARAM)PhFormatString(L"Error: [%lu] %s", ErrorCode, Error) + ); + } +} + +PSERVICE_INFO GetUploadServiceInfo( + _In_ ULONG Id + ) +{ + ULONG i; + + for (i = 0; i < ARRAYSIZE(UploadServiceInfo); i++) + { + if (UploadServiceInfo[i].Id == Id) + return &UploadServiceInfo[i]; + } + + return NULL; +} + +BOOLEAN PerformSubRequest( + _In_ PUPLOAD_CONTEXT Context, + _In_ PWSTR HostName, + _In_ INTERNET_PORT HostPort, + _In_ ULONG HostFlags, + _In_ PWSTR ObjectName, + _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data, + _Out_opt_ PULONG DataLength + ) +{ + BOOLEAN result = FALSE; + HINTERNET connectHandle = NULL; + HINTERNET requestHandle = NULL; + + __try + { + // Connect to the online service. + if (!(connectHandle = WinHttpConnect( + Context->HttpHandle, + HostName, + HostPort, + 0 + ))) + { + RaiseUploadError(Context, L"Unable to connect to the service", GetLastError()); + __leave; + } + + // Create the request. + if (!(requestHandle = WinHttpOpenRequest( + connectHandle, + NULL, + ObjectName, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH | HostFlags + ))) + { + RaiseUploadError(Context, L"Unable to create the request", GetLastError()); + __leave; + } + + // Send the request. + if (!WinHttpSendRequest( + requestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, + 0 + )) + { + RaiseUploadError(Context, L"Unable to send the request", GetLastError()); + __leave; + } + + // Wait for the send request to complete and receive the response. + if (WinHttpReceiveResponse(requestHandle, NULL)) + { + BYTE buffer[PAGE_SIZE]; + PSTR data; + ULONG allocatedLength; + ULONG dataLength; + ULONG returnLength; + + allocatedLength = sizeof(buffer); + data = PhAllocate(allocatedLength); + dataLength = 0; + + while (WinHttpReadData(requestHandle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + data = PhReAllocate(data, allocatedLength); + } + + memcpy(data + dataLength, buffer, returnLength); + dataLength += returnLength; + } + + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + data = PhReAllocate(data, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + data[dataLength] = 0; + + *Data = data; + + if (DataLength) + { + *DataLength = dataLength; + } + } + else + { + RaiseUploadError(Context, L"Unable to receive the response", GetLastError()); + __leave; + } + + result = TRUE; + } + __finally + { + if (requestHandle) + WinHttpCloseHandle(requestHandle); + if (connectHandle) + WinHttpCloseHandle(connectHandle); + } + + return result; +} + +NTSTATUS HashFileAndResetPosition( + _In_ HANDLE FileHandle, + _In_ PLARGE_INTEGER FileSize, + _In_ ULONG Algorithm, + _Out_ PVOID Hash + ) +{ + NTSTATUS status; + IO_STATUS_BLOCK iosb; + PH_HASH_CONTEXT hashContext; + sha256_context sha256; + ULONG64 bytesRemaining; + FILE_POSITION_INFORMATION positionInfo; + UCHAR buffer[PAGE_SIZE]; + + bytesRemaining = FileSize->QuadPart; + + switch (Algorithm) + { + case HASH_SHA1: + PhInitializeHash(&hashContext, Sha1HashAlgorithm); + break; + case HASH_SHA256: + sha256_starts(&sha256); + break; + } + + while (bytesRemaining) + { + status = NtReadFile( + FileHandle, + NULL, + NULL, + NULL, + &iosb, + buffer, + sizeof(buffer), + NULL, + NULL + ); + + if (!NT_SUCCESS(status)) + break; + + switch (Algorithm) + { + case HASH_SHA1: + PhUpdateHash(&hashContext, buffer, (ULONG)iosb.Information); + break; + case HASH_SHA256: + sha256_update(&sha256, (PUCHAR)buffer, (ULONG)iosb.Information); + break; + } + + bytesRemaining -= (ULONG)iosb.Information; + } + + if (status == STATUS_END_OF_FILE) + status = STATUS_SUCCESS; + + if (NT_SUCCESS(status)) + { + switch (Algorithm) + { + case HASH_SHA1: + PhFinalHash(&hashContext, Hash, 20, NULL); + break; + case HASH_SHA256: + sha256_finish(&sha256, Hash); + break; + } + + positionInfo.CurrentByteOffset.QuadPart = 0; + status = NtSetInformationFile( + FileHandle, + &iosb, + &positionInfo, + sizeof(FILE_POSITION_INFORMATION), + FilePositionInformation + ); + } + + return status; +} + +NTSTATUS UploadFileThreadStart( + _In_ PVOID Parameter + ) +{ + NTSTATUS status = STATUS_SUCCESS; + ULONG httpStatus = 0; + ULONG httpStatusLength = sizeof(ULONG); + ULONG httpPostSeed = 0; + ULONG totalUploadLength = 0; + ULONG totalUploadedLength = 0; + ULONG totalPostHeaderWritten = 0; + ULONG totalPostFooterWritten = 0; + ULONG totalWriteLength = 0; + LARGE_INTEGER timeNow; + LARGE_INTEGER timeStart; + ULONG64 timeTicks = 0; + ULONG64 timeBitsPerSecond = 0; + + HANDLE fileHandle = INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK isb; + PSERVICE_INFO serviceInfo = NULL; + HINTERNET connectHandle = NULL; + HINTERNET requestHandle = NULL; + + PPH_STRING postBoundary = NULL; + PPH_BYTES asciiPostData = NULL; + PPH_BYTES asciiFooterData = NULL; + PH_STRING_BUILDER httpRequestHeaders = { 0 }; + PH_STRING_BUILDER httpPostHeader = { 0 }; + PH_STRING_BUILDER httpPostFooter = { 0 }; + BYTE buffer[PAGE_SIZE]; + + PUPLOAD_CONTEXT context = (PUPLOAD_CONTEXT)Parameter; + + serviceInfo = GetUploadServiceInfo(context->Service); + + __try + { + // Open the file and check its size. + status = PhCreateFileWin32( + &fileHandle, + context->FileName->Buffer, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + { + RaiseUploadError(context, L"Unable to open the file", RtlNtStatusToDosError(status)); + __leave; + } + + // Connect to the online service. + if (!(connectHandle = WinHttpConnect( + context->HttpHandle, + serviceInfo->HostName, + serviceInfo->HostPort, + 0 + ))) + { + RaiseUploadError(context, L"Unable to connect to the service", GetLastError()); + __leave; + } + + // Create the request. + if (!(requestHandle = WinHttpOpenRequest( + connectHandle, + L"POST", + context->ObjectName->Buffer, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH | serviceInfo->HostFlags + ))) + { + RaiseUploadError(context, L"Unable to create the request", GetLastError()); + __leave; + } + + if (context->Service == UPLOAD_SERVICE_JOTTI) + { + PPH_STRING ajaxHeader; + + ajaxHeader = PhCreateString(L"X-Requested-With: XMLHttpRequest"); + + WinHttpAddRequestHeaders( + requestHandle, + ajaxHeader->Buffer, + (ULONG)ajaxHeader->Length / sizeof(WCHAR), + WINHTTP_ADDREQ_FLAG_ADD + ); + + PhDereferenceObject(ajaxHeader); + } + + // Create and POST data. + PhInitializeStringBuilder(&httpRequestHeaders, MAX_PATH); + PhInitializeStringBuilder(&httpPostHeader, MAX_PATH); + PhInitializeStringBuilder(&httpPostFooter, MAX_PATH); + + // build request boundary string + postBoundary = PhFormatString( + L"------------------------%I64u", + (ULONG64)RtlRandomEx(&httpPostSeed) | ((ULONG64)RtlRandomEx(&httpPostSeed) << 31) + ); + // build request header string + PhAppendFormatStringBuilder( + &httpRequestHeaders, + L"Content-Type: multipart/form-data; boundary=%s\r\n", + postBoundary->Buffer + ); + + if (context->Service == UPLOAD_SERVICE_JOTTI) + { + // POST boundary header + PhAppendFormatStringBuilder( + &httpPostHeader, + L"\r\n--%s\r\n", + postBoundary->Buffer + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n268435456\r\n" + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"--%s\r\n", + postBoundary->Buffer + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", + serviceInfo->FileNameFieldName, + context->BaseFileName->Buffer + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"Content-Type: application/x-msdownload\r\n\r\n" + ); + + // POST boundary footer + PhAppendFormatStringBuilder( + &httpPostFooter, + L"\r\n--%s--\r\n", + postBoundary->Buffer + ); + } + else + { + // POST boundary header + PhAppendFormatStringBuilder( + &httpPostHeader, + L"--%s\r\n", + postBoundary->Buffer + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", + serviceInfo->FileNameFieldName, + context->BaseFileName->Buffer + ); + PhAppendFormatStringBuilder( + &httpPostHeader, + L"Content-Type: application/octet-stream\r\n\r\n" + ); + + // POST boundary footer + PhAppendFormatStringBuilder( + &httpPostFooter, + L"\r\n--%s--\r\n\r\n", + postBoundary->Buffer + ); + } + + // add headers + if (!WinHttpAddRequestHeaders( + requestHandle, + httpRequestHeaders.String->Buffer, + -1L, + WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD + )) + { + RaiseUploadError(context, L"Unable to add request headers", GetLastError()); + __leave; + } + + // All until now has been just for this; Calculate the total request length. + totalUploadLength = (ULONG)httpPostHeader.String->Length / sizeof(WCHAR) + context->TotalFileLength + (ULONG)httpPostFooter.String->Length / sizeof(WCHAR); + + // Send the request. + if (!WinHttpSendRequest( + requestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + totalUploadLength, + 0 + )) + { + RaiseUploadError(context, L"Unable to send the request", GetLastError()); + __leave; + } + + // Convert to ASCII + asciiPostData = PhConvertUtf16ToAscii(httpPostHeader.String->Buffer, '-'); + asciiFooterData = PhConvertUtf16ToAscii(httpPostFooter.String->Buffer, '-'); + + // Start the clock. + PhQuerySystemTime(&timeStart); + + // Write the header + if (!WinHttpWriteData( + requestHandle, + asciiPostData->Buffer, + (ULONG)asciiPostData->Length, + &totalPostHeaderWritten + )) + { + RaiseUploadError(context, L"Unable to write the post header", GetLastError()); + __leave; + } + + // Upload the file... + while (TRUE) + { + status = NtReadFile( + fileHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + PAGE_SIZE, + NULL, + NULL + ); + + if (!NT_SUCCESS(status)) + break; + + if (!WinHttpWriteData(requestHandle, buffer, (ULONG)isb.Information, &totalWriteLength)) + { + RaiseUploadError(context, L"Unable to upload the file data", GetLastError()); + __leave; + } + + totalUploadedLength += totalWriteLength; + + // Query the current time + PhQuerySystemTime(&timeNow); + + // Calculate the number of ticks + timeTicks = (timeNow.QuadPart - timeStart.QuadPart) / PH_TICKS_PER_SEC; + timeBitsPerSecond = totalUploadedLength / __max(timeTicks, 1); + + { + FLOAT percent = ((FLOAT)totalUploadedLength / context->TotalFileLength * 100); + PPH_STRING totalLength = PhFormatSize(context->TotalFileLength, -1); + PPH_STRING totalUploaded = PhFormatSize(totalUploadedLength, -1); + PPH_STRING totalSpeed = PhFormatSize(timeBitsPerSecond, -1); + + PPH_STRING statusMessage = PhFormatString( + L"%s of %s @ %s/s", + totalUploaded->Buffer, + totalLength->Buffer, + totalSpeed->Buffer + ); + + Static_SetText(context->StatusHandle, statusMessage->Buffer); + + // Update the progress bar position + PostMessage(context->ProgressHandle, PBM_SETPOS, (INT)percent, 0); + + PhDereferenceObject(statusMessage); + PhDereferenceObject(totalSpeed); + PhDereferenceObject(totalLength); + PhDereferenceObject(totalUploaded); + } + } + + // Write the footer bytes + if (!WinHttpWriteData( + requestHandle, + asciiFooterData->Buffer, + (ULONG)asciiFooterData->Length, + &totalPostFooterWritten + )) + { + RaiseUploadError(context, L"Unable to write the post footer", GetLastError()); + __leave; + } + + // Wait for the send request to complete and receive the response. + if (!WinHttpReceiveResponse(requestHandle, NULL)) + { + RaiseUploadError(context, L"Unable to receive the response", GetLastError()); + __leave; + } + + // Handle service-specific actions. + WinHttpQueryHeaders( + requestHandle, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + NULL, + &httpStatus, + &httpStatusLength, + NULL + ); + + if (httpStatus == HTTP_STATUS_OK || httpStatus == HTTP_STATUS_REDIRECT_METHOD || httpStatus == HTTP_STATUS_REDIRECT) + { + switch (context->Service) + { + case UPLOAD_SERVICE_VIRUSTOTAL: + { + ULONG bufferLength = 0; + + // Use WinHttpQueryOption to obtain a buffer size. + if (!WinHttpQueryOption(requestHandle, WINHTTP_OPTION_URL, NULL, &bufferLength)) + { + PPH_STRING buffer = PhCreateStringEx(NULL, bufferLength); + + // Use WinHttpQueryOption again, this time to retrieve the URL in the new buffer + if (WinHttpQueryOption(requestHandle, WINHTTP_OPTION_URL, buffer->Buffer, &bufferLength)) + { + // Format the retrieved URL... + context->LaunchCommand = PhDuplicateString(buffer); + } + + PhDereferenceObject(buffer); + } + } + break; + case UPLOAD_SERVICE_JOTTI: + { + PSTR hrefEquals = NULL; + PSTR quote = NULL; + PSTR buffer = NULL; + ULONG bufferLength = 0; + json_object_ptr rootJsonObject; + + //This service returns some json that redirects the user to the new location. + if (!ReadRequestString(requestHandle, &buffer, &bufferLength)) + { + RaiseUploadError(context, L"Unable to complete the request", GetLastError()); + __leave; + } + + if (rootJsonObject = json_tokener_parse(buffer)) + { + PSTR redirectUrl = json_object_get_string(json_get_object(rootJsonObject, "redirecturl")); + + context->LaunchCommand = PhFormatString( + L"http://virusscan.jotti.org%hs", + redirectUrl + ); + + json_object_put(rootJsonObject); + } + } + break; + case UPLOAD_SERVICE_CIMA: + { + PSTR urlEquals = NULL; + PSTR quote = NULL; + PSTR buffer = NULL; + ULONG bufferLength = 0; + + // This service returns some HTML that redirects the user to the new location. + if (!ReadRequestString(requestHandle, &buffer, &bufferLength)) + { + RaiseUploadError(context, L"Unable to complete the CIMA request", GetLastError()); + __leave; + } + + // The HTML looks like this: + // + urlEquals = strstr(buffer, "url="); + + if (urlEquals) + { + urlEquals += 4; + quote = strchr(urlEquals, '"'); + + if (quote) + { + context->LaunchCommand = PhFormatString( + L"http://camas.comodo.com%.*S", + quote - urlEquals, + urlEquals + ); + } + } + } + break; + } + } + else + { + RaiseUploadError(context, L"Unable to complete the request", STATUS_FVE_PARTIAL_METADATA); + __leave; + } + + if (!PhIsNullOrEmptyString(context->LaunchCommand)) + { + PostMessage(context->DialogHandle, UM_LAUNCH, 0, 0); + } + else + { + RaiseUploadError(context, L"Unable to complete the Launch request (please try again after a few minutes)", ERROR_INVALID_DATA); + __leave; + } + } + __finally + { + if (postBoundary) + { + PhDereferenceObject(postBoundary); + } + + if (asciiFooterData) + { + PhDereferenceObject(asciiFooterData); + } + + if (asciiPostData) + { + PhDereferenceObject(asciiPostData); + } + + if (httpPostFooter.String) + { + PhDeleteStringBuilder(&httpPostFooter); + } + + if (httpPostHeader.String) + { + PhDeleteStringBuilder(&httpPostHeader); + } + + if (httpRequestHeaders.String) + { + PhDeleteStringBuilder(&httpRequestHeaders); + } + + if (fileHandle != INVALID_HANDLE_VALUE) + { + NtClose(fileHandle); + } + } + + return status; +} + +NTSTATUS UploadCheckThreadStart( + _In_ PVOID Parameter + ) +{ + NTSTATUS status = STATUS_SUCCESS; + BOOLEAN fileExists = FALSE; + LARGE_INTEGER fileSize64; + PSTR subRequestBuffer = NULL; + HINTERNET connectHandle = NULL; + HINTERNET requestHandle = NULL; + PSERVICE_INFO serviceInfo = NULL; + PPH_STRING hashString = NULL; + PPH_STRING subObjectName = NULL; + HANDLE fileHandle = INVALID_HANDLE_VALUE; + + PUPLOAD_CONTEXT context = (PUPLOAD_CONTEXT)Parameter; + + serviceInfo = GetUploadServiceInfo(context->Service); + + __try + { + // Open the file and check its size. + status = PhCreateFileWin32( + &fileHandle, + context->FileName->Buffer, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + { + RaiseUploadError(context, L"Unable to open the file", RtlNtStatusToDosError(status)); + __leave; + } + + if (NT_SUCCESS(status = PhGetFileSize(fileHandle, &fileSize64))) + { + if (context->Service == UPLOAD_SERVICE_VIRUSTOTAL) + { + if (fileSize64.QuadPart > 128 * 1024 * 1024) // 128 MB + { + RaiseUploadError(context, L"The file is too large (over 128 MB)", ERROR_FILE_TOO_LARGE); + __leave; + } + } + else + { + if (fileSize64.QuadPart > 20 * 1024 * 1024) // 20 MB + { + RaiseUploadError(context, L"The file is too large (over 20 MB)", ERROR_FILE_TOO_LARGE); + __leave; + } + } + + context->TotalFileLength = fileSize64.LowPart; + } + + // Get proxy configuration and create winhttp handle (used for all winhttp sessions + requests). + { + PPH_STRING phVersion = NULL; + PPH_STRING userAgent = NULL; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; + + // Create a user agent string. + phVersion = PhGetPhVersion(); + userAgent = PhConcatStrings2(L"ProcessHacker_", phVersion->Buffer); + + // Query the current system proxy + WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); + + // Open the HTTP session with the system proxy configuration if available + context->HttpHandle = WinHttpOpen( + userAgent->Buffer, + proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + proxyConfig.lpszProxy, + proxyConfig.lpszProxyBypass, + 0 + ); + + PhClearReference(&phVersion); + PhClearReference(&userAgent); + + if (!context->HttpHandle) + __leave; + + if (WindowsVersion >= WINDOWS_8_1) + { + // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. + ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + + WinHttpSetOption( + context->HttpHandle, + WINHTTP_OPTION_DECOMPRESSION, + &httpFlags, + sizeof(ULONG) + ); + } + } + + switch (context->Service) + { + case UPLOAD_SERVICE_VIRUSTOTAL: + { + PSTR uploadUrl = NULL; + PSTR quote = NULL; + ULONG bufferLength = 0; + UCHAR hash[32]; + json_object_ptr rootJsonObject; + + if (!NT_SUCCESS(status = HashFileAndResetPosition(fileHandle, &fileSize64, HASH_SHA256, hash))) + { + RaiseUploadError(context, L"Unable to hash the file", RtlNtStatusToDosError(status)); + __leave; + } + + hashString = PhBufferToHexString(hash, 32); + subObjectName = PhConcatStrings2(L"/file/upload/?sha256=", hashString->Buffer); + context->LaunchCommand = PhFormatString(L"http://www.virustotal.com/file/%s/analysis/", hashString->Buffer); + + if (!PerformSubRequest( + context, + serviceInfo->HostName, + serviceInfo->HostPort, + serviceInfo->HostFlags, + subObjectName->Buffer, + &subRequestBuffer, + &bufferLength + )) + { + __leave; + } + + if (rootJsonObject = json_tokener_parse(subRequestBuffer)) + { + json_bool file_Exists; + PSTR uploadUrl; + + file_Exists = json_object_get_boolean(json_get_object(rootJsonObject, "file_exists")); + uploadUrl = json_object_get_string(json_get_object(rootJsonObject, "upload_url")); + + if (file_Exists) + { + fileExists = TRUE; + } + + context->ObjectName = PhZeroExtendToUtf16(uploadUrl + strlen("https://www.virustotal.com")); + + json_object_put(rootJsonObject); + } + + // Create the default upload URL + if (!context->ObjectName) + context->ObjectName = PhCreateString(serviceInfo->UploadObjectName); + } + break; + case UPLOAD_SERVICE_JOTTI: + { + // Create the default upload URL + if (!context->ObjectName) + context->ObjectName = PhCreateString(serviceInfo->UploadObjectName); + } + break; + case UPLOAD_SERVICE_CIMA: + { + PSTR quote = NULL; + ULONG bufferLength = 0; + UCHAR hash[32]; + ULONG status = 0; + ULONG statusLength = sizeof(statusLength); + + if (!NT_SUCCESS(status = HashFileAndResetPosition(fileHandle, &fileSize64, HASH_SHA256, hash))) + { + RaiseUploadError(context, L"Unable to hash the file", RtlNtStatusToDosError(status)); + __leave; + } + + hashString = PhBufferToHexString(hash, 32); + subObjectName = PhConcatStrings2(L"/cgi-bin/submit?file=", hashString->Buffer); + context->LaunchCommand = PhFormatString(L"http://camas.comodo.com/cgi-bin/submit?file=%s", hashString->Buffer); + + // Connect to the CIMA online service. + if (!(connectHandle = WinHttpConnect( + context->HttpHandle, + serviceInfo->HostName, + INTERNET_DEFAULT_HTTP_PORT, + 0 + ))) + { + RaiseUploadError(context, L"Unable to connect to the CIMA service", GetLastError()); + __leave; + } + + // Create the request. + if (!(requestHandle = WinHttpOpenRequest( + connectHandle, + NULL, + subObjectName->Buffer, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH + ))) + { + RaiseUploadError(context, L"Unable to create the CIMA request", GetLastError()); + __leave; + } + + // Send the request. + if (!WinHttpSendRequest( + requestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, + 0 + )) + { + RaiseUploadError(context, L"Unable to send the CIMA request", GetLastError()); + __leave; + } + + // Wait for the send request to complete and receive the response. + if (!WinHttpReceiveResponse(requestHandle, NULL)) + { + RaiseUploadError(context, L"Unable to receive the CIMA response", GetLastError()); + __leave; + } + + WinHttpQueryHeaders( + requestHandle, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + NULL, + &status, + &statusLength, + NULL + ); + + if (status == HTTP_STATUS_OK) + { + fileExists = TRUE; + } + + if (!context->ObjectName) + { + // Create the default upload URL + context->ObjectName = PhCreateString(serviceInfo->UploadObjectName); + } + } + break; + } + + // Do we need to prompt the user? + if (fileExists && !PhIsNullOrEmptyString(context->LaunchCommand)) + { + PostMessage(context->DialogHandle, UM_EXISTS, 0, 0); + __leave; + } + + // No existing file found... Start the upload. + if (!NT_SUCCESS(UploadFileThreadStart(context))) + __leave; + } + __finally + { + PhClearReference(&hashString); + PhClearReference(&subObjectName); + + if (requestHandle) + { + WinHttpCloseHandle(requestHandle); + } + + if (connectHandle) + { + WinHttpCloseHandle(connectHandle); + } + + if (fileHandle != INVALID_HANDLE_VALUE) + { + NtClose(fileHandle); + } + } + + return status; +} + +INT_PTR CALLBACK UploadDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PUPLOAD_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PUPLOAD_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PUPLOAD_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + PhClearReference(&context->FileName); + PhClearReference(&context->BaseFileName); + PhClearReference(&context->WindowFileName); + PhClearReference(&context->LaunchCommand); + PhClearReference(&context->ObjectName); + + if (context->MessageFont) + DeleteObject(context->MessageFont); + + if (context->HttpHandle) + WinHttpCloseHandle(context->HttpHandle); + + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + + PostQuitMessage(0); + } + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HANDLE dialogThread = NULL; + HWND parentWindow = GetParent(hwndDlg); + + PhCenterWindow(hwndDlg, (IsWindowVisible(parentWindow) && !IsMinimized(parentWindow)) ? parentWindow : NULL); + + context->DialogHandle = hwndDlg; + context->StatusHandle = GetDlgItem(hwndDlg, IDC_STATUS); + context->ProgressHandle = GetDlgItem(hwndDlg, IDC_PROGRESS1); + context->MessageHandle = GetDlgItem(hwndDlg, IDC_MESSAGE); + context->MessageFont = InitializeFont(context->MessageHandle); + context->WindowFileName = PhFormatString(L"Uploading: %s", context->BaseFileName->Buffer); + context->UploadServiceState = PhUploadServiceChecking; + + // Reset the window status... + Static_SetText(context->MessageHandle, context->WindowFileName->Buffer); + + switch (context->Service) + { + case UPLOAD_SERVICE_VIRUSTOTAL: + Static_SetText(hwndDlg, L"Uploading to VirusTotal..."); + break; + case UPLOAD_SERVICE_JOTTI: + Static_SetText(hwndDlg, L"Uploading to Jotti..."); + break; + case UPLOAD_SERVICE_CIMA: + Static_SetText(hwndDlg, L"Uploading to Comodo..."); + break; + } + + if (dialogThread = PhCreateThread(0, UploadCheckThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDYES: + { + context->UploadServiceState = PhUploadServiceMaximum; + + if (!PhIsNullOrEmptyString(context->LaunchCommand)) + { + PhShellExecute(hwndDlg, context->LaunchCommand->Buffer, NULL); + } + + DestroyWindow(hwndDlg); + } + break; + case IDNO: + { + if (context->UploadServiceState == PhUploadServiceViewReport) + { + HANDLE dialogThread = NULL; + + // Set state to uploading... + context->UploadServiceState = PhUploadServiceUploading; + + // Reset the window status... + Static_SetText(context->MessageHandle, context->WindowFileName->Buffer); + Static_SetText(GetDlgItem(hwndDlg, IDNO), L"Cancel"); + Control_Visible(GetDlgItem(hwndDlg, IDYES), FALSE); + + // Start the upload thread... + if (dialogThread = PhCreateThread(0, UploadFileThreadStart, (PVOID)context)) + NtClose(dialogThread); + } + else + { + context->UploadServiceState = PhUploadServiceMaximum; + DestroyWindow(hwndDlg); + } + } + break; + case IDCANCEL: + { + context->UploadServiceState = PhUploadServiceMaximum; + DestroyWindow(hwndDlg); + } + } + break; + } + break; + case WM_CTLCOLORBTN: + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + HWND hwndChild = (HWND)lParam; + + // Check for our static label and change the color. + if (GetDlgCtrlID(hwndChild) == IDC_MESSAGE) + { + SetTextColor(hDC, RGB(19, 112, 171)); + } + + // Set a transparent background for the control backcolor. + SetBkMode(hDC, TRANSPARENT); + + // set window background color. + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + } + break; + case UM_EXISTS: + { + context->UploadServiceState = PhUploadServiceViewReport; + + Static_SetText(GetDlgItem(hwndDlg, IDNO), L"No"); + Control_Visible(GetDlgItem(hwndDlg, IDYES), TRUE); + + Static_SetText(context->MessageHandle, L"File already analysed."); + Static_SetText(context->StatusHandle, L"View existing report?"); + } + break; + case UM_LAUNCH: + { + context->UploadServiceState = PhUploadServiceMaximum; + + if (!PhIsNullOrEmptyString(context->LaunchCommand)) + { + PhShellExecute(hwndDlg, context->LaunchCommand->Buffer, NULL); + } + + DestroyWindow(hwndDlg); + } + break; + case UM_ERROR: + { + PPH_STRING errorMessage = (PPH_STRING)lParam; + + context->UploadServiceState = PhUploadServiceMaximum; + + if (errorMessage) + { + Static_SetText(GetDlgItem(hwndDlg, IDC_MESSAGE), errorMessage->Buffer); + PhClearReference(&errorMessage); + } + else + { + Static_SetText(GetDlgItem(hwndDlg, IDC_MESSAGE), L"Error"); + } + + Static_SetText(GetDlgItem(hwndDlg, IDC_STATUS), L""); + Static_SetText(GetDlgItem(hwndDlg, IDNO), L"Close"); + } + break; + } + + return FALSE; +} + +NTSTATUS PhUploadToDialogThreadStart( + _In_ PVOID Parameter + ) +{ + BOOL result; + MSG message; + HWND dialogHandle; + PH_AUTO_POOL autoPool; + PUPLOAD_CONTEXT context = (PUPLOAD_CONTEXT)Parameter; + + PhInitializeAutoPool(&autoPool); + + dialogHandle = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_PROGRESS), + PhMainWndHandle, + UploadDlgProc, + (LPARAM)Parameter + ); + + ShowWindow(dialogHandle, SW_SHOW); + SetForegroundWindow(dialogHandle); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!IsDialogMessage(dialogHandle, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +VOID UploadToOnlineService( + _In_ PPH_STRING FileName, + _In_ ULONG Service + ) +{ + HANDLE dialogThread = NULL; + PUPLOAD_CONTEXT context; + + context = (PUPLOAD_CONTEXT)PhAllocate(sizeof(UPLOAD_CONTEXT)); + memset(context, 0, sizeof(UPLOAD_CONTEXT)); + + context->Service = Service; + context->FileName = PhDuplicateString(FileName); + context->BaseFileName = PhGetBaseName(context->FileName); + + if (dialogThread = PhCreateThread(0, PhUploadToDialogThreadStart, (PVOID)context)) + NtClose(dialogThread); +} \ No newline at end of file diff --git a/plugins/Plugins.props b/plugins/Plugins.props new file mode 100644 index 0000000..16e6bc2 --- /dev/null +++ b/plugins/Plugins.props @@ -0,0 +1,122 @@ + + + + + + $(SolutionDir)..\bin\$(Configuration)$(PlatformArchitecture)\plugins\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + NativeRecommendedRules.ruleset + true + false + true + false + false + true + + $(SolutionDir)..\bin\$(Configuration)$(PlatformArchitecture)\ProcessHacker.exe + $(SolutionDir)..\bin\$(Configuration)$(PlatformArchitecture)\ + WindowsLocalDebugger + + + + + + ..\include;..\..\sdk\include;%(AdditionalIncludeDirectories) + true + Level3 + true + StdCall + false + true + ProgramDatabase + + + ProcessHacker.lib;ntdll.lib;%(AdditionalDependencies) + true + Windows + + + $(IntDir)$(MSBuildProjectName).log + + + + + + + Disabled + MultiThreadedDebug + false + false + EnableFastChecks + + + + + + + MaxSpeed + MultiThreaded + true + true + AnySuitable + true + true + + + true + true + true + UseLinkTimeCodeGeneration + + + + + + + StreamingSIMDExtensions + + + ..\..\sdk\lib\i386;%(AdditionalLibraryDirectories) + 5.01 + MachineX86 + + + + + + + ..\..\sdk\lib\amd64;%(AdditionalLibraryDirectories) + 5.02 + MachineX64 + + + + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + + + + + + + WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + + + + + + + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + + + + + + + WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + + + + diff --git a/plugins/Plugins.sln b/plugins/Plugins.sln new file mode 100644 index 0000000..991d715 --- /dev/null +++ b/plugins/Plugins.sln @@ -0,0 +1,145 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{53C3AE07-D96F-4F5C-B407-4195084472CF}" + ProjectSection(SolutionItems) = preProject + include\commonutil.h = include\commonutil.h + Plugins.props = Plugins.props + include\toolstatusintf.h = include\toolstatusintf.h + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SbieSupport", "SbieSupport\SbieSupport.vcxproj", "{EEF1E81D-D286-422A-89E6-C6C8F3BE648A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExtendedNotifications", "ExtendedNotifications\ExtendedNotifications.vcxproj", "{80E791B8-AC98-407E-8FF9-5154AF50E887}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ToolStatus", "ToolStatus\ToolStatus.vcxproj", "{60B43533-C75E-4741-9E19-C4D581BEF51C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExtendedServices", "ExtendedServices\ExtendedServices.vcxproj", "{AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NetworkTools", "NetworkTools\NetworkTools.vcxproj", "{B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OnlineChecks", "OnlineChecks\OnlineChecks.vcxproj", "{79D24223-1122-40A9-BC8F-46A2089FE089}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExtendedTools", "ExtendedTools\ExtendedTools.vcxproj", "{3437FD16-A3BF-4938-883A-EB0DF1584A2A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowExplorer", "WindowExplorer\WindowExplorer.vcxproj", "{37488DC1-E45F-4626-A87C-3A854A153D1A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DotNetTools", "DotNetTools\DotNetTools.vcxproj", "{2B3235B0-31E1-44DA-81A1-183F40517E47}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "Updater\Updater.vcxproj", "{A0C1595C-FA3E-4B7A-936C-306BC6294C5E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UserNotes", "UserNotes\UserNotes.vcxproj", "{7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HardwareDevices", "HardwareDevices\HardwareDevices.vcxproj", "{5F0D72C4-8319-4B61-9E13-6084B680EB90}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Debug|Win32.ActiveCfg = Debug|Win32 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Debug|Win32.Build.0 = Debug|Win32 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Debug|x64.ActiveCfg = Debug|x64 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Debug|x64.Build.0 = Debug|x64 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Release|Win32.ActiveCfg = Release|Win32 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Release|Win32.Build.0 = Release|Win32 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Release|x64.ActiveCfg = Release|x64 + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A}.Release|x64.Build.0 = Release|x64 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Debug|Win32.ActiveCfg = Debug|Win32 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Debug|Win32.Build.0 = Debug|Win32 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Debug|x64.ActiveCfg = Debug|x64 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Debug|x64.Build.0 = Debug|x64 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Release|Win32.ActiveCfg = Release|Win32 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Release|Win32.Build.0 = Release|Win32 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Release|x64.ActiveCfg = Release|x64 + {80E791B8-AC98-407E-8FF9-5154AF50E887}.Release|x64.Build.0 = Release|x64 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Debug|Win32.ActiveCfg = Debug|Win32 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Debug|Win32.Build.0 = Debug|Win32 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Debug|x64.ActiveCfg = Debug|x64 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Debug|x64.Build.0 = Debug|x64 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Release|Win32.ActiveCfg = Release|Win32 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Release|Win32.Build.0 = Release|Win32 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Release|x64.ActiveCfg = Release|x64 + {60B43533-C75E-4741-9E19-C4D581BEF51C}.Release|x64.Build.0 = Release|x64 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Debug|Win32.ActiveCfg = Debug|Win32 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Debug|Win32.Build.0 = Debug|Win32 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Debug|x64.ActiveCfg = Debug|x64 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Debug|x64.Build.0 = Debug|x64 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Release|Win32.ActiveCfg = Release|Win32 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Release|Win32.Build.0 = Release|Win32 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Release|x64.ActiveCfg = Release|x64 + {AC547368-D861-4B88-B5D4-E3B1F1AEEEDD}.Release|x64.Build.0 = Release|x64 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Debug|Win32.ActiveCfg = Debug|Win32 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Debug|Win32.Build.0 = Debug|Win32 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Debug|x64.ActiveCfg = Debug|x64 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Debug|x64.Build.0 = Debug|x64 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Release|Win32.ActiveCfg = Release|Win32 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Release|Win32.Build.0 = Release|Win32 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Release|x64.ActiveCfg = Release|x64 + {B5E0DA09-EA01-4D5A-A9D6-5B22DB0C306E}.Release|x64.Build.0 = Release|x64 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Debug|Win32.ActiveCfg = Debug|Win32 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Debug|Win32.Build.0 = Debug|Win32 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Debug|x64.ActiveCfg = Debug|x64 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Debug|x64.Build.0 = Debug|x64 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Release|Win32.ActiveCfg = Release|Win32 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Release|Win32.Build.0 = Release|Win32 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Release|x64.ActiveCfg = Release|x64 + {79D24223-1122-40A9-BC8F-46A2089FE089}.Release|x64.Build.0 = Release|x64 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Debug|Win32.ActiveCfg = Debug|Win32 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Debug|Win32.Build.0 = Debug|Win32 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Debug|x64.ActiveCfg = Debug|x64 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Debug|x64.Build.0 = Debug|x64 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Release|Win32.ActiveCfg = Release|Win32 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Release|Win32.Build.0 = Release|Win32 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Release|x64.ActiveCfg = Release|x64 + {3437FD16-A3BF-4938-883A-EB0DF1584A2A}.Release|x64.Build.0 = Release|x64 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Debug|Win32.ActiveCfg = Debug|Win32 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Debug|Win32.Build.0 = Debug|Win32 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Debug|x64.ActiveCfg = Debug|x64 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Debug|x64.Build.0 = Debug|x64 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Release|Win32.ActiveCfg = Release|Win32 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Release|Win32.Build.0 = Release|Win32 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Release|x64.ActiveCfg = Release|x64 + {37488DC1-E45F-4626-A87C-3A854A153D1A}.Release|x64.Build.0 = Release|x64 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Debug|Win32.ActiveCfg = Debug|Win32 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Debug|Win32.Build.0 = Debug|Win32 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Debug|x64.ActiveCfg = Debug|x64 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Debug|x64.Build.0 = Debug|x64 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Release|Win32.ActiveCfg = Release|Win32 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Release|Win32.Build.0 = Release|Win32 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Release|x64.ActiveCfg = Release|x64 + {2B3235B0-31E1-44DA-81A1-183F40517E47}.Release|x64.Build.0 = Release|x64 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Debug|Win32.Build.0 = Debug|Win32 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Debug|x64.ActiveCfg = Debug|x64 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Debug|x64.Build.0 = Debug|x64 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Release|Win32.ActiveCfg = Release|Win32 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Release|Win32.Build.0 = Release|Win32 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Release|x64.ActiveCfg = Release|x64 + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E}.Release|x64.Build.0 = Release|x64 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Debug|Win32.ActiveCfg = Debug|Win32 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Debug|Win32.Build.0 = Debug|Win32 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Debug|x64.ActiveCfg = Debug|x64 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Debug|x64.Build.0 = Debug|x64 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Release|Win32.ActiveCfg = Release|Win32 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Release|Win32.Build.0 = Release|Win32 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Release|x64.ActiveCfg = Release|x64 + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF}.Release|x64.Build.0 = Release|x64 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Debug|Win32.ActiveCfg = Debug|Win32 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Debug|Win32.Build.0 = Debug|Win32 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Debug|x64.ActiveCfg = Debug|x64 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Debug|x64.Build.0 = Debug|x64 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Release|Win32.ActiveCfg = Release|Win32 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Release|Win32.Build.0 = Release|Win32 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Release|x64.ActiveCfg = Release|x64 + {5F0D72C4-8319-4B61-9E13-6084B680EB90}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugins/SamplePlugin/SamplePlugin.sln b/plugins/SamplePlugin/SamplePlugin.sln new file mode 100644 index 0000000..947d0cd --- /dev/null +++ b/plugins/SamplePlugin/SamplePlugin.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SamplePlugin", "SamplePlugin.vcxproj", "{C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Debug|Win32.ActiveCfg = Debug|Win32 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Debug|Win32.Build.0 = Debug|Win32 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Debug|x64.ActiveCfg = Debug|x64 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Debug|x64.Build.0 = Debug|x64 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Release|Win32.ActiveCfg = Release|Win32 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Release|Win32.Build.0 = Release|Win32 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Release|x64.ActiveCfg = Release|x64 + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/plugins/SamplePlugin/SamplePlugin.vcxproj b/plugins/SamplePlugin/SamplePlugin.vcxproj new file mode 100644 index 0000000..876d3ba --- /dev/null +++ b/plugins/SamplePlugin/SamplePlugin.vcxproj @@ -0,0 +1,172 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {C74D269B-3FCC-4C3E-93C7-1B4A94E7BBEE} + SamplePlugin + Win32Proj + 10.0.10586.0 + + + + DynamicLibrary + Unicode + true + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + true + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + + + + + + + + + + + + $(ProjectDir)\bin\$(Configuration)32\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(ProjectDir)\bin\$(Configuration)64\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(ProjectDir)\bin\$(Configuration)32\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(ProjectDir)\bin\$(Configuration)64\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + + + + Disabled + ../../sdk/include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Level3 + EditAndContinue + StdCall + + + ProcessHacker.lib;ntdll.lib;%(AdditionalDependencies) + ../../sdk/lib/i386;%(AdditionalLibraryDirectories) + true + Windows + MachineX86 + + + + + Disabled + ../../sdk/include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + + + ProcessHacker.lib;ntdll.lib;%(AdditionalDependencies) + ../../sdk/lib/amd64;%(AdditionalLibraryDirectories) + true + Windows + MachineX64 + + + + + MaxSpeed + true + ../../sdk/include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + + + ProcessHacker.lib;ntdll.lib;%(AdditionalDependencies) + ../../sdk/lib/i386;%(AdditionalLibraryDirectories) + true + Windows + true + true + MachineX86 + true + + + + + MaxSpeed + true + ../../sdk/include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + + + ProcessHacker.lib;ntdll.lib;%(AdditionalDependencies) + ../../sdk/lib/amd64;%(AdditionalLibraryDirectories) + true + Windows + true + true + MachineX64 + true + + + + + + + + + \ No newline at end of file diff --git a/plugins/SamplePlugin/SamplePlugin.vcxproj.filters b/plugins/SamplePlugin/SamplePlugin.vcxproj.filters new file mode 100644 index 0000000..533d3fb --- /dev/null +++ b/plugins/SamplePlugin/SamplePlugin.vcxproj.filters @@ -0,0 +1,14 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + \ No newline at end of file diff --git a/plugins/SamplePlugin/bin/Release32/SamplePlugin.dll b/plugins/SamplePlugin/bin/Release32/SamplePlugin.dll new file mode 100644 index 0000000..8f8d896 Binary files /dev/null and b/plugins/SamplePlugin/bin/Release32/SamplePlugin.dll differ diff --git a/plugins/SamplePlugin/bin/Release64/SamplePlugin.dll b/plugins/SamplePlugin/bin/Release64/SamplePlugin.dll new file mode 100644 index 0000000..e1e254a Binary files /dev/null and b/plugins/SamplePlugin/bin/Release64/SamplePlugin.dll differ diff --git a/plugins/SamplePlugin/main.c b/plugins/SamplePlugin/main.c new file mode 100644 index 0000000..45a9349 --- /dev/null +++ b/plugins/SamplePlugin/main.c @@ -0,0 +1,261 @@ +#include + +#define ID_SAMPLE_MENU_ITEM 1 +#define ID_SHOW_ME_SOME_OBJECTS 2 + +VOID LoadCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +VOID ShowOptionsCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +VOID MenuItemCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +VOID MainWindowShowingCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +VOID GetProcessHighlightingColorCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +VOID GetProcessTooltipTextCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ); + +PPH_PLUGIN PluginInstance; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +PH_CALLBACK_REGISTRATION GetProcessHighlightingColorCallbackRegistration; +PH_CALLBACK_REGISTRATION GetProcessTooltipTextCallbackRegistration; + +LOGICAL DllMain( + __in HINSTANCE Instance, + __in ULONG Reason, + __reserved PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + + // Register your plugin with a unique name, otherwise it will fail. + PluginInstance = PhRegisterPlugin(L"YourName.SamplePlugin", Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Sample Plugin"; + info->Author = L"Someone"; + info->Description = L"Description goes here"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackGetProcessHighlightingColor), + GetProcessHighlightingColorCallback, + NULL, + &GetProcessHighlightingColorCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackGetProcessTooltipText), + GetProcessTooltipTextCallback, + NULL, + &GetProcessTooltipTextCallbackRegistration + ); + + // Add some settings. Note that we cannot access these settings + // in DllMain. Settings must be added in DllMain. + { + static PH_SETTING_CREATE settings[] = + { + // You must prepend your plugin name to the setting names. + { IntegerSettingType, L"ProcessHacker.SamplePlugin.SomeInteger", L"1234" }, + { StringSettingType, L"ProcessHacker.SamplePlugin.SomeString", L"my string" } + }; + + PhAddSettings(settings, sizeof(settings) / sizeof(PH_SETTING_CREATE)); + } + } + break; + } + + return TRUE; +} + +VOID LoadCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + ULONG myInteger; + PPH_STRING myString; + + myInteger = PhGetIntegerSetting(L"ProcessHacker.SamplePlugin.SomeInteger"); + // Do stuff to the integer. Possibly modify the setting. + PhSetIntegerSetting(L"ProcessHacker.SamplePlugin.SomeInteger", myInteger + 100); + + myString = PhGetStringSetting(L"ProcessHacker.SamplePlugin.SomeString"); + // Do stuff to the string. + // Dereference the string when you're done, or memory will be leaked. + PhDereferenceObject(myString); +} + +VOID ShowOptionsCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + PhShowError((HWND)Parameter, L"Show some options here."); +} + +BOOLEAN NTAPI EnumDirectoryObjectsCallback( + __in PPH_STRINGREF Name, + __in PPH_STRINGREF TypeName, + __in_opt PVOID Context + ) +{ + INT result; + PPH_STRING name; + PPH_STRING typeName; + + name = PhCreateString2(Name); + typeName = PhCreateString2(TypeName); + result = PhShowMessage( + PhMainWndHandle, + MB_ICONINFORMATION | MB_OKCANCEL, + L"%s: %s", + name->Buffer, + typeName->Buffer + ); + PhDereferenceObject(name); + PhDereferenceObject(typeName); + + return result == IDOK; +} + +VOID MenuItemCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + case ID_SAMPLE_MENU_ITEM: + { + PhShowInformation(PhMainWndHandle, L"You clicked the sample menu item!"); + } + break; + case ID_SHOW_ME_SOME_OBJECTS: + { + NTSTATUS status; + HANDLE directoryHandle; + OBJECT_ATTRIBUTES oa; + UNICODE_STRING name; + + // Use the Native API seamlessly alongside Win32. + RtlInitUnicodeString(&name, L"\\"); + InitializeObjectAttributes(&oa, &name, 0, NULL, NULL); + + if (NT_SUCCESS(status = NtOpenDirectoryObject(&directoryHandle, DIRECTORY_QUERY, &oa))) + { + PhEnumDirectoryObjects(directoryHandle, EnumDirectoryObjectsCallback, NULL); + NtClose(directoryHandle); + } + } + break; + } +} + +VOID MainWindowShowingCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + // $ won't match anything, so the menu item will get added to the end. + PhPluginAddMenuItem(PluginInstance, PH_MENU_ITEM_LOCATION_TOOLS, L"$", + ID_SAMPLE_MENU_ITEM, L"Sample menu item", NULL); + PhPluginAddMenuItem(PluginInstance, PH_MENU_ITEM_LOCATION_TOOLS, L"$", + ID_SHOW_ME_SOME_OBJECTS, L"Show me some objects", NULL); +} + +VOID GetProcessHighlightingColorCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + PPH_PLUGIN_GET_HIGHLIGHTING_COLOR getHighlightingColor = Parameter; + PPH_PROCESS_ITEM processItem; + + processItem = getHighlightingColor->Parameter; + + // Optional: if another plugin handled the highlighting, don't override it. + if (getHighlightingColor->Handled) + return; + + // Set the background color of svchost.exe processes to black. + if (PhEqualString2(processItem->ProcessName, L"svchost.exe", TRUE)) + { + getHighlightingColor->BackColor = RGB(0x00, 0x00, 0x00); + getHighlightingColor->Cache = TRUE; + getHighlightingColor->Handled = TRUE; + } +} + +VOID GetProcessTooltipTextCallback( + __in_opt PVOID Parameter, + __in_opt PVOID Context + ) +{ + PPH_PLUGIN_GET_TOOLTIP_TEXT getTooltipText = Parameter; + PPH_PROCESS_ITEM processItem; + + processItem = getTooltipText->Parameter; + + // Put some text into the tooltip. This will go in just before the Notes section. + PhAppendFormatStringBuilder( + getTooltipText->StringBuilder, + L"Sample plugin:\n The process name is: %s\n", + processItem->ProcessName->Buffer + ); +} diff --git a/plugins/SbieSupport/SbieSupport.rc b/plugins/SbieSupport/SbieSupport.rc new file mode 100644 index 0000000..02871e9 --- /dev/null +++ b/plugins/SbieSupport/SbieSupport.rc @@ -0,0 +1,146 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Sandboxie Support for Process Hacker" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "SbieSupport" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "SbieSupport.dll" + VALUE "ProductName", "Sandboxie Support for Process Hacker" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS DIALOGEX 0, 0, 268, 50 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "SbieDll.dll path:",IDC_STATIC,7,9,50,8 + EDITTEXT IDC_SBIEDLLPATH,63,8,143,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,211,7,50,14 + DEFPUSHBUTTON "OK",IDOK,158,29,50,14 + PUSHBUTTON "Cancel",IDCANCEL,211,29,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 261 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/SbieSupport/SbieSupport.vcxproj b/plugins/SbieSupport/SbieSupport.vcxproj new file mode 100644 index 0000000..9ce1aa8 --- /dev/null +++ b/plugins/SbieSupport/SbieSupport.vcxproj @@ -0,0 +1,77 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {EEF1E81D-D286-422A-89E6-C6C8F3BE648A} + SbieSupport + Win32Proj + SbieSupport + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/SbieSupport/SbieSupport.vcxproj.filters b/plugins/SbieSupport/SbieSupport.vcxproj.filters new file mode 100644 index 0000000..279b664 --- /dev/null +++ b/plugins/SbieSupport/SbieSupport.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/SbieSupport/main.c b/plugins/SbieSupport/main.c new file mode 100644 index 0000000..211009d --- /dev/null +++ b/plugins/SbieSupport/main.c @@ -0,0 +1,551 @@ +/* + * Process Hacker Sandboxie Support - + * main program + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include "resource.h" +#include "sbiedll.h" + +typedef struct _BOX_INFO +{ + WCHAR BoxName[34]; + PH_STRINGREF IpcRoot; + WCHAR IpcRootBuffer[256]; +} BOX_INFO, *PBOX_INFO; + +typedef struct _BOXED_PROCESS +{ + HANDLE ProcessId; + WCHAR BoxName[34]; +} BOXED_PROCESS, *PBOXED_PROCESS; + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI MainMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI GetProcessHighlightingColorCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI GetProcessTooltipTextCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI GetIsDotNetDirectoryNamesCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI RefreshSandboxieInfo( + _In_opt_ PVOID Context, + _In_ BOOLEAN TimerOrWaitFired + ); + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PPH_PLUGIN PluginInstance; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +PH_CALLBACK_REGISTRATION MainMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +PH_CALLBACK_REGISTRATION GetProcessHighlightingColorCallbackRegistration; +PH_CALLBACK_REGISTRATION GetProcessTooltipTextCallbackRegistration; + +P_SbieApi_QueryBoxPath SbieApi_QueryBoxPath; +P_SbieApi_EnumBoxes SbieApi_EnumBoxes; +P_SbieApi_EnumProcessEx SbieApi_EnumProcessEx; +P_SbieDll_KillAll SbieDll_KillAll; + +PPH_HASHTABLE BoxedProcessesHashtable; +PH_QUEUED_LOCK BoxedProcessesLock = PH_QUEUED_LOCK_INIT; +BOOLEAN BoxedProcessesUpdated = FALSE; + +BOX_INFO BoxInfo[16]; +ULONG BoxInfoCount; + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Sandboxie Support"; + info->Author = L"wj32"; + info->Description = L"Provides functionality for sandboxed processes."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1115"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainMenuInitializing), + MainMenuInitializingCallback, + NULL, + &MainMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessesUpdated), + ProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackGetProcessHighlightingColor), + GetProcessHighlightingColorCallback, + NULL, + &GetProcessHighlightingColorCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackGetProcessTooltipText), + GetProcessTooltipTextCallback, + NULL, + &GetProcessTooltipTextCallbackRegistration + ); + + { + static PH_SETTING_CREATE settings[] = + { + { StringSettingType, SETTING_NAME_SBIE_DLL_PATH, L"C:\\Program Files\\Sandboxie\\SbieDll.dll" } + }; + + PhAddSettings(settings, sizeof(settings) / sizeof(PH_SETTING_CREATE)); + } + } + break; + } + + return TRUE; +} + +BOOLEAN NTAPI BoxedProcessesEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + return ((PBOXED_PROCESS)Entry1)->ProcessId == ((PBOXED_PROCESS)Entry2)->ProcessId; +} + +ULONG NTAPI BoxedProcessesHashFunction( + _In_ PVOID Entry + ) +{ + return HandleToUlong(((PBOXED_PROCESS)Entry)->ProcessId) / 4; +} + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_STRING sbieDllPath; + HMODULE module; + HANDLE timerQueueHandle; + HANDLE timerHandle; + + BoxedProcessesHashtable = PhCreateHashtable( + sizeof(BOXED_PROCESS), + BoxedProcessesEqualFunction, + BoxedProcessesHashFunction, + 32 + ); + + sbieDllPath = PhaGetStringSetting(SETTING_NAME_SBIE_DLL_PATH); + module = LoadLibrary(sbieDllPath->Buffer); + + SbieApi_QueryBoxPath = PhGetProcedureAddress(module, SbieApi_QueryBoxPath_Name, 0); + SbieApi_EnumBoxes = PhGetProcedureAddress(module, SbieApi_EnumBoxes_Name, 0); + SbieApi_EnumProcessEx = PhGetProcedureAddress(module, SbieApi_EnumProcessEx_Name, 0); + SbieDll_KillAll = PhGetProcedureAddress(module, SbieDll_KillAll_Name, 0); + + if (NT_SUCCESS(RtlCreateTimerQueue(&timerQueueHandle))) + { + RtlCreateTimer(timerQueueHandle, &timerHandle, RefreshSandboxieInfo, NULL, 0, 4000, 0); + } +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + (HWND)Parameter, + OptionsDlgProc + ); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + case 1: + { + if (PhShowConfirmMessage( + PhMainWndHandle, + L"terminate", + L"all sandboxed processes", + NULL, + FALSE + )) + { + PBOXED_PROCESS boxedProcess; + ULONG enumerationKey = 0; + + // Make sure we have an update-to-date list. + RefreshSandboxieInfo(NULL, FALSE); + + PhAcquireQueuedLockShared(&BoxedProcessesLock); + + while (PhEnumHashtable(BoxedProcessesHashtable, &boxedProcess, &enumerationKey)) + { + HANDLE processHandle; + + if (NT_SUCCESS(PhOpenProcess(&processHandle, PROCESS_TERMINATE, boxedProcess->ProcessId))) + { + PhTerminateProcess(processHandle, STATUS_SUCCESS); + NtClose(processHandle); + } + } + + PhReleaseQueuedLockShared(&BoxedProcessesLock); + } + } + break; + } +} + +VOID NTAPI MainMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + + if (!SbieDll_KillAll) + return; + if (menuInfo->u.MainMenu.SubMenuIndex != PH_MENU_ITEM_LOCATION_TOOLS) + return; + + PhInsertEMenuItem(menuInfo->Menu, PhPluginCreateEMenuItem(PluginInstance, PH_EMENU_SEPARATOR, 0, NULL, NULL), -1); + PhInsertEMenuItem(menuInfo->Menu, PhPluginCreateEMenuItem(PluginInstance, 0, 1, L"Terminate sandboxed processes", NULL), -1); +} + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PBOXED_PROCESS boxedProcess; + ULONG enumerationKey = 0; + + if (BoxedProcessesUpdated) + { + // Invalidate the nodes of boxed processes (so they use the correct highlighting color). + + PhAcquireQueuedLockShared(&BoxedProcessesLock); + + if (BoxedProcessesUpdated) + { + while (PhEnumHashtable(BoxedProcessesHashtable, &boxedProcess, &enumerationKey)) + { + PPH_PROCESS_NODE processNode; + + if (processNode = PhFindProcessNode(boxedProcess->ProcessId)) + PhUpdateProcessNode(processNode); + } + + BoxedProcessesUpdated = FALSE; + } + + PhReleaseQueuedLockShared(&BoxedProcessesLock); + } +} + +VOID NTAPI GetProcessHighlightingColorCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_GET_HIGHLIGHTING_COLOR getHighlightingColor = Parameter; + BOXED_PROCESS lookupBoxedProcess; + PBOXED_PROCESS boxedProcess; + + PhAcquireQueuedLockShared(&BoxedProcessesLock); + + lookupBoxedProcess.ProcessId = ((PPH_PROCESS_ITEM)getHighlightingColor->Parameter)->ProcessId; + + if (boxedProcess = PhFindEntryHashtable(BoxedProcessesHashtable, &lookupBoxedProcess)) + { + getHighlightingColor->BackColor = RGB(0x33, 0x33, 0x00); + getHighlightingColor->Cache = TRUE; + getHighlightingColor->Handled = TRUE; + } + + PhReleaseQueuedLockShared(&BoxedProcessesLock); +} + +VOID NTAPI GetProcessTooltipTextCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_GET_TOOLTIP_TEXT getTooltipText = Parameter; + BOXED_PROCESS lookupBoxedProcess; + PBOXED_PROCESS boxedProcess; + + PhAcquireQueuedLockShared(&BoxedProcessesLock); + + lookupBoxedProcess.ProcessId = ((PPH_PROCESS_ITEM)getTooltipText->Parameter)->ProcessId; + + if (boxedProcess = PhFindEntryHashtable(BoxedProcessesHashtable, &lookupBoxedProcess)) + { + PhAppendFormatStringBuilder(getTooltipText->StringBuilder, L"Sandboxie:\n Box name: %s\n", boxedProcess->BoxName); + } + + PhReleaseQueuedLockShared(&BoxedProcessesLock); +} + +VOID NTAPI RefreshSandboxieInfo( + _In_opt_ PVOID Context, + _In_ BOOLEAN TimerOrWaitFired + ) +{ + LONG index; + WCHAR boxName[34]; + ULONG pids[512]; + PBOX_INFO boxInfo; + + if (!SbieApi_QueryBoxPath || !SbieApi_EnumBoxes || !SbieApi_EnumProcessEx) + return; + + PhAcquireQueuedLockExclusive(&BoxedProcessesLock); + + PhClearHashtable(BoxedProcessesHashtable); + + BoxInfoCount = 0; + + index = -1; + + while ((index = SbieApi_EnumBoxes(index, boxName)) != -1) + { + if (SbieApi_EnumProcessEx(boxName, TRUE, 0, pids) == 0) + { + ULONG count; + PULONG pid; + + count = pids[0]; + pid = &pids[1]; + + while (count != 0) + { + BOXED_PROCESS boxedProcess; + + boxedProcess.ProcessId = UlongToHandle(*pid); + memcpy(boxedProcess.BoxName, boxName, sizeof(boxName)); + + PhAddEntryHashtable(BoxedProcessesHashtable, &boxedProcess); + + count--; + pid++; + } + } + + if (BoxInfoCount < 16) + { + ULONG filePathLength = 0; + ULONG keyPathLength = 0; + ULONG ipcPathLength = 0; + + boxInfo = &BoxInfo[BoxInfoCount++]; + memcpy(boxInfo->BoxName, boxName, sizeof(boxName)); + + SbieApi_QueryBoxPath( + boxName, + NULL, + NULL, + NULL, + &filePathLength, + &keyPathLength, + &ipcPathLength + ); + + if (ipcPathLength < sizeof(boxInfo->IpcRootBuffer)) + { + boxInfo->IpcRootBuffer[0] = 0; + SbieApi_QueryBoxPath( + boxName, + NULL, + NULL, + boxInfo->IpcRootBuffer, + NULL, + NULL, + &ipcPathLength + ); + + if (boxInfo->IpcRootBuffer[0] != 0) + { + PhInitializeStringRef(&boxInfo->IpcRoot, boxInfo->IpcRootBuffer); + } + else + { + BoxInfoCount--; + } + } + else + { + BoxInfoCount--; + } + } + } + + BoxedProcessesUpdated = TRUE; + + PhReleaseQueuedLockExclusive(&BoxedProcessesLock); +} + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_STRING sbieDllPath; + + sbieDllPath = PhaGetStringSetting(SETTING_NAME_SBIE_DLL_PATH); + SetDlgItemText(hwndDlg, IDC_SBIEDLLPATH, sbieDllPath->Buffer); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PhSetStringSetting2(SETTING_NAME_SBIE_DLL_PATH, + &PhaGetDlgItemText(hwndDlg, IDC_SBIEDLLPATH)->sr); + + EndDialog(hwndDlg, IDOK); + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"SbieDll.dll", L"SbieDll.dll" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PH_AUTO(PhGetFileName(PhaGetDlgItemText(hwndDlg, IDC_SBIEDLLPATH))); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + SetDlgItemText(hwndDlg, IDC_SBIEDLLPATH, fileName->Buffer); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/SbieSupport/resource.h b/plugins/SbieSupport/resource.h new file mode 100644 index 0000000..b338721 --- /dev/null +++ b/plugins/SbieSupport/resource.h @@ -0,0 +1,18 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by SbieSupport.rc +// +#define IDD_OPTIONS 101 +#define IDC_SBIEDLLPATH 1001 +#define IDC_BROWSE 1002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/SbieSupport/sbiedll.h b/plugins/SbieSupport/sbiedll.h new file mode 100644 index 0000000..fab2af6 --- /dev/null +++ b/plugins/SbieSupport/sbiedll.h @@ -0,0 +1,42 @@ +#ifndef SBIEDLL_H +#define SBIEDLL_H + +#define PLUGIN_NAME L"ProcessHacker.SbieSupport" +#define SETTING_NAME_SBIE_DLL_PATH (PLUGIN_NAME L".SbieDllPath") + +typedef LONG (__stdcall *P_SbieApi_QueryBoxPath)( + const WCHAR *box_name, // pointer to WCHAR [34] + WCHAR *file_path, + WCHAR *key_path, + WCHAR *ipc_path, + ULONG *file_path_len, + ULONG *key_path_len, + ULONG *ipc_path_len); + +typedef LONG (__stdcall *P_SbieApi_EnumBoxes)( + LONG index, // initialize to -1 + WCHAR *box_name); // pointer to WCHAR [34] + +typedef LONG (__stdcall *P_SbieApi_EnumProcessEx)( + const WCHAR *box_name, // pointer to WCHAR [34] + BOOLEAN all_sessions, + ULONG which_session, + ULONG *boxed_pids); // pointer to ULONG [512] + +typedef BOOLEAN (__stdcall *P_SbieDll_KillAll)( + ULONG session_id, + const WCHAR *box_name); + +#ifdef _WIN64 +#define SbieApi_QueryBoxPath_Name "SbieApi_QueryBoxPath" +#define SbieApi_EnumBoxes_Name "SbieApi_EnumBoxes" +#define SbieApi_EnumProcessEx_Name "SbieApi_EnumProcessEx" +#define SbieDll_KillAll_Name "SbieDll_KillAll" +#else +#define SbieApi_QueryBoxPath_Name "_SbieApi_QueryBoxPath@28" +#define SbieApi_EnumBoxes_Name "_SbieApi_EnumBoxes@8" +#define SbieApi_EnumProcessEx_Name "_SbieApi_EnumProcessEx@16" +#define SbieDll_KillAll_Name "_SbieDll_KillAll@8" +#endif + +#endif diff --git a/plugins/ToolStatus/CHANGELOG.txt b/plugins/ToolStatus/CHANGELOG.txt new file mode 100644 index 0000000..87015df --- /dev/null +++ b/plugins/ToolStatus/CHANGELOG.txt @@ -0,0 +1,64 @@ +2.4 + * Added 32x32 icons for high DPI displays + * Fixed status bar crash + +2.3 + * Added Auto-hide main menu + * Added CPU, Memory and IO graphs to main window + * Added Right-click menu on the Toolbar + * Added Toolbar customize dialog + * Added Statusbar customize dialog + +2.2 + * Added Modern Toolbar theme + * Added portable Toolbar customization + * Added Toolbar power menu (ported from PH1.x) + * Fixed Toolbar DPI scaling issues + +2.1 + * Fixed Auto-hide Searchbox (Ctrl+K to show) + +2.0 + * Added Toolbar customization + * Added Rebar Chevron support + * Added Auto-hide Searchbox + * Fixed searching multiple processes + +1.9 + * Added PNG images + * Fixed search box fonts + * Fixed plugin settings state (disable/enable) + * Updated Searchbox UI + * Updated Searchbox commands + * Updated plugin settings + +1.8 + * Added search box + +1.7 + * Added option to configure Toolbar display style. + +1.6 + * Fixed Always on Top being reset when using Find Window + * Fixed Esc key when using Find Window + +1.5 + * Added Find Window and Kill + +1.4 + * Added option to resolve ghost windows to the hung windows + they represent + +1.3 + * Fixed layout problems + +1.2 + * Added options + * Status bar panel widths are more stable + +1.1 + * Fixed hanging when using Find Window on hung programs + * Fixed status bar with no selected parts + +1.0 + * Initial release \ No newline at end of file diff --git a/plugins/ToolStatus/ToolStatus.rc b/plugins/ToolStatus/ToolStatus.rc new file mode 100644 index 0000000..2c65edc --- /dev/null +++ b/plugins/ToolStatus/ToolStatus.rc @@ -0,0 +1,294 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,4,0,0 + PRODUCTVERSION 2,4,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "dmex" + VALUE "FileDescription", "ToolStatus plugin for Process Hacker" + VALUE "FileVersion", "2.4" + VALUE "InternalName", "ToolStatus" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "ToolStatus.dll" + VALUE "ProductName", "ToolStatus plugin for Process Hacker" + VALUE "ProductVersion", "2.4" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS DIALOGEX 0, 0, 191, 103 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Enable toolbar",IDC_ENABLE_TOOLBAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,62,10 + CONTROL "Enable status bar",IDC_ENABLE_STATUSBAR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,19,72,10 + CONTROL "Resolve ghost windows to hung windows",IDC_RESOLVEGHOSTWINDOWS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,32,152,10 + CONTROL "Auto-hide main menu (Alt or F10 key toggle)",IDC_ENABLE_AUTOHIDE_MENU, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,44,157,10 + DEFPUSHBUTTON "OK",IDOK,79,82,50,14 + PUSHBUTTON "Cancel",IDCANCEL,133,82,50,14 + LTEXT "Note: Right-click the toolbar on the main window to customize the icons and graphs.",IDC_STATIC,7,59,157,18 +END + +IDD_CUSTOMIZE_TB DIALOGEX 0, 0, 369, 191 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Customize Toolbar" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Available toolbar buttons:",IDC_STATIC,7,7,85,8 + LISTBOX IDC_AVAILABLE,7,18,122,117,LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add >",IDC_ADD,133,50,50,14 + PUSHBUTTON "< Remove",IDC_REMOVE,133,68,50,14 + LTEXT "Current toolbar buttons:",IDC_STATIC,188,7,81,8 + LISTBOX IDC_CURRENT,187,18,122,117,LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Move up",IDC_MOVEUP,312,18,50,14 + PUSHBUTTON "Move down",IDC_MOVEDOWN,312,36,50,14 + LTEXT "Text options:",IDC_STATIC,7,142,44,8 + COMBOBOX IDC_TEXTOPTIONS,56,139,123,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Search box:",IDC_STATIC,7,158,45,8 + COMBOBOX IDC_SEARCHOPTIONS,56,155,123,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Enable modern icons",IDC_ENABLE_MODERN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,188,141,83,10 + CONTROL "Auto-hide main menu (Alt or F10 key toggle)",IDC_ENABLE_AUTOHIDE_MENU, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,188,154,157,10 + PUSHBUTTON "Reset",IDC_RESET,258,170,50,14 + DEFPUSHBUTTON "Close",IDCANCEL,312,170,50,14 + LTEXT "Theme:",IDC_STATIC,7,174,45,8,NOT WS_VISIBLE + COMBOBOX IDC_THEMEOPTIONS,56,171,123,30,CBS_DROPDOWNLIST | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP +END + +IDD_CUSTOMIZE_SB DIALOGEX 0, 0, 373, 142 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Customize Status Bar" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Available status bar entries:",-1,7,7,91,8 + LISTBOX IDC_AVAILABLE,7,18,122,117,LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add >",IDC_ADD,133,50,50,14 + PUSHBUTTON "< Remove",IDC_REMOVE,133,68,50,14 + LTEXT "Current status bar entries:",-1,188,7,87,8 + LISTBOX IDC_CURRENT,187,18,122,117,LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Move up",IDC_MOVEUP,316,18,50,14 + PUSHBUTTON "Move down",IDC_MOVEDOWN,316,36,50,14 + PUSHBUTTON "Reset",IDC_RESET,316,103,50,14 + DEFPUSHBUTTON "Close",IDCANCEL,316,121,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 184 + TOPMARGIN, 7 + BOTTOMMARGIN, 96 + END + + IDD_CUSTOMIZE_TB, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 362 + TOPMARGIN, 7 + BOTTOMMARGIN, 184 + END + + IDD_CUSTOMIZE_SB, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 366 + TOPMARGIN, 7 + BOTTOMMARGIN, 135 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDR_MAINWND_ACCEL ACCELERATORS +BEGIN + "K", ID_SEARCH, VIRTKEY, CONTROL, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// PNG +// + +IDB_SEARCH_ACTIVE PNG "resources\\active_search.png" + +IDB_SEARCH_INACTIVE PNG "resources\\inactive_search.png" + +IDB_APPLICATION_GET_MODERN PNG "resources\\application_get_modern.png" + +IDB_APPLICATION_GO_MODERN PNG "resources\\application_go_modern.png" + +IDB_APPLICATION_MODERN PNG "resources\\application_modern.png" + +IDB_ARROW_REFRESH_MODERN PNG "resources\\arrow_refresh_modern.png" + +IDB_CHART_LINE_MODERN PNG "resources\\chart_line_modern.png" + +IDB_COG_EDIT_MODERN PNG "resources\\cog_edit_modern.png" + +IDB_CROSS_MODERN PNG "resources\\cross_modern.png" + +IDB_FIND_MODERN PNG "resources\\find_modern.png" + +IDB_POWER_MODERN PNG "resources\\lightbulb_off_modern.png" + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_SEARCH_ACTIVE_BMP BITMAP "resources\\active_search.bmp" + +IDB_SEARCH_INACTIVE_BMP BITMAP "resources\\inactive_search.bmp" + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_CUSTOMIZE_TB AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_CUSTOMIZE_SB AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ARROW_REFRESH ICON "resources\\arrow_refresh.ico" + +IDI_COG_EDIT ICON "resources\\cog_edit.ico" + +IDI_FIND ICON "resources\\find.ico" + +IDI_CHART_LINE ICON "resources\\chart_line.ico" + +IDI_TBAPPLICATION ICON "resources\\application.ico" + +IDI_APPLICATION_GO ICON "resources\\application_go.ico" + +IDI_CROSS ICON "resources\\cross.ico" + +IDI_APPLICATION_GET ICON "resources\\application_get.ico" + +IDI_LIGHTBULB_OFF ICON "resources\\lightbulb_off.ico" + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/ToolStatus/ToolStatus.vcxproj b/plugins/ToolStatus/ToolStatus.vcxproj new file mode 100644 index 0000000..810f3eb --- /dev/null +++ b/plugins/ToolStatus/ToolStatus.vcxproj @@ -0,0 +1,132 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {60B43533-C75E-4741-9E19-C4D581BEF51C} + ToolStatus + Win32Proj + ToolStatus + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + WindowsCodecs.lib;uxtheme.lib;%(AdditionalDependencies) + + + + + WindowsCodecs.lib;uxtheme.lib;%(AdditionalDependencies) + + + + + WindowsCodecs.lib;uxtheme.lib;%(AdditionalDependencies) + + + + + WindowsCodecs.lib;uxtheme.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/ToolStatus/ToolStatus.vcxproj.filters b/plugins/ToolStatus/ToolStatus.vcxproj.filters new file mode 100644 index 0000000..466322c --- /dev/null +++ b/plugins/ToolStatus/ToolStatus.vcxproj.filters @@ -0,0 +1,133 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {6b19a091-ecf3-43f6-8756-eabd86ca6db7} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files\Images + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/ToolStatus/customizesb.c b/plugins/ToolStatus/customizesb.c new file mode 100644 index 0000000..ee55829 --- /dev/null +++ b/plugins/ToolStatus/customizesb.c @@ -0,0 +1,679 @@ +/* + * Process Hacker ToolStatus - + * Statusbar Customize Dialog + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" +#include "commonutil.h" + +BOOLEAN CustomizeStatusBarItemExists( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IdCommand + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + + buttonCount = ListBox_GetCount(Context->CurrentListHandle); + + if (buttonCount == LB_ERR) + return FALSE; + + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, buttonIndex); + + if (itemContext == NULL) + continue; + + if (itemContext->IdCommand == IdCommand) + return TRUE; + } + + return FALSE; +} + +VOID CustomizeInsertStatusBarItem( + _In_ INT Index, + _In_ PBUTTON_CONTEXT ItemContext + ) +{ + PSTATUSBAR_ITEM statusItem; + + statusItem = PhAllocate(sizeof(STATUSBAR_ITEM)); + memset(statusItem, 0, sizeof(STATUSBAR_ITEM)); + + statusItem->Id = ItemContext->IdCommand; + + PhInsertItemList(StatusBarItemList, Index, statusItem); + + StatusBarUpdate(TRUE); +} + +VOID CustomizeAddStatusBarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexAvail, + _In_ INT IndexTo + ) +{ + INT count; + PBUTTON_CONTEXT itemContext; + + count = ListBox_GetCount(Context->AvailableListHandle); + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->AvailableListHandle, IndexAvail); + + if (count == LB_ERR) + return; + + if (itemContext == NULL) + return; + + if (!itemContext->IsVirtual) + { + // remove from 'available' list + ListBox_DeleteString(Context->AvailableListHandle, IndexAvail); + + if (IndexAvail == count - 1) + { + ListBox_SetCurSel(Context->AvailableListHandle, IndexAvail - 1); + } + else + { + ListBox_SetCurSel(Context->AvailableListHandle, IndexAvail); + } + + // insert into 'current' list + ListBox_InsertItemData(Context->CurrentListHandle, IndexTo, itemContext); + + CustomizeInsertStatusBarItem(IndexTo, itemContext); + } + + SendMessage(Context->DialogHandle, WM_COMMAND, MAKEWPARAM(IDC_AVAILABLE, LBN_SELCHANGE), 0); +} + +VOID CustomizeRemoveStatusBarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexFrom + ) +{ + PBUTTON_CONTEXT itemContext; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, IndexFrom); + + if (itemContext == NULL) + return; + + ListBox_DeleteString(Context->CurrentListHandle, IndexFrom); + ListBox_SetCurSel(Context->CurrentListHandle, IndexFrom); + + PhRemoveItemList(StatusBarItemList, IndexFrom); + + if (!itemContext->IsVirtual) + { + INT count; + + count = ListBox_GetCount(Context->AvailableListHandle); + + if (count == LB_ERR) + count = 1; + + // insert into 'available' list + ListBox_InsertItemData(Context->AvailableListHandle, count - 1, itemContext); + } + + SendMessage(Context->DialogHandle, WM_COMMAND, MAKEWPARAM(IDC_CURRENT, LBN_SELCHANGE), 0); + + StatusBarUpdate(TRUE); +} + +VOID CustomizeMoveStatusBarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexFrom, + _In_ INT IndexTo + ) +{ + INT count; + PBUTTON_CONTEXT itemContext; + + if (IndexFrom == IndexTo) + return; + + count = ListBox_GetCount(Context->CurrentListHandle); + + if (count == LB_ERR) + return; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, IndexFrom); + + if (itemContext == NULL) + return; + + ListBox_DeleteString(Context->CurrentListHandle, IndexFrom); + ListBox_InsertItemData(Context->CurrentListHandle, IndexTo, itemContext); + ListBox_SetCurSel(Context->CurrentListHandle, IndexTo); + + if (IndexTo <= 0) + { + Button_Enable(Context->MoveUpButtonHandle, FALSE); + } + else + { + Button_Enable(Context->MoveUpButtonHandle, TRUE); + } + + // last item is always separator + if (IndexTo >= (count - 2)) + { + Button_Enable(Context->MoveDownButtonHandle, FALSE); + } + else + { + Button_Enable(Context->MoveDownButtonHandle, TRUE); + } + + PhRemoveItemList(StatusBarItemList, IndexFrom); + + CustomizeInsertStatusBarItem(IndexTo, itemContext); +} + +VOID CustomizeFreeStatusBarItems( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + + buttonCount = ListBox_GetCount(Context->CurrentListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, buttonIndex)) + { + PhFree(itemContext); + } + } + } + + buttonCount = ListBox_GetCount(Context->AvailableListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->AvailableListHandle, buttonIndex)) + { + PhFree(itemContext); + } + } + } +} + +VOID CustomizeLoadStatusBarItems( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + ULONG buttonIndex; + PBUTTON_CONTEXT itemContext; + + CustomizeFreeStatusBarItems(Context); + + ListBox_ResetContent(Context->AvailableListHandle); + ListBox_ResetContent(Context->CurrentListHandle); + + for (buttonIndex = 0; buttonIndex < StatusBarItemList->Count; buttonIndex++) + { + PSTATUSBAR_ITEM statusItem; + + statusItem = StatusBarItemList->Items[buttonIndex]; + + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + + itemContext->IdCommand = statusItem->Id; + + ListBox_AddItemData(Context->CurrentListHandle, itemContext); + } + + for (buttonIndex = 0; buttonIndex < MAX_STATUSBAR_ITEMS; buttonIndex++) + { + ULONG buttonId = StatusBarItems[buttonIndex]; + + if (CustomizeStatusBarItemExists(Context, buttonId)) + continue; + + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + + itemContext->IdCommand = buttonId; + + ListBox_AddItemData(Context->AvailableListHandle, itemContext); + } + + // Append separator to the last 'current list' position + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + itemContext->IsVirtual = TRUE; + + buttonIndex = ListBox_AddItemData(Context->CurrentListHandle, itemContext); + ListBox_SetCurSel(Context->CurrentListHandle, buttonIndex); + ListBox_SetTopIndex(Context->CurrentListHandle, buttonIndex); + + // Append separator to the last 'available list' position + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + itemContext->IsVirtual = TRUE; + + buttonIndex = ListBox_AddItemData(Context->AvailableListHandle, itemContext); + ListBox_SetCurSel(Context->AvailableListHandle, buttonIndex); + ListBox_SetTopIndex(Context->AvailableListHandle, 0); // NOTE: This is intentional. + + // Disable buttons + Button_Enable(Context->MoveUpButtonHandle, FALSE); + Button_Enable(Context->MoveDownButtonHandle, FALSE); + Button_Enable(Context->AddButtonHandle, FALSE); + Button_Enable(Context->RemoveButtonHandle, FALSE); +} + +INT_PTR CALLBACK CustomizeStatusBarDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCUSTOMIZE_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PCUSTOMIZE_CONTEXT)PhAllocate(sizeof(CUSTOMIZE_CONTEXT)); + memset(context, 0, sizeof(CUSTOMIZE_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PCUSTOMIZE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, PhMainWndHandle); + + context->DialogHandle = hwndDlg; + context->AvailableListHandle = GetDlgItem(hwndDlg, IDC_AVAILABLE); + context->CurrentListHandle = GetDlgItem(hwndDlg, IDC_CURRENT); + context->MoveUpButtonHandle = GetDlgItem(hwndDlg, IDC_MOVEUP); + context->MoveDownButtonHandle = GetDlgItem(hwndDlg, IDC_MOVEDOWN); + context->AddButtonHandle = GetDlgItem(hwndDlg, IDC_ADD); + context->RemoveButtonHandle = GetDlgItem(hwndDlg, IDC_REMOVE); + context->FontHandle = CommonDuplicateFont((HFONT)SendMessage(StatusBarHandle, WM_GETFONT, 0, 0)); + + ListBox_SetItemHeight(context->AvailableListHandle, 0, PhMultiplyDivide(22, PhGlobalDpi, 96)); // BitmapHeight + ListBox_SetItemHeight(context->CurrentListHandle, 0, PhMultiplyDivide(22, PhGlobalDpi, 96)); // BitmapHeight + + CustomizeLoadStatusBarItems(context); + + SendMessage(context->DialogHandle, WM_NEXTDLGCTL, (WPARAM)context->CurrentListHandle, TRUE); + } + return TRUE; + case WM_DESTROY: + { + StatusBarSaveSettings(); + + CustomizeFreeStatusBarItems(context); + + if (context->FontHandle) + { + DeleteObject(context->FontHandle); + } + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_AVAILABLE: + { + switch (GET_WM_COMMAND_CMD(wParam, lParam)) + { + case LBN_SELCHANGE: + { + INT count; + INT index; + + count = ListBox_GetCount(context->AvailableListHandle); + index = ListBox_GetCurSel(context->AvailableListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + if (index == (count - 1)) + { + Button_Enable(context->AddButtonHandle, FALSE); + } + else + { + Button_Enable(context->AddButtonHandle, TRUE); + } + } + break; + case LBN_DBLCLK: + { + INT count; + INT index; + INT indexto; + + count = ListBox_GetCount(context->AvailableListHandle); + index = ListBox_GetCurSel(context->AvailableListHandle); + indexto = ListBox_GetCurSel(context->CurrentListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + if (indexto == LB_ERR) + break; + + if (index == (count - 1)) + { + // virtual separator + break; + } + + CustomizeAddStatusBarItem(context, index, indexto); + } + break; + } + } + break; + case IDC_CURRENT: + { + switch (GET_WM_COMMAND_CMD(wParam, lParam)) + { + case LBN_SELCHANGE: + { + INT count; + INT index; + PBUTTON_CONTEXT itemContext; + + count = ListBox_GetCount(context->CurrentListHandle); + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(context->CurrentListHandle, index); + if (itemContext == NULL) + break; + + if (index == 0 && count == 2) + { + // first and last item + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == (count - 1)) + { + // last item (virtual separator) + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == (count - 2)) + { + // second last item (last non-virtual item) + Button_Enable(context->MoveUpButtonHandle, TRUE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == 0) + { + // first item + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, TRUE); + } + else + { + Button_Enable(context->MoveUpButtonHandle, TRUE); + Button_Enable(context->MoveDownButtonHandle, TRUE); + } + + Button_Enable(context->RemoveButtonHandle, !itemContext->IsVirtual); + } + break; + case LBN_DBLCLK: + { + INT count; + INT index; + + count = ListBox_GetCount(context->CurrentListHandle); + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + if (index == (count - 1)) + { + // virtual separator + break; + } + + CustomizeRemoveStatusBarItem(context, index); + } + break; + } + } + break; + case IDC_ADD: + { + INT index; + INT indexto; + + index = ListBox_GetCurSel(context->AvailableListHandle); + indexto = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + if (indexto == LB_ERR) + break; + + CustomizeAddStatusBarItem(context, index, indexto); + } + break; + case IDC_REMOVE: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeRemoveStatusBarItem(context, index); + } + break; + case IDC_MOVEUP: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeMoveStatusBarItem(context, index, index - 1); + } + break; + case IDC_MOVEDOWN: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeMoveStatusBarItem(context, index, index + 1); + } + break; + case IDC_RESET: + { + // Reset to default settings. + StatusBarResetSettings(); + + // Save as the new defaults. + StatusBarSaveSettings(); + + StatusBarUpdate(TRUE); + + CustomizeLoadStatusBarItems(context); + } + break; + case IDCANCEL: + { + EndDialog(hwndDlg, FALSE); + } + break; + } + } + break; + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT drawInfo = (LPDRAWITEMSTRUCT)lParam; + + if (drawInfo->CtlID == IDC_AVAILABLE || drawInfo->CtlID == IDC_CURRENT) + { + HDC bufferDc; + HBITMAP bufferBitmap; + HBITMAP oldBufferBitmap; + PBUTTON_CONTEXT itemContext; + RECT bufferRect = + { + 0, 0, + drawInfo->rcItem.right - drawInfo->rcItem.left, + drawInfo->rcItem.bottom - drawInfo->rcItem.top + }; + BOOLEAN isSelected = (drawInfo->itemState & ODS_SELECTED) == ODS_SELECTED; + BOOLEAN isFocused = (drawInfo->itemState & ODS_FOCUS) == ODS_FOCUS; + + if (drawInfo->itemID == LB_ERR) + break; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(drawInfo->hwndItem, drawInfo->itemID); + if (itemContext == NULL) + break; + + bufferDc = CreateCompatibleDC(drawInfo->hDC); + bufferBitmap = CreateCompatibleBitmap(drawInfo->hDC, bufferRect.right, bufferRect.bottom); + oldBufferBitmap = SelectBitmap(bufferDc, bufferBitmap); + SelectFont(bufferDc, context->FontHandle); + + SetBkMode(bufferDc, TRANSPARENT); + FillRect(bufferDc, &bufferRect, GetSysColorBrush(isFocused ? COLOR_HIGHLIGHT : COLOR_WINDOW)); + + if (isSelected) + { + FrameRect(bufferDc, &bufferRect, isFocused ? GetStockBrush(BLACK_BRUSH) : GetSysColorBrush(COLOR_HIGHLIGHT)); + } + else + { + FrameRect(bufferDc, &bufferRect, isFocused ? GetStockBrush(BLACK_BRUSH) : GetSysColorBrush(COLOR_HIGHLIGHTTEXT)); + } + + if (!itemContext->IsVirtual) + { + SetTextColor(bufferDc, GetSysColor(isFocused ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); + + bufferRect.left += 5; + + DrawText( + bufferDc, + StatusBarGetText(itemContext->IdCommand), + -1, + &bufferRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE + ); + } + + BitBlt( + drawInfo->hDC, + drawInfo->rcItem.left, + drawInfo->rcItem.top, + drawInfo->rcItem.right, + drawInfo->rcItem.bottom, + bufferDc, + 0, + 0, + SRCCOPY + ); + + SelectBitmap(bufferDc, oldBufferBitmap); + DeleteBitmap(bufferBitmap); + DeleteDC(bufferDc); + + return TRUE; + } + } + break; + } + + return FALSE; +} + +VOID StatusBarShowCustomizeDialog( + VOID + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_CUSTOMIZE_SB), + PhMainWndHandle, + CustomizeStatusBarDialogProc + ); +} \ No newline at end of file diff --git a/plugins/ToolStatus/customizetb.c b/plugins/ToolStatus/customizetb.c new file mode 100644 index 0000000..1e8eb6b --- /dev/null +++ b/plugins/ToolStatus/customizetb.c @@ -0,0 +1,983 @@ +/* + * Process Hacker ToolStatus - + * Toolbar Customize Dialog + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" +#include "commonutil.h" + +static PWSTR CustomizeTextOptionsStrings[] = +{ + L"No text labels", + L"Selective text", + L"Show text labels" +}; + +static PWSTR CustomizeSearchDisplayStrings[] = +{ + L"Always show", + L"Hide when inactive (Ctrl+K)", + // L"Auto-hide" +}; + +static PWSTR CustomizeThemeOptionsStrings[] = +{ + L"None", + L"Black", + L"Blue" +}; + +BOOLEAN CustomizeToolbarItemExists( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IdCommand + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + + buttonCount = ListBox_GetCount(Context->CurrentListHandle); + + if (buttonCount == LB_ERR) + return FALSE; + + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, buttonIndex); + + if (itemContext == NULL) + continue; + + if (itemContext->IdCommand == IdCommand) + return TRUE; + } + + return FALSE; +} + +VOID CustomizeInsertToolbarButton( + _In_ INT Index, + _In_ PBUTTON_CONTEXT ItemContext + ) +{ + TBBUTTON button; + + memset(&button, 0, sizeof(TBBUTTON)); + + button.idCommand = ItemContext->IdCommand; + button.iBitmap = I_IMAGECALLBACK; + button.fsState = TBSTATE_ENABLED; + button.fsStyle = ItemContext->IsSeparator ? BTNS_SEP : BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT; + button.iString = (INT_PTR)ToolbarGetText(ItemContext->IdCommand); + + SendMessage(ToolBarHandle, TB_INSERTBUTTON, Index, (LPARAM)&button); +} + +VOID CustomizeAddToolbarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexAvail, + _In_ INT IndexTo + ) +{ + INT count; + PBUTTON_CONTEXT itemContext; + + count = ListBox_GetCount(Context->AvailableListHandle); + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->AvailableListHandle, IndexAvail); + + if (count == LB_ERR) + return; + + if (itemContext == NULL) + return; + + if (IndexAvail != 0) // index 0 is separator + { + // remove from 'available' list + ListBox_DeleteString(Context->AvailableListHandle, IndexAvail); + + if (IndexAvail == count - 1) + { + ListBox_SetCurSel(Context->AvailableListHandle, IndexAvail - 1); + } + else + { + ListBox_SetCurSel(Context->AvailableListHandle, IndexAvail); + } + } + else + { + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + + itemContext->IsSeparator = TRUE; + itemContext->IsRemovable = TRUE; + } + + // insert into 'current' list + ListBox_InsertItemData(Context->CurrentListHandle, IndexTo, itemContext); + + CustomizeInsertToolbarButton(IndexTo, itemContext); +} + +VOID CustomizeRemoveToolbarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexFrom + ) +{ + PBUTTON_CONTEXT itemContext; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, IndexFrom); + + if (itemContext == NULL) + return; + + ListBox_DeleteString(Context->CurrentListHandle, IndexFrom); + ListBox_SetCurSel(Context->CurrentListHandle, IndexFrom); + + SendMessage(ToolBarHandle, TB_DELETEBUTTON, IndexFrom, 0); + + if (itemContext->IsSeparator) + { + PhFree(itemContext); + } + else + { + // insert into 'available' list + ListBox_AddItemData(Context->AvailableListHandle, itemContext); + } + + SendMessage(Context->DialogHandle, WM_COMMAND, MAKEWPARAM(IDC_CURRENT, LBN_SELCHANGE), 0); +} + +VOID CustomizeMoveToolbarItem( + _In_ PCUSTOMIZE_CONTEXT Context, + _In_ INT IndexFrom, + _In_ INT IndexTo + ) +{ + INT count; + PBUTTON_CONTEXT itemContext; + + if (IndexFrom == IndexTo) + return; + + count = ListBox_GetCount(Context->CurrentListHandle); + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, IndexFrom); + + if (count == LB_ERR) + return; + + if (itemContext == NULL) + return; + + ListBox_DeleteString(Context->CurrentListHandle, IndexFrom); + ListBox_InsertItemData(Context->CurrentListHandle, IndexTo, itemContext); + ListBox_SetCurSel(Context->CurrentListHandle, IndexTo); + + if (IndexTo <= 0) + { + Button_Enable(Context->MoveUpButtonHandle, FALSE); + } + else + { + Button_Enable(Context->MoveUpButtonHandle, TRUE); + } + + // last item is always separator + if (IndexTo >= (count - 2)) + { + Button_Enable(Context->MoveDownButtonHandle, FALSE); + } + else + { + Button_Enable(Context->MoveDownButtonHandle, TRUE); + } + + SendMessage(ToolBarHandle, TB_DELETEBUTTON, IndexFrom, 0); + + CustomizeInsertToolbarButton(IndexTo, itemContext); +} + +VOID CustomizeFreeToolbarItems( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + + buttonCount = ListBox_GetCount(Context->CurrentListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, buttonIndex)) + { + PhFree(itemContext); + } + } + } + + buttonCount = ListBox_GetCount(Context->AvailableListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->AvailableListHandle, buttonIndex)) + { + PhFree(itemContext); + } + } + } +} + +VOID CustomizeLoadToolbarItems( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + PBUTTON_CONTEXT itemContext; + + CustomizeFreeToolbarItems(Context); + + ListBox_ResetContent(Context->AvailableListHandle); + ListBox_ResetContent(Context->CurrentListHandle); + + buttonCount = (INT)SendMessage(ToolBarHandle, TB_BUTTONCOUNT, 0, 0); + + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + TBBUTTON button; + + memset(&button, 0, sizeof(TBBUTTON)); + + if (SendMessage(ToolBarHandle, TB_GETBUTTON, buttonIndex, (LPARAM)&button)) + { + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + + itemContext->IsVirtual = FALSE; + itemContext->IsRemovable = TRUE; + itemContext->IdCommand = button.idCommand; + + if (button.fsStyle & BTNS_SEP) + { + itemContext->IsSeparator = TRUE; + } + else + { + HBITMAP buttonImage; + + if (buttonImage = ToolbarGetImage(button.idCommand)) + { + itemContext->IdBitmap = ImageList_Add( + Context->ImageListHandle, + buttonImage, + NULL + ); + + DeleteObject(buttonImage); + } + } + + ListBox_AddItemData(Context->CurrentListHandle, itemContext); + } + } + + for (buttonIndex = 0; buttonIndex < MAX_TOOLBAR_ITEMS; buttonIndex++) + { + HBITMAP buttonImage; + TBBUTTON button = ToolbarButtons[buttonIndex]; + + if (button.idCommand == 0) + continue; + + if (CustomizeToolbarItemExists(Context, button.idCommand)) + continue; + + // HACK and violation of abstraction. + // Don't show the 'Show Details for All Processes' button on XP. + if (!WINDOWS_HAS_UAC && button.idCommand == PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES) + { + continue; + } + + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + + itemContext->IsRemovable = TRUE; + itemContext->IdCommand = button.idCommand; + + if (buttonImage = ToolbarGetImage(button.idCommand)) + { + itemContext->IdBitmap = ImageList_Add( + Context->ImageListHandle, + buttonImage, + NULL + ); + DeleteObject(buttonImage); + } + + ListBox_AddItemData(Context->AvailableListHandle, itemContext); + } + + // Append separator to the last 'current list' position + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + itemContext->IsSeparator = TRUE; + itemContext->IsVirtual = TRUE; + itemContext->IsRemovable = FALSE; + + buttonIndex = ListBox_AddItemData(Context->CurrentListHandle, itemContext); + ListBox_SetCurSel(Context->CurrentListHandle, buttonIndex); + ListBox_SetTopIndex(Context->CurrentListHandle, buttonIndex); + + // Insert separator into first 'available list' position + itemContext = PhAllocate(sizeof(BUTTON_CONTEXT)); + memset(itemContext, 0, sizeof(BUTTON_CONTEXT)); + itemContext->IsSeparator = TRUE; + itemContext->IsVirtual = FALSE; + itemContext->IsRemovable = FALSE; + + buttonIndex = ListBox_InsertItemData(Context->AvailableListHandle, 0, itemContext); + ListBox_SetCurSel(Context->AvailableListHandle, buttonIndex); + ListBox_SetTopIndex(Context->AvailableListHandle, buttonIndex); + + // Disable buttons + Button_Enable(Context->MoveUpButtonHandle, FALSE); + Button_Enable(Context->MoveDownButtonHandle, FALSE); + Button_Enable(Context->RemoveButtonHandle, FALSE); +} + +VOID CustomizeLoadToolbarSettings( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + HWND toolbarCombo = GetDlgItem(Context->DialogHandle, IDC_TEXTOPTIONS); + HWND searchboxCombo = GetDlgItem(Context->DialogHandle, IDC_SEARCHOPTIONS); + HWND themeCombo = GetDlgItem(Context->DialogHandle, IDC_THEMEOPTIONS); + + PhAddComboBoxStrings( + toolbarCombo, + CustomizeTextOptionsStrings, + ARRAYSIZE(CustomizeTextOptionsStrings) + ); + PhAddComboBoxStrings( + searchboxCombo, + CustomizeSearchDisplayStrings, + ARRAYSIZE(CustomizeSearchDisplayStrings) + ); + PhAddComboBoxStrings( + themeCombo, + CustomizeThemeOptionsStrings, + ARRAYSIZE(CustomizeThemeOptionsStrings) + ); + ComboBox_SetCurSel(toolbarCombo, PhGetIntegerSetting(SETTING_NAME_TOOLBARDISPLAYSTYLE)); + ComboBox_SetCurSel(searchboxCombo, PhGetIntegerSetting(SETTING_NAME_SEARCHBOXDISPLAYMODE)); + ComboBox_SetCurSel(themeCombo, PhGetIntegerSetting(SETTING_NAME_TOOLBAR_THEME)); + + Button_SetCheck(GetDlgItem(Context->DialogHandle, IDC_ENABLE_MODERN), + ToolStatusConfig.ModernIcons ? BST_CHECKED : BST_UNCHECKED); + Button_SetCheck(GetDlgItem(Context->DialogHandle, IDC_ENABLE_AUTOHIDE_MENU), + ToolStatusConfig.AutoHideMenu ? BST_CHECKED : BST_UNCHECKED); + + if (!ToolStatusConfig.SearchBoxEnabled) + { + ComboBox_Enable(searchboxCombo, FALSE); + } + + if (WindowsVersion <= WINDOWS_VISTA) + { + ComboBox_Enable(themeCombo, FALSE); + } +} + +VOID CustomizeResetImages( + _In_ PCUSTOMIZE_CONTEXT Context + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + + buttonCount = ListBox_GetCount(Context->CurrentListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + HBITMAP buttonImage; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->CurrentListHandle, buttonIndex)) + { + if (buttonImage = ToolbarGetImage(itemContext->IdCommand)) + { + ImageList_Replace( + Context->ImageListHandle, + itemContext->IdBitmap, + buttonImage, + NULL + ); + DeleteObject(buttonImage); + } + } + } + } + + buttonCount = ListBox_GetCount(Context->AvailableListHandle); + + if (buttonCount != LB_ERR) + { + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + PBUTTON_CONTEXT itemContext; + HBITMAP buttonImage; + + if (itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(Context->AvailableListHandle, buttonIndex)) + { + if (buttonImage = ToolbarGetImage(itemContext->IdCommand)) + { + ImageList_Replace( + Context->ImageListHandle, + itemContext->IdBitmap, + buttonImage, + NULL + ); + DeleteObject(buttonImage); + } + } + } + } + + InvalidateRect(Context->AvailableListHandle, NULL, TRUE); + InvalidateRect(Context->CurrentListHandle, NULL, TRUE); +} + +VOID CustomizeResetToolbarImages( + VOID + ) +{ + // Reset the image cache with the new icons. + // TODO: Move function to Toolbar.c + for (INT i = 0; i < ARRAYSIZE(ToolbarButtons); i++) + { + if (ToolbarButtons[i].iBitmap != I_IMAGECALLBACK) + { + HBITMAP buttonImage; + + if (buttonImage = ToolbarGetImage(ToolbarButtons[i].idCommand)) + { + ImageList_Replace( + ToolBarImageList, + ToolbarButtons[i].iBitmap, + buttonImage, + NULL + ); + + DeleteObject(buttonImage); + } + } + } +} + +INT_PTR CALLBACK CustomizeToolbarDialogProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PCUSTOMIZE_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PCUSTOMIZE_CONTEXT)PhAllocate(sizeof(CUSTOMIZE_CONTEXT)); + memset(context, 0, sizeof(CUSTOMIZE_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PCUSTOMIZE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + RemoveProp(hwndDlg, L"Context"); + PhFree(context); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, PhMainWndHandle); + + context->DialogHandle = hwndDlg; + context->AvailableListHandle = GetDlgItem(hwndDlg, IDC_AVAILABLE); + context->CurrentListHandle = GetDlgItem(hwndDlg, IDC_CURRENT); + context->MoveUpButtonHandle = GetDlgItem(hwndDlg, IDC_MOVEUP); + context->MoveDownButtonHandle = GetDlgItem(hwndDlg, IDC_MOVEDOWN); + context->AddButtonHandle = GetDlgItem(hwndDlg, IDC_ADD); + context->RemoveButtonHandle = GetDlgItem(hwndDlg, IDC_REMOVE); + context->BitmapWidth = GetSystemMetrics(SM_CYSMICON) + 4; + context->FontHandle = CommonDuplicateFont((HFONT)SendMessage(ToolBarHandle, WM_GETFONT, 0, 0)); + context->ImageListHandle = ImageList_Create( + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32 | ILC_MASK, + 0, + 0 + ); + + ListBox_SetItemHeight(context->AvailableListHandle, 0, context->BitmapWidth); // BitmapHeight + ListBox_SetItemHeight(context->CurrentListHandle, 0, context->BitmapWidth); // BitmapHeight + + CustomizeLoadToolbarItems(context); + CustomizeLoadToolbarSettings(context); + + SendMessage(context->DialogHandle, WM_NEXTDLGCTL, (WPARAM)context->CurrentListHandle, TRUE); + } + return TRUE; + case WM_DESTROY: + { + ToolbarSaveButtonSettings(); + ToolbarLoadSettings(); + + CustomizeFreeToolbarItems(context); + + if (context->ImageListHandle) + { + ImageList_Destroy(context->ImageListHandle); + } + + if (context->FontHandle) + { + DeleteObject(context->FontHandle); + } + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDC_AVAILABLE: + { + switch (GET_WM_COMMAND_CMD(wParam, lParam)) + { + case LBN_DBLCLK: + { + INT index; + INT indexto; + + index = ListBox_GetCurSel(context->AvailableListHandle); + indexto = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + if (indexto == LB_ERR) + break; + + CustomizeAddToolbarItem(context, index, indexto); + } + break; + } + } + break; + case IDC_CURRENT: + { + switch (GET_WM_COMMAND_CMD(wParam, lParam)) + { + case LBN_SELCHANGE: + { + INT count; + INT index; + PBUTTON_CONTEXT itemContext; + + count = ListBox_GetCount(context->CurrentListHandle); + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(context->CurrentListHandle, index); + + if (itemContext == NULL) + break; + + if (index == 0 && count == 2) + { + // first and last item + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == (count - 1)) + { + // last item (virtual separator) + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == (count - 2)) + { + // second last item (last non-virtual item) + Button_Enable(context->MoveUpButtonHandle, TRUE); + Button_Enable(context->MoveDownButtonHandle, FALSE); + } + else if (index == 0) + { + // first item + Button_Enable(context->MoveUpButtonHandle, FALSE); + Button_Enable(context->MoveDownButtonHandle, TRUE); + } + else + { + Button_Enable(context->MoveUpButtonHandle, TRUE); + Button_Enable(context->MoveDownButtonHandle, TRUE); + } + + Button_Enable(context->RemoveButtonHandle, itemContext->IsRemovable); + } + break; + case LBN_DBLCLK: + { + INT count; + INT index; + + count = ListBox_GetCount(context->CurrentListHandle); + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (count == LB_ERR) + break; + + if (index == LB_ERR) + break; + + if (index == (count - 1)) + { + // virtual separator + break; + } + + CustomizeRemoveToolbarItem(context, index); + } + break; + } + } + break; + case IDC_ADD: + { + INT index; + INT indexto; + + index = ListBox_GetCurSel(context->AvailableListHandle); + indexto = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + if (indexto == LB_ERR) + break; + + CustomizeAddToolbarItem(context, index, indexto); + } + break; + case IDC_REMOVE: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeRemoveToolbarItem(context, index); + } + break; + case IDC_MOVEUP: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeMoveToolbarItem(context, index, index - 1); + } + break; + case IDC_MOVEDOWN: + { + INT index; + + index = ListBox_GetCurSel(context->CurrentListHandle); + + if (index == LB_ERR) + break; + + CustomizeMoveToolbarItem(context, index, index + 1); + } + break; + case IDC_RESET: + { + // Reset the Toolbar buttons to default settings. + ToolbarResetSettings(); + // Re-load the settings. + ToolbarLoadSettings(); + // Save as the new defaults. + ToolbarSaveButtonSettings(); + + CustomizeLoadToolbarItems(context); + } + break; + case IDC_TEXTOPTIONS: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE) + { + PhSetIntegerSetting(SETTING_NAME_TOOLBARDISPLAYSTYLE, + (DisplayStyle = (TOOLBAR_DISPLAY_STYLE)ComboBox_GetCurSel(GET_WM_COMMAND_HWND(wParam, lParam)))); + + ToolbarLoadSettings(); + } + } + break; + case IDC_SEARCHOPTIONS: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE) + { + PhSetIntegerSetting(SETTING_NAME_SEARCHBOXDISPLAYMODE, + (SearchBoxDisplayMode = (SEARCHBOX_DISPLAY_MODE)ComboBox_GetCurSel(GET_WM_COMMAND_HWND(wParam, lParam)))); + + ToolbarLoadSettings(); + } + } + break; + case IDC_THEMEOPTIONS: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == CBN_SELCHANGE) + { + PhSetIntegerSetting(SETTING_NAME_TOOLBAR_THEME, + (ToolBarTheme = (TOOLBAR_THEME)ComboBox_GetCurSel(GET_WM_COMMAND_HWND(wParam, lParam)))); + + switch (ToolBarTheme) + { + case TOOLBAR_THEME_NONE: + { + SendMessage(RebarHandle, RB_SETWINDOWTHEME, 0, (LPARAM)L""); + SendMessage(ToolBarHandle, TB_SETWINDOWTHEME, 0, (LPARAM)L""); + } + break; + case TOOLBAR_THEME_BLACK: + { + SendMessage(RebarHandle, RB_SETWINDOWTHEME, 0, (LPARAM)L"Media"); + SendMessage(ToolBarHandle, TB_SETWINDOWTHEME, 0, (LPARAM)L"Media"); + } + break; + case TOOLBAR_THEME_BLUE: + { + SendMessage(RebarHandle, RB_SETWINDOWTHEME, 0, (LPARAM)L"Communications"); + SendMessage(ToolBarHandle, TB_SETWINDOWTHEME, 0, (LPARAM)L"Communications"); + } + break; + } + } + } + break; + case IDC_ENABLE_MODERN: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED) + { + ToolStatusConfig.ModernIcons = Button_GetCheck(GET_WM_COMMAND_HWND(wParam, lParam)) == BST_CHECKED; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + + CustomizeResetImages(context); + CustomizeResetToolbarImages(); + //CustomizeLoadItems(context); + } + } + break; + case IDC_ENABLE_AUTOHIDE_MENU: + { + if (GET_WM_COMMAND_CMD(wParam, lParam) == BN_CLICKED) + { + ToolStatusConfig.AutoHideMenu = !ToolStatusConfig.AutoHideMenu; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + if (ToolStatusConfig.AutoHideMenu) + { + SetMenu(PhMainWndHandle, NULL); + } + else + { + SetMenu(PhMainWndHandle, MainMenu); + DrawMenuBar(PhMainWndHandle); + } + } + } + break; + case IDCANCEL: + { + EndDialog(hwndDlg, FALSE); + } + break; + } + } + break; + case WM_DRAWITEM: + { + LPDRAWITEMSTRUCT drawInfo = (LPDRAWITEMSTRUCT)lParam; + + if (drawInfo->CtlID == IDC_AVAILABLE || drawInfo->CtlID == IDC_CURRENT) + { + HDC bufferDc; + HBITMAP bufferBitmap; + HBITMAP oldBufferBitmap; + PBUTTON_CONTEXT itemContext; + RECT bufferRect = + { + 0, 0, + drawInfo->rcItem.right - drawInfo->rcItem.left, + drawInfo->rcItem.bottom - drawInfo->rcItem.top + }; + BOOLEAN isSelected = (drawInfo->itemState & ODS_SELECTED) == ODS_SELECTED; + BOOLEAN isFocused = (drawInfo->itemState & ODS_FOCUS) == ODS_FOCUS; + + if (drawInfo->itemID == LB_ERR) + break; + + itemContext = (PBUTTON_CONTEXT)ListBox_GetItemData(drawInfo->hwndItem, drawInfo->itemID); + if (itemContext == NULL) + break; + + bufferDc = CreateCompatibleDC(drawInfo->hDC); + bufferBitmap = CreateCompatibleBitmap(drawInfo->hDC, bufferRect.right, bufferRect.bottom); + oldBufferBitmap = SelectBitmap(bufferDc, bufferBitmap); + SelectFont(bufferDc, context->FontHandle); + + SetBkMode(bufferDc, TRANSPARENT); + FillRect(bufferDc, &bufferRect, GetSysColorBrush(isFocused ? COLOR_HIGHLIGHT : COLOR_WINDOW)); + + if (isSelected) + { + FrameRect(bufferDc, &bufferRect, isFocused ? GetStockBrush(BLACK_BRUSH) : GetSysColorBrush(COLOR_HIGHLIGHT)); + } + else + { + FrameRect(bufferDc, &bufferRect, isFocused ? GetStockBrush(BLACK_BRUSH) : GetSysColorBrush(COLOR_HIGHLIGHTTEXT)); + } + + if (itemContext->IsVirtual) + { + SetTextColor(bufferDc, GetSysColor(COLOR_GRAYTEXT)); + } + else + { + SetTextColor(bufferDc, GetSysColor(isFocused ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); + } + + if (!itemContext->IsSeparator) + { + ImageList_Draw( + context->ImageListHandle, + itemContext->IdBitmap, + bufferDc, + bufferRect.left + 2, + bufferRect.top + 2, + ILD_NORMAL + ); + } + + bufferRect.left += context->BitmapWidth; //+ 2; + + if (itemContext->IdCommand != 0) + { + DrawText( + bufferDc, + ToolbarGetText(itemContext->IdCommand), + -1, + &bufferRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP + ); + } + else + { + DrawText( + bufferDc, + L"Separator", + -1, + &bufferRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOCLIP + ); + } + + BitBlt( + drawInfo->hDC, + drawInfo->rcItem.left, + drawInfo->rcItem.top, + drawInfo->rcItem.right, + drawInfo->rcItem.bottom, + bufferDc, + 0, + 0, + SRCCOPY + ); + + SelectBitmap(bufferDc, oldBufferBitmap); + DeleteBitmap(bufferBitmap); + DeleteDC(bufferDc); + + return TRUE; + } + } + break; + } + + return FALSE; +} + +VOID ToolBarShowCustomizeDialog( + VOID + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_CUSTOMIZE_TB), + PhMainWndHandle, + CustomizeToolbarDialogProc + ); +} \ No newline at end of file diff --git a/plugins/ToolStatus/filter.c b/plugins/ToolStatus/filter.c new file mode 100644 index 0000000..4672c15 --- /dev/null +++ b/plugins/ToolStatus/filter.c @@ -0,0 +1,467 @@ +/* + * Process Hacker ToolStatus - + * search filter callbacks + * + * Copyright (C) 2011-2015 dmex + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" +#include + +BOOLEAN WordMatchStringRef( + _In_ PPH_STRINGREF Text + ) +{ + PH_STRINGREF part; + PH_STRINGREF remainingPart; + + remainingPart = SearchboxText->sr; + + while (remainingPart.Length != 0) + { + PhSplitStringRefAtChar(&remainingPart, '|', &part, &remainingPart); + + if (part.Length != 0) + { + if (PhFindStringInStringRef(Text, &part, TRUE) != -1) + return TRUE; + } + } + + return FALSE; +} + +BOOLEAN WordMatchStringZ( + _In_ PWSTR Text + ) +{ + PH_STRINGREF text; + + PhInitializeStringRef(&text, Text); + return WordMatchStringRef(&text); +} + +BOOLEAN ProcessTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_NODE processNode = (PPH_PROCESS_NODE)Node; + + if (PhIsNullOrEmptyString(SearchboxText)) + return TRUE; + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->ProcessName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->ProcessName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->FileName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->FileName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->CommandLine)) + { + if (WordMatchStringRef(&processNode->ProcessItem->CommandLine->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->VersionInfo.CompanyName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->VersionInfo.CompanyName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->VersionInfo.FileDescription)) + { + if (WordMatchStringRef(&processNode->ProcessItem->VersionInfo.FileDescription->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->VersionInfo.FileVersion)) + { + if (WordMatchStringRef(&processNode->ProcessItem->VersionInfo.FileVersion->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->VersionInfo.ProductName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->VersionInfo.ProductName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->UserName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->UserName->sr)) + return TRUE; + } + + if (processNode->ProcessItem->IntegrityString) + { + if (WordMatchStringZ(processNode->ProcessItem->IntegrityString)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->JobName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->JobName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->VerifySignerName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->VerifySignerName->sr)) + return TRUE; + } + + if (processNode->ProcessItem->ProcessIdString[0] != 0) + { + if (WordMatchStringZ(processNode->ProcessItem->ProcessIdString)) + return TRUE; + } + + if (processNode->ProcessItem->ParentProcessIdString[0] != 0) + { + if (WordMatchStringZ(processNode->ProcessItem->ParentProcessIdString)) + return TRUE; + } + + if (processNode->ProcessItem->SessionIdString[0] != 0) + { + if (WordMatchStringZ(processNode->ProcessItem->SessionIdString)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(processNode->ProcessItem->PackageFullName)) + { + if (WordMatchStringRef(&processNode->ProcessItem->PackageFullName->sr)) + return TRUE; + } + + if (WordMatchStringZ(PhGetProcessPriorityClassString(processNode->ProcessItem->PriorityClass))) + { + return TRUE; + } + + if (processNode->ProcessItem->VerifyResult != VrUnknown) + { + switch (processNode->ProcessItem->VerifyResult) + { + case VrNoSignature: + if (WordMatchStringZ(L"NoSignature")) + return TRUE; + break; + case VrTrusted: + if (WordMatchStringZ(L"Trusted")) + return TRUE; + break; + case VrExpired: + if (WordMatchStringZ(L"Expired")) + return TRUE; + break; + case VrRevoked: + if (WordMatchStringZ(L"Revoked")) + return TRUE; + break; + case VrDistrust: + if (WordMatchStringZ(L"Distrust")) + return TRUE; + break; + case VrSecuritySettings: + if (WordMatchStringZ(L"SecuritySettings")) + return TRUE; + break; + case VrBadSignature: + if (WordMatchStringZ(L"BadSignature")) + return TRUE; + break; + default: + if (WordMatchStringZ(L"Unknown")) + return TRUE; + break; + } + } + + if (WINDOWS_HAS_UAC && processNode->ProcessItem->ElevationType != TokenElevationTypeDefault) + { + switch (processNode->ProcessItem->ElevationType) + { + case TokenElevationTypeLimited: + if (WordMatchStringZ(L"Limited")) + return TRUE; + break; + case TokenElevationTypeFull: + if (WordMatchStringZ(L"Full")) + return TRUE; + break; + default: + if (WordMatchStringZ(L"Unknown")) + return TRUE; + break; + } + } + + if (WordMatchStringZ(L"UpdateIsDotNet") && processNode->ProcessItem->UpdateIsDotNet) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsBeingDebugged") && processNode->ProcessItem->IsBeingDebugged) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsDotNet") && processNode->ProcessItem->IsDotNet) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsElevated") && processNode->ProcessItem->IsElevated) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsInJob") && processNode->ProcessItem->IsInJob) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsInSignificantJob") && processNode->ProcessItem->IsInSignificantJob) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsPacked") && processNode->ProcessItem->IsPacked) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsSuspended") && processNode->ProcessItem->IsSuspended) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsWow64") && processNode->ProcessItem->IsWow64) + { + return TRUE; + } + + if (WordMatchStringZ(L"IsImmersive") && processNode->ProcessItem->IsImmersive) + { + return TRUE; + } + + if (processNode->ProcessItem->ServiceList && processNode->ProcessItem->ServiceList->Count != 0) + { + ULONG enumerationKey = 0; + PPH_SERVICE_ITEM serviceItem; + PPH_LIST serviceList; + ULONG i; + BOOLEAN matched = FALSE; + + // Copy the service list so we can search it. + serviceList = PhCreateList(processNode->ProcessItem->ServiceList->Count); + + PhAcquireQueuedLockShared(&processNode->ProcessItem->ServiceListLock); + + while (PhEnumPointerList( + processNode->ProcessItem->ServiceList, + &enumerationKey, + &serviceItem + )) + { + PhReferenceObject(serviceItem); + PhAddItemList(serviceList, serviceItem); + } + + PhReleaseQueuedLockShared(&processNode->ProcessItem->ServiceListLock); + + for (i = 0; i < serviceList->Count; i++) + { + serviceItem = serviceList->Items[i]; + + if (!PhIsNullOrEmptyString(serviceItem->Name)) + { + if (WordMatchStringRef(&serviceItem->Name->sr)) + { + matched = TRUE; + break; + } + } + + if (!PhIsNullOrEmptyString(serviceItem->DisplayName)) + { + if (WordMatchStringRef(&serviceItem->DisplayName->sr)) + { + matched = TRUE; + break; + } + } + + if (serviceItem->ProcessId) + { + WCHAR processIdString[PH_INT32_STR_LEN_1]; + + PhPrintUInt32(processIdString, HandleToUlong(serviceItem->ProcessId)); + + if (WordMatchStringZ(processIdString)) + { + matched = TRUE; + break; + } + } + } + + PhDereferenceObjects(serviceList->Items, serviceList->Count); + PhDereferenceObject(serviceList); + + if (matched) + return TRUE; + } + + return FALSE; +} + +BOOLEAN ServiceTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_SERVICE_NODE serviceNode = (PPH_SERVICE_NODE)Node; + + if (PhIsNullOrEmptyString(SearchboxText)) + return TRUE; + + if (WordMatchStringZ(PhGetServiceTypeString(serviceNode->ServiceItem->Type))) + return TRUE; + + if (WordMatchStringZ(PhGetServiceStateString(serviceNode->ServiceItem->State))) + return TRUE; + + if (WordMatchStringZ(PhGetServiceStartTypeString(serviceNode->ServiceItem->StartType))) + return TRUE; + + if (WordMatchStringZ(PhGetServiceErrorControlString(serviceNode->ServiceItem->ErrorControl))) + return TRUE; + + if (!PhIsNullOrEmptyString(serviceNode->ServiceItem->Name)) + { + if (WordMatchStringRef(&serviceNode->ServiceItem->Name->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(serviceNode->ServiceItem->DisplayName)) + { + if (WordMatchStringRef(&serviceNode->ServiceItem->DisplayName->sr)) + return TRUE; + } + + if (serviceNode->ServiceItem->ProcessId) + { + WCHAR processIdString[PH_INT32_STR_LEN_1]; + + PhPrintUInt32(processIdString, HandleToUlong(serviceNode->ServiceItem->ProcessId)); + + if (WordMatchStringZ(processIdString)) + return TRUE; + } + + return FALSE; +} + +BOOLEAN NetworkTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ) +{ + PPH_NETWORK_NODE networkNode = (PPH_NETWORK_NODE)Node; + + if (PhIsNullOrEmptyString(SearchboxText)) + return TRUE; + + if (!PhIsNullOrEmptyString(networkNode->NetworkItem->ProcessName)) + { + if (WordMatchStringRef(&networkNode->NetworkItem->ProcessName->sr)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(networkNode->NetworkItem->OwnerName)) + { + if (WordMatchStringRef(&networkNode->NetworkItem->OwnerName->sr)) + return TRUE; + } + + if (networkNode->NetworkItem->LocalAddressString[0] != 0) + { + if (WordMatchStringZ(networkNode->NetworkItem->LocalAddressString)) + return TRUE; + } + + if (networkNode->NetworkItem->LocalPortString[0] != 0) + { + if (WordMatchStringZ(networkNode->NetworkItem->LocalPortString)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(networkNode->NetworkItem->LocalHostString)) + { + if (WordMatchStringRef(&networkNode->NetworkItem->LocalHostString->sr)) + return TRUE; + } + + if (networkNode->NetworkItem->RemoteAddressString[0] != 0) + { + if (WordMatchStringZ(networkNode->NetworkItem->RemoteAddressString)) + return TRUE; + } + + if (networkNode->NetworkItem->RemotePortString[0] != 0) + { + if (WordMatchStringZ(networkNode->NetworkItem->RemotePortString)) + return TRUE; + } + + if (!PhIsNullOrEmptyString(networkNode->NetworkItem->RemoteHostString)) + { + if (WordMatchStringRef(&networkNode->NetworkItem->RemoteHostString->sr)) + return TRUE; + } + + if (WordMatchStringZ(PhGetProtocolTypeName(networkNode->NetworkItem->ProtocolType))) + return TRUE; + + if ((networkNode->NetworkItem->ProtocolType & PH_TCP_PROTOCOL_TYPE) && + WordMatchStringZ(PhGetTcpStateName(networkNode->NetworkItem->State))) + return TRUE; + + if (networkNode->NetworkItem->ProcessId) + { + WCHAR processIdString[PH_INT32_STR_LEN_1]; + + PhPrintUInt32(processIdString, HandleToUlong(networkNode->NetworkItem->ProcessId)); + + if (WordMatchStringZ(processIdString)) + return TRUE; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/ToolStatus/graph.c b/plugins/ToolStatus/graph.c new file mode 100644 index 0000000..c8effcd --- /dev/null +++ b/plugins/ToolStatus/graph.c @@ -0,0 +1,662 @@ +/* + * Process Hacker ToolStatus - + * Toolbar Graph Bands + * + * Copyright (C) 2015-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" + +HWND CpuGraphHandle = NULL; +HWND MemGraphHandle = NULL; +HWND CommitGraphHandle = NULL; +HWND IoGraphHandle = NULL; +static PH_GRAPH_STATE CpuGraphState; +static PH_GRAPH_STATE MemGraphState; +static PH_GRAPH_STATE CommitGraphState; +static PH_GRAPH_STATE IoGraphState; + +VOID ToolbarCreateGraphs(VOID) +{ + UINT height = (UINT)SendMessage(RebarHandle, RB_GETROWHEIGHT, 0, 0); + + if (ToolStatusConfig.CpuGraphEnabled && !CpuGraphHandle) + { + CpuGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 0, + 0, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(CpuGraphHandle, TRUE); + + PhInitializeGraphState(&CpuGraphState); + } + + if (ToolStatusConfig.MemGraphEnabled && !MemGraphHandle) + { + MemGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 0, + 0, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(MemGraphHandle, TRUE); + + PhInitializeGraphState(&MemGraphState); + } + + if (ToolStatusConfig.CommitGraphEnabled && !CommitGraphHandle) + { + CommitGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 0, + 0, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(CommitGraphHandle, TRUE); + + PhInitializeGraphState(&CommitGraphState); + } + + if (ToolStatusConfig.IoGraphEnabled && !IoGraphHandle) + { + IoGraphHandle = CreateWindow( + PH_GRAPH_CLASSNAME, + NULL, + WS_VISIBLE | WS_CHILD | WS_BORDER, + 0, + 0, + 0, + 0, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + Graph_SetTooltip(IoGraphHandle, TRUE); + + PhInitializeGraphState(&IoGraphState); + } + + if (ToolStatusConfig.CpuGraphEnabled) + { + if (!RebarBandExists(REBAR_BAND_ID_CPUGRAPH)) + RebarBandInsert(REBAR_BAND_ID_CPUGRAPH, CpuGraphHandle, 145, height); // 85 + + if (CpuGraphHandle && !IsWindowVisible(CpuGraphHandle)) + ShowWindow(CpuGraphHandle, SW_SHOW); + } + else + { + if (RebarBandExists(REBAR_BAND_ID_CPUGRAPH)) + RebarBandRemove(REBAR_BAND_ID_CPUGRAPH); + + if (CpuGraphHandle) + { + PhDeleteGraphState(&CpuGraphState); + + DestroyWindow(CpuGraphHandle); + CpuGraphHandle = NULL; + } + } + + if (ToolStatusConfig.MemGraphEnabled) + { + if (!RebarBandExists(REBAR_BAND_ID_MEMGRAPH)) + RebarBandInsert(REBAR_BAND_ID_MEMGRAPH, MemGraphHandle, 145, height); // 85 + + if (MemGraphHandle && !IsWindowVisible(MemGraphHandle)) + { + ShowWindow(MemGraphHandle, SW_SHOW); + } + } + else + { + if (RebarBandExists(REBAR_BAND_ID_MEMGRAPH)) + RebarBandRemove(REBAR_BAND_ID_MEMGRAPH); + + if (MemGraphHandle) + { + PhDeleteGraphState(&MemGraphState); + + DestroyWindow(MemGraphHandle); + MemGraphHandle = NULL; + } + } + + if (ToolStatusConfig.CommitGraphEnabled) + { + if (!RebarBandExists(REBAR_BAND_ID_COMMITGRAPH)) + RebarBandInsert(REBAR_BAND_ID_COMMITGRAPH, CommitGraphHandle, 145, height); // 85 + + if (CommitGraphHandle && !IsWindowVisible(CommitGraphHandle)) + { + ShowWindow(CommitGraphHandle, SW_SHOW); + } + } + else + { + if (RebarBandExists(REBAR_BAND_ID_COMMITGRAPH)) + RebarBandRemove(REBAR_BAND_ID_COMMITGRAPH); + + if (CommitGraphHandle) + { + PhDeleteGraphState(&CommitGraphState); + + DestroyWindow(CommitGraphHandle); + CommitGraphHandle = NULL; + } + } + + if (ToolStatusConfig.IoGraphEnabled) + { + if (!RebarBandExists(REBAR_BAND_ID_IOGRAPH)) + RebarBandInsert(REBAR_BAND_ID_IOGRAPH, IoGraphHandle, 145, height); // 85 + + if (IoGraphHandle && !IsWindowVisible(IoGraphHandle)) + { + ShowWindow(IoGraphHandle, SW_SHOW); + } + } + else + { + if (RebarBandExists(REBAR_BAND_ID_IOGRAPH)) + RebarBandRemove(REBAR_BAND_ID_IOGRAPH); + + if (IoGraphHandle) + { + PhDeleteGraphState(&IoGraphState); + + DestroyWindow(IoGraphHandle); + IoGraphHandle = NULL; + } + } +} + +VOID ToolbarUpdateGraphs(VOID) +{ + if (ToolStatusConfig.CpuGraphEnabled && CpuGraphHandle) + { + CpuGraphState.Valid = FALSE; + CpuGraphState.TooltipIndex = -1; + Graph_MoveGrid(CpuGraphHandle, 1); + Graph_Draw(CpuGraphHandle); + Graph_UpdateTooltip(CpuGraphHandle); + InvalidateRect(CpuGraphHandle, NULL, FALSE); + } + + if (ToolStatusConfig.MemGraphEnabled && MemGraphHandle) + { + MemGraphState.Valid = FALSE; + MemGraphState.TooltipIndex = -1; + Graph_MoveGrid(MemGraphHandle, 1); + Graph_Draw(MemGraphHandle); + Graph_UpdateTooltip(MemGraphHandle); + InvalidateRect(MemGraphHandle, NULL, FALSE); + } + + if (ToolStatusConfig.CommitGraphEnabled && CommitGraphHandle) + { + CommitGraphState.Valid = FALSE; + CommitGraphState.TooltipIndex = -1; + Graph_MoveGrid(CommitGraphHandle, 1); + Graph_Draw(CommitGraphHandle); + Graph_UpdateTooltip(CommitGraphHandle); + InvalidateRect(CommitGraphHandle, NULL, FALSE); + } + + if (ToolStatusConfig.IoGraphEnabled && IoGraphHandle) + { + IoGraphState.Valid = FALSE; + IoGraphState.TooltipIndex = -1; + Graph_MoveGrid(IoGraphHandle, 1); + Graph_Draw(IoGraphHandle); + Graph_UpdateTooltip(IoGraphHandle); + InvalidateRect(IoGraphHandle, NULL, FALSE); + } +} + +// +// BEGIN copied from ProcessHacker/sysinfo.c +// + +static PPH_PROCESS_RECORD PhSipReferenceMaxCpuRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + LONG maxProcessIdLong; + HANDLE maxProcessId; + + // Find the process record for the max. CPU process for the particular time. + + maxProcessIdLong = PhGetItemCircularBuffer_ULONG(SystemStatistics.MaxCpuHistory, Index); + + if (!maxProcessIdLong) + return NULL; + + // This must be treated as a signed integer to handle Interrupts correctly. + maxProcessId = LongToHandle(maxProcessIdLong); + + // Note that the time we get has its components beyond seconds cleared. + // For example: + // * At 2.5 seconds a process is started. + // * At 2.75 seconds our process provider is fired, and the process is determined + // to have 75% CPU usage, which happens to be the maximum CPU usage. + // * However the 2.75 seconds is recorded as 2 seconds due to + // RtlTimeToSecondsSince1980. + // * If we call PhFindProcessRecord, it cannot find the process because it was + // started at 2.5 seconds, not 2 seconds or older. + // + // This mean we must add one second minus one tick (100ns) to the time, giving us + // 2.9999999 seconds. This will then make sure we find the process. + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(maxProcessId, &time); +} + +static PPH_STRING PhSipGetMaxCpuString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; + FLOAT maxCpuUsage; + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = PhSipReferenceMaxCpuRecord(Index)) + { + // We found the process record, so now we construct the max. usage string. + maxCpuUsage = PhGetItemCircularBuffer_FLOAT(SystemStatistics.MaxCpuUsageHistory, Index); + + // Make sure we don't try to display the PID of DPCs or Interrupts. + if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) + { + maxUsageString = PhaFormatString( + L"\n%s (%u): %.2f%%", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId), + maxCpuUsage * 100 + ); + } + else + { + maxUsageString = PhaFormatString( + L"\n%s: %.2f%%", + maxProcessRecord->ProcessName->Buffer, + maxCpuUsage * 100 + ); + } + + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} + +static PPH_PROCESS_RECORD PhSipReferenceMaxIoRecord( + _In_ LONG Index + ) +{ + LARGE_INTEGER time; + ULONG maxProcessId; + + // Find the process record for the max. I/O process for the particular time. + + maxProcessId = PhGetItemCircularBuffer_ULONG(SystemStatistics.MaxIoHistory, Index); + + if (!maxProcessId) + return NULL; + + // See above for the explanation. + PhGetStatisticsTime(NULL, Index, &time); + time.QuadPart += PH_TICKS_PER_SEC - 1; + + return PhFindProcessRecord(UlongToHandle(maxProcessId), &time); +} + +static PPH_STRING PhSipGetMaxIoString( + _In_ LONG Index + ) +{ + PPH_PROCESS_RECORD maxProcessRecord; + ULONG64 maxIoReadOther; + ULONG64 maxIoWrite; + + PPH_STRING maxUsageString = NULL; + + if (maxProcessRecord = PhSipReferenceMaxIoRecord(Index)) + { + // We found the process record, so now we construct the max. usage string. + maxIoReadOther = PhGetItemCircularBuffer_ULONG64(SystemStatistics.MaxIoReadOtherHistory, Index); + maxIoWrite = PhGetItemCircularBuffer_ULONG64(SystemStatistics.MaxIoWriteHistory, Index); + + if (!PH_IS_FAKE_PROCESS_ID(maxProcessRecord->ProcessId)) + { + maxUsageString = PhaFormatString( + L"\n%s (%u): R+O: %s, W: %s", + maxProcessRecord->ProcessName->Buffer, + HandleToUlong(maxProcessRecord->ProcessId), + PhaFormatSize(maxIoReadOther, -1)->Buffer, + PhaFormatSize(maxIoWrite, -1)->Buffer + ); + } + else + { + maxUsageString = PhaFormatString( + L"\n%s: R+O: %s, W: %s", + maxProcessRecord->ProcessName->Buffer, + PhaFormatSize(maxIoReadOther, -1)->Buffer, + PhaFormatSize(maxIoWrite, -1)->Buffer + ); + } + + PhDereferenceProcessRecord(maxProcessRecord); + } + + return maxUsageString; +} + +// +// END copied from ProcessHacker/sysinfo.c +// + +VOID ToolbarUpdateGraphsInfo(LPNMHDR Header) +{ + switch (Header->code) + { + case GCN_GETDRAWINFO: + { + if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), PhGetIntegerSetting(L"ColorCpuUser")); + + if (ProcessesUpdatedCount < 2) + return; + + PhGraphStateGetDrawInfo(&CpuGraphState, getDrawInfo, SystemStatistics.CpuUserHistory->Count); + + if (!CpuGraphState.Valid) + { + PhCopyCircularBuffer_FLOAT(SystemStatistics.CpuKernelHistory, + CpuGraphState.Data1, drawInfo->LineDataCount); + PhCopyCircularBuffer_FLOAT(SystemStatistics.CpuUserHistory, + CpuGraphState.Data2, drawInfo->LineDataCount); + + CpuGraphState.Valid = TRUE; + } + } + else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0); + + if (ProcessesUpdatedCount < 2) + return; + + PhGraphStateGetDrawInfo(&MemGraphState, getDrawInfo, SystemStatistics.PhysicalHistory->Count); + + if (!MemGraphState.Valid) + { + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + MemGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(SystemStatistics.PhysicalHistory, i); + } + + PhDivideSinglesBySingle( + MemGraphState.Data1, + (FLOAT)PhSystemBasicInformation.NumberOfPhysicalPages, + drawInfo->LineDataCount + ); + + MemGraphState.Valid = TRUE; + } + } + else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPrivate"), 0); + + if (ProcessesUpdatedCount < 2) + return; + + PhGraphStateGetDrawInfo(&CommitGraphState, getDrawInfo, SystemStatistics.CommitHistory->Count); + + if (!CommitGraphState.Valid) + { + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + CommitGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(SystemStatistics.CommitHistory, i); + } + + PhDivideSinglesBySingle( + CommitGraphState.Data1, + (FLOAT)SystemStatistics.Performance->CommitLimit, + drawInfo->LineDataCount + ); + + CommitGraphState.Valid = TRUE; + } + } + else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) + { + PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)Header; + PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo; + + drawInfo->Flags = PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_LINE_2; + PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorIoReadOther"), PhGetIntegerSetting(L"ColorIoWrite")); + + if (ProcessesUpdatedCount < 2) + return; + + PhGraphStateGetDrawInfo(&IoGraphState, getDrawInfo, SystemStatistics.IoReadHistory->Count); + + if (!IoGraphState.Valid) + { + FLOAT max = 1024 * 1024; // minimum scaling of 1 MB. + + for (ULONG i = 0; i < drawInfo->LineDataCount; i++) + { + IoGraphState.Data1[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoReadHistory, i) + + (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoOtherHistory, i); + IoGraphState.Data2[i] = + (FLOAT)PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoWriteHistory, i); + + if (max < IoGraphState.Data1[i] + IoGraphState.Data2[i]) + max = IoGraphState.Data1[i] + IoGraphState.Data2[i]; + } + + PhDivideSinglesBySingle(IoGraphState.Data1, max, drawInfo->LineDataCount); + PhDivideSinglesBySingle(IoGraphState.Data2, max, drawInfo->LineDataCount); + + IoGraphState.Valid = TRUE; + } + } + } + break; + case GCN_GETTOOLTIPTEXT: + { + PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)Header; + + if (getTooltipText->Index < getTooltipText->TotalCount) + { + if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) + { + if (CpuGraphState.TooltipIndex != getTooltipText->Index) + { + FLOAT cpuKernel; + FLOAT cpuUser; + + cpuKernel = PhGetItemCircularBuffer_FLOAT(SystemStatistics.CpuKernelHistory, getTooltipText->Index); + cpuUser = PhGetItemCircularBuffer_FLOAT(SystemStatistics.CpuUserHistory, getTooltipText->Index); + + PhMoveReference(&CpuGraphState.TooltipText, PhFormatString( + L"%.2f%%%s\n%s", + (cpuKernel + cpuUser) * 100, + PhGetStringOrEmpty(PhSipGetMaxCpuString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + } + + getTooltipText->Text = CpuGraphState.TooltipText->sr; + } + else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) + { + ULONG physicalUsage; + + physicalUsage = PhGetItemCircularBuffer_ULONG(SystemStatistics.PhysicalHistory, getTooltipText->Index); + + PhMoveReference(&MemGraphState.TooltipText, PhFormatString( + L"Physical memory: %s\n%s", + PhaFormatSize(UInt32x32To64(physicalUsage, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = MemGraphState.TooltipText->sr; + } + else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) + { + ULONG commitUsage; + + commitUsage = PhGetItemCircularBuffer_ULONG(SystemStatistics.CommitHistory, getTooltipText->Index); + + PhMoveReference(&CommitGraphState.TooltipText, PhFormatString( + L"Commit charge: %s\n%s", + PhaFormatSize(UInt32x32To64(commitUsage, PAGE_SIZE), -1)->Buffer, + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = CommitGraphState.TooltipText->sr; + } + else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) + { + ULONG64 ioRead; + ULONG64 ioWrite; + ULONG64 ioOther; + + ioRead = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoReadHistory, getTooltipText->Index); + ioWrite = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoWriteHistory, getTooltipText->Index); + ioOther = PhGetItemCircularBuffer_ULONG64(SystemStatistics.IoOtherHistory, getTooltipText->Index); + + PhMoveReference(&IoGraphState.TooltipText, PhFormatString( + L"R: %s\nW: %s\nO: %s%s\n%s", + PhaFormatSize(ioRead, -1)->Buffer, + PhaFormatSize(ioWrite, -1)->Buffer, + PhaFormatSize(ioOther, -1)->Buffer, + PhGetStringOrEmpty(PhSipGetMaxIoString(getTooltipText->Index)), + PH_AUTO_T(PH_STRING, PhGetStatisticsTimeString(NULL, getTooltipText->Index))->Buffer + )); + getTooltipText->Text = IoGraphState.TooltipText->sr; + } + } + } + break; + case GCN_MOUSEEVENT: + { + PPH_GRAPH_MOUSEEVENT mouseEvent = (PPH_GRAPH_MOUSEEVENT)Header; + PPH_PROCESS_RECORD record = NULL; + + if (ToolStatusConfig.CpuGraphEnabled && Header->hwndFrom == CpuGraphHandle) + { + if (mouseEvent->Message == WM_RBUTTONUP) + { + ShowCustomizeMenu(); + } + else + { + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = PhSipReferenceMaxCpuRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(PhMainWndHandle, record); + PhDereferenceProcessRecord(record); + } + } + } + else if (ToolStatusConfig.MemGraphEnabled && Header->hwndFrom == MemGraphHandle) + { + if (mouseEvent->Message == WM_RBUTTONUP) + { + ShowCustomizeMenu(); + } + } + else if (ToolStatusConfig.CommitGraphEnabled && Header->hwndFrom == CommitGraphHandle) + { + if (mouseEvent->Message == WM_RBUTTONUP) + { + ShowCustomizeMenu(); + } + } + else if (ToolStatusConfig.IoGraphEnabled && Header->hwndFrom == IoGraphHandle) + { + if (mouseEvent->Message == WM_RBUTTONUP) + { + ShowCustomizeMenu(); + } + else + { + if (mouseEvent->Message == WM_LBUTTONDBLCLK && mouseEvent->Index < mouseEvent->TotalCount) + { + record = PhSipReferenceMaxIoRecord(mouseEvent->Index); + } + + if (record) + { + PhShowProcessRecordDialog(PhMainWndHandle, record); + PhDereferenceProcessRecord(record); + } + } + } + } + break; + } +} \ No newline at end of file diff --git a/plugins/ToolStatus/main.c b/plugins/ToolStatus/main.c new file mode 100644 index 0000000..071aa7e --- /dev/null +++ b/plugins/ToolStatus/main.c @@ -0,0 +1,1421 @@ +/* + * Process Hacker ToolStatus - + * main program + * + * Copyright (C) 2011-2016 dmex + * Copyright (C) 2010-2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" + +PPH_STRING GetSearchboxText( + VOID + ); + +VOID RegisterTabSearch( + _In_ INT TabIndex, + _In_ PWSTR BannerText + ); + +PTOOLSTATUS_TAB_INFO RegisterTabInfo( + _In_ INT TabIndex + ); + +TOOLSTATUS_CONFIG ToolStatusConfig = { 0 }; +HWND ProcessTreeNewHandle = NULL; +HWND ServiceTreeNewHandle = NULL; +HWND NetworkTreeNewHandle = NULL; +INT SelectedTabIndex; +BOOLEAN UpdateAutomatically = TRUE; +BOOLEAN UpdateGraphs = TRUE; +TOOLBAR_THEME ToolBarTheme = TOOLBAR_THEME_NONE; +TOOLBAR_DISPLAY_STYLE DisplayStyle = TOOLBAR_DISPLAY_STYLE_SELECTIVETEXT; +SEARCHBOX_DISPLAY_MODE SearchBoxDisplayMode = SEARCHBOX_DISPLAY_MODE_ALWAYSSHOW; +REBAR_DISPLAY_LOCATION RebarDisplayLocation = REBAR_DISPLAY_LOCATION_TOP; +HWND RebarHandle = NULL; +HWND ToolBarHandle = NULL; +HWND SearchboxHandle = NULL; +HMENU MainMenu = NULL; +HACCEL AcceleratorTable = NULL; +PPH_STRING SearchboxText = NULL; +PH_PLUGIN_SYSTEM_STATISTICS SystemStatistics = { 0 }; +PH_CALLBACK_DECLARE(SearchChangedEvent); +PPH_HASHTABLE TabInfoHashtable; +PPH_TN_FILTER_ENTRY ProcessTreeFilterEntry = NULL; +PPH_TN_FILTER_ENTRY ServiceTreeFilterEntry = NULL; +PPH_TN_FILTER_ENTRY NetworkTreeFilterEntry = NULL; +PPH_PLUGIN PluginInstance = NULL; +TOOLSTATUS_INTERFACE PluginInterface = +{ + TOOLSTATUS_INTERFACE_VERSION, + GetSearchboxText, + WordMatchStringRef, + RegisterTabSearch, + &SearchChangedEvent, + RegisterTabInfo +}; + +static ULONG TargetingMode = 0; +static BOOLEAN TargetingWindow = FALSE; +static BOOLEAN TargetingCurrentWindowDraw = FALSE; +static BOOLEAN TargetingCompleted = FALSE; +static HWND TargetingCurrentWindow = NULL; +static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +static PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +static PH_CALLBACK_REGISTRATION LayoutPaddingCallbackRegistration; +static PH_CALLBACK_REGISTRATION TabPageCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ServiceTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION NetworkTreeNewInitializingCallbackRegistration; + +PPH_STRING GetSearchboxText( + VOID + ) +{ + return SearchboxText; +} + +VOID NTAPI ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ProcessesUpdatedCount++; + + if (ProcessesUpdatedCount < 2) + return; + + PhPluginGetSystemStatistics(&SystemStatistics); + + if (UpdateGraphs) + ToolbarUpdateGraphs(); + + if (ToolStatusConfig.StatusBarEnabled) + StatusBarUpdate(FALSE); +} + +VOID NTAPI TreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + *(HWND *)Context = ((PPH_PLUGIN_TREENEW_INFORMATION)Parameter)->TreeNewHandle; +} + +VOID RegisterTabSearch( + _In_ INT TabIndex, + _In_ PWSTR BannerText + ) +{ + PTOOLSTATUS_TAB_INFO tabInfo; + + tabInfo = RegisterTabInfo(TabIndex); + tabInfo->BannerText = BannerText; +} + +PTOOLSTATUS_TAB_INFO RegisterTabInfo( + _In_ INT TabIndex + ) +{ + PTOOLSTATUS_TAB_INFO tabInfoCopy; + PVOID *entry; + + tabInfoCopy = PhCreateAlloc(sizeof(TOOLSTATUS_TAB_INFO)); + memset(tabInfoCopy, 0, sizeof(TOOLSTATUS_TAB_INFO)); + + if (!PhAddItemSimpleHashtable(TabInfoHashtable, IntToPtr(TabIndex), tabInfoCopy)) + { + PhClearReference(&tabInfoCopy); + + if (entry = PhFindItemSimpleHashtable(TabInfoHashtable, IntToPtr(TabIndex))) + tabInfoCopy = *entry; + } + + return tabInfoCopy; +} + +PTOOLSTATUS_TAB_INFO FindTabInfo( + _In_ INT TabIndex + ) +{ + PVOID *entry; + + if (entry = PhFindItemSimpleHashtable(TabInfoHashtable, IntToPtr(TabIndex))) + return *entry; + + return NULL; +} + +HWND GetCurrentTreeNewHandle( + VOID + ) +{ + HWND treeNewHandle = NULL; + + switch (SelectedTabIndex) + { + case 0: + treeNewHandle = ProcessTreeNewHandle; + break; + case 1: + treeNewHandle = ServiceTreeNewHandle; + break; + case 2: + treeNewHandle = NetworkTreeNewHandle; + break; + default: + { + PTOOLSTATUS_TAB_INFO tabInfo; + + if ((tabInfo = FindTabInfo(SelectedTabIndex)) && tabInfo->GetTreeNewHandle) + { + treeNewHandle = tabInfo->GetTreeNewHandle(); + } + } + break; + } + + return treeNewHandle; +} + +VOID ShowCustomizeMenu( + VOID + ) +{ + POINT cursorPos; + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + + GetCursorPos(&cursorPos); + + menu = PhCreateEMenu(); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_SEARCHBOX, L"Search box", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_CPU_GRAPH, L"CPU history", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_IO_GRAPH, L"I/O history", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_MEMORY_GRAPH, L"Physical memory history", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_COMMIT_GRAPH, L"Commit charge history", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_TOOLBAR_LOCKUNLOCK, L"Lock the toolbar", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_TOOLBAR_CUSTOMIZE, L"Customize...", NULL, NULL), -1); + + if (ToolStatusConfig.SearchBoxEnabled) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_ENABLE_SEARCHBOX, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (ToolStatusConfig.CpuGraphEnabled) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_ENABLE_CPU_GRAPH, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (ToolStatusConfig.MemGraphEnabled) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_ENABLE_MEMORY_GRAPH, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (ToolStatusConfig.CommitGraphEnabled) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_ENABLE_COMMIT_GRAPH, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (ToolStatusConfig.IoGraphEnabled) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_ENABLE_IO_GRAPH, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + if (ToolStatusConfig.ToolBarLocked) + { + PhSetFlagsEMenuItem(menu, COMMAND_ID_TOOLBAR_LOCKUNLOCK, PH_EMENU_CHECKED, PH_EMENU_CHECKED); + } + + selectedItem = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + cursorPos.x, + cursorPos.y + ); + + if (selectedItem && selectedItem->Id != -1) + { + switch (selectedItem->Id) + { + case COMMAND_ID_ENABLE_SEARCHBOX: + { + ToolStatusConfig.SearchBoxEnabled = !ToolStatusConfig.SearchBoxEnabled; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + ReBarSaveLayoutSettings(); + + if (ToolStatusConfig.SearchBoxEnabled) + { + // Adding the Searchbox makes it focused, + // reset the focus back to the main window. + SetFocus(PhMainWndHandle); + } + } + break; + case COMMAND_ID_ENABLE_CPU_GRAPH: + { + ToolStatusConfig.CpuGraphEnabled = !ToolStatusConfig.CpuGraphEnabled; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + ReBarSaveLayoutSettings(); + } + break; + case COMMAND_ID_ENABLE_MEMORY_GRAPH: + { + ToolStatusConfig.MemGraphEnabled = !ToolStatusConfig.MemGraphEnabled; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + ReBarSaveLayoutSettings(); + } + break; + case COMMAND_ID_ENABLE_COMMIT_GRAPH: + { + ToolStatusConfig.CommitGraphEnabled = !ToolStatusConfig.CommitGraphEnabled; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + ReBarSaveLayoutSettings(); + } + break; + case COMMAND_ID_ENABLE_IO_GRAPH: + { + ToolStatusConfig.IoGraphEnabled = !ToolStatusConfig.IoGraphEnabled; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + ReBarSaveLayoutSettings(); + } + break; + case COMMAND_ID_TOOLBAR_LOCKUNLOCK: + { + UINT bandCount; + UINT bandIndex; + + bandCount = (UINT)SendMessage(RebarHandle, RB_GETBANDCOUNT, 0, 0); + + for (bandIndex = 0; bandIndex < bandCount; bandIndex++) + { + REBARBANDINFO rebarBandInfo = + { + REBARBANDINFO_V6_SIZE, + RBBIM_STYLE + }; + + SendMessage(RebarHandle, RB_GETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo); + + if (!(rebarBandInfo.fStyle & RBBS_GRIPPERALWAYS)) + { + // Removing the RBBS_NOGRIPPER style doesn't remove the gripper padding, + // So we toggle the RBBS_GRIPPERALWAYS style to make the Toolbar remove the padding. + + rebarBandInfo.fStyle |= RBBS_GRIPPERALWAYS; + + SendMessage(RebarHandle, RB_SETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo); + + rebarBandInfo.fStyle &= ~RBBS_GRIPPERALWAYS; + } + + if (rebarBandInfo.fStyle & RBBS_NOGRIPPER) + { + rebarBandInfo.fStyle &= ~RBBS_NOGRIPPER; + } + else + { + rebarBandInfo.fStyle |= RBBS_NOGRIPPER; + } + + SendMessage(RebarHandle, RB_SETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo); + } + + ToolStatusConfig.ToolBarLocked = !ToolStatusConfig.ToolBarLocked; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + } + break; + case COMMAND_ID_TOOLBAR_CUSTOMIZE: + { + ToolBarShowCustomizeDialog(); + } + break; + } + } + + PhDestroyEMenu(menu); +} + +VOID NTAPI TabPageUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + INT tabIndex = PtrToInt(Parameter); + + SelectedTabIndex = tabIndex; + + if (!SearchboxHandle) + return; + + switch (tabIndex) + { + case 0: + Edit_SetCueBannerText(SearchboxHandle, L"Search Processes (Ctrl+K)"); + break; + case 1: + Edit_SetCueBannerText(SearchboxHandle, L"Search Services (Ctrl+K)"); + break; + case 2: + Edit_SetCueBannerText(SearchboxHandle, L"Search Network (Ctrl+K)"); + break; + default: + { + PTOOLSTATUS_TAB_INFO tabInfo; + + if ((tabInfo = FindTabInfo(tabIndex)) && tabInfo->BannerText) + { + Edit_SetCueBannerText(SearchboxHandle, PhaConcatStrings2(tabInfo->BannerText, L" (Ctrl+K)")->Buffer); + } + else + { + // Disable the textbox if we're on an unsupported tab. + Edit_SetCueBannerText(SearchboxHandle, L"Search disabled"); + } + } + break; + } +} + +VOID NTAPI LayoutPaddingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_LAYOUT_PADDING_DATA layoutPadding = Parameter; + + if (RebarHandle && ToolStatusConfig.ToolBarEnabled) + { + RECT rebarRect; + //RECT clientRect; + //INT x, y, cx, cy; + + SendMessage(RebarHandle, WM_SIZE, 0, 0); + + // TODO: GetClientRect with PhMainWndHandle causes crash. + //GetClientRect(PhMainWndHandle, &clientRect); + GetClientRect(RebarHandle, &rebarRect); + + // Adjust the PH client area and exclude the rebar width. + layoutPadding->Padding.top += rebarRect.bottom; + + // TODO: Replace CCS_TOP with CCS_NOPARENTALIGN and use below code + //switch (RebarDisplayLocation) + //{ + //case RebarLocationLeft: + // { + // //x = 0; + // //y = 0; + // //cx = rebarRect.right - rebarRect.left; + // //cy = clientRect.bottom - clientRect.top; + // } + // break; + //case RebarLocationTop: + // { + // //x = 0; + // //y = 0; + // //cx = clientRect.right - clientRect.left; + // //cy = clientRect.bottom - clientRect.top; + // + // // Adjust the PH client area and exclude the rebar height. + // //layoutPadding->Padding.top += rebarRect.bottom; + // } + // break; + //case RebarLocationRight: + // { + // //x = clientRect.right - (rebarRect.right - rebarRect.left); + // //y = 0; + // //cx = rebarRect.right - rebarRect.left; + // //cy = clientRect.bottom - clientRect.top; + // } + // break; + //case RebarLocationBottom: + // { + // //x = 0; + // //y = clientRect.bottom - (rebarRect.bottom - rebarRect.top) - (StatusBarEnabled ? rebarRect.bottom + 1 : 0); + // //cx = clientRect.right - clientRect.left; + // //cy = rebarRect.bottom - rebarRect.top; + // + // // Adjust the PH client area and exclude the rebar width. + // //layoutPadding->Padding.bottom += rebarRect.bottom; + // } + // break; + //} + //MoveWindow(RebarHandle, x, y, cx, cy, TRUE); + + //if (SearchBoxDisplayStyle == SearchBoxDisplayAutoHide) + //{ + // static BOOLEAN isSearchboxVisible = FALSE; + // SIZE idealWidth; + // + // // Query the Toolbar ideal width + // SendMessage(ToolBarHandle, TB_GETIDEALSIZE, FALSE, (LPARAM)&idealWidth); + // + // // Hide the Searcbox band if the window size is too small... + // if (rebarRect.right > idealWidth.cx) + // { + // if (isSearchboxVisible) + // { + // if (!RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + // RebarBandInsert(REBAR_BAND_ID_SEARCHBOX, SearchboxHandle, 180, 20); + // + // isSearchboxVisible = FALSE; + // } + // } + // else + // { + // if (!isSearchboxVisible) + // { + // if (RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + // RebarBandRemove(REBAR_BAND_ID_SEARCHBOX); + // + // isSearchboxVisible = TRUE; + // } + // } + //} + } + + if (StatusBarHandle && ToolStatusConfig.StatusBarEnabled) + { + RECT statusBarRect; + + SendMessage(StatusBarHandle, WM_SIZE, 0, 0); + + GetClientRect(StatusBarHandle, &statusBarRect); + + // Adjust the PH client area and exclude the StatusBar width. + layoutPadding->Padding.bottom += statusBarRect.bottom; + + //InvalidateRect(StatusBarHandle, NULL, TRUE); + } +} + +BOOLEAN NTAPI MessageLoopFilter( + _In_ PMSG Message, + _In_ PVOID Context + ) +{ + if ( + Message->hwnd == PhMainWndHandle || + IsChild(PhMainWndHandle, Message->hwnd) + ) + { + if (TranslateAccelerator(PhMainWndHandle, AcceleratorTable, Message)) + return TRUE; + + if (Message->message == WM_SYSCHAR && ToolStatusConfig.AutoHideMenu && !GetMenu(PhMainWndHandle)) + { + ULONG key = (ULONG)Message->wParam; + + if (key == 'h' || key == 'v' || key == 't' || key == 'u' || key == 'e') + { + SetMenu(PhMainWndHandle, MainMenu); + DrawMenuBar(PhMainWndHandle); + SendMessage(PhMainWndHandle, WM_SYSCHAR, Message->wParam, Message->lParam); + return TRUE; + } + } + } + + return FALSE; +} + +VOID DrawWindowBorderForTargeting( + _In_ HWND hWnd + ) +{ + RECT rect; + HDC hdc; + + GetWindowRect(hWnd, &rect); + hdc = GetWindowDC(hWnd); + + if (hdc) + { + INT penWidth; + INT oldDc; + HPEN pen; + HBRUSH brush; + + penWidth = GetSystemMetrics(SM_CXBORDER) * 3; + oldDc = SaveDC(hdc); + + // Get an inversion effect. + SetROP2(hdc, R2_NOT); + + pen = CreatePen(PS_INSIDEFRAME, penWidth, RGB(0x00, 0x00, 0x00)); + SelectPen(hdc, pen); + + brush = GetStockBrush(NULL_BRUSH); + SelectBrush(hdc, brush); + + // Draw the rectangle. + Rectangle(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top); + + // Cleanup. + DeleteObject(pen); + + RestoreDC(hdc, oldDc); + ReleaseDC(hWnd, hdc); + } +} + +LRESULT CALLBACK MainWndSubclassProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ ULONG_PTR dwRefData + ) +{ + switch (uMsg) + { + case WM_COMMAND: + { + switch (GET_WM_COMMAND_CMD(wParam, lParam)) + { + case EN_CHANGE: + { + PPH_STRING newSearchboxText; + + if (GET_WM_COMMAND_HWND(wParam, lParam) != SearchboxHandle) + break; + + newSearchboxText = PH_AUTO(PhGetWindowText(SearchboxHandle)); + + if (!PhEqualString(SearchboxText, newSearchboxText, FALSE)) + { + // Cache the current search text for our callback. + PhSwapReference(&SearchboxText, newSearchboxText); + + if (!PhIsNullOrEmptyString(SearchboxText)) + { + // Expand the nodes to ensure that they will be visible to the user. + PhExpandAllProcessNodes(TRUE); + PhDeselectAllProcessNodes(); + PhDeselectAllServiceNodes(); + } + + PhApplyTreeNewFilters(PhGetFilterSupportProcessTreeList()); + PhApplyTreeNewFilters(PhGetFilterSupportServiceTreeList()); + PhApplyTreeNewFilters(PhGetFilterSupportNetworkTreeList()); + + PhInvokeCallback(&SearchChangedEvent, SearchboxText); + } + + goto DefaultWndProc; + } + break; + case EN_KILLFOCUS: + { + if (GET_WM_COMMAND_HWND(wParam, lParam) != SearchboxHandle) + break; + + if (SearchBoxDisplayMode != SEARCHBOX_DISPLAY_MODE_HIDEINACTIVE) + break; + + if (SearchboxText->Length == 0) + { + if (RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + RebarBandRemove(REBAR_BAND_ID_SEARCHBOX); + } + } + break; + } + + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case PHAPP_ID_ESC_EXIT: + { + // If we're targeting and the user presses the Esc key, cancel the targeting. + // We also make sure the window doesn't get closed, by filtering out the message. + if (TargetingWindow) + { + TargetingWindow = FALSE; + ReleaseCapture(); + + goto DefaultWndProc; + } + } + break; + case ID_SEARCH: + { + // handle keybind Ctrl + K + if (SearchboxHandle && ToolStatusConfig.SearchBoxEnabled) + { + SetFocus(SearchboxHandle); + Edit_SetSel(SearchboxHandle, 0, -1); + } + + goto DefaultWndProc; + } + break; + case ID_SEARCH_CLEAR: + { + if (SearchboxHandle && ToolStatusConfig.SearchBoxEnabled) + { + SetFocus(SearchboxHandle); + Static_SetText(SearchboxHandle, L""); + } + + goto DefaultWndProc; + } + break; + case PHAPP_ID_VIEW_ALWAYSONTOP: + { + // Let Process Hacker perform the default processing. + DefSubclassProc(hWnd, uMsg, wParam, lParam); + + // Query the settings. + BOOLEAN isAlwaysOnTopEnabled = (BOOLEAN)PhGetIntegerSetting(L"MainWindowAlwaysOnTop"); + + // Set the pressed button state. + SendMessage(ToolBarHandle, TB_PRESSBUTTON, (WPARAM)PHAPP_ID_VIEW_ALWAYSONTOP, (LPARAM)(MAKELONG(isAlwaysOnTopEnabled, 0))); + + goto DefaultWndProc; + } + break; + case PHAPP_ID_UPDATEINTERVAL_FAST: + case PHAPP_ID_UPDATEINTERVAL_NORMAL: + case PHAPP_ID_UPDATEINTERVAL_BELOWNORMAL: + case PHAPP_ID_UPDATEINTERVAL_SLOW: + case PHAPP_ID_UPDATEINTERVAL_VERYSLOW: + { + // Let Process Hacker perform the default processing. + DefSubclassProc(hWnd, uMsg, wParam, lParam); + + StatusBarUpdate(TRUE); + + goto DefaultWndProc; + } + break; + case PHAPP_ID_VIEW_UPDATEAUTOMATICALLY: + { + UpdateAutomatically = !UpdateAutomatically; + + StatusBarUpdate(TRUE); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR hdr = (LPNMHDR)lParam; + + if (RebarHandle && hdr->hwndFrom == RebarHandle) + { + switch (hdr->code) + { + case RBN_HEIGHTCHANGE: + { + // Invoke the LayoutPaddingCallback. + SendMessage(PhMainWndHandle, WM_SIZE, 0, 0); + } + break; + case RBN_CHEVRONPUSHED: + { + LPNMREBARCHEVRON rebar; + ULONG index = 0; + ULONG buttonCount = 0; + RECT toolbarRect; + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + + rebar = (LPNMREBARCHEVRON)lParam; + menu = PhCreateEMenu(); + + GetClientRect(ToolBarHandle, &toolbarRect); + + buttonCount = (ULONG)SendMessage(ToolBarHandle, TB_BUTTONCOUNT, 0, 0); + + for (index = 0; index < buttonCount; index++) + { + RECT buttonRect; + TBBUTTONINFO buttonInfo = + { + sizeof(TBBUTTONINFO), + TBIF_BYINDEX | TBIF_STYLE | TBIF_COMMAND | TBIF_IMAGE + }; + + // Get the client coordinates of the button. + if (SendMessage(ToolBarHandle, TB_GETITEMRECT, index, (LPARAM)&buttonRect) == -1) + break; + + if (buttonRect.right <= toolbarRect.right) + continue; + + // Get extended button information. + if (SendMessage(ToolBarHandle, TB_GETBUTTONINFO, index, (LPARAM)&buttonInfo) == -1) + break; + + if (buttonInfo.fsStyle == BTNS_SEP) + { + // Add separators to menu. + PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + } + else + { + PPH_EMENU_ITEM menuItem; + + if (PhGetOwnTokenAttributes().Elevated && buttonInfo.idCommand == PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES) + { + // Don't show the 'Show Details for All Processes' button in the + // dropdown menu when we're elevated. + continue; + } + + // Add buttons to menu. + menuItem = PhCreateEMenuItem(0, buttonInfo.idCommand, ToolbarGetText(buttonInfo.idCommand), NULL, NULL); + + menuItem->Flags |= PH_EMENU_BITMAP_OWNED; + menuItem->Bitmap = ToolbarGetImage(buttonInfo.idCommand); + + switch (buttonInfo.idCommand) + { + case PHAPP_ID_VIEW_ALWAYSONTOP: + { + // Set the pressed state. + if (PhGetIntegerSetting(L"MainWindowAlwaysOnTop")) + menuItem->Flags |= PH_EMENU_CHECKED; + } + break; + case TIDC_FINDWINDOW: + case TIDC_FINDWINDOWTHREAD: + case TIDC_FINDWINDOWKILL: + { + // Note: These buttons are incompatible with menus. + menuItem->Flags |= PH_EMENU_DISABLED; + } + break; + case TIDC_POWERMENUDROPDOWN: + { + // Create the sub-menu... + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_LOCK, L"&Lock", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_LOGOFF, L"Log o&ff", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SLEEP, L"&Sleep", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_HIBERNATE, L"&Hibernate", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_RESTART, L"R&estart", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_RESTARTBOOTOPTIONS, L"Restart to boot &options", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SHUTDOWN, L"Shu&t down", NULL, NULL), -1); + PhInsertEMenuItem(menuItem, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SHUTDOWNHYBRID, L"H&ybrid shut down", NULL, NULL), -1); + } + break; + } + + PhInsertEMenuItem(menu, menuItem, -1); + } + } + + MapWindowPoints(RebarHandle, NULL, (LPPOINT)&rebar->rc, 2); + + selectedItem = PhShowEMenu( + menu, + hWnd, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + rebar->rc.left, + rebar->rc.bottom + ); + + if (selectedItem && selectedItem->Id != -1) + { + SendMessage(PhMainWndHandle, WM_COMMAND, MAKEWPARAM(selectedItem->Id, BN_CLICKED), 0); + } + + PhDestroyEMenu(menu); + } + break; + case RBN_LAYOUTCHANGED: + { + ReBarSaveLayoutSettings(); + } + break; + } + + goto DefaultWndProc; + } + else if (ToolBarHandle && hdr->hwndFrom == ToolBarHandle) + { + switch (hdr->code) + { + case TBN_GETDISPINFO: + { + LPNMTBDISPINFO toolbarDisplayInfo = (LPNMTBDISPINFO)lParam; + + if (toolbarDisplayInfo->dwMask & TBNF_IMAGE) + { + BOOLEAN found = FALSE; + + // Try to find the cached bitmap index. + // NOTE: The TBNF_DI_SETITEM flag below will cache the index so we only get called once. + // However, when adding buttons from the customize dialog we get called a second time, + // so we cache the index in our ToolbarButtons array to prevent ToolBarImageList from growing. + for (INT i = 0; i < ARRAYSIZE(ToolbarButtons); i++) + { + if (ToolbarButtons[i].idCommand == toolbarDisplayInfo->idCommand) + { + if (ToolbarButtons[i].iBitmap != I_IMAGECALLBACK) + { + found = TRUE; + + // Cache the bitmap index. + toolbarDisplayInfo->dwMask |= TBNF_DI_SETITEM; + // Set the bitmap index. + toolbarDisplayInfo->iImage = ToolbarButtons[i].iBitmap; + } + break; + } + } + + if (!found) + { + // We didn't find a cached bitmap index... + // Load the button bitmap and cache the index. + for (INT i = 0; i < ARRAYSIZE(ToolbarButtons); i++) + { + if (ToolbarButtons[i].idCommand == toolbarDisplayInfo->idCommand) + { + HBITMAP buttonImage; + + buttonImage = ToolbarGetImage(toolbarDisplayInfo->idCommand); + + // Cache the bitmap index. + toolbarDisplayInfo->dwMask |= TBNF_DI_SETITEM; + // Add the image, cache the value in the ToolbarButtons array, set the bitmap index. + toolbarDisplayInfo->iImage = ToolbarButtons[i].iBitmap = ImageList_Add( + ToolBarImageList, + buttonImage, + NULL + ); + + DeleteObject(buttonImage); + break; + } + } + } + } + } + break; + case TBN_DROPDOWN: + { + LPNMTOOLBAR toolbar = (LPNMTOOLBAR)hdr; + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + + if (toolbar->iItem != TIDC_POWERMENUDROPDOWN) + break; + + menu = PhCreateEMenu(); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_LOCK, L"&Lock", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_LOGOFF, L"Log o&ff", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SLEEP, L"&Sleep", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_HIBERNATE, L"&Hibernate", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(PH_EMENU_SEPARATOR, 0, NULL, NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_RESTART, L"R&estart", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_RESTARTBOOTOPTIONS, L"Restart to boot &options", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SHUTDOWN, L"Shu&t down", NULL, NULL), -1); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, PHAPP_ID_COMPUTER_SHUTDOWNHYBRID, L"H&ybrid shut down", NULL, NULL), -1); + + MapWindowPoints(ToolBarHandle, NULL, (LPPOINT)&toolbar->rcButton, 2); + + selectedItem = PhShowEMenu( + menu, + hWnd, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_TOP, + toolbar->rcButton.left, + toolbar->rcButton.bottom + ); + + if (selectedItem && selectedItem->Id != -1) + { + SendMessage(PhMainWndHandle, WM_COMMAND, MAKEWPARAM(selectedItem->Id, BN_CLICKED), 0); + } + + PhDestroyEMenu(menu); + } + return TBDDRET_DEFAULT; + case NM_LDOWN: + { + LPNMCLICK toolbar = (LPNMCLICK)hdr; + ULONG id = (ULONG)toolbar->dwItemSpec; + + if (id == -1) + break; + + if (id == TIDC_FINDWINDOW || id == TIDC_FINDWINDOWTHREAD || id == TIDC_FINDWINDOWKILL) + { + // Direct all mouse events to this window. + SetCapture(hWnd); + + // Set the cursor. + SetCursor(LoadCursor(NULL, IDC_CROSS)); + + // Send the window to the bottom. + SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + + TargetingWindow = TRUE; + TargetingCurrentWindow = NULL; + TargetingCurrentWindowDraw = FALSE; + TargetingCompleted = FALSE; + TargetingMode = id; + + SendMessage(hWnd, WM_MOUSEMOVE, 0, 0); + } + } + break; + case NM_RCLICK: + { + ShowCustomizeMenu(); + } + break; + } + + goto DefaultWndProc; + } + else if (StatusBarHandle && hdr->hwndFrom == StatusBarHandle) + { + switch (hdr->code) + { + case NM_RCLICK: + { + StatusBarShowMenu(); + } + break; + } + + goto DefaultWndProc; + } + else if ( + CpuGraphHandle && hdr->hwndFrom == CpuGraphHandle || + MemGraphHandle && hdr->hwndFrom == MemGraphHandle || + CommitGraphHandle && hdr->hwndFrom == CommitGraphHandle || + IoGraphHandle && hdr->hwndFrom == IoGraphHandle + ) + { + ToolbarUpdateGraphsInfo(hdr); + + goto DefaultWndProc; + } + } + break; + case WM_MOUSEMOVE: + { + if (TargetingWindow) + { + POINT cursorPos; + HWND windowOverMouse; + ULONG processId; + ULONG threadId; + + GetCursorPos(&cursorPos); + windowOverMouse = WindowFromPoint(cursorPos); + + if (TargetingCurrentWindow != windowOverMouse) + { + if (TargetingCurrentWindow && TargetingCurrentWindowDraw) + { + // Invert the old border (to remove it). + DrawWindowBorderForTargeting(TargetingCurrentWindow); + } + + if (windowOverMouse) + { + threadId = GetWindowThreadProcessId(windowOverMouse, &processId); + + // Draw a rectangle over the current window (but not if it's one of our own). + if (UlongToHandle(processId) != NtCurrentProcessId()) + { + DrawWindowBorderForTargeting(windowOverMouse); + TargetingCurrentWindowDraw = TRUE; + } + else + { + TargetingCurrentWindowDraw = FALSE; + } + } + + TargetingCurrentWindow = windowOverMouse; + } + + goto DefaultWndProc; + } + } + break; + case WM_LBUTTONUP: + { + if (TargetingWindow) + { + ULONG processId; + ULONG threadId; + + TargetingCompleted = TRUE; + + // Reset the original cursor. + SetCursor(LoadCursor(NULL, IDC_ARROW)); + + // Bring the window back to the top, and preserve the Always on Top setting. + SetWindowPos(PhMainWndHandle, PhGetIntegerSetting(L"MainWindowAlwaysOnTop") ? HWND_TOPMOST : HWND_TOP, + 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + + TargetingWindow = FALSE; + ReleaseCapture(); + + if (TargetingCurrentWindow) + { + if (TargetingCurrentWindowDraw) + { + // Remove the border on the window we found. + DrawWindowBorderForTargeting(TargetingCurrentWindow); + } + + if (ToolStatusConfig.ResolveGhostWindows) + { + // This is an undocumented function exported by user32.dll that + // retrieves the hung window represented by a ghost window. + static HWND (WINAPI *HungWindowFromGhostWindow_I)( + _In_ HWND hWnd + ); + + if (!HungWindowFromGhostWindow_I) + HungWindowFromGhostWindow_I = PhGetModuleProcAddress(L"user32.dll", "HungWindowFromGhostWindow"); + + if (HungWindowFromGhostWindow_I) + { + HWND hungWindow = HungWindowFromGhostWindow_I(TargetingCurrentWindow); + + // The call will have failed if the window wasn't actually a ghost + // window. + if (hungWindow) + TargetingCurrentWindow = hungWindow; + } + } + + threadId = GetWindowThreadProcessId(TargetingCurrentWindow, &processId); + + if (threadId && processId && UlongToHandle(processId) != NtCurrentProcessId()) + { + PPH_PROCESS_NODE processNode; + + processNode = PhFindProcessNode(UlongToHandle(processId)); + + if (processNode) + { + ProcessHacker_SelectTabPage(hWnd, 0); + ProcessHacker_SelectProcessNode(hWnd, processNode); + } + + switch (TargetingMode) + { + case TIDC_FINDWINDOWTHREAD: + { + PPH_PROCESS_PROPCONTEXT propContext; + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(UlongToHandle(processId))) + { + if (propContext = PhCreateProcessPropContext(hWnd, processItem)) + { + PhSetSelectThreadIdProcessPropContext(propContext, UlongToHandle(threadId)); + PhShowProcessProperties(propContext); + PhDereferenceObject(propContext); + } + + PhDereferenceObject(processItem); + } + else + { + PhShowError(hWnd, L"The process (PID %lu) does not exist.", processId); + } + } + break; + case TIDC_FINDWINDOWKILL: + { + PPH_PROCESS_ITEM processItem; + + if (processItem = PhReferenceProcessItem(UlongToHandle(processId))) + { + PhUiTerminateProcesses(hWnd, &processItem, 1); + PhDereferenceObject(processItem); + } + else + { + PhShowError(hWnd, L"The process (PID %lu) does not exist.", processId); + } + } + break; + } + } + } + + goto DefaultWndProc; + } + } + break; + case WM_CAPTURECHANGED: + { + if (!TargetingCompleted) + { + // The user cancelled the targeting, probably by pressing the Esc key. + + // Remove the border on the currently selected window. + if (TargetingCurrentWindow) + { + if (TargetingCurrentWindowDraw) + { + // Remove the border on the window we found. + DrawWindowBorderForTargeting(TargetingCurrentWindow); + } + } + + SetWindowPos(PhMainWndHandle, PhGetIntegerSetting(L"MainWindowAlwaysOnTop") ? HWND_TOPMOST : HWND_TOP, + 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + + TargetingCompleted = TRUE; + } + } + break; + case WM_SIZE: + // Resize PH main window client-area. + ProcessHacker_InvalidateLayoutPadding(hWnd); + break; + case WM_SETTINGCHANGE: + // Forward to the Searchbox so we can reinitialize the settings... + SendMessage(SearchboxHandle, WM_SETTINGCHANGE, 0, 0); + break; + case WM_SHOWWINDOW: + { + UpdateGraphs = (BOOLEAN)wParam; + } + break; + case WM_SYSCOMMAND: + { + if ((wParam & 0xFFF0) == SC_KEYMENU && lParam == 0) + { + if (!ToolStatusConfig.AutoHideMenu) + break; + + if (GetMenu(PhMainWndHandle)) + { + SetMenu(PhMainWndHandle, NULL); + } + else + { + SetMenu(PhMainWndHandle, MainMenu); + DrawMenuBar(PhMainWndHandle); + } + } + else if ((wParam & 0xFFF0) == SC_MINIMIZE) + { + UpdateGraphs = FALSE; + } + else if ((wParam & 0xFFF0) == SC_RESTORE) + { + UpdateGraphs = TRUE; + } + } + break; + case WM_EXITMENULOOP: + { + if (!ToolStatusConfig.AutoHideMenu) + break; + + if (GetMenu(PhMainWndHandle)) + { + SetMenu(PhMainWndHandle, NULL); + } + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); + +DefaultWndProc: + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +VOID NTAPI MainWindowShowingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PhRegisterMessageLoopFilter(MessageLoopFilter, NULL); + PhRegisterCallback( + ProcessHacker_GetCallbackLayoutPadding(PhMainWndHandle), + LayoutPaddingCallback, + NULL, + &LayoutPaddingCallbackRegistration + ); + SetWindowSubclass(PhMainWndHandle, MainWndSubclassProc, 0, 0); + + ToolbarLoadSettings(); + ReBarLoadLayoutSettings(); + StatusBarLoadSettings(); + + MainMenu = GetMenu(PhMainWndHandle); + if (ToolStatusConfig.AutoHideMenu) + { + SetMenu(PhMainWndHandle, NULL); + } +} + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ToolStatusConfig.Flags = PhGetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG); + ToolBarTheme = (TOOLBAR_THEME)PhGetIntegerSetting(SETTING_NAME_TOOLBAR_THEME); + DisplayStyle = (TOOLBAR_DISPLAY_STYLE)PhGetIntegerSetting(SETTING_NAME_TOOLBARDISPLAYSTYLE); + SearchBoxDisplayMode = (SEARCHBOX_DISPLAY_MODE)PhGetIntegerSetting(SETTING_NAME_SEARCHBOXDISPLAYMODE); + UpdateGraphs = !PhGetIntegerSetting(L"StartHidden"); +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ShowOptionsDialog(Parameter); +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_TOOLSTATUS_CONFIG, L"1F" }, + { IntegerSettingType, SETTING_NAME_TOOLBAR_THEME, L"0" }, + { IntegerSettingType, SETTING_NAME_TOOLBARDISPLAYSTYLE, L"1" }, + { IntegerSettingType, SETTING_NAME_SEARCHBOXDISPLAYMODE, L"0" }, + { StringSettingType, SETTING_NAME_REBAR_CONFIG, L"" }, + { StringSettingType, SETTING_NAME_TOOLBAR_CONFIG, L"" }, + { StringSettingType, SETTING_NAME_STATUSBAR_CONFIG, L"" } + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Toolbar and Status Bar"; + info->Author = L"dmex, wj32"; + info->Description = L"Adds a Toolbar, Status Bar and Search box.\r\n\r\nModern Toolbar icons by http://www.icons8.com"; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1119"; + info->HasOptions = TRUE; + info->Interface = &PluginInterface; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessesUpdated), + ProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowTabChanged), + TabPageUpdatedCallback, + NULL, + &TabPageCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), + TreeNewInitializingCallback, + &ProcessTreeNewHandle, + &ProcessTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackServiceTreeNewInitializing), + TreeNewInitializingCallback, + &ServiceTreeNewHandle, + &ServiceTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackNetworkTreeNewInitializing), + TreeNewInitializingCallback, + &NetworkTreeNewHandle, + &NetworkTreeNewInitializingCallbackRegistration + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + + AcceleratorTable = LoadAccelerators( + Instance, + MAKEINTRESOURCE(IDR_MAINWND_ACCEL) + ); + + TabInfoHashtable = PhCreateSimpleHashtable(3); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/ToolStatus/options.c b/plugins/ToolStatus/options.c new file mode 100644 index 0000000..d8a6bfb --- /dev/null +++ b/plugins/ToolStatus/options.c @@ -0,0 +1,101 @@ +/* + * Process Hacker ToolStatus - + * Plugin Options + * + * Copyright (C) 2011-2016 dmex + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, GetParent(hwndDlg)); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_TOOLBAR), + ToolStatusConfig.ToolBarEnabled ? BST_CHECKED : BST_UNCHECKED); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_STATUSBAR), + ToolStatusConfig.StatusBarEnabled ? BST_CHECKED : BST_UNCHECKED); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_RESOLVEGHOSTWINDOWS), + ToolStatusConfig.ResolveGhostWindows ? BST_CHECKED : BST_UNCHECKED); + + Button_SetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_AUTOHIDE_MENU), + ToolStatusConfig.AutoHideMenu ? BST_CHECKED : BST_UNCHECKED); + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + ToolStatusConfig.ToolBarEnabled = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_TOOLBAR)) == BST_CHECKED; + ToolStatusConfig.StatusBarEnabled = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_STATUSBAR)) == BST_CHECKED; + ToolStatusConfig.ResolveGhostWindows = Button_GetCheck(GetDlgItem(hwndDlg, IDC_RESOLVEGHOSTWINDOWS)) == BST_CHECKED; + ToolStatusConfig.AutoHideMenu = Button_GetCheck(GetDlgItem(hwndDlg, IDC_ENABLE_AUTOHIDE_MENU)) == BST_CHECKED; + + PhSetIntegerSetting(SETTING_NAME_TOOLSTATUS_CONFIG, ToolStatusConfig.Flags); + + ToolbarLoadSettings(); + + if (ToolStatusConfig.AutoHideMenu) + { + SetMenu(PhMainWndHandle, NULL); + } + else + { + SetMenu(PhMainWndHandle, MainMenu); + DrawMenuBar(PhMainWndHandle); + } + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} + +VOID ShowOptionsDialog( + _In_opt_ HWND Parent + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + Parent, + OptionsDlgProc + ); +} \ No newline at end of file diff --git a/plugins/ToolStatus/resource.h b/plugins/ToolStatus/resource.h new file mode 100644 index 0000000..7c019d0 --- /dev/null +++ b/plugins/ToolStatus/resource.h @@ -0,0 +1,57 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ToolStatus.rc +// +#define IDD_OPTIONS 102 +#define IDR_MAINWND_ACCEL 103 +#define IDB_SEARCH_ACTIVE 104 +#define IDB_SEARCH_ACTIVE_BMP 119 +#define IDB_SEARCH_INACTIVE 120 +#define IDB_SEARCH_INACTIVE_BMP 121 +#define IDB_APPLICATION_GET_MODERN 126 +#define IDB_APPLICATION_GO_MODERN 127 +#define IDB_APPLICATION_MODERN 128 +#define IDB_ARROW_REFRESH_MODERN 129 +#define IDB_CHART_LINE_MODERN 130 +#define IDB_COG_EDIT_MODERN 131 +#define IDB_CROSS_MODERN 132 +#define IDB_FIND_MODERN 133 +#define IDB_POWER_MODERN 134 +#define IDD_CUSTOMIZE_TB 135 +#define IDD_CUSTOMIZE_SB 136 +#define IDI_ARROW_REFRESH 137 +#define IDI_COG_EDIT 138 +#define IDI_FIND 139 +#define IDI_CHART_LINE 140 +#define IDI_TBAPPLICATION 141 +#define IDI_APPLICATION_GO 142 +#define IDI_CROSS 143 +#define IDI_APPLICATION_GET 144 +#define IDI_LIGHTBULB_OFF 145 +#define IDC_ENABLE_TOOLBAR 1001 +#define IDC_ENABLE_MODERN 1002 +#define IDC_ENABLE_STATUSBAR 1003 +#define IDC_RESOLVEGHOSTWINDOWS 1004 +#define IDC_AVAILABLE 1005 +#define IDC_ADD 1006 +#define IDC_REMOVE 1007 +#define IDC_CURRENT 1008 +#define IDC_SEARCHOPTIONS 1009 +#define IDC_TEXTOPTIONS 1010 +#define IDC_MOVEUP 1011 +#define IDC_MOVEDOWN 1012 +#define IDC_THEMEOPTIONS 1013 +#define IDC_RESET 1014 +#define IDC_ENABLE_AUTOHIDE_MENU 1015 +#define ID_SEARCH 40011 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 146 +#define _APS_NEXT_COMMAND_VALUE 40016 +#define _APS_NEXT_CONTROL_VALUE 1016 +#define _APS_NEXT_SYMED_VALUE 11010 +#endif +#endif diff --git a/plugins/ToolStatus/resources/active_search.bmp b/plugins/ToolStatus/resources/active_search.bmp new file mode 100644 index 0000000..61b13de Binary files /dev/null and b/plugins/ToolStatus/resources/active_search.bmp differ diff --git a/plugins/ToolStatus/resources/active_search.png b/plugins/ToolStatus/resources/active_search.png new file mode 100644 index 0000000..b96bfb7 Binary files /dev/null and b/plugins/ToolStatus/resources/active_search.png differ diff --git a/plugins/ToolStatus/resources/application.ico b/plugins/ToolStatus/resources/application.ico new file mode 100644 index 0000000..3627a17 Binary files /dev/null and b/plugins/ToolStatus/resources/application.ico differ diff --git a/plugins/ToolStatus/resources/application_get.ico b/plugins/ToolStatus/resources/application_get.ico new file mode 100644 index 0000000..4d23600 Binary files /dev/null and b/plugins/ToolStatus/resources/application_get.ico differ diff --git a/plugins/ToolStatus/resources/application_get_modern.png b/plugins/ToolStatus/resources/application_get_modern.png new file mode 100644 index 0000000..f2ea26c Binary files /dev/null and b/plugins/ToolStatus/resources/application_get_modern.png differ diff --git a/plugins/ToolStatus/resources/application_go.ico b/plugins/ToolStatus/resources/application_go.ico new file mode 100644 index 0000000..b38afeb Binary files /dev/null and b/plugins/ToolStatus/resources/application_go.ico differ diff --git a/plugins/ToolStatus/resources/application_go_modern.png b/plugins/ToolStatus/resources/application_go_modern.png new file mode 100644 index 0000000..be2510f Binary files /dev/null and b/plugins/ToolStatus/resources/application_go_modern.png differ diff --git a/plugins/ToolStatus/resources/application_modern.png b/plugins/ToolStatus/resources/application_modern.png new file mode 100644 index 0000000..a56dd9f Binary files /dev/null and b/plugins/ToolStatus/resources/application_modern.png differ diff --git a/plugins/ToolStatus/resources/arrow_refresh.ico b/plugins/ToolStatus/resources/arrow_refresh.ico new file mode 100644 index 0000000..1a74f80 Binary files /dev/null and b/plugins/ToolStatus/resources/arrow_refresh.ico differ diff --git a/plugins/ToolStatus/resources/arrow_refresh_modern.png b/plugins/ToolStatus/resources/arrow_refresh_modern.png new file mode 100644 index 0000000..3ad5a70 Binary files /dev/null and b/plugins/ToolStatus/resources/arrow_refresh_modern.png differ diff --git a/plugins/ToolStatus/resources/chart_line.ico b/plugins/ToolStatus/resources/chart_line.ico new file mode 100644 index 0000000..c9f9cf0 Binary files /dev/null and b/plugins/ToolStatus/resources/chart_line.ico differ diff --git a/plugins/ToolStatus/resources/chart_line_modern.png b/plugins/ToolStatus/resources/chart_line_modern.png new file mode 100644 index 0000000..5334f3b Binary files /dev/null and b/plugins/ToolStatus/resources/chart_line_modern.png differ diff --git a/plugins/ToolStatus/resources/cog_edit.ico b/plugins/ToolStatus/resources/cog_edit.ico new file mode 100644 index 0000000..336599f Binary files /dev/null and b/plugins/ToolStatus/resources/cog_edit.ico differ diff --git a/plugins/ToolStatus/resources/cog_edit_modern.png b/plugins/ToolStatus/resources/cog_edit_modern.png new file mode 100644 index 0000000..6048568 Binary files /dev/null and b/plugins/ToolStatus/resources/cog_edit_modern.png differ diff --git a/plugins/ToolStatus/resources/cross.ico b/plugins/ToolStatus/resources/cross.ico new file mode 100644 index 0000000..ca77b1e Binary files /dev/null and b/plugins/ToolStatus/resources/cross.ico differ diff --git a/plugins/ToolStatus/resources/cross_modern.png b/plugins/ToolStatus/resources/cross_modern.png new file mode 100644 index 0000000..1198f3d Binary files /dev/null and b/plugins/ToolStatus/resources/cross_modern.png differ diff --git a/plugins/ToolStatus/resources/find.ico b/plugins/ToolStatus/resources/find.ico new file mode 100644 index 0000000..81e8b54 Binary files /dev/null and b/plugins/ToolStatus/resources/find.ico differ diff --git a/plugins/ToolStatus/resources/find_modern.png b/plugins/ToolStatus/resources/find_modern.png new file mode 100644 index 0000000..ce3e664 Binary files /dev/null and b/plugins/ToolStatus/resources/find_modern.png differ diff --git a/plugins/ToolStatus/resources/inactive_search.bmp b/plugins/ToolStatus/resources/inactive_search.bmp new file mode 100644 index 0000000..cf19cb8 Binary files /dev/null and b/plugins/ToolStatus/resources/inactive_search.bmp differ diff --git a/plugins/ToolStatus/resources/inactive_search.png b/plugins/ToolStatus/resources/inactive_search.png new file mode 100644 index 0000000..7f73718 Binary files /dev/null and b/plugins/ToolStatus/resources/inactive_search.png differ diff --git a/plugins/ToolStatus/resources/lightbulb_off.ico b/plugins/ToolStatus/resources/lightbulb_off.ico new file mode 100644 index 0000000..30b08ea Binary files /dev/null and b/plugins/ToolStatus/resources/lightbulb_off.ico differ diff --git a/plugins/ToolStatus/resources/lightbulb_off_modern.png b/plugins/ToolStatus/resources/lightbulb_off_modern.png new file mode 100644 index 0000000..ecbf453 Binary files /dev/null and b/plugins/ToolStatus/resources/lightbulb_off_modern.png differ diff --git a/plugins/ToolStatus/searchbox.c b/plugins/ToolStatus/searchbox.c new file mode 100644 index 0000000..6070d7c --- /dev/null +++ b/plugins/ToolStatus/searchbox.c @@ -0,0 +1,739 @@ +/* + * Process Hacker - + * Subclassed Edit control + * + * Copyright (C) 2012-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + * + */ + +#include "toolstatus.h" +#include "commonutil.h" +#include +#include + +VOID NcAreaFreeTheme( + _Inout_ PEDIT_CONTEXT Context + ) +{ + if (Context->BrushNormal) + DeleteObject(Context->BrushNormal); + + if (Context->BrushHot) + DeleteObject(Context->BrushHot); + + if (Context->BrushPushed) + DeleteObject(Context->BrushPushed); +} + +VOID NcAreaInitializeFont( + _Inout_ PEDIT_CONTEXT Context + ) +{ + // Cleanup existing font handle. + if (Context->WindowFont) + DeleteObject(Context->WindowFont); + + Context->WindowFont = CommonDuplicateFont((HFONT)SendMessage(ToolBarHandle, WM_GETFONT, 0, 0)); + + SendMessage(Context->WindowHandle, WM_SETFONT, (WPARAM)Context->WindowFont, TRUE); +} + +VOID NcAreaInitializeTheme( + _Inout_ PEDIT_CONTEXT Context + ) +{ + Context->CXWidth = PhMultiplyDivide(19, PhGlobalDpi, 96); + Context->BrushNormal = GetSysColorBrush(COLOR_WINDOW); + Context->BrushHot = CreateSolidBrush(RGB(205, 232, 255)); + Context->BrushPushed = CreateSolidBrush(RGB(153, 209, 255)); + + if (IsThemeActive()) + { + HTHEME themeDataHandle; + + if (themeDataHandle = OpenThemeData(Context->WindowHandle, VSCLASS_EDIT)) + { + //IsThemePartDefined_I(themeDataHandle, EP_EDITBORDER_NOSCROLL, EPSHV_NORMAL); + + if (!SUCCEEDED(GetThemeInt( + themeDataHandle, + EP_EDITBORDER_NOSCROLL, + EPSHV_NORMAL, + TMT_BORDERSIZE, + &Context->CXBorder + ))) + { + Context->CXBorder = GetSystemMetrics(SM_CXBORDER) * 2; + } + + CloseThemeData(themeDataHandle); + } + else + { + Context->CXBorder = GetSystemMetrics(SM_CXBORDER) * 2; + } + } + else + { + Context->CXBorder = GetSystemMetrics(SM_CXBORDER) * 2; + } +} + +VOID NcAreaInitializeImageList( + _Inout_ PEDIT_CONTEXT Context + ) +{ + HBITMAP bitmapActive = NULL; + HBITMAP bitmapInactive = NULL; + + Context->ImageWidth = GetSystemMetrics(SM_CXSMICON) + 4; + Context->ImageHeight = GetSystemMetrics(SM_CYSMICON) + 4; + Context->ImageList = ImageList_Create(32, 32, ILC_COLOR32, 2, 2); + ImageList_SetImageCount(Context->ImageList, 2); + + // Add the images to the imagelist + if (bitmapActive = LoadImageFromResources(Context->ImageWidth, Context->ImageHeight, MAKEINTRESOURCE(IDB_SEARCH_ACTIVE))) + { + ImageList_Replace(Context->ImageList, 0, bitmapActive, NULL); + DeleteObject(bitmapActive); + } + else + { + PhSetImageListBitmap(Context->ImageList, 0, PluginInstance->DllBase, MAKEINTRESOURCE(IDB_SEARCH_ACTIVE_BMP)); + } + + if (bitmapInactive = LoadImageFromResources(Context->ImageWidth, Context->ImageHeight, MAKEINTRESOURCE(IDB_SEARCH_INACTIVE))) + { + ImageList_Replace(Context->ImageList, 1, bitmapInactive, NULL); + DeleteObject(bitmapInactive); + } + else + { + PhSetImageListBitmap(Context->ImageList, 1, PluginInstance->DllBase, MAKEINTRESOURCE(IDB_SEARCH_INACTIVE_BMP)); + } +} + +VOID NcAreaGetButtonRect( + _Inout_ PEDIT_CONTEXT Context, + _Inout_ PRECT ButtonRect + ) +{ + ButtonRect->left = (ButtonRect->right - Context->CXWidth) - Context->CXBorder - 1; // offset left border by 1 + ButtonRect->bottom -= Context->CXBorder; + ButtonRect->right -= Context->CXBorder; + ButtonRect->top += Context->CXBorder; +} + +VOID NcAreaDrawButton( + _Inout_ PEDIT_CONTEXT Context, + _In_ RECT ButtonRect + ) +{ + HDC hdc; + HDC bufferDc; + HBITMAP bufferBitmap; + HBITMAP oldBufferBitmap; + RECT bufferRect = + { + 0, 0, + ButtonRect.right - ButtonRect.left, + ButtonRect.bottom - ButtonRect.top + }; + + if (!(hdc = GetWindowDC(Context->WindowHandle))) + return; + + bufferDc = CreateCompatibleDC(hdc); + bufferBitmap = CreateCompatibleBitmap(hdc, bufferRect.right, bufferRect.bottom); + oldBufferBitmap = SelectObject(bufferDc, bufferBitmap); + + if (Context->Pushed) + { + FillRect(bufferDc, &bufferRect, Context->BrushPushed); + //FrameRect(bufferDc, &bufferRect, CreateSolidBrush(RGB(0xff, 0, 0))); + } + else if (Context->Hot) + { + FillRect(bufferDc, &bufferRect, Context->BrushHot); + //FrameRect(bufferDc, &bufferRect, CreateSolidBrush(RGB(38, 160, 218))); + } + else + { + FillRect(bufferDc, &bufferRect, Context->BrushNormal); + } + + // Draw the image centered within the rect. + if (SearchboxText->Length > 0) + { + ImageList_Draw( + Context->ImageList, + 0, + bufferDc, + bufferRect.left + ((bufferRect.right - bufferRect.left) - Context->ImageWidth) / 2, + bufferRect.top + ((bufferRect.bottom - bufferRect.top) - Context->ImageHeight) / 2, + ILD_NORMAL | ILD_TRANSPARENT + ); + } + else + { + ImageList_Draw( + Context->ImageList, + 1, + bufferDc, + bufferRect.left + ((bufferRect.right - bufferRect.left) - (Context->ImageWidth - 2)) / 2, // (ImageWidth - 2) offset left by two + bufferRect.top + ((bufferRect.bottom - bufferRect.top) - (Context->ImageHeight - 2)) / 2, // (ImageHeight - 2) offset top by one + ILD_NORMAL | ILD_TRANSPARENT + ); + } + + BitBlt(hdc, ButtonRect.left, ButtonRect.top, ButtonRect.right, ButtonRect.bottom, bufferDc, 0, 0, SRCCOPY); + SelectObject(bufferDc, oldBufferBitmap); + DeleteObject(bufferBitmap); + DeleteDC(bufferDc); + + ReleaseDC(Context->WindowHandle, hdc); +} + +LRESULT CALLBACK NcAreaWndSubclassProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ ULONG_PTR dwRefData + ) +{ + PEDIT_CONTEXT context; + + context = (PEDIT_CONTEXT)GetProp(hWnd, L"EditSubclassContext"); + + switch (uMsg) + { + case WM_NCDESTROY: + { + NcAreaFreeTheme(context); + + if (context->ImageList) + ImageList_Destroy(context->ImageList); + + if (context->WindowFont) + DeleteObject(context->WindowFont); + + RemoveWindowSubclass(hWnd, NcAreaWndSubclassProc, uIdSubclass); + RemoveProp(hWnd, L"EditSubclassContext"); + PhFree(context); + } + break; + case WM_ERASEBKGND: + return 1; + case WM_NCCALCSIZE: + { + LPNCCALCSIZE_PARAMS ncCalcSize = (NCCALCSIZE_PARAMS*)lParam; + + // Let Windows handle the non-client defaults. + DefSubclassProc(hWnd, uMsg, wParam, lParam); + + // Deflate the client area to accommodate the custom button. + ncCalcSize->rgrc[0].right -= context->CXWidth; + } + return 0; + case WM_NCPAINT: + { + RECT windowRect; + + // Let Windows handle the non-client defaults. + DefSubclassProc(hWnd, uMsg, wParam, lParam); + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Adjust the coordinates (start from 0,0). + OffsetRect(&windowRect, -windowRect.left, -windowRect.top); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Draw the button. + NcAreaDrawButton(context, windowRect); + } + return 0; + case WM_NCHITTEST: + { + POINT windowPoint; + RECT windowRect; + + // Get the screen coordinates of the mouse. + if (!GetCursorPos(&windowPoint)) + break; + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Check that the mouse is within the inserted button. + if (PtInRect(&windowRect, windowPoint)) + { + return HTBORDER; + } + } + break; + case WM_NCLBUTTONDOWN: + { + POINT windowPoint; + RECT windowRect; + + // Get the screen coordinates of the mouse. + if (!GetCursorPos(&windowPoint)) + break; + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Check that the mouse is within the inserted button. + if (PtInRect(&windowRect, windowPoint)) + { + context->Pushed = TRUE; + + SetCapture(hWnd); + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + } + } + break; + case WM_LBUTTONUP: + { + POINT windowPoint; + RECT windowRect; + + // Get the screen coordinates of the mouse. + if (!GetCursorPos(&windowPoint)) + break; + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Check that the mouse is within the inserted button. + if (PtInRect(&windowRect, windowPoint)) + { + // Forward click notification. + SendMessage(PhMainWndHandle, WM_COMMAND, MAKEWPARAM(context->CommandID, BN_CLICKED), 0); + } + + if (GetCapture() == hWnd) + { + context->Pushed = FALSE; + ReleaseCapture(); + } + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + } + break; + case WM_KEYDOWN: + { + if (wParam == '\t' || wParam == '\r') + { + HWND tnHandle; + + tnHandle = GetCurrentTreeNewHandle(); + + if (tnHandle) + { + SetFocus(tnHandle); + + if (wParam == '\r') + { + if (TreeNew_GetFlatNodeCount(tnHandle) > 0) + { + TreeNew_DeselectRange(tnHandle, 0, -1); + TreeNew_SelectRange(tnHandle, 0, 0); + TreeNew_SetFocusNode(tnHandle, TreeNew_GetFlatNode(tnHandle, 0)); + TreeNew_SetMarkNode(tnHandle, TreeNew_GetFlatNode(tnHandle, 0)); + } + } + } + else + { + PTOOLSTATUS_TAB_INFO tabInfo; + + if ((tabInfo = FindTabInfo(SelectedTabIndex)) && tabInfo->ActivateContent) + tabInfo->ActivateContent(wParam == '\r'); + } + + return FALSE; + } + + // Handle CTRL+A below Vista. + if (WindowsVersion < WINDOWS_VISTA && (GetKeyState(VK_CONTROL) & VK_LCONTROL) && wParam == 'A') + { + Edit_SetSel(hWnd, 0, -1); + return FALSE; + } + } + break; + case WM_CHAR: + if (wParam == '\t' || wParam == '\r') + return FALSE; + break; + case WM_CUT: + case WM_CLEAR: + case WM_PASTE: + case WM_UNDO: + case WM_KEYUP: + case WM_SETTEXT: + case WM_KILLFOCUS: + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + break; + case WM_SETTINGCHANGE: + case WM_SYSCOLORCHANGE: + case WM_THEMECHANGED: + { + NcAreaFreeTheme(context); + NcAreaInitializeTheme(context); + NcAreaInitializeFont(context); + + // Reset the client area margins. + SendMessage(hWnd, EM_SETMARGINS, EC_LEFTMARGIN, MAKELPARAM(0, 0)); + + // Force the edit control to update its non-client area. + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + //SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); + } + break; + case WM_SETFOCUS: + { + if (SearchBoxDisplayMode != SEARCHBOX_DISPLAY_MODE_HIDEINACTIVE) + break; + + if (!RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + { + UINT height = (UINT)SendMessage(RebarHandle, RB_GETROWHEIGHT, 0, 0); + RebarBandInsert(REBAR_BAND_ID_SEARCHBOX, SearchboxHandle, PhMultiplyDivide(180, PhGlobalDpi, 96), height - 2); + } + } + break; + case WM_NCMOUSEMOVE: + { + POINT windowPoint; + RECT windowRect; + + // Get the screen coordinates of the mouse. + if (!GetCursorPos(&windowPoint)) + break; + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Check that the mouse is within the inserted button. + if (PtInRect(&windowRect, windowPoint) && !context->Hot) + { + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE | TME_NONCLIENT; + trackMouseEvent.hwndTrack = hWnd; + trackMouseEvent.dwHoverTime = 0; + + context->Hot = TRUE; + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + + TrackMouseEvent(&trackMouseEvent); + } + } + break; + case WM_NCMOUSELEAVE: + { + if (context->Hot) + { + context->Hot = FALSE; + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + } + } + break; + case WM_MOUSEMOVE: + { + if ((wParam & MK_LBUTTON) && GetCapture() == hWnd) + { + POINT windowPoint; + RECT windowRect; + + // Get the screen coordinates of the mouse. + if (!GetCursorPos(&windowPoint)) + break; + + // Get the screen coordinates of the window. + GetWindowRect(hWnd, &windowRect); + + // Get the position of the inserted button. + NcAreaGetButtonRect(context, &windowRect); + + // Check that the mouse is within the inserted button. + context->Pushed = PtInRect(&windowRect, windowPoint); + + RedrawWindow(hWnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE); + } + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +HBITMAP LoadImageFromResources( + _In_ UINT Width, + _In_ UINT Height, + _In_ PCWSTR Name + ) +{ + UINT width = 0; + UINT height = 0; + UINT frameCount = 0; + BOOLEAN isSuccess = FALSE; + ULONG resourceLength = 0; + HGLOBAL resourceHandle = NULL; + HRSRC resourceHandleSource = NULL; + WICInProcPointer resourceBuffer = NULL; + + HDC screenHdc = NULL; + HDC bufferDc = NULL; + BITMAPINFO bitmapInfo = { 0 }; + HBITMAP bitmapHandle = NULL; + PBYTE bitmapBuffer = NULL; + + IWICStream* wicStream = NULL; + IWICBitmapSource* wicBitmapSource = NULL; + IWICBitmapDecoder* wicDecoder = NULL; + IWICBitmapFrameDecode* wicFrame = NULL; + IWICImagingFactory* wicFactory = NULL; + IWICBitmapScaler* wicScaler = NULL; + WICPixelFormatGUID pixelFormat; + + WICRect rect = { 0, 0, Width, Height }; + + __try + { + // Create the ImagingFactory + if (FAILED(CoCreateInstance(&CLSID_WICImagingFactory1, NULL, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, &wicFactory))) + __leave; + + // Find the resource + if ((resourceHandleSource = FindResource(PluginInstance->DllBase, Name, L"PNG")) == NULL) + __leave; + + // Get the resource length + resourceLength = SizeofResource(PluginInstance->DllBase, resourceHandleSource); + + // Load the resource + if ((resourceHandle = LoadResource(PluginInstance->DllBase, resourceHandleSource)) == NULL) + __leave; + + if ((resourceBuffer = (WICInProcPointer)LockResource(resourceHandle)) == NULL) + __leave; + + // Create the Stream + if (FAILED(IWICImagingFactory_CreateStream(wicFactory, &wicStream))) + __leave; + + // Initialize the Stream from Memory + if (FAILED(IWICStream_InitializeFromMemory(wicStream, resourceBuffer, resourceLength))) + __leave; + + if (FAILED(IWICImagingFactory_CreateDecoder(wicFactory, &GUID_ContainerFormatPng, NULL, &wicDecoder))) + __leave; + + if (FAILED(IWICBitmapDecoder_Initialize(wicDecoder, (IStream*)wicStream, WICDecodeMetadataCacheOnLoad))) + __leave; + + // Get the Frame count + if (FAILED(IWICBitmapDecoder_GetFrameCount(wicDecoder, &frameCount)) || frameCount < 1) + __leave; + + // Get the Frame + if (FAILED(IWICBitmapDecoder_GetFrame(wicDecoder, 0, &wicFrame))) + __leave; + + // Get the WicFrame image format + if (FAILED(IWICBitmapFrameDecode_GetPixelFormat(wicFrame, &pixelFormat))) + __leave; + + // Check if the image format is supported: + if (IsEqualGUID(&pixelFormat, &GUID_WICPixelFormat32bppRGBA)) // GUID_WICPixelFormat32bppPBGRA + { + wicBitmapSource = (IWICBitmapSource*)wicFrame; + } + else + { + IWICFormatConverter* wicFormatConverter = NULL; + + if (FAILED(IWICImagingFactory_CreateFormatConverter(wicFactory, &wicFormatConverter))) + __leave; + + if (FAILED(IWICFormatConverter_Initialize( + wicFormatConverter, + (IWICBitmapSource*)wicFrame, + &GUID_WICPixelFormat32bppBGRA, + WICBitmapDitherTypeNone, + NULL, + 0.0, + WICBitmapPaletteTypeCustom + ))) + { + IWICFormatConverter_Release(wicFormatConverter); + __leave; + } + + // Convert the image to the correct format: + IWICFormatConverter_QueryInterface(wicFormatConverter, &IID_IWICBitmapSource, &wicBitmapSource); + + // Cleanup the converter. + IWICFormatConverter_Release(wicFormatConverter); + + // Dispose the old frame now that the converted frame is in wicBitmapSource. + IWICBitmapFrameDecode_Release(wicFrame); + } + + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = rect.Width; + bitmapInfo.bmiHeader.biHeight = -((LONG)rect.Height); + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + + screenHdc = GetDC(NULL); + bufferDc = CreateCompatibleDC(screenHdc); + bitmapHandle = CreateDIBSection(screenHdc, &bitmapInfo, DIB_RGB_COLORS, (PVOID*)&bitmapBuffer, NULL, 0); + + // Check if it's the same rect as the requested size. + //if (width != rect.Width || height != rect.Height) + if (FAILED(IWICImagingFactory_CreateBitmapScaler(wicFactory, &wicScaler))) + __leave; + if (FAILED(IWICBitmapScaler_Initialize(wicScaler, wicBitmapSource, rect.Width, rect.Height, WICBitmapInterpolationModeFant))) + __leave; + if (FAILED(IWICBitmapScaler_CopyPixels(wicScaler, &rect, rect.Width * 4, rect.Width * rect.Height * 4, bitmapBuffer))) + __leave; + + isSuccess = TRUE; + } + __finally + { + // Cleanup resources in the same order they were created. + + if (wicScaler) + { + IWICBitmapScaler_Release(wicScaler); + } + + if (bufferDc) + { + DeleteDC(bufferDc); + } + + if (screenHdc) + { + ReleaseDC(NULL, screenHdc); + } + + if (wicBitmapSource) + { + IWICBitmapSource_Release(wicBitmapSource); + } + + if (wicStream) + { + IWICStream_Release(wicStream); + } + + if (wicDecoder) + { + IWICBitmapDecoder_Release(wicDecoder); + } + + if (wicFactory) + { + IWICImagingFactory_Release(wicFactory); + } + + if (resourceHandle) + { + FreeResource(resourceHandle); + } + } + + return bitmapHandle; +} + +HWND CreateSearchControl( + _In_ UINT CommandID + ) +{ + PEDIT_CONTEXT context; + + context = (PEDIT_CONTEXT)PhAllocate(sizeof(EDIT_CONTEXT)); + memset(context, 0, sizeof(EDIT_CONTEXT)); + + context->CommandID = CommandID; + + // Create the SearchBox window. + context->WindowHandle = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_EDIT, + NULL, + WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | ES_LEFT | ES_AUTOHSCROLL | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + RebarHandle, + NULL, + NULL, + NULL + ); + + // TODO: Why does the Edit control require WS_VISIBLE to be correctly initialized under some conditions? + // For now just call ShowWindow with SW_HIDE instead of removing the WS_VISIBLE style passed to CreateWindowEx. + if (SearchBoxDisplayMode == SEARCHBOX_DISPLAY_MODE_HIDEINACTIVE) + { + ShowWindow(SearchboxHandle, SW_HIDE); + } + + //NcAreaInitializeTheme(context); + NcAreaInitializeImageList(context); + + // Set initial text + Edit_SetCueBannerText(context->WindowHandle, L"Search Processes (Ctrl+K)"); + + // Set our window context data. + SetProp(context->WindowHandle, L"EditSubclassContext", (HANDLE)context); + + // Subclass the Edit control window procedure. + SetWindowSubclass(context->WindowHandle, NcAreaWndSubclassProc, 0, (ULONG_PTR)context); + + // Initialize the theme parameters. + SendMessage(context->WindowHandle, WM_THEMECHANGED, 0, 0); + + return context->WindowHandle; +} \ No newline at end of file diff --git a/plugins/ToolStatus/statusbar.c b/plugins/ToolStatus/statusbar.c new file mode 100644 index 0000000..b1bb007 --- /dev/null +++ b/plugins/ToolStatus/statusbar.c @@ -0,0 +1,584 @@ +/* + * Process Hacker ToolStatus - + * statusbar main + * + * Copyright (C) 2011-2016 dmex + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" + +HWND StatusBarHandle = NULL; +ULONG ProcessesUpdatedCount = 0; +ULONG StatusBarMaxWidths[MAX_STATUSBAR_ITEMS]; +// Note: no lock is needed because we only ever modify the list on this same thread. +PPH_LIST StatusBarItemList = NULL; +ULONG StatusBarItems[MAX_STATUSBAR_ITEMS] = +{ + // Default items (displayed) + { ID_STATUS_CPUUSAGE }, + { ID_STATUS_PHYSICALMEMORY }, + { ID_STATUS_NUMBEROFPROCESSES }, + // Available items (hidden) + { ID_STATUS_COMMITCHARGE }, + { ID_STATUS_FREEMEMORY }, + { ID_STATUS_NUMBEROFTHREADS }, + { ID_STATUS_NUMBEROFHANDLES }, + { ID_STATUS_NUMBEROFVISIBLEITEMS, }, + { ID_STATUS_NUMBEROFSELECTEDITEMS, }, + { ID_STATUS_INTERVALSTATUS }, + { ID_STATUS_IO_RO }, + { ID_STATUS_IO_W }, + { ID_STATUS_MAX_CPU_PROCESS }, + { ID_STATUS_MAX_IO_PROCESS }, +}; + +VOID StatusBarLoadDefault( + VOID + ) +{ + ULONG i; + + if (!StatusBarItemList) + StatusBarItemList = PhCreateList(MAX_DEFAULT_STATUSBAR_ITEMS); + + for (i = 0; i < MAX_DEFAULT_STATUSBAR_ITEMS; i++) + { + PSTATUSBAR_ITEM statusItem; + + statusItem = PhAllocate(sizeof(STATUSBAR_ITEM)); + memset(statusItem, 0, sizeof(STATUSBAR_ITEM)); + + statusItem->Id = StatusBarItems[i]; + + PhAddItemList(StatusBarItemList, statusItem); + } +} + +VOID StatusBarLoadSettings( + VOID + ) +{ + ULONG64 buttonCount = 0; + PPH_STRING settingsString; + PH_STRINGREF remaining; + PH_STRINGREF part; + + settingsString = PhaGetStringSetting(SETTING_NAME_STATUSBAR_CONFIG); + remaining = settingsString->sr; + + if (remaining.Length == 0) + { + // Load default settings + StatusBarLoadDefault(); + return; + } + + // Query the number of buttons to insert + if (!PhSplitStringRefAtChar(&remaining, '|', &part, &remaining)) + { + // Load default settings + StatusBarLoadDefault(); + return; + } + + if (!PhStringToInteger64(&part, 10, &buttonCount)) + { + // Load default settings + StatusBarLoadDefault(); + return; + } + + StatusBarItemList = PhCreateList((ULONG)buttonCount); + + for (ULONG i = 0; i < (ULONG)buttonCount; i++) + { + PH_STRINGREF idPart; + ULONG64 idInteger; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, '|', &idPart, &remaining); + + if (PhStringToInteger64(&idPart, 10, &idInteger)) + { + PSTATUSBAR_ITEM statusItem; + + statusItem = PhAllocate(sizeof(STATUSBAR_ITEM)); + memset(statusItem, 0, sizeof(STATUSBAR_ITEM)); + + statusItem->Id = (ULONG)idInteger; + + PhInsertItemList(StatusBarItemList, i, statusItem); + } + } +} + +VOID StatusBarSaveSettings( + VOID + ) +{ + ULONG buttonIndex = 0; + PPH_STRING settingsString; + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 100); + + PhAppendFormatStringBuilder( + &stringBuilder, + L"%lu|", + StatusBarItemList->Count + ); + + for (buttonIndex = 0; buttonIndex < StatusBarItemList->Count; buttonIndex++) + { + PSTATUSBAR_ITEM statusItem = StatusBarItemList->Items[buttonIndex]; + + PhAppendFormatStringBuilder( + &stringBuilder, + L"%lu|", + statusItem->Id + ); + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); + PhSetStringSetting2(SETTING_NAME_STATUSBAR_CONFIG, &settingsString->sr); +} + +VOID StatusBarResetSettings( + VOID + ) +{ + for (ULONG i = 0; i < StatusBarItemList->Count; i++) + { + PhFree(StatusBarItemList->Items[i]); + } + + PhClearList(StatusBarItemList); + + StatusBarLoadDefault(); +} + +PWSTR StatusBarGetText( + _In_ ULONG CommandID + ) +{ + switch (CommandID) + { + case ID_STATUS_CPUUSAGE: + return L"CPU usage"; + case ID_STATUS_PHYSICALMEMORY: + return L"Physical memory"; + case ID_STATUS_NUMBEROFPROCESSES: + return L"Number of processes"; + case ID_STATUS_COMMITCHARGE: + return L"Commit charge"; + case ID_STATUS_FREEMEMORY: + return L"Free physical memory"; + case ID_STATUS_NUMBEROFTHREADS: + return L"Number of threads"; + case ID_STATUS_NUMBEROFHANDLES: + return L"Number of handles"; + case ID_STATUS_NUMBEROFVISIBLEITEMS: + return L"Number of visible items"; + case ID_STATUS_NUMBEROFSELECTEDITEMS: + return L"Number of selected items"; + case ID_STATUS_INTERVALSTATUS: + return L"Interval status"; + case ID_STATUS_IO_RO: + return L"I/O read+other"; + case ID_STATUS_IO_W: + return L"I/O write"; + case ID_STATUS_MAX_CPU_PROCESS: + return L"Max. CPU process"; + case ID_STATUS_MAX_IO_PROCESS: + return L"Max. I/O process"; + } + + return L"ERROR"; +} + +VOID StatusBarShowMenu( + VOID + ) +{ + PPH_EMENU menu; + PPH_EMENU_ITEM selectedItem; + POINT cursorPos; + + GetCursorPos(&cursorPos); + + menu = PhCreateEMenu(); + PhInsertEMenuItem(menu, PhCreateEMenuItem(0, COMMAND_ID_ENABLE_SEARCHBOX, L"Customize...", NULL, NULL), -1); + + selectedItem = PhShowEMenu( + menu, + PhMainWndHandle, + PH_EMENU_SHOW_LEFTRIGHT, + PH_ALIGN_LEFT | PH_ALIGN_BOTTOM, + cursorPos.x, + cursorPos.y + ); + + if (selectedItem && selectedItem->Id != -1) + { + StatusBarShowCustomizeDialog(); + + StatusBarUpdate(TRUE); + } + + PhDestroyEMenu(menu); +} + +VOID StatusBarUpdate( + _In_ BOOLEAN ResetMaxWidths + ) +{ + static ULONG64 lastTickCount = 0; + + ULONG count; + ULONG i; + HDC hdc; + BOOLEAN resetMaxWidths = FALSE; + PPH_STRING text[MAX_STATUSBAR_ITEMS]; + ULONG widths[MAX_STATUSBAR_ITEMS]; + + if (ProcessesUpdatedCount < 2) + return; + + if (ResetMaxWidths) + resetMaxWidths = TRUE; + + if (!StatusBarItemList || StatusBarItemList->Count == 0) + { + // The status bar doesn't cope well with 0 parts. + widths[0] = -1; + SendMessage(StatusBarHandle, SB_SETPARTS, 1, (LPARAM)widths); + SendMessage(StatusBarHandle, SB_SETTEXT, 0, (LPARAM)L""); + return; + } + + hdc = GetDC(StatusBarHandle); + SelectObject(hdc, (HFONT)SendMessage(StatusBarHandle, WM_GETFONT, 0, 0)); + + // Reset max. widths for Max. CPU Process and Max. I/O Process parts once in a while. + { + LARGE_INTEGER tickCount; + + PhQuerySystemTime(&tickCount); + + if (tickCount.QuadPart - lastTickCount >= 10 * PH_TICKS_PER_SEC) + { + resetMaxWidths = TRUE; + lastTickCount = tickCount.QuadPart; + } + } + + count = 0; + + for (i = 0; i < StatusBarItemList->Count; i++) + { + SIZE size; + ULONG width; + PSTATUSBAR_ITEM statusItem; + + statusItem = StatusBarItemList->Items[i]; + + switch (statusItem->Id) + { + case ID_STATUS_CPUUSAGE: + { + text[count] = PhFormatString( + L"CPU Usage: %.2f%%", + (SystemStatistics.CpuKernelUsage + SystemStatistics.CpuUserUsage) * 100 + ); + } + break; + case ID_STATUS_COMMITCHARGE: + { + ULONG commitUsage = SystemStatistics.Performance->CommittedPages; + FLOAT commitFraction = (FLOAT)commitUsage / SystemStatistics.Performance->CommitLimit * 100; + + text[count] = PhFormatString( + L"Commit charge: %s (%.2f%%)", + PhaFormatSize(UInt32x32To64(commitUsage, PAGE_SIZE), -1)->Buffer, + commitFraction + ); + } + break; + case ID_STATUS_PHYSICALMEMORY: + { + ULONG physicalUsage = PhSystemBasicInformation.NumberOfPhysicalPages - SystemStatistics.Performance->AvailablePages; + FLOAT physicalFraction = (FLOAT)physicalUsage / PhSystemBasicInformation.NumberOfPhysicalPages * 100; + + text[count] = PhFormatString( + L"Physical memory: %s (%.2f%%)", + PhaFormatSize(UInt32x32To64(physicalUsage, PAGE_SIZE), -1)->Buffer, + physicalFraction + ); + } + break; + case ID_STATUS_FREEMEMORY: + { + ULONG physicalFree = SystemStatistics.Performance->AvailablePages; + FLOAT physicalFreeFraction = (FLOAT)physicalFree / PhSystemBasicInformation.NumberOfPhysicalPages * 100; + + text[count] = PhFormatString( + L"Free memory: %s (%.2f%%)", + PhaFormatSize(UInt32x32To64(physicalFree, PAGE_SIZE), -1)->Buffer, + physicalFreeFraction + ); + } + break; + case ID_STATUS_NUMBEROFPROCESSES: + { + text[count] = PhConcatStrings2( + L"Processes: ", + PhaFormatUInt64(SystemStatistics.NumberOfProcesses, TRUE)->Buffer + ); + } + break; + case ID_STATUS_NUMBEROFTHREADS: + { + text[count] = PhConcatStrings2( + L"Threads: ", + PhaFormatUInt64(SystemStatistics.NumberOfThreads, TRUE)->Buffer + ); + } + break; + case ID_STATUS_NUMBEROFHANDLES: + { + text[count] = PhConcatStrings2( + L"Handles: ", + PhaFormatUInt64(SystemStatistics.NumberOfHandles, TRUE)->Buffer + ); + } + break; + case ID_STATUS_IO_RO: + { + text[count] = PhConcatStrings2( + L"I/O R+O: ", + PhaFormatSize(SystemStatistics.IoReadDelta.Delta + SystemStatistics.IoOtherDelta.Delta, -1)->Buffer + ); + } + break; + case ID_STATUS_IO_W: + { + text[count] = PhConcatStrings2( + L"I/O W: ", + PhaFormatSize(SystemStatistics.IoWriteDelta.Delta, -1)->Buffer + ); + } + break; + case ID_STATUS_MAX_CPU_PROCESS: + { + PPH_PROCESS_ITEM processItem; + + if (SystemStatistics.MaxCpuProcessId && (processItem = PhReferenceProcessItem(SystemStatistics.MaxCpuProcessId))) + { + if (!PH_IS_FAKE_PROCESS_ID(processItem->ProcessId)) + { + text[count] = PhFormatString( + L"%s (%lu): %.2f%%", + processItem->ProcessName->Buffer, + HandleToUlong(processItem->ProcessId), + processItem->CpuUsage * 100 + ); + } + else + { + text[count] = PhFormatString( + L"%s: %.2f%%", + processItem->ProcessName->Buffer, + processItem->CpuUsage * 100 + ); + } + + PhDereferenceObject(processItem); + } + else + { + text[count] = PhCreateString(L"-"); + } + } + break; + case ID_STATUS_MAX_IO_PROCESS: + { + PPH_PROCESS_ITEM processItem; + + if (SystemStatistics.MaxIoProcessId && (processItem = PhReferenceProcessItem(SystemStatistics.MaxIoProcessId))) + { + if (!PH_IS_FAKE_PROCESS_ID(processItem->ProcessId)) + { + text[count] = PhFormatString( + L"%s (%lu): %s", + processItem->ProcessName->Buffer, + HandleToUlong(processItem->ProcessId), + PhaFormatSize(processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta + processItem->IoOtherDelta.Delta, -1)->Buffer + ); + } + else + { + text[count] = PhFormatString( + L"%s: %s", + processItem->ProcessName->Buffer, + PhaFormatSize(processItem->IoReadDelta.Delta + processItem->IoWriteDelta.Delta + processItem->IoOtherDelta.Delta, -1)->Buffer + ); + } + + PhDereferenceObject(processItem); + } + else + { + text[count] = PhCreateString(L"-"); + } + } + break; + case ID_STATUS_NUMBEROFVISIBLEITEMS: + { + HWND tnHandle = NULL; + + tnHandle = GetCurrentTreeNewHandle(); + + if (tnHandle) + { + ULONG visibleCount = 0; + + visibleCount = TreeNew_GetFlatNodeCount(tnHandle); + + text[count] = PhFormatString( + L"Visible: %lu", + visibleCount + ); + } + else + { + text[count] = PhCreateString( + L"Visible: N/A" + ); + } + } + break; + case ID_STATUS_NUMBEROFSELECTEDITEMS: + { + HWND tnHandle = NULL; + + tnHandle = GetCurrentTreeNewHandle(); + + if (tnHandle) + { + ULONG visibleCount = 0; + ULONG selectedCount = 0; + + visibleCount = TreeNew_GetFlatNodeCount(tnHandle); + + for (ULONG i = 0; i < visibleCount; i++) + { + if (TreeNew_GetFlatNode(tnHandle, i)->Selected) + selectedCount++; + } + + text[count] = PhFormatString( + L"Selected: %lu", + selectedCount + ); + } + else + { + text[count] = PhCreateString( + L"Selected: N/A" + ); + } + } + break; + case ID_STATUS_INTERVALSTATUS: + { + ULONG interval; + + interval = PhGetIntegerSetting(L"UpdateInterval"); + + if (UpdateAutomatically) + { + switch (interval) + { + case 500: + text[count] = PhCreateString(L"Interval: Fast"); + break; + case 1000: + text[count] = PhCreateString(L"Interval: Normal"); + break; + case 2000: + text[count] = PhCreateString(L"Interval: Below normal"); + break; + case 5000: + text[count] = PhCreateString(L"Interval: Slow"); + break; + case 10000: + text[count] = PhCreateString(L"Interval: Very slow"); + break; + } + } + else + { + text[count] = PhCreateString(L"Interval: Paused"); + } + } + break; + } + + if (resetMaxWidths) + StatusBarMaxWidths[count] = 0; + + if (!GetTextExtentPoint32(hdc, text[count]->Buffer, (ULONG)text[count]->Length / sizeof(WCHAR), &size)) + size.cx = 200; + + if (count != 0) + widths[count] = widths[count - 1]; + else + widths[count] = 0; + + width = size.cx + 10; + + if (width <= StatusBarMaxWidths[count]) + { + width = StatusBarMaxWidths[count]; + } + else + { + StatusBarMaxWidths[count] = width; + } + + widths[count] += width; + + count++; + } + + ReleaseDC(StatusBarHandle, hdc); + + SendMessage(StatusBarHandle, SB_SETPARTS, count, (LPARAM)widths); + + for (i = 0; i < count; i++) + { + SendMessage(StatusBarHandle, SB_SETTEXT, i, (LPARAM)text[i]->Buffer); + PhDereferenceObject(text[i]); + } +} \ No newline at end of file diff --git a/plugins/ToolStatus/toolbar.c b/plugins/ToolStatus/toolbar.c new file mode 100644 index 0000000..eb5585c --- /dev/null +++ b/plugins/ToolStatus/toolbar.c @@ -0,0 +1,899 @@ +/* + * Process Hacker ToolStatus - + * main toolbar + * + * Copyright (C) 2011-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "toolstatus.h" + +HIMAGELIST ToolBarImageList = NULL; + +TBBUTTON ToolbarButtons[MAX_TOOLBAR_ITEMS] = +{ + // Default toolbar buttons (displayed) + { I_IMAGECALLBACK, PHAPP_ID_VIEW_REFRESH, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, PHAPP_ID_HACKER_OPTIONS, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { 0, 0, 0, BTNS_SEP, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, PHAPP_ID_HACKER_FINDHANDLESORDLLS, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, PHAPP_ID_VIEW_SYSTEMINFORMATION, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { 0, 0, 0, BTNS_SEP, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, TIDC_FINDWINDOW, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, TIDC_FINDWINDOWTHREAD, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, TIDC_FINDWINDOWKILL, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + // Available toolbar buttons (hidden) + { I_IMAGECALLBACK, PHAPP_ID_VIEW_ALWAYSONTOP, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, { 0 }, 0, 0 }, + { I_IMAGECALLBACK, TIDC_POWERMENUDROPDOWN, TBSTATE_ENABLED, BTNS_WHOLEDROPDOWN | BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT,{ 0 }, 0, 0 }, + { I_IMAGECALLBACK, PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT,{ 0 }, 0, 0 }, +}; + +VOID RebarBandInsert( + _In_ UINT BandID, + _In_ HWND HwndChild, + _In_ UINT cxMinChild, + _In_ UINT cyMinChild + ) +{ + REBARBANDINFO rebarBandInfo = + { + REBARBANDINFO_V6_SIZE, + RBBIM_STYLE | RBBIM_ID | RBBIM_CHILD | RBBIM_CHILDSIZE, + RBBS_USECHEVRON // RBBS_NOGRIPPER | RBBS_HIDETITLE | RBBS_TOPALIGN + }; + + rebarBandInfo.wID = BandID; + rebarBandInfo.hwndChild = HwndChild; + rebarBandInfo.cxMinChild = cxMinChild; + rebarBandInfo.cyMinChild = cyMinChild; + + if (BandID == REBAR_BAND_ID_SEARCHBOX) + { + rebarBandInfo.fStyle |= RBBS_FIXEDSIZE; + } + + if (ToolStatusConfig.ToolBarLocked) + { + rebarBandInfo.fStyle |= RBBS_NOGRIPPER; + } + + SendMessage(RebarHandle, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rebarBandInfo); +} + +VOID RebarBandRemove( + _In_ UINT BandID + ) +{ + UINT index = (UINT)SendMessage(RebarHandle, RB_IDTOINDEX, (WPARAM)BandID, 0); + + if (index == -1) + return; + + SendMessage(RebarHandle, RB_DELETEBAND, (WPARAM)index, 0); +} + +BOOLEAN RebarBandExists( + _In_ UINT BandID + ) +{ + UINT index = (UINT)SendMessage(RebarHandle, RB_IDTOINDEX, (WPARAM)BandID, 0); + + if (index != -1) + return TRUE; + + return FALSE; +} + +VOID RebarLoadSettings( + VOID + ) +{ + // Initialize the Toolbar Imagelist. + if (ToolStatusConfig.ToolBarEnabled && !ToolBarImageList) + { + ToolBarImageList = ImageList_Create( + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, + 0, + 0 + ); + } + + // Initialize the Rebar and Toolbar controls. + if (ToolStatusConfig.ToolBarEnabled && !RebarHandle) + { + REBARINFO rebarInfo = { sizeof(REBARINFO) }; + ULONG toolbarButtonSize; + + // Create the ReBar window. + RebarHandle = CreateWindowEx( + WS_EX_TOOLWINDOW, + REBARCLASSNAME, + NULL, + WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CCS_NODIVIDER | CCS_TOP | RBS_VARHEIGHT | RBS_AUTOSIZE, // CCS_NOPARENTALIGN | RBS_FIXEDORDER + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + + // Set the toolbar info with no imagelist. + SendMessage(RebarHandle, RB_SETBARINFO, 0, (LPARAM)&rebarInfo); + + // Create the ToolBar window. + ToolBarHandle = CreateWindowEx( + 0, + TOOLBARCLASSNAME, + NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER | TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TRANSPARENT | TBSTYLE_TOOLTIPS | TBSTYLE_AUTOSIZE, // TBSTYLE_CUSTOMERASE TBSTYLE_ALTDRAG + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + RebarHandle, + NULL, + NULL, + NULL + ); + + // Set the toolbar struct size. + SendMessage(ToolBarHandle, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); + // Set the toolbar extended toolbar styles. + SendMessage(ToolBarHandle, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DOUBLEBUFFER | TBSTYLE_EX_MIXEDBUTTONS | TBSTYLE_EX_HIDECLIPPEDBUTTONS); + // Configure the toolbar imagelist. + SendMessage(ToolBarHandle, TB_SETIMAGELIST, 0, (LPARAM)ToolBarImageList); + // Add the buttons to the toolbar. + ToolbarLoadButtonSettings(); + // Resize the toolbar. + SendMessage(ToolBarHandle, TB_AUTOSIZE, 0, 0); + // Query the toolbar width and height. + //SendMessage(ToolBarHandle, TB_GETMAXSIZE, 0, (LPARAM)&toolbarSize); + toolbarButtonSize = (ULONG)SendMessage(ToolBarHandle, TB_GETBUTTONSIZE, 0, 0); + + // Enable theming + switch (ToolBarTheme) + { + case TOOLBAR_THEME_BLACK: + { + SendMessage(RebarHandle, RB_SETWINDOWTHEME, 0, (LPARAM)L"Media"); //Media/Communications/BrowserTabBar/Help + SendMessage(ToolBarHandle, TB_SETWINDOWTHEME, 0, (LPARAM)L"Media"); //Media/Communications/BrowserTabBar/Help + } + break; + case TOOLBAR_THEME_BLUE: + { + SendMessage(RebarHandle, RB_SETWINDOWTHEME, 0, (LPARAM)L"Communications"); + SendMessage(ToolBarHandle, TB_SETWINDOWTHEME, 0, (LPARAM)L"Communications"); + } + break; + } + + // Inset the toolbar into the rebar control. + RebarBandInsert(REBAR_BAND_ID_TOOLBAR, ToolBarHandle, LOWORD(toolbarButtonSize), HIWORD(toolbarButtonSize)); + } + + // Initialize the Searchbox and TreeNewFilters. + if (ToolStatusConfig.SearchBoxEnabled && !SearchboxHandle) + { + SearchboxText = PhReferenceEmptyString(); + + ProcessTreeFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportProcessTreeList(), (PPH_TN_FILTER_FUNCTION)ProcessTreeFilterCallback, NULL); + ServiceTreeFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportServiceTreeList(), (PPH_TN_FILTER_FUNCTION)ServiceTreeFilterCallback, NULL); + NetworkTreeFilterEntry = PhAddTreeNewFilter(PhGetFilterSupportNetworkTreeList(), (PPH_TN_FILTER_FUNCTION)NetworkTreeFilterCallback, NULL); + + // Create the Searchbox control. + SearchboxHandle = CreateSearchControl(ID_SEARCH_CLEAR); + } + + // Initialize the Statusbar control. + if (ToolStatusConfig.StatusBarEnabled && !StatusBarHandle) + { + // Create the StatusBar window. + StatusBarHandle = CreateWindowEx( + 0, + STATUSCLASSNAME, + NULL, + WS_CHILD | WS_VISIBLE | CCS_BOTTOM | SBARS_SIZEGRIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + PhMainWndHandle, + NULL, + NULL, + NULL + ); + } + + // Hide or show controls (Note: don't unload or remove at runtime). + if (ToolStatusConfig.ToolBarEnabled) + { + if (RebarHandle && !IsWindowVisible(RebarHandle)) + ShowWindow(RebarHandle, SW_SHOW); + } + else + { + if (RebarHandle && IsWindowVisible(RebarHandle)) + ShowWindow(RebarHandle, SW_HIDE); + } + + if (ToolStatusConfig.SearchBoxEnabled && RebarHandle && SearchboxHandle) + { + UINT height = (UINT)SendMessage(RebarHandle, RB_GETROWHEIGHT, 0, 0); + + // Add the Searchbox band into the rebar control. + if (!RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + RebarBandInsert(REBAR_BAND_ID_SEARCHBOX, SearchboxHandle, PhMultiplyDivide(180, PhGlobalDpi, 96), height - 2); + + if (!IsWindowVisible(SearchboxHandle)) + ShowWindow(SearchboxHandle, SW_SHOW); + + if (SearchBoxDisplayMode == SEARCHBOX_DISPLAY_MODE_HIDEINACTIVE) + { + if (RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + RebarBandRemove(REBAR_BAND_ID_SEARCHBOX); + } + } + else + { + // Remove the Searchbox band from the rebar control. + if (RebarBandExists(REBAR_BAND_ID_SEARCHBOX)) + RebarBandRemove(REBAR_BAND_ID_SEARCHBOX); + + if (SearchboxHandle) + { + // Clear search text and reset search filters. + SetFocus(SearchboxHandle); + Static_SetText(SearchboxHandle, L""); + + if (IsWindowVisible(SearchboxHandle)) + ShowWindow(SearchboxHandle, SW_HIDE); + } + } + + if (ToolStatusConfig.StatusBarEnabled) + { + if (StatusBarHandle && !IsWindowVisible(StatusBarHandle)) + ShowWindow(StatusBarHandle, SW_SHOW); + } + else + { + if (StatusBarHandle && IsWindowVisible(StatusBarHandle)) + ShowWindow(StatusBarHandle, SW_HIDE); + } + + ToolbarCreateGraphs(); +} + +VOID ToolbarLoadSettings( + VOID + ) +{ + RebarLoadSettings(); + + if (ToolStatusConfig.ToolBarEnabled && ToolBarHandle) + { + INT index = 0; + INT buttonCount = 0; + + buttonCount = (INT)SendMessage(ToolBarHandle, TB_BUTTONCOUNT, 0, 0); + + for (index = 0; index < buttonCount; index++) + { + TBBUTTONINFO buttonInfo = + { + sizeof(TBBUTTONINFO), + TBIF_BYINDEX | TBIF_STYLE | TBIF_COMMAND | TBIF_STATE + }; + + // Get settings for first button + if (SendMessage(ToolBarHandle, TB_GETBUTTONINFO, index, (LPARAM)&buttonInfo) == -1) + break; + + // Skip separator buttons + if (buttonInfo.fsStyle == BTNS_SEP) + continue; + + // Add the button text + buttonInfo.dwMask |= TBIF_TEXT; + buttonInfo.pszText = ToolbarGetText(buttonInfo.idCommand); + + switch (DisplayStyle) + { + case TOOLBAR_DISPLAY_STYLE_IMAGEONLY: + buttonInfo.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + break; + case TOOLBAR_DISPLAY_STYLE_SELECTIVETEXT: + { + switch (buttonInfo.idCommand) + { + case PHAPP_ID_VIEW_REFRESH: + case PHAPP_ID_HACKER_OPTIONS: + case PHAPP_ID_HACKER_FINDHANDLESORDLLS: + case PHAPP_ID_VIEW_SYSTEMINFORMATION: + buttonInfo.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT; + break; + default: + buttonInfo.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + break; + } + } + break; + case TOOLBAR_DISPLAY_STYLE_ALLTEXT: + buttonInfo.fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT; + break; + } + + switch (buttonInfo.idCommand) + { + case PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES: + { + if (WINDOWS_HAS_UAC && PhGetOwnTokenAttributes().Elevated) + { + buttonInfo.fsState |= TBSTATE_HIDDEN; + } + } + break; + case PHAPP_ID_VIEW_ALWAYSONTOP: + { + // Set the pressed state + if (PhGetIntegerSetting(L"MainWindowAlwaysOnTop")) + { + buttonInfo.fsState |= TBSTATE_PRESSED; + } + } + break; + case TIDC_POWERMENUDROPDOWN: + { + buttonInfo.fsStyle |= BTNS_WHOLEDROPDOWN; + } + break; + } + + // Set updated button info + SendMessage(ToolBarHandle, TB_SETBUTTONINFO, index, (LPARAM)&buttonInfo); + } + + // Resize the toolbar + SendMessage(ToolBarHandle, TB_AUTOSIZE, 0, 0); + } + + if (ToolStatusConfig.ToolBarEnabled && RebarHandle && ToolBarHandle) + { + UINT index; + REBARBANDINFO rebarBandInfo = + { + REBARBANDINFO_V6_SIZE, + RBBIM_IDEALSIZE + }; + + index = (UINT)SendMessage(RebarHandle, RB_IDTOINDEX, (WPARAM)REBAR_BAND_ID_TOOLBAR, 0); + + // Get settings for Rebar band. + if (SendMessage(RebarHandle, RB_GETBANDINFO, index, (LPARAM)&rebarBandInfo) != -1) + { + SIZE idealWidth; + + // Reset the cxIdeal for the Chevron + SendMessage(ToolBarHandle, TB_GETIDEALSIZE, FALSE, (LPARAM)&idealWidth); + + rebarBandInfo.cxIdeal = idealWidth.cx; + + SendMessage(RebarHandle, RB_SETBANDINFO, index, (LPARAM)&rebarBandInfo); + } + } + + // Invoke the LayoutPaddingCallback. + SendMessage(PhMainWndHandle, WM_SIZE, 0, 0); +} + +VOID ToolbarResetSettings( + VOID + ) +{ + // Remove all buttons. + INT buttonCount = (INT)SendMessage(ToolBarHandle, TB_BUTTONCOUNT, 0, 0); + + while (buttonCount--) + SendMessage(ToolBarHandle, TB_DELETEBUTTON, (WPARAM)buttonCount, 0); + + // Add the default buttons. + SendMessage(ToolBarHandle, TB_ADDBUTTONS, MAX_DEFAULT_TOOLBAR_ITEMS, (LPARAM)ToolbarButtons); +} + +PWSTR ToolbarGetText( + _In_ INT CommandID + ) +{ + switch (CommandID) + { + case PHAPP_ID_VIEW_REFRESH: + return L"Refresh"; + case PHAPP_ID_HACKER_OPTIONS: + return L"Options"; + case PHAPP_ID_HACKER_FINDHANDLESORDLLS: + return L"Find handles or DLLs"; + case PHAPP_ID_VIEW_SYSTEMINFORMATION: + return L"System information"; + case TIDC_FINDWINDOW: + return L"Find window"; + case TIDC_FINDWINDOWTHREAD: + return L"Find window and thread"; + case TIDC_FINDWINDOWKILL: + return L"Find window and kill"; + case PHAPP_ID_VIEW_ALWAYSONTOP: + return L"Always on top"; + case TIDC_POWERMENUDROPDOWN: + return L"Computer"; + case PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES: + return L"Show details for all processes"; + } + + return L"ERROR"; +} + +HBITMAP ToolbarLoadImageFromIcon( + _In_ ULONG Width, + _In_ ULONG Height, + _In_ PWSTR Name + ) +{ + HICON icon = PhLoadIcon(PluginInstance->DllBase, Name, 0, Width, Height); + HBITMAP bitmap = PhIconToBitmap(icon, Width, Height); + DestroyIcon(icon); + return bitmap; +} + +HBITMAP ToolbarGetImage( + _In_ INT CommandID + ) +{ + static INT cx = 0; + static INT cy = 0; + + if (!cx) + { + cx = GetSystemMetrics(SM_CXSMICON); + } + + if (!cy) + { + cy = GetSystemMetrics(SM_CYSMICON); + } + + switch (CommandID) + { + case PHAPP_ID_VIEW_REFRESH: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_ARROW_REFRESH_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_ARROW_REFRESH)); + } + + return toolbarBitmap; + } + break; + case PHAPP_ID_HACKER_OPTIONS: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_COG_EDIT_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_COG_EDIT)); + } + + return toolbarBitmap; + } + break; + case PHAPP_ID_HACKER_FINDHANDLESORDLLS: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_FIND_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_FIND)); + } + + return toolbarBitmap; + } + break; + case PHAPP_ID_VIEW_SYSTEMINFORMATION: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_CHART_LINE_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_CHART_LINE)); + } + + return toolbarBitmap; + } + break; + case TIDC_FINDWINDOW: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_APPLICATION_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_TBAPPLICATION)); + } + + return toolbarBitmap; + } + break; + case TIDC_FINDWINDOWTHREAD: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_APPLICATION_GO_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_APPLICATION_GO)); + } + + return toolbarBitmap; + } + break; + case TIDC_FINDWINDOWKILL: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_CROSS_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_CROSS)); + } + + return toolbarBitmap; + } + break; + case PHAPP_ID_VIEW_ALWAYSONTOP: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_APPLICATION_GET_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_APPLICATION_GET)); + } + + return toolbarBitmap; + } + break; + case TIDC_POWERMENUDROPDOWN: + { + HBITMAP toolbarBitmap = NULL; + + if (ToolStatusConfig.ModernIcons) + { + toolbarBitmap = LoadImageFromResources(cx, cy, MAKEINTRESOURCE(IDB_POWER_MODERN)); + } + else + { + toolbarBitmap = ToolbarLoadImageFromIcon(cx, cy, MAKEINTRESOURCE(IDI_LIGHTBULB_OFF)); + } + + return toolbarBitmap; + } + break; + case PHAPP_ID_HACKER_SHOWDETAILSFORALLPROCESSES: + { + HBITMAP toolbarBitmap = NULL; + HICON shieldIcon = NULL; + + if (shieldIcon = PhLoadIcon(NULL, IDI_SHIELD, PH_LOAD_ICON_SIZE_SMALL | PH_LOAD_ICON_STRICT, 0, 0)) + { + toolbarBitmap = PhIconToBitmap( + shieldIcon, + cx, + cy + ); + + DestroyIcon(shieldIcon); + } + + return toolbarBitmap; + } + break; + } + + return NULL; +} + +VOID ToolbarLoadButtonSettings( + VOID + ) +{ + INT buttonCount; + ULONG64 countInteger; + PPH_STRING settingsString; + PTBBUTTON buttonArray; + PH_STRINGREF remaining; + PH_STRINGREF part; + + settingsString = PhaGetStringSetting(SETTING_NAME_TOOLBAR_CONFIG); + remaining = settingsString->sr; + + if (remaining.Length == 0) + { + // Load default settings + SendMessage(ToolBarHandle, TB_ADDBUTTONS, MAX_DEFAULT_TOOLBAR_ITEMS, (LPARAM)ToolbarButtons); + return; + } + + // Query the number of buttons to insert + if (!PhSplitStringRefAtChar(&remaining, '|', &part, &remaining)) + { + // Load default settings + SendMessage(ToolBarHandle, TB_ADDBUTTONS, MAX_DEFAULT_TOOLBAR_ITEMS, (LPARAM)ToolbarButtons); + return; + } + + if (!PhStringToInteger64(&part, 10, &countInteger)) + { + // Load default settings + SendMessage(ToolBarHandle, TB_ADDBUTTONS, MAX_DEFAULT_TOOLBAR_ITEMS, (LPARAM)ToolbarButtons); + return; + } + + buttonCount = (INT)countInteger; + + // Allocate the button array + buttonArray = PhAllocate(buttonCount * sizeof(TBBUTTON)); + memset(buttonArray, 0, buttonCount * sizeof(TBBUTTON)); + + for (INT index = 0; index < buttonCount; index++) + { + ULONG64 commandInteger; + PH_STRINGREF commandIdPart; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, '|', &commandIdPart, &remaining); + PhStringToInteger64(&commandIdPart, 10, &commandInteger); + + buttonArray[index].idCommand = (INT)commandInteger; + //buttonArray[index].iBitmap = I_IMAGECALLBACK; + buttonArray[index].fsState = TBSTATE_ENABLED; + + if (commandInteger) + { + buttonArray[index].fsStyle = BTNS_BUTTON | BTNS_AUTOSIZE; + } + else + { + buttonArray[index].fsStyle = BTNS_SEP; + } + + // Pre-cache the image in the Toolbar array on startup. + for (INT i = 0; i < ARRAYSIZE(ToolbarButtons); i++) + { + if (ToolbarButtons[i].idCommand == buttonArray[index].idCommand) + { + HBITMAP buttonImage; + + buttonImage = ToolbarGetImage(ToolbarButtons[i].idCommand); + + // Add the image, cache the value in the ToolbarButtons array, set the bitmap index. + buttonArray[index].iBitmap = ToolbarButtons[i].iBitmap = ImageList_Add( + ToolBarImageList, + buttonImage, + NULL + ); + + DeleteObject(buttonImage); + break; + } + } + } + + SendMessage(ToolBarHandle, TB_ADDBUTTONS, buttonCount, (LPARAM)buttonArray); + + PhFree(buttonArray); +} + +VOID ToolbarSaveButtonSettings( + VOID + ) +{ + INT buttonIndex = 0; + INT buttonCount = 0; + PPH_STRING settingsString; + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 100); + + buttonCount = (INT)SendMessage(ToolBarHandle, TB_BUTTONCOUNT, 0, 0); + + PhAppendFormatStringBuilder( + &stringBuilder, + L"%d|", + buttonCount + ); + + for (buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) + { + TBBUTTONINFO buttonInfo = + { + sizeof(TBBUTTONINFO), + TBIF_BYINDEX | TBIF_IMAGE | TBIF_STYLE | TBIF_COMMAND + }; + + // Get button information. + if (SendMessage(ToolBarHandle, TB_GETBUTTONINFO, buttonIndex, (LPARAM)&buttonInfo) == -1) + break; + + PhAppendFormatStringBuilder( + &stringBuilder, + L"%d|", + buttonInfo.idCommand + ); + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); + PhSetStringSetting2(SETTING_NAME_TOOLBAR_CONFIG, &settingsString->sr); +} + +VOID ReBarLoadLayoutSettings( + VOID + ) +{ + UINT bandIndex = 0; + UINT bandCount = 0; + PPH_STRING settingsString; + PH_STRINGREF remaining; + + settingsString = PhGetStringSetting(SETTING_NAME_REBAR_CONFIG); + remaining = settingsString->sr; + + if (remaining.Length == 0) + return; + + bandCount = (UINT)SendMessage(RebarHandle, RB_GETBANDCOUNT, 0, 0); + + for (bandIndex = 0; bandIndex < bandCount; bandIndex++) + { + PH_STRINGREF idPart; + PH_STRINGREF cxPart; + PH_STRINGREF stylePart; + ULONG64 idInteger; + ULONG64 cxInteger; + ULONG64 styleInteger; + UINT oldBandIndex; + REBARBANDINFO rebarBandInfo = + { + REBARBANDINFO_V6_SIZE, + RBBIM_STYLE | RBBIM_SIZE + }; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, '|', &idPart, &remaining); + PhSplitStringRefAtChar(&remaining, '|', &cxPart, &remaining); + PhSplitStringRefAtChar(&remaining, '|', &stylePart, &remaining); + + PhStringToInteger64(&idPart, 10, &idInteger); + PhStringToInteger64(&cxPart, 10, &cxInteger); + PhStringToInteger64(&stylePart, 10, &styleInteger); + + if ((oldBandIndex = (UINT)SendMessage(RebarHandle, RB_IDTOINDEX, (UINT)idInteger, 0)) == -1) + break; + + if (oldBandIndex != bandIndex) + { + SendMessage(RebarHandle, RB_MOVEBAND, oldBandIndex, bandIndex); + } + + if (SendMessage(RebarHandle, RB_GETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo)) + { + if (idInteger == REBAR_BAND_ID_SEARCHBOX) + { + rebarBandInfo.fStyle |= RBBS_FIXEDSIZE; + } + + rebarBandInfo.cx = (UINT)cxInteger; + rebarBandInfo.fStyle |= (UINT)styleInteger; + + SendMessage(RebarHandle, RB_SETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo); + } + } +} + +VOID ReBarSaveLayoutSettings( + VOID + ) +{ + UINT bandIndex = 0; + UINT bandCount = 0; + PPH_STRING settingsString; + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 100); + + bandCount = (UINT)SendMessage(RebarHandle, RB_GETBANDCOUNT, 0, 0); + + for (bandIndex = 0; bandIndex < bandCount; bandIndex++) + { + REBARBANDINFO rebarBandInfo = + { + REBARBANDINFO_V6_SIZE, + RBBIM_STYLE | RBBIM_SIZE | RBBIM_ID + }; + + SendMessage(RebarHandle, RB_GETBANDINFO, bandIndex, (LPARAM)&rebarBandInfo); + + if (rebarBandInfo.fStyle & RBBS_GRIPPERALWAYS) + { + rebarBandInfo.fStyle &= ~RBBS_GRIPPERALWAYS; + } + + if (rebarBandInfo.fStyle & RBBS_NOGRIPPER) + { + rebarBandInfo.fStyle &= ~RBBS_NOGRIPPER; + } + + if (rebarBandInfo.fStyle & RBBS_FIXEDSIZE) + { + rebarBandInfo.fStyle &= ~RBBS_FIXEDSIZE; + } + + PhAppendFormatStringBuilder( + &stringBuilder, + L"%u|%u|%u|", + rebarBandInfo.wID, + rebarBandInfo.cx, + rebarBandInfo.fStyle + ); + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); + PhSetStringSetting2(SETTING_NAME_REBAR_CONFIG, &settingsString->sr); +} \ No newline at end of file diff --git a/plugins/ToolStatus/toolstatus.h b/plugins/ToolStatus/toolstatus.h new file mode 100644 index 0000000..123c2c9 --- /dev/null +++ b/plugins/ToolStatus/toolstatus.h @@ -0,0 +1,409 @@ +/* + * Process Hacker ToolStatus - + * toolstatus header + * + * Copyright (C) 2011-2016 dmex + * Copyright (C) 2010-2013 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef _TOOLSTATUS_H +#define _TOOLSTATUS_H + +#define CINTERFACE +#define COBJMACROS +#define INITGUID +#include +#include +#include +#include +#include + +#include "resource.h" + +#define PLUGIN_NAME TOOLSTATUS_PLUGIN_NAME +#define SETTING_NAME_TOOLSTATUS_CONFIG (PLUGIN_NAME L".Config") +#define SETTING_NAME_REBAR_CONFIG (PLUGIN_NAME L".RebarConfig") +#define SETTING_NAME_TOOLBAR_CONFIG (PLUGIN_NAME L".ToolbarConfig") +#define SETTING_NAME_STATUSBAR_CONFIG (PLUGIN_NAME L".StatusbarConfig") +#define SETTING_NAME_TOOLBAR_THEME (PLUGIN_NAME L".ToolbarTheme") +#define SETTING_NAME_TOOLBARDISPLAYSTYLE (PLUGIN_NAME L".ToolbarDisplayStyle") +#define SETTING_NAME_SEARCHBOXDISPLAYMODE (PLUGIN_NAME L".SearchBoxDisplayMode") + +#define MAX_DEFAULT_TOOLBAR_ITEMS 9 +#define MAX_DEFAULT_STATUSBAR_ITEMS 3 +#define MAX_TOOLBAR_ITEMS 12 +#define MAX_STATUSBAR_ITEMS 14 + +#define ID_SEARCH_CLEAR (WM_APP + 1) +#define TIDC_FINDWINDOW (WM_APP + 2) +#define TIDC_FINDWINDOWTHREAD (WM_APP + 3) +#define TIDC_FINDWINDOWKILL (WM_APP + 4) +#define TIDC_POWERMENUDROPDOWN (WM_APP + 5) + +typedef enum _TOOLBAR_DISPLAY_STYLE +{ + TOOLBAR_DISPLAY_STYLE_IMAGEONLY, + TOOLBAR_DISPLAY_STYLE_SELECTIVETEXT, + TOOLBAR_DISPLAY_STYLE_ALLTEXT +} TOOLBAR_DISPLAY_STYLE; + +typedef enum _TOOLBAR_COMMAND_ID +{ + COMMAND_ID_ENABLE_MENU = 1, + COMMAND_ID_ENABLE_SEARCHBOX, + COMMAND_ID_ENABLE_CPU_GRAPH, + COMMAND_ID_ENABLE_MEMORY_GRAPH, + COMMAND_ID_ENABLE_COMMIT_GRAPH, + COMMAND_ID_ENABLE_IO_GRAPH, + COMMAND_ID_TOOLBAR_LOCKUNLOCK, + COMMAND_ID_TOOLBAR_CUSTOMIZE, +} TOOLBAR_COMMAND_ID; + +typedef enum _TOOLBAR_THEME +{ + TOOLBAR_THEME_NONE, + TOOLBAR_THEME_BLACK, + TOOLBAR_THEME_BLUE +} TOOLBAR_THEME; + +typedef enum _SEARCHBOX_DISPLAY_MODE +{ + SEARCHBOX_DISPLAY_MODE_ALWAYSSHOW, + SEARCHBOX_DISPLAY_MODE_HIDEINACTIVE, + //SEARCHBOX_DISPLAY_MODE_AUTOHIDE +} SEARCHBOX_DISPLAY_MODE; + +typedef enum _REBAR_BAND_ID +{ + REBAR_BAND_ID_TOOLBAR, + REBAR_BAND_ID_SEARCHBOX, + REBAR_BAND_ID_CPUGRAPH, + REBAR_BAND_ID_MEMGRAPH, + REBAR_BAND_ID_COMMITGRAPH, + REBAR_BAND_ID_IOGRAPH +} REBAR_BAND; + +typedef enum _REBAR_DISPLAY_LOCATION +{ + REBAR_DISPLAY_LOCATION_TOP, + REBAR_DISPLAY_LOCATION_LEFT, + REBAR_DISPLAY_LOCATION_BOTTOM, + REBAR_DISPLAY_LOCATION_RIGHT, +} REBAR_DISPLAY_LOCATION; + +typedef union _TOOLSTATUS_CONFIG +{ + ULONG Flags; + struct + { + ULONG ToolBarEnabled : 1; + ULONG SearchBoxEnabled : 1; + ULONG StatusBarEnabled : 1; + ULONG ToolBarLocked : 1; + ULONG ResolveGhostWindows : 1; + + ULONG ModernIcons : 1; + ULONG AutoHideMenu : 1; + ULONG CpuGraphEnabled : 1; + ULONG MemGraphEnabled : 1; + ULONG CommitGraphEnabled : 1; + ULONG IoGraphEnabled : 1; + + ULONG Spare : 21; + }; +} TOOLSTATUS_CONFIG; + +extern TOOLSTATUS_CONFIG ToolStatusConfig; +extern HWND ProcessTreeNewHandle; +extern HWND ServiceTreeNewHandle; +extern HWND NetworkTreeNewHandle; +extern INT SelectedTabIndex; +extern BOOLEAN UpdateAutomatically; +extern BOOLEAN UpdateGraphs; +extern TOOLBAR_THEME ToolBarTheme; +extern TOOLBAR_DISPLAY_STYLE DisplayStyle; +extern SEARCHBOX_DISPLAY_MODE SearchBoxDisplayMode; +extern REBAR_DISPLAY_LOCATION RebarDisplayLocation; + +extern HWND RebarHandle; +extern HWND ToolBarHandle; +extern HWND SearchboxHandle; + +extern HMENU MainMenu; +extern HACCEL AcceleratorTable; +extern PPH_STRING SearchboxText; +extern PH_PLUGIN_SYSTEM_STATISTICS SystemStatistics; + +extern HIMAGELIST ToolBarImageList; +extern TBBUTTON ToolbarButtons[MAX_TOOLBAR_ITEMS]; + +extern PPH_PLUGIN PluginInstance; +extern PPH_TN_FILTER_ENTRY ProcessTreeFilterEntry; +extern PPH_TN_FILTER_ENTRY ServiceTreeFilterEntry; +extern PPH_TN_FILTER_ENTRY NetworkTreeFilterEntry; + +PTOOLSTATUS_TAB_INFO FindTabInfo( + _In_ INT TabIndex + ); + +// toolbar.c + +typedef HRESULT (WINAPI *_LoadIconMetric)( + _In_ HINSTANCE hinst, + _In_ PCWSTR pszName, + _In_ int lims, + _Out_ HICON *phico + ); + +VOID RebarBandInsert( + _In_ UINT BandID, + _In_ HWND HwndChild, + _In_ UINT cyMinChild, + _In_ UINT cxMinChild + ); + +VOID RebarBandRemove( + _In_ UINT BandID + ); + +BOOLEAN RebarBandExists( + _In_ UINT BandID + ); + +VOID ToolbarLoadSettings( + VOID + ); + +VOID ToolbarResetSettings( + VOID + ); + +PWSTR ToolbarGetText( + _In_ INT CommandID + ); + +HBITMAP ToolbarGetImage( + _In_ INT CommandID + ); + +VOID ToolbarLoadButtonSettings( + VOID + ); + +VOID ToolbarSaveButtonSettings( + VOID + ); + +VOID ReBarLoadLayoutSettings( + VOID + ); + +VOID ReBarSaveLayoutSettings( + VOID + ); + +// main.c + +HWND GetCurrentTreeNewHandle( + VOID + ); + +VOID ShowCustomizeMenu( + VOID + ); + +// options.c + +VOID ShowOptionsDialog( + _In_opt_ HWND Parent + ); + +// filter.c + +BOOLEAN WordMatchStringRef( + _In_ PPH_STRINGREF Text + ); + +BOOLEAN ProcessTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); +BOOLEAN ServiceTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); +BOOLEAN NetworkTreeFilterCallback( + _In_ PPH_TREENEW_NODE Node, + _In_opt_ PVOID Context + ); + +// searchbox.c + +HWND CreateSearchControl( + _In_ UINT CmdId + ); + +typedef struct _EDIT_CONTEXT +{ + UINT CommandID; + LONG CXWidth; + INT CXBorder; + INT ImageWidth; + INT ImageHeight; + + HWND WindowHandle; + HFONT WindowFont; + HIMAGELIST ImageList; + + HBRUSH BrushNormal; + HBRUSH BrushPushed; + HBRUSH BrushHot; + //COLORREF BackgroundColorRef; + + union + { + ULONG Flags; + struct + { + ULONG Hot : 1; + ULONG Pushed : 1; + ULONG Spare : 30; + }; + }; +} EDIT_CONTEXT, *PEDIT_CONTEXT; + +HBITMAP LoadImageFromResources( + _In_ UINT Width, + _In_ UINT Height, + _In_ PCWSTR Name + ); + +// graph.c + +extern HWND CpuGraphHandle; +extern HWND MemGraphHandle; +extern HWND CommitGraphHandle; +extern HWND IoGraphHandle; + +VOID ToolbarCreateGraphs(VOID); +VOID ToolbarUpdateGraphs(VOID); +VOID ToolbarUpdateGraphsInfo(LPNMHDR Header); + +// statusbar.c + +typedef struct _STATUSBAR_ITEM +{ + ULONG Id; +} STATUSBAR_ITEM, *PSTATUSBAR_ITEM; + +extern ULONG ProcessesUpdatedCount; +extern HWND StatusBarHandle; +extern PPH_LIST StatusBarItemList; +extern ULONG StatusBarItems[MAX_STATUSBAR_ITEMS]; + +VOID StatusBarLoadSettings( + VOID + ); + +VOID StatusBarSaveSettings( + VOID + ); + +VOID StatusBarResetSettings( + VOID + ); + +PWSTR StatusBarGetText( + _In_ ULONG CommandID + ); + +VOID StatusBarUpdate( + _In_ BOOLEAN ResetMaxWidths + ); + +VOID StatusBarShowMenu( + VOID + ); + +// customizetb.c + +VOID ToolBarShowCustomizeDialog( + VOID + ); + +// customizesb.c + +typedef enum _ID_STATUS +{ + ID_STATUS_NONE, + ID_STATUS_CPUUSAGE, + ID_STATUS_COMMITCHARGE, + ID_STATUS_PHYSICALMEMORY, + ID_STATUS_NUMBEROFPROCESSES, + ID_STATUS_NUMBEROFTHREADS, + ID_STATUS_NUMBEROFHANDLES, + ID_STATUS_IO_RO, + ID_STATUS_IO_W, + ID_STATUS_MAX_CPU_PROCESS, + ID_STATUS_MAX_IO_PROCESS, + ID_STATUS_NUMBEROFVISIBLEITEMS, + ID_STATUS_NUMBEROFSELECTEDITEMS, + ID_STATUS_INTERVALSTATUS, + ID_STATUS_FREEMEMORY +} ID_STATUS; + +VOID StatusBarShowCustomizeDialog( + VOID + ); + +// Shared by customizetb.c and customizesb.c + +typedef struct _BUTTON_CONTEXT +{ + INT IdCommand; + INT IdBitmap; + + union + { + ULONG Flags; + struct + { + ULONG IsVirtual : 1; + ULONG IsRemovable : 1; + ULONG IsSeparator : 1; + ULONG Spare : 29; + }; + }; +} BUTTON_CONTEXT, *PBUTTON_CONTEXT; + +typedef struct _CUSTOMIZE_CONTEXT +{ + HWND DialogHandle; + HWND AvailableListHandle; + HWND CurrentListHandle; + HWND MoveUpButtonHandle; + HWND MoveDownButtonHandle; + HWND AddButtonHandle; + HWND RemoveButtonHandle; + + INT BitmapWidth; + HFONT FontHandle; + HIMAGELIST ImageListHandle; +} CUSTOMIZE_CONTEXT, *PCUSTOMIZE_CONTEXT; + +#endif \ No newline at end of file diff --git a/plugins/Updater/CHANGELOG.txt b/plugins/Updater/CHANGELOG.txt new file mode 100644 index 0000000..c4b1158 --- /dev/null +++ b/plugins/Updater/CHANGELOG.txt @@ -0,0 +1,38 @@ +1.7 + * Ignore v2.40 and above on XP and Vista. + +1.6 + * Fixed background update check interval + +1.5 + * Added dynamic URL support + * Added digital signature verification + +1.4 + * Added PNG images + * Fixed offline crash + * Improved background update check + * Removed BMP images + +1.3 + * Added revision checking + * Fixed invalid xml crash + * Fixed GDI handle leak + * Fixed caching + +1.2 + * Improved UI + * Improved internet connectivity check for Vista and above (INetworkListManager) + * Improved download speed calculation + * Improved time remaining calculation + * Fixed auto-update prompt location if PH minimized + * Fixed install failures if hash check failed + +1.1 + * Added download speed + * Added remaining time + * Fixed buffer zeroing + * Fixed threading memory leak + +1.0 + * Initial release diff --git a/plugins/Updater/Updater.rc b/plugins/Updater/Updater.rc new file mode 100644 index 0000000..0d3b0fc --- /dev/null +++ b/plugins/Updater/Updater.rc @@ -0,0 +1,176 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,7,0,0 + PRODUCTVERSION 1,7,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "dmex" + VALUE "FileDescription", "Update checker plugin for Process Hacker" + VALUE "FileVersion", "1.7" + VALUE "InternalName", "Updater" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "Updater.dll" + VALUE "ProductName", "Update checker plugin for Process Hacker" + VALUE "ProductVersion", "1.7" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_UPDATE DIALOGEX 0, 0, 259, 89 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Process Hacker Updater" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Check for new releases?",IDC_MESSAGE,"Static",SS_SIMPLE | SS_NOPREFIX | SS_WORDELLIPSIS | WS_GROUP,7,6,194,13,WS_EX_TRANSPARENT + CONTROL "",IDC_RELDATE,"Static",SS_SIMPLE | SS_NOPREFIX | SS_WORDELLIPSIS | WS_GROUP,8,24,194,11,WS_EX_TRANSPARENT + CONTROL "View Changelog",IDC_INFOSYSLINK,"SysLink",NOT WS_VISIBLE | WS_TABSTOP,8,39,89,11,WS_EX_TRANSPARENT + CONTROL "",IDC_PROGRESS,"msctls_progress32",PBS_SMOOTH | NOT WS_VISIBLE,8,54,244,11 + CONTROL "",IDC_STATUS,"Static",SS_SIMPLE | SS_NOPREFIX | SS_WORDELLIPSIS | WS_GROUP,7,72,135,8,WS_EX_TRANSPARENT + PUSHBUTTON "Start",IDC_DOWNLOAD,149,70,50,14 + PUSHBUTTON "Close",IDOK,203,70,50,14 + CONTROL "",IDC_UPDATEICON,"Static",SS_BITMAP | SS_CENTERIMAGE,224,0,32,32,WS_EX_TRANSPARENT +END + +IDD_OPTIONS DIALOGEX 0, 0, 215, 54 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Updater Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "Check for updates automatically",IDC_AUTOCHECKBOX, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,142,10 + DEFPUSHBUTTON "OK",IDOK,105,33,50,14 + PUSHBUTTON "Cancel",IDCANCEL,158,33,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_UPDATE, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 252 + TOPMARGIN, 7 + BOTTOMMARGIN, 84 + END + + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 208 + TOPMARGIN, 7 + BOTTOMMARGIN, 47 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_UPDATE AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/Updater/Updater.vcxproj b/plugins/Updater/Updater.vcxproj new file mode 100644 index 0000000..6237e90 --- /dev/null +++ b/plugins/Updater/Updater.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A0C1595C-FA3E-4B7A-936C-306BC6294C5E} + Updater + Win32Proj + Updater + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + winhttp.lib;%(AdditionalDependencies) + winhttp.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/Updater/Updater.vcxproj.filters b/plugins/Updater/Updater.vcxproj.filters new file mode 100644 index 0000000..f2810fc --- /dev/null +++ b/plugins/Updater/Updater.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/Updater/main.c b/plugins/Updater/main.c new file mode 100644 index 0000000..f948b5d --- /dev/null +++ b/plugins/Updater/main.c @@ -0,0 +1,179 @@ +/* + * Process Hacker Plugins - + * Update Checker Plugin + * + * Copyright (C) 2011-2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "updater.h" + +PPH_PLUGIN PluginInstance; +static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +static PH_CALLBACK_REGISTRATION MainMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; + +VOID NTAPI MainWindowShowingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + // Check if the user want's us to auto-check for updates. + if (PhGetIntegerSetting(SETTING_NAME_AUTO_CHECK)) + { + // All good, queue up our update check. + StartInitialCheck(); + } +} + +VOID NTAPI MainMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + + // Check this menu is the Help menu + if (!menuInfo || menuInfo->u.MainMenu.SubMenuIndex != 4) + return; + + PhInsertEMenuItem(menuInfo->Menu, PhPluginCreateEMenuItem(PluginInstance, 0, UPDATE_MENUITEM, L"Check for updates", NULL), 0); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + if (menuItem && menuItem->Id == UPDATE_MENUITEM) + { + ShowUpdateDialog(NULL); + } +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + (HWND)Parameter, + OptionsDlgProc + ); +} + +PPH_STRING PhGetOpaqueXmlNodeText( + _In_ mxml_node_t *xmlNode + ) +{ + if (xmlNode && xmlNode->child && xmlNode->child->type == MXML_OPAQUE && xmlNode->child->value.opaque) + { + return PhConvertUtf8ToUtf16(xmlNode->child->value.opaque); + } + + return PhReferenceEmptyString(); +} + +BOOL PhInstalledUsingSetup( + VOID + ) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Process_Hacker2_is1"); + + HANDLE keyHandle = NULL; + + // Check uninstall entries for the 'Process_Hacker2_is1' registry key. + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName, + 0 + ))) + { + NtClose(keyHandle); + return TRUE; + } + + return FALSE; +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_AUTO_CHECK, L"1" }, + { StringSettingType, SETTING_NAME_LAST_CHECK, L"0" } + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Update Checker"; + info->Author = L"dmex"; + info->Description = L"Plugin for checking new Process Hacker releases via the Help menu."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1121"; + info->HasOptions = TRUE; + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainMenuInitializing), + MainMenuInitializingCallback, + NULL, + &MainMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + break; + } + + return TRUE; +} \ No newline at end of file diff --git a/plugins/Updater/options.c b/plugins/Updater/options.c new file mode 100644 index 0000000..3d32e4d --- /dev/null +++ b/plugins/Updater/options.c @@ -0,0 +1,62 @@ +/* + * Process Hacker Plugins - + * Update Checker Plugin + * + * Copyright (C) 2011-2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "updater.h" + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + if (PhGetIntegerSetting(SETTING_NAME_AUTO_CHECK)) + Button_SetCheck(GetDlgItem(hwndDlg, IDC_AUTOCHECKBOX), BST_CHECKED); + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PhSetIntegerSetting( + SETTING_NAME_AUTO_CHECK, + Button_GetCheck(GetDlgItem(hwndDlg, IDC_AUTOCHECKBOX)) == BST_CHECKED); + + EndDialog(hwndDlg, IDOK); + } + break; + } + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/Updater/resource.h b/plugins/Updater/resource.h new file mode 100644 index 0000000..857a056 --- /dev/null +++ b/plugins/Updater/resource.h @@ -0,0 +1,26 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Updater.rc +// +#define IDD_UPDATE 101 +#define IDD_OPTIONS 103 +#define IDB_PNG1 104 +#define IDC_MESSAGE 1001 +#define IDC_AUTOCHECKBOX 1002 +#define IDC_PROGRESS 1003 +#define IDC_RELDATE 1004 +#define IDC_STATUS 1005 +#define IDC_DOWNLOAD 1006 +#define IDC_UPDATEICON 1007 +#define IDC_INFOSYSLINK 1008 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1009 +#define _APS_NEXT_SYMED_VALUE 103 +#endif +#endif diff --git a/plugins/Updater/updater.c b/plugins/Updater/updater.c new file mode 100644 index 0000000..72210ac --- /dev/null +++ b/plugins/Updater/updater.c @@ -0,0 +1,1495 @@ +/* + * Process Hacker Plugins - + * Update Checker Plugin + * + * Copyright (C) 2011-2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "updater.h" +#include +#include + +static HANDLE UpdateDialogThreadHandle = NULL; +static HWND UpdateDialogHandle = NULL; +static PH_EVENT InitializedEvent = PH_EVENT_INIT; + +mxml_type_t QueryXmlDataCallback( + _In_ mxml_node_t *node + ) +{ + return MXML_OPAQUE; +} + +BOOLEAN LastUpdateCheckExpired( + VOID + ) +{ + ULONG64 lastUpdateTimeTicks = 0; + LARGE_INTEGER currentUpdateTimeTicks; + PPH_STRING lastUpdateTimeString; + + // Get the last update check time + lastUpdateTimeString = PhGetStringSetting(SETTING_NAME_LAST_CHECK); + PhStringToInteger64(&lastUpdateTimeString->sr, 0, &lastUpdateTimeTicks); + + // Query the current time + PhQuerySystemTime(¤tUpdateTimeTicks); + + // Check if the last update check was more than 7 days ago + if (currentUpdateTimeTicks.QuadPart - lastUpdateTimeTicks >= 7 * PH_TICKS_PER_DAY) + { + PPH_STRING currentUpdateTimeString = PhFormatUInt64(currentUpdateTimeTicks.QuadPart, FALSE); + + // Save the current time + PhSetStringSetting2(SETTING_NAME_LAST_CHECK, ¤tUpdateTimeString->sr); + + // Cleanup + PhDereferenceObject(currentUpdateTimeString); + PhDereferenceObject(lastUpdateTimeString); + return TRUE; + } + + // Cleanup + PhDereferenceObject(lastUpdateTimeString); + return FALSE; +} + +PPH_STRING UpdateVersionString( + VOID + ) +{ + ULONG majorVersion; + ULONG minorVersion; + ULONG revisionVersion; + PPH_STRING currentVersion = NULL; + PPH_STRING versionHeader = NULL; + + PhGetPhVersionNumbers( + &majorVersion, + &minorVersion, + NULL, + &revisionVersion + ); + + currentVersion = PhFormatString( + L"%lu.%lu.%lu", + majorVersion, + minorVersion, + revisionVersion + ); + + if (currentVersion) + { + versionHeader = PhConcatStrings2(L"ProcessHacker-Build: ", currentVersion->Buffer); + PhDereferenceObject(currentVersion); + } + + return versionHeader; +} + +PPH_STRING UpdateWindowsString( + VOID + ) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion"); + + HANDLE keyHandle = NULL; + PPH_STRING buildLabHeader = NULL; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName, + 0 + ))) + { + PPH_STRING buildLabString; + + if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLabEx")) + { + buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer); + PhDereferenceObject(buildLabString); + } + else if (buildLabString = PhQueryRegistryString(keyHandle, L"BuildLab")) + { + buildLabHeader = PhConcatStrings2(L"ProcessHacker-OsBuild: ", buildLabString->Buffer); + PhDereferenceObject(buildLabString); + } + + NtClose(keyHandle); + } + + return buildLabHeader; +} + +BOOLEAN ParseVersionString( + _Inout_ PPH_UPDATER_CONTEXT Context + ) +{ + PH_STRINGREF sr, majorPart, minorPart, revisionPart; + ULONG64 majorInteger = 0, minorInteger = 0, revisionInteger = 0; + + PhInitializeStringRef(&sr, Context->Version->Buffer); + PhInitializeStringRef(&revisionPart, Context->RevVersion->Buffer); + + if (PhSplitStringRefAtChar(&sr, '.', &majorPart, &minorPart)) + { + PhStringToInteger64(&majorPart, 10, &majorInteger); + PhStringToInteger64(&minorPart, 10, &minorInteger); + PhStringToInteger64(&revisionPart, 10, &revisionInteger); + + Context->MajorVersion = (ULONG)majorInteger; + Context->MinorVersion = (ULONG)minorInteger; + Context->RevisionVersion = (ULONG)revisionInteger; + + return TRUE; + } + + return FALSE; +} + +BOOLEAN ReadRequestString( + _In_ HINTERNET Handle, + _Out_ _Deref_post_z_cap_(*DataLength) PSTR *Data, + _Out_ ULONG *DataLength + ) +{ + PSTR data; + ULONG allocatedLength; + ULONG dataLength; + ULONG returnLength; + BYTE buffer[PAGE_SIZE]; + + allocatedLength = sizeof(buffer); + data = (PSTR)PhAllocate(allocatedLength); + dataLength = 0; + + // Zero the buffer + memset(buffer, 0, PAGE_SIZE); + + while (WinHttpReadData(Handle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Copy the returned buffer into our pointer + memcpy(data + dataLength, buffer, returnLength); + // Zero the returned buffer for the next loop + //memset(buffer, 0, returnLength); + + dataLength += returnLength; + } + + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + data = (PSTR)PhReAllocate(data, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + data[dataLength] = 0; + + *DataLength = dataLength; + *Data = data; + + return TRUE; +} + +PPH_UPDATER_CONTEXT CreateUpdateContext( + VOID + ) +{ + PPH_UPDATER_CONTEXT context; + + context = (PPH_UPDATER_CONTEXT)PhAllocate(sizeof(PH_UPDATER_CONTEXT)); + memset(context, 0, sizeof(PH_UPDATER_CONTEXT)); + + return context; +} + +VOID FreeUpdateContext( + _In_ _Post_invalid_ PPH_UPDATER_CONTEXT Context + ) +{ + if (!Context) + return; + + Context->HaveData = FALSE; + Context->UpdaterState = PhUpdateMaximum; + + Context->MinorVersion = 0; + Context->MajorVersion = 0; + Context->RevisionVersion = 0; + Context->CurrentMinorVersion = 0; + Context->CurrentMajorVersion = 0; + Context->CurrentRevisionVersion = 0; + + PhClearReference(&Context->Version); + PhClearReference(&Context->RevVersion); + PhClearReference(&Context->RelDate); + PhClearReference(&Context->Size); + PhClearReference(&Context->Hash); + PhClearReference(&Context->ReleaseNotesUrl); + PhClearReference(&Context->SetupFilePath); + PhClearReference(&Context->SetupFileDownloadUrl); + + if (Context->FontHandle) + { + DeleteObject(Context->FontHandle); + Context->FontHandle = NULL; + } + + if (Context->IconBitmap) + { + DeleteObject(Context->IconBitmap); + Context->IconBitmap = NULL; + } + + if (Context->IconHandle) + { + DestroyIcon(Context->IconHandle); + Context->IconHandle = NULL; + } + + PhFree(Context); +} + +BOOLEAN QueryUpdateData( + _Inout_ PPH_UPDATER_CONTEXT Context, + _In_ BOOLEAN UseFailServer + ) +{ + BOOLEAN isSuccess = FALSE; + HINTERNET httpSessionHandle = NULL; + HINTERNET httpConnectionHandle = NULL; + HINTERNET httpRequestHandle = NULL; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; + mxml_node_t* xmlNode = NULL; + ULONG xmlStringBufferLength = 0; + PSTR xmlStringBuffer = NULL; + PPH_STRING versionHeader = UpdateVersionString(); + PPH_STRING windowsHeader = UpdateWindowsString(); + + // Get the current Process Hacker version + PhGetPhVersionNumbers( + &Context->CurrentMajorVersion, + &Context->CurrentMinorVersion, + NULL, + &Context->CurrentRevisionVersion + ); + + __try + { + // Query the current system proxy + WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); + + // Open the HTTP session with the system proxy configuration if available + if (!(httpSessionHandle = WinHttpOpen( + NULL, + proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + proxyConfig.lpszProxy, + proxyConfig.lpszProxyBypass, + 0 + ))) + { + __leave; + } + + if (WindowsVersion >= WINDOWS_8_1) + { + // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. + ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + + WinHttpSetOption( + httpSessionHandle, + WINHTTP_OPTION_DECOMPRESSION, + &httpFlags, + sizeof(ULONG) + ); + } + + if (UseFailServer) + { + if (!(httpConnectionHandle = WinHttpConnect( + httpSessionHandle, + L"processhacker.sourceforge.net", + INTERNET_DEFAULT_HTTP_PORT, + 0 + ))) + { + __leave; + } + + if (!(httpRequestHandle = WinHttpOpenRequest( + httpConnectionHandle, + NULL, + L"/update.php", + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH + ))) + { + __leave; + } + } + else + { + if (!(httpConnectionHandle = WinHttpConnect( + httpSessionHandle, + L"wj32.org", + INTERNET_DEFAULT_HTTPS_PORT, + 0 + ))) + { + __leave; + } + + if (!(httpRequestHandle = WinHttpOpenRequest( + httpConnectionHandle, + NULL, + L"/processhacker/update.php", + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE + ))) + { + __leave; + } + } + + if (versionHeader) + { + WinHttpAddRequestHeaders( + httpRequestHandle, + versionHeader->Buffer, + (ULONG)versionHeader->Length / sizeof(WCHAR), + WINHTTP_ADDREQ_FLAG_ADD + ); + } + + if (windowsHeader) + { + WinHttpAddRequestHeaders( + httpRequestHandle, + windowsHeader->Buffer, + (ULONG)windowsHeader->Length / sizeof(WCHAR), + WINHTTP_ADDREQ_FLAG_ADD + ); + } + + if (!WinHttpSendRequest( + httpRequestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, + 0 + )) + { + __leave; + } + + if (!WinHttpReceiveResponse(httpRequestHandle, NULL)) + __leave; + + // Read the resulting xml into our buffer. + if (!ReadRequestString(httpRequestHandle, &xmlStringBuffer, &xmlStringBufferLength)) + __leave; + + // Check the buffer for valid data. + if (xmlStringBuffer == NULL || xmlStringBuffer[0] == '\0') + __leave; + + // Load our XML + xmlNode = mxmlLoadString(NULL, xmlStringBuffer, QueryXmlDataCallback); + if (xmlNode == NULL || xmlNode->type != MXML_ELEMENT) + __leave; + + // Find the version node + Context->Version = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "ver", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->Version)) + __leave; + + // Find the revision node + Context->RevVersion = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "rev", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->RevVersion)) + __leave; + + // Find the release date node + Context->RelDate = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "reldate", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->RelDate)) + __leave; + + // Find the size node + Context->Size = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "size", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->Size)) + __leave; + + //Find the hash node + Context->Hash = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "sha1", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->Hash)) + __leave; + + // Find the release notes URL + Context->ReleaseNotesUrl = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "relnotes", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->ReleaseNotesUrl)) + __leave; + + // Find the installer download URL + Context->SetupFileDownloadUrl = PhGetOpaqueXmlNodeText( + mxmlFindElement(xmlNode->child, xmlNode, "setupurl", NULL, NULL, MXML_DESCEND) + ); + if (PhIsNullOrEmptyString(Context->SetupFileDownloadUrl)) + __leave; + + if (!ParseVersionString(Context)) + __leave; + + isSuccess = TRUE; + } + __finally + { + if (httpRequestHandle) + WinHttpCloseHandle(httpRequestHandle); + + if (httpConnectionHandle) + WinHttpCloseHandle(httpConnectionHandle); + + if (httpSessionHandle) + WinHttpCloseHandle(httpSessionHandle); + + if (xmlNode) + mxmlDelete(xmlNode); + + if (xmlStringBuffer) + PhFree(xmlStringBuffer); + + PhClearReference(&versionHeader); + PhClearReference(&windowsHeader); + } + + return isSuccess; +} + +NTSTATUS UpdateCheckSilentThread( + _In_ PVOID Parameter + ) +{ + PPH_UPDATER_CONTEXT context = NULL; + ULONGLONG currentVersion = 0; + ULONGLONG latestVersion = 0; + + context = CreateUpdateContext(); + + __try + { + if (!LastUpdateCheckExpired()) + { + __leave; + } + + if (!QueryUpdateData(context, FALSE)) + { + if (!QueryUpdateData(context, TRUE)) + { + __leave; + } + } + + currentVersion = MAKEDLLVERULL( + context->CurrentMajorVersion, + context->CurrentMinorVersion, + 0, + context->CurrentRevisionVersion + ); + +#ifdef DEBUG_UPDATE + latestVersion = MAKEDLLVERULL( + 9999, + 9999, + 0, + 9999 + ); +#else + latestVersion = MAKEDLLVERULL( + context->MajorVersion, + context->MinorVersion, + 0, + context->RevisionVersion + ); +#endif + + if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2) + { + // If we're running on XP or Vista, we only support 2.x builds and below. + latestVersion = currentVersion; + } + + // Compare the current version against the latest available version + if (currentVersion < latestVersion) + { + // Don't spam the user the second they open PH, delay dialog creation for 3 seconds. + Sleep(3000); + + if (!UpdateDialogHandle) + { + // We have data we're going to cache and pass into the dialog + context->HaveData = TRUE; + + // Show the dialog asynchronously on a new thread. + ShowUpdateDialog(context); + } + } + } + __finally + { + // Check the dialog doesn't own the window context... + if (!context->HaveData) + { + FreeUpdateContext(context); + } + } + + return STATUS_SUCCESS; +} + +NTSTATUS UpdateCheckThread( + _In_ PVOID Parameter + ) +{ + PPH_UPDATER_CONTEXT context = NULL; + ULONGLONG currentVersion = 0; + ULONGLONG latestVersion = 0; + + context = (PPH_UPDATER_CONTEXT)Parameter; + + // Check if we have cached update data + if (!context->HaveData) + { + context->HaveData = QueryUpdateData(context, FALSE); + + if (!context->HaveData) + { + context->HaveData = QueryUpdateData(context, TRUE); + } + } + + // sanity check + if (!context->HaveData) + { + PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0); + return STATUS_SUCCESS; + } + + currentVersion = MAKEDLLVERULL( + context->CurrentMajorVersion, + context->CurrentMinorVersion, + 0, + context->CurrentRevisionVersion + ); + +#ifdef DEBUG_UPDATE + latestVersion = MAKEDLLVERULL( + 9999, + 9999, + 0, + 9999 + ); +#else + latestVersion = MAKEDLLVERULL( + context->MajorVersion, + context->MinorVersion, + 0, + context->RevisionVersion + ); +#endif + + if (WindowsVersion < WINDOWS_7 && context->MajorVersion != 2) + { + // If we're running on XP or Vista, we only support 2.x builds and below. + PostMessage(context->DialogHandle, PH_UPDATENOTSUPPORTED, 0, 0); + } + else if (currentVersion == latestVersion) + { + // User is running the latest version + PostMessage(context->DialogHandle, PH_UPDATEISCURRENT, 0, 0); + } + else if (currentVersion > latestVersion) + { + // User is running a newer version + PostMessage(context->DialogHandle, PH_UPDATENEWER, 0, 0); + } + else + { + // User is running an older version + PostMessage(context->DialogHandle, PH_UPDATEAVAILABLE, 0, 0); + } + + return STATUS_SUCCESS; +} + +NTSTATUS UpdateDownloadThread( + _In_ PVOID Parameter + ) +{ + BOOLEAN downloadSuccess = FALSE; + BOOLEAN hashSuccess = FALSE; + BOOLEAN verifySuccess = FALSE; + HANDLE tempFileHandle = NULL; + HINTERNET httpSessionHandle = NULL; + HINTERNET httpConnectionHandle = NULL; + HINTERNET httpRequestHandle = NULL; + PPH_STRING setupTempPath = NULL; + PPH_STRING downloadHostPath = NULL; + PPH_STRING downloadUrlPath = NULL; + PPH_STRING userAgentString = NULL; + PPH_STRING fullSetupPath = NULL; + PPH_STRING randomGuidString = NULL; + ULONG indexOfFileName = -1; + GUID randomGuid; + URL_COMPONENTS httpUrlComponents = { sizeof(URL_COMPONENTS) }; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; + + PPH_UPDATER_CONTEXT context = (PPH_UPDATER_CONTEXT)Parameter; + + __try + { + // Create a user agent string. + userAgentString = PhFormatString( + L"PH_%lu.%lu_%lu", + context->CurrentMajorVersion, + context->CurrentMinorVersion, + context->CurrentRevisionVersion + ); + if (PhIsNullOrEmptyString(userAgentString)) + __leave; + + // Allocate the GetTempPath buffer + setupTempPath = PhCreateStringEx(NULL, GetTempPath(0, NULL) * sizeof(WCHAR)); + if (PhIsNullOrEmptyString(setupTempPath)) + __leave; + + // Get the temp path + if (GetTempPath((ULONG)setupTempPath->Length / sizeof(WCHAR), setupTempPath->Buffer) == 0) + __leave; + if (PhIsNullOrEmptyString(setupTempPath)) + __leave; + + // Generate random guid for our directory path. + PhGenerateGuid(&randomGuid); + + if (randomGuidString = PhFormatGuid(&randomGuid)) + { + PPH_STRING guidSubString; + + // Strip the left and right curly brackets. + guidSubString = PhSubstring(randomGuidString, 1, randomGuidString->Length / sizeof(WCHAR) - 2); + + PhSwapReference(&randomGuidString, guidSubString); + } + + // Append the tempath to our string: %TEMP%RandomString\\processhacker-%lu.%lu-setup.exe + // Example: C:\\Users\\dmex\\AppData\\Temp\\ABCD\\processhacker-2.90-setup.exe + context->SetupFilePath = PhFormatString( + L"%s%s\\processhacker-%lu.%lu-setup.exe", + setupTempPath->Buffer, + randomGuidString->Buffer, + context->MajorVersion, + context->MinorVersion + ); + if (PhIsNullOrEmptyString(context->SetupFilePath)) + __leave; + + // Create the directory if it does not exist. + if (fullSetupPath = PhGetFullPath(context->SetupFilePath->Buffer, &indexOfFileName)) + { + PPH_STRING directoryPath; + + if (indexOfFileName == -1) + __leave; + + if (directoryPath = PhSubstring(fullSetupPath, 0, indexOfFileName)) + { + SHCreateDirectoryEx(NULL, directoryPath->Buffer, NULL); + PhDereferenceObject(directoryPath); + } + } + + // Create output file + if (!NT_SUCCESS(PhCreateFileWin32( + &tempFileHandle, + context->SetupFilePath->Buffer, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + __leave; + } + + // Set lengths to non-zero enabling these params to be cracked. + httpUrlComponents.dwSchemeLength = (ULONG)-1; + httpUrlComponents.dwHostNameLength = (ULONG)-1; + httpUrlComponents.dwUrlPathLength = (ULONG)-1; + + if (!WinHttpCrackUrl( + context->SetupFileDownloadUrl->Buffer, + (ULONG)context->SetupFileDownloadUrl->Length, + 0, + &httpUrlComponents + )) + { + __leave; + } + + // Create the Host string. + downloadHostPath = PhCreateStringEx( + httpUrlComponents.lpszHostName, + httpUrlComponents.dwHostNameLength * sizeof(WCHAR) + ); + if (PhIsNullOrEmptyString(downloadHostPath)) + __leave; + + // Create the Path string. + downloadUrlPath = PhCreateStringEx( + httpUrlComponents.lpszUrlPath, + httpUrlComponents.dwUrlPathLength * sizeof(WCHAR) + ); + if (PhIsNullOrEmptyString(downloadUrlPath)) + __leave; + + SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Connecting..."); + + // Query the current system proxy + WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); + + // Open the HTTP session with the system proxy configuration if available + if (!(httpSessionHandle = WinHttpOpen( + userAgentString->Buffer, + proxyConfig.lpszProxy != NULL ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + proxyConfig.lpszProxy, + proxyConfig.lpszProxyBypass, + 0 + ))) + { + __leave; + } + + if (WindowsVersion >= WINDOWS_8_1) + { + // Enable GZIP and DEFLATE support on Windows 8.1 and above using undocumented flags. + ULONG httpFlags = WINHTTP_DECOMPRESSION_FLAG_GZIP | WINHTTP_DECOMPRESSION_FLAG_DEFLATE; + + WinHttpSetOption( + httpSessionHandle, + WINHTTP_OPTION_DECOMPRESSION, + &httpFlags, + sizeof(ULONG) + ); + } + + if (!(httpConnectionHandle = WinHttpConnect( + httpSessionHandle, + downloadHostPath->Buffer, + httpUrlComponents.nScheme == INTERNET_SCHEME_HTTP ? INTERNET_DEFAULT_HTTP_PORT : INTERNET_DEFAULT_HTTPS_PORT, + 0 + ))) + { + __leave; + } + + if (!(httpRequestHandle = WinHttpOpenRequest( + httpConnectionHandle, + NULL, + downloadUrlPath->Buffer, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_REFRESH | (httpUrlComponents.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0) + ))) + { + __leave; + } + + SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Sending request..."); + + if (!WinHttpSendRequest( + httpRequestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, + 0 + )) + { + __leave; + } + + SetDlgItemText(context->DialogHandle, IDC_STATUS, L"Waiting for response..."); + + if (WinHttpReceiveResponse(httpRequestHandle, NULL)) + { + ULONG bytesDownloaded = 0; + ULONG downloadedBytes = 0; + ULONG contentLengthSize = sizeof(ULONG); + ULONG contentLength = 0; + BYTE buffer[PAGE_SIZE]; + BYTE hashBuffer[20]; + + PH_HASH_CONTEXT hashContext; + IO_STATUS_BLOCK isb; + + if (!WinHttpQueryHeaders( + httpRequestHandle, + WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &contentLength, + &contentLengthSize, + 0 + )) + { + __leave; + } + + // Initialize hash algorithm. + PhInitializeHash(&hashContext, Sha1HashAlgorithm); + + // Zero the buffer. + memset(buffer, 0, PAGE_SIZE); + + // Download the data. + while (WinHttpReadData(httpRequestHandle, buffer, PAGE_SIZE, &bytesDownloaded)) + { + // If we get zero bytes, the file was uploaded or there was an error + if (bytesDownloaded == 0) + break; + + // If the dialog was closed, just cleanup and exit + //if (context->UpdaterState == PhUpdateMaximum) + if (!UpdateDialogThreadHandle) + __leave; + + // Update the hash of bytes we downloaded. + PhUpdateHash(&hashContext, buffer, bytesDownloaded); + + // Write the downloaded bytes to disk. + if (!NT_SUCCESS(NtWriteFile( + tempFileHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + bytesDownloaded, + NULL, + NULL + ))) + { + __leave; + } + + downloadedBytes += (DWORD)isb.Information; + + // Check the number of bytes written are the same we downloaded. + if (bytesDownloaded != isb.Information) + __leave; + + // Update the GUI progress. + // TODO: Update on GUI thread. + { + //int percent = MulDiv(100, downloadedBytes, contentLength); + FLOAT percent = ((FLOAT)downloadedBytes / contentLength * 100); + PPH_STRING totalDownloaded = PhFormatSize(downloadedBytes, -1); + PPH_STRING totalLength = PhFormatSize(contentLength, -1); + + PPH_STRING dlLengthString = PhFormatString( + L"%s of %s (%.0f%%)", + totalDownloaded->Buffer, + totalLength->Buffer, + percent + ); + + // Update the progress bar position + SendMessage(context->ProgressHandle, PBM_SETPOS, (ULONG)percent, 0); + Static_SetText(context->StatusHandle, dlLengthString->Buffer); + + PhDereferenceObject(dlLengthString); + PhDereferenceObject(totalDownloaded); + PhDereferenceObject(totalLength); + } + } + + // Compute hash result (will fail if file not downloaded correctly). + if (PhFinalHash(&hashContext, &hashBuffer, 20, NULL)) + { + // Allocate our hash string, hex the final hash result in our hashBuffer. + PPH_STRING hexString = PhBufferToHexString(hashBuffer, 20); + + if (PhEqualString(hexString, context->Hash, TRUE)) + { + hashSuccess = TRUE; + } + + PhDereferenceObject(hexString); + } + } + + downloadSuccess = TRUE; + } + __finally + { + if (tempFileHandle) + NtClose(tempFileHandle); + + if (httpRequestHandle) + WinHttpCloseHandle(httpRequestHandle); + + if (httpConnectionHandle) + WinHttpCloseHandle(httpConnectionHandle); + + if (httpSessionHandle) + WinHttpCloseHandle(httpSessionHandle); + + PhClearReference(&randomGuidString); + PhClearReference(&fullSetupPath); + PhClearReference(&setupTempPath); + PhClearReference(&downloadHostPath); + PhClearReference(&downloadUrlPath); + PhClearReference(&userAgentString); + } + + if (WindowsVersion < WINDOWS_8) + { + // Disable signature checking on XP, Vista and Win7 due to SHA2 certificate issues. + verifySuccess = TRUE; + } + else + { + // Check the digital signature of the installer... + if (context->SetupFilePath && PhVerifyFile(context->SetupFilePath->Buffer, NULL) == VrTrusted) + { + verifySuccess = TRUE; + } + } + + if (downloadSuccess && hashSuccess && verifySuccess) + { + PostMessage(context->DialogHandle, PH_UPDATESUCCESS, 0, 0); + } + else if (downloadSuccess) + { + PostMessage(context->DialogHandle, PH_UPDATEFAILURE, verifySuccess, hashSuccess); + } + else + { + PostMessage(context->DialogHandle, PH_UPDATEISERRORED, 0, 0); + } + + return STATUS_SUCCESS; +} + +INT_PTR CALLBACK UpdaterWndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PPH_UPDATER_CONTEXT context = NULL; + + if (uMsg == WM_INITDIALOG) + { + context = (PPH_UPDATER_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PPH_UPDATER_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_NCDESTROY) + { + RemoveProp(hwndDlg, L"Context"); + FreeUpdateContext(context); + + PostQuitMessage(0); + } + } + + if (context == NULL) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + LOGFONT logFont; + HWND parentWindow = GetParent(hwndDlg); + + context->DialogHandle = hwndDlg; + context->StatusHandle = GetDlgItem(hwndDlg, IDC_STATUS); + context->ProgressHandle = GetDlgItem(hwndDlg, IDC_PROGRESS); + + if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logFont, 0)) + { + HDC hdc; + + if (hdc = GetDC(hwndDlg)) + { + // Create the font handle + context->FontHandle = CreateFont( + -MulDiv(-14, GetDeviceCaps(hdc, LOGPIXELSY), 72), + 0, + 0, + 0, + FW_MEDIUM, + FALSE, + FALSE, + FALSE, + ANSI_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + CLEARTYPE_QUALITY | ANTIALIASED_QUALITY, + DEFAULT_PITCH, + logFont.lfFaceName + ); + + ReleaseDC(hwndDlg, hdc); + } + } + + // Load the Process Hacker icon. + context->IconHandle = (HICON)LoadImage( + NtCurrentPeb()->ImageBaseAddress, + MAKEINTRESOURCE(PHAPP_IDI_PROCESSHACKER), + IMAGE_ICON, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON), + 0 + ); + + context->IconBitmap = PhIconToBitmap(context->IconHandle, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)); + + // Set the window icons + if (context->IconHandle) + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)context->IconHandle); + // Set the text font + if (context->FontHandle) + SetWindowFont(GetDlgItem(hwndDlg, IDC_MESSAGE), context->FontHandle, FALSE); + // Set the window image + if (context->IconBitmap) + SendMessage(GetDlgItem(hwndDlg, IDC_UPDATEICON), STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)context->IconBitmap); + + // Center the update window on PH if it's visible else we center on the desktop. + PhCenterWindow(hwndDlg, (IsWindowVisible(parentWindow) && !IsMinimized(parentWindow)) ? parentWindow : NULL); + + // Show new version info (from the background update check) + if (context->HaveData) + { + HANDLE updateCheckThread = NULL; + + // Create the update check thread. + if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context)) + NtClose(updateCheckThread); + } + } + break; + case WM_SHOWDIALOG: + { + if (IsMinimized(hwndDlg)) + ShowWindow(hwndDlg, SW_RESTORE); + else + ShowWindow(hwndDlg, SW_SHOW); + + SetForegroundWindow(hwndDlg); + } + break; + case WM_CTLCOLORBTN: + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + HWND hwndChild = (HWND)lParam; + + // Check for our static label and change the color. + if (GetWindowID(hwndChild) == IDC_MESSAGE) + { + SetTextColor(hDC, RGB(19, 112, 171)); + } + + // Set a transparent background for the control backcolor. + SetBkMode(hDC, TRANSPARENT); + + // set window background color. + return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); + } + break; + case WM_COMMAND: + { + switch (GET_WM_COMMAND_ID(wParam, lParam)) + { + case IDCANCEL: + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_DOWNLOAD: + { + switch (context->UpdaterState) + { + case PhUpdateDefault: + { + HANDLE updateCheckThread = NULL; + + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Checking for new releases..."); + SetDlgItemText(hwndDlg, IDC_RELDATE, L""); + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + + if (updateCheckThread = PhCreateThread(0, UpdateCheckThread, context)) + NtClose(updateCheckThread); + } + break; + case PhUpdateDownload: + { + // We can't download updates below Vista due to SHA2 and the + // WINHTTP_OPTION_SECURITY_FLAGS option doesn't work on XP for some reason. + // Just show the downloads page when there is a new release. + if (WindowsVersion >= WINDOWS_VISTA && PhInstalledUsingSetup()) + { + HANDLE downloadThreadHandle = NULL; + + // Disable the download button + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + + // Reset the progress bar (might be a download retry) + SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0); + + if (WindowsVersion >= WINDOWS_VISTA) + SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_NORMAL, 0); + + // Start file download thread + if (downloadThreadHandle = PhCreateThread(0, (PUSER_THREAD_START_ROUTINE)UpdateDownloadThread, context)) + NtClose(downloadThreadHandle); + } + else + { + // Let the user handle non-setup installation, show the homepage and close this dialog. + PhShellExecute(hwndDlg, L"http://processhacker.sourceforge.net/downloads.php", NULL); + DestroyWindow(hwndDlg); + } + } + break; + case PhUpdateInstall: + { + SHELLEXECUTEINFO info = { sizeof(SHELLEXECUTEINFO) }; + + if (PhIsNullOrEmptyString(context->SetupFilePath)) + break; + + info.lpFile = context->SetupFilePath->Buffer; + info.lpVerb = PhGetOwnTokenAttributes().Elevated ? NULL : L"runas"; + info.nShow = SW_SHOW; + info.hwnd = hwndDlg; + + ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle); + + if (!ShellExecuteEx(&info)) + { + // Install failed, cancel the shutdown. + ProcessHacker_CancelEarlyShutdown(PhMainWndHandle); + + // Set button text for next action + Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); + } + else + { + ProcessHacker_Destroy(PhMainWndHandle); + } + } + break; + } + } + break; + } + break; + } + break; + case PH_UPDATEAVAILABLE: + { + // Set updater state + context->UpdaterState = PhUpdateDownload; + + // Set the UI text + SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString( + L"Process Hacker %lu.%lu.%lu", + context->MajorVersion, + context->MinorVersion, + context->RevisionVersion + )->Buffer); + SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( + L"Released: %s", + context->RelDate->Buffer + )->Buffer); + SetDlgItemText(hwndDlg, IDC_STATUS, PhaFormatString( + L"Size: %s", + context->Size->Buffer + )->Buffer); + Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Download"); + + // Enable the controls + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); + Control_Visible(GetDlgItem(hwndDlg, IDC_PROGRESS), TRUE); + Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); + } + break; + case PH_UPDATEISCURRENT: + { + // Set updater state + context->UpdaterState = PhUpdateMaximum; + + // Set the UI text + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running the latest version."); + SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( + L"Stable release build: v%lu.%lu.%lu", + context->CurrentMajorVersion, + context->CurrentMinorVersion, + context->CurrentRevisionVersion + )->Buffer); + + // Disable the download button + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + // Enable the changelog link + Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); + } + break; + case PH_UPDATENEWER: + { + context->UpdaterState = PhUpdateMaximum; + + // Set the UI text + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"You're running a newer version!"); + SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( + L"Pre-release build: v%lu.%lu.%lu", + context->CurrentMajorVersion, + context->CurrentMinorVersion, + context->CurrentRevisionVersion + )->Buffer); + + // Disable the download button + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + // Disable the changelog link + Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), FALSE); + } + break; + case PH_UPDATESUCCESS: + { + context->UpdaterState = PhUpdateInstall; + + // If PH is not elevated, set the UAC shield for the install button as the setup requires elevation. + if (!PhGetOwnTokenAttributes().Elevated) + SendMessage(GetDlgItem(hwndDlg, IDC_DOWNLOAD), BCM_SETSHIELD, 0, TRUE); + + // Set the download result, don't include hash status since it succeeded. + SetDlgItemText(hwndDlg, IDC_STATUS, L"Click Install to continue update..."); + + // Set button text for next action + Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Install"); + // Enable the Download/Install button so the user can install the update + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); + } + break; + case PH_UPDATEFAILURE: + { + context->UpdaterState = PhUpdateDefault; + + if (WindowsVersion >= WINDOWS_VISTA) + SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETSTATE, PBST_ERROR, 0); + + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again..."); + SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates."); + + if ((BOOLEAN)wParam) + SetDlgItemText(hwndDlg, IDC_STATUS, L"Hash check failed."); + else if ((BOOLEAN)lParam) + SetDlgItemText(hwndDlg, IDC_STATUS, L"Signature check failed."); + + // Set button text for next action + Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); + // Enable the Install button + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); + // Hash failed, reset state to downloading so user can redownload the file. + } + break; + case PH_UPDATEISERRORED: + { + context->UpdaterState = PhUpdateDefault; + + SetDlgItemText(hwndDlg, IDC_MESSAGE, L"Please check for updates again..."); + SetDlgItemText(hwndDlg, IDC_RELDATE, L"An error was encountered while checking for updates."); + + Button_SetText(GetDlgItem(hwndDlg, IDC_DOWNLOAD), L"Retry"); + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), TRUE); + } + break; + case PH_UPDATENOTSUPPORTED: + { + // Set updater state + context->UpdaterState = PhUpdateMaximum; + + // Set the UI text + SetDlgItemText(hwndDlg, IDC_MESSAGE, PhaFormatString( + L"You're running the latest version: v%lu.%lu.%lu", + context->CurrentMajorVersion, + context->CurrentMinorVersion, + context->CurrentRevisionVersion + )->Buffer); + SetDlgItemText(hwndDlg, IDC_RELDATE, PhaFormatString( + L"v%lu.%lu.%lu is available for Windows 7 and above.", + context->MajorVersion, + context->MinorVersion, + context->RevisionVersion + )->Buffer); + + // Disable the download button + Button_Enable(GetDlgItem(hwndDlg, IDC_DOWNLOAD), FALSE); + // Enable the changelog link + Control_Visible(GetDlgItem(hwndDlg, IDC_INFOSYSLINK), TRUE); + } + break; + case WM_NOTIFY: + { + switch (((LPNMHDR)lParam)->code) + { + case NM_CLICK: // Mouse + case NM_RETURN: // Keyboard + { + // Launch the ReleaseNotes URL (if it exists) with the default browser + if (!PhIsNullOrEmptyString(context->ReleaseNotesUrl)) + PhShellExecute(hwndDlg, context->ReleaseNotesUrl->Buffer, NULL); + } + break; + } + } + break; + } + + return FALSE; +} + +NTSTATUS ShowUpdateDialogThread( + _In_ PVOID Parameter + ) +{ + BOOL result; + MSG message; + PH_AUTO_POOL autoPool; + PPH_UPDATER_CONTEXT context = NULL; + + if (Parameter) + context = (PPH_UPDATER_CONTEXT)Parameter; + else + context = CreateUpdateContext(); + + PhInitializeAutoPool(&autoPool); + + UpdateDialogHandle = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_UPDATE), + PhMainWndHandle, + UpdaterWndProc, + (LPARAM)context + ); + + PhSetEvent(&InitializedEvent); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (!IsDialogMessage(UpdateDialogHandle, &message)) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + PhResetEvent(&InitializedEvent); + + if (UpdateDialogThreadHandle) + { + NtClose(UpdateDialogThreadHandle); + UpdateDialogThreadHandle = NULL; + } + + return STATUS_SUCCESS; +} + +VOID ShowUpdateDialog( + _In_opt_ PPH_UPDATER_CONTEXT Context + ) +{ + if (!UpdateDialogThreadHandle) + { + if (!(UpdateDialogThreadHandle = PhCreateThread(0, ShowUpdateDialogThread, Context))) + { + PhShowStatus(PhMainWndHandle, L"Unable to create the updater window.", 0, GetLastError()); + return; + } + + PhWaitForEvent(&InitializedEvent, NULL); + } + + PostMessage(UpdateDialogHandle, WM_SHOWDIALOG, 0, 0); +} + +VOID StartInitialCheck( + VOID + ) +{ + HANDLE silentCheckThread = NULL; + + if (silentCheckThread = PhCreateThread(0, UpdateCheckSilentThread, NULL)) + NtClose(silentCheckThread); +} \ No newline at end of file diff --git a/plugins/Updater/updater.h b/plugins/Updater/updater.h new file mode 100644 index 0000000..1af96f8 --- /dev/null +++ b/plugins/Updater/updater.h @@ -0,0 +1,130 @@ +/* + * Process Hacker Plugins - + * Update Checker Plugin + * + * Copyright (C) 2011-2015 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef __UPDATER_H__ +#define __UPDATER_H__ + +#define CINTERFACE +#define COBJMACROS +#define INITGUID +#include +#include +#include +#include +#include +#include + +#include "resource.h" + +// Force update checks to succeed with debug builds +//#define DEBUG_UPDATE +#define UPDATE_MENUITEM 1001 +#define PH_UPDATEISERRORED (WM_APP + 101) +#define PH_UPDATEAVAILABLE (WM_APP + 102) +#define PH_UPDATEISCURRENT (WM_APP + 103) +#define PH_UPDATENEWER (WM_APP + 104) +#define PH_UPDATESUCCESS (WM_APP + 105) +#define PH_UPDATEFAILURE (WM_APP + 106) +#define PH_UPDATENOTSUPPORTED (WM_APP + 107) +#define WM_SHOWDIALOG (WM_APP + 150) + +#define PLUGIN_NAME L"ProcessHacker.UpdateChecker" +#define SETTING_NAME_AUTO_CHECK (PLUGIN_NAME L".PromptStart") +#define SETTING_NAME_LAST_CHECK (PLUGIN_NAME L".LastUpdateCheckTime") + +#define MAKEDLLVERULL(major, minor, build, revision) \ + (((ULONGLONG)(major) << 48) | \ + ((ULONGLONG)(minor) << 32) | \ + ((ULONGLONG)(build) << 16) | \ + ((ULONGLONG)(revision) << 0)) + +#define Control_Visible(hWnd, visible) \ + ShowWindow(hWnd, visible ? SW_SHOW : SW_HIDE); + +extern PPH_PLUGIN PluginInstance; + +typedef enum _PH_UPDATER_STATE +{ + PhUpdateDefault = 0, + PhUpdateDownload = 1, + PhUpdateInstall = 2, + PhUpdateMaximum = 3 +} PH_UPDATER_STATE; + +typedef struct _PH_UPDATER_CONTEXT +{ + BOOLEAN HaveData; + PH_UPDATER_STATE UpdaterState; + HBITMAP IconBitmap; + HICON IconHandle; + HFONT FontHandle; + HWND StatusHandle; + HWND ProgressHandle; + HWND DialogHandle; + + ULONG MinorVersion; + ULONG MajorVersion; + ULONG RevisionVersion; + ULONG CurrentMinorVersion; + ULONG CurrentMajorVersion; + ULONG CurrentRevisionVersion; + PPH_STRING Version; + PPH_STRING RevVersion; + PPH_STRING RelDate; + PPH_STRING Size; + PPH_STRING Hash; + PPH_STRING ReleaseNotesUrl; + PPH_STRING SetupFileDownloadUrl; + PPH_STRING SetupFilePath; +} PH_UPDATER_CONTEXT, *PPH_UPDATER_CONTEXT; + +VOID ShowUpdateDialog( + _In_opt_ PPH_UPDATER_CONTEXT Context + ); + +VOID StartInitialCheck( + VOID + ); + +PPH_STRING PhGetOpaqueXmlNodeText( + _In_ mxml_node_t *xmlNode + ); + +BOOL PhInstalledUsingSetup( + VOID + ); + +INT_PTR CALLBACK UpdaterWndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#endif \ No newline at end of file diff --git a/plugins/UserNotes/CHANGELOG.txt b/plugins/UserNotes/CHANGELOG.txt new file mode 100644 index 0000000..8cf6470 --- /dev/null +++ b/plugins/UserNotes/CHANGELOG.txt @@ -0,0 +1,21 @@ +1.6 + * Added "Collapse by Default" option for processes + +1.5 + * Added individual process highlighting support + +1.4 + * Added tray icon mini info window support + +1.3 + * Added ability to save I/O priority + +1.2 + * Fixed bug where process priorities were not actually saved + +1.1 + * Added ability to save process priority + * Added "Only for processes with the same command line" option for process comments + +1.0 + * Initial release diff --git a/plugins/UserNotes/UserNotes.rc b/plugins/UserNotes/UserNotes.rc new file mode 100644 index 0000000..daa0f6d --- /dev/null +++ b/plugins/UserNotes/UserNotes.rc @@ -0,0 +1,182 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS DIALOGEX 0, 0, 316, 71 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Options" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Database location:",IDC_STATIC,7,9,61,8 + EDITTEXT IDC_DATABASE,72,8,183,12,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_BROWSE,259,7,50,14 + LTEXT "If a relative path is specified, it is relative to Process Hacker's directory. Environment variables can be used. Changes will take place after Process Hacker is restarted.",IDC_STATIC,7,26,302,19 + DEFPUSHBUTTON "OK",IDOK,205,50,50,14 + PUSHBUTTON "Cancel",IDCANCEL,259,50,50,14 +END + +IDD_PROCCOMMENT DIALOGEX 0, 0, 260, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Comment" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_COMMENT,7,7,246,229,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Revert",IDC_REVERT,7,239,50,14,WS_DISABLED + CONTROL "Only for processes with the same command line",IDC_MATCHCOMMANDLINE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,86,241,167,10 +END + +IDD_SRVCOMMENT DIALOGEX 0, 0, 252, 179 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Comment" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_COMMENT,7,7,238,165,ES_MULTILINE | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 309 + TOPMARGIN, 7 + BOTTOMMARGIN, 64 + END + + IDD_PROCCOMMENT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 253 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END + + IDD_SRVCOMMENT, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 245 + TOPMARGIN, 7 + BOTTOMMARGIN, 172 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,6,0,0 + PRODUCTVERSION 1,6,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "User Notes plugin for Process Hacker" + VALUE "FileVersion", "1.6" + VALUE "InternalName", "UserNotes" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "UserNotes.dll" + VALUE "ProductName", "User Notes plugin for Process Hacker" + VALUE "ProductVersion", "1.6" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/UserNotes/UserNotes.vcxproj b/plugins/UserNotes/UserNotes.vcxproj new file mode 100644 index 0000000..78c4641 --- /dev/null +++ b/plugins/UserNotes/UserNotes.vcxproj @@ -0,0 +1,102 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7C38D0AA-572C-4D75-8E4E-D68AF3C051AF} + UserNotes + Win32Proj + UserNotes + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + uxtheme.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/UserNotes/UserNotes.vcxproj.filters b/plugins/UserNotes/UserNotes.vcxproj.filters new file mode 100644 index 0000000..dd1cb20 --- /dev/null +++ b/plugins/UserNotes/UserNotes.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/plugins/UserNotes/db.c b/plugins/UserNotes/db.c new file mode 100644 index 0000000..14b886b --- /dev/null +++ b/plugins/UserNotes/db.c @@ -0,0 +1,457 @@ +/* + * Process Hacker User Notes - + * database functions + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "usernotes.h" + +BOOLEAN NTAPI ObjectDbEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG NTAPI ObjectDbHashFunction( + _In_ PVOID Entry + ); + +PPH_HASHTABLE ObjectDb; +PH_QUEUED_LOCK ObjectDbLock = PH_QUEUED_LOCK_INIT; +PPH_STRING ObjectDbPath; + +VOID InitializeDb( + VOID + ) +{ + ObjectDb = PhCreateHashtable( + sizeof(PDB_OBJECT), + ObjectDbEqualFunction, + ObjectDbHashFunction, + 64 + ); +} + +BOOLEAN NTAPI ObjectDbEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PDB_OBJECT object1 = *(PDB_OBJECT *)Entry1; + PDB_OBJECT object2 = *(PDB_OBJECT *)Entry2; + + return object1->Tag == object2->Tag && PhEqualStringRef(&object1->Key, &object2->Key, TRUE); +} + +ULONG NTAPI ObjectDbHashFunction( + _In_ PVOID Entry + ) +{ + PDB_OBJECT object = *(PDB_OBJECT *)Entry; + + return object->Tag + PhHashStringRef(&object->Key, TRUE); +} + +ULONG GetNumberOfDbObjects( + VOID + ) +{ + return ObjectDb->Count; +} + +VOID LockDb( + VOID + ) +{ + PhAcquireQueuedLockExclusive(&ObjectDbLock); +} + +VOID UnlockDb( + VOID + ) +{ + PhReleaseQueuedLockExclusive(&ObjectDbLock); +} + +PDB_OBJECT FindDbObject( + _In_ ULONG Tag, + _In_ PPH_STRINGREF Name + ) +{ + DB_OBJECT lookupObject; + PDB_OBJECT lookupObjectPtr; + PDB_OBJECT *objectPtr; + + lookupObject.Tag = Tag; + lookupObject.Key = *Name; + lookupObjectPtr = &lookupObject; + + objectPtr = PhFindEntryHashtable(ObjectDb, &lookupObjectPtr); + + if (objectPtr) + return *objectPtr; + else + return NULL; +} + +PDB_OBJECT CreateDbObject( + _In_ ULONG Tag, + _In_ PPH_STRINGREF Name, + _In_opt_ PPH_STRING Comment + ) +{ + PDB_OBJECT object; + BOOLEAN added; + PDB_OBJECT *realObject; + + object = PhAllocate(sizeof(DB_OBJECT)); + memset(object, 0, sizeof(DB_OBJECT)); + object->Tag = Tag; + object->Key = *Name; + object->BackColor = ULONG_MAX; + + realObject = PhAddEntryHashtableEx(ObjectDb, &object, &added); + + if (added) + { + object->Name = PhCreateStringEx(Name->Buffer, Name->Length); + object->Key = object->Name->sr; + + if (Comment) + PhSetReference(&object->Comment, Comment); + else + object->Comment = PhReferenceEmptyString(); + } + else + { + PhFree(object); + object = *realObject; + + if (Comment) + PhSwapReference(&object->Comment, Comment); + } + + return object; +} + +VOID DeleteDbObject( + _In_ PDB_OBJECT Object + ) +{ + PhRemoveEntryHashtable(ObjectDb, &Object); + + PhDereferenceObject(Object->Name); + PhDereferenceObject(Object->Comment); + PhFree(Object); +} + +VOID SetDbPath( + _In_ PPH_STRING Path + ) +{ + PhSwapReference(&ObjectDbPath, Path); +} + +mxml_type_t MxmlLoadCallback( + _In_ mxml_node_t *node + ) +{ + return MXML_OPAQUE; +} + +PPH_STRING GetOpaqueXmlNodeText( + _In_ mxml_node_t *node + ) +{ + if (node->child && node->child->type == MXML_OPAQUE && node->child->value.opaque) + { + return PhConvertUtf8ToUtf16(node->child->value.opaque); + } + else + { + return PhReferenceEmptyString(); + } +} + +NTSTATUS LoadDb( + VOID + ) +{ + NTSTATUS status; + HANDLE fileHandle; + LARGE_INTEGER fileSize; + mxml_node_t *topNode; + mxml_node_t *currentNode; + + status = PhCreateFileWin32( + &fileHandle, + ObjectDbPath->Buffer, + FILE_GENERIC_READ, + 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + return status; + + if (NT_SUCCESS(PhGetFileSize(fileHandle, &fileSize)) && fileSize.QuadPart == 0) + { + // A blank file is OK. There are no objects to load. + NtClose(fileHandle); + return status; + } + + topNode = mxmlLoadFd(NULL, fileHandle, MxmlLoadCallback); + NtClose(fileHandle); + + if (!topNode) + return STATUS_FILE_CORRUPT_ERROR; + + if (topNode->type != MXML_ELEMENT) + { + mxmlDelete(topNode); + return STATUS_FILE_CORRUPT_ERROR; + } + + LockDb(); + + for (currentNode = topNode->child; currentNode; currentNode = currentNode->next) + { + PDB_OBJECT object = NULL; + PPH_STRING tag = NULL; + PPH_STRING name = NULL; + PPH_STRING priorityClass = NULL; + PPH_STRING ioPriorityPlusOne = NULL; + PPH_STRING comment = NULL; + PPH_STRING backColor = NULL; + PPH_STRING collapse = NULL; + + if (currentNode->type == MXML_ELEMENT && + currentNode->value.element.num_attrs >= 2) + { + for (INT i = 0; i < currentNode->value.element.num_attrs; i++) + { + if (_stricmp(currentNode->value.element.attrs[i].name, "tag") == 0) + PhMoveReference(&tag, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + else if (_stricmp(currentNode->value.element.attrs[i].name, "name") == 0) + PhMoveReference(&name, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + else if (_stricmp(currentNode->value.element.attrs[i].name, "priorityclass") == 0) + PhMoveReference(&priorityClass, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + else if (_stricmp(currentNode->value.element.attrs[i].name, "iopriorityplusone") == 0) + PhMoveReference(&ioPriorityPlusOne, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + else if (_stricmp(currentNode->value.element.attrs[i].name, "backcolor") == 0) + PhMoveReference(&backColor, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + else if (_stricmp(currentNode->value.element.attrs[i].name, "collapse") == 0) + PhMoveReference(&collapse, PhConvertUtf8ToUtf16(currentNode->value.element.attrs[i].value)); + } + } + + comment = GetOpaqueXmlNodeText(currentNode); + + if (tag && name && comment) + { + ULONG64 tagInteger; + ULONG64 priorityClassInteger = 0; + ULONG64 ioPriorityPlusOneInteger = 0; + + PhStringToInteger64(&tag->sr, 10, &tagInteger); + + if (priorityClass) + PhStringToInteger64(&priorityClass->sr, 10, &priorityClassInteger); + if (ioPriorityPlusOne) + PhStringToInteger64(&ioPriorityPlusOne->sr, 10, &ioPriorityPlusOneInteger); + + object = CreateDbObject((ULONG)tagInteger, &name->sr, comment); + object->PriorityClass = (ULONG)priorityClassInteger; + object->IoPriorityPlusOne = (ULONG)ioPriorityPlusOneInteger; + } + + // NOTE: These items are handled separately to maintain compatibility with previous versions of the database. + if (object && backColor) + { + ULONG64 backColorInteger = ULONG_MAX; + + PhStringToInteger64(&backColor->sr, 10, &backColorInteger); + + object->BackColor = (COLORREF)backColorInteger; + } + + if (object && collapse) + { + ULONG64 collapseInteger = 0; + + PhStringToInteger64(&collapse->sr, 10, &collapseInteger); + + object->Collapse = !!collapseInteger; + } + + PhClearReference(&tag); + PhClearReference(&name); + PhClearReference(&priorityClass); + PhClearReference(&ioPriorityPlusOne); + PhClearReference(&comment); + PhClearReference(&backColor); + PhClearReference(&collapse); + } + + UnlockDb(); + + mxmlDelete(topNode); + + return STATUS_SUCCESS; +} + +char *MxmlSaveCallback( + _In_ mxml_node_t *node, + _In_ int position + ) +{ + if (PhEqualBytesZ(node->value.element.name, "object", TRUE)) + { + if (position == MXML_WS_BEFORE_OPEN) + return " "; + else if (position == MXML_WS_AFTER_CLOSE) + return "\r\n"; + } + else if (PhEqualBytesZ(node->value.element.name, "objects", TRUE)) + { + if (position == MXML_WS_AFTER_OPEN) + return "\r\n"; + } + + return NULL; +} + +PPH_BYTES StringRefToUtf8( + _In_ PPH_STRINGREF String + ) +{ + return PH_AUTO(PhConvertUtf16ToUtf8Ex(String->Buffer, String->Length)); +} + +mxml_node_t *CreateObjectElement( + _Inout_ mxml_node_t *ParentNode, + _In_ PPH_STRINGREF Tag, + _In_ PPH_STRINGREF Name, + _In_ PPH_STRINGREF PriorityClass, + _In_ PPH_STRINGREF IoPriorityPlusOne, + _In_ PPH_STRINGREF Comment, + _In_ PPH_STRINGREF BackColor, + _In_ PPH_STRINGREF Collapse + ) +{ + mxml_node_t *objectNode; + mxml_node_t *textNode; + + // Create the setting element. + objectNode = mxmlNewElement(ParentNode, "object"); + + // Set the attributes. + mxmlElementSetAttr(objectNode, "tag", StringRefToUtf8(Tag)->Buffer); + mxmlElementSetAttr(objectNode, "name", StringRefToUtf8(Name)->Buffer); + mxmlElementSetAttr(objectNode, "priorityclass", StringRefToUtf8(PriorityClass)->Buffer); + mxmlElementSetAttr(objectNode, "iopriorityplusone", StringRefToUtf8(IoPriorityPlusOne)->Buffer); + mxmlElementSetAttr(objectNode, "backcolor", StringRefToUtf8(BackColor)->Buffer); + mxmlElementSetAttr(objectNode, "collapse", StringRefToUtf8(Collapse)->Buffer); + + // Set the value. + textNode = mxmlNewOpaque(objectNode, StringRefToUtf8(Comment)->Buffer); + + return objectNode; +} + +PPH_STRING UInt64ToBase10String( + _In_ ULONG64 Integer + ) +{ + return PH_AUTO(PhIntegerToString64(Integer, 10, FALSE)); +} + +NTSTATUS SaveDb( + VOID + ) +{ + PH_AUTO_POOL autoPool; + NTSTATUS status; + HANDLE fileHandle; + mxml_node_t *topNode; + ULONG enumerationKey = 0; + PDB_OBJECT *object; + + PhInitializeAutoPool(&autoPool); + + topNode = mxmlNewElement(MXML_NO_PARENT, "objects"); + + LockDb(); + + while (PhEnumHashtable(ObjectDb, (PVOID*)&object, &enumerationKey)) + { + CreateObjectElement( + topNode, + &UInt64ToBase10String((*object)->Tag)->sr, + &(*object)->Name->sr, + &UInt64ToBase10String((*object)->PriorityClass)->sr, + &UInt64ToBase10String((*object)->IoPriorityPlusOne)->sr, + &(*object)->Comment->sr, + &UInt64ToBase10String((*object)->BackColor)->sr, + &UInt64ToBase10String((*object)->Collapse)->sr + ); + PhDrainAutoPool(&autoPool); + } + + UnlockDb(); + + // Create the directory if it does not exist. + { + PPH_STRING fullPath; + ULONG indexOfFileName; + + if (fullPath = PH_AUTO(PhGetFullPath(ObjectDbPath->Buffer, &indexOfFileName))) + { + if (indexOfFileName != -1) + SHCreateDirectoryEx(NULL, PhaSubstring(fullPath, 0, indexOfFileName)->Buffer, NULL); + } + } + + PhDeleteAutoPool(&autoPool); + + status = PhCreateFileWin32( + &fileHandle, + ObjectDbPath->Buffer, + FILE_GENERIC_WRITE, + 0, + FILE_SHARE_READ, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ); + + if (!NT_SUCCESS(status)) + { + mxmlDelete(topNode); + return status; + } + + mxmlSaveFd(topNode, fileHandle, MxmlSaveCallback); + mxmlDelete(topNode); + NtClose(fileHandle); + + return STATUS_SUCCESS; +} diff --git a/plugins/UserNotes/db.h b/plugins/UserNotes/db.h new file mode 100644 index 0000000..d8b44a5 --- /dev/null +++ b/plugins/UserNotes/db.h @@ -0,0 +1,91 @@ +/* + * Process Hacker User Notes - + * database functions + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef DB_H +#define DB_H + +#define PLUGIN_NAME L"ProcessHacker.UserNotes" +#define SETTING_NAME_DATABASE_PATH (PLUGIN_NAME L".DatabasePath") +#define SETTING_NAME_CUSTOM_COLOR_LIST (PLUGIN_NAME L".ColorCustomList") + +#define FILE_TAG 1 +#define SERVICE_TAG 2 +#define COMMAND_LINE_TAG 3 + +typedef struct _DB_OBJECT +{ + ULONG Tag; + PH_STRINGREF Key; + + PPH_STRING Name; + PPH_STRING Comment; + ULONG PriorityClass; + ULONG IoPriorityPlusOne; + COLORREF BackColor; + BOOLEAN Collapse; +} DB_OBJECT, *PDB_OBJECT; + +VOID InitializeDb( + VOID + ); + +ULONG GetNumberOfDbObjects( + VOID + ); + +VOID LockDb( + VOID + ); + +VOID UnlockDb( + VOID + ); + +PDB_OBJECT FindDbObject( + _In_ ULONG Tag, + _In_ PPH_STRINGREF Name + ); + +PDB_OBJECT CreateDbObject( + _In_ ULONG Tag, + _In_ PPH_STRINGREF Name, + _In_opt_ PPH_STRING Comment + ); + +VOID DeleteDbObject( + _In_ PDB_OBJECT Object + ); + +VOID SetDbPath( + _In_ PPH_STRING Path + ); + +NTSTATUS LoadDb( + VOID + ); + +NTSTATUS SaveDb( + VOID + ); + +#endif diff --git a/plugins/UserNotes/main.c b/plugins/UserNotes/main.c new file mode 100644 index 0000000..d3b9377 --- /dev/null +++ b/plugins/UserNotes/main.c @@ -0,0 +1,1717 @@ +/* + * Process Hacker User Notes - + * main program + * + * Copyright (C) 2011-2016 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "usernotes.h" +#include +#include + +VOID SearchChangedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +static PPH_PLUGIN PluginInstance; +static PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginUnloadCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +static PH_CALLBACK_REGISTRATION PluginMenuHookCallbackRegistration; +static PH_CALLBACK_REGISTRATION TreeNewMessageCallbackRegistration; +static PH_CALLBACK_REGISTRATION MainWindowShowingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessPropertiesInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ServicePropertiesInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION GetProcessHighlightingColorCallbackRegistration; +static PH_CALLBACK_REGISTRATION ServiceTreeNewInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION MiListSectionMenuInitializingCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessModifiedCallbackRegistration; +static PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration; +static PH_CALLBACK_REGISTRATION SearchChangedRegistration; + +static PTOOLSTATUS_INTERFACE ToolStatusInterface = NULL; + +HWND ProcessTreeNewHandle; +LIST_ENTRY ProcessListHead = { &ProcessListHead, &ProcessListHead }; +PH_QUEUED_LOCK ProcessListLock = PH_QUEUED_LOCK_INIT; + +HWND ServiceTreeNewHandle; +LIST_ENTRY ServiceListHead = { &ServiceListHead, &ServiceListHead }; +PH_QUEUED_LOCK ServiceListLock = PH_QUEUED_LOCK_INIT; + +static COLORREF ProcessCustomColors[16] = { 0 }; + +BOOLEAN MatchDbObjectIntent( + _In_ PDB_OBJECT Object, + _In_ ULONG Intent + ) +{ + return (!(Intent & INTENT_PROCESS_COMMENT) || Object->Comment->Length != 0) && + (!(Intent & INTENT_PROCESS_PRIORITY_CLASS) || Object->PriorityClass != 0) && + (!(Intent & INTENT_PROCESS_IO_PRIORITY) || Object->IoPriorityPlusOne != 0) && + (!(Intent & INTENT_PROCESS_HIGHLIGHT) || Object->BackColor != ULONG_MAX) && + (!(Intent & INTENT_PROCESS_COLLAPSE) || Object->Collapse); +} + +PDB_OBJECT FindDbObjectForProcess( + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ ULONG Intent + ) +{ + PDB_OBJECT object; + + if ( + ProcessItem->CommandLine && + (object = FindDbObject(COMMAND_LINE_TAG, &ProcessItem->CommandLine->sr)) && + MatchDbObjectIntent(object, Intent) + ) + { + return object; + } + + if ( + (object = FindDbObject(FILE_TAG, &ProcessItem->ProcessName->sr)) && + MatchDbObjectIntent(object, Intent) + ) + { + return object; + } + + return NULL; +} + +VOID DeleteDbObjectForProcessIfUnused( + _In_ PDB_OBJECT Object + ) +{ + if ( + Object->Comment->Length == 0 && + Object->PriorityClass == 0 && + Object->IoPriorityPlusOne == 0 && + Object->BackColor == ULONG_MAX && + !Object->Collapse + ) + { + DeleteDbObject(Object); + } +} + +VOID LoadCustomColors( + VOID + ) +{ + PPH_STRING settingsString; + PH_STRINGREF remaining; + PH_STRINGREF part; + + settingsString = PhaGetStringSetting(SETTING_NAME_CUSTOM_COLOR_LIST); + remaining = settingsString->sr; + + if (remaining.Length == 0) + return; + + for (ULONG i = 0; i < ARRAYSIZE(ProcessCustomColors); i++) + { + ULONG64 integer = 0; + + if (remaining.Length == 0) + break; + + PhSplitStringRefAtChar(&remaining, ',', &part, &remaining); + + if (PhStringToInteger64(&part, 10, &integer)) + { + ProcessCustomColors[i] = (COLORREF)integer; + } + } +} + +PPH_STRING SaveCustomColors( + VOID + ) +{ + PH_STRING_BUILDER stringBuilder; + + PhInitializeStringBuilder(&stringBuilder, 100); + + for (ULONG i = 0; i < ARRAYSIZE(ProcessCustomColors); i++) + { + PhAppendFormatStringBuilder( + &stringBuilder, + L"%lu,", + ProcessCustomColors[i] + ); + } + + if (stringBuilder.String->Length != 0) + PhRemoveEndStringBuilder(&stringBuilder, 1); + + return PhFinalStringBuilderString(&stringBuilder); +} + +IO_PRIORITY_HINT GetProcessIoPriority( + _In_ HANDLE ProcessId + ) +{ + HANDLE processHandle; + IO_PRIORITY_HINT ioPriority = -1; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + ProcessQueryAccess, + ProcessId + ))) + { + if (!NT_SUCCESS(PhGetProcessIoPriority( + processHandle, + &ioPriority + ))) + { + ioPriority = -1; + } + + NtClose(processHandle); + } + + return ioPriority; +} + +ULONG GetPriorityClassFromId( + _In_ ULONG Id + ) +{ + switch (Id) + { + case PHAPP_ID_PRIORITY_REALTIME: + return PROCESS_PRIORITY_CLASS_REALTIME; + case PHAPP_ID_PRIORITY_HIGH: + return PROCESS_PRIORITY_CLASS_HIGH; + case PHAPP_ID_PRIORITY_ABOVENORMAL: + return PROCESS_PRIORITY_CLASS_ABOVE_NORMAL; + case PHAPP_ID_PRIORITY_NORMAL: + return PROCESS_PRIORITY_CLASS_NORMAL; + case PHAPP_ID_PRIORITY_BELOWNORMAL: + return PROCESS_PRIORITY_CLASS_BELOW_NORMAL; + case PHAPP_ID_PRIORITY_IDLE: + return PROCESS_PRIORITY_CLASS_IDLE; + } + + return 0; +} + +ULONG GetIoPriorityFromId( + _In_ ULONG Id + ) +{ + switch (Id) + { + case PHAPP_ID_IOPRIORITY_VERYLOW: + return 0; + case PHAPP_ID_IOPRIORITY_LOW: + return 1; + case PHAPP_ID_IOPRIORITY_NORMAL: + return 2; + case PHAPP_ID_IOPRIORITY_HIGH: + return 3; + } + + return -1; +} + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN toolStatusPlugin; + PPH_STRING path; + + if (toolStatusPlugin = PhFindPlugin(TOOLSTATUS_PLUGIN_NAME)) + { + ToolStatusInterface = PhGetPluginInformation(toolStatusPlugin)->Interface; + + if (ToolStatusInterface->Version < TOOLSTATUS_INTERFACE_VERSION) + ToolStatusInterface = NULL; + } + + path = PhaGetStringSetting(SETTING_NAME_DATABASE_PATH); + path = PH_AUTO(PhExpandEnvironmentStrings(&path->sr)); + + LoadCustomColors(); + + if (RtlDetermineDosPathNameType_U(path->Buffer) == RtlPathTypeRelative) + { + PPH_STRING directory; + + directory = PH_AUTO(PhGetApplicationDirectory()); + path = PH_AUTO(PhConcatStringRef2(&directory->sr, &path->sr)); + } + + SetDbPath(path); + LoadDb(); +} + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + SaveDb(); +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + DialogBox( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_OPTIONS), + (HWND)Parameter, + OptionsDlgProc + ); +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + PPH_PROCESS_ITEM processItem = PhGetSelectedProcessItem(); + PDB_OBJECT object; + + if (!processItem) + return; + + switch (menuItem->Id) + { + case PROCESS_PRIORITY_SAVE_ID: + { + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->PriorityClass != 0) + { + object->PriorityClass = 0; + DeleteDbObjectForProcessIfUnused(object); + } + else + { + object = CreateDbObject(FILE_TAG, &processItem->ProcessName->sr, NULL); + object->PriorityClass = processItem->PriorityClass; + } + + UnlockDb(); + SaveDb(); + } + break; + case PROCESS_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID: + { + if (processItem->CommandLine) + { + LockDb(); + + if ((object = FindDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr)) && object->PriorityClass != 0) + { + object->PriorityClass = 0; + DeleteDbObjectForProcessIfUnused(object); + } + else + { + object = CreateDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr, NULL); + object->PriorityClass = processItem->PriorityClass; + } + + UnlockDb(); + SaveDb(); + } + } + break; + case PROCESS_IO_PRIORITY_SAVE_ID: + { + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->IoPriorityPlusOne != 0) + { + object->IoPriorityPlusOne = 0; + DeleteDbObjectForProcessIfUnused(object); + } + else + { + object = CreateDbObject(FILE_TAG, &processItem->ProcessName->sr, NULL); + object->IoPriorityPlusOne = GetProcessIoPriority(processItem->ProcessId) + 1; + } + + UnlockDb(); + SaveDb(); + } + break; + case PROCESS_IO_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID: + { + if (processItem->CommandLine) + { + LockDb(); + + if ((object = FindDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr)) && object->IoPriorityPlusOne != 0) + { + object->IoPriorityPlusOne = 0; + DeleteDbObjectForProcessIfUnused(object); + } + else + { + object = CreateDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr, NULL); + object->IoPriorityPlusOne = GetProcessIoPriority(processItem->ProcessId) + 1; + } + + UnlockDb(); + SaveDb(); + } + } + break; + case PROCESS_HIGHLIGHT_ID: + { + BOOLEAN highlightPresent = (BOOLEAN)menuItem->Context; + + if (!highlightPresent) + { + CHOOSECOLOR chooseColor = { sizeof(CHOOSECOLOR) }; + chooseColor.hwndOwner = PhMainWndHandle; + chooseColor.lpCustColors = ProcessCustomColors; + chooseColor.lpfnHook = ColorDlgHookProc; + chooseColor.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_SOLIDCOLOR | CC_ENABLEHOOK; + + if (ChooseColor(&chooseColor)) + { + PhSetStringSetting2(SETTING_NAME_CUSTOM_COLOR_LIST, &PH_AUTO_T(PH_STRING, SaveCustomColors())->sr); + + LockDb(); + + object = CreateDbObject(FILE_TAG, &processItem->ProcessName->sr, NULL); + object->BackColor = chooseColor.rgbResult; + + UnlockDb(); + SaveDb(); + } + } + else + { + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->BackColor != ULONG_MAX) + { + object->BackColor = ULONG_MAX; + DeleteDbObjectForProcessIfUnused(object); + } + + UnlockDb(); + SaveDb(); + } + + PhInvalidateAllProcessNodes(); + } + break; + case PROCESS_COLLAPSE_ID: + { + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->Collapse) + { + object->Collapse = FALSE; + DeleteDbObjectForProcessIfUnused(object); + } + else + { + object = CreateDbObject(FILE_TAG, &processItem->ProcessName->sr, NULL); + object->Collapse = TRUE; + } + + UnlockDb(); + SaveDb(); + } + break; + } +} + +VOID NTAPI MenuHookCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_HOOK_INFORMATION menuHookInfo = Parameter; + ULONG id = menuHookInfo->SelectedItem->Id; + + switch (id) + { + case PHAPP_ID_PRIORITY_REALTIME: + case PHAPP_ID_PRIORITY_HIGH: + case PHAPP_ID_PRIORITY_ABOVENORMAL: + case PHAPP_ID_PRIORITY_NORMAL: + case PHAPP_ID_PRIORITY_BELOWNORMAL: + case PHAPP_ID_PRIORITY_IDLE: + { + BOOLEAN changed = FALSE; + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + ULONG i; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + LockDb(); + + for (i = 0; i < numberOfProcesses; i++) + { + PDB_OBJECT object; + + if (object = FindDbObjectForProcess(processes[i], INTENT_PROCESS_PRIORITY_CLASS)) + { + ULONG newPriorityClass = GetPriorityClassFromId(id); + + if (object->PriorityClass != newPriorityClass) + { + object->PriorityClass = newPriorityClass; + changed = TRUE; + } + } + } + + UnlockDb(); + PhFree(processes); + + if (changed) + SaveDb(); + } + break; + case PHAPP_ID_IOPRIORITY_VERYLOW: + case PHAPP_ID_IOPRIORITY_LOW: + case PHAPP_ID_IOPRIORITY_NORMAL: + case PHAPP_ID_IOPRIORITY_HIGH: + { + BOOLEAN changed = FALSE; + PPH_PROCESS_ITEM *processes; + ULONG numberOfProcesses; + ULONG i; + + PhGetSelectedProcessItems(&processes, &numberOfProcesses); + LockDb(); + + for (i = 0; i < numberOfProcesses; i++) + { + PDB_OBJECT object; + + if (object = FindDbObjectForProcess(processes[i], INTENT_PROCESS_IO_PRIORITY)) + { + ULONG newIoPriorityPlusOne = GetIoPriorityFromId(id) + 1; + + if (object->IoPriorityPlusOne != newIoPriorityPlusOne) + { + object->IoPriorityPlusOne = newIoPriorityPlusOne; + changed = TRUE; + } + } + } + + UnlockDb(); + PhFree(processes); + + if (changed) + SaveDb(); + } + break; + } +} + +VOID InvalidateProcessComments( + VOID + ) +{ + PLIST_ENTRY listEntry; + + PhAcquireQueuedLockExclusive(&ProcessListLock); + + listEntry = ProcessListHead.Flink; + + while (listEntry != &ProcessListHead) + { + PPROCESS_EXTENSION extension; + + extension = CONTAINING_RECORD(listEntry, PROCESS_EXTENSION, ListEntry); + + extension->Valid = FALSE; + + listEntry = listEntry->Flink; + } + + PhReleaseQueuedLockExclusive(&ProcessListLock); +} + +VOID UpdateProcessComment( + _In_ PPH_PROCESS_NODE Node, + _In_ PPROCESS_EXTENSION Extension + ) +{ + if (!Extension->Valid) + { + PDB_OBJECT object; + + LockDb(); + + if (object = FindDbObjectForProcess(Node->ProcessItem, INTENT_PROCESS_COMMENT)) + { + PhSwapReference(&Extension->Comment, object->Comment); + } + else + { + PhClearReference(&Extension->Comment); + } + + UnlockDb(); + + Extension->Valid = TRUE; + } +} + +VOID InvalidateServiceComments( + VOID + ) +{ + PLIST_ENTRY listEntry; + + PhAcquireQueuedLockExclusive(&ServiceListLock); + + listEntry = ServiceListHead.Flink; + + while (listEntry != &ServiceListHead) + { + PSERVICE_EXTENSION extension; + + extension = CONTAINING_RECORD(listEntry, SERVICE_EXTENSION, ListEntry); + + extension->Valid = FALSE; + + listEntry = listEntry->Flink; + } + + PhReleaseQueuedLockExclusive(&ServiceListLock); +} + +VOID UpdateServiceComment( + _In_ PPH_SERVICE_NODE Node, + _In_ PSERVICE_EXTENSION Extension + ) +{ + if (!Extension->Valid) + { + PDB_OBJECT object; + + LockDb(); + + if (object = FindDbObject(SERVICE_TAG, &Node->ServiceItem->Name->sr)) + { + PhSwapReference(&Extension->Comment, object->Comment); + } + else + { + PhClearReference(&Extension->Comment); + } + + UnlockDb(); + + Extension->Valid = TRUE; + } +} + +VOID TreeNewMessageCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_MESSAGE message = Parameter; + + switch (message->Message) + { + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = message->Parameter1; + + if (message->TreeNewHandle == ProcessTreeNewHandle) + { + PPH_PROCESS_NODE node; + + node = (PPH_PROCESS_NODE)getCellText->Node; + + switch (message->SubId) + { + case COMMENT_COLUMN_ID: + { + PPROCESS_EXTENSION extension; + + extension = PhPluginGetObjectExtension(PluginInstance, node->ProcessItem, EmProcessItemType); + UpdateProcessComment(node, extension); + getCellText->Text = PhGetStringRef(extension->Comment); + } + break; + } + } + else if (message->TreeNewHandle == ServiceTreeNewHandle) + { + PPH_SERVICE_NODE node; + + node = (PPH_SERVICE_NODE)getCellText->Node; + + switch (message->SubId) + { + case COMMENT_COLUMN_ID: + { + PSERVICE_EXTENSION extension; + + extension = PhPluginGetObjectExtension(PluginInstance, node->ServiceItem, EmServiceItemType); + UpdateServiceComment(node, extension); + getCellText->Text = PhGetStringRef(extension->Comment); + } + break; + } + } + } + break; + } +} + +VOID MainWindowShowingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + if (ToolStatusInterface) + { + PhRegisterCallback(ToolStatusInterface->SearchChangedEvent, SearchChangedHandler, NULL, &SearchChangedRegistration); + } +} + +VOID ProcessPropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_PROCESS_PROPCONTEXT propContext = Parameter; + + PhAddProcessPropPage( + propContext->PropContext, + PhCreateProcessPropPageContextEx(PluginInstance->DllBase, MAKEINTRESOURCE(IDD_PROCCOMMENT), ProcessCommentPageDlgProc, NULL) + ); +} + +VOID AddSavePriorityMenuItemsAndHook( + _In_ PPH_PLUGIN_MENU_INFORMATION MenuInfo, + _In_ PPH_PROCESS_ITEM ProcessItem, + _In_ BOOLEAN UseSelectionForHook + ) +{ + PPH_EMENU_ITEM priorityMenuItem; + PPH_EMENU_ITEM ioPriorityMenuItem; + PPH_EMENU_ITEM saveMenuItem; + PPH_EMENU_ITEM saveForCommandLineMenuItem; + PDB_OBJECT object; + + // Priority + if (priorityMenuItem = PhFindEMenuItem(MenuInfo->Menu, 0, L"Priority", 0)) + { + PhInsertEMenuItem(priorityMenuItem, PhPluginCreateEMenuItem(PluginInstance, PH_EMENU_SEPARATOR, 0, NULL, NULL), -1); + PhInsertEMenuItem(priorityMenuItem, saveMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_PRIORITY_SAVE_ID, PhaFormatString(L"Save for %s", ProcessItem->ProcessName->Buffer)->Buffer, NULL), -1); + PhInsertEMenuItem(priorityMenuItem, saveForCommandLineMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID, L"Save for this command line", NULL), -1); + + if (!ProcessItem->CommandLine) + saveForCommandLineMenuItem->Flags |= PH_EMENU_DISABLED; + + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &ProcessItem->ProcessName->sr)) && object->PriorityClass != 0) + saveMenuItem->Flags |= PH_EMENU_CHECKED; + if (ProcessItem->CommandLine && (object = FindDbObject(COMMAND_LINE_TAG, &ProcessItem->CommandLine->sr)) && object->PriorityClass != 0) + saveForCommandLineMenuItem->Flags |= PH_EMENU_CHECKED; + + UnlockDb(); + } + + // I/O Priority + if (ioPriorityMenuItem = PhFindEMenuItem(MenuInfo->Menu, 0, L"I/O Priority", 0)) + { + PhInsertEMenuItem(ioPriorityMenuItem, PhPluginCreateEMenuItem(PluginInstance, PH_EMENU_SEPARATOR, 0, NULL, NULL), -1); + PhInsertEMenuItem(ioPriorityMenuItem, saveMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_IO_PRIORITY_SAVE_ID, PhaFormatString(L"Save for %s", ProcessItem->ProcessName->Buffer)->Buffer, NULL), -1); + PhInsertEMenuItem(ioPriorityMenuItem, saveForCommandLineMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_IO_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID, L"Save for this command line", NULL), -1); + + if (!ProcessItem->CommandLine) + saveForCommandLineMenuItem->Flags |= PH_EMENU_DISABLED; + + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &ProcessItem->ProcessName->sr)) && object->IoPriorityPlusOne != 0) + saveMenuItem->Flags |= PH_EMENU_CHECKED; + if (ProcessItem->CommandLine && (object = FindDbObject(COMMAND_LINE_TAG, &ProcessItem->CommandLine->sr)) && object->IoPriorityPlusOne != 0) + saveForCommandLineMenuItem->Flags |= PH_EMENU_CHECKED; + + UnlockDb(); + } + + PhPluginAddMenuHook(MenuInfo, PluginInstance, UseSelectionForHook ? NULL : ProcessItem->ProcessId); +} + +VOID ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem; + PPH_EMENU_ITEM miscMenuItem; + BOOLEAN highlightPresent = FALSE; + PPH_EMENU_ITEM collapseMenuItem; + PPH_EMENU_ITEM highlightMenuItem; + PDB_OBJECT object; + + if (menuInfo->u.Process.NumberOfProcesses != 1) + return; + + processItem = menuInfo->u.Process.Processes[0]; + + if (PH_IS_FAKE_PROCESS_ID(processItem->ProcessId) || processItem->ProcessId == SYSTEM_IDLE_PROCESS_ID || processItem->ProcessId == SYSTEM_PROCESS_ID) + return; + + AddSavePriorityMenuItemsAndHook(menuInfo, processItem, TRUE); + + if (!(miscMenuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Miscellaneous", 0))) + return; + + LockDb(); + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->BackColor != ULONG_MAX) + highlightPresent = TRUE; + UnlockDb(); + + PhInsertEMenuItem(miscMenuItem, collapseMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_COLLAPSE_ID, L"Collapse by default", NULL), 0); + PhInsertEMenuItem(miscMenuItem, highlightMenuItem = PhPluginCreateEMenuItem(PluginInstance, 0, PROCESS_HIGHLIGHT_ID, L"Highlight", UlongToPtr(highlightPresent)), 1); + PhInsertEMenuItem(miscMenuItem, PhPluginCreateEMenuItem(PluginInstance, PH_EMENU_SEPARATOR, 0, NULL, NULL), 2); + + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &menuInfo->u.Process.Processes[0]->ProcessName->sr)) && object->Collapse) + collapseMenuItem->Flags |= PH_EMENU_CHECKED; + if (highlightPresent) + highlightMenuItem->Flags |= PH_EMENU_CHECKED; + + UnlockDb(); +} + +static LONG NTAPI ProcessCommentSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ) +{ + PPH_PROCESS_NODE node1 = Node1; + PPH_PROCESS_NODE node2 = Node2; + PPROCESS_EXTENSION extension1 = PhPluginGetObjectExtension(PluginInstance, node1->ProcessItem, EmProcessItemType); + PPROCESS_EXTENSION extension2 = PhPluginGetObjectExtension(PluginInstance, node2->ProcessItem, EmProcessItemType); + + UpdateProcessComment(node1, extension1); + UpdateProcessComment(node2, extension2); + + return PhCompareStringWithNull(extension1->Comment, extension2->Comment, TRUE); +} + +VOID ProcessTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_INFORMATION info = Parameter; + PH_TREENEW_COLUMN column; + + ProcessTreeNewHandle = info->TreeNewHandle; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Text = L"Comment"; + column.Width = 120; + column.Alignment = PH_ALIGN_LEFT; + + PhPluginAddTreeNewColumn(PluginInstance, info->CmData, &column, COMMENT_COLUMN_ID, NULL, ProcessCommentSortFunction); +} + +VOID GetProcessHighlightingColorCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_GET_HIGHLIGHTING_COLOR getHighlightingColor = Parameter; + PPH_PROCESS_ITEM processItem = (PPH_PROCESS_ITEM)getHighlightingColor->Parameter; + PDB_OBJECT object; + + if (getHighlightingColor->Handled) + return; + + LockDb(); + + if ((object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr)) && object->BackColor != ULONG_MAX) + { + getHighlightingColor->BackColor = object->BackColor; + getHighlightingColor->Cache = TRUE; + getHighlightingColor->Handled = TRUE; + } + + UnlockDb(); +} + +VOID ServicePropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_OBJECT_PROPERTIES objectProperties = Parameter; + PROPSHEETPAGE propSheetPage; + + if (objectProperties->NumberOfPages < objectProperties->MaximumNumberOfPages) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_SRVCOMMENT); + propSheetPage.pszTitle = L"Comment"; + propSheetPage.pfnDlgProc = ServiceCommentPageDlgProc; + propSheetPage.lParam = (LPARAM)objectProperties->Parameter; + objectProperties->Pages[objectProperties->NumberOfPages++] = CreatePropertySheetPage(&propSheetPage); + } +} + +LONG NTAPI ServiceCommentSortFunction( + _In_ PVOID Node1, + _In_ PVOID Node2, + _In_ ULONG SubId, + _In_ PVOID Context + ) +{ + PPH_SERVICE_NODE node1 = Node1; + PPH_SERVICE_NODE node2 = Node2; + PSERVICE_EXTENSION extension1 = PhPluginGetObjectExtension(PluginInstance, node1->ServiceItem, EmServiceItemType); + PSERVICE_EXTENSION extension2 = PhPluginGetObjectExtension(PluginInstance, node2->ServiceItem, EmServiceItemType); + + UpdateServiceComment(node1, extension1); + UpdateServiceComment(node2, extension2); + + return PhCompareStringWithNull(extension1->Comment, extension2->Comment, TRUE); +} + +VOID ServiceTreeNewInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_TREENEW_INFORMATION info = Parameter; + PH_TREENEW_COLUMN column; + + ServiceTreeNewHandle = info->TreeNewHandle; + + memset(&column, 0, sizeof(PH_TREENEW_COLUMN)); + column.Text = L"Comment"; + column.Width = 120; + column.Alignment = PH_ALIGN_LEFT; + + PhPluginAddTreeNewColumn(PluginInstance, info->CmData, &column, COMMENT_COLUMN_ID, NULL, ServiceCommentSortFunction); +} + +VOID MiListSectionMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem = menuInfo->u.MiListSection.ProcessGroup->Representative; + + if (PH_IS_FAKE_PROCESS_ID(processItem->ProcessId) || processItem->ProcessId == SYSTEM_IDLE_PROCESS_ID || processItem->ProcessId == SYSTEM_PROCESS_ID) + return; + + AddSavePriorityMenuItemsAndHook(menuInfo, processItem, FALSE); +} + +VOID ProcessModifiedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PROCESS_ITEM processItem = Parameter; + PPROCESS_EXTENSION extension = PhPluginGetObjectExtension(PluginInstance, processItem, EmProcessItemType); + + extension->Valid = FALSE; +} + +VOID ProcessesUpdatedCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PLIST_ENTRY listEntry; + + if (GetNumberOfDbObjects() == 0) + return; + + PhAcquireQueuedLockExclusive(&ProcessListLock); + LockDb(); + + listEntry = ProcessListHead.Flink; + + while (listEntry != &ProcessListHead) + { + PPROCESS_EXTENSION extension; + PPH_PROCESS_ITEM processItem; + PDB_OBJECT object; + + extension = CONTAINING_RECORD(listEntry, PROCESS_EXTENSION, ListEntry); + processItem = extension->ProcessItem; + + if (object = FindDbObjectForProcess(processItem, INTENT_PROCESS_PRIORITY_CLASS)) + { + if (processItem->PriorityClass != object->PriorityClass) + { + HANDLE processHandle; + PROCESS_PRIORITY_CLASS priorityClass; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + processItem->ProcessId + ))) + { + priorityClass.Foreground = FALSE; + priorityClass.PriorityClass = (UCHAR)object->PriorityClass; + NtSetInformationProcess(processHandle, ProcessPriorityClass, &priorityClass, sizeof(PROCESS_PRIORITY_CLASS)); + + NtClose(processHandle); + } + } + } + + if (object = FindDbObjectForProcess(processItem, INTENT_PROCESS_IO_PRIORITY)) + { + if (object->IoPriorityPlusOne != 0) + { + HANDLE processHandle; + + if (NT_SUCCESS(PhOpenProcess( + &processHandle, + PROCESS_SET_INFORMATION, + processItem->ProcessId + ))) + { + PhSetProcessIoPriority(processHandle, object->IoPriorityPlusOne - 1); + NtClose(processHandle); + } + } + } + + listEntry = listEntry->Flink; + } + + UnlockDb(); + PhReleaseQueuedLockExclusive(&ProcessListLock); +} + +VOID SearchChangedHandler( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_STRING searchText = Parameter; + + if (PhIsNullOrEmptyString(searchText)) + { + // ToolStatus expanded all nodes for searching, but the search text just became empty. We + // should re-collapse processes. + + PPH_LIST nodes = PH_AUTO(PhDuplicateProcessNodeList()); + ULONG i; + BOOLEAN changed = FALSE; + + LockDb(); + + for (i = 0; i < nodes->Count; i++) + { + PPH_PROCESS_NODE node = nodes->Items[i]; + PDB_OBJECT object; + + if ((object = FindDbObjectForProcess(node->ProcessItem, INTENT_PROCESS_COLLAPSE)) && object->Collapse) + { + node->Node.Expanded = FALSE; + changed = TRUE; + } + } + + UnlockDb(); + + if (changed) + TreeNew_NodesStructured(ProcessTreeNewHandle); + } +} + +VOID ProcessItemCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_PROCESS_ITEM processItem = Object; + PPROCESS_EXTENSION extension = Extension; + + memset(extension, 0, sizeof(PROCESS_EXTENSION)); + extension->ProcessItem = processItem; + + PhAcquireQueuedLockExclusive(&ProcessListLock); + InsertTailList(&ProcessListHead, &extension->ListEntry); + PhReleaseQueuedLockExclusive(&ProcessListLock); +} + +VOID ProcessItemDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_PROCESS_ITEM processItem = Object; + PPROCESS_EXTENSION extension = Extension; + + PhClearReference(&extension->Comment); + PhAcquireQueuedLockExclusive(&ProcessListLock); + RemoveEntryList(&extension->ListEntry); + PhReleaseQueuedLockExclusive(&ProcessListLock); +} + +VOID ProcessNodeCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_PROCESS_NODE processNode = Object; + PDB_OBJECT object; + + LockDb(); + + if ((object = FindDbObjectForProcess(processNode->ProcessItem, INTENT_PROCESS_COLLAPSE)) && object->Collapse) + processNode->Node.Expanded = FALSE; + + UnlockDb(); +} + +VOID ServiceItemCreateCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_SERVICE_ITEM processItem = Object; + PSERVICE_EXTENSION extension = Extension; + + memset(extension, 0, sizeof(SERVICE_EXTENSION)); + PhAcquireQueuedLockExclusive(&ServiceListLock); + InsertTailList(&ServiceListHead, &extension->ListEntry); + PhReleaseQueuedLockExclusive(&ServiceListLock); +} + +VOID ServiceItemDeleteCallback( + _In_ PVOID Object, + _In_ PH_EM_OBJECT_TYPE ObjectType, + _In_ PVOID Extension + ) +{ + PPH_SERVICE_ITEM processItem = Object; + PSERVICE_EXTENSION extension = Extension; + + PhClearReference(&extension->Comment); + PhAcquireQueuedLockExclusive(&ServiceListLock); + RemoveEntryList(&extension->ListEntry); + PhReleaseQueuedLockExclusive(&ServiceListLock); +} + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + if (Reason == DLL_PROCESS_ATTACH) + { + PPH_PLUGIN_INFORMATION info; + PH_SETTING_CREATE settings[] = + { + { StringSettingType, SETTING_NAME_DATABASE_PATH, L"%APPDATA%\\Process Hacker 2\\usernotesdb.xml" }, + { StringSettingType, SETTING_NAME_CUSTOM_COLOR_LIST, L"" } + }; + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"User Notes"; + info->Author = L"dmex, wj32"; + info->Description = L"Allows the user to add comments for processes and services. Also allows the user to save process priority. Also allows the user to highlight individual processes."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1120"; + info->HasOptions = TRUE; + + InitializeDb(); + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackUnload), + UnloadCallback, + NULL, + &PluginUnloadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + ShowOptionsCallback, + NULL, + &PluginShowOptionsCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuHook), + MenuHookCallback, + NULL, + &PluginMenuHookCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackTreeNewMessage), + TreeNewMessageCallback, + NULL, + &TreeNewMessageCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainWindowShowing), + MainWindowShowingCallback, + NULL, + &MainWindowShowingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessPropertiesInitializing), + ProcessPropertiesInitializingCallback, + NULL, + &ProcessPropertiesInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackServicePropertiesInitializing), + ServicePropertiesInitializingCallback, + NULL, + &ServicePropertiesInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + ProcessMenuInitializingCallback, + NULL, + &ProcessMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing), + ProcessTreeNewInitializingCallback, + NULL, + &ProcessTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackGetProcessHighlightingColor), + GetProcessHighlightingColorCallback, + NULL, + &GetProcessHighlightingColorCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackServiceTreeNewInitializing), + ServiceTreeNewInitializingCallback, + NULL, + &ServiceTreeNewInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMiListSectionMenuInitializing), + MiListSectionMenuInitializingCallback, + NULL, + &MiListSectionMenuInitializingCallbackRegistration + ); + PhRegisterCallback(&PhProcessModifiedEvent, + ProcessModifiedCallback, + NULL, + &ProcessModifiedCallbackRegistration + ); + PhRegisterCallback( + &PhProcessesUpdatedEvent, + ProcessesUpdatedCallback, + NULL, + &ProcessesUpdatedCallbackRegistration + ); + + PhPluginSetObjectExtension( + PluginInstance, + EmProcessItemType, + sizeof(PROCESS_EXTENSION), + ProcessItemCreateCallback, + ProcessItemDeleteCallback + ); + PhPluginSetObjectExtension( + PluginInstance, + EmProcessNodeType, + sizeof(PROCESS_EXTENSION), + ProcessNodeCreateCallback, + NULL + ); + PhPluginSetObjectExtension( + PluginInstance, + EmServiceItemType, + sizeof(SERVICE_EXTENSION), + ServiceItemCreateCallback, + ServiceItemDeleteCallback + ); + + PhAddSettings(settings, ARRAYSIZE(settings)); + } + + return TRUE; +} + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + SetDlgItemText(hwndDlg, IDC_DATABASE, PhaGetStringSetting(SETTING_NAME_DATABASE_PATH)->Buffer); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + case IDOK: + { + PhSetStringSetting2(SETTING_NAME_DATABASE_PATH, + &PhaGetDlgItemText(hwndDlg, IDC_DATABASE)->sr); + + EndDialog(hwndDlg, IDOK); + } + break; + case IDC_BROWSE: + { + static PH_FILETYPE_FILTER filters[] = + { + { L"XML files (*.xml)", L"*.xml" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + PPH_STRING fileName; + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + fileName = PH_AUTO(PhGetFileName(PhaGetDlgItemText(hwndDlg, IDC_DATABASE))); + PhSetFileDialogFileName(fileDialog, fileName->Buffer); + + if (PhShowFileDialog(hwndDlg, fileDialog)) + { + fileName = PH_AUTO(PhGetFileDialogFileName(fileDialog)); + SetDlgItemText(hwndDlg, IDC_DATABASE, fileName->Buffer); + } + + PhFreeFileDialog(fileDialog); + } + break; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK ProcessCommentPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LPPROPSHEETPAGE propSheetPage; + PPH_PROCESS_PROPPAGECONTEXT propPageContext; + PPH_PROCESS_ITEM processItem; + PPROCESS_COMMENT_PAGE_CONTEXT context; + + if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem)) + { + context = propPageContext->Context; + } + else + { + return FALSE; + } + + switch (uMsg) + { + case WM_INITDIALOG: + { + PDB_OBJECT object; + PPH_STRING comment; + + context = PhAllocate(sizeof(PROCESS_COMMENT_PAGE_CONTEXT)); + context->CommentHandle = GetDlgItem(hwndDlg, IDC_COMMENT); + context->RevertHandle = GetDlgItem(hwndDlg, IDC_REVERT); + context->MatchCommandlineHandle = GetDlgItem(hwndDlg, IDC_MATCHCOMMANDLINE); + propPageContext->Context = context; + + // Load the comment. + Edit_LimitText(context->CommentHandle, UNICODE_STRING_MAX_CHARS); + + LockDb(); + + if (object = FindDbObjectForProcess(processItem, INTENT_PROCESS_COMMENT)) + { + PhSetReference(&comment, object->Comment); + + if (processItem->CommandLine && (object = FindDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr)) && object->Comment->Length != 0) + { + Button_SetCheck(context->MatchCommandlineHandle, BST_CHECKED); + } + } + else + { + comment = PhReferenceEmptyString(); + } + + UnlockDb(); + + Edit_SetText(context->CommentHandle, comment->Buffer); + context->OriginalComment = comment; + + if (!processItem->CommandLine) + EnableWindow(context->MatchCommandlineHandle, FALSE); + } + break; + case WM_DESTROY: + { + PDB_OBJECT object; + PPH_STRING comment; + BOOLEAN matchCommandLine; + BOOLEAN done = FALSE; + + comment = PH_AUTO(PhGetWindowText(context->CommentHandle)); + matchCommandLine = Button_GetCheck(context->MatchCommandlineHandle) == BST_CHECKED; + + if (!processItem->CommandLine) + matchCommandLine = FALSE; + + LockDb(); + + if (processItem->CommandLine && !matchCommandLine) + { + PDB_OBJECT objectForProcessName; + PPH_STRING message = NULL; + + object = FindDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr); + objectForProcessName = FindDbObject(FILE_TAG, &processItem->ProcessName->sr); + + if (object && objectForProcessName && object->Comment->Length != 0 && objectForProcessName->Comment->Length != 0 && + !PhEqualString(comment, objectForProcessName->Comment, FALSE)) + { + message = PhaFormatString( + L"Do you want to replace the comment for %s which is currently\n \"%s\"\n" + L"with\n \"%s\"?", + processItem->ProcessName->Buffer, + objectForProcessName->Comment->Buffer, + comment->Buffer + ); + } + + if (object) + { + PhMoveReference(&object->Comment, PhReferenceEmptyString()); + DeleteDbObjectForProcessIfUnused(object); + } + + if (message) + { + // Prevent deadlocks. + UnlockDb(); + + if (MessageBox(hwndDlg, message->Buffer, L"Comment", MB_ICONQUESTION | MB_YESNO) == IDNO) + { + done = TRUE; + } + + LockDb(); + } + } + + if (!done) + { + if (comment->Length != 0) + { + if (matchCommandLine) + CreateDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr, comment); + else + CreateDbObject(FILE_TAG, &processItem->ProcessName->sr, comment); + } + else + { + if ( + (!matchCommandLine && (object = FindDbObject(FILE_TAG, &processItem->ProcessName->sr))) || + (matchCommandLine && (object = FindDbObject(COMMAND_LINE_TAG, &processItem->CommandLine->sr))) + ) + { + PhMoveReference(&object->Comment, PhReferenceEmptyString()); + DeleteDbObjectForProcessIfUnused(object); + } + } + } + + UnlockDb(); + + PhDereferenceObject(context->OriginalComment); + PhFree(context); + + PhPropPageDlgProcDestroy(hwndDlg); + + SaveDb(); + InvalidateProcessComments(); + } + break; + case WM_SHOWWINDOW: + { + PPH_LAYOUT_ITEM dialogItem; + + if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext)) + { + PhAddPropPageLayoutItem(hwndDlg, context->CommentHandle, dialogItem, PH_ANCHOR_ALL); + PhAddPropPageLayoutItem(hwndDlg, context->RevertHandle, dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_BOTTOM); + PhAddPropPageLayoutItem(hwndDlg, context->MatchCommandlineHandle, dialogItem, PH_ANCHOR_RIGHT | PH_ANCHOR_BOTTOM); + PhEndPropPageLayout(hwndDlg, propPageContext); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_COMMENT: + { + if (HIWORD(wParam) == EN_CHANGE) + EnableWindow(context->RevertHandle, TRUE); + } + break; + case IDC_REVERT: + { + Edit_SetText(context->CommentHandle, context->OriginalComment->Buffer); + SendMessage(context->CommentHandle, EM_SETSEL, 0, -1); + SendMessage(hwndDlg, WM_NEXTDLGCTL, (WPARAM)context->CommentHandle, TRUE); + EnableWindow(context->RevertHandle, FALSE); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)context->MatchCommandlineHandle); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK ServiceCommentPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PSERVICE_COMMENT_PAGE_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = PhAllocate(sizeof(SERVICE_COMMENT_PAGE_CONTEXT)); + memset(context, 0, sizeof(SERVICE_COMMENT_PAGE_CONTEXT)); + + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PSERVICE_COMMENT_PAGE_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + LPPROPSHEETPAGE propSheetPage = (LPPROPSHEETPAGE)lParam; + PPH_SERVICE_ITEM serviceItem = (PPH_SERVICE_ITEM)propSheetPage->lParam; + PDB_OBJECT object; + PPH_STRING comment; + + context->ServiceItem = serviceItem; + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_COMMENT), NULL, PH_ANCHOR_ALL); + + // Load the comment. + + SendMessage(GetDlgItem(hwndDlg, IDC_COMMENT), EM_SETLIMITTEXT, UNICODE_STRING_MAX_CHARS, 0); + + LockDb(); + + if (object = FindDbObject(SERVICE_TAG, &serviceItem->Name->sr)) + comment = object->Comment; + else + comment = PH_AUTO(PhReferenceEmptyString()); + + UnlockDb(); + + SetDlgItemText(hwndDlg, IDC_COMMENT, comment->Buffer); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_DESTROY: + { + PhDeleteLayoutManager(&context->LayoutManager); + PhFree(context); + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_COMMENT: + { + if (HIWORD(wParam) == EN_CHANGE) + EnableWindow(GetDlgItem(hwndDlg, IDC_REVERT), TRUE); + } + break; + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_KILLACTIVE: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, FALSE); + } + return TRUE; + case PSN_APPLY: + { + PDB_OBJECT object; + PPH_STRING comment; + + comment = PH_AUTO(PhGetWindowText(GetDlgItem(hwndDlg, IDC_COMMENT))); + + LockDb(); + + if (comment->Length != 0) + { + CreateDbObject(SERVICE_TAG, &context->ServiceItem->Name->sr, comment); + } + else + { + if (object = FindDbObject(SERVICE_TAG, &context->ServiceItem->Name->sr)) + DeleteDbObject(object); + } + + UnlockDb(); + + SaveDb(); + InvalidateServiceComments(); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR); + } + return TRUE; + } + } + break; + } + + return FALSE; +} + +UINT_PTR CALLBACK ColorDlgHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PhCenterWindow(hwndDlg, PhMainWndHandle); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/plugins/UserNotes/resource.h b/plugins/UserNotes/resource.h new file mode 100644 index 0000000..7d5001f --- /dev/null +++ b/plugins/UserNotes/resource.h @@ -0,0 +1,25 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by UserNotes.rc +// +#define IDD_OPTIONS 101 +#define IDD_COMMENT 102 +#define IDD_PROCCOMMENT 102 +#define IDD_SRVCOMMENT 103 +#define IDC_DATABASE 1001 +#define IDC_BROWSE 1002 +#define IDC_COMMENT 1003 +#define IDC_REVERT 1004 +#define IDC_CHECK1 1005 +#define IDC_MATCHCOMMANDLINE 1005 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/UserNotes/usernotes.h b/plugins/UserNotes/usernotes.h new file mode 100644 index 0000000..35ac2c9 --- /dev/null +++ b/plugins/UserNotes/usernotes.h @@ -0,0 +1,113 @@ +/* + * Process Hacker User Notes - + * UserNotes Header + * + * Copyright (C) 2011-2015 wj32 + * Copyright (C) 2016 dmex + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#ifndef _USERNOTES_H_ +#define _USERNOTES_H_ + +#include +#include +#include + +#include +#include +#include + +#include "db.h" +#include "resource.h" + +#define INTENT_PROCESS_COMMENT 0x1 +#define INTENT_PROCESS_PRIORITY_CLASS 0x2 +#define INTENT_PROCESS_IO_PRIORITY 0x4 +#define INTENT_PROCESS_HIGHLIGHT 0x8 +#define INTENT_PROCESS_COLLAPSE 0x10 + +typedef enum _USERNOTES_COMMAND_ID +{ + PROCESS_PRIORITY_SAVE_ID = 1, + PROCESS_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID, + PROCESS_IO_PRIORITY_SAVE_ID, + PROCESS_IO_PRIORITY_SAVE_FOR_THIS_COMMAND_LINE_ID, + PROCESS_HIGHLIGHT_ID, + PROCESS_COLLAPSE_ID, +} USERNOTES_COMMAND_ID; + +#define COMMENT_COLUMN_ID 1 + +typedef struct _PROCESS_EXTENSION +{ + LIST_ENTRY ListEntry; + PPH_PROCESS_ITEM ProcessItem; + BOOLEAN Valid; + PPH_STRING Comment; +} PROCESS_EXTENSION, *PPROCESS_EXTENSION; + +typedef struct _PROCESS_COMMENT_PAGE_CONTEXT +{ + HWND CommentHandle; + HWND RevertHandle; + HWND MatchCommandlineHandle; + PPH_STRING OriginalComment; +} PROCESS_COMMENT_PAGE_CONTEXT, *PPROCESS_COMMENT_PAGE_CONTEXT; + +typedef struct _SERVICE_EXTENSION +{ + LIST_ENTRY ListEntry; + BOOLEAN Valid; + PPH_STRING Comment; +} SERVICE_EXTENSION, *PSERVICE_EXTENSION; + +typedef struct _SERVICE_COMMENT_PAGE_CONTEXT +{ + PPH_SERVICE_ITEM ServiceItem; + PH_LAYOUT_MANAGER LayoutManager; +} SERVICE_COMMENT_PAGE_CONTEXT, *PSERVICE_COMMENT_PAGE_CONTEXT; + +INT_PTR CALLBACK OptionsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK ProcessCommentPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK ServiceCommentPageDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +UINT_PTR CALLBACK ColorDlgHookProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#endif \ No newline at end of file diff --git a/plugins/WindowExplorer/CHANGELOG.txt b/plugins/WindowExplorer/CHANGELOG.txt new file mode 100644 index 0000000..862f2c7 --- /dev/null +++ b/plugins/WindowExplorer/CHANGELOG.txt @@ -0,0 +1,18 @@ +1.5 + * Fixed window list not displaying Modern UI windows + +1.4 + * Fixed hook support for low integrity processes + +1.3 + * Fixed hook bugs + +1.2 + * Added more window properties + +1.1 + * Added Always on Top and Opacity + * Renamed Show/Hide and Enable/Disable to Visible and Enabled + +1.0 + * Initial release \ No newline at end of file diff --git a/plugins/WindowExplorer/WindowExplorer.rc b/plugins/WindowExplorer/WindowExplorer.rc new file mode 100644 index 0000000..9b5de40 --- /dev/null +++ b/plugins/WindowExplorer/WindowExplorer.rc @@ -0,0 +1,307 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,5,0,0 + PRODUCTVERSION 1,5,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "Window Explorer plugin for Process Hacker" + VALUE "FileVersion", "1.5" + VALUE "InternalName", "WindowExplorer" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "WindowExplorer.dll" + VALUE "ProductName", "Window Explorer plugin for Process Hacker" + VALUE "ProductVersion", "1.5" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_WNDLIST DIALOGEX 0, 0, 447, 309 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +EXSTYLE WS_EX_APPWINDOW +CAPTION "Windows" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Refresh",IDC_REFRESH,7,6,50,14 + CONTROL "Windows",IDC_LIST,"PhTreeNew",WS_CLIPSIBLINGS | WS_CLIPCHILDREN | 0x2,7,25,433,277,WS_EX_CLIENTEDGE +END + +IDD_WNDPROPS DIALOGEX 0, 0, 233, 152 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Property list for the window:",IDC_STATIC,7,7,92,8 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,20,219,125 +END + +IDD_WNDGENERAL DIALOGEX 0, 0, 233, 147 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Text:",IDC_STATIC,7,8,18,8 + EDITTEXT IDC_TEXT,33,7,193,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Thread:",IDC_STATIC,7,22,26,8 + LTEXT "Static",IDC_THREAD,77,22,149,8,SS_ENDELLIPSIS + LTEXT "Rectangle:",IDC_STATIC,7,33,36,8 + LTEXT "Static",IDC_RECTANGLE,77,33,149,8,SS_ENDELLIPSIS + LTEXT "Normal rectangle:",IDC_STATIC,7,44,60,8 + LTEXT "Static",IDC_NORMALRECTANGLE,77,44,149,8,SS_ENDELLIPSIS + LTEXT "Client rectangle:",IDC_STATIC,7,55,56,8 + LTEXT "Static",IDC_CLIENTRECTANGLE,77,55,149,8,SS_ENDELLIPSIS + LTEXT "Instance handle:",IDC_STATIC,7,66,56,8 + LTEXT "Static",IDC_INSTANCEHANDLE,77,66,149,8,SS_ENDELLIPSIS + LTEXT "Menu handle:",IDC_STATIC,7,77,45,8 + LTEXT "Static",IDC_MENUHANDLE,77,77,149,8,SS_ENDELLIPSIS + LTEXT "User data:",IDC_STATIC,7,88,36,8 + LTEXT "Static",IDC_USERDATA,77,88,149,8,SS_ENDELLIPSIS + LTEXT "Unicode:",IDC_STATIC,7,99,29,8 + LTEXT "Static",IDC_UNICODE,77,99,149,8 + LTEXT "Window proc:",IDC_STATIC,7,110,45,8 + EDITTEXT IDC_WINDOWPROC,75,110,151,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Dialog proc:",IDC_STATIC,7,121,39,8 + EDITTEXT IDC_DIALOGPROC,75,121,151,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Dialog control ID:",IDC_STATIC,7,132,61,8 + LTEXT "Static",IDC_CTRLID,77,132,149,8 +END + +IDD_WNDSTYLES DIALOGEX 0, 0, 233, 140 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Styles" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Styles:",IDC_STATIC,7,7,23,8 + LTEXT "Static",IDC_STYLES,67,7,159,8 + LISTBOX IDC_STYLESLIST,7,19,219,46,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + LTEXT "Extended styles:",IDC_STATIC,7,69,56,8 + LTEXT "Static",IDC_EXTENDEDSTYLES,67,69,159,8 + LISTBOX IDC_EXTENDEDSTYLESLIST,7,81,219,46,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP +END + +IDD_WNDCLASS DIALOGEX 0, 0, 233, 132 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Class" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Name:",IDC_STATIC,7,8,22,8 + EDITTEXT IDC_NAME,33,7,193,12,ES_AUTOHSCROLL | ES_READONLY + LTEXT "Atom:",IDC_STATIC,7,22,20,8 + LTEXT "Static",IDC_ATOM,77,22,149,8,SS_ENDELLIPSIS + LTEXT "Styles:",IDC_STATIC,7,33,23,8 + EDITTEXT IDC_STYLES,75,33,150,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Instance handle:",IDC_STATIC,7,44,56,8 + LTEXT "Static",IDC_INSTANCEHANDLE,77,44,149,8,SS_ENDELLIPSIS + LTEXT "Icon handle:",IDC_STATIC,7,55,42,8 + LTEXT "Static",IDC_ICONHANDLE,77,55,149,8,SS_ENDELLIPSIS + LTEXT "Small icon handle:",IDC_STATIC,7,66,60,8 + LTEXT "Static",IDC_SMALLICONHANDLE,77,66,149,8,SS_ENDELLIPSIS + LTEXT "Cursor handle:",IDC_STATIC,7,77,49,8 + LTEXT "Static",IDC_CURSORHANDLE,77,77,149,8,SS_ENDELLIPSIS + LTEXT "Background brush:",IDC_STATIC,7,88,61,8 + LTEXT "Static",IDC_BACKGROUNDBRUSH,77,88,149,8,SS_ENDELLIPSIS + LTEXT "Menu name:",IDC_STATIC,7,99,41,8 + LTEXT "Static",IDC_MENUNAME,77,99,149,8,SS_ENDELLIPSIS + LTEXT "Window proc:",IDC_STATIC,7,110,45,8 + EDITTEXT IDC_WINDOWPROC,75,110,151,14,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_WNDLIST, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 440 + TOPMARGIN, 6 + BOTTOMMARGIN, 302 + END + + IDD_WNDPROPS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 226 + TOPMARGIN, 7 + BOTTOMMARGIN, 145 + END + + IDD_WNDGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 226 + TOPMARGIN, 7 + BOTTOMMARGIN, 140 + END + + IDD_WNDSTYLES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 226 + TOPMARGIN, 7 + BOTTOMMARGIN, 133 + END + + IDD_WNDCLASS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 226 + TOPMARGIN, 7 + BOTTOMMARGIN, 125 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_WINDOW MENU +BEGIN + POPUP "Window" + BEGIN + MENUITEM "Bring to front", ID_WINDOW_BRINGTOFRONT + MENUITEM "Restore", ID_WINDOW_RESTORE + MENUITEM "Minimize", ID_WINDOW_MINIMIZE + MENUITEM "Maximize", ID_WINDOW_MAXIMIZE + MENUITEM "Close", ID_WINDOW_CLOSE + MENUITEM SEPARATOR + MENUITEM "Visible", ID_WINDOW_VISIBLE + MENUITEM "Enabled", ID_WINDOW_ENABLED + MENUITEM "Always on top", ID_WINDOW_ALWAYSONTOP + POPUP "Opacity" + BEGIN + MENUITEM "10%", ID_OPACITY_10 + MENUITEM "20%", ID_OPACITY_20 + MENUITEM "30%", ID_OPACITY_30 + MENUITEM "40%", ID_OPACITY_40 + MENUITEM "50%", ID_OPACITY_50 + MENUITEM "60%", ID_OPACITY_60 + MENUITEM "70%", ID_OPACITY_70 + MENUITEM "80%", ID_OPACITY_80 + MENUITEM "90%", ID_OPACITY_90 + MENUITEM "Opaque", ID_OPACITY_OPAQUE + END + MENUITEM SEPARATOR + MENUITEM "Highlight", ID_WINDOW_HIGHLIGHT + MENUITEM "Go to thread", ID_WINDOW_GOTOTHREAD + MENUITEM "Properties", ID_WINDOW_PROPERTIES + MENUITEM SEPARATOR + MENUITEM "Copy\aCtrl+C", ID_WINDOW_COPY + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_WNDGENERAL AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_WNDLIST AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_WNDCLASS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/WindowExplorer/WindowExplorer.vcxproj b/plugins/WindowExplorer/WindowExplorer.vcxproj new file mode 100644 index 0000000..86224f0 --- /dev/null +++ b/plugins/WindowExplorer/WindowExplorer.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {37488DC1-E45F-4626-A87C-3A854A153D1A} + WindowExplorer + Win32Proj + WindowExplorer + 10.0.10586.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + + + + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release64 + + + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86);$(NETFXKitsDir)Lib\um\x86;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Release32 + + + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Users\AirDog46\Downloads\processhacker-2.39-src\bin\Debug64 + + + + ProcessHacker.exe;comctl32.dll;%(DelayLoadDLLs) + + + + + ProcessHacker.exe;comctl32.dll;%(DelayLoadDLLs) + + + + + ProcessHacker.exe;comctl32.dll;%(DelayLoadDLLs) + + + + + ProcessHacker.exe;comctl32.dll;%(DelayLoadDLLs) + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/WindowExplorer/WindowExplorer.vcxproj.filters b/plugins/WindowExplorer/WindowExplorer.vcxproj.filters new file mode 100644 index 0000000..20bc08e --- /dev/null +++ b/plugins/WindowExplorer/WindowExplorer.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/plugins/WindowExplorer/hook.c b/plugins/WindowExplorer/hook.c new file mode 100644 index 0000000..abecae5 --- /dev/null +++ b/plugins/WindowExplorer/hook.c @@ -0,0 +1,509 @@ +/* + * Process Hacker Window Explorer - + * hook procedure + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +/* + * Window Explorer uses a hook procedure in order to get window procedure + * and other information that can't be retrieved using GetWindowLongPtr. + * Because WindowExplorer.dll needs to be loaded into processes other + * than Process Hacker, both ProcessHacker.exe and comctl32.dll are set as + * delay-loaded DLLs. The other DLLs that we depend on (gdi32.dll, + * kernel32.dll, ntdll.dll, user32.dll) are all guaranteed to be already + * loaded whenever WindowExplorer.dll needs to be loaded. + */ + +#include "wndexp.h" + +BOOLEAN WepCreateServerObjects( + VOID + ); + +BOOLEAN WepOpenServerObjects( + VOID + ); + +VOID WepCloseServerObjects( + VOID + ); + +VOID WepWriteClientData( + _In_ HWND hwnd + ); + +LRESULT CALLBACK WepCallWndProc( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +// Shared +ULONG WeServerMessage; +HANDLE WeServerSharedSection; +PWE_HOOK_SHARED_DATA WeServerSharedData; +HANDLE WeServerSharedSectionLock; +HANDLE WeServerSharedSectionEvent; +// Server +HHOOK WeHookHandle = NULL; +// The current message ID is used to detect out-of-sync clients. +ULONG WeCurrentMessageId = 0; + +// Server + +VOID WeHookServerInitialization( + VOID + ) +{ + if (WeHookHandle) + return; + + WeServerMessage = RegisterWindowMessage(WE_SERVER_MESSAGE_NAME); + + if (!WepCreateServerObjects()) + return; + + WeHookHandle = SetWindowsHookEx(WH_CALLWNDPROC, WepCallWndProc, PluginInstance->DllBase, 0); +} + +VOID WeHookServerUninitialization( + VOID + ) +{ + if (WeHookHandle) + { + UnhookWindowsHookEx(WeHookHandle); + WeHookHandle = NULL; + } +} + +BOOLEAN WepCreateServerObjects( + VOID + ) +{ + OBJECT_ATTRIBUTES objectAttributes; + WCHAR buffer[256]; + UNICODE_STRING objectName; + SECURITY_DESCRIPTOR securityDescriptor; + UCHAR saclBuffer[sizeof(ACL) + FIELD_OFFSET(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; + PACL sacl; + UCHAR mandatoryLabelAceBuffer[FIELD_OFFSET(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG)]; + PSYSTEM_MANDATORY_LABEL_ACE mandatoryLabelAce; + PSID sid; + + if (!WeServerSharedSection) + { + LARGE_INTEGER maximumSize; + + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + maximumSize.QuadPart = sizeof(WE_HOOK_SHARED_DATA); + + if (!NT_SUCCESS(NtCreateSection( + &WeServerSharedSection, + SECTION_ALL_ACCESS, + &objectAttributes, + &maximumSize, + PAGE_READWRITE, + SEC_COMMIT, + NULL + ))) + { + return FALSE; + } + } + + if (!WeServerSharedData) + { + PVOID viewBase; + SIZE_T viewSize; + + viewBase = NULL; + viewSize = sizeof(WE_HOOK_SHARED_DATA); + + if (!NT_SUCCESS(NtMapViewOfSection( + WeServerSharedSection, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + PAGE_READWRITE + ))) + { + WepCloseServerObjects(); + return FALSE; + } + + WeServerSharedData = viewBase; + } + + if (!WeServerSharedSectionLock) + { + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_LOCK_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + if (!NT_SUCCESS(NtCreateMutant( + &WeServerSharedSectionLock, + MUTANT_ALL_ACCESS, + &objectAttributes, + FALSE + ))) + { + WepCloseServerObjects(); + return FALSE; + } + } + + if (!WeServerSharedSectionEvent) + { + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_EVENT_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + if (!NT_SUCCESS(NtCreateEvent( + &WeServerSharedSectionEvent, + EVENT_ALL_ACCESS, + &objectAttributes, + NotificationEvent, + FALSE + ))) + { + WepCloseServerObjects(); + return FALSE; + } + } + + // If mandatory labels are supported, set it to the lowest possible level. + if (WE_WindowsVersion >= WINDOWS_VISTA) + { + static SID_IDENTIFIER_AUTHORITY mandatoryLabelAuthority = SECURITY_MANDATORY_LABEL_AUTHORITY; + + RtlCreateSecurityDescriptor(&securityDescriptor, SECURITY_DESCRIPTOR_REVISION); + + sacl = (PACL)saclBuffer; + RtlCreateAcl(sacl, sizeof(saclBuffer), ACL_REVISION); + + mandatoryLabelAce = (PSYSTEM_MANDATORY_LABEL_ACE)mandatoryLabelAceBuffer; + mandatoryLabelAce->Header.AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE; + mandatoryLabelAce->Header.AceFlags = 0; + mandatoryLabelAce->Header.AceSize = sizeof(mandatoryLabelAceBuffer); + mandatoryLabelAce->Mask = SYSTEM_MANDATORY_LABEL_NO_WRITE_UP; + + sid = (PSID)&mandatoryLabelAce->SidStart; + RtlInitializeSid(sid, &mandatoryLabelAuthority, 1); + *RtlSubAuthoritySid(sid, 0) = SECURITY_MANDATORY_LOW_RID; + + if (NT_SUCCESS(RtlAddAce(sacl, ACL_REVISION, MAXULONG32, mandatoryLabelAce, sizeof(mandatoryLabelAceBuffer)))) + { + if (NT_SUCCESS(RtlSetSaclSecurityDescriptor(&securityDescriptor, TRUE, sacl, FALSE))) + { + NtSetSecurityObject(WeServerSharedSection, LABEL_SECURITY_INFORMATION, &securityDescriptor); + NtSetSecurityObject(WeServerSharedSectionLock, LABEL_SECURITY_INFORMATION, &securityDescriptor); + NtSetSecurityObject(WeServerSharedSectionEvent, LABEL_SECURITY_INFORMATION, &securityDescriptor); + } + } + } + + return TRUE; +} + +BOOLEAN WepOpenServerObjects( + VOID + ) +{ + OBJECT_ATTRIBUTES objectAttributes; + WCHAR buffer[256]; + UNICODE_STRING objectName; + + if (!WeServerSharedSection) + { + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + if (!NT_SUCCESS(NtOpenSection( + &WeServerSharedSection, + SECTION_ALL_ACCESS, + &objectAttributes + ))) + { + return FALSE; + } + } + + if (!WeServerSharedData) + { + PVOID viewBase; + SIZE_T viewSize; + + viewBase = NULL; + viewSize = sizeof(WE_HOOK_SHARED_DATA); + + if (!NT_SUCCESS(NtMapViewOfSection( + WeServerSharedSection, + NtCurrentProcess(), + &viewBase, + 0, + 0, + NULL, + &viewSize, + ViewShare, + 0, + PAGE_READWRITE + ))) + { + WepCloseServerObjects(); + return FALSE; + } + + WeServerSharedData = viewBase; + } + + if (!WeServerSharedSectionLock) + { + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_LOCK_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + if (!NT_SUCCESS(NtOpenMutant( + &WeServerSharedSectionLock, + MUTANT_ALL_ACCESS, + &objectAttributes + ))) + { + WepCloseServerObjects(); + return FALSE; + } + } + + if (!WeServerSharedSectionEvent) + { + WeFormatLocalObjectName(WE_SERVER_SHARED_SECTION_EVENT_NAME, buffer, &objectName); + InitializeObjectAttributes(&objectAttributes, &objectName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + if (!NT_SUCCESS(NtOpenEvent( + &WeServerSharedSectionEvent, + EVENT_ALL_ACCESS, + &objectAttributes + ))) + { + WepCloseServerObjects(); + return FALSE; + } + } + + return TRUE; +} + +VOID WepCloseServerObjects( + VOID + ) +{ + if (WeServerSharedSection) + { + NtClose(WeServerSharedSection); + WeServerSharedSection = NULL; + } + + if (WeServerSharedData) + { + NtUnmapViewOfSection(NtCurrentProcess(), WeServerSharedData); + WeServerSharedData = NULL; + } + + if (WeServerSharedSectionLock) + { + NtClose(WeServerSharedSectionLock); + WeServerSharedSectionLock = NULL; + } + + if (WeServerSharedSectionEvent) + { + NtClose(WeServerSharedSectionEvent); + WeServerSharedSectionEvent = NULL; + } +} + +BOOLEAN WeIsServerActive( + VOID + ) +{ + if (WepOpenServerObjects()) + { + WepCloseServerObjects(); + + return TRUE; + } + else + { + return FALSE; + } +} + +BOOLEAN WeLockServerSharedData( + _Out_ PWE_HOOK_SHARED_DATA *Data + ) +{ + LARGE_INTEGER timeout; + + if (!WeServerSharedSectionLock) + return FALSE; + + timeout.QuadPart = -WE_CLIENT_MESSAGE_TIMEOUT * PH_TIMEOUT_MS; + + if (NtWaitForSingleObject(WeServerSharedSectionLock, FALSE, &timeout) != WAIT_OBJECT_0) + return FALSE; + + *Data = WeServerSharedData; + + return TRUE; +} + +VOID WeUnlockServerSharedData( + VOID + ) +{ + NtReleaseMutant(WeServerSharedSectionLock, NULL); +} + +BOOLEAN WeSendServerRequest( + _In_ HWND hWnd + ) +{ + ULONG threadId; + ULONG processId; + LARGE_INTEGER timeout; + + if (!WeServerSharedData || !WeServerSharedSectionEvent) + return FALSE; + + threadId = GetWindowThreadProcessId(hWnd, &processId); + + if (UlongToHandle(processId) == NtCurrentProcessId()) + { + // We are trying to get information about the server. Call the procedure directly. + WepWriteClientData(hWnd); + return TRUE; + } + + // Call the client and wait for the client to finish. + + WeCurrentMessageId++; + WeServerSharedData->MessageId = WeCurrentMessageId; + NtResetEvent(WeServerSharedSectionEvent, NULL); + + if (!SendNotifyMessage(hWnd, WeServerMessage, (WPARAM)NtCurrentProcessId(), WeCurrentMessageId)) + return FALSE; + + timeout.QuadPart = -WE_CLIENT_MESSAGE_TIMEOUT * PH_TIMEOUT_MS; + + if (NtWaitForSingleObject(WeServerSharedSectionEvent, FALSE, &timeout) != STATUS_WAIT_0) + return FALSE; + + return TRUE; +} + +// Client + +VOID WeHookClientInitialization( + VOID + ) +{ + WeServerMessage = RegisterWindowMessage(WE_SERVER_MESSAGE_NAME); +} + +VOID WeHookClientUninitialization( + VOID + ) +{ + NOTHING; +} + +VOID WepWriteClientData( + _In_ HWND hwnd + ) +{ + WCHAR className[256]; + LOGICAL isUnicode; + + memset(&WeServerSharedData->c, 0, sizeof(WeServerSharedData->c)); + isUnicode = IsWindowUnicode(hwnd); + + if (isUnicode) + { + WeServerSharedData->c.WndProc = GetWindowLongPtrW(hwnd, GWLP_WNDPROC); + WeServerSharedData->c.DlgProc = GetWindowLongPtrW(hwnd, DWLP_DLGPROC); + } + else + { + WeServerSharedData->c.WndProc = GetWindowLongPtrA(hwnd, GWLP_WNDPROC); + WeServerSharedData->c.DlgProc = GetWindowLongPtrA(hwnd, DWLP_DLGPROC); + } + + if (!GetClassName(hwnd, className, sizeof(className) / sizeof(WCHAR))) + className[0] = 0; + + WeServerSharedData->c.ClassInfo.cbSize = sizeof(WNDCLASSEX); + GetClassInfoEx(NULL, className, &WeServerSharedData->c.ClassInfo); + + if (isUnicode) + WeServerSharedData->c.ClassInfo.lpfnWndProc = (PVOID)GetClassLongPtrW(hwnd, GCLP_WNDPROC); + else + WeServerSharedData->c.ClassInfo.lpfnWndProc = (PVOID)GetClassLongPtrA(hwnd, GCLP_WNDPROC); +} + +LRESULT CALLBACK WepCallWndProc( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + LRESULT result; + PCWPSTRUCT info; + + result = CallNextHookEx(NULL, nCode, wParam, lParam); + + info = (PCWPSTRUCT)lParam; + + if (info->message == WeServerMessage) + { + HANDLE serverProcessId; + ULONG messageId; + + serverProcessId = (HANDLE)info->wParam; + messageId = (ULONG)info->lParam; + + if (serverProcessId != NtCurrentProcessId()) + { + if (WepOpenServerObjects()) + { + if (WeServerSharedData->MessageId == messageId) + { + WepWriteClientData(info->hwnd); + NtSetEvent(WeServerSharedSectionEvent, NULL); + } + + WepCloseServerObjects(); + } + } + } + + return result; +} diff --git a/plugins/WindowExplorer/main.c b/plugins/WindowExplorer/main.c new file mode 100644 index 0000000..217bc29 --- /dev/null +++ b/plugins/WindowExplorer/main.c @@ -0,0 +1,390 @@ +/* + * Process Hacker Window Explorer - + * main program + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "wndexp.h" +#include "resource.h" + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI MainMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ProcessPropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +VOID NTAPI ThreadMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ); + +BOOLEAN IsHookClient; +PPH_PLUGIN PluginInstance; +PH_CALLBACK_REGISTRATION PluginLoadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginUnloadCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginShowOptionsCallbackRegistration; +PH_CALLBACK_REGISTRATION PluginMenuItemCallbackRegistration; +PH_CALLBACK_REGISTRATION MainMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessPropertiesInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ProcessMenuInitializingCallbackRegistration; +PH_CALLBACK_REGISTRATION ThreadMenuInitializingCallbackRegistration; + +LOGICAL DllMain( + _In_ HINSTANCE Instance, + _In_ ULONG Reason, + _Reserved_ PVOID Reserved + ) +{ + switch (Reason) + { + case DLL_PROCESS_ATTACH: + { + PPH_PLUGIN_INFORMATION info; + BOOLEAN isClient; + + isClient = FALSE; + + if (!GetModuleHandle(L"ProcessHacker.exe") || !WeGetProcedureAddress("PhLibImageBase")) + { + isClient = TRUE; + } + else + { + // WindowExplorer appears to be loading within Process Hacker. However, if there is + // already a server instance, the the hook will be active, and our DllMain routine + // will most likely be called before the plugin system is even initialized. Attempting + // to register a plugin would result in an access violation, so load as a client for now. + if (WeIsServerActive()) + isClient = TRUE; + } + + if (isClient) + { + // This DLL is being loaded not as a Process Hacker plugin, but as a hook. + IsHookClient = TRUE; + WeHookClientInitialization(); + + break; + } + + PluginInstance = PhRegisterPlugin(PLUGIN_NAME, Instance, &info); + + if (!PluginInstance) + return FALSE; + + info->DisplayName = L"Window Explorer"; + info->Author = L"wj32"; + info->Description = L"View and manipulate windows."; + info->Url = L"https://wj32.org/processhacker/forums/viewtopic.php?t=1116"; + info->HasOptions = FALSE; + + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackLoad), + LoadCallback, + NULL, + &PluginLoadCallbackRegistration + ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackUnload), + UnloadCallback, + NULL, + &PluginUnloadCallbackRegistration + ); + //PhRegisterCallback( + // PhGetPluginCallback(PluginInstance, PluginCallbackShowOptions), + // ShowOptionsCallback, + // NULL, + // &PluginShowOptionsCallbackRegistration + // ); + PhRegisterCallback( + PhGetPluginCallback(PluginInstance, PluginCallbackMenuItem), + MenuItemCallback, + NULL, + &PluginMenuItemCallbackRegistration + ); + + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackMainMenuInitializing), + MainMenuInitializingCallback, + NULL, + &MainMenuInitializingCallbackRegistration + ); + //PhRegisterCallback( + // PhGetGeneralCallback(GeneralCallbackProcessPropertiesInitializing), + // ProcessPropertiesInitializingCallback, + // NULL, + // &ProcessPropertiesInitializingCallbackRegistration + // ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackProcessMenuInitializing), + ProcessMenuInitializingCallback, + NULL, + &ProcessMenuInitializingCallbackRegistration + ); + PhRegisterCallback( + PhGetGeneralCallback(GeneralCallbackThreadMenuInitializing), + ThreadMenuInitializingCallback, + NULL, + &ThreadMenuInitializingCallbackRegistration + ); + + { + static PH_SETTING_CREATE settings[] = + { + { IntegerSettingType, SETTING_NAME_SHOW_DESKTOP_WINDOWS, L"0" }, + { StringSettingType, SETTING_NAME_WINDOW_TREE_LIST_COLUMNS, L"" }, + { IntegerPairSettingType, SETTING_NAME_WINDOWS_WINDOW_POSITION, L"100,100" }, + { ScalableIntegerPairSettingType, SETTING_NAME_WINDOWS_WINDOW_SIZE, L"@96|690,540" } + }; + + PhAddSettings(settings, sizeof(settings) / sizeof(PH_SETTING_CREATE)); + } + } + break; + case DLL_PROCESS_DETACH: + { + if (IsHookClient) + { + WeHookClientUninitialization(); + } + } + break; + } + + return TRUE; +} + +VOID NTAPI LoadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI UnloadCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + WeHookServerUninitialization(); +} + +VOID NTAPI ShowOptionsCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +BOOL CALLBACK WepEnumDesktopProc( + _In_ LPTSTR lpszDesktop, + _In_ LPARAM lParam + ) +{ + PhAddItemList((PPH_LIST)lParam, PhaCreateString(lpszDesktop)->Buffer); + + return TRUE; +} + +VOID NTAPI MenuItemCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_ITEM menuItem = Parameter; + + switch (menuItem->Id) + { + case ID_VIEW_WINDOWS: + { + WE_WINDOW_SELECTOR selector; + + selector.Type = WeWindowSelectorAll; + WeShowWindowsDialog(WE_PhMainWndHandle, &selector); + } + break; + case ID_VIEW_DESKTOPWINDOWS: + { + PPH_LIST desktopNames; + PPH_STRING selectedChoice = NULL; + + desktopNames = PH_AUTO(PhCreateList(4)); + EnumDesktops(GetProcessWindowStation(), WepEnumDesktopProc, (LPARAM)desktopNames); + + if (PhaChoiceDialog( + WE_PhMainWndHandle, + L"Desktop Windows", + L"Display windows for the following desktop:", + (PWSTR *)desktopNames->Items, + desktopNames->Count, + NULL, + PH_CHOICE_DIALOG_CHOICE, + &selectedChoice, + NULL, + NULL + )) + { + WE_WINDOW_SELECTOR selector; + + selector.Type = WeWindowSelectorDesktop; + PhSetReference(&selector.Desktop.DesktopName, selectedChoice); + WeShowWindowsDialog(WE_PhMainWndHandle, &selector); + } + } + break; + case ID_PROCESS_WINDOWS: + { + WE_WINDOW_SELECTOR selector; + + selector.Type = WeWindowSelectorProcess; + selector.Process.ProcessId = ((PPH_PROCESS_ITEM)menuItem->Context)->ProcessId; + WeShowWindowsDialog(WE_PhMainWndHandle, &selector); + } + break; + case ID_THREAD_WINDOWS: + { + WE_WINDOW_SELECTOR selector; + + selector.Type = WeWindowSelectorThread; + selector.Thread.ThreadId = ((PPH_THREAD_ITEM)menuItem->Context)->ThreadId; + WeShowWindowsDialog(WE_PhMainWndHandle, &selector); + } + break; + } +} + +VOID NTAPI MainMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + ULONG insertIndex; + PPH_EMENU_ITEM menuItem; + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + + if (menuInfo->u.MainMenu.SubMenuIndex != PH_MENU_ITEM_LOCATION_VIEW) + return; + + if (menuItem = PhFindEMenuItem(menuInfo->Menu, PH_EMENU_FIND_STARTSWITH, L"System Information", 0)) + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, menuItem) + 1; + else + insertIndex = 0; + + PhInsertEMenuItem(menuInfo->Menu, menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_VIEW_WINDOWS, L"Windows", NULL), insertIndex); + + if (PhGetIntegerSetting(SETTING_NAME_SHOW_DESKTOP_WINDOWS)) + { + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, menuItem) + 1; + + PhInsertEMenuItem(menuInfo->Menu, PhPluginCreateEMenuItem(PluginInstance, 0, ID_VIEW_DESKTOPWINDOWS, L"Desktop Windows...", NULL), insertIndex); + } +} + +VOID NTAPI ProcessPropertiesInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + NOTHING; +} + +VOID NTAPI ProcessMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_PROCESS_ITEM processItem; + ULONG flags; + PPH_EMENU_ITEM miscMenu; + + if (menuInfo->u.Process.NumberOfProcesses == 1) + processItem = menuInfo->u.Process.Processes[0]; + else + processItem = NULL; + + flags = 0; + + if (!processItem) + flags = PH_EMENU_DISABLED; + + miscMenu = PhFindEMenuItem(menuInfo->Menu, 0, L"Miscellaneous", 0); + + if (miscMenu) + { + PhInsertEMenuItem(miscMenu, PhPluginCreateEMenuItem(PluginInstance, flags, ID_PROCESS_WINDOWS, L"Windows", processItem), -1); + } +} + +VOID NTAPI ThreadMenuInitializingCallback( + _In_opt_ PVOID Parameter, + _In_opt_ PVOID Context + ) +{ + PPH_PLUGIN_MENU_INFORMATION menuInfo = Parameter; + PPH_THREAD_ITEM threadItem; + ULONG insertIndex; + PPH_EMENU_ITEM menuItem; + + if (menuInfo->u.Thread.NumberOfThreads == 1) + threadItem = menuInfo->u.Thread.Threads[0]; + else + threadItem = NULL; + + if (menuItem = PhFindEMenuItem(menuInfo->Menu, 0, L"Token", 0)) + insertIndex = PhIndexOfEMenuItem(menuInfo->Menu, menuItem) + 1; + else + insertIndex = 0; + + PhInsertEMenuItem(menuInfo->Menu, menuItem = PhPluginCreateEMenuItem(PluginInstance, 0, ID_THREAD_WINDOWS, + L"Windows", threadItem), insertIndex); + + if (!threadItem) menuItem->Flags |= PH_EMENU_DISABLED; +} diff --git a/plugins/WindowExplorer/resource.h b/plugins/WindowExplorer/resource.h new file mode 100644 index 0000000..0e912df --- /dev/null +++ b/plugins/WindowExplorer/resource.h @@ -0,0 +1,81 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by WindowExplorer.rc +// +#define IDD_WNDPROPS 9 +#define IDD_WNDLIST 101 +#define ID_VIEW_WINDOWS 101 +#define ID_THREAD_WINDOWS 102 +#define ID_PROCESS_WINDOWS 103 +#define IDR_WINDOW 103 +#define ID_SHOWCONTEXTMENU 104 +#define IDD_WNDGENERAL 104 +#define ID_VIEW_DESKTOPWINDOWS 105 +#define IDD_WNDSTYLES 105 +#define IDD_WNDCLASS 106 +#define IDD_WNDGENERAL1 106 +#define IDC_LIST 1001 +#define IDC_REFRESH 1002 +#define IDC_TEXT 1009 +#define IDC_RECTANGLE 1010 +#define IDC_NORMALRECTANGLE 1011 +#define IDC_CLIENTRECTANGLE 1012 +#define IDC_THREAD 1016 +#define IDC_STYLESLIST 1017 +#define IDC_STYLES 1018 +#define IDC_EXTENDEDSTYLES 1019 +#define IDC_EXTENDEDSTYLESLIST 1020 +#define IDC_INSTANCEHANDLE 1021 +#define IDC_MENUHANDLE 1022 +#define IDC_WINDOWPROC 1023 +#define IDC_WINDOWPROC2 1024 +#define IDC_DIALOGPROC 1024 +#define IDC_USERDATA 1025 +#define IDC_NAME 1026 +#define IDC_ATOM 1028 +#define IDC_CURSORHANDLE 1029 +#define IDC_ICONHANDLE 1030 +#define IDC_ICONHANDLE2 1031 +#define IDC_SMALLICONHANDLE 1031 +#define IDC_BACKGROUNDBRUSH 1032 +#define IDC_MENUNAME 1033 +#define IDC_UNICODE 1034 +#define IDC_CTRLID 1035 +#define ID_WINDOW_GOTOTHREAD 40001 +#define ID_WINDOW_COPY 40002 +#define ID_WINDOW_PROPERTIES 40003 +#define ID_WINDOW_BRINGTOFRONT 40004 +#define ID_WINDOW_RESTORE 40005 +#define ID_WINDOW_MINIMIZE 40006 +#define ID_WINDOW_MAXIMIZE 40007 +#define ID_WINDOW_CLOSE 40008 +#define ID_WINDOW_SHOW 40009 +#define ID_WINDOW_HIGHLIGHT 40010 +#define ID_WINDOW_SHOWHIDE 40011 +#define ID_WINDOW_ENABLE 40012 +#define ID_WINDOW_ENABLEDISABLE 40013 +#define ID_WINDOW_ALWAYSONTOP 40014 +#define ID_WINDOW_OPACITY 40015 +#define ID_OPACITY_10 40016 +#define ID_OPACITY_20 40017 +#define ID_OPACITY_30 40018 +#define ID_OPACITY_40 40019 +#define ID_OPACITY_50 40020 +#define ID_OPACITY_60 40021 +#define ID_OPACITY_70 40022 +#define ID_OPACITY_80 40023 +#define ID_OPACITY_90 40024 +#define ID_OPACITY_OPAQUE 40025 +#define ID_WINDOW_VISIBLE 40026 +#define ID_WINDOW_ENABLED 40027 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40028 +#define _APS_NEXT_CONTROL_VALUE 1035 +#define _APS_NEXT_SYMED_VALUE 106 +#endif +#endif diff --git a/plugins/WindowExplorer/utils.c b/plugins/WindowExplorer/utils.c new file mode 100644 index 0000000..55baeb6 --- /dev/null +++ b/plugins/WindowExplorer/utils.c @@ -0,0 +1,104 @@ +/* + * Process Hacker Window Explorer - + * utility functions + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "wndexp.h" + +// WARNING: No functions from ProcessHacker.exe should be used in this file! + +PVOID WeGetProcedureAddress( + _In_ PSTR Name + ) +{ + static PVOID imageBase = NULL; + + if (!imageBase) + imageBase = GetModuleHandle(L"ProcessHacker.exe"); + + return (PVOID)GetProcAddress(imageBase, Name); +} + +VOID WeFormatLocalObjectName( + _In_ PWSTR OriginalName, + _Inout_updates_(256) PWCHAR Buffer, + _Out_ PUNICODE_STRING ObjectName + ) +{ + SIZE_T length; + SIZE_T originalNameLength; + + // Sessions other than session 0 require SeCreateGlobalPrivilege. + if (NtCurrentPeb()->SessionId != 0) + { + memcpy(Buffer, L"\\Sessions\\", 10 * sizeof(WCHAR)); + _ultow(NtCurrentPeb()->SessionId, Buffer + 10, 10); + length = wcslen(Buffer); + originalNameLength = wcslen(OriginalName); + memcpy(Buffer + length, OriginalName, (originalNameLength + 1) * sizeof(WCHAR)); + length += originalNameLength; + + ObjectName->Buffer = Buffer; + ObjectName->MaximumLength = (ObjectName->Length = (USHORT)(length * sizeof(WCHAR))) + sizeof(WCHAR); + } + else + { + RtlInitUnicodeString(ObjectName, OriginalName); + } +} + +VOID WeInvertWindowBorder( + _In_ HWND hWnd + ) +{ + RECT rect; + HDC hdc; + + GetWindowRect(hWnd, &rect); + hdc = GetWindowDC(hWnd); + + if (hdc) + { + ULONG penWidth = GetSystemMetrics(SM_CXBORDER) * 3; + INT oldDc; + HPEN pen; + HBRUSH brush; + + oldDc = SaveDC(hdc); + + // Get an inversion effect. + SetROP2(hdc, R2_NOT); + + pen = CreatePen(PS_INSIDEFRAME, penWidth, RGB(0x00, 0x00, 0x00)); + SelectObject(hdc, pen); + + brush = GetStockObject(NULL_BRUSH); + SelectObject(hdc, brush); + + // Draw the rectangle. + Rectangle(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top); + + // Cleanup. + DeleteObject(pen); + + RestoreDC(hdc, oldDc); + ReleaseDC(hWnd, hdc); + } +} diff --git a/plugins/WindowExplorer/wnddlg.c b/plugins/WindowExplorer/wnddlg.c new file mode 100644 index 0000000..ed29aad --- /dev/null +++ b/plugins/WindowExplorer/wnddlg.c @@ -0,0 +1,729 @@ +/* + * Process Hacker Window Explorer - + * window tree dialog + * + * Copyright (C) 2016 dmex + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "wndexp.h" +#include "resource.h" +#include + +typedef struct _WINDOWS_CONTEXT +{ + HWND TreeNewHandle; + WE_WINDOW_TREE_CONTEXT TreeContext; + WE_WINDOW_SELECTOR Selector; + + PH_LAYOUT_MANAGER LayoutManager; + + HWND HighlightingWindow; + ULONG HighlightingWindowCount; +} WINDOWS_CONTEXT, *PWINDOWS_CONTEXT; + +VOID WepShowWindowsDialogCallback( + _In_ PVOID Parameter + ); + +INT_PTR CALLBACK WepWindowsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +static RECT MinimumSize = { -1, -1, -1, -1 }; + +VOID WeShowWindowsDialog( + _In_ HWND ParentWindowHandle, + _In_ PWE_WINDOW_SELECTOR Selector + ) +{ + PWINDOWS_CONTEXT context; + + context = PhAllocate(sizeof(WINDOWS_CONTEXT)); + memset(context, 0, sizeof(WINDOWS_CONTEXT)); + memcpy(&context->Selector, Selector, sizeof(WE_WINDOW_SELECTOR)); + + ProcessHacker_Invoke(WE_PhMainWndHandle, WepShowWindowsDialogCallback, context); +} + +VOID WepShowWindowsDialogCallback( + _In_ PVOID Parameter + ) +{ + HWND hwnd; + PWINDOWS_CONTEXT context = Parameter; + + hwnd = CreateDialogParam( + PluginInstance->DllBase, + MAKEINTRESOURCE(IDD_WNDLIST), + NULL, + WepWindowsDlgProc, + (LPARAM)context + ); + ShowWindow(hwnd, SW_SHOW); +} + +VOID WepDeleteWindowSelector( + _In_ PWE_WINDOW_SELECTOR Selector + ) +{ + switch (Selector->Type) + { + case WeWindowSelectorDesktop: + PhDereferenceObject(Selector->Desktop.DesktopName); + break; + } +} + +VOID WepFillWindowInfo( + _In_ PWE_WINDOW_NODE Node + ) +{ + HWND hwnd; + ULONG threadId; + ULONG processId; + + hwnd = Node->WindowHandle; + + GetClassName(hwnd, Node->WindowClass, sizeof(Node->WindowClass) / sizeof(WCHAR)); + Node->WindowText = PhGetWindowText(hwnd); + + if (!Node->WindowText) + Node->WindowText = PhReferenceEmptyString(); + + threadId = GetWindowThreadProcessId(hwnd, &processId); + Node->ClientId.UniqueProcess = UlongToHandle(processId); + Node->ClientId.UniqueThread = UlongToHandle(threadId); + + Node->WindowVisible = !!IsWindowVisible(hwnd); + Node->HasChildren = !!FindWindowEx(hwnd, NULL, NULL, NULL); +} + +VOID WepAddChildWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _In_opt_ PWE_WINDOW_NODE ParentNode, + _In_ HWND hwnd + ) +{ + PWE_WINDOW_NODE childNode; + + childNode = WeAddWindowNode(Context); + childNode->WindowHandle = hwnd; + WepFillWindowInfo(childNode); + + childNode->Node.Expanded = FALSE; + + if (ParentNode) + { + // This is a child node. + childNode->Parent = ParentNode; + PhAddItemList(ParentNode->Children, childNode); + } + else + { + // This is a root node. + PhAddItemList(Context->NodeRootList, childNode); + } +} + +VOID WepAddChildWindows( + _In_ PWINDOWS_CONTEXT Context, + _In_opt_ PWE_WINDOW_NODE ParentNode, + _In_ HWND hwnd, + _In_opt_ HANDLE FilterProcessId, + _In_opt_ HANDLE FilterThreadId + ) +{ + HWND childWindow = NULL; + ULONG i = 0; + + // We use FindWindowEx because EnumWindows doesn't return Metro app windows. + // Set a reasonable limit to prevent infinite loops. + while (i < 0x800 && (childWindow = FindWindowEx(hwnd, childWindow, NULL, NULL))) + { + ULONG processId; + ULONG threadId; + + threadId = GetWindowThreadProcessId(childWindow, &processId); + + if ( + (!FilterProcessId || UlongToHandle(processId) == FilterProcessId) && + (!FilterThreadId || UlongToHandle(threadId) == FilterThreadId) + ) + { + WepAddChildWindowNode(&Context->TreeContext, ParentNode, childWindow); + } + + i++; + } +} + +BOOL CALLBACK WepEnumDesktopWindowsProc( + _In_ HWND hwnd, + _In_ LPARAM lParam + ) +{ + PWINDOWS_CONTEXT context = (PWINDOWS_CONTEXT)lParam; + + WepAddChildWindowNode(&context->TreeContext, NULL, hwnd); + + return TRUE; +} + +VOID WepAddDesktopWindows( + _In_ PWINDOWS_CONTEXT Context, + _In_ PWSTR DesktopName + ) +{ + HDESK desktopHandle; + + if (desktopHandle = OpenDesktop(DesktopName, 0, FALSE, DESKTOP_ENUMERATE)) + { + EnumDesktopWindows(desktopHandle, WepEnumDesktopWindowsProc, (LPARAM)Context); + CloseDesktop(desktopHandle); + } +} + +VOID WepRefreshWindows( + _In_ PWINDOWS_CONTEXT Context + ) +{ + TreeNew_SetRedraw(Context->TreeNewHandle, FALSE); + WeClearWindowTree(&Context->TreeContext); + TreeNew_NodesStructured(Context->TreeNewHandle); + + switch (Context->Selector.Type) + { + case WeWindowSelectorAll: + { + PWE_WINDOW_NODE desktopNode; + + desktopNode = WeAddWindowNode(&Context->TreeContext); + desktopNode->WindowHandle = GetDesktopWindow(); + WepFillWindowInfo(desktopNode); + + PhAddItemList(Context->TreeContext.NodeRootList, desktopNode); + + WepAddChildWindows(Context, desktopNode, desktopNode->WindowHandle, NULL, NULL); + + desktopNode->HasChildren = TRUE; + desktopNode->Opened = TRUE; + } + break; + case WeWindowSelectorThread: + { + WepAddChildWindows(Context, NULL, GetDesktopWindow(), NULL, Context->Selector.Thread.ThreadId); + } + break; + case WeWindowSelectorProcess: + { + WepAddChildWindows(Context, NULL, GetDesktopWindow(), Context->Selector.Process.ProcessId, NULL); + } + break; + case WeWindowSelectorDesktop: + { + WepAddDesktopWindows(Context, Context->Selector.Desktop.DesktopName->Buffer); + } + break; + } + + TreeNew_SetRedraw(Context->TreeNewHandle, TRUE); +} + +PPH_STRING WepGetWindowTitleForSelector( + _In_ PWE_WINDOW_SELECTOR Selector + ) +{ + switch (Selector->Type) + { + case WeWindowSelectorAll: + { + return PhCreateString(L"Windows - All"); + } + break; + case WeWindowSelectorThread: + { + return PhFormatString(L"Windows - Thread %lu", HandleToUlong(Selector->Thread.ThreadId)); + } + break; + case WeWindowSelectorProcess: + { + CLIENT_ID clientId; + + clientId.UniqueProcess = Selector->Process.ProcessId; + clientId.UniqueThread = NULL; + + return PhConcatStrings2(L"Windows - ", PH_AUTO_T(PH_STRING, PhGetClientIdName(&clientId))->Buffer); + } + break; + case WeWindowSelectorDesktop: + { + return PhFormatString(L"Windows - Desktop \"%s\"", Selector->Desktop.DesktopName->Buffer); + } + break; + default: + return PhCreateString(L"Windows"); + } +} + +INT_PTR CALLBACK WepWindowsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWINDOWS_CONTEXT context; + + if (uMsg == WM_INITDIALOG) + { + context = (PWINDOWS_CONTEXT)lParam; + SetProp(hwndDlg, L"Context", (HANDLE)context); + } + else + { + context = (PWINDOWS_CONTEXT)GetProp(hwndDlg, L"Context"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"Context"); + } + + if (!context) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + PPH_STRING windowTitle; + PH_RECTANGLE windowRectangle; + + context->TreeNewHandle = GetDlgItem(hwndDlg, IDC_LIST); + WeInitializeWindowTree(hwndDlg, context->TreeNewHandle, &context->TreeContext); + + PhRegisterDialog(hwndDlg); + + PhInitializeLayoutManager(&context->LayoutManager, hwndDlg); + PhAddLayoutItem(&context->LayoutManager, GetDlgItem(hwndDlg, IDC_LIST), NULL, PH_ANCHOR_ALL); + + if (MinimumSize.left == -1) + { + RECT rect; + + rect.left = 0; + rect.top = 0; + rect.right = 160; + rect.bottom = 100; + MapDialogRect(hwndDlg, &rect); + MinimumSize = rect; + MinimumSize.left = 0; + } + + // Set up the window position and size. + + windowRectangle.Position = PhGetIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION); + windowRectangle.Size = PhGetScalableIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_SIZE, TRUE).Pair; + PhAdjustRectangleToWorkingArea(NULL, &windowRectangle); + + MoveWindow(hwndDlg, windowRectangle.Left, windowRectangle.Top, + windowRectangle.Width, windowRectangle.Height, FALSE); + + // Implement cascading by saving an offsetted rectangle. + windowRectangle.Left += 20; + windowRectangle.Top += 20; + PhSetIntegerPairSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION, windowRectangle.Position); + + windowTitle = PH_AUTO(WepGetWindowTitleForSelector(&context->Selector)); + SetWindowText(hwndDlg, windowTitle->Buffer); + + WepRefreshWindows(context); + } + break; + case WM_DESTROY: + { + PhSaveWindowPlacementToSetting(SETTING_NAME_WINDOWS_WINDOW_POSITION, SETTING_NAME_WINDOWS_WINDOW_SIZE, hwndDlg); + + PhDeleteLayoutManager(&context->LayoutManager); + PhUnregisterDialog(hwndDlg); + + WeDeleteWindowTree(&context->TreeContext); + WepDeleteWindowSelector(&context->Selector); + PhFree(context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDCANCEL: + //case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_REFRESH: + WepRefreshWindows(context); + break; + case ID_SHOWCONTEXTMENU: + { + POINT point; + PWE_WINDOW_NODE *windows; + ULONG numberOfWindows; + PPH_EMENU menu; + + point.x = (SHORT)LOWORD(lParam); + point.y = (SHORT)HIWORD(lParam); + + WeGetSelectedWindowNodes( + &context->TreeContext, + &windows, + &numberOfWindows + ); + + if (numberOfWindows != 0) + { + menu = PhCreateEMenu(); + PhLoadResourceEMenuItem(menu, PluginInstance->DllBase, MAKEINTRESOURCE(IDR_WINDOW), 0); + PhSetFlagsEMenuItem(menu, ID_WINDOW_PROPERTIES, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT); + + if (numberOfWindows == 1) + { + WINDOWPLACEMENT placement = { sizeof(placement) }; + BYTE alpha; + ULONG flags; + ULONG i; + ULONG id; + + // State + + GetWindowPlacement(windows[0]->WindowHandle, &placement); + + if (placement.showCmd == SW_MINIMIZE) + PhSetFlagsEMenuItem(menu, ID_WINDOW_MINIMIZE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + else if (placement.showCmd == SW_MAXIMIZE) + PhSetFlagsEMenuItem(menu, ID_WINDOW_MAXIMIZE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + else if (placement.showCmd == SW_NORMAL) + PhSetFlagsEMenuItem(menu, ID_WINDOW_RESTORE, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + + // Visible + + PhSetFlagsEMenuItem(menu, ID_WINDOW_VISIBLE, PH_EMENU_CHECKED, + (GetWindowLong(windows[0]->WindowHandle, GWL_STYLE) & WS_VISIBLE) ? PH_EMENU_CHECKED : 0); + + // Enabled + + PhSetFlagsEMenuItem(menu, ID_WINDOW_ENABLED, PH_EMENU_CHECKED, + !(GetWindowLong(windows[0]->WindowHandle, GWL_STYLE) & WS_DISABLED) ? PH_EMENU_CHECKED : 0); + + // Always on Top + + PhSetFlagsEMenuItem(menu, ID_WINDOW_ALWAYSONTOP, PH_EMENU_CHECKED, + (GetWindowLong(windows[0]->WindowHandle, GWL_EXSTYLE) & WS_EX_TOPMOST) ? PH_EMENU_CHECKED : 0); + + // Opacity + + if (GetLayeredWindowAttributes(windows[0]->WindowHandle, NULL, &alpha, &flags)) + { + if (!(flags & LWA_ALPHA)) + alpha = 255; + } + else + { + alpha = 255; + } + + if (alpha == 255) + { + id = ID_OPACITY_OPAQUE; + } + else + { + id = 0; + + // Due to integer division, we cannot use simple arithmetic to calculate which menu item to check. + for (i = 0; i < 10; i++) + { + if (alpha == (BYTE)(255 * (i + 1) / 10)) + { + id = ID_OPACITY_10 + i; + break; + } + } + } + + if (id != 0) + { + PhSetFlagsEMenuItem(menu, id, PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK, + PH_EMENU_CHECKED | PH_EMENU_RADIOCHECK); + } + } + else + { + PhSetFlagsAllEMenuItems(menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED); + PhSetFlagsEMenuItem(menu, ID_WINDOW_COPY, PH_EMENU_DISABLED, 0); + } + + PhShowEMenu(menu, hwndDlg, PH_EMENU_SHOW_SEND_COMMAND | PH_EMENU_SHOW_LEFTRIGHT, PH_ALIGN_LEFT | PH_ALIGN_TOP, point.x, point.y); + PhDestroyEMenu(menu); + } + } + break; + case ID_WINDOW_BRINGTOFRONT: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + WINDOWPLACEMENT placement = { sizeof(placement) }; + + GetWindowPlacement(selectedNode->WindowHandle, &placement); + + if (placement.showCmd == SW_MINIMIZE) + ShowWindowAsync(selectedNode->WindowHandle, SW_RESTORE); + else + SetForegroundWindow(selectedNode->WindowHandle); + } + } + break; + case ID_WINDOW_RESTORE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + ShowWindowAsync(selectedNode->WindowHandle, SW_RESTORE); + } + } + break; + case ID_WINDOW_MINIMIZE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + ShowWindowAsync(selectedNode->WindowHandle, SW_MINIMIZE); + } + } + break; + case ID_WINDOW_MAXIMIZE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + ShowWindowAsync(selectedNode->WindowHandle, SW_MAXIMIZE); + } + } + break; + case ID_WINDOW_CLOSE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + PostMessage(selectedNode->WindowHandle, WM_CLOSE, 0, 0); + } + } + break; + case ID_WINDOW_VISIBLE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + if (IsWindowVisible(selectedNode->WindowHandle)) + { + selectedNode->WindowVisible = FALSE; + ShowWindowAsync(selectedNode->WindowHandle, SW_HIDE); + } + else + { + selectedNode->WindowVisible = TRUE; + ShowWindowAsync(selectedNode->WindowHandle, SW_SHOW); + } + + PhInvalidateTreeNewNode(&selectedNode->Node, TN_CACHE_COLOR); + TreeNew_InvalidateNode(context->TreeNewHandle, &selectedNode->Node); + } + } + break; + case ID_WINDOW_ENABLED: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + EnableWindow(selectedNode->WindowHandle, !IsWindowEnabled(selectedNode->WindowHandle)); + } + } + break; + case ID_WINDOW_ALWAYSONTOP: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + LOGICAL topMost; + + topMost = GetWindowLong(selectedNode->WindowHandle, GWL_EXSTYLE) & WS_EX_TOPMOST; + SetWindowPos(selectedNode->WindowHandle, topMost ? HWND_NOTOPMOST : HWND_TOPMOST, + 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); + } + } + break; + case ID_OPACITY_10: + case ID_OPACITY_20: + case ID_OPACITY_30: + case ID_OPACITY_40: + case ID_OPACITY_50: + case ID_OPACITY_60: + case ID_OPACITY_70: + case ID_OPACITY_80: + case ID_OPACITY_90: + case ID_OPACITY_OPAQUE: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + ULONG opacity; + + opacity = ((ULONG)LOWORD(wParam) - ID_OPACITY_10) + 1; + + if (opacity == 10) + { + // Remove the WS_EX_LAYERED bit since it is not needed. + PhSetWindowExStyle(selectedNode->WindowHandle, WS_EX_LAYERED, 0); + RedrawWindow(selectedNode->WindowHandle, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); + } + else + { + // Add the WS_EX_LAYERED bit so opacity will work. + PhSetWindowExStyle(selectedNode->WindowHandle, WS_EX_LAYERED, WS_EX_LAYERED); + SetLayeredWindowAttributes(selectedNode->WindowHandle, 0, (BYTE)(255 * opacity / 10), LWA_ALPHA); + } + } + } + break; + case ID_WINDOW_HIGHLIGHT: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + if (context->HighlightingWindow) + { + if (context->HighlightingWindowCount & 1) + WeInvertWindowBorder(context->HighlightingWindow); + } + + context->HighlightingWindow = selectedNode->WindowHandle; + context->HighlightingWindowCount = 10; + SetTimer(hwndDlg, 9, 100, NULL); + } + } + break; + case ID_WINDOW_GOTOTHREAD: + { + PWE_WINDOW_NODE selectedNode; + PPH_PROCESS_ITEM processItem; + PPH_PROCESS_PROPCONTEXT propContext; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + { + if (processItem = PhReferenceProcessItem(selectedNode->ClientId.UniqueProcess)) + { + if (propContext = PhCreateProcessPropContext(WE_PhMainWndHandle, processItem)) + { + PhSetSelectThreadIdProcessPropContext(propContext, selectedNode->ClientId.UniqueThread); + PhShowProcessProperties(propContext); + PhDereferenceObject(propContext); + } + + PhDereferenceObject(processItem); + } + else + { + PhShowError(hwndDlg, L"The process does not exist."); + } + } + } + break; + case ID_WINDOW_PROPERTIES: + { + PWE_WINDOW_NODE selectedNode; + + if (selectedNode = WeGetSelectedWindowNode(&context->TreeContext)) + WeShowWindowProperties(hwndDlg, selectedNode->WindowHandle); + } + break; + case ID_WINDOW_COPY: + { + PPH_STRING text; + + text = PhGetTreeNewText(context->TreeNewHandle, 0); + PhSetClipboardString(hwndDlg, &text->sr); + PhDereferenceObject(text); + } + break; + } + } + break; + case WM_TIMER: + { + switch (wParam) + { + case 9: + { + WeInvertWindowBorder(context->HighlightingWindow); + + if (--context->HighlightingWindowCount == 0) + KillTimer(hwndDlg, 9); + } + break; + } + } + break; + case WM_SIZE: + { + PhLayoutManagerLayout(&context->LayoutManager); + } + break; + case WM_SIZING: + { + PhResizingMinimumSize((PRECT)lParam, wParam, MinimumSize.right, MinimumSize.bottom); + } + break; + case WM_WE_PLUSMINUS: + { + PWE_WINDOW_NODE node = (PWE_WINDOW_NODE)lParam; + + if (!node->Opened) + { + TreeNew_SetRedraw(context->TreeNewHandle, FALSE); + WepAddChildWindows(context, node, node->WindowHandle, NULL, NULL); + node->Opened = TRUE; + TreeNew_SetRedraw(context->TreeNewHandle, TRUE); + } + } + break; + } + + return FALSE; +} diff --git a/plugins/WindowExplorer/wndexp.h b/plugins/WindowExplorer/wndexp.h new file mode 100644 index 0000000..ed16fc8 --- /dev/null +++ b/plugins/WindowExplorer/wndexp.h @@ -0,0 +1,131 @@ +#ifndef WNDEXP_H +#define WNDEXP_H + +#include +#include "wndtree.h" + +extern BOOLEAN IsHookClient; +extern PPH_PLUGIN PluginInstance; + +#define PLUGIN_NAME L"ProcessHacker.WindowExplorer" +#define SETTING_NAME_SHOW_DESKTOP_WINDOWS (PLUGIN_NAME L".ShowDesktopWindows") +#define SETTING_NAME_WINDOW_TREE_LIST_COLUMNS (PLUGIN_NAME L".WindowTreeListColumns") +#define SETTING_NAME_WINDOWS_WINDOW_POSITION (PLUGIN_NAME L".WindowsWindowPosition") +#define SETTING_NAME_WINDOWS_WINDOW_SIZE (PLUGIN_NAME L".WindowsWindowSize") + +// hook + +#define WE_SERVER_MESSAGE_NAME L"WE_ServerMessage" +#define WE_SERVER_SHARED_SECTION_NAME L"\\BaseNamedObjects\\WeSharedSection" +#define WE_SERVER_SHARED_SECTION_LOCK_NAME L"\\BaseNamedObjects\\WeSharedSectionLock" +#define WE_SERVER_SHARED_SECTION_EVENT_NAME L"\\BaseNamedObjects\\WeSharedSectionEvent" +#define WE_CLIENT_MESSAGE_TIMEOUT 2000 + +typedef struct _WE_HOOK_SHARED_DATA +{ + ULONG MessageId; + + struct + { + ULONG_PTR WndProc; + ULONG_PTR DlgProc; + WNDCLASSEX ClassInfo; + } c; +} WE_HOOK_SHARED_DATA, *PWE_HOOK_SHARED_DATA; + +VOID WeHookServerInitialization( + VOID + ); + +VOID WeHookServerUninitialization( + VOID + ); + +BOOLEAN WeIsServerActive( + VOID + ); + +BOOLEAN WeLockServerSharedData( + _Out_ PWE_HOOK_SHARED_DATA *Data + ); + +VOID WeUnlockServerSharedData( + VOID + ); + +BOOLEAN WeSendServerRequest( + _In_ HWND hWnd + ); + +VOID WeHookClientInitialization( + VOID + ); + +VOID WeHookClientUninitialization( + VOID + ); + +// wnddlg + +typedef enum _WE_WINDOW_SELECTOR_TYPE +{ + WeWindowSelectorAll, + WeWindowSelectorProcess, + WeWindowSelectorThread, + WeWindowSelectorDesktop +} WE_WINDOW_SELECTOR_TYPE; + +typedef struct _WE_WINDOW_SELECTOR +{ + WE_WINDOW_SELECTOR_TYPE Type; + union + { + struct + { + HANDLE ProcessId; + } Process; + struct + { + HANDLE ThreadId; + } Thread; + struct + { + PPH_STRING DesktopName; + } Desktop; + }; +} WE_WINDOW_SELECTOR, *PWE_WINDOW_SELECTOR; + +VOID WeShowWindowsDialog( + _In_ HWND ParentWindowHandle, + _In_ PWE_WINDOW_SELECTOR Selector + ); + +#define WM_WE_PLUSMINUS (WM_APP + 1) + +// wndprp + +VOID WeShowWindowProperties( + _In_ HWND ParentWindowHandle, + _In_ HWND WindowHandle + ); + +// utils + +#define WE_PhMainWndHandle (*(HWND *)WeGetProcedureAddress("PhMainWndHandle")) +#define WE_WindowsVersion (*(ULONG *)WeGetProcedureAddress("WindowsVersion")) + +PVOID WeGetProcedureAddress( + _In_ PSTR Name + ); + +VOID WeFormatLocalObjectName( + _In_ PWSTR OriginalName, + _Inout_updates_(256) PWCHAR Buffer, + _Out_ PUNICODE_STRING ObjectName + ); + +VOID WeInvertWindowBorder( + _In_ HWND hWnd + ); + +#endif diff --git a/plugins/WindowExplorer/wndprp.c b/plugins/WindowExplorer/wndprp.c new file mode 100644 index 0000000..476bc69 --- /dev/null +++ b/plugins/WindowExplorer/wndprp.c @@ -0,0 +1,1207 @@ +/* + * Process Hacker Window Explorer - + * window properties + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +// Since the main message loop doesn't support property sheets, we need a separate thread to run +// property sheets on. + +#include "wndexp.h" +#include "resource.h" +#include +#include +#include + +#define NUMBER_OF_PAGES 4 +#define WEM_RESOLVE_DONE (WM_APP + 1234) + +typedef struct _WINDOW_PROPERTIES_CONTEXT +{ + LONG RefCount; + + HWND ParentWindowHandle; + + HWND WindowHandle; + CLIENT_ID ClientId; + PH_INITONCE SymbolProviderInitOnce; + PPH_SYMBOL_PROVIDER SymbolProvider; + LIST_ENTRY ResolveListHead; + PH_QUEUED_LOCK ResolveListLock; + + PPH_STRING WndProcSymbol; + ULONG WndProcResolving; + PPH_STRING DlgProcSymbol; + ULONG DlgProcResolving; + PPH_STRING ClassWndProcSymbol; + ULONG ClassWndProcResolving; + + BOOLEAN HookDataValid; + BOOLEAN HookDataSuccess; + ULONG_PTR WndProc; + ULONG_PTR DlgProc; + WNDCLASSEX ClassInfo; +} WINDOW_PROPERTIES_CONTEXT, *PWINDOW_PROPERTIES_CONTEXT; + +typedef struct _SYMBOL_RESOLVE_CONTEXT +{ + LIST_ENTRY ListEntry; + ULONG64 Address; + PPH_STRING Symbol; + PH_SYMBOL_RESOLVE_LEVEL ResolveLevel; + HWND NotifyWindow; + PWINDOW_PROPERTIES_CONTEXT Context; + ULONG Id; +} SYMBOL_RESOLVE_CONTEXT, *PSYMBOL_RESOLVE_CONTEXT; + +typedef struct _STRING_INTEGER_PAIR +{ + PWSTR String; + ULONG Integer; +} STRING_INTEGER_PAIR, *PSTRING_INTEGER_PAIR; + +VOID WepReferenceWindowPropertiesContext( + _Inout_ PWINDOW_PROPERTIES_CONTEXT Context + ); + +VOID WepDereferenceWindowPropertiesContext( + _Inout_ PWINDOW_PROPERTIES_CONTEXT Context + ); + +HWND WepCreateWindowProperties( + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ); + +INT CALLBACK WepPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ); + +LRESULT CALLBACK WepPropSheetWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +HPROPSHEETPAGE WepCommonCreatePage( + _In_ PWINDOW_PROPERTIES_CONTEXT Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ); + +INT CALLBACK WepCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ); + +NTSTATUS WepPropertiesThreadStart( + _In_ PVOID Parameter + ); + +INT_PTR CALLBACK WepWindowGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK WepWindowStylesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK WepWindowClassDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK WepWindowPropertiesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +#define DEFINE_PAIR(Symbol) { L#Symbol, Symbol } + +static STRING_INTEGER_PAIR WepStylePairs[] = +{ + DEFINE_PAIR(WS_POPUP), + DEFINE_PAIR(WS_CHILD), + DEFINE_PAIR(WS_MINIMIZE), + DEFINE_PAIR(WS_VISIBLE), + DEFINE_PAIR(WS_DISABLED), + DEFINE_PAIR(WS_CLIPSIBLINGS), + DEFINE_PAIR(WS_CLIPCHILDREN), + DEFINE_PAIR(WS_MAXIMIZE), + DEFINE_PAIR(WS_BORDER), + DEFINE_PAIR(WS_DLGFRAME), + DEFINE_PAIR(WS_VSCROLL), + DEFINE_PAIR(WS_HSCROLL), + DEFINE_PAIR(WS_SYSMENU), + DEFINE_PAIR(WS_THICKFRAME), + DEFINE_PAIR(WS_GROUP), + DEFINE_PAIR(WS_TABSTOP), + DEFINE_PAIR(WS_MINIMIZEBOX), + DEFINE_PAIR(WS_MAXIMIZEBOX) +}; + +static STRING_INTEGER_PAIR WepExtendedStylePairs[] = +{ + DEFINE_PAIR(WS_EX_DLGMODALFRAME), + DEFINE_PAIR(WS_EX_NOPARENTNOTIFY), + DEFINE_PAIR(WS_EX_TOPMOST), + DEFINE_PAIR(WS_EX_ACCEPTFILES), + DEFINE_PAIR(WS_EX_TRANSPARENT), + DEFINE_PAIR(WS_EX_MDICHILD), + DEFINE_PAIR(WS_EX_TOOLWINDOW), + DEFINE_PAIR(WS_EX_WINDOWEDGE), + DEFINE_PAIR(WS_EX_CLIENTEDGE), + DEFINE_PAIR(WS_EX_CONTEXTHELP), + DEFINE_PAIR(WS_EX_RIGHT), + DEFINE_PAIR(WS_EX_RTLREADING), + DEFINE_PAIR(WS_EX_LEFTSCROLLBAR), + DEFINE_PAIR(WS_EX_CONTROLPARENT), + DEFINE_PAIR(WS_EX_STATICEDGE), + DEFINE_PAIR(WS_EX_APPWINDOW), + DEFINE_PAIR(WS_EX_LAYERED), + DEFINE_PAIR(WS_EX_NOINHERITLAYOUT), + DEFINE_PAIR(WS_EX_LAYOUTRTL), + DEFINE_PAIR(WS_EX_COMPOSITED), + DEFINE_PAIR(WS_EX_NOACTIVATE) +}; + +static STRING_INTEGER_PAIR WepClassStylePairs[] = +{ + DEFINE_PAIR(CS_VREDRAW), + DEFINE_PAIR(CS_HREDRAW), + DEFINE_PAIR(CS_DBLCLKS), + DEFINE_PAIR(CS_OWNDC), + DEFINE_PAIR(CS_CLASSDC), + DEFINE_PAIR(CS_PARENTDC), + DEFINE_PAIR(CS_NOCLOSE), + DEFINE_PAIR(CS_SAVEBITS), + DEFINE_PAIR(CS_BYTEALIGNCLIENT), + DEFINE_PAIR(CS_BYTEALIGNWINDOW), + DEFINE_PAIR(CS_GLOBALCLASS), + DEFINE_PAIR(CS_IME), + DEFINE_PAIR(CS_DROPSHADOW) +}; + +HANDLE WePropertiesThreadHandle = NULL; +CLIENT_ID WePropertiesThreadClientId; +PH_EVENT WePropertiesThreadReadyEvent = PH_EVENT_INIT; +PPH_LIST WePropertiesCreateList; +PPH_LIST WePropertiesWindowList; +PH_QUEUED_LOCK WePropertiesCreateLock = PH_QUEUED_LOCK_INIT; + +VOID WeShowWindowProperties( + _In_ HWND ParentWindowHandle, + _In_ HWND WindowHandle + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + ULONG threadId; + ULONG processId; + + if (!WePropertiesCreateList) + WePropertiesCreateList = PhCreateList(4); + if (!WePropertiesWindowList) + WePropertiesWindowList = PhCreateList(4); + + if (!WePropertiesThreadHandle) + { + WePropertiesThreadHandle = PhCreateThread(0, WepPropertiesThreadStart, NULL); + PhWaitForEvent(&WePropertiesThreadReadyEvent, NULL); + } + + context = PhAllocate(sizeof(WINDOW_PROPERTIES_CONTEXT)); + memset(context, 0, sizeof(WINDOW_PROPERTIES_CONTEXT)); + context->RefCount = 1; + context->ParentWindowHandle = ParentWindowHandle; + context->WindowHandle = WindowHandle; + + processId = 0; + threadId = GetWindowThreadProcessId(WindowHandle, &processId); + context->ClientId.UniqueProcess = UlongToHandle(processId); + context->ClientId.UniqueThread = UlongToHandle(threadId); + PhInitializeInitOnce(&context->SymbolProviderInitOnce); + InitializeListHead(&context->ResolveListHead); + PhInitializeQueuedLock(&context->ResolveListLock); + + // Queue the window for creation and wake up the host thread. + PhAcquireQueuedLockExclusive(&WePropertiesCreateLock); + PhAddItemList(WePropertiesCreateList, context); + PhReleaseQueuedLockExclusive(&WePropertiesCreateLock); + PostThreadMessage(HandleToUlong(WePropertiesThreadClientId.UniqueThread), WM_NULL, 0, 0); +} + +VOID WepReferenceWindowPropertiesContext( + _Inout_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + _InterlockedIncrement(&Context->RefCount); +} + +VOID WepDereferenceWindowPropertiesContext( + _Inout_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + if (_InterlockedDecrement(&Context->RefCount) == 0) + { + PLIST_ENTRY listEntry; + + PhClearReference(&Context->SymbolProvider); + + // Destroy results that have not been processed by any property pages. + + listEntry = Context->ResolveListHead.Flink; + + while (listEntry != &Context->ResolveListHead) + { + PSYMBOL_RESOLVE_CONTEXT resolveContext; + + resolveContext = CONTAINING_RECORD(listEntry, SYMBOL_RESOLVE_CONTEXT, ListEntry); + listEntry = listEntry->Flink; + + PhClearReference(&resolveContext->Symbol); + PhFree(resolveContext); + } + + PhClearReference(&Context->WndProcSymbol); + PhClearReference(&Context->DlgProcSymbol); + PhClearReference(&Context->ClassWndProcSymbol); + + PhFree(Context); + } +} + +static HWND WepCreateWindowProperties( + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + HPROPSHEETPAGE pages[NUMBER_OF_PAGES]; + + propSheetHeader.dwFlags = + PSH_MODELESS | + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE | + PSH_USECALLBACK; + propSheetHeader.hwndParent = Context->ParentWindowHandle; + propSheetHeader.pszCaption = PhaFormatString(L"Window %Ix", Context->WindowHandle)->Buffer; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + propSheetHeader.pfnCallback = WepPropSheetProc; + + // General + pages[propSheetHeader.nPages++] = WepCommonCreatePage( + Context, + MAKEINTRESOURCE(IDD_WNDGENERAL), + WepWindowGeneralDlgProc + ); + // Styles + pages[propSheetHeader.nPages++] = WepCommonCreatePage( + Context, + MAKEINTRESOURCE(IDD_WNDSTYLES), + WepWindowStylesDlgProc + ); + // Class + pages[propSheetHeader.nPages++] = WepCommonCreatePage( + Context, + MAKEINTRESOURCE(IDD_WNDCLASS), + WepWindowClassDlgProc + ); + // Properties + pages[propSheetHeader.nPages++] = WepCommonCreatePage( + Context, + MAKEINTRESOURCE(IDD_WNDPROPS), + WepWindowPropertiesDlgProc + ); + + return (HWND)PropertySheet(&propSheetHeader); +} + +static INT CALLBACK WepPropSheetProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case PSCB_INITIALIZED: + { + WNDPROC oldWndProc; + HWND refreshButtonHandle; + + oldWndProc = (WNDPROC)GetWindowLongPtr(hwndDlg, GWLP_WNDPROC); + SetWindowLongPtr(hwndDlg, GWLP_WNDPROC, (LONG_PTR)WepPropSheetWndProc); + SetProp(hwndDlg, L"OldWndProc", (HANDLE)oldWndProc); + + // Hide the Cancel button. + ShowWindow(GetDlgItem(hwndDlg, IDCANCEL), SW_HIDE); + // Set the OK button's text to "Close". + SetDlgItemText(hwndDlg, IDOK, L"Close"); + // Add the Refresh button. + refreshButtonHandle = CreateWindow(L"BUTTON", L"Refresh", WS_CHILD | WS_TABSTOP | WS_VISIBLE, 0, 0, 3, 3, hwndDlg, (HMENU)IDC_REFRESH, + PluginInstance->DllBase, NULL); + SendMessage(refreshButtonHandle, WM_SETFONT, (WPARAM)SendMessage(GetDlgItem(hwndDlg, IDOK), WM_GETFONT, 0, 0), FALSE); + } + break; + } + + return 0; +} + +LRESULT CALLBACK WepPropSheetWndProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, L"OldWndProc"); + + switch (uMsg) + { + case WM_DESTROY: + { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); + RemoveProp(hwnd, L"OldWndProc"); + RemoveProp(hwnd, L"Moved"); + } + break; + case WM_SHOWWINDOW: + { + if (!GetProp(hwnd, L"Moved")) + { + // Move the Refresh button to where the OK button is, and move the OK button to + // where the Cancel button is. + // This must be done here because in the prop sheet callback the buttons are not + // in the right places. + PhCopyControlRectangle(hwnd, GetDlgItem(hwnd, IDOK), GetDlgItem(hwnd, IDC_REFRESH)); + PhCopyControlRectangle(hwnd, GetDlgItem(hwnd, IDCANCEL), GetDlgItem(hwnd, IDOK)); + SetProp(hwnd, L"Moved", (HANDLE)1); + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_REFRESH: + { + ULONG i; + HWND pageHandle; + + // Broadcast the message to all property pages. + for (i = 0; i < NUMBER_OF_PAGES; i++) + { + if (pageHandle = PropSheet_IndexToHwnd(hwnd, i)) + SendMessage(pageHandle, WM_COMMAND, IDC_REFRESH, 0); + } + } + break; + } + } + break; + } + + return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); +} + +static HPROPSHEETPAGE WepCommonCreatePage( + _In_ PWINDOW_PROPERTIES_CONTEXT Context, + _In_ PWSTR Template, + _In_ DLGPROC DlgProc + ) +{ + HPROPSHEETPAGE propSheetPageHandle; + PROPSHEETPAGE propSheetPage; + + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USECALLBACK; + propSheetPage.hInstance = PluginInstance->DllBase; + propSheetPage.pszTemplate = Template; + propSheetPage.pfnDlgProc = DlgProc; + propSheetPage.lParam = (LPARAM)Context; + propSheetPage.pfnCallback = WepCommonPropPageProc; + + propSheetPageHandle = CreatePropertySheetPage(&propSheetPage); + + return propSheetPageHandle; +} + +static INT CALLBACK WepCommonPropPageProc( + _In_ HWND hwnd, + _In_ UINT uMsg, + _In_ LPPROPSHEETPAGE ppsp + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + + context = (PWINDOW_PROPERTIES_CONTEXT)ppsp->lParam; + + if (uMsg == PSPCB_ADDREF) + WepReferenceWindowPropertiesContext(context); + else if (uMsg == PSPCB_RELEASE) + WepDereferenceWindowPropertiesContext(context); + + return 1; +} + +NTSTATUS WepPropertiesThreadStart( + _In_ PVOID Parameter + ) +{ + PH_AUTO_POOL autoPool; + BOOL result; + MSG message; + BOOLEAN processed; + ULONG i; + + PhInitializeAutoPool(&autoPool); + + WePropertiesThreadClientId = NtCurrentTeb()->ClientId; + + // Force the creation of the message queue so PostThreadMessage works. + PeekMessage(&message, NULL, WM_USER, WM_USER, PM_NOREMOVE); + PhSetEvent(&WePropertiesThreadReadyEvent); + + while (result = GetMessage(&message, NULL, 0, 0)) + { + if (result == -1) + break; + + if (WePropertiesCreateList->Count != 0) + { + PhAcquireQueuedLockExclusive(&WePropertiesCreateLock); + + for (i = 0; i < WePropertiesCreateList->Count; i++) + { + PWINDOW_PROPERTIES_CONTEXT context; + HWND hwnd; + + context = WePropertiesCreateList->Items[i]; + hwnd = WepCreateWindowProperties(context); + WepDereferenceWindowPropertiesContext(context); + PhAddItemList(WePropertiesWindowList, hwnd); + } + + PhClearList(WePropertiesCreateList); + PhReleaseQueuedLockExclusive(&WePropertiesCreateLock); + } + + processed = FALSE; + + for (i = 0; i < WePropertiesWindowList->Count; i++) + { + if (PropSheet_IsDialogMessage(WePropertiesWindowList->Items[i], &message)) + { + processed = TRUE; + break; + } + } + + if (!processed) + { + TranslateMessage(&message); + DispatchMessage(&message); + } + + // Destroy properties windows when necessary. + for (i = 0; i < WePropertiesWindowList->Count; i++) + { + if (!PropSheet_GetCurrentPageHwnd(WePropertiesWindowList->Items[i])) + { + DestroyWindow(WePropertiesWindowList->Items[i]); + PhRemoveItemList(WePropertiesWindowList, i); + i--; + } + } + + PhDrainAutoPool(&autoPool); + } + + PhDeleteAutoPool(&autoPool); + + return STATUS_SUCCESS; +} + +FORCEINLINE BOOLEAN WepPropPageDlgProcHeader( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ LPARAM lParam, + _Out_opt_ LPPROPSHEETPAGE *PropSheetPage, + _Out_opt_ PWINDOW_PROPERTIES_CONTEXT *Context + ) +{ + LPPROPSHEETPAGE propSheetPage; + + if (uMsg == WM_INITDIALOG) + { + propSheetPage = (LPPROPSHEETPAGE)lParam; + // Save the context. + SetProp(hwndDlg, L"PropSheetPage", (HANDLE)lParam); + } + else + { + propSheetPage = (LPPROPSHEETPAGE)GetProp(hwndDlg, L"PropSheetPage"); + + if (uMsg == WM_DESTROY) + RemoveProp(hwndDlg, L"PropSheetPage"); + } + + if (!propSheetPage) + return FALSE; + + if (PropSheetPage) + *PropSheetPage = propSheetPage; + if (Context) + *Context = (PWINDOW_PROPERTIES_CONTEXT)propSheetPage->lParam; + + return TRUE; +} + +static VOID WepEnsureHookDataValid( + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + if (!Context->HookDataValid) + { + PWE_HOOK_SHARED_DATA data; +#ifdef _WIN64 + HANDLE processHandle; + BOOLEAN isWow64 = FALSE; +#endif + + // The desktop window is owned by CSR. The hook will never work on the desktop window. + if (Context->WindowHandle == GetDesktopWindow()) + { + Context->HookDataValid = TRUE; + return; + } + +#ifdef _WIN64 + // We can't use the hook on WOW64 processes. + if (NT_SUCCESS(PhOpenProcess(&processHandle, *(PULONG)WeGetProcedureAddress("ProcessQueryAccess"), Context->ClientId.UniqueProcess))) + { + PhGetProcessIsWow64(processHandle, &isWow64); + NtClose(processHandle); + } + + if (isWow64) + return; +#endif + + WeHookServerInitialization(); + + Context->HookDataSuccess = FALSE; + + if (WeLockServerSharedData(&data)) + { + if (WeSendServerRequest(Context->WindowHandle)) + { + Context->WndProc = data->c.WndProc; + Context->DlgProc = data->c.DlgProc; + memcpy(&Context->ClassInfo, &data->c.ClassInfo, sizeof(WNDCLASSEX)); + Context->HookDataSuccess = TRUE; + } + + WeUnlockServerSharedData(); + } + + Context->HookDataValid = TRUE; + } +} + +static BOOLEAN NTAPI EnumGenericModulesCallback( + _In_ PPH_MODULE_INFO Module, + _In_opt_ PVOID Context + ) +{ + PWINDOW_PROPERTIES_CONTEXT context = Context; + + PhLoadModuleSymbolProvider(context->SymbolProvider, Module->FileName->Buffer, + (ULONG64)Module->BaseAddress, Module->Size); + + return TRUE; +} + +static NTSTATUS WepResolveSymbolFunction( + _In_ PVOID Parameter + ) +{ + PSYMBOL_RESOLVE_CONTEXT context = Parameter; + + if (PhBeginInitOnce(&context->Context->SymbolProviderInitOnce)) + { + PhEnumGenericModules(context->Context->ClientId.UniqueProcess, NULL, 0, EnumGenericModulesCallback, context->Context); + PhEndInitOnce(&context->Context->SymbolProviderInitOnce); + } + + context->Symbol = PhGetSymbolFromAddress( + context->Context->SymbolProvider, + (ULONG64)context->Address, + &context->ResolveLevel, + NULL, + NULL, + NULL + ); + + // Fail if we don't have a symbol. + if (!context->Symbol) + { + WepDereferenceWindowPropertiesContext(context->Context); + PhFree(context); + return STATUS_SUCCESS; + } + + PhAcquireQueuedLockExclusive(&context->Context->ResolveListLock); + InsertHeadList(&context->Context->ResolveListHead, &context->ListEntry); + PhReleaseQueuedLockExclusive(&context->Context->ResolveListLock); + + PostMessage(context->NotifyWindow, WEM_RESOLVE_DONE, 0, (LPARAM)context); + + WepDereferenceWindowPropertiesContext(context->Context); + + return STATUS_SUCCESS; +} + +static VOID WepQueueResolveSymbol( + _In_ PWINDOW_PROPERTIES_CONTEXT Context, + _In_ HWND NotifyWindow, + _In_ ULONG64 Address, + _In_ ULONG Id + ) +{ + PSYMBOL_RESOLVE_CONTEXT resolveContext; + + if (!Context->SymbolProvider) + { + Context->SymbolProvider = PhCreateSymbolProvider(Context->ClientId.UniqueProcess); + PhLoadSymbolProviderOptions(Context->SymbolProvider); + } + + resolveContext = PhAllocate(sizeof(SYMBOL_RESOLVE_CONTEXT)); + resolveContext->Address = Address; + resolveContext->Symbol = NULL; + resolveContext->ResolveLevel = PhsrlInvalid; + resolveContext->NotifyWindow = NotifyWindow; + resolveContext->Context = Context; + WepReferenceWindowPropertiesContext(Context); + resolveContext->Id = Id; + + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), WepResolveSymbolFunction, resolveContext); +} + +static PPH_STRING WepFormatRect( + _In_ PRECT Rect + ) +{ + return PhaFormatString(L"(%d, %d) - (%d, %d) [%dx%d]", + Rect->left, Rect->top, Rect->right, Rect->bottom, + Rect->right - Rect->left, Rect->bottom - Rect->top); +} + +static VOID WepRefreshWindowGeneralInfoSymbols( + _In_ HWND hwndDlg, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + if (Context->WndProcResolving != 0) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix (resolving...)", Context->WndProc)->Buffer); + else if (Context->WndProcSymbol) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix (%s)", Context->WndProc, Context->WndProcSymbol->Buffer)->Buffer); + else if (Context->WndProc != 0) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix", Context->WndProc)->Buffer); + else + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, L"Unknown"); + + if (Context->DlgProcResolving != 0) + SetDlgItemText(hwndDlg, IDC_DIALOGPROC, PhaFormatString(L"0x%Ix (resolving...)", Context->DlgProc)->Buffer); + else if (Context->DlgProcSymbol) + SetDlgItemText(hwndDlg, IDC_DIALOGPROC, PhaFormatString(L"0x%Ix (%s)", Context->DlgProc, Context->DlgProcSymbol->Buffer)->Buffer); + else if (Context->DlgProc != 0) + SetDlgItemText(hwndDlg, IDC_DIALOGPROC, PhaFormatString(L"0x%Ix", Context->DlgProc)->Buffer); + else if (Context->WndProc != 0) + SetDlgItemText(hwndDlg, IDC_DIALOGPROC, L"N/A"); + else + SetDlgItemText(hwndDlg, IDC_DIALOGPROC, L"Unknown"); +} + +static VOID WepRefreshWindowGeneralInfo( + _In_ HWND hwndDlg, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + WINDOWINFO windowInfo = { sizeof(WINDOWINFO) }; + WINDOWPLACEMENT windowPlacement = { sizeof(WINDOWPLACEMENT) }; + MONITORINFO monitorInfo = { sizeof(MONITORINFO) }; + + SetDlgItemText(hwndDlg, IDC_THREAD, PH_AUTO_T(PH_STRING, PhGetClientIdName(&Context->ClientId))->Buffer); + SetDlgItemText(hwndDlg, IDC_TEXT, PhGetStringOrEmpty(PH_AUTO(PhGetWindowText(Context->WindowHandle)))); + + if (GetWindowInfo(Context->WindowHandle, &windowInfo)) + { + SetDlgItemText(hwndDlg, IDC_RECTANGLE, WepFormatRect(&windowInfo.rcWindow)->Buffer); + SetDlgItemText(hwndDlg, IDC_CLIENTRECTANGLE, WepFormatRect(&windowInfo.rcClient)->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_RECTANGLE, L"N/A"); + SetDlgItemText(hwndDlg, IDC_CLIENTRECTANGLE, L"N/A"); + } + + if (GetWindowPlacement(Context->WindowHandle, &windowPlacement)) + { + // The rectangle is in workspace coordinates. Convert the values back to screen coordinates. + if (GetMonitorInfo(MonitorFromRect(&windowPlacement.rcNormalPosition, MONITOR_DEFAULTTOPRIMARY), &monitorInfo)) + { + windowPlacement.rcNormalPosition.left += monitorInfo.rcWork.left; + windowPlacement.rcNormalPosition.top += monitorInfo.rcWork.top; + windowPlacement.rcNormalPosition.right += monitorInfo.rcWork.left; + windowPlacement.rcNormalPosition.bottom += monitorInfo.rcWork.top; + } + + SetDlgItemText(hwndDlg, IDC_NORMALRECTANGLE, WepFormatRect(&windowPlacement.rcNormalPosition)->Buffer); + } + else + { + SetDlgItemText(hwndDlg, IDC_NORMALRECTANGLE, L"N/A"); + } + + SetDlgItemText(hwndDlg, IDC_INSTANCEHANDLE, PhaFormatString(L"0x%Ix", GetWindowLongPtr(Context->WindowHandle, GWLP_HINSTANCE))->Buffer); + SetDlgItemText(hwndDlg, IDC_MENUHANDLE, PhaFormatString(L"0x%Ix", GetMenu(Context->WindowHandle))->Buffer); + SetDlgItemText(hwndDlg, IDC_USERDATA, PhaFormatString(L"0x%Ix", GetWindowLongPtr(Context->WindowHandle, GWLP_USERDATA))->Buffer); + SetDlgItemText(hwndDlg, IDC_UNICODE, IsWindowUnicode(Context->WindowHandle) ? L"Yes" : L"No"); + SetDlgItemText(hwndDlg, IDC_CTRLID, PhaFormatString(L"%lu", GetDlgCtrlID(Context->WindowHandle))->Buffer); + + WepEnsureHookDataValid(Context); + + if (Context->WndProc != 0) + { + Context->WndProcResolving++; + WepQueueResolveSymbol(Context, hwndDlg, Context->WndProc, 1); + } + + if (Context->DlgProc != 0) + { + Context->DlgProcResolving++; + WepQueueResolveSymbol(Context, hwndDlg, Context->DlgProc, 2); + } + + WepRefreshWindowGeneralInfoSymbols(hwndDlg, Context); +} + +INT_PTR CALLBACK WepWindowGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + + if (!WepPropPageDlgProcHeader(hwndDlg, uMsg, lParam, NULL, &context)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + WepRefreshWindowGeneralInfo(hwndDlg, context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_REFRESH: + context->HookDataValid = FALSE; + PhClearReference(&context->WndProcSymbol); + WepRefreshWindowGeneralInfo(hwndDlg, context); + break; + } + } + break; + case WEM_RESOLVE_DONE: + { + PSYMBOL_RESOLVE_CONTEXT resolveContext = (PSYMBOL_RESOLVE_CONTEXT)lParam; + + if (resolveContext->Id == 1) + { + PhAcquireQueuedLockExclusive(&context->ResolveListLock); + RemoveEntryList(&resolveContext->ListEntry); + PhReleaseQueuedLockExclusive(&context->ResolveListLock); + + if (resolveContext->ResolveLevel != PhsrlModule && resolveContext->ResolveLevel != PhsrlFunction) + PhClearReference(&resolveContext->Symbol); + + PhMoveReference(&context->WndProcSymbol, resolveContext->Symbol); + PhFree(resolveContext); + + context->WndProcResolving--; + } + else if (resolveContext->Id == 2) + { + PhAcquireQueuedLockExclusive(&context->ResolveListLock); + RemoveEntryList(&resolveContext->ListEntry); + PhReleaseQueuedLockExclusive(&context->ResolveListLock); + + if (resolveContext->ResolveLevel != PhsrlModule && resolveContext->ResolveLevel != PhsrlFunction) + PhClearReference(&resolveContext->Symbol); + + PhMoveReference(&context->DlgProcSymbol, resolveContext->Symbol); + PhFree(resolveContext); + + context->DlgProcResolving--; + } + + WepRefreshWindowGeneralInfoSymbols(hwndDlg, context); + } + break; + } + + return FALSE; +} + +static VOID WepRefreshWindowStyles( + _In_ HWND hwndDlg, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + WINDOWINFO windowInfo = { sizeof(WINDOWINFO) }; + HWND stylesListBox; + HWND extendedStylesListBox; + ULONG i; + + stylesListBox = GetDlgItem(hwndDlg, IDC_STYLESLIST); + extendedStylesListBox = GetDlgItem(hwndDlg, IDC_EXTENDEDSTYLESLIST); + + ListBox_ResetContent(stylesListBox); + ListBox_ResetContent(extendedStylesListBox); + + if (GetWindowInfo(Context->WindowHandle, &windowInfo)) + { + SetDlgItemText(hwndDlg, IDC_STYLES, PhaFormatString(L"0x%x", windowInfo.dwStyle)->Buffer); + SetDlgItemText(hwndDlg, IDC_EXTENDEDSTYLES, PhaFormatString(L"0x%x", windowInfo.dwExStyle)->Buffer); + + for (i = 0; i < sizeof(WepStylePairs) / sizeof(STRING_INTEGER_PAIR); i++) + { + if (windowInfo.dwStyle & WepStylePairs[i].Integer) + { + // Skip irrelevant styles. + + if (WepStylePairs[i].Integer == WS_MAXIMIZEBOX || + WepStylePairs[i].Integer == WS_MINIMIZEBOX) + { + if (windowInfo.dwStyle & WS_CHILD) + continue; + } + + if (WepStylePairs[i].Integer == WS_TABSTOP || + WepStylePairs[i].Integer == WS_GROUP) + { + if (!(windowInfo.dwStyle & WS_CHILD)) + continue; + } + + ListBox_AddString(stylesListBox, WepStylePairs[i].String); + } + } + + for (i = 0; i < sizeof(WepExtendedStylePairs) / sizeof(STRING_INTEGER_PAIR); i++) + { + if (windowInfo.dwExStyle & WepExtendedStylePairs[i].Integer) + { + ListBox_AddString(extendedStylesListBox, WepExtendedStylePairs[i].String); + } + } + } + else + { + SetDlgItemText(hwndDlg, IDC_STYLES, L"N/A"); + SetDlgItemText(hwndDlg, IDC_EXTENDEDSTYLES, L"N/A"); + } +} + +INT_PTR CALLBACK WepWindowStylesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + + if (!WepPropPageDlgProcHeader(hwndDlg, uMsg, lParam, NULL, &context)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + WepRefreshWindowStyles(hwndDlg, context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_REFRESH: + WepRefreshWindowStyles(hwndDlg, context); + break; + } + } + break; + } + + return FALSE; +} + +static VOID WepRefreshWindowClassInfoSymbols( + _In_ HWND hwndDlg, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + if (Context->ClassWndProcResolving != 0) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix (resolving...)", Context->ClassInfo.lpfnWndProc)->Buffer); + else if (Context->ClassWndProcSymbol) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix (%s)", Context->ClassInfo.lpfnWndProc, Context->ClassWndProcSymbol->Buffer)->Buffer); + else if (Context->ClassInfo.lpfnWndProc) + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, PhaFormatString(L"0x%Ix", Context->ClassInfo.lpfnWndProc)->Buffer); + else + SetDlgItemText(hwndDlg, IDC_WINDOWPROC, L"Unknown"); +} + +static VOID WepRefreshWindowClassInfo( + _In_ HWND hwndDlg, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + WCHAR className[256]; + PH_STRING_BUILDER stringBuilder; + ULONG i; + + if (!GetClassName(Context->WindowHandle, className, sizeof(className) / sizeof(WCHAR))) + className[0] = 0; + + WepEnsureHookDataValid(Context); + + if (!Context->HookDataSuccess) + { + Context->ClassInfo.cbSize = sizeof(WNDCLASSEX); + GetClassInfoEx(NULL, className, &Context->ClassInfo); + } + + SetDlgItemText(hwndDlg, IDC_NAME, className); + SetDlgItemText(hwndDlg, IDC_ATOM, PhaFormatString(L"0x%x", GetClassWord(Context->WindowHandle, GCW_ATOM))->Buffer); + SetDlgItemText(hwndDlg, IDC_INSTANCEHANDLE, PhaFormatString(L"0x%Ix", GetClassLongPtr(Context->WindowHandle, GCLP_HMODULE))->Buffer); + SetDlgItemText(hwndDlg, IDC_ICONHANDLE, PhaFormatString(L"0x%Ix", Context->ClassInfo.hIcon)->Buffer); + SetDlgItemText(hwndDlg, IDC_SMALLICONHANDLE, PhaFormatString(L"0x%Ix", Context->ClassInfo.hIconSm)->Buffer); + SetDlgItemText(hwndDlg, IDC_MENUNAME, PhaFormatString(L"0x%Ix", Context->ClassInfo.lpszMenuName)->Buffer); + + PhInitializeStringBuilder(&stringBuilder, 100); + PhAppendFormatStringBuilder(&stringBuilder, L"0x%x (", Context->ClassInfo.style); + + for (i = 0; i < sizeof(WepClassStylePairs) / sizeof(STRING_INTEGER_PAIR); i++) + { + if (Context->ClassInfo.style & WepClassStylePairs[i].Integer) + { + PhAppendStringBuilder2(&stringBuilder, WepClassStylePairs[i].String); + PhAppendStringBuilder2(&stringBuilder, L" | "); + } + } + + if (PhEndsWithString2(stringBuilder.String, L" | ", FALSE)) + { + PhRemoveEndStringBuilder(&stringBuilder, 3); + PhAppendCharStringBuilder(&stringBuilder, ')'); + } + else + { + // No styles. Remove the brackets. + PhRemoveEndStringBuilder(&stringBuilder, 1); + } + + SetDlgItemText(hwndDlg, IDC_STYLES, stringBuilder.String->Buffer); + PhDeleteStringBuilder(&stringBuilder); + + // TODO: Add symbols for these values. + SetDlgItemText(hwndDlg, IDC_CURSORHANDLE, PhaFormatString(L"0x%Ix", Context->ClassInfo.hCursor)->Buffer); + SetDlgItemText(hwndDlg, IDC_BACKGROUNDBRUSH, PhaFormatString(L"0x%Ix", Context->ClassInfo.hbrBackground)->Buffer); + + if (Context->ClassInfo.lpfnWndProc) + { + Context->ClassWndProcResolving++; + WepQueueResolveSymbol(Context, hwndDlg, (ULONG_PTR)Context->ClassInfo.lpfnWndProc, 0); + } + + WepRefreshWindowClassInfoSymbols(hwndDlg, Context); +} + +INT_PTR CALLBACK WepWindowClassDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + + if (!WepPropPageDlgProcHeader(hwndDlg, uMsg, lParam, NULL, &context)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + WepRefreshWindowClassInfo(hwndDlg, context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_REFRESH: + context->HookDataValid = FALSE; + PhClearReference(&context->ClassWndProcSymbol); + WepRefreshWindowClassInfo(hwndDlg, context); + break; + } + } + break; + case WEM_RESOLVE_DONE: + { + PSYMBOL_RESOLVE_CONTEXT resolveContext = (PSYMBOL_RESOLVE_CONTEXT)lParam; + + PhAcquireQueuedLockExclusive(&context->ResolveListLock); + RemoveEntryList(&resolveContext->ListEntry); + PhReleaseQueuedLockExclusive(&context->ResolveListLock); + + if (resolveContext->ResolveLevel != PhsrlModule && resolveContext->ResolveLevel != PhsrlFunction) + PhClearReference(&resolveContext->Symbol); + + PhMoveReference(&context->ClassWndProcSymbol, resolveContext->Symbol); + PhFree(resolveContext); + + context->ClassWndProcResolving--; + WepRefreshWindowClassInfoSymbols(hwndDlg, context); + } + break; + } + + return FALSE; +} + +static BOOL CALLBACK EnumPropsExCallback( + _In_ HWND hwnd, + _In_ LPTSTR lpszString, + _In_ HANDLE hData, + _In_ ULONG_PTR dwData + ) +{ + INT lvItemIndex; + PWSTR propName; + WCHAR value[PH_PTR_STR_LEN_1]; + + propName = lpszString; + + if ((ULONG_PTR)lpszString < USHRT_MAX) + { + // This is an integer atom. + propName = PhaFormatString(L"#%lu", (ULONG_PTR)lpszString)->Buffer; + } + + lvItemIndex = PhAddListViewItem((HWND)dwData, MAXINT, propName, NULL); + + PhPrintPointer(value, (PVOID)hData); + PhSetListViewSubItem((HWND)dwData, lvItemIndex, 1, value); + + return TRUE; +} + +static VOID WepRefreshWindowProps( + _In_ HWND hwndDlg, + _In_ HWND ListViewHandle, + _In_ PWINDOW_PROPERTIES_CONTEXT Context + ) +{ + ExtendedListView_SetRedraw(ListViewHandle, FALSE); + ListView_DeleteAllItems(ListViewHandle); + EnumPropsEx(Context->WindowHandle, EnumPropsExCallback, (LPARAM)ListViewHandle); + ExtendedListView_SortItems(ListViewHandle); + ExtendedListView_SetRedraw(ListViewHandle, TRUE); +} + +INT_PTR CALLBACK WepWindowPropertiesDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + PWINDOW_PROPERTIES_CONTEXT context; + + if (!WepPropPageDlgProcHeader(hwndDlg, uMsg, lParam, NULL, &context)) + return FALSE; + + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 160, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 100, L"Value"); + PhSetExtendedListView(lvHandle); + + WepRefreshWindowProps(hwndDlg, lvHandle, context); + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_REFRESH: + WepRefreshWindowProps(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), context); + break; + } + } + break; + } + + return FALSE; +} diff --git a/plugins/WindowExplorer/wndtree.c b/plugins/WindowExplorer/wndtree.c new file mode 100644 index 0000000..773d6c1 --- /dev/null +++ b/plugins/WindowExplorer/wndtree.c @@ -0,0 +1,461 @@ +/* + * Process Hacker Window Explorer - + * window treelist + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include "wndexp.h" +#include "resource.h" + +BOOLEAN WepWindowNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ); + +ULONG WepWindowNodeHashtableHashFunction( + _In_ PVOID Entry + ); + +VOID WepDestroyWindowNode( + _In_ PWE_WINDOW_NODE WindowNode + ); + +BOOLEAN NTAPI WepWindowTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ); + +VOID WeInitializeWindowTree( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PWE_WINDOW_TREE_CONTEXT Context + ) +{ + HWND hwnd; + PPH_STRING settings; + + memset(Context, 0, sizeof(WE_WINDOW_TREE_CONTEXT)); + + Context->NodeHashtable = PhCreateHashtable( + sizeof(PWE_WINDOW_NODE), + WepWindowNodeHashtableEqualFunction, + WepWindowNodeHashtableHashFunction, + 100 + ); + Context->NodeList = PhCreateList(100); + Context->NodeRootList = PhCreateList(30); + + Context->ParentWindowHandle = ParentWindowHandle; + Context->TreeNewHandle = TreeNewHandle; + hwnd = TreeNewHandle; + PhSetControlTheme(hwnd, L"explorer"); + + TreeNew_SetCallback(hwnd, WepWindowTreeNewCallback, Context); + + PhAddTreeNewColumn(hwnd, WEWNTLC_CLASS, TRUE, L"Class", 180, PH_ALIGN_LEFT, 0, 0); + PhAddTreeNewColumn(hwnd, WEWNTLC_HANDLE, TRUE, L"Handle", 70, PH_ALIGN_LEFT, 1, 0); + PhAddTreeNewColumn(hwnd, WEWNTLC_TEXT, TRUE, L"Text", 220, PH_ALIGN_LEFT, 2, 0); + PhAddTreeNewColumn(hwnd, WEWNTLC_THREAD, TRUE, L"Thread", 150, PH_ALIGN_LEFT, 3, 0); + + TreeNew_SetTriState(hwnd, TRUE); + TreeNew_SetSort(hwnd, 0, NoSortOrder); + + settings = PhGetStringSetting(SETTING_NAME_WINDOW_TREE_LIST_COLUMNS); + PhCmLoadSettings(hwnd, &settings->sr); + PhDereferenceObject(settings); +} + +VOID WeDeleteWindowTree( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ) +{ + PPH_STRING settings; + ULONG i; + + settings = PhCmSaveSettings(Context->TreeNewHandle); + PhSetStringSetting2(SETTING_NAME_WINDOW_TREE_LIST_COLUMNS, &settings->sr); + PhDereferenceObject(settings); + + for (i = 0; i < Context->NodeList->Count; i++) + WepDestroyWindowNode(Context->NodeList->Items[i]); + + PhDereferenceObject(Context->NodeHashtable); + PhDereferenceObject(Context->NodeList); + PhDereferenceObject(Context->NodeRootList); +} + +BOOLEAN WepWindowNodeHashtableEqualFunction( + _In_ PVOID Entry1, + _In_ PVOID Entry2 + ) +{ + PWE_WINDOW_NODE windowNode1 = *(PWE_WINDOW_NODE *)Entry1; + PWE_WINDOW_NODE windowNode2 = *(PWE_WINDOW_NODE *)Entry2; + + return windowNode1->WindowHandle == windowNode2->WindowHandle; +} + +ULONG WepWindowNodeHashtableHashFunction( + _In_ PVOID Entry + ) +{ + return PhHashIntPtr((ULONG_PTR)(*(PWE_WINDOW_NODE *)Entry)->WindowHandle); +} + +PWE_WINDOW_NODE WeAddWindowNode( + _Inout_ PWE_WINDOW_TREE_CONTEXT Context + ) +{ + PWE_WINDOW_NODE windowNode; + + windowNode = PhAllocate(sizeof(WE_WINDOW_NODE)); + memset(windowNode, 0, sizeof(WE_WINDOW_NODE)); + PhInitializeTreeNewNode(&windowNode->Node); + + memset(windowNode->TextCache, 0, sizeof(PH_STRINGREF) * WEWNTLC_MAXIMUM); + windowNode->Node.TextCache = windowNode->TextCache; + windowNode->Node.TextCacheSize = WEWNTLC_MAXIMUM; + + windowNode->Children = PhCreateList(1); + + PhAddEntryHashtable(Context->NodeHashtable, &windowNode); + PhAddItemList(Context->NodeList, windowNode); + + TreeNew_NodesStructured(Context->TreeNewHandle); + + return windowNode; +} + +PWE_WINDOW_NODE WeFindWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _In_ HWND WindowHandle + ) +{ + WE_WINDOW_NODE lookupWindowNode; + PWE_WINDOW_NODE lookupWindowNodePtr = &lookupWindowNode; + PWE_WINDOW_NODE *windowNode; + + lookupWindowNode.WindowHandle = WindowHandle; + + windowNode = (PWE_WINDOW_NODE *)PhFindEntryHashtable( + Context->NodeHashtable, + &lookupWindowNodePtr + ); + + if (windowNode) + return *windowNode; + else + return NULL; +} + +VOID WeRemoveWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _In_ PWE_WINDOW_NODE WindowNode + ) +{ + ULONG index; + + // Remove from hashtable/list and cleanup. + + PhRemoveEntryHashtable(Context->NodeHashtable, &WindowNode); + + if ((index = PhFindItemList(Context->NodeList, WindowNode)) != -1) + PhRemoveItemList(Context->NodeList, index); + + WepDestroyWindowNode(WindowNode); + + TreeNew_NodesStructured(Context->TreeNewHandle); +} + +VOID WepDestroyWindowNode( + _In_ PWE_WINDOW_NODE WindowNode + ) +{ + PhDereferenceObject(WindowNode->Children); + + if (WindowNode->WindowText) PhDereferenceObject(WindowNode->WindowText); + + if (WindowNode->ThreadString) PhDereferenceObject(WindowNode->ThreadString); + + PhFree(WindowNode); +} + +#define SORT_FUNCTION(Column) WepWindowTreeNewCompare##Column + +#define BEGIN_SORT_FUNCTION(Column) static int __cdecl WepWindowTreeNewCompare##Column( \ + _In_ void *_context, \ + _In_ const void *_elem1, \ + _In_ const void *_elem2 \ + ) \ +{ \ + PWE_WINDOW_NODE node1 = *(PWE_WINDOW_NODE *)_elem1; \ + PWE_WINDOW_NODE node2 = *(PWE_WINDOW_NODE *)_elem2; \ + int sortResult = 0; + +#define END_SORT_FUNCTION \ + return PhModifySort(sortResult, ((PWE_WINDOW_TREE_CONTEXT)_context)->TreeNewSortOrder); \ +} + +BEGIN_SORT_FUNCTION(Class) +{ + sortResult = _wcsicmp(node1->WindowClass, node2->WindowClass); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Handle) +{ + sortResult = uintptrcmp((ULONG_PTR)node1->WindowHandle, (ULONG_PTR)node2->WindowHandle); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Text) +{ + sortResult = PhCompareString(node1->WindowText, node2->WindowText, TRUE); +} +END_SORT_FUNCTION + +BEGIN_SORT_FUNCTION(Thread) +{ + sortResult = uintptrcmp((ULONG_PTR)node1->ClientId.UniqueProcess, (ULONG_PTR)node2->ClientId.UniqueProcess); + + if (sortResult == 0) + sortResult = uintptrcmp((ULONG_PTR)node1->ClientId.UniqueThread, (ULONG_PTR)node2->ClientId.UniqueThread); +} +END_SORT_FUNCTION + +BOOLEAN NTAPI WepWindowTreeNewCallback( + _In_ HWND hwnd, + _In_ PH_TREENEW_MESSAGE Message, + _In_opt_ PVOID Parameter1, + _In_opt_ PVOID Parameter2, + _In_opt_ PVOID Context + ) +{ + PWE_WINDOW_TREE_CONTEXT context; + PWE_WINDOW_NODE node; + + context = Context; + + switch (Message) + { + case TreeNewGetChildren: + { + PPH_TREENEW_GET_CHILDREN getChildren = Parameter1; + + node = (PWE_WINDOW_NODE)getChildren->Node; + + if (context->TreeNewSortOrder == NoSortOrder) + { + if (!node) + { + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeRootList->Items; + getChildren->NumberOfChildren = context->NodeRootList->Count; + } + else + { + getChildren->Children = (PPH_TREENEW_NODE *)node->Children->Items; + getChildren->NumberOfChildren = node->Children->Count; + } + } + else + { + if (!node) + { + static PVOID sortFunctions[] = + { + SORT_FUNCTION(Class), + SORT_FUNCTION(Handle), + SORT_FUNCTION(Text), + SORT_FUNCTION(Thread) + }; + int (__cdecl *sortFunction)(void *, const void *, const void *); + + if (context->TreeNewSortColumn < WEWNTLC_MAXIMUM) + sortFunction = sortFunctions[context->TreeNewSortColumn]; + else + sortFunction = NULL; + + if (sortFunction) + { + qsort_s(context->NodeList->Items, context->NodeList->Count, sizeof(PVOID), sortFunction, context); + } + + getChildren->Children = (PPH_TREENEW_NODE *)context->NodeList->Items; + getChildren->NumberOfChildren = context->NodeList->Count; + } + } + } + return TRUE; + case TreeNewIsLeaf: + { + PPH_TREENEW_IS_LEAF isLeaf = Parameter1; + + node = (PWE_WINDOW_NODE)isLeaf->Node; + + if (context->TreeNewSortOrder == NoSortOrder) + isLeaf->IsLeaf = !node->HasChildren; + else + isLeaf->IsLeaf = TRUE; + } + return TRUE; + case TreeNewGetCellText: + { + PPH_TREENEW_GET_CELL_TEXT getCellText = Parameter1; + + node = (PWE_WINDOW_NODE)getCellText->Node; + + switch (getCellText->Id) + { + case WEWNTLC_CLASS: + PhInitializeStringRef(&getCellText->Text, node->WindowClass); + break; + case WEWNTLC_HANDLE: + PhPrintPointer(node->WindowHandleString, node->WindowHandle); + PhInitializeStringRef(&getCellText->Text, node->WindowHandleString); + break; + case WEWNTLC_TEXT: + getCellText->Text = PhGetStringRef(node->WindowText); + break; + case WEWNTLC_THREAD: + if (!node->ThreadString) + node->ThreadString = PhGetClientIdName(&node->ClientId); + getCellText->Text = PhGetStringRef(node->ThreadString); + break; + default: + return FALSE; + } + + getCellText->Flags = TN_CACHE; + } + return TRUE; + case TreeNewGetNodeColor: + { + PPH_TREENEW_GET_NODE_COLOR getNodeColor = Parameter1; + + node = (PWE_WINDOW_NODE)getNodeColor->Node; + + if (!node->WindowVisible) + getNodeColor->ForeColor = RGB(0x55, 0x55, 0x55); + + getNodeColor->Flags = TN_CACHE; + } + return TRUE; + case TreeNewSortChanged: + { + TreeNew_GetSort(hwnd, &context->TreeNewSortColumn, &context->TreeNewSortOrder); + // Force a rebuild to sort the items. + TreeNew_NodesStructured(hwnd); + } + return TRUE; + case TreeNewKeyDown: + { + PPH_TREENEW_KEY_EVENT keyEvent = Parameter1; + + switch (keyEvent->VirtualKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_WINDOW_COPY, 0); + break; + } + } + return TRUE; + case TreeNewLeftDoubleClick: + { + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_WINDOW_PROPERTIES, 0); + } + return TRUE; + case TreeNewNodeExpanding: + { + SendMessage(context->ParentWindowHandle, WM_WE_PLUSMINUS, 0, (LPARAM)Parameter1); + } + return FALSE; + case TreeNewContextMenu: + { + PPH_TREENEW_MOUSE_EVENT mouseEvent = Parameter1; + + SendMessage(context->ParentWindowHandle, WM_COMMAND, ID_SHOWCONTEXTMENU, MAKELONG(mouseEvent->Location.x, mouseEvent->Location.y)); + } + return TRUE; + } + + return FALSE; +} + +VOID WeClearWindowTree( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ) +{ + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + WepDestroyWindowNode(Context->NodeList->Items[i]); + + PhClearHashtable(Context->NodeHashtable); + PhClearList(Context->NodeList); + PhClearList(Context->NodeRootList); +} + +PWE_WINDOW_NODE WeGetSelectedWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ) +{ + PWE_WINDOW_NODE windowNode = NULL; + ULONG i; + + for (i = 0; i < Context->NodeList->Count; i++) + { + windowNode = Context->NodeList->Items[i]; + + if (windowNode->Node.Selected) + return windowNode; + } + + return NULL; +} + +VOID WeGetSelectedWindowNodes( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _Out_ PWE_WINDOW_NODE **Windows, + _Out_ PULONG NumberOfWindows + ) +{ + PPH_LIST list; + ULONG i; + + list = PhCreateList(2); + + for (i = 0; i < Context->NodeList->Count; i++) + { + PWE_WINDOW_NODE node = Context->NodeList->Items[i]; + + if (node->Node.Selected) + { + PhAddItemList(list, node); + } + } + + *Windows = PhAllocateCopy(list->Items, sizeof(PVOID) * list->Count); + *NumberOfWindows = list->Count; + + PhDereferenceObject(list); +} diff --git a/plugins/WindowExplorer/wndtree.h b/plugins/WindowExplorer/wndtree.h new file mode 100644 index 0000000..d207045 --- /dev/null +++ b/plugins/WindowExplorer/wndtree.h @@ -0,0 +1,90 @@ +#ifndef WNDTREE_H +#define WNDTREE_H + +#define WEWNTLC_CLASS 0 +#define WEWNTLC_HANDLE 1 +#define WEWNTLC_TEXT 2 +#define WEWNTLC_THREAD 3 +#define WEWNTLC_MAXIMUM 4 + +typedef struct _WE_WINDOW_NODE +{ + PH_TREENEW_NODE Node; + + struct _WE_WINDOW_NODE *Parent; + PPH_LIST Children; + + union + { + ULONG Flags; + struct + { + ULONG HasChildren : 1; + ULONG Opened : 1; + ULONG WindowVisible : 1; + ULONG Spare : 29; + }; + }; + + PH_STRINGREF TextCache[WEWNTLC_MAXIMUM]; + + HWND WindowHandle; + WCHAR WindowClass[64]; + PPH_STRING WindowText; + CLIENT_ID ClientId; + + WCHAR WindowHandleString[PH_PTR_STR_LEN_1]; + PPH_STRING ThreadString; +} WE_WINDOW_NODE, *PWE_WINDOW_NODE; + +typedef struct _WE_WINDOW_TREE_CONTEXT +{ + HWND ParentWindowHandle; + HWND TreeNewHandle; + ULONG TreeNewSortColumn; + PH_SORT_ORDER TreeNewSortOrder; + + PPH_HASHTABLE NodeHashtable; + PPH_LIST NodeList; + PPH_LIST NodeRootList; +} WE_WINDOW_TREE_CONTEXT, *PWE_WINDOW_TREE_CONTEXT; + +VOID WeInitializeWindowTree( + _In_ HWND ParentWindowHandle, + _In_ HWND TreeNewHandle, + _Out_ PWE_WINDOW_TREE_CONTEXT Context + ); + +VOID WeDeleteWindowTree( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ); + +PWE_WINDOW_NODE WeAddWindowNode( + _Inout_ PWE_WINDOW_TREE_CONTEXT Context + ); + +PWE_WINDOW_NODE WeFindWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _In_ HWND WindowHandle + ); + +VOID WeRemoveWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _In_ PWE_WINDOW_NODE WindowNode + ); + +VOID WeClearWindowTree( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ); + +PWE_WINDOW_NODE WeGetSelectedWindowNode( + _In_ PWE_WINDOW_TREE_CONTEXT Context + ); + +VOID WeGetSelectedWindowNodes( + _In_ PWE_WINDOW_TREE_CONTEXT Context, + _Out_ PWE_WINDOW_NODE **Windows, + _Out_ PULONG NumberOfWindows + ); + +#endif diff --git a/plugins/include/commonutil.h b/plugins/include/commonutil.h new file mode 100644 index 0000000..eea1c1c --- /dev/null +++ b/plugins/include/commonutil.h @@ -0,0 +1,18 @@ +#ifndef _COMMONUTIL_H +#define _COMMONUTIL_H + +FORCEINLINE +HFONT +CommonDuplicateFont( + _In_ HFONT Font + ) +{ + LOGFONT logFont; + + if (GetObject(Font, sizeof(LOGFONT), &logFont)) + return CreateFontIndirect(&logFont); + else + return NULL; +} + +#endif \ No newline at end of file diff --git a/plugins/include/toolstatusintf.h b/plugins/include/toolstatusintf.h new file mode 100644 index 0000000..9f366f9 --- /dev/null +++ b/plugins/include/toolstatusintf.h @@ -0,0 +1,49 @@ +#ifndef _TOOLSTATUSINTF_H +#define _TOOLSTATUSINTF_H + +#define TOOLSTATUS_PLUGIN_NAME L"ProcessHacker.ToolStatus" +#define TOOLSTATUS_INTERFACE_VERSION 1 + +typedef PPH_STRING (NTAPI *PTOOLSTATUS_GET_SEARCHBOX_TEXT)( + VOID + ); + +typedef BOOLEAN (NTAPI *PTOOLSTATUS_WORD_MATCH)( + _In_ PPH_STRINGREF Text + ); + +typedef VOID (NTAPI *PTOOLSTATUS_REGISTER_TAB_SEARCH)( + _In_ INT TabIndex, + _In_ PWSTR BannerText + ); + +typedef VOID (NTAPI *PTOOLSTATUS_TAB_ACTIVATE_CONTENT)( + _In_ BOOLEAN Select + ); + +typedef HWND (NTAPI *PTOOLSTATUS_GET_TREENEW_HANDLE)( + VOID + ); + +typedef struct _TOOLSTATUS_TAB_INFO +{ + PWSTR BannerText; + PTOOLSTATUS_TAB_ACTIVATE_CONTENT ActivateContent; + PTOOLSTATUS_GET_TREENEW_HANDLE GetTreeNewHandle; +} TOOLSTATUS_TAB_INFO, *PTOOLSTATUS_TAB_INFO; + +typedef PTOOLSTATUS_TAB_INFO (NTAPI *PTOOLSTATUS_REGISTER_TAB_INFO)( + _In_ INT TabIndex + ); + +typedef struct _TOOLSTATUS_INTERFACE +{ + ULONG Version; + PTOOLSTATUS_GET_SEARCHBOX_TEXT GetSearchboxText; + PTOOLSTATUS_WORD_MATCH WordMatch; + PTOOLSTATUS_REGISTER_TAB_SEARCH RegisterTabSearchDeprecated; + PPH_CALLBACK SearchChangedEvent; + PTOOLSTATUS_REGISTER_TAB_INFO RegisterTabInfo; +} TOOLSTATUS_INTERFACE, *PTOOLSTATUS_INTERFACE; + +#endif diff --git a/plugins/readme.txt b/plugins/readme.txt new file mode 100644 index 0000000..7e0a287 --- /dev/null +++ b/plugins/readme.txt @@ -0,0 +1,3 @@ +Before compiling these plugins you must have generated the +Process Hacker SDK. You can do this by running makesdk.cmd in +build/sdk. \ No newline at end of file diff --git a/tests/phlib-test/main.c b/tests/phlib-test/main.c new file mode 100644 index 0000000..c6799fb --- /dev/null +++ b/tests/phlib-test/main.c @@ -0,0 +1,16 @@ +#include "tests.h" + +int __cdecl wmain(int argc, wchar_t *argv[]) +{ + NTSTATUS status; + + status = PhInitializePhLib(); + assert(NT_SUCCESS(status)); + + Test_basesup(); + Test_avltree(); + Test_format(); + Test_util(); + + return 0; +} diff --git a/tests/phlib-test/phlib-test.vcxproj b/tests/phlib-test/phlib-test.vcxproj new file mode 100644 index 0000000..05b1d7d --- /dev/null +++ b/tests/phlib-test/phlib-test.vcxproj @@ -0,0 +1,116 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {0C21014E-BC90-4AE5-AA32-398445C13B28} + phlib-test + Win32Proj + 10.0.10586.0 + + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + + + + + + + + + + + $(ProjectDir)bin\$(Configuration)\ + $(ProjectDir)obj\$(Configuration)\ + true + $(ProjectDir)bin\$(Configuration)\ + $(ProjectDir)obj\$(Configuration)\ + false + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;%(AdditionalIncludeDirectories) + _PHLIB_;_CONSOLE;WIN32;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + true + Console + MachineX86 + 5.01 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;%(AdditionalIncludeDirectories) + _PHLIB_;_CONSOLE;WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX86 + true + 5.01 + + + + + + + + + + + + {477d0215-f252-41a1-874b-f27e3ea1ed17} + false + + + + + + + + + \ No newline at end of file diff --git a/tests/phlib-test/phlib-test.vcxproj.filters b/tests/phlib-test/phlib-test.vcxproj.filters new file mode 100644 index 0000000..4e5709f --- /dev/null +++ b/tests/phlib-test/phlib-test.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/tests/phlib-test/t_avltree.c b/tests/phlib-test/t_avltree.c new file mode 100644 index 0000000..61b6882 --- /dev/null +++ b/tests/phlib-test/t_avltree.c @@ -0,0 +1,100 @@ +#include "tests.h" + +typedef struct _NODE +{ + PH_AVL_LINKS Links; + ULONG Value; +} NODE, *PNODE; + +static LONG CompareFunction( + _In_ PPH_AVL_LINKS Links1, + _In_ PPH_AVL_LINKS Links2 + ) +{ + PNODE node1 = CONTAINING_RECORD(Links1, NODE, Links); + PNODE node2 = CONTAINING_RECORD(Links2, NODE, Links); + + return uintcmp(node1->Value, node2->Value); +} + +#define BOUND_FUNCTION(DefineName, FunctionName) \ + static ULONG DefineName( \ + _In_ PPH_AVL_TREE Tree, \ + _In_ ULONG Value \ + ) \ + { \ + NODE node; \ + PPH_AVL_LINKS result; \ +\ + node.Value = Value; \ + result = FunctionName(Tree, &node.Links); \ +\ + if (result) \ + return CONTAINING_RECORD(result, NODE, Links)->Value; \ + else \ + return -1; \ + } + +BOUND_FUNCTION(lbound, PhLowerBoundElementAvlTree); +BOUND_FUNCTION(ubound, PhUpperBoundElementAvlTree); +BOUND_FUNCTION(ldbound, PhLowerDualBoundElementAvlTree); +BOUND_FUNCTION(udbound, PhUpperDualBoundElementAvlTree); + +VOID Test_avltree( + VOID + ) +{ + PH_AVL_TREE tree; + NODE nodes[4]; + + PhInitializeAvlTree(&tree, CompareFunction); + + nodes[0].Value = 3; + PhAddElementAvlTree(&tree, &nodes[0].Links); + nodes[1].Value = 1; + PhAddElementAvlTree(&tree, &nodes[1].Links); + nodes[2].Value = 5; + PhAddElementAvlTree(&tree, &nodes[2].Links); + nodes[3].Value = 7; + PhAddElementAvlTree(&tree, &nodes[3].Links); + + assert(lbound(&tree, 0) == 1); + assert(lbound(&tree, 1) == 1); + assert(lbound(&tree, 2) == 3); + assert(lbound(&tree, 3) == 3); + assert(lbound(&tree, 4) == 5); + assert(lbound(&tree, 5) == 5); + assert(lbound(&tree, 6) == 7); + assert(lbound(&tree, 7) == 7); + assert(lbound(&tree, 8) == -1); + + assert(ubound(&tree, 0) == 1); + assert(ubound(&tree, 1) == 3); + assert(ubound(&tree, 2) == 3); + assert(ubound(&tree, 3) == 5); + assert(ubound(&tree, 4) == 5); + assert(ubound(&tree, 5) == 7); + assert(ubound(&tree, 6) == 7); + assert(ubound(&tree, 7) == -1); + assert(ubound(&tree, 8) == -1); + + assert(ldbound(&tree, 0) == -1); + assert(ldbound(&tree, 1) == -1); + assert(ldbound(&tree, 2) == 1); + assert(ldbound(&tree, 3) == 1); + assert(ldbound(&tree, 4) == 3); + assert(ldbound(&tree, 5) == 3); + assert(ldbound(&tree, 6) == 5); + assert(ldbound(&tree, 7) == 5); + assert(ldbound(&tree, 8) == 7); + + assert(udbound(&tree, 0) == -1); + assert(udbound(&tree, 1) == 1); + assert(udbound(&tree, 2) == 1); + assert(udbound(&tree, 3) == 3); + assert(udbound(&tree, 4) == 3); + assert(udbound(&tree, 5) == 5); + assert(udbound(&tree, 6) == 5); + assert(udbound(&tree, 7) == 7); + assert(udbound(&tree, 8) == 7); +} diff --git a/tests/phlib-test/t_basesup.c b/tests/phlib-test/t_basesup.c new file mode 100644 index 0000000..f32d49d --- /dev/null +++ b/tests/phlib-test/t_basesup.c @@ -0,0 +1,352 @@ +#include "tests.h" + +static VOID Test_time( + VOID + ) +{ + LARGE_INTEGER time; + FILETIME fileTime; + LARGE_INTEGER localTime; + FILETIME localFileTime; + LARGE_INTEGER systemTime; + FILETIME systemFileTime; + + PhQuerySystemTime(&time); + fileTime.dwLowDateTime = time.LowPart; + fileTime.dwHighDateTime = time.HighPart; + + PhSystemTimeToLocalTime(&time, &localTime); + FileTimeToLocalFileTime(&fileTime, &localFileTime); + + assert(localTime.LowPart == localFileTime.dwLowDateTime); + assert(localTime.HighPart == localFileTime.dwHighDateTime); + + PhLocalTimeToSystemTime(&localTime, &systemTime); + LocalFileTimeToFileTime(&localFileTime, &systemFileTime); + + assert(systemTime.LowPart == systemFileTime.dwLowDateTime); + assert(systemTime.HighPart == systemFileTime.dwHighDateTime); +} + +static VOID Test_stringz( + VOID + ) +{ + BOOLEAN result; + CHAR inputA[16] = "test"; + CHAR outputA[16]; + WCHAR inputW[16] = L"test"; + WCHAR outputW[16]; + ULONG returnCount; + PWSTR zero = L"\0\0\0\0\0\0\0\0"; + PWSTR asdf = L"asdfasdfasdfasdf"; + ULONG i; + + for (i = 0; i < 8; i++) + assert(PhCountStringZ(zero + i) == 0); + for (i = 0; i < 16; i++) + assert(PhCountStringZ(asdf + i) == 16 - i); + + result = PhCopyBytesZ(inputA, 4, outputA, 4, &returnCount); + assert(!result && returnCount == 5); + result = PhCopyBytesZ(inputA, 100, outputA, 4, &returnCount); + assert(!result && returnCount == 5); + result = PhCopyBytesZ(inputA, 3, outputA, 4, &returnCount); + assert(result && returnCount == 4); + result = PhCopyBytesZ(inputA, 4, outputA, 5, &returnCount); + assert(result && returnCount == 5); + result = PhCopyBytesZ(inputA, 100, outputA, 5, &returnCount); + assert(result && returnCount == 5); + + result = PhCopyStringZ(inputW, 100, outputW, 4, &returnCount); + assert(!result && returnCount == 5); + result = PhCopyStringZ(inputW, 4, outputW, 5, &returnCount); + assert(result && returnCount == 5); + result = PhCopyStringZ(inputW, 100, outputW, 5, &returnCount); + assert(result && returnCount == 5); + + result = PhCopyStringZFromMultiByte(inputA, 4, outputW, 4, &returnCount); + assert(!result && returnCount == 5); + result = PhCopyStringZFromMultiByte(inputA, 100, outputW, 4, &returnCount); + assert(!result && returnCount == 5); + result = PhCopyStringZFromMultiByte(inputA, 3, outputW, 4, &returnCount); + assert(result && returnCount == 4); + result = PhCopyStringZFromMultiByte(inputA, 4, outputW, 5, &returnCount); + assert(result && returnCount == 5); + result = PhCopyStringZFromMultiByte(inputA, 100, outputW, 5, &returnCount); + assert(result && returnCount == 5); + + assert(PhCompareStringZNatural(L"abc", L"abc", FALSE) == 0); + assert(PhCompareStringZNatural(L"abc", L"abc", TRUE) == 0); + assert(PhCompareStringZNatural(L"abc", L"ABC", FALSE) != 0); + assert(PhCompareStringZNatural(L"abc", L"ABC", TRUE) == 0); + assert(PhCompareStringZNatural(L"abc", L"abd", FALSE) < 0); + assert(PhCompareStringZNatural(L"abe", L"abd", FALSE) > 0); + assert(PhCompareStringZNatural(L"1", L"2", FALSE) < 0); + assert(PhCompareStringZNatural(L"12", L"9", FALSE) > 0); + assert(PhCompareStringZNatural(L"file-1", L"file-9", FALSE) < 0); + assert(PhCompareStringZNatural(L"file-12", L"file-9", FALSE) > 0); + assert(PhCompareStringZNatural(L"file-12", L"file-90", FALSE) < 0); +} + +VOID Test_stringref( + VOID + ) +{ + ULONG i; + ULONG j; + PH_STRINGREF s1; + PH_STRINGREF s2; + PH_STRINGREF s3; + WCHAR buffer[26 * 2]; + + // PhEqualStringRef, PhFindCharInStringRef, PhFindLastCharInStringRef + + // Alignment tests + + s1.Buffer = buffer; + s1.Length = sizeof(buffer); + + for (i = 0; i < 26; i++) + s1.Buffer[i] = (WCHAR)i; + + for (i = 0; i < 26; i++) + assert(PhFindCharInStringRef(&s1, (WCHAR)i, FALSE) == i); + + memset(buffer, 0, sizeof(buffer)); + s1.Length = 0; + + for (i = 0; i < 26; i++) + assert(PhFindCharInStringRef(&s1, 0, FALSE) == -1); + + buffer[26] = 1; + + for (i = 0; i < 26; i++) + { + s1.Buffer = buffer + 26 - i; + s1.Length = i * sizeof(WCHAR); + assert(PhFindCharInStringRef(&s1, 1, FALSE) == -1); + } + + for (i = 1; i < 26; i++) + { + s1.Buffer = buffer; + s1.Length = i * 2 * sizeof(WCHAR); + + for (j = 0; j < i; j++) + { + buffer[j] = (WCHAR)('a' + j); + buffer[i + j] = (WCHAR)('A' + j); + } + + s2.Buffer = buffer; + s2.Length = i * sizeof(WCHAR); + s3.Buffer = buffer + i; + s3.Length = i * sizeof(WCHAR); + assert(!PhEqualStringRef(&s2, &s3, FALSE)); + assert(PhEqualStringRef(&s2, &s3, TRUE)); + + for (j = 0; j < i; j++) + { + buffer[j] = 'z'; + assert(!PhEqualStringRef(&s2, &s3, FALSE)); + assert(!PhEqualStringRef(&s2, &s3, TRUE)); + buffer[j] = (WCHAR)('a' + j); + } + + s3 = s2; + assert(PhEqualStringRef(&s2, &s3, FALSE)); + assert(PhEqualStringRef(&s2, &s3, TRUE)); + + for (j = 0; j < i; j++) + { + assert(PhFindCharInStringRef(&s1, s1.Buffer[j], FALSE) == j); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j], FALSE) == j); + assert(PhFindCharInStringRef(&s1, s1.Buffer[j], TRUE) == j % i); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j], TRUE) == i + j % i); + } + + s1.Length = i * sizeof(WCHAR); + + for (j = 0; j < i; j++) + { + assert(PhFindCharInStringRef(&s1, s1.Buffer[j] - ('a' - 'A'), FALSE) == -1); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j] - ('a' - 'A'), FALSE) == -1); + assert(PhFindCharInStringRef(&s1, s1.Buffer[j] - ('a' - 'A'), TRUE) == j); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j] - ('a' - 'A'), TRUE) == j); + } + + s1.Buffer += i; + + for (j = 0; j < i; j++) + { + assert(PhFindCharInStringRef(&s1, s1.Buffer[j] + ('a' - 'A'), FALSE) == -1); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j] + ('a' - 'A'), FALSE) == -1); + assert(PhFindCharInStringRef(&s1, s1.Buffer[j] + ('a' - 'A'), TRUE) == j); + assert(PhFindLastCharInStringRef(&s1, s1.Buffer[j] + ('a' - 'A'), TRUE) == j); + } + } + + // PhFindStringInStringRef + +#define DO_STRSTR_TEST(func, s1, s2, expected, ...) \ + do { \ + PH_STRINGREF ___t1; \ + PH_STRINGREF ___t2; \ + PhInitializeStringRef(&___t1, s1); \ + PhInitializeStringRef(&___t2, s2); \ + assert(func(&___t1, &___t2, __VA_ARGS__) == expected); \ + } while (0) + + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdf", L"f", 3, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdf", L"g", -1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"g", 7, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"asdg", 4, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"asdgh", -1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"asdfasdg", 0, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"sdfasdg", 1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"asdfasdgg", -1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"asdfasdgggggg", -1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"", L"asdfasdgggggg", -1, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"asdfasdg", L"", 0, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"", L"", 0, FALSE); + // Test roll-over + DO_STRSTR_TEST(PhFindStringInStringRef, L"0sdfasdf1sdfasdf2sdfasdf3sdfasdg4sdfg", L"0sdfasdf1sdfasdf2sdfasdf3sdfasdg4sdfg", 0, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"0sdfasdf1sdfasdf2sdfasdf3sdfasdg4sdfg", L"asdg4sdfg", 28, FALSE); + DO_STRSTR_TEST(PhFindStringInStringRef, L"0sdfasdf1sdfasdf2sdfasdf3sdfasdg4sdfg", L"asdg4Gdfg", -1, FALSE); +} + +VOID Test_hexstring( + VOID + ) +{ + BOOLEAN result; + PH_STRINGREF sr; + PPH_STRING string; + UCHAR buffer[16]; + + PhInitializeStringRef(&sr, L"0011223344"); + result = PhHexStringToBuffer(&sr, buffer); + assert(result && buffer[0] == 0 && buffer[1] == 0x11 && buffer[2] == 0x22 && buffer[3] == 0x33 && buffer[4] == 0x44); + + PhInitializeStringRef(&sr, L"00111"); + result = PhHexStringToBuffer(&sr, buffer); + assert(!result); + + buffer[0] = 0; + buffer[1] = 0x99; + buffer[2] = 0xff; + + string = PhBufferToHexString(buffer, 3); + assert(wcscmp(string->Buffer, L"0099ff") == 0); +} + +VOID Test_strint( + VOID + ) +{ + PH_STRINGREF sr; + LONG64 integer; + PPH_STRING string; + + PhInitializeStringRef(&sr, L"123"); + PhStringToInteger64(&sr, 0, &integer); + assert(integer == 123); + PhStringToInteger64(&sr, 10, &integer); + assert(integer == 123); + PhStringToInteger64(&sr, 8, &integer); + assert(integer == 0123); + PhStringToInteger64(&sr, 16, &integer); + assert(integer == 0x123); + + PhInitializeStringRef(&sr, L"0o123"); + PhStringToInteger64(&sr, 0, &integer); + assert(integer == 0123); + PhInitializeStringRef(&sr, L"0x123"); + PhStringToInteger64(&sr, 0, &integer); + assert(integer == 0x123); + + string = PhIntegerToString64(123, 0, FALSE); + assert(wcscmp(string->Buffer, L"123") == 0); + string = PhIntegerToString64(123, 10, FALSE); + assert(wcscmp(string->Buffer, L"123") == 0); + string = PhIntegerToString64(123, 8, FALSE); + assert(wcscmp(string->Buffer, L"173") == 0); + string = PhIntegerToString64(123, 16, FALSE); + assert(wcscmp(string->Buffer, L"7b") == 0); + + string = PhIntegerToString64(-123, 0, TRUE); + assert(wcscmp(string->Buffer, L"-123") == 0); + string = PhIntegerToString64(-123, 10, TRUE); + assert(wcscmp(string->Buffer, L"-123") == 0); + string = PhIntegerToString64(-123, 8, TRUE); + assert(wcscmp(string->Buffer, L"-173") == 0); + string = PhIntegerToString64(-123, 16, TRUE); + assert(wcscmp(string->Buffer, L"-7b") == 0); + + string = PhIntegerToString64(-123, 0, FALSE); + assert(wcscmp(string->Buffer, L"18446744073709551493") == 0); +} + +VOID Test_unicode( + VOID + ) +{ + BOOLEAN result; + ULONG codePoints[6]; + SIZE_T i; + WCHAR utf16[sizeof(codePoints) / sizeof(WCHAR)]; + CHAR utf8[sizeof(codePoints) / sizeof(CHAR)]; + ULONG numberOfCodePoints; + SIZE_T utf16Position = 0; + SIZE_T utf8Position = 0; + PPH_STRING utf16_1, utf16_2, utf16_3; + PPH_BYTES utf8_1, utf8_2, utf8_3; + + codePoints[0] = 0; + codePoints[1] = 0x50; + codePoints[2] = 0x312; + codePoints[3] = 0x3121; + codePoints[4] = 0x31212; + codePoints[5] = PH_UNICODE_MAX_CODE_POINT; + + for (i = 0; i < sizeof(codePoints) / sizeof(ULONG); i++) + { + result = PhEncodeUnicode(PH_UNICODE_UTF16, codePoints[i], utf16 + utf16Position, &numberOfCodePoints); + assert(result); + utf16Position += numberOfCodePoints; + + result = PhEncodeUnicode(PH_UNICODE_UTF8, codePoints[i], utf8 + utf8Position, &numberOfCodePoints); + assert(result); + utf8Position += numberOfCodePoints; + } + + utf16_1 = PhCreateStringEx(utf16, utf16Position * sizeof(WCHAR)); + utf8_1 = PhCreateBytesEx(utf8, utf8Position); + utf16_2 = PhConvertUtf8ToUtf16Ex(utf8_1->Buffer, utf8_1->Length); + utf8_2 = PhConvertUtf16ToUtf8Ex(utf16_1->Buffer, utf16_1->Length); + utf16_3 = PhConvertUtf8ToUtf16Ex(utf8_2->Buffer, utf8_2->Length); + utf8_3 = PhConvertUtf16ToUtf8Ex(utf16_2->Buffer, utf16_2->Length); + + assert(utf16_1->Length == utf16_2->Length); + assert(memcmp(utf16_1->Buffer, utf16_2->Buffer, utf16_1->Length) == 0); + assert(utf16_2->Length == utf16_3->Length); + assert(memcmp(utf16_2->Buffer, utf16_3->Buffer, utf16_2->Length) == 0); + + assert(utf8_1->Length == utf8_2->Length); + assert(memcmp(utf8_1->Buffer, utf8_2->Buffer, utf8_1->Length) == 0); + assert(utf8_2->Length == utf8_3->Length); + assert(memcmp(utf8_2->Buffer, utf8_3->Buffer, utf8_2->Length) == 0); +} + +VOID Test_basesup( + VOID + ) +{ + Test_time(); + Test_stringz(); + Test_stringref(); + Test_hexstring(); + Test_strint(); + Test_unicode(); +} diff --git a/tests/phlib-test/t_format.c b/tests/phlib-test/t_format.c new file mode 100644 index 0000000..825f7f6 --- /dev/null +++ b/tests/phlib-test/t_format.c @@ -0,0 +1,511 @@ +#include "tests.h" + +static VOID Test_buffer( + VOID + ) +{ +#define OUTPUT_COUNT 10 + + BOOLEAN result; + PH_FORMAT format[1]; + WCHAR buffer[16]; + SIZE_T returnLength; + + format[0].Type = Int32FormatType; + format[0].u.Int32 = 1234567890; + + result = PhFormatToBuffer(format, 1, buffer, (OUTPUT_COUNT + 1) * sizeof(WCHAR), &returnLength); + assert(result && wcscmp(buffer, L"1234567890") == 0 && returnLength == (OUTPUT_COUNT + 1) * sizeof(WCHAR)); + result = PhFormatToBuffer(format, 1, buffer, 16 * sizeof(WCHAR), &returnLength); + assert(result && wcscmp(buffer, L"1234567890") == 0 && returnLength == (OUTPUT_COUNT + 1) * sizeof(WCHAR)); + result = PhFormatToBuffer(format, 1, buffer, OUTPUT_COUNT * sizeof(WCHAR), &returnLength); + assert(!result && buffer[0] == 0 && returnLength == (OUTPUT_COUNT + 1) * sizeof(WCHAR)); + result = PhFormatToBuffer(format, 1, buffer, 0, &returnLength); + assert(!result && returnLength == (OUTPUT_COUNT + 1) * sizeof(WCHAR)); + result = PhFormatToBuffer(format, 1, NULL, 9999, &returnLength); + assert(!result && returnLength == (OUTPUT_COUNT + 1) * sizeof(WCHAR)); +} + +static VOID Test_char( + VOID + ) +{ + BOOLEAN result; + PH_FORMAT format[2]; + WCHAR buffer[1024]; + + format[0].Type = CharFormatType; + format[0].u.Char = 'H'; + format[1].Type = CharFormatType; + format[1].u.Char = 'i'; + result = PhFormatToBuffer(format, 2, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"Hi") == 0); +} + +static VOID Test_string( + VOID + ) +{ + BOOLEAN result; + PH_FORMAT format[4]; + WCHAR buffer[1024]; + + format[0].Type = StringFormatType; + PhInitializeStringRef(&format[0].u.String, L"This "); + format[1].Type = StringZFormatType; + format[1].u.StringZ = L"is "; + format[2].Type = MultiByteStringFormatType; + PhInitializeBytesRef(&format[2].u.MultiByteString, "a "); + format[3].Type = MultiByteStringZFormatType; + format[3].u.MultiByteStringZ = "string."; + result = PhFormatToBuffer(format, 4, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"This is a string.") == 0); +} + +static BOOLEAN IsThousandSepComma( + VOID + ) +{ + WCHAR thousandSep[4]; + + if (!GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousandSep, 4)) + return FALSE; + if (thousandSep[0] != ',' || thousandSep[1] != 0) + return FALSE; + + return TRUE; +} + +static VOID Test_integer( + VOID + ) +{ + BOOLEAN result; + PH_FORMAT format[1]; + WCHAR buffer[1024]; + + // Basic + + format[0].Type = Int32FormatType; + format[0].u.Int32 = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0") == 0); + + format[0].Type = Int32FormatType; + format[0].u.Int32 = 123; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123") == 0); + + format[0].Type = Int32FormatType; + format[0].u.Int32 = -123; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-123") == 0); + + format[0].Type = UInt32FormatType; + format[0].u.UInt32 = -123; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"4294967173") == 0); + + format[0].Type = Int64FormatType; + format[0].u.Int64 = -1234567890; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1234567890") == 0); + + format[0].Type = UInt64FormatType; + format[0].u.UInt64 = 12345678901234567890; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"12345678901234567890") == 0); + + // Bases + + format[0].Type = UInt64FormatType | FormatUseRadix; + format[0].u.UInt64 = 12345678901234567890; + format[0].Radix = 16; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"ab54a98ceb1f0ad2") == 0); + + format[0].Type = UInt64FormatType | FormatUseRadix | FormatUpperCase; + format[0].u.UInt64 = 12345678901234567890; + format[0].Radix = 16; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"AB54A98CEB1F0AD2") == 0); + + format[0].Type = Int32FormatType | FormatUseRadix; + format[0].u.Int32 = -1234; + format[0].Radix = 8; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-2322") == 0); + + // Prefix sign + + format[0].Type = Int32FormatType | FormatPrefixSign; + format[0].u.Int32 = 1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"+1234") == 0); + + format[0].Type = Int32FormatType | FormatPrefixSign; + format[0].u.Int32 = -1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1234") == 0); + + // Zero pad + + format[0].Type = Int32FormatType | FormatPadZeros; + format[0].Width = 6; + + format[0].u.Int32 = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"000000") == 0); + format[0].u.Int32 = 1; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"000001") == 0); + format[0].u.Int32 = 12345; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"012345") == 0); + format[0].u.Int32 = 123456; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123456") == 0); + format[0].u.Int32 = 1234567890; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1234567890") == 0); + format[0].u.Int32 = -1; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-00001") == 0); + format[0].u.Int32 = -1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-01234") == 0); + format[0].u.Int32 = -12345; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-12345") == 0); + format[0].u.Int32 = -1234567890; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1234567890") == 0); + + // Digit grouping + + if (!IsThousandSepComma()) + return; + + format[0].Type = Int32FormatType | FormatGroupDigits; + + format[0].u.Int32 = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0") == 0); + format[0].u.Int32 = 1; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1") == 0); + format[0].u.Int32 = 12; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"12") == 0); + format[0].u.Int32 = 123; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123") == 0); + format[0].u.Int32 = 1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1,234") == 0); + format[0].u.Int32 = 12345; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"12,345") == 0); + format[0].u.Int32 = 123456; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123,456") == 0); + format[0].u.Int32 = -123; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-123") == 0); + format[0].u.Int32 = -1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1,234") == 0); + format[0].u.Int32 = -12345; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-12,345") == 0); +} + +static VOID Test_float( + VOID + ) +{ + BOOLEAN result; + PH_FORMAT format[1]; + WCHAR buffer[1024]; + + // TODO: Standard and hexadecimal form + + // Basic + + format[0].Type = DoubleFormatType; + format[0].u.Double = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0.000000") == 0); + + format[0].Type = DoubleFormatType; + format[0].u.Double = 1; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1.000000") == 0); + + format[0].Type = DoubleFormatType; + format[0].u.Double = 123456789; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123456789.000000") == 0); + + format[0].Type = DoubleFormatType; + format[0].u.Double = 3.14159265358979; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"3.141593") == 0); + + // Precision + + format[0].Type = DoubleFormatType | FormatUsePrecision; + format[0].u.Double = 0; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision; + format[0].u.Double = 3.14159265358979; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"3") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision; + format[0].u.Double = 3.14159265358979; + format[0].Precision = 1; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"3.1") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision; + format[0].u.Double = 3.14159265358979; + format[0].Precision = 4; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"3.1416") == 0); + + // Crop zeros + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros; + format[0].u.Double = 0; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros; + format[0].u.Double = 1.2; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1.2") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros; + format[0].u.Double = 1.21; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1.21") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros; + format[0].u.Double = 1.216; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1.216") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros; + format[0].u.Double = 1.2159; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1.216") == 0); + + // Prefix sign + + format[0].Type = DoubleFormatType | FormatPrefixSign; + format[0].u.Double = 1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"+1234.000000") == 0); + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatPrefixSign; + format[0].u.Double = 1234; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"+1234") == 0); + + format[0].Type = DoubleFormatType | FormatPrefixSign; + format[0].u.Double = -1234; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1234.000000") == 0); + + format[0].Type = DoubleFormatType | FormatPrefixSign; + format[0].u.Double = -1234.12; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1234.120000") == 0); + + // Zero pad + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatPadZeros; + format[0].Width = 6; + + format[0].u.Double = 0; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"00.000") == 0); + format[0].u.Double = 1.23; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"01.230") == 0); + format[0].u.Double = 123456; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123456.000") == 0); + format[0].u.Double = -1.23; + format[0].Precision = 2; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-01.23") == 0); + format[0].u.Double = -1.23; + format[0].Precision = 3; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1.230") == 0); + + // Digit grouping + + if (!IsThousandSepComma()) + return; + + format[0].Type = DoubleFormatType | FormatUsePrecision | FormatGroupDigits; + + format[0].u.Double = 0; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"0") == 0); + format[0].u.Double = 1; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1") == 0); + format[0].u.Double = 12; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"12") == 0); + format[0].u.Double = 123; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123") == 0); + format[0].u.Double = 1234; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"1,234") == 0); + format[0].u.Double = 12345; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"12,345") == 0); + format[0].u.Double = 123456; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"123,456") == 0); + format[0].u.Double = -123; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-123") == 0); + format[0].u.Double = -1234; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-1,234") == 0); + format[0].u.Double = -12345; + format[0].Precision = 0; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-12,345") == 0); + format[0].u.Double = -12345; + format[0].Precision = 2; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-12,345.00") == 0); + format[0].u.Double = -9876543.21; + format[0].Precision = 5; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"-9,876,543.21000") == 0); +} + +static VOID Test_width( + VOID + ) +{ + BOOLEAN result; + PH_FORMAT format[2]; + WCHAR buffer[1024]; + + PhInitializeStringRef(&format[0].u.String, L"asdf"); + + format[0].Type = StringFormatType; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf") == 0); + + // Left align + + format[0].Type = StringFormatType | FormatLeftAlign; + format[0].Width = 4; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf") == 0); + + format[0].Type = StringFormatType | FormatLeftAlign; + format[0].Width = 2; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf") == 0); + + format[0].Type = StringFormatType | FormatLeftAlign; + format[0].Width = 5; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf ") == 0); + + format[0].Type = StringFormatType | FormatLeftAlign; + format[0].Width = 10; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf ") == 0); + + format[0].Type = StringFormatType | FormatLeftAlign | FormatUsePad; + format[0].Width = 6; + format[0].Pad = '!'; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf!!") == 0); + + // Right align + + format[0].Type = StringFormatType | FormatRightAlign; + format[0].Width = 4; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf") == 0); + + format[0].Type = StringFormatType | FormatRightAlign; + format[0].Width = 2; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"asdf") == 0); + + format[0].Type = StringFormatType | FormatRightAlign; + format[0].Width = 5; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L" asdf") == 0); + + format[0].Type = StringFormatType | FormatRightAlign; + format[0].Width = 10; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L" asdf") == 0); + + format[0].Type = StringFormatType | FormatRightAlign | FormatUsePad; + format[0].Width = 6; + format[0].Pad = '!'; + result = PhFormatToBuffer(format, 1, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L"!!asdf") == 0); + + // Multiple + + format[0].Type = Int32FormatType | FormatRightAlign; + format[0].u.Int32 = 1234; + format[0].Width = 7; + format[1].Type = StringZFormatType | FormatLeftAlign; + format[1].u.StringZ = L"asdf"; + format[1].Width = 10; + result = PhFormatToBuffer(format, 2, buffer, sizeof(buffer), NULL); + assert(result && wcscmp(buffer, L" 1234asdf ") == 0); +} + +VOID Test_format( + VOID + ) +{ + Test_buffer(); + Test_char(); + Test_string(); + Test_integer(); + Test_float(); + Test_width(); +} diff --git a/tests/phlib-test/t_util.c b/tests/phlib-test/t_util.c new file mode 100644 index 0000000..047042d --- /dev/null +++ b/tests/phlib-test/t_util.c @@ -0,0 +1,257 @@ +#include "tests.h" + +static VOID Test_rectangle( + VOID + ) +{ + PH_RECTANGLE r1; + PH_RECTANGLE r2; + + r1.Left = 0; + r1.Top = 0; + r1.Width = 1024; + r1.Height = 1024; + + r2.Width = 100; + r2.Height = 100; + + r2.Left = -10; + r2.Top = -10; + PhAdjustRectangleToBounds(&r2, &r1); + assert(r2.Left == 0 && r2.Top == 0 && r2.Width == 100 && r2.Height == 100); + + r2.Left = 1100; + r2.Top = 1100; + PhAdjustRectangleToBounds(&r2, &r1); + assert(r2.Left == 924 && r2.Top == 924 && r2.Width == 100 && r2.Height == 100); + + PhCenterRectangle(&r2, &r1); + assert(r2.Left == 462 && r2.Top == 462 && r2.Width == 100 && r2.Height == 100); +} + +static BOOLEAN AreGuidsEqual( + _In_ PGUID Guid1, + _In_ PWSTR Guid2 + ) +{ + GUID guid2; + UNICODE_STRING us; + + RtlInitUnicodeString(&us, Guid2); + RtlGUIDFromString(&us, &guid2); + + return memcmp(Guid1, &guid2, sizeof(GUID)) == 0; +} + +static VOID Test_guid( + VOID + ) +{ + GUID guid; + GUID ns; + UNICODE_STRING dnsNamespace = RTL_CONSTANT_STRING(L"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}"); + UNICODE_STRING urlNamespace = RTL_CONSTANT_STRING(L"{6ba7b811-9dad-11d1-80b4-00c04fd430c8}"); + UNICODE_STRING oidNamespace = RTL_CONSTANT_STRING(L"{6ba7b812-9dad-11d1-80b4-00c04fd430c8}"); + UNICODE_STRING x500Namespace = RTL_CONSTANT_STRING(L"{6ba7b814-9dad-11d1-80b4-00c04fd430c8}"); + + // Taken from http://svn.python.org/projects/python/branches/py3k/Lib/test/test_uuid.py + + RtlGUIDFromString(&dnsNamespace, &ns); + PhGenerateGuidFromName(&guid, &ns, "python.org", 10, GUID_VERSION_MD5); + assert(AreGuidsEqual(&guid, L"{6fa459ea-ee8a-3ca4-894e-db77e160355e}")); + + RtlGUIDFromString(&urlNamespace, &ns); + PhGenerateGuidFromName(&guid, &ns, "http://python.org/", 18, GUID_VERSION_MD5); + assert(AreGuidsEqual(&guid, L"{9fe8e8c4-aaa8-32a9-a55c-4535a88b748d}")); + + RtlGUIDFromString(&oidNamespace, &ns); + PhGenerateGuidFromName(&guid, &ns, "1.3.6.1", 7, GUID_VERSION_SHA1); + assert(AreGuidsEqual(&guid, L"{1447fa61-5277-5fef-a9b3-fbc6e44f4af3}")); + + RtlGUIDFromString(&x500Namespace, &ns); + PhGenerateGuidFromName(&guid, &ns, "c=ca", 4, GUID_VERSION_SHA1); + assert(AreGuidsEqual(&guid, L"{cc957dd1-a972-5349-98cd-874190002798}")); +} + +static VOID Test_ellipsis( + VOID + ) +{ + PPH_STRING input; + PPH_STRING output; + + // Normal + + input = PhCreateString(L"asdf 1234"); + + output = PhEllipsisString(input, 9); + assert(wcscmp(output->Buffer, L"asdf 1234") == 0); + output = PhEllipsisString(input, 999); + assert(wcscmp(output->Buffer, L"asdf 1234") == 0); + output = PhEllipsisString(input, 8); + assert(wcscmp(output->Buffer, L"asdf ...") == 0); + output = PhEllipsisString(input, 7); + assert(wcscmp(output->Buffer, L"asdf...") == 0); + output = PhEllipsisString(input, 5); + assert(wcscmp(output->Buffer, L"as...") == 0); + output = PhEllipsisString(input, 4); + assert(wcscmp(output->Buffer, L"a...") == 0); + output = PhEllipsisString(input, 3); + assert(wcscmp(output->Buffer, L"...") == 0); + output = PhEllipsisString(input, 2); + assert(wcscmp(output->Buffer, L"asdf 1234") == 0); + output = PhEllipsisString(input, 1); + assert(wcscmp(output->Buffer, L"asdf 1234") == 0); + output = PhEllipsisString(input, 0); + assert(wcscmp(output->Buffer, L"asdf 1234") == 0); + + // Path + + input = PhCreateString(L"C:\\abcdef\\1234.abc"); + + output = PhEllipsisStringPath(input, 18); + assert(wcscmp(output->Buffer, L"C:\\abcdef\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 999); + assert(wcscmp(output->Buffer, L"C:\\abcdef\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 17); + assert(wcscmp(output->Buffer, L"C:\\ab...\\1234.abc") == 0); // last part is kept + output = PhEllipsisStringPath(input, 16); + assert(wcscmp(output->Buffer, L"C:\\a...\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 15); + assert(wcscmp(output->Buffer, L"C:\\...\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 14); + assert(wcscmp(output->Buffer, L"C:...\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 13); + assert(wcscmp(output->Buffer, L"C...\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 12); + assert(wcscmp(output->Buffer, L"...\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 11); + assert(wcscmp(output->Buffer, L"C:\\a....abc") == 0); // the two sides are split as evenly as possible + output = PhEllipsisStringPath(input, 10); + assert(wcscmp(output->Buffer, L"C:\\....abc") == 0); + output = PhEllipsisStringPath(input, 9); + assert(wcscmp(output->Buffer, L"C:\\...abc") == 0); + output = PhEllipsisStringPath(input, 8); + assert(wcscmp(output->Buffer, L"C:...abc") == 0); + output = PhEllipsisStringPath(input, 7); + assert(wcscmp(output->Buffer, L"C:...bc") == 0); + output = PhEllipsisStringPath(input, 6); + assert(wcscmp(output->Buffer, L"C...bc") == 0); + output = PhEllipsisStringPath(input, 5); + assert(wcscmp(output->Buffer, L"C...c") == 0); + output = PhEllipsisStringPath(input, 4); + assert(wcscmp(output->Buffer, L"...c") == 0); + output = PhEllipsisStringPath(input, 3); + assert(wcscmp(output->Buffer, L"...") == 0); + output = PhEllipsisStringPath(input, 2); + assert(wcscmp(output->Buffer, L"C:\\abcdef\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 1); + assert(wcscmp(output->Buffer, L"C:\\abcdef\\1234.abc") == 0); + output = PhEllipsisStringPath(input, 0); + assert(wcscmp(output->Buffer, L"C:\\abcdef\\1234.abc") == 0); +} + +VOID Test_compareignoremenuprefix( + VOID + ) +{ + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"", L"", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asdf", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asDF", FALSE, FALSE) > 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asDF", TRUE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asdff", FALSE, FALSE) < 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdfff", L"asdff", FALSE, FALSE) > 0); + + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&asdf", L"asdf", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&asdf", L"&asdf", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf", L"&asdf", FALSE, FALSE) != 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf", L"&&asdf", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&&asdf", L"&&asdf", FALSE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&&&asdf", L"&&asdf", FALSE, FALSE) != 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"AAA&&asdf", L"aaa&&&asdf", TRUE, FALSE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"AAA&&&&asdf", L"aaa&&&&asdf", TRUE, FALSE) == 0); + + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"", L"", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asdf", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asDF", FALSE, TRUE) > 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asDF", TRUE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdf", L"asdff", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"asdfff", L"asdff", FALSE, TRUE) != 0); + + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&asdf", L"asdf", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&asdf", L"&asdf", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf", L"&asdf", FALSE, TRUE) != 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf", L"&&asdf&", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf", L"&&asdf&&", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf&", L"&&asdf", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&asdf&&", L"&&asdf", FALSE, TRUE) != 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&&asdf", L"&&asdf", FALSE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"&&&&asdf", L"&&asdf&&", FALSE, TRUE) != 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"AAA&&asdf", L"aaa&&&asdf&&", TRUE, TRUE) == 0); + assert(PhCompareUnicodeStringZIgnoreMenuPrefix(L"AAA&&&&asdf", L"aaa&&&&asdf&&", TRUE, TRUE) == 0); +} + +static VOID Test_wildcards( + VOID + ) +{ + static WCHAR *testCases[][3] = + { + { L"", L"", L"true" }, + { L"", L"a", L"false" }, + { L"a", L"a", L"true" }, + { L"a", L"b", L"false" }, + { L"?", L"b", L"true" }, + { L"??", L"bc", L"true" }, + { L"?c", L"bc", L"true" }, + { L"b?", L"bc", L"true" }, + { L"*", L"a", L"true" }, + { L"**", L"a", L"true" }, + { L"*", L"", L"true" }, + { L"*bc*hij", L"abcdfghij", L"true" }, + { L"*b*a*", L"b", L"false" }, + { L"*bc*hik", L"abcdfghij", L"false" }, + { L"abc*", L"abc", L"true" }, + { L"abc**", L"abc", L"true" }, + { L"*???", L"abc", L"true" }, + { L"*???", L"ab", L"false" }, + { L"*???", L"abcd", L"true" }, + { L"*?*", L"abcd", L"true" }, + { L"*bc", L"abc", L"true" }, + { L"*cc", L"abc", L"false" }, + { L"*a*", L"de", L"false" }, + { L"*???*", L"123", L"true" }, + { L"a*bc", L"abbc", L"true" }, + { L"a*b", L"a", L"false" }, + { L"a*?b", L"axb", L"true" }, + { L"a**b", L"axb", L"true" } + }; + + ULONG i; + BOOLEAN r; + BOOLEAN fail; + + for (i = 0; i < sizeof(testCases) / sizeof(WCHAR *[3]); i++) + { + r = PhMatchWildcards(testCases[i][0], testCases[i][1], TRUE); + fail = r != PhEqualStringZ(testCases[i][2], L"true", FALSE); + + if (fail) + { + wprintf(L"pattern '%s' against '%s': %s (%s expected)\n", + testCases[i][0], testCases[i][1], r ? L"true" : L"false", testCases[i][2]); + assert(FALSE); + } + } +} + +VOID Test_util( + VOID + ) +{ + Test_rectangle(); + Test_guid(); + Test_ellipsis(); + Test_compareignoremenuprefix(); + Test_wildcards(); +} diff --git a/tests/phlib-test/tests.h b/tests/phlib-test/tests.h new file mode 100644 index 0000000..9e65f73 --- /dev/null +++ b/tests/phlib-test/tests.h @@ -0,0 +1,22 @@ +#ifndef TESTS_H +#define TESTS_H + +#include + +VOID Test_basesup( + VOID + ); + +VOID Test_avltree( + VOID + ); + +VOID Test_format( + VOID + ); + +VOID Test_util( + VOID + ); + +#endif diff --git a/tools/CustomSignTool/CustomSignTool.vcxproj b/tools/CustomSignTool/CustomSignTool.vcxproj new file mode 100644 index 0000000..2630567 --- /dev/null +++ b/tools/CustomSignTool/CustomSignTool.vcxproj @@ -0,0 +1,207 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E8CD0A41-1537-4EA6-98AC-E80CD59C478E} + CustomSignTool + Win32Proj + 10.0.10586.0 + + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + + + false + + + false + + + false + + + false + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN32;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + + + phlib.lib;ntdll.lib;bcrypt.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Console + MachineX86 + 5.01 + + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN64;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + + + phlib.lib;ntdll.lib;bcrypt.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Console + MachineX64 + 5.02 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + StreamingSIMDExtensions + true + + + phlib.lib;ntdll.lib;bcrypt.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Console + true + true + MachineX86 + true + 5.01 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN64;DEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;bcrypt.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Console + true + true + MachineX64 + true + 5.02 + + + + + + + + {477d0215-f252-41a1-874b-f27e3ea1ed17} + false + + + + + + \ No newline at end of file diff --git a/tools/CustomSignTool/CustomSignTool.vcxproj.filters b/tools/CustomSignTool/CustomSignTool.vcxproj.filters new file mode 100644 index 0000000..dd9fd41 --- /dev/null +++ b/tools/CustomSignTool/CustomSignTool.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + \ No newline at end of file diff --git a/tools/CustomSignTool/bin/Release32/CustomSignTool.exe b/tools/CustomSignTool/bin/Release32/CustomSignTool.exe new file mode 100644 index 0000000..7256ca2 Binary files /dev/null and b/tools/CustomSignTool/bin/Release32/CustomSignTool.exe differ diff --git a/tools/CustomSignTool/bin/Release64/CustomSignTool.exe b/tools/CustomSignTool/bin/Release64/CustomSignTool.exe new file mode 100644 index 0000000..a814786 Binary files /dev/null and b/tools/CustomSignTool/bin/Release64/CustomSignTool.exe differ diff --git a/tools/CustomSignTool/main.c b/tools/CustomSignTool/main.c new file mode 100644 index 0000000..5618469 --- /dev/null +++ b/tools/CustomSignTool/main.c @@ -0,0 +1,362 @@ +/* + * Process Hacker - + * Custom data signing tool + * + * Copyright (C) 2016 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +#define CST_SIGN_ALGORITHM BCRYPT_ECDSA_P256_ALGORITHM +#define CST_SIGN_ALGORITHM_BITS 256 +#define CST_HASH_ALGORITHM BCRYPT_SHA256_ALGORITHM +#define CST_BLOB_PRIVATE BCRYPT_ECCPRIVATE_BLOB +#define CST_BLOB_PUBLIC BCRYPT_ECCPUBLIC_BLOB + +#define FILE_BUFFER_SIZE PAGE_SIZE + +#define ARG_KEY 1 +#define ARG_SIG 2 + +PPH_STRING CstCommand = NULL; +PPH_STRING CstArgument1 = NULL; +PPH_STRING CstArgument2 = NULL; +PPH_STRING CstKeyFileName = NULL; +PPH_STRING CstSigFileName = NULL; + +PWSTR CstHelpMessage = + L"Usage: CustomSignTool.exe command ...\n" + L"Commands:\n" + L"createkeypair\tprivatekeyfile publickeyfile\n" + L"sign\t\t-k privatekeyfile -s outputsigfile inputfile\n" + L"verify\t\t-k publickeyfile -s inputsigfile inputfile\n" + ; + +static BOOLEAN NTAPI CstCommandLineCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + if (Option) + { + switch (Option->Id) + { + case ARG_KEY: + PhSwapReference(&CstKeyFileName, Value); + break; + case ARG_SIG: + PhSwapReference(&CstSigFileName, Value); + break; + } + } + else + { + if (!CstCommand) + PhSwapReference(&CstCommand, Value); + else if (!CstArgument1) + PhSwapReference(&CstArgument1, Value); + else if (!CstArgument2) + PhSwapReference(&CstArgument2, Value); + } + + return TRUE; +} + +DECLSPEC_NORETURN +static VOID CstFailWith( + _In_ PWSTR Message + ) +{ + wprintf(L"%s\n", Message); + RtlExitUserProcess(1); +} + +DECLSPEC_NORETURN +static VOID CstFailWithStatus( + _In_ PWSTR Message, + _In_ NTSTATUS Status, + _In_opt_ ULONG Win32Result + ) +{ + wprintf(L"%s: %s\n", Message, PhGetStatusMessage(Status, Win32Result)->Buffer); + RtlExitUserProcess(1); +} + +static VOID CstExportKey( + _In_ BCRYPT_KEY_HANDLE KeyHandle, + _In_ PWSTR BlobType, + _In_ PWSTR FileName, + _In_ PWSTR Description + ) +{ + NTSTATUS status; + ULONG blobSize; + PVOID blob; + HANDLE fileHandle; + IO_STATUS_BLOCK iosb; + + if (!NT_SUCCESS(status = BCryptExportKey(KeyHandle, NULL, BlobType, NULL, 0, &blobSize, 0))) + CstFailWithStatus(PhFormatString(L"Unable to export %s: Unable to get blob size", Description)->Buffer, status, 0); + blob = PhAllocate(blobSize); + if (!NT_SUCCESS(status = BCryptExportKey(KeyHandle, NULL, BlobType, blob, blobSize, &blobSize, 0))) + CstFailWithStatus(PhFormatString(L"Unable to export %s: Unable to get blob data", Description)->Buffer, status, 0); + + if (!NT_SUCCESS(status = PhCreateFileWin32(&fileHandle, FileName, FILE_GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE))) + CstFailWithStatus(PhFormatString(L"Unable to create '%s'", FileName)->Buffer, status, 0); + if (!NT_SUCCESS(status = NtWriteFile(fileHandle, NULL, NULL, NULL, &iosb, blob, blobSize, NULL, NULL))) + CstFailWithStatus(PhFormatString(L"Unable to write blob to '%s'", FileName)->Buffer, status, 0); + NtClose(fileHandle); + + RtlSecureZeroMemory(blob, blobSize); + PhFree(blob); +} + +static PVOID CstReadFile( + _In_ PWSTR FileName, + _In_ ULONG FileSizeLimit, + _Out_ PULONG FileSize + ) +{ + NTSTATUS status; + HANDLE fileHandle; + LARGE_INTEGER fileSize; + PVOID buffer; + IO_STATUS_BLOCK iosb; + + if (!NT_SUCCESS(status = PhCreateFileWin32(&fileHandle, FileName, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE))) + CstFailWithStatus(PhFormatString(L"Unable to open '%s'", FileName)->Buffer, status, 0); + if (!NT_SUCCESS(status = PhGetFileSize(fileHandle, &fileSize))) + CstFailWithStatus(PhFormatString(L"Unable to get the size of '%s'", FileName)->Buffer, status, 0); + if (fileSize.QuadPart > FileSizeLimit) + CstFailWith(PhFormatString(L"The file '%s' is too large", FileName)->Buffer); + buffer = PhAllocate((ULONG)fileSize.QuadPart); + if (!NT_SUCCESS(status = NtReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, (ULONG)fileSize.QuadPart, NULL, NULL))) + CstFailWithStatus(PhFormatString(L"Unable to read '%s'", FileName)->Buffer, status, 0); + NtClose(fileHandle); + + *FileSize = (ULONG)fileSize.QuadPart; + + return buffer; +} + +static PVOID CstHashFile( + _In_ PWSTR FileName, + _Out_ PULONG HashSize + ) +{ + NTSTATUS status; + HANDLE fileHandle; + PVOID buffer; + IO_STATUS_BLOCK iosb; + BCRYPT_ALG_HANDLE hashAlgHandle; + ULONG querySize; + ULONG hashObjectSize; + ULONG hashSize; + PVOID hashObject; + BCRYPT_HASH_HANDLE hashHandle; + PVOID hash; + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hashAlgHandle, CST_HASH_ALGORITHM, NULL, 0))) + CstFailWithStatus(L"Unable to open the hashing algorithm provider", status, 0); + if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectSize, sizeof(ULONG), &querySize, 0))) + CstFailWithStatus(L"Unable to query hash object size", status, 0); + if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_HASH_LENGTH, (PUCHAR)&hashSize, sizeof(ULONG), &querySize, 0))) + CstFailWithStatus(L"Unable to query hash size", status, 0); + hashObject = PhAllocate(hashObjectSize); + if (!NT_SUCCESS(status = BCryptCreateHash(hashAlgHandle, &hashHandle, hashObject, hashObjectSize, NULL, 0, 0))) + CstFailWithStatus(L"Unable to get hash handle", status, 0); + + if (!NT_SUCCESS(status = PhCreateFileWin32(&fileHandle, FileName, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE))) + CstFailWithStatus(PhFormatString(L"Unable to open '%s'", FileName)->Buffer, status, 0); + + buffer = PhAllocatePage(FILE_BUFFER_SIZE, NULL); + + while (TRUE) + { + if (!NT_SUCCESS(status = NtReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, FILE_BUFFER_SIZE, NULL, NULL))) + { + if (status == STATUS_END_OF_FILE) + break; + + CstFailWithStatus(PhFormatString(L"Unable to read '%s'", FileName)->Buffer, status, 0); + } + + if (!NT_SUCCESS(status = BCryptHashData(hashHandle, buffer, (ULONG)iosb.Information, 0))) + CstFailWithStatus(L"Unable to hash file", status, 0); + } + + PhFreePage(buffer); + NtClose(fileHandle); + + hash = PhAllocate(hashSize); + if (!NT_SUCCESS(status = BCryptFinishHash(hashHandle, hash, hashSize, 0))) + CstFailWithStatus(L"Unable to complete the hash", status, 0); + PhFree(hashObject); + BCryptDestroyHash(hashHandle); + BCryptCloseAlgorithmProvider(hashAlgHandle, 0); + + *HashSize = hashSize; + + return hash; +} + +int __cdecl wmain(int argc, wchar_t *argv[]) +{ + static PH_COMMAND_LINE_OPTION options[] = + { + { ARG_KEY, L"k", MandatoryArgumentType }, + { ARG_SIG, L"s", MandatoryArgumentType } + }; + + NTSTATUS status; + PH_STRINGREF commandLine; + + if (!NT_SUCCESS(PhInitializePhLibEx(0, 0, 0))) + return 1; + + PhUnicodeStringToStringRef(&NtCurrentPeb()->ProcessParameters->CommandLine, &commandLine); + PhParseCommandLine( + &commandLine, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_FIRST_PART, + CstCommandLineCallback, + NULL + ); + + if (!CstCommand) + CstFailWith(CstHelpMessage); + + if (PhEqualString2(CstCommand, L"createkeypair", TRUE)) + { + BCRYPT_ALG_HANDLE signAlgHandle; + BCRYPT_KEY_HANDLE keyHandle; + + if (!CstArgument1 || !CstArgument2) + CstFailWith(CstHelpMessage); + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0))) + CstFailWithStatus(L"Unable to open the signing algorithm provider", status, 0); + if (!NT_SUCCESS(status = BCryptGenerateKeyPair(signAlgHandle, &keyHandle, CST_SIGN_ALGORITHM_BITS, 0))) + CstFailWithStatus(L"Unable to create the key", status, 0); + if (!NT_SUCCESS(status = BCryptFinalizeKeyPair(keyHandle, 0))) + CstFailWithStatus(L"Unable to finalize the key", status, 0); + + CstExportKey(keyHandle, CST_BLOB_PRIVATE, CstArgument1->Buffer, L"private key"); + CstExportKey(keyHandle, CST_BLOB_PUBLIC, CstArgument2->Buffer, L"public key"); + + BCryptDestroyKey(keyHandle); + BCryptCloseAlgorithmProvider(signAlgHandle, 0); + } + else if (PhEqualString2(CstCommand, L"sign", TRUE)) + { + BCRYPT_ALG_HANDLE signAlgHandle; + HANDLE fileHandle; + ULONG bufferSize; + PVOID buffer; + IO_STATUS_BLOCK iosb; + BCRYPT_KEY_HANDLE keyHandle; + ULONG hashSize; + PVOID hash; + ULONG signatureSize; + PVOID signature; + + if (!CstArgument1 || !CstKeyFileName || !CstSigFileName) + CstFailWith(CstHelpMessage); + + // Import the key. + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0))) + CstFailWithStatus(L"Unable to open the signing algorithm provider", status, 0); + buffer = CstReadFile(CstKeyFileName->Buffer, 1024 * 1024, &bufferSize); + if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, CST_BLOB_PRIVATE, &keyHandle, buffer, bufferSize, 0))) + CstFailWithStatus(PhFormatString(L"Unable to import the private key", CstKeyFileName->Buffer)->Buffer, status, 0); + PhFree(buffer); + + // Hash the file. + + hash = CstHashFile(CstArgument1->Buffer, &hashSize); + + // Sign the hash. + + if (!NT_SUCCESS(status = BCryptSignHash(keyHandle, NULL, hash, hashSize, NULL, 0, &signatureSize, 0))) + CstFailWithStatus(L"Unable to get the signature size", status, 0); + signature = PhAllocate(signatureSize); + if (!NT_SUCCESS(status = BCryptSignHash(keyHandle, NULL, hash, hashSize, signature, signatureSize, &signatureSize, 0))) + CstFailWithStatus(L"Unable to create the signature", status, 0); + PhFree(hash); + BCryptDestroyKey(keyHandle); + BCryptCloseAlgorithmProvider(signAlgHandle, 0); + + // Write the signature to the output file. + + if (!NT_SUCCESS(status = PhCreateFileWin32(&fileHandle, CstSigFileName->Buffer, FILE_GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE))) + CstFailWithStatus(PhFormatString(L"Unable to create '%s'", CstSigFileName->Buffer)->Buffer, status, 0); + if (!NT_SUCCESS(status = NtWriteFile(fileHandle, NULL, NULL, NULL, &iosb, signature, signatureSize, NULL, NULL))) + CstFailWithStatus(PhFormatString(L"Unable to write signature to '%s'", CstSigFileName->Buffer)->Buffer, status, 0); + NtClose(fileHandle); + PhFree(signature); + } + else if (PhEqualString2(CstCommand, L"verify", TRUE)) + { + BCRYPT_ALG_HANDLE signAlgHandle; + ULONG bufferSize; + PVOID buffer; + BCRYPT_KEY_HANDLE keyHandle; + ULONG hashSize; + PVOID hash; + ULONG signatureSize; + PVOID signature; + + if (!CstArgument1 || !CstKeyFileName || !CstSigFileName) + CstFailWith(CstHelpMessage); + + if (!CstArgument1 || !CstKeyFileName || !CstSigFileName) + CstFailWith(CstHelpMessage); + + // Import the key. + + if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0))) + CstFailWithStatus(L"Unable to open the signing algorithm provider", status, 0); + buffer = CstReadFile(CstKeyFileName->Buffer, 1024 * 1024, &bufferSize); + if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, CST_BLOB_PUBLIC, &keyHandle, buffer, bufferSize, 0))) + CstFailWithStatus(PhFormatString(L"Unable to import the public key", CstKeyFileName->Buffer)->Buffer, status, 0); + PhFree(buffer); + + // Read the signature. + + signature = CstReadFile(CstSigFileName->Buffer, 1024 * 1024, &signatureSize); + + // Hash the file. + + hash = CstHashFile(CstArgument1->Buffer, &hashSize); + + // Verify the hash. + + if (!NT_SUCCESS(status = BCryptVerifySignature(keyHandle, NULL, hash, hashSize, signature, signatureSize, 0))) + CstFailWithStatus(PhFormatString(L"Signature verification failed", CstKeyFileName->Buffer)->Buffer, status, 0); + PhFree(signature); + PhFree(hash); + + wprintf(L"The signature is valid.\n"); + } + + return 0; +} diff --git a/tools/GenerateHeader/GenerateHeader.sln b/tools/GenerateHeader/GenerateHeader.sln new file mode 100644 index 0000000..295c17a --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateHeader", "GenerateHeader\GenerateHeader.csproj", "{DA075B6D-3506-42FA-8FD8-34F79E5578E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA075B6D-3506-42FA-8FD8-34F79E5578E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA075B6D-3506-42FA-8FD8-34F79E5578E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA075B6D-3506-42FA-8FD8-34F79E5578E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA075B6D-3506-42FA-8FD8-34F79E5578E0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tools/GenerateHeader/GenerateHeader/App.config b/tools/GenerateHeader/GenerateHeader/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tools/GenerateHeader/GenerateHeader/GenerateHeader.csproj b/tools/GenerateHeader/GenerateHeader/GenerateHeader.csproj new file mode 100644 index 0000000..9ec80c6 --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader/GenerateHeader.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {DA075B6D-3506-42FA-8FD8-34F79E5578E0} + Exe + Properties + GenerateHeader + GenerateHeader + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/GenerateHeader/GenerateHeader/HeaderGen.cs b/tools/GenerateHeader/GenerateHeader/HeaderGen.cs new file mode 100644 index 0000000..458adec --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader/HeaderGen.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GenerateHeader +{ + class HeaderFile + { + public string Name; + public List Lines; + public List Dependencies; + } + + class HeaderGen + { + private string _baseDirectory; + private string[] _modes; + private string[] _files; + private string _outputFile; + private string _header = ""; + private string _footer = ""; + + private string UnEscape(string text) + { + return text.Replace("\\r", "\r").Replace("\\n", "\n").Replace("\\\\", "\\"); + } + + public void LoadConfig(string fileName) + { + string[] lines = File.ReadAllLines(fileName); + + foreach (string line in lines) + { + string[] split = line.Split(new char[] { '=' }, 2); + + switch (split[0]) + { + case "base": + _baseDirectory = split[1]; + break; + case "modes": + _modes = split[1].ToLowerInvariant().Split(';'); + break; + case "in": + _files = split[1].Split(';'); + break; + case "out": + _outputFile = split[1]; + break; + case "header": + _header = UnEscape(split[1]); + break; + case "footer": + _footer = UnEscape(split[1]); + break; + } + } + } + + private List OrderHeaderFiles(List headerFiles) + { + var result = new List(); + var done = new HashSet(); + + foreach (var h in headerFiles) + OrderHeaderFiles(result, done, h); + + return result; + } + + private void OrderHeaderFiles(List result, HashSet done, HeaderFile headerFile) + { + if (done.Contains(headerFile)) + return; + + done.Add(headerFile); + + foreach (var h in headerFile.Dependencies) + OrderHeaderFiles(result, done, h); + + result.Add(headerFile); + } + + private List ProcessHeaderLines(IEnumerable lines) + { + var result = new List(); + var modes = new HashSet(); + var blankLine = false; + + foreach (var line in lines) + { + var s = line.Trim(); + + if (s.StartsWith("// begin_")) + { + modes.Add(s.Remove(0, "// begin_".Length)); + } + else if (s.StartsWith("// end_")) + { + modes.Remove(s.Remove(0, "// end_".Length)); + } + else + { + bool blockMode = _modes.Any(modes.Contains); + bool lineMode = _modes.Any(mode => + { + int indexOfMarker = s.LastIndexOf("// " + mode); + if (indexOfMarker == -1) + return false; + + return s.Substring(indexOfMarker).Trim().All(c => char.IsLetterOrDigit(c) || c == ' ' || c == '/'); + }); + + if (blockMode || lineMode) + { + if (blankLine && result.Count != 0) + result.Add(string.Empty); + + result.Add(line); + blankLine = false; + } + else if (s.Length == 0) + { + blankLine = true; + } + } + } + + return result; + } + + public void Execute() + { + // Read in all header files. + + var headerFiles = _files.Select(fileName => + { + var fullFileName = _baseDirectory + "\\" + fileName; + var lines = File.ReadAllLines(fullFileName).ToList(); + + return new HeaderFile { Name = Path.GetFileName(fullFileName).ToLowerInvariant(), Lines = lines }; + }).ToDictionary(h => h.Name); + + foreach (var h in headerFiles.Values) + { + var partitions = + h.Lines + .Select(s => + { + var trimmed = s.Trim().ToLowerInvariant(); + if (trimmed.StartsWith("#include <") && trimmed.EndsWith(">")) + { + HeaderFile d; + if (headerFiles.TryGetValue(trimmed.Remove(trimmed.Length - 1).Remove(0, "#include <".Length), out d)) + return Tuple.Create(s, d); + else + return Tuple.Create(s, null); + } + return Tuple.Create(s, null); + }) + .ToLookup(p => p.Item2 != null); + + h.Lines = partitions[false].Select(p => p.Item1).ToList(); + h.Dependencies = partitions[true].Select(p => p.Item2).Distinct().ToList(); + + foreach (var d in h.Dependencies) + Console.WriteLine("Dependency: " + h.Name + " -> " + d.Name); + } + + // Generate the ordering. + + var orderedHeaderFiles = OrderHeaderFiles(_files.Select(s => headerFiles[Path.GetFileName(s).ToLower()]).ToList()); + + // Process each header file and remove irrelevant content. + + foreach (var h in orderedHeaderFiles) + h.Lines = ProcessHeaderLines(h.Lines); + + // Write out the result. + + StreamWriter sw = new StreamWriter(_baseDirectory + "\\" + _outputFile); + + // Header + + sw.Write(_header); + + // Header files + + foreach (var h in orderedHeaderFiles) + { + Console.WriteLine("Header file: " + h.Name); + + sw.WriteLine(); + sw.WriteLine("//"); + sw.WriteLine("// " + Path.GetFileNameWithoutExtension(h.Name)); + sw.WriteLine("//"); + sw.WriteLine(); + + foreach (var line in h.Lines) + sw.WriteLine(line); + } + + // Footer + + sw.Write(_footer); + + sw.Close(); + } + } +} diff --git a/tools/GenerateHeader/GenerateHeader/Program.cs b/tools/GenerateHeader/GenerateHeader/Program.cs new file mode 100644 index 0000000..9e0b33e --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader/Program.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GenerateHeader +{ + class Program + { + static void Main(string[] args) + { + HeaderGen gen = new HeaderGen(); + string configFile = args.Length > 0 ? args[0] : "options.txt"; + + try + { + gen.LoadConfig(configFile); + gen.Execute(); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + } +} diff --git a/tools/GenerateHeader/GenerateHeader/Properties/AssemblyInfo.cs b/tools/GenerateHeader/GenerateHeader/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9290c15 --- /dev/null +++ b/tools/GenerateHeader/GenerateHeader/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GenerateHeader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GenerateHeader")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("efba3f55-999d-4dde-8217-118ba2aed831")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/GenerateHeader/GenerateHeader/bin/Release/GenerateHeader.exe b/tools/GenerateHeader/GenerateHeader/bin/Release/GenerateHeader.exe new file mode 100644 index 0000000..225fed8 Binary files /dev/null and b/tools/GenerateHeader/GenerateHeader/bin/Release/GenerateHeader.exe differ diff --git a/tools/GenerateZw/GenerateZw.sln b/tools/GenerateZw/GenerateZw.sln new file mode 100644 index 0000000..6ca3b16 --- /dev/null +++ b/tools/GenerateZw/GenerateZw.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateZw", "GenerateZw\GenerateZw.csproj", "{10589240-84D9-4935-9868-7FFADB6545F9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {10589240-84D9-4935-9868-7FFADB6545F9}.Debug|x86.ActiveCfg = Debug|x86 + {10589240-84D9-4935-9868-7FFADB6545F9}.Debug|x86.Build.0 = Debug|x86 + {10589240-84D9-4935-9868-7FFADB6545F9}.Release|x86.ActiveCfg = Release|x86 + {10589240-84D9-4935-9868-7FFADB6545F9}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tools/GenerateZw/GenerateZw/GenerateZw.csproj b/tools/GenerateZw/GenerateZw/GenerateZw.csproj new file mode 100644 index 0000000..eaa1c1b --- /dev/null +++ b/tools/GenerateZw/GenerateZw/GenerateZw.csproj @@ -0,0 +1,58 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {10589240-84D9-4935-9868-7FFADB6545F9} + Exe + Properties + GenerateZw + GenerateZw + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/GenerateZw/GenerateZw/Program.cs b/tools/GenerateZw/GenerateZw/Program.cs new file mode 100644 index 0000000..20ba3a8 --- /dev/null +++ b/tools/GenerateZw/GenerateZw/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GenerateZw +{ + class Program + { + static void Main(string[] args) + { + ZwGen gen = new ZwGen(); + string configFile = args.Length > 0 ? args[0] : "options.txt"; + + try + { + gen.LoadConfig(configFile); + gen.Execute(); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + } +} diff --git a/tools/GenerateZw/GenerateZw/Properties/AssemblyInfo.cs b/tools/GenerateZw/GenerateZw/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1fd5113 --- /dev/null +++ b/tools/GenerateZw/GenerateZw/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GenerateZw")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GenerateZw")] +[assembly: AssemblyCopyright("Copyright © 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("db557e10-0bf4-4a74-b8b4-ade1faa91177")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/GenerateZw/GenerateZw/ZwGen.cs b/tools/GenerateZw/GenerateZw/ZwGen.cs new file mode 100644 index 0000000..737610f --- /dev/null +++ b/tools/GenerateZw/GenerateZw/ZwGen.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Text.RegularExpressions; + +namespace GenerateZw +{ + class ServiceDefinition + { + public string Name; + public string Text; + public int NameIndex; + } + + class ServiceDefinitionComparer : IEqualityComparer + { + public bool Equals(ServiceDefinition x, ServiceDefinition y) + { + return string.Equals(x.Name, y.Name); + } + + public int GetHashCode(ServiceDefinition obj) + { + return obj.Name.GetHashCode(); + } + } + + class ZwGen + { + private string _baseDirectory; + private string[] _files; + private string _outputFile; + private string _header = ""; + private string _footer = ""; + + private List _defs; + + private string UnEscape(string text) + { + return text.Replace("\\r", "\r").Replace("\\n", "\n").Replace("\\\\", "\\"); + } + + public void LoadConfig(string fileName) + { + string[] lines = File.ReadAllLines(fileName); + + foreach (string line in lines) + { + string[] split = line.Split(new char[] { '=' }, 2); + + switch (split[0]) + { + case "base": + _baseDirectory = split[1]; + break; + case "in": + _files = split[1].Split(';'); + break; + case "out": + _outputFile = split[1]; + break; + case "header": + _header = UnEscape(split[1]); + break; + case "footer": + _footer = UnEscape(split[1]); + break; + } + } + } + + private void Parse(string text) + { + Regex regex = new Regex(@"NTSYSCALLAPI[\w\s_]*NTAPI\s*(Nt(\w)*)\(.*?\);", RegexOptions.Compiled | RegexOptions.Singleline); + MatchCollection matches; + + matches = regex.Matches(text); + + foreach (Match match in matches) + { + _defs.Add(new ServiceDefinition() { Name = match.Groups[1].Value, Text = match.Value, NameIndex = match.Groups[1].Index - match.Index }); + } + } + + public void Execute() + { + // Build up a list of definitions. + + _defs = new List(); + + foreach (string fileName in _files) + Parse(File.ReadAllText(_baseDirectory + "\\" + fileName)); + + StreamWriter sw = new StreamWriter(_baseDirectory + "\\" + _outputFile); + + // Remove duplicates and sort. + _defs = new List(_defs.Distinct(new ServiceDefinitionComparer())); + _defs.Sort((x, y) => string.CompareOrdinal(x.Name, y.Name)); + + // Header + + sw.Write(_header); + + // Definitions + + foreach (var d in _defs) + { + Console.WriteLine("System service: " + d.Name); + + // Write the original definition, replacing "Nt" with "Zw". + sw.Write(d.Text.Substring(0, d.NameIndex) + "Zw" + d.Text.Substring(d.NameIndex + 2) + "\r\n\r\n"); + } + + // Footer + + sw.Write(_footer); + + sw.Close(); + } + } +} diff --git a/tools/GenerateZw/GenerateZw/bin/Release/GenerateZw.exe b/tools/GenerateZw/GenerateZw/bin/Release/GenerateZw.exe new file mode 100644 index 0000000..67b76e2 Binary files /dev/null and b/tools/GenerateZw/GenerateZw/bin/Release/GenerateZw.exe differ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup.sln b/tools/ProcessHackerSetup/ProcessHackerSetup.sln new file mode 100644 index 0000000..cc1b659 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProcessHackerSetup", "ProcessHackerSetup\ProcessHackerSetup.vcxproj", "{5C00734F-F50A-49FC-9D2A-F6EE51ECB00F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5C00734F-F50A-49FC-9D2A-F6EE51ECB00F}.Debug|Win32.ActiveCfg = Debug|Win32 + {5C00734F-F50A-49FC-9D2A-F6EE51ECB00F}.Debug|Win32.Build.0 = Debug|Win32 + {5C00734F-F50A-49FC-9D2A-F6EE51ECB00F}.Release|Win32.ActiveCfg = Release|Win32 + {5C00734F-F50A-49FC-9D2A-F6EE51ECB00F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj b/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj new file mode 100644 index 0000000..6d8aa3f --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj @@ -0,0 +1,143 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {5C00734F-F50A-49FC-9D2A-F6EE51ECB00F} + ProcessHackerSetup + 10.0.10586.0 + ProcessHackerSetup + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + + + $(ProjectDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + + + + Level3 + Disabled + true + ..\..\..\phnt\include;..\..\..\phlib\include;include;%(AdditionalIncludeDirectories) + false + StdCall + MultiThreadedDebug + _PHLIB_;_WINDOWS;WIN32;DEBUG;%(PreprocessorDefinitions) + true + ProgramDatabase + true + + + true + uxtheme.lib;phlib.lib;ntdll.lib;%(AdditionalDependencies) + RequireAdministrator + ..\..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + Windows + + + + + Level3 + Full + true + true + true + ..\..\..\phnt\include;..\..\..\phlib\include;include;%(AdditionalIncludeDirectories) + StdCall + _PHLIB_;_WINDOWS;WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + AnySuitable + true + StreamingSIMDExtensions + + + true + true + true + uxtheme.lib;phlib.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + RequireAdministrator + Windows + 5.01 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj.filters b/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj.filters new file mode 100644 index 0000000..a55e50f --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/ProcessHackerSetup.vcxproj.filters @@ -0,0 +1,113 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {3f81aff6-dff1-4573-bb20-ac132fa9297b} + + + {7a7dfb06-20d6-4ea2-8992-26ea6e6cf1d9} + + + {c6910170-43ca-47cd-b0cf-f8fae8c47669} + + + {c394360d-d423-4249-a23e-70558948e7f2} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\lib + + + Source Files\lib + + + Source Files\lib + + + Source Files\setup + + + Source Files\setup + + + Source Files\setup + + + Source Files\lib + + + Source Files\lib + + + + + Header Files + + + Header Files + + + Header Files\lib + + + Header Files\lib + + + Header Files\lib + + + + + Resource Files\Images + + + Resource Files\Images + + + + + Resource Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/include/appsup.h b/tools/ProcessHackerSetup/ProcessHackerSetup/include/appsup.h new file mode 100644 index 0000000..ce86af9 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/include/appsup.h @@ -0,0 +1,79 @@ +#ifndef _APPSUP_H +#define _APPSUP_H + + +extern PWSTR Version; +extern PWSTR DownloadURL; +extern HWND _hwndProgress; + + +#define STATUS_MSG(Format, ...) \ +{ \ + PPH_STRING msgString = PhFormatString(Format, __VA_ARGS__); \ + if (msgString) \ + { \ + SetDlgItemText(_hwndProgress, IDC_MAINHEADER1, msgString->Buffer); \ + DEBUG_MSG(L"%s\n", msgString->Buffer); \ + PhDereferenceObject(msgString); \ + } \ +} + +typedef struct _STOPWATCH +{ + LARGE_INTEGER StartCounter; + LARGE_INTEGER EndCounter; + LARGE_INTEGER Frequency; +} STOPWATCH, *PSTOPWATCH; + +PPH_STRING GetSystemTemp(VOID); + +PPH_STRING BrowseForFolder( + _In_opt_ HWND DialogHandle, + _In_opt_ PCWSTR Title + ); + +VOID InitializeFont( + _In_ HWND ControlHandle, + _In_ LONG Height, + _In_ LONG Weight + ); + +VOID StopwatchInitialize( + __out PSTOPWATCH Stopwatch + ); + +VOID StopwatchStart( + _Inout_ PSTOPWATCH Stopwatch + ); + +ULONG StopwatchGetMilliseconds( + _In_ PSTOPWATCH Stopwatch + ); + + +BOOLEAN CreateLink( + _In_ PWSTR DestFilePath, + _In_ PWSTR FilePath, + _In_ PWSTR FileParentDir, + _In_ PWSTR FileComment + ); + +PWSTR XmlParseToken( + _In_ PWSTR XmlString, + _In_ PWSTR XmlTokenName + ); + +HBITMAP LoadPngImageFromResources( + _In_ PCWSTR Name + ); + +_Maybenull_ +PPH_STRING GetProcessHackerInstallPath( + VOID + ); + +BOOLEAN CreateDirectoryPath( + _In_ PPH_STRING DirectoryPath + ); + +#endif _APPSUP_H \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/include/netio.h b/tools/ProcessHackerSetup/ProcessHackerSetup/include/netio.h new file mode 100644 index 0000000..521f724 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/include/netio.h @@ -0,0 +1,71 @@ +#ifndef _PH_NETIO_H +#define _PH_NETIO_H + +#pragma comment(lib, "Winhttp.lib") +#include + +typedef struct _HTTP_SESSION +{ + HINTERNET SessionHandle; + HINTERNET ConnectionHandle; + HINTERNET RequestHandle; +} HTTP_SESSION, *P_HTTP_SESSION; + +typedef struct _HTTP_PARSED_URL +{ + WCHAR HttpMethod[10]; + WCHAR HttpServer[200]; + WCHAR HttpPath[200]; +} *HTTP_PARSED_URL; + + +P_HTTP_SESSION HttpSocketCreate(VOID); + +BOOLEAN HttpConnect( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR ServerName, + _In_ INTERNET_PORT ServerPort + ); + +BOOLEAN HttpBeginRequest( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR MethodType, + _In_ PCWSTR UrlPath, + _In_ ULONG Flags + ); + +BOOLEAN HttpSendRequest( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ ULONG TotalLength + ); + +BOOLEAN HttpEndRequest( + _Inout_ P_HTTP_SESSION HttpSocket + ); + +BOOLEAN HttpAddRequestHeaders( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR RequestHeaders + ); + +PPH_STRING HttpGetRequestHeaderString( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR RequestHeader + ); + +ULONG HttpGetRequestHeaderDword( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ ULONG Flags + ); + +PPH_STRING HttpDownloadString( + _Inout_ P_HTTP_SESSION HttpSocket + ); + +BOOLEAN HttpParseURL( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR Url, + _Out_ HTTP_PARSED_URL* HttpParsedUrl + ); + +#endif \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/include/setup.h b/tools/ProcessHackerSetup/ProcessHackerSetup/include/setup.h new file mode 100644 index 0000000..9caa0b4 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/include/setup.h @@ -0,0 +1,126 @@ +#ifndef _SETUP_H +#define _SETUP_H + +#pragma comment(lib, "Comctl32.lib") +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "WindowsCodecs.lib") + +#define CINTERFACE +#define COBJMACROS + +#include +#include +#include + +#include +#include +#include +#include + +#include "..\resource.h" + +// PropertySheet Control IDs +#define IDD_PROPSHEET_ID 1006 // ID of the propsheet dialog template in comctl32.dll +#define IDC_PROPSHEET_APPLYNOW 0x3021 +#define IDC_PROPSHEET_DLGFRAME 0x3022 +#define IDC_PROPSHEET_BACK 0x3023 +#define IDC_PROPSHEET_NEXT 0x3024 +#define IDC_PROPSHEET_FINISH 0x3025 +#define IDC_PROPSHEET_DIVIDER 0x3026 +#define IDC_PROPSHEET_TOPDIVIDER 0x3027 + +// Debug Macro +#ifdef _DEBUG +#define DEBUG_MSG(Format, ...) \ +{ \ + PPH_STRING debugString = PhFormatString(Format, __VA_ARGS__); \ + if (debugString) \ + { \ + OutputDebugString(debugString->Buffer); \ + PhDereferenceObject(debugString); \ + } \ +} +#else +#define DEBUG_MSG(Format, ...) +#endif + +extern PPH_STRING SetupInstallPath; + +INT_PTR CALLBACK PropSheetPage1_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ); + +INT_PTR CALLBACK PropSheetPage2_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ); + +INT_PTR CALLBACK PropSheetPage3_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ); + +INT_PTR CALLBACK PropSheetPage4_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ); + +INT_PTR CALLBACK PropSheetPage5_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ); + +VOID StartProgress(VOID); +VOID _SetProgressTime(VOID); +VOID SetProgress( + _In_ LONG Completed, + _In_ LONG Total + ); + +_Check_return_ +BOOLEAN ProcessHackerShutdown( + VOID + ); +_Check_return_ +BOOLEAN RemoveAppCompatEntries( + VOID + ); +_Check_return_ +ULONG KphUninstall( + VOID + ); + + +BOOLEAN SetupDownloadBuild( + _In_ PVOID Arguments + ); +BOOLEAN SetupResetCurrentInstall( + _In_ PVOID Arguments + ); +BOOLEAN SetupExtractBuild( + _In_ PVOID Arguments + ); + + + +LRESULT CALLBACK SubclassWindowProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ DWORD_PTR dwRefData + ); + +#endif \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/appsup.c b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/appsup.c new file mode 100644 index 0000000..3d5290a --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/appsup.c @@ -0,0 +1,1140 @@ +#include +#include + +#include +#include + +HBITMAP LoadPngImageFromResources( + _In_ PCWSTR Name + ) +{ + UINT width = 0; + UINT height = 0; + UINT frameCount = 0; + BOOLEAN isSuccess = FALSE; + ULONG resourceLength = 0; + HGLOBAL resourceHandle = NULL; + HRSRC resourceHandleSource = NULL; + WICInProcPointer resourceBuffer = NULL; + + BITMAPINFO bitmapInfo = { 0 }; + HBITMAP bitmapHandle = NULL; + PBYTE bitmapBuffer = NULL; + + IWICStream* wicStream = NULL; + IWICBitmapSource* wicBitmapSource = NULL; + IWICBitmapDecoder* wicDecoder = NULL; + IWICBitmapFrameDecode* wicFrame = NULL; + IWICImagingFactory* wicFactory = NULL; + IWICBitmapScaler* wicScaler = NULL; + WICPixelFormatGUID pixelFormat; + + WICRect rect = { 0, 0, 164, 164 }; + + __try + { + // Create the ImagingFactory + if (FAILED(CoCreateInstance(&CLSID_WICImagingFactory1, NULL, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, &wicFactory))) + __leave; + + // Find the resource + if ((resourceHandleSource = FindResource(PhLibImageBase, Name, L"PNG")) == NULL) + __leave; + + // Get the resource length + resourceLength = SizeofResource(PhLibImageBase, resourceHandleSource); + + // Load the resource + if ((resourceHandle = LoadResource(PhLibImageBase, resourceHandleSource)) == NULL) + __leave; + + if ((resourceBuffer = (WICInProcPointer)LockResource(resourceHandle)) == NULL) + __leave; + + // Create the Stream + if (FAILED(IWICImagingFactory_CreateStream(wicFactory, &wicStream))) + __leave; + + // Initialize the Stream from Memory + if (FAILED(IWICStream_InitializeFromMemory(wicStream, resourceBuffer, resourceLength))) + __leave; + + if (FAILED(IWICImagingFactory_CreateDecoder(wicFactory, &GUID_ContainerFormatPng, NULL, &wicDecoder))) + __leave; + + if (FAILED(IWICBitmapDecoder_Initialize(wicDecoder, (IStream*)wicStream, WICDecodeMetadataCacheOnLoad))) + __leave; + + // Get the Frame count + if (FAILED(IWICBitmapDecoder_GetFrameCount(wicDecoder, &frameCount)) || frameCount < 1) + __leave; + + // Get the Frame + if (FAILED(IWICBitmapDecoder_GetFrame(wicDecoder, 0, &wicFrame))) + __leave; + + // Get the WicFrame image format + if (FAILED(IWICBitmapFrameDecode_GetPixelFormat(wicFrame, &pixelFormat))) + __leave; + + // Check if the image format is supported: + if (IsEqualGUID(&pixelFormat, &GUID_WICPixelFormat32bppRGBA)) // GUID_WICPixelFormat32bppPBGRA + { + wicBitmapSource = (IWICBitmapSource*)wicFrame; + } + else + { + IWICFormatConverter* wicFormatConverter = NULL; + + if (FAILED(IWICImagingFactory_CreateFormatConverter(wicFactory, &wicFormatConverter))) + __leave; + + if (FAILED(IWICFormatConverter_Initialize( + wicFormatConverter, + (IWICBitmapSource*)wicFrame, + &GUID_WICPixelFormat32bppBGRA, + WICBitmapDitherTypeNone, + NULL, + 0.0, + WICBitmapPaletteTypeCustom + ))) + { + IWICFormatConverter_Release(wicFormatConverter); + __leave; + } + + // Convert the image to the correct format: + IWICFormatConverter_QueryInterface(wicFormatConverter, &IID_IWICBitmapSource, &wicBitmapSource); + IWICFormatConverter_Release(wicFormatConverter); + IWICBitmapFrameDecode_Release(wicFrame); + } + + bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapInfo.bmiHeader.biWidth = rect.Width; + bitmapInfo.bmiHeader.biHeight = -((LONG)rect.Height); + bitmapInfo.bmiHeader.biPlanes = 1; + bitmapInfo.bmiHeader.biBitCount = 32; + bitmapInfo.bmiHeader.biCompression = BI_RGB; + + HDC hdc = CreateCompatibleDC(NULL); + bitmapHandle = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, (PVOID*)&bitmapBuffer, NULL, 0); + ReleaseDC(NULL, hdc); + + // Check if it's the same rect as the requested size. + //if (width != rect.Width || height != rect.Height) + if (FAILED(IWICImagingFactory_CreateBitmapScaler(wicFactory, &wicScaler))) + __leave; + if (FAILED(IWICBitmapScaler_Initialize(wicScaler, wicBitmapSource, rect.Width, rect.Height, WICBitmapInterpolationModeFant))) + __leave; + if (FAILED(IWICBitmapScaler_CopyPixels(wicScaler, &rect, rect.Width * 4, rect.Width * rect.Height * 4, bitmapBuffer))) + __leave; + + isSuccess = TRUE; + } + __finally + { + if (wicScaler) + { + IWICBitmapScaler_Release(wicScaler); + } + + if (wicBitmapSource) + { + IWICBitmapSource_Release(wicBitmapSource); + } + + if (wicStream) + { + IWICStream_Release(wicStream); + } + + if (wicDecoder) + { + IWICBitmapDecoder_Release(wicDecoder); + } + + if (wicFactory) + { + IWICImagingFactory_Release(wicFactory); + } + + if (resourceHandle) + { + FreeResource(resourceHandle); + } + } + + return bitmapHandle; +} + +VOID InitializeFont( + _In_ HWND ControlHandle, + _In_ LONG Height, + _In_ LONG Weight + ) +{ + LOGFONT fontAttributes = { 0 }; + fontAttributes.lfHeight = Height; + fontAttributes.lfWeight = Weight; + fontAttributes.lfQuality = CLEARTYPE_QUALITY | ANTIALIASED_QUALITY; + + wcscpy_s( + fontAttributes.lfFaceName, + ARRAYSIZE(fontAttributes.lfFaceName), + WindowsVersion > WINDOWS_VISTA ? L"Segoe UI" : L"MS Shell Dlg 2" + ); + + // Verdana + // Tahoma + // MS Sans Serif + //wcscpy_s( + // fontAttributes.lfFaceName, + // ARRAYSIZE(fontAttributes.lfFaceName), + // L"Marlett" + // ); + + HFONT fontHandle = CreateFontIndirect(&fontAttributes); + + SendMessage(ControlHandle, WM_SETFONT, (WPARAM)fontHandle, FALSE); +} + + +PPH_STRING GetSystemTemp(VOID) +{ + PPH_STRING dirPath; + ULONG dirPathLength; + + // Query the directory length... + dirPathLength = GetTempPath(0, NULL); + + // Allocate the string... + dirPath = PhCreateStringEx(NULL, dirPathLength * sizeof(TCHAR)); + + // Query the directory path... + if (!GetTempPath(dirPath->Length, dirPath->Buffer)) + { + PhDereferenceObject(dirPath); + return NULL; + } + + return dirPath; +} + + +PPH_STRING PhGetUrlBaseName( + _In_ PPH_STRING FileName + ) +{ + PH_STRINGREF pathPart; + PH_STRINGREF baseNamePart; + + if (!PhSplitStringRefAtLastChar(&FileName->sr, '/', &pathPart, &baseNamePart)) + return NULL; + + return PhCreateString2(&baseNamePart); +} + + +PPH_STRING BrowseForFolder( + _In_opt_ HWND DialogHandle, + _In_opt_ PCWSTR Title + ) +{ + PPH_STRING folderPath = NULL; + + if (WINDOWS_HAS_IFILEDIALOG) + { + PVOID fileDialog; + + fileDialog = PhCreateOpenFileDialog(); + + PhSetFileDialogOptions(fileDialog, PH_FILEDIALOG_PICKFOLDERS); + + if (PhShowFileDialog(DialogHandle, fileDialog)) + { + PPH_STRING folderPath; + PPH_STRING fileDialogFolderPath = PhGetFileDialogFileName(fileDialog); + + folderPath = PhCreateStringEx(fileDialogFolderPath->Buffer, fileDialogFolderPath->Length * 2); + + // Ensure the folder path ends with a slash + // We must make sure the install path ends with a backslash since + // this string is wcscat' with our zip extraction paths. + PathAddBackslash(folderPath->Buffer); + + PhTrimToNullTerminatorString(folderPath); + + PhFreeFileDialog(fileDialog); + + return folderPath; + } + } + else + { + PIDLIST_ABSOLUTE shellItemId; + BROWSEINFO browseInformation; + + memset(&browseInformation, 0, sizeof(BROWSEINFO)); + + browseInformation.hwndOwner = DialogHandle; + browseInformation.lpszTitle = Title; + browseInformation.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // Caller needs to call OleInitialize() before using BIF_NEWDIALOGSTYLE? + + if (shellItemId = SHBrowseForFolder(&browseInformation)) + { + folderPath = PhCreateStringEx(NULL, MAX_PATH * 2); + + if (SHGetPathFromIDList(shellItemId, folderPath->Buffer)) + { + // Ensure the folder path ends with a slash + // We must make sure the install path ends with a backslash since + // this string is wcscat' with our zip extraction paths. + PathAddBackslash(folderPath->Buffer); + + PhTrimToNullTerminatorString(folderPath); + } + else + { + PhClearReference(&folderPath); + } + + CoTaskMemFree(shellItemId); + } + } + + return folderPath; +} + +VOID StopwatchInitialize( + __out PSTOPWATCH Stopwatch + ) +{ + Stopwatch->StartCounter.QuadPart = 0; + Stopwatch->EndCounter.QuadPart = 0; +} + +VOID StopwatchStart( + _Inout_ PSTOPWATCH Stopwatch + ) +{ + QueryPerformanceCounter(&Stopwatch->StartCounter); + QueryPerformanceFrequency(&Stopwatch->Frequency); +} + +ULONG StopwatchGetMilliseconds( + _In_ PSTOPWATCH Stopwatch + ) +{ +#define CLOCKS_PER_SEC 1000 + + LARGE_INTEGER countsPerMs; + + countsPerMs = Stopwatch->Frequency; + countsPerMs.QuadPart /= CLOCKS_PER_SEC; + + QueryPerformanceCounter(&Stopwatch->EndCounter); + + return (ULONG)((Stopwatch->EndCounter.QuadPart - Stopwatch->StartCounter.QuadPart) / countsPerMs.QuadPart); +} + +BOOLEAN ConnectionAvailable(VOID) +{ + if (WindowsVersion > WINDOWS_VISTA) + { + INetworkListManager* pNetworkListManager = NULL; + + // Create an instance of the INetworkListManger COM object. + if (SUCCEEDED(CoCreateInstance(&CLSID_NetworkListManager, NULL, CLSCTX_ALL, &IID_INetworkListManager, &pNetworkListManager))) + { + VARIANT_BOOL isConnected = VARIANT_FALSE; + VARIANT_BOOL isConnectedInternet = VARIANT_FALSE; + + // Query the relevant properties. + INetworkListManager_get_IsConnected(pNetworkListManager, &isConnected); + INetworkListManager_get_IsConnectedToInternet(pNetworkListManager, &isConnectedInternet); + + // Cleanup the INetworkListManger COM objects. + INetworkListManager_Release(pNetworkListManager); + + // Check if Windows is connected to a network and it's connected to the internet. + if (isConnected == VARIANT_TRUE && isConnectedInternet == VARIANT_TRUE) + return TRUE; + + // We're not connected to anything + //return FALSE; + } + } + + typedef BOOL(WINAPI *_InternetGetConnectedState)( + __out PULONG lpdwFlags, + __reserved ULONG dwReserved + ); + + BOOLEAN isSuccess = FALSE; + ULONG wininetState = 0; + _InternetGetConnectedState InternetGetConnectedState_I = NULL; + HMODULE inetHandle = NULL; + + __try + { + if (!(inetHandle = LoadLibrary(L"wininet.dll"))) + __leave; + + InternetGetConnectedState_I = (_InternetGetConnectedState)GetProcAddress(inetHandle, "InternetGetConnectedState"); + if (!InternetGetConnectedState_I) + __leave; + + if (InternetGetConnectedState_I(&wininetState, 0)) + isSuccess = TRUE; + } + __finally + { + if (inetHandle) + { + FreeLibrary(inetHandle); + } + } + + return isSuccess; +} + +BOOLEAN CreateLink( + _In_ PWSTR DestFilePath, + _In_ PWSTR FilePath, + _In_ PWSTR FileParentDir, + _In_ PWSTR FileComment + ) +{ + IShellLink* shellLinkPtr = NULL; + IPersistFile* persistFilePtr = NULL; + BOOLEAN isSuccess = FALSE; + + __try + { + if (FAILED(CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLink, &shellLinkPtr))) + __leave; + if (FAILED(IShellLinkW_QueryInterface(shellLinkPtr, &IID_IPersistFile, &persistFilePtr))) + __leave; + + // Load existing shell item if it exists... + //if (SUCCEEDED(IPersistFile_Load(persistFilePtr, DestFilePath, STGM_READ))) + //{ + // IShellLinkW_Resolve(shellLinkPtr, NULL, 0); + //} + + IShellLinkW_SetDescription(shellLinkPtr, FileComment); + IShellLinkW_SetWorkingDirectory(shellLinkPtr, FileParentDir); + IShellLinkW_SetIconLocation(shellLinkPtr, FilePath, 0); + + // Set the shortcut target path... + if (FAILED(IShellLinkW_SetPath(shellLinkPtr, FilePath))) + __leave; + + // Save the shortcut to the file system... + if (FAILED(IPersistFile_Save(persistFilePtr, DestFilePath, TRUE))) + __leave; + + isSuccess = TRUE; + } + __finally + { + if (persistFilePtr) + { + IPersistFile_Release(persistFilePtr); + } + + if (shellLinkPtr) + { + IShellLinkW_Release(shellLinkPtr); + } + } + + return isSuccess; +} + +PWSTR XmlParseToken( + _In_ PWSTR XmlString, + _In_ PWSTR XmlTokenName + ) +{ + PWSTR xmlTokenNext = NULL; + PWSTR xmlStringData; + PWSTR xmlTokenString; + + xmlStringData = PhDuplicateStringZ(XmlString); + + xmlTokenString = wcstok_s( + xmlStringData, + L"<>", + &xmlTokenNext + ); + + while (xmlTokenString) + { + if (PhEqualStringZ(xmlTokenString, XmlTokenName, TRUE)) + { + // We found the value. + xmlTokenString = wcstok_s(NULL, L"<>", &xmlTokenNext); + break; + } + + xmlTokenString = wcstok_s(NULL, L"<>", &xmlTokenNext); + } + + if (xmlTokenString) + { + PWSTR xmlStringDup = PhDuplicateStringZ(xmlTokenString); + + PhFree(xmlStringData); + + return xmlStringDup; + } + + PhFree(xmlStringData); + return NULL; +} + + +BOOLEAN DialogPromptExit( + _In_ HWND hwndDlg + ) +{ + if (WindowsVersion > WINDOWS_VISTA && TaskDialogIndirect) + { + INT nButtonPressed = 0; + + TASKDIALOGCONFIG tdConfig = { sizeof(TASKDIALOGCONFIG) }; + tdConfig.hwndParent = hwndDlg; + tdConfig.hInstance = PhLibImageBase; + tdConfig.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW; + tdConfig.nDefaultButton = IDNO; + tdConfig.dwCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON; + tdConfig.pszMainIcon = TD_WARNING_ICON; + tdConfig.pszMainInstruction = L"Exit Setup"; + tdConfig.pszWindowTitle = PhApplicationName; + tdConfig.pszContent = L"Are you sure you want to cancel the Setup?"; + + TaskDialogIndirect(&tdConfig, &nButtonPressed, NULL, NULL); + + return nButtonPressed == IDNO; + } + else + { + MSGBOXPARAMS mbParams = { sizeof(MSGBOXPARAMS) }; + mbParams.hwndOwner = hwndDlg; + mbParams.hInstance = PhLibImageBase; + mbParams.lpszText = L"Are you sure you want to cancel the Setup?"; + mbParams.lpszCaption = PhApplicationName; + mbParams.dwStyle = MB_YESNO | MB_ICONEXCLAMATION; + // | MB_USERICON; + //params.lpszIcon = MAKEINTRESOURCE(IDI_ICON1); + + return MessageBoxIndirect(&mbParams) == IDNO; + } +} + +VOID DialogPromptsProcessHackerIsRunning( + _In_ HWND hwndDlg + ) +{ + if (WindowsVersion > WINDOWS_VISTA && TaskDialogIndirect) // We're on Vista or above. + { + INT nButtonPressed = 0; + + TASKDIALOGCONFIG tdConfig = { sizeof(TASKDIALOGCONFIG) }; + tdConfig.hwndParent = hwndDlg; + tdConfig.hInstance = PhLibImageBase; + tdConfig.dwFlags = TDF_POSITION_RELATIVE_TO_WINDOW; + tdConfig.nDefaultButton = IDOK; + tdConfig.dwCommonButtons = TDCBF_OK_BUTTON; + tdConfig.pszMainIcon = TD_INFORMATION_ICON; + tdConfig.pszMainInstruction = L"Please close Process Hacker before continuing."; + tdConfig.pszWindowTitle = PhApplicationName; + //tdConfig.pszContent = L"Please close Process Hacker before continuing."; + + TaskDialogIndirect(&tdConfig, &nButtonPressed, NULL, NULL); + } + else + { + MSGBOXPARAMS mbParams = { sizeof(MSGBOXPARAMS) }; + mbParams.hwndOwner = hwndDlg; + mbParams.hInstance = PhLibImageBase; + mbParams.lpszText = L"Please close Process Hacker before continuing."; + mbParams.lpszCaption = PhApplicationName; + mbParams.dwStyle = MB_OK; // | MB_USERICON; + //params.lpszIcon = MAKEINTRESOURCE(IDI_ICON1); + + MessageBoxIndirect(&mbParams); + } +} + +_Check_return_ +BOOLEAN IsProcessHackerRunning( + VOID + ) +{ + HANDLE mutantHandle; + OBJECT_ATTRIBUTES oa; + UNICODE_STRING mutantName; + + RtlInitUnicodeString(&mutantName, L"\\BaseNamedObjects\\ProcessHacker2Mutant"); + InitializeObjectAttributes( + &oa, + &mutantName, + 0, + NULL, + NULL + ); + + if (NT_SUCCESS(NtOpenMutant( + &mutantHandle, + MUTANT_QUERY_STATE, + &oa + ))) + { + NtClose(mutantHandle); + return TRUE; + } + + return FALSE; +} + +_Check_return_ +BOOLEAN IsProcessHackerInstalledUsingSetup( + VOID + ) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Process_Hacker2_is1"); + + HANDLE keyHandle; + + // Check uninstall entries for the 'Process_Hacker2_is1' registry key. + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ, + PH_KEY_LOCAL_MACHINE, + &keyName, + 0 + ))) + { + NtClose(keyHandle); + return TRUE; + } + + return FALSE; +} + +_Check_return_ +BOOLEAN IsProcessHackerInstalled(VOID) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Process_Hacker2_is1"); + BOOLEAN keySuccess = FALSE; + HANDLE keyHandle; + PPH_STRING installPath = NULL; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ | KEY_WOW64_64KEY, // 64bit key + PH_KEY_LOCAL_MACHINE, + &keyName, + 0 + ))) + { + installPath = PhQueryRegistryString(keyHandle, L"InstallLocation"); + NtClose(keyHandle); + } + + if (!PhEndsWithString2(installPath, L"ProcessHacker.exe", TRUE)) + { + + } + + // Check if KeyData value maps to valid file path. + if (GetFileAttributes(installPath->Buffer) == INVALID_FILE_ATTRIBUTES) + { + + } + + keySuccess = TRUE; + + return keySuccess; +} + +_Maybenull_ +PPH_STRING GetProcessHackerInstallPath( + VOID + ) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Process_Hacker2_is1"); + + HANDLE keyHandle; + PPH_STRING installPath = NULL; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_READ | KEY_WOW64_64KEY, + PH_KEY_LOCAL_MACHINE, + &keyName, + 0 + ))) + { + installPath = PhQueryRegistryString(keyHandle, L"InstallLocation"); + NtClose(keyHandle); + } + + return installPath; +} + +_Check_return_ +BOOLEAN ProcessHackerShutdown( + VOID + ) +{ + HWND WindowHandle; + + WindowHandle = FindWindow(L"ProcessHacker", NULL); + + if (WindowHandle) + { + HANDLE processHandle; + ULONG processID = 0; + ULONG threadID = GetWindowThreadProcessId(WindowHandle, &processID); + + SendMessageTimeout(WindowHandle, WM_QUIT, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 5000, NULL); + + if (NT_SUCCESS(PhOpenProcess(&processHandle, SYNCHRONIZE | PROCESS_TERMINATE, ULongToHandle(processID)))) + { + //PostMessage(WindowHandle, WM_QUIT, 0, 0); + // Wait on the process handle, if we timeout, kill it. + //if (WaitForSingleObject(processHandle, 10000) != WAIT_OBJECT_0) + + NtTerminateProcess(processHandle, 1); + NtClose(processHandle); + } + } + + return FALSE; +} + +_Check_return_ +ULONG KphUninstall( + VOID + ) +{ + ULONG status = ERROR_SUCCESS; + SC_HANDLE scmHandle; + SC_HANDLE serviceHandle; + + scmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + + if (!scmHandle) + return PhGetLastWin32ErrorAsNtStatus(); + + serviceHandle = OpenService(scmHandle, L"KProcessHacker2", SERVICE_STOP | DELETE); + + if (serviceHandle) + { + SERVICE_STATUS serviceStatus; + + ControlService(serviceHandle, SERVICE_CONTROL_STOP, &serviceStatus); + + if (!DeleteService(serviceHandle)) + { + status = GetLastError(); + } + + CloseServiceHandle(serviceHandle); + } + else + { + status = GetLastError(); + } + + CloseServiceHandle(scmHandle); + + return status; +} + + +static VOID RemoveAppCompatEntry( + _In_ HANDLE ParentKey + ) +{ + static PH_STRINGREF keyName = PH_STRINGREF_INIT(L"ProcessHacker.exe"); + ULONG bufferLength; + KEY_FULL_INFORMATION fullInfo; + + memset(&fullInfo, 0, sizeof(KEY_FULL_INFORMATION)); + + if (!NT_SUCCESS(NtQueryKey( + ParentKey, + KeyFullInformation, + &fullInfo, + sizeof(KEY_FULL_INFORMATION), + &bufferLength + ))) + { + return; + } + + for (ULONG i = 0; i < fullInfo.Values; i++) + { + PPH_STRING value; + PKEY_VALUE_FULL_INFORMATION buffer; + + bufferLength = sizeof(KEY_VALUE_FULL_INFORMATION); + buffer = PhAllocate(bufferLength); + memset(buffer, 0, bufferLength); + + if (NT_SUCCESS(NtEnumerateValueKey( + ParentKey, + i, + KeyValueFullInformation, + buffer, + bufferLength, + &bufferLength + ))) + { + PhFree(buffer); + break; + } + + //bufferLength = bufferLength; + buffer = PhReAllocate(buffer, bufferLength); + memset(buffer, 0, bufferLength); + + if (!NT_SUCCESS(NtEnumerateValueKey( + ParentKey, + i, + KeyValueFullInformation, + buffer, + bufferLength, + &bufferLength + ))) + { + PhFree(buffer); + break; + } + + if (value = PhCreateStringEx(buffer->Name, buffer->NameLength)) + { + UNICODE_STRING us; + + PhStringRefToUnicodeString(&value->sr, &us); + + if (PhEndsWithStringRef(&value->sr, &keyName, TRUE)) + { + NtDeleteValueKey(ParentKey, &us); + } + + PhDereferenceObject(value); + } + + PhFree(buffer); + } +} + +_Check_return_ +BOOLEAN RemoveAppCompatEntries( + VOID + ) +{ + static PH_STRINGREF appCompatLayersName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"); + static PH_STRINGREF appCompatPersistedName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Compatibility Assistant\\Persisted"); + HANDLE keyHandle; + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_ALL_ACCESS | KEY_WOW64_64KEY, + PH_KEY_CURRENT_USER, + &appCompatLayersName, + 0 + ))) + { + RemoveAppCompatEntry(keyHandle); + NtClose(keyHandle); + } + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_ALL_ACCESS | KEY_WOW64_64KEY, + PH_KEY_CURRENT_USER, + &appCompatPersistedName, + 0 + ))) + { + RemoveAppCompatEntry(keyHandle); + NtClose(keyHandle); + } + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_ALL_ACCESS | KEY_WOW64_64KEY, + PH_KEY_LOCAL_MACHINE, + &appCompatLayersName, + 0 + ))) + { + RemoveAppCompatEntry(keyHandle); + NtClose(keyHandle); + } + + if (NT_SUCCESS(PhOpenKey( + &keyHandle, + KEY_ALL_ACCESS | KEY_WOW64_64KEY, + PH_KEY_LOCAL_MACHINE, + &appCompatPersistedName, + 0 + ))) + { + RemoveAppCompatEntry(keyHandle); + NtClose(keyHandle); + } + + return TRUE; +} + + + + +//PPH_STRING FileGetParentDir( +// _In_ PWSTR FilePath +// ) +//{ +// ULONG index = 0; +// PCWSTR offset = _tcsrchr(FilePath, TEXT('\\')); +// +// if (offset == NULL) +// offset = _tcsrchr(FilePath, TEXT('/')); +// +// index = offset - FilePath; +// +// return PhCreateStringEx(FilePath, index * sizeof(TCHAR)); +//} + +//BOOLEAN FileMakeDirPathRecurse( +// _In_ PWSTR DirPath +// ) +//{ +// BOOLEAN isSuccess = FALSE; +// PPH_STRING dirPathString = NULL; +// PWSTR dirTokenNext = NULL; +// PWSTR dirTokenDup = NULL; +// PWSTR dirTokenString = NULL; +// +// __try +// { +// if ((dirTokenDup = PhDuplicateStringZ(DirPath)) == NULL) +// __leave; +// +// // Find the first directory path token... +// if ((dirTokenString = _tcstok_s(dirTokenDup, TEXT("\\"), &dirTokenNext)) == NULL) +// __leave; +// +// while (dirTokenString) +// { +// if (dirPathString) +// { +// // Copy the new folder path to the previous folder path... +// PPH_STRING tempPathString = StringFormat(TEXT("%s\\%s"), +// dirPathString->Buffer, +// dirTokenString +// ); +// +// if (!FileExists(tempPathString->Buffer)) +// { +// if (_tmkdir(tempPathString->Buffer) != 0) +// { +// DEBUG_MSG(TEXT("ERROR: _tmkdir (%u)\n"), _doserrno); +// PhFree(tempPathString); +// __leave; +// } +// else +// { +// DEBUG_MSG(TEXT("CreateDir: %s\n"), tempPathString->Buffer); +// } +// } +// +// PhFree(dirPathString); +// dirPathString = tempPathString; +// } +// else +// { +// // Copy the drive letter and root folder... +// dirPathString = StringFormat(TEXT("%s"), dirTokenString); +// } +// +// // Find the next directory path token... +// dirTokenString = _tcstok_s(NULL, TEXT("\\"), &dirTokenNext); +// } +// +// isSuccess = TRUE; +// } +// __finally +// { +// if (dirPathString) +// { +// PhFree(dirPathString); +// } +// +// if (dirTokenDup) +// { +// PhFree(dirTokenDup); +// } +// } +// +// return isSuccess; +//} + +//BOOLEAN FileRemoveDirPathRecurse( +// _In_ PWSTR DirPath +// ) +//{ +// struct _tfinddata_t findData; +// intptr_t findHandle = (intptr_t)INVALID_HANDLE_VALUE; +// PPH_STRING dirPath = NULL; +// +// dirPath = StringFormat(TEXT("%s\\*.*"), DirPath); +// +// // Find the first file... +// if ((findHandle = _tfindfirst(dirPath->Buffer, &findData)) == (intptr_t)INVALID_HANDLE_VALUE) +// { +// if (errno == ENOENT) // ERROR_FILE_NOT_FOUND +// { +// PhFree(dirPath); +// return TRUE; +// } +// +// DEBUG_MSG(TEXT("_tfindfirst: (%u) %s\n"), _doserrno, dirPath->Buffer); +// PhFree(dirPath); +// return FALSE; +// } +// +// do +// { +// // Check for "." and ".." +// if (!_tcsicmp(findData.name, TEXT(".")) || !_tcsicmp(findData.name, TEXT(".."))) +// continue; +// +// if (findData.attrib & _A_SUBDIR) +// { +// PPH_STRING subDirPath = StringFormat( +// TEXT("%s\\%s"), +// DirPath, +// findData.name +// ); +// +// // Found a directory... +// if (!FileRemoveDirPathRecurse(subDirPath->Buffer)) +// { +// PhFree(subDirPath); +// _findclose(findHandle); +// return FALSE; +// } +// +// PhFree(subDirPath); +// } +// else +// { +// PPH_STRING subDirPath = StringFormat( +// TEXT("%s\\%s"), +// DirPath, +// findData.name +// ); +// +// if (findData.attrib & _A_RDONLY) +// { +// if (_tchmod(subDirPath->Buffer, _S_IWRITE) == 0) +// { +// DEBUG_MSG(TEXT("_tchmod: %s\n"), subDirPath->Buffer); +// } +// else +// { +// DEBUG_MSG(TEXT("ERROR _tchmod: (%u) %s\n"), _doserrno, subDirPath->Buffer); +// } +// } +// +// if (_tremove(subDirPath->Buffer) == 0) +// { +// DEBUG_MSG(TEXT("DeleteFile: %s\n"), subDirPath->Buffer); +// } +// else +// { +// DEBUG_MSG(TEXT("ERROR DeleteFile: (%u) %s\n"), _doserrno, subDirPath->Buffer); +// } +// +// PhFree(subDirPath); +// } +// +// // Find the next entry +// } while (_tfindnext(findHandle, &findData) == 0); +// +// // Close the file handle +// _findclose(findHandle); +// +// // Check for write permission (read-only attribute ignored on directories?) +// //if (_taccess(DirPath, 2) == -1) +// //{ +// // if (_tchmod(DirPath, _S_IWRITE) == 0) +// // { +// // DEBUG_MSG(TEXT("_tchmod: %s\n"), DirPath); +// // } +// // else +// // { +// // DEBUG_MSG(TEXT("ERROR _tchmod: (%u) %s\n"), _doserrno, DirPath); +// // } +// //} +// +// // Lastly delete the parent directory +// if (_trmdir(DirPath) == 0) +// { +// DEBUG_MSG(TEXT("DeleteDirectory: %s\n"), DirPath); +// } +// else +// { +// DEBUG_MSG(TEXT("ERROR DeleteDirectory: (%u) %s\n"), _doserrno, DirPath); +// } +// +// PhFree(dirPath); +// return TRUE; +//} + + +BOOLEAN CreateDirectoryPath( + _In_ PPH_STRING DirectoryPath + ) +{ + BOOLEAN success = FALSE; + BOOLEAN directoryExists = FALSE; + FILE_NETWORK_OPEN_INFORMATION directoryInfo; + + if (NT_SUCCESS(PhQueryFullAttributesFileWin32(DirectoryPath->Buffer, &directoryInfo))) + { + if (directoryInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + directoryExists = TRUE; + } + } + + if (!directoryExists) + { + INT errorCode = SHCreateDirectoryEx(NULL, DirectoryPath->Buffer, NULL); + + if (errorCode == ERROR_SUCCESS) + { + DEBUG_MSG(L"Created Directory: %s\r\n", DirectoryPath->Buffer); + success = TRUE; + } + else + { + DEBUG_MSG(L"SHCreateDirectoryEx Failed\r\n"); + } + } + else + { + //DEBUG_MSG(L"Directory Exists: %s\r\n", DirectoryPath->Buffer); + success = TRUE; + } + + return success; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.c b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.c new file mode 100644 index 0000000..b88f43b --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.c @@ -0,0 +1,4172 @@ +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#include "miniz.h" +#include +#include + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for (; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31] = { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0 }; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) *p++ = 8; for (; i <= 255; ++i) *p++ = 9; for (; i <= 279; ++i) *p++ = 7; for (; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (; ; ) + { + mz_uint8 *pSrc; + for (; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + + common_exit : + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } + else A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } + else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } + while (avbl > used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2 * used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } + else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for (; i <= 255; ++i) *p++ = 9; + for (; i <= 279; ++i) *p++ = 7; + for (; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for (; ; ) + { + for (; ; ) + { + if (--num_probes_left == 0) return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do {} while ((TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for (; ; ) + { + for (; ; ) + { + if (--num_probes_left == 0) return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do {} while ((TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w >> 8),(mz_uint8)w,0,0,(mz_uint8)(h >> 8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out >> 24),(mz_uint8)(*pLen_out >> 16),(mz_uint8)(*pLen_out >> 8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54 }; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); for (i = 0; i < 4; ++i, c <<= 8) ((mz_uint8*)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); for (i = 0; i < 4; ++i, c <<= 8) (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#else +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct _mz_zip_array +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for (; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE* pFile = MZ_FOPEN(pFilename, "rb"); + + if (!pFile) + return MZ_FALSE; + + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + file_size = MZ_FTELL64(pFile); + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + + name_len = strlen(pName); + + if (name_len > 0xFFFF) + return -1; + + comment_len = pComment ? strlen(pComment) : 0; + + if (comment_len > 0xFFFF) + return -1; + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + + if (filename_len < name_len) + continue; + + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + + const char *pFile_comment = pFilename + filename_len + file_extra_len; + + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for (; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.h b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.h new file mode 100644 index 0000000..987099f --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/miniz/miniz.h @@ -0,0 +1,785 @@ +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void(*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + + + + // ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct _mz_zip_archive_file_stat +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t(*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t(*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum _mz_zip_mode +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct _mz_zip_archive +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum _mz_zip_flags +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + + // Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int(*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + + // Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + + // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. + // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + + // tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): + // TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool(*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS + // Create tdefl_compress() flags given zlib-style compression parameters. + // level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) + // window_bits may be -15 (raw deflate) or 15 (zlib) + // strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#endif // MINIZ_HEADER_INCLUDED \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/netio.c b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/netio.c new file mode 100644 index 0000000..38e0b56 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/netio.c @@ -0,0 +1,327 @@ +#include +#include "netio.h" + +static VOID HttpSocketFree( + _In_ __deref_out PVOID Object + ) +{ + P_HTTP_SESSION httpSocket = (P_HTTP_SESSION)Object; + + if (httpSocket->RequestHandle) + WinHttpCloseHandle(httpSocket->RequestHandle); + + if (httpSocket->ConnectionHandle) + WinHttpCloseHandle(httpSocket->ConnectionHandle); + + if (httpSocket->SessionHandle) + WinHttpCloseHandle(httpSocket->SessionHandle); +} + +P_HTTP_SESSION HttpSocketCreate(VOID) +{ + P_HTTP_SESSION httpSocket; + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = { 0 }; + + httpSocket = (P_HTTP_SESSION)PhAllocate(sizeof(HTTP_SESSION)); + memset(httpSocket, 0, sizeof(HTTP_SESSION)); + + WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig); + + httpSocket->SessionHandle = WinHttpOpen( + NULL, + proxyConfig.lpszProxy ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + proxyConfig.lpszProxy ? proxyConfig.lpszProxy : WINHTTP_NO_PROXY_NAME, + proxyConfig.lpszProxy ? proxyConfig.lpszProxyBypass : WINHTTP_NO_PROXY_BYPASS, + 0 + ); + + return httpSocket; +} + +BOOLEAN HttpConnect( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR ServerName, + _In_ INTERNET_PORT ServerPort + ) +{ + // Create the HTTP connection handle. + HttpSocket->ConnectionHandle = WinHttpConnect( + HttpSocket->SessionHandle, + ServerName, + ServerPort, + 0 + ); + + if (HttpSocket->ConnectionHandle) + return TRUE; + + return FALSE; +} + +BOOLEAN HttpBeginRequest( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR MethodType, + _In_ PCWSTR UrlPath, + _In_ ULONG Flags + ) +{ + HttpSocket->RequestHandle = WinHttpOpenRequest( + HttpSocket->ConnectionHandle, + MethodType, + UrlPath, + NULL, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + Flags + ); + + if (HttpSocket->RequestHandle) + return TRUE; + + return FALSE; +} + +BOOLEAN HttpSendRequest( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ ULONG TotalLength + ) +{ + return WinHttpSendRequest( + HttpSocket->RequestHandle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + TotalLength, + 0 + ) == TRUE; +} + +BOOLEAN HttpEndRequest( + _Inout_ P_HTTP_SESSION HttpSocket + ) +{ + return WinHttpReceiveResponse(HttpSocket->RequestHandle, NULL) == TRUE; +} + +BOOLEAN HttpAddRequestHeaders( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR RequestHeaders + ) +{ + return WinHttpAddRequestHeaders(HttpSocket->RequestHandle, RequestHeaders, -1L, WINHTTP_ADDREQ_FLAG_ADD) == TRUE; +} + +//PVOID HttpGetRequestHeaderValue( +// _Inout_ P_HTTP_SESSION HttpSocket, +// _In_ LPCWSTR RequestHeader, +// _In_ ULONG Flags +// ) +//{ +// PVOID buffer = NULL; +// ULONG bufferSize = 0; +// LRESULT keyResult = NO_ERROR; +// +// // Get the length of the data... +// if (!WinHttpQueryHeaders( +// HttpSocket->RequestHandle, +// Flags, +// RequestHeader, +// WINHTTP_NO_OUTPUT_BUFFER, +// &bufferSize, +// WINHTTP_NO_HEADER_INDEX +// )) +// { +// if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) +// return NULL; +// } +// +// // Allocate the buffer... +// buffer = PhAllocate(bufferSize, NULL); +// +// // Query the value... +// if (!WinHttpQueryHeaders( +// HttpSocket->RequestHandle, +// Flags, +// RequestHeader, +// buffer, +// &bufferSize, +// WINHTTP_NO_HEADER_INDEX +// )) +// { +// if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) +// return NULL; +// } +// +// if (buffer) +// return buffer; +// +// PhFree(buffer); +// return NULL; +//} + +PPH_STRING HttpGetRequestHeaderString( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR RequestHeader + ) +{ + ULONG bufferSize = 0; + PPH_STRING stringBuffer = NULL; + + // Get the length of the data... + if (!WinHttpQueryHeaders( + HttpSocket->RequestHandle, + WINHTTP_QUERY_CUSTOM, + RequestHeader, + WINHTTP_NO_OUTPUT_BUFFER, + &bufferSize, + WINHTTP_NO_HEADER_INDEX + )) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return NULL; + } + + // Allocate the buffer... + stringBuffer = PhCreateStringEx(NULL, bufferSize); + + // Query the data value... + if (WinHttpQueryHeaders( + HttpSocket->RequestHandle, + WINHTTP_QUERY_CUSTOM, + RequestHeader, + stringBuffer->Buffer, + &bufferSize, + WINHTTP_NO_HEADER_INDEX + )) + { + return stringBuffer; + } + + PhDereferenceObject(stringBuffer); + return NULL; +} + +ULONG HttpGetRequestHeaderDword( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ ULONG Flags + ) +{ + ULONG dwordResult = 0; + ULONG dwordLength = sizeof(ULONG); + ULONG dwordResultTemp = 0; + + if (WinHttpQueryHeaders( + HttpSocket->RequestHandle, + Flags | WINHTTP_QUERY_FLAG_NUMBER, + NULL, + &dwordResultTemp, + &dwordLength, + WINHTTP_NO_HEADER_INDEX + )) + { + dwordResult = dwordResultTemp; + } + + return dwordResult; +} + +PPH_STRING HttpDownloadString( + _Inout_ P_HTTP_SESSION HttpSocket + ) +{ + PSTR tempDataPtr = NULL; + PPH_STRING tempHttpString = NULL; + PPH_STRING hashETag = NULL; + PPH_STRING finalHexString = NULL; + + ULONG dataLength = 0; + ULONG returnLength = 0; + ULONG allocatedLength = PAGE_SIZE; + BYTE buffer[PAGE_SIZE]; + + tempDataPtr = (PSTR)PhAllocate(allocatedLength); + + while (WinHttpReadData(HttpSocket->RequestHandle, buffer, PAGE_SIZE, &returnLength)) + { + if (returnLength == 0) + break; + + if (allocatedLength < dataLength + returnLength) + { + allocatedLength *= 2; + tempDataPtr = (PSTR)PhReAllocate(tempDataPtr, allocatedLength); + } + + memcpy(tempDataPtr + dataLength, buffer, returnLength); + memset(buffer, 0, returnLength); + + dataLength += returnLength; + } + + // Add space for the null terminator.. + if (allocatedLength < dataLength + 1) + { + allocatedLength++; + tempDataPtr = (PSTR)PhReAllocate(tempDataPtr, allocatedLength); + } + + // Ensure that the buffer is null-terminated. + tempDataPtr[dataLength] = 0; + + tempHttpString = PhConvertMultiByteToUtf16(tempDataPtr); + + if (hashETag) + { + PhDereferenceObject(hashETag); + } + + if (finalHexString) + { + PhDereferenceObject(finalHexString); + } + + if (tempDataPtr) + { + PhFree(tempDataPtr); + } + + return tempHttpString; +} + +BOOLEAN HttpParseURL( + _Inout_ P_HTTP_SESSION HttpSocket, + _In_ PCWSTR Url, + _Out_ HTTP_PARSED_URL* HttpParsedUrl + ) +{ + URL_COMPONENTS httpUrlComponents; + + memset(&httpUrlComponents, 0, sizeof(URL_COMPONENTS)); + + httpUrlComponents.dwStructSize = sizeof(URL_COMPONENTS); + httpUrlComponents.dwSchemeLength = (ULONG)-1; + httpUrlComponents.dwHostNameLength = (ULONG)-1; + httpUrlComponents.dwUrlPathLength = (ULONG)-1; + + if (WinHttpCrackUrl( + Url, + 0,//(ULONG)wcslen(Url), + 0, + &httpUrlComponents + )) + { + HTTP_PARSED_URL httpParsedUrl = PhAllocate(sizeof(struct _HTTP_PARSED_URL)); + memset(httpParsedUrl, 0, sizeof(struct _HTTP_PARSED_URL)); + + wmemcpy(httpParsedUrl->HttpMethod, httpUrlComponents.lpszScheme, httpUrlComponents.dwSchemeLength); + wmemcpy(httpParsedUrl->HttpServer, httpUrlComponents.lpszHostName, httpUrlComponents.dwHostNameLength); + wmemcpy(httpParsedUrl->HttpPath, httpUrlComponents.lpszUrlPath, httpUrlComponents.dwUrlPathLength); + + *HttpParsedUrl = httpParsedUrl; + + return TRUE; + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/progress.c b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/progress.c new file mode 100644 index 0000000..18e28dd --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/progress.c @@ -0,0 +1,256 @@ +#include +#include +#include "appsup.h" +#include "netio.h" + +#define TIME_DAYS_IN_YEAR 365 +#define TIME_HOURS_IN_DAY 24 +#define TIME_MINUTES_IN_HOUR 60 +#define TIME_SECONDS_IN_MINUTE 60 + +HWND _hwndProgress = NULL; +static BOOLEAN _TotalChanged = FALSE; +static BOOLEAN _CompletedChanged = FALSE; +static STOPWATCH InstallTimer = { 0 }; +static volatile LONG _Completed = 0; // progress completed +static volatile LONG _Total = 0; // total progress +static volatile LONG _PrevRate = 0; // previous progress rate (used for computing time remaining) +static volatile LONG _PrevTickCount = 0; // the tick count when we last updated the progress time +static volatile LONG _PrevCompleted = 0; // the amount we had completed when we last updated the progress time +static volatile LONG _LastUpdatedTimeRemaining = 0; // tick count when we last update the "Time remaining" field, we only update it every 5 seconds +static volatile LONG _LastUpdatedTickCount = 0; // tick count when SetProgress was last called, used to calculate the rate +static volatile LONG _NumTimesSetProgressCalled = 0; // how many times has the user called SetProgress? + +VOID StartProgress(VOID) +{ + StopwatchInitialize(&InstallTimer); + StopwatchStart(&InstallTimer); + + _PrevRate = 0; + _PrevCompleted = 0; + _Completed = 0; // progress completed + //_dwTotal = 0; + _PrevRate = 0; // previous progress rate (used for computing time remaining) + _PrevTickCount = 0; // the tick count when we last updated the progress time + _PrevCompleted = 0; // the amount we had completed when we last updated the progress time + _LastUpdatedTimeRemaining = 0;// tick count when we last update the "Time remaining" field, we only update it every 5 seconds + _LastUpdatedTickCount = 0; // tick count when SetProgress was last called, used to calculate the rate + _NumTimesSetProgressCalled = 0;// how many times has the user called SetProgress? + + SetProgress(0, 0); +} + +VOID _SetProgressTime(VOID) +{ + volatile LONG dwTotal = 0; + volatile LONG dwCompleted = 0; + volatile LONG dwCurrentRate = 0; + volatile LONG dwTickDelta = 0; + volatile LONG dwLeft = 0; + volatile LONG dwCurrentTickCount = 0; + volatile LONG dwAverageRate = 0; + + LONG dwSecondsLeft = 0; + LONG dwTickCount = 0; + LONG time_taken = 0; + LONG download_speed = 0; + PPH_STRING timeRemainingString = NULL; + + InterlockedIncrement(&_NumTimesSetProgressCalled); + InterlockedExchange(&dwTotal, _Total); + InterlockedExchange(&dwCompleted, _Completed); + InterlockedExchange(&dwCurrentTickCount, _LastUpdatedTickCount); + + dwLeft = dwTotal - dwCompleted; + dwTickDelta = dwCurrentTickCount - _PrevTickCount; + dwTickCount = StopwatchGetMilliseconds(&InstallTimer); + + if (_TotalChanged) + { + _TotalChanged = FALSE; + SendDlgItemMessage(_hwndProgress, IDC_PROGRESS1, PBM_SETRANGE32, 0, (LPARAM)dwTotal); + } + + if (_CompletedChanged) + { + _CompletedChanged = FALSE; + SendDlgItemMessage(_hwndProgress, IDC_PROGRESS1, PBM_SETPOS, (WPARAM)dwCompleted, 0); + } + + if (dwCompleted <= _PrevCompleted) + { + // we are going backwards... + dwCurrentRate = (_PrevRate ? _PrevRate : 2); + } + else + { + // calculate the current rate in points per tenth of a second + dwTickDelta /= 100; + + if (dwTickDelta == 0) + dwTickDelta = __max(dwTickDelta, 1); // Protect from divide by zero + + dwCurrentRate = (dwCompleted - _PrevCompleted) / dwTickDelta; + } + + // we divide the TickDelta by 100 to give tenths of seconds, so if we have recieved an update faster than that, just skip it + //if (dwTickDelta < 100) + // return; + + // time remaining in seconds (we take a REAL average to smooth out random fluxuations) + dwAverageRate = (ULONG)((dwCurrentRate + (__int64)_PrevRate * _NumTimesSetProgressCalled) / (_NumTimesSetProgressCalled + 1)); + dwAverageRate = __max(dwAverageRate, 1); // Protect from divide by zero + dwSecondsLeft = (dwLeft / dwAverageRate) / 10; + + // Skip the first five calls (we need an average)... + //if (_NumTimesSetProgressCalled > 5)// ((dwSecondsLeft >= MIN_MINTIME4FEEDBACK) && (_iNumTimesSetProgressCalled >= 5)) + { + time_taken = dwCurrentTickCount - dwTickDelta; + time_taken = __max(time_taken, 1); // Protect from divide by zero + download_speed = (dwCompleted / time_taken) * 1024; + + //if (download_speed) + { + LONGLONG nPercent = 0; + PPH_STRING statusRemaningBytesString = PhFormatSize(dwCompleted, -1); + PPH_STRING statusLengthString = PhFormatSize(dwTotal, -1); + PPH_STRING statusSpeedString = PhFormatSize(download_speed, -1); + + if (dwTotal) + { + // Will scaling it up cause a wrap? + if ((100 * 100) <= dwTotal) + { + // Yes, so scale down. + nPercent = (dwCompleted / (dwTotal / 100)); + } + else + { + // No, so scale up. + nPercent = ((100 * dwCompleted) / dwTotal); + } + } + + PPH_STRING statusText1 = PhFormatString(L"Transfer rate: %s/s ", statusSpeedString->Buffer); + PPH_STRING statusText2 = PhFormatString(L"Downloaded: %s of %s (%u%% Completed)", + statusRemaningBytesString->Buffer, + statusLengthString->Buffer, + nPercent + ); + + SetDlgItemText(_hwndProgress, IDC_REMAINTIME, statusText1->Buffer); + SetDlgItemText(_hwndProgress, IDC_REMAINTIME3, statusText2->Buffer); + + PhDereferenceObject(statusText2); + PhDereferenceObject(statusText1); + PhDereferenceObject(statusSpeedString); + PhDereferenceObject(statusLengthString); + PhDereferenceObject(statusRemaningBytesString); + } + + // Is it more than an hour? + if (dwSecondsLeft > (TIME_SECONDS_IN_MINUTE * TIME_MINUTES_IN_HOUR)) + { + LONGLONG dwMinutes = dwSecondsLeft / TIME_SECONDS_IN_MINUTE; + LONGLONG dwHours = dwMinutes / TIME_MINUTES_IN_HOUR; + LONGLONG dwDays = dwHours / TIME_HOURS_IN_DAY; + + if (dwDays) + { + dwHours %= TIME_HOURS_IN_DAY; + + // It's more than a day, so display days and hours. + if (dwDays == 1) + { + if (dwHours == 1) + timeRemainingString = PhFormatString(L"Remaining time: %u day and %u hour", dwHours, dwMinutes); + else + timeRemainingString = PhFormatString(L"Remaining time: %u day and %u hours", dwHours, dwMinutes); + } + else + { + if (dwHours == 1) + timeRemainingString = PhFormatString(L"Remaining time: %u days and %u hour", dwHours, dwMinutes); + else + timeRemainingString = PhFormatString(L"Remaining time: %u days and %u hours", dwHours, dwMinutes); + } + } + else + { + // It's less than a day, so display hours and minutes. + dwMinutes %= TIME_MINUTES_IN_HOUR; + + if (dwHours == 1) + { + if (dwMinutes == 1) + timeRemainingString = PhFormatString(L"Remaining time: %u hour and %u minute", dwHours, dwMinutes); + else + timeRemainingString = PhFormatString(L"Remaining time: %u hour and %u minutes", dwHours, dwMinutes); + } + else + { + if (dwMinutes == 1) + timeRemainingString = PhFormatString(L"Remaining time: %u hours and %u minute", dwHours, dwMinutes); + else + { + ULONG stime = dwSecondsLeft %= TIME_MINUTES_IN_HOUR; + timeRemainingString = PhFormatString(L"Remaining time: %u hours, %u minutes and %u seconds", dwHours, dwMinutes, stime); + } + } + } + } + else + { + if (dwSecondsLeft > TIME_SECONDS_IN_MINUTE) + { + ULONG time = dwSecondsLeft / TIME_SECONDS_IN_MINUTE; + ULONG stime = dwSecondsLeft %= TIME_MINUTES_IN_HOUR; + + timeRemainingString = PhFormatString(L"Remaining time: %u minutes and %u seconds", time, stime); + } + else + { + if (dwSecondsLeft > 0) + { + // Round up to 5 seconds so it doesn't look so random + //ULONG stime = ((dwSecondsLeft + 4) / 5) * 5; + timeRemainingString = PhFormatString(L"Remaining time: %u seconds", dwSecondsLeft); + } + else + { + timeRemainingString = PhFormatString(L"Remaining time: 0 seconds"); + } + } + } + + // update the Time remaining field + SetDlgItemText(_hwndProgress, IDC_REMAINTIME2, timeRemainingString->Buffer); + PhDereferenceObject(timeRemainingString); + } + + // we are updating now, so set all the stuff for next time + InterlockedExchange(&_LastUpdatedTimeRemaining, dwTickCount); + InterlockedExchange(&_PrevRate, dwAverageRate); + InterlockedExchange(&_PrevTickCount, dwCurrentTickCount); + InterlockedExchange(&_PrevCompleted, dwCompleted); +} + +VOID SetProgress( + _In_ LONG Completed, + _In_ LONG Total + ) +{ + if (InterlockedCompareExchange(&_Completed, _Completed, Completed) != Completed) + { + InterlockedExchange(&_Completed, Completed); + _CompletedChanged = TRUE; + } + + if (InterlockedCompareExchange(&_Total, _Total, Total) != Total) + { + InterlockedExchange(&_Total, Total); + _TotalChanged = TRUE; + } + + InterlockedExchange(&_LastUpdatedTickCount, StopwatchGetMilliseconds(&InstallTimer)); +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/lib/superclass.c b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/superclass.c new file mode 100644 index 0000000..adadf08 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/lib/superclass.c @@ -0,0 +1,247 @@ +#include + +#pragma comment(lib, "Msimg32.lib") + +//_Success_(return != NULL) + +INT GetWindowWidth(HWND hwnd) +{ + RECT rect = { 0 }; + + GetWindowRect(hwnd, &rect); + + return (rect.right - rect.left); +} + +INT GetWindowHeight(HWND hwnd) +{ + RECT rect = { 0 }; + + GetWindowRect(hwnd, &rect); + + return (rect.bottom - rect.top); +} + +INT GetClientWindowWidth(HWND hwnd) +{ + RECT rect = { 0 }; + + GetClientRect(hwnd, &rect); + + return (rect.right - rect.left); +} + +INT GetClientWindowHeight(HWND hwnd) +{ + RECT rect = { 0 }; + + GetClientRect(hwnd, &rect); + + return (rect.bottom - rect.top); +} + + +static VOID InflateClientRect( + _Inout_ PRECT rect, + _In_ INT x, + _In_ INT y + ) +{ + rect->left -= x; + rect->top -= y; + rect->right += x; + rect->bottom += y; +} + +static VOID FrameClientRect( + _In_ HDC hdc, + _In_ const PRECT rect, + _In_ HBRUSH hbrush + ) +{ + if ((rect->right <= rect->left) || (rect->bottom <= rect->top)) + return; + + HBRUSH prevBrush = SelectObject(hdc, hbrush); + + PatBlt(hdc, rect->left, rect->top, 1, rect->bottom - rect->top, PATCOPY); + PatBlt(hdc, rect->right - 1, rect->top, 1, rect->bottom - rect->top, PATCOPY); + PatBlt(hdc, rect->left, rect->top, rect->right - rect->left, 1, PATCOPY); + PatBlt(hdc, rect->left, rect->bottom - 1, rect->right - rect->left, 1, PATCOPY); + + if (prevBrush) + { + SelectObject(hdc, prevBrush); + } +} + +static VOID FillClientRect( + _In_ HDC hdc, + _In_ const PRECT rect, + _In_ HBRUSH hbrush + ) +{ + HBRUSH prevBrush = SelectObject(hdc, hbrush); + + PatBlt( + hdc, + rect->left, + rect->top, + rect->right - rect->left, + rect->bottom - rect->top, + PATCOPY + ); + + if (prevBrush) + { + SelectObject(hdc, prevBrush); + } +} + + + + + + + +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb760816.aspx +VOID DrawProgressBarControl( + _In_ HWND WindowHandle, + _In_ HDC WindowDC, + _In_ RECT ClientRect + ) +{ + INT curValue = SendMessage(WindowHandle, PBM_GETPOS, 0, 0); + INT maxValue = SendMessage(WindowHandle, PBM_GETRANGE, 0, 0); + FLOAT percent = ((FLOAT)curValue / (FLOAT)maxValue) * 100.f; + + int oldBkMode = SetBkMode(WindowDC, TRANSPARENT); + + // Fill window background + FillClientRect(WindowDC, &ClientRect, GetSysColorBrush(COLOR_3DFACE)); + // Draw window border + FrameClientRect(WindowDC, &ClientRect, GetStockBrush(LTGRAY_BRUSH)); + + if (curValue < 1) + { + return; + } + + //if (maxValue < 1) + //{ + // return; + //} + + InflateClientRect( + &ClientRect, + -GetSystemMetrics(SM_CXBORDER) * 2, + -GetSystemMetrics(SM_CYBORDER) * 2 + ); + + // Set progress fill length + ClientRect.right = ((LONG)(ClientRect.left + ((ClientRect.right - ClientRect.left) * percent) / 100)); + + // Draw progress fill border + //FrameClientRect(WindowDC, &ClientRect, GetStockBrush(BLACK_BRUSH)); + + //InflateClientRect( + // &ClientRect, + // -GetSystemMetrics(SM_CXBORDER), + // -GetSystemMetrics(SM_CYBORDER) + // ); + + HBRUSH brush = GetSysColorBrush(COLOR_HIGHLIGHT);// CreateSolidBrush(RGB(48, 190, 50)); + FillClientRect(WindowDC, &ClientRect, brush); + DeleteObject(brush); + + // draw background last? + //SubtractRect() + + SetBkMode(WindowDC, oldBkMode); +} + + +LRESULT CALLBACK SubclassWindowProc( + _In_ HWND hWnd, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam, + _In_ UINT_PTR uIdSubclass, + _In_ DWORD_PTR dwRefData + ) +{ + switch (uMsg) + { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + PAINTSTRUCT paintStruct; + RECT clientRect; + + GetClientRect(hWnd, &clientRect); + + if (BeginPaint(hWnd, &paintStruct)) + { + SetBkMode(paintStruct.hdc, TRANSPARENT); + + //if (BeginBufferedPaint_I) + //{ + // HDC bufferedHdc = NULL; + // HPAINTBUFFER bufferedHandle = NULL; + + // if (bufferedHandle = BeginBufferedPaint_I( + // paintStruct.hdc, + // &clientRect, + // BPBF_COMPATIBLEBITMAP, + // NULL, + // &bufferedHdc + // )) + // { + // if (uIdSubclass == IDC_PROGRESS1) + // { + // DrawProgressBarControl(hWnd, bufferedHdc, clientRect); + // } + + // if (EndBufferedPaint_I) + // { + // EndBufferedPaint_I(bufferedHandle, TRUE); + // } + // } + // else + // { + // DrawProgressBarControl(hWnd, paintStruct.hdc, clientRect); + // } + //} + //else + { + DrawProgressBarControl(hWnd, paintStruct.hdc, clientRect); + } + + EndPaint(hWnd, &paintStruct); + } + return 1; + } + break; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +//VOID SetupSuperSubclass(VOID) +//{ +// WNDCLASSEX windowClassInfo = { sizeof(WNDCLASSEX) }; +// +// if (GetClassInfoEx(NULL, WC_BUTTON, &windowClassInfo)) +// { +// parentWndProc = windowClassInfo.lpfnWndProc; // save the original message handler +// windowClassInfo.lpfnWndProc = WindowProc; // Set our new message handler +// +// UnregisterClass(WC_BUTTON, NULL); +// RegisterClassEx(&windowClassInfo); +// +// return TRUE; +// } +// +// return FALSE; +//} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/main.c b/tools/ProcessHackerSetup/ProcessHackerSetup/main.c new file mode 100644 index 0000000..73e48d6 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/main.c @@ -0,0 +1,120 @@ +#include + +PPH_STRING SetupInstallPath = NULL; + +static VOID PvpInitializeDpi( + VOID + ) +{ + HDC hdc; + + if (hdc = GetDC(NULL)) + { + PhGlobalDpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } +} + +INT CALLBACK MainPropSheet_Callback( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + case PSCB_INITIALIZED: + { + + } + break; + case PSCB_PRECREATE: + { + if (lParam) + { + ((LPDLGTEMPLATE)lParam)->style |= WS_MINIMIZEBOX; + } + } + break; + } + + return FALSE; +} + +INT WINAPI wWinMain( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ PWSTR lpCmdLine, + _In_ INT nCmdShow + ) +{ + PROPSHEETPAGE propSheetPage = { sizeof(PROPSHEETPAGE) }; + PROPSHEETHEADER propSheetHeader = { sizeof(PROPSHEETHEADER) }; + HPROPSHEETPAGE pages[5]; + + if (!NT_SUCCESS(PhInitializePhLibEx(0, 0, 0))) + return 1; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + PhApplicationName = L"Process Hacker - Setup"; + PhGuiSupportInitialization(); + PvpInitializeDpi(); + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_USECALLBACK | + PSH_WIZARD_LITE; + propSheetHeader.hInstance = PhLibImageBase; + propSheetHeader.pszIcon = MAKEINTRESOURCE(IDI_ICON1); + propSheetHeader.pfnCallback = MainPropSheet_Callback; + propSheetHeader.phpage = pages; + + // page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.pszTitle = PhApplicationName; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); + propSheetPage.pfnDlgProc = PropSheetPage1_WndProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.pszTitle = PhApplicationName; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); + propSheetPage.pfnDlgProc = PropSheetPage2_WndProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); + propSheetPage.pfnDlgProc = PropSheetPage3_WndProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.pszTitle = PhApplicationName; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DIALOG4); + propSheetPage.pfnDlgProc = PropSheetPage4_WndProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.dwFlags = PSP_USETITLE; + propSheetPage.pszTitle = PhApplicationName; + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_DIALOG5); + propSheetPage.pfnDlgProc = PropSheetPage5_WndProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + PhModalPropertySheet(&propSheetHeader); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/page1.c b/tools/ProcessHackerSetup/ProcessHackerSetup/page1.c new file mode 100644 index 0000000..2a9c74a --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/page1.c @@ -0,0 +1,130 @@ +#include +#include + +VOID LoadSetupIcons( + _In_ HWND hwndDlg + ) +{ + HBITMAP smallIconHandle = (HBITMAP)LoadImage( + PhLibImageBase, + MAKEINTRESOURCE(IDI_ICON1), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR + ); + HBITMAP largeIconHandle = (HBITMAP)LoadImage( + PhLibImageBase, + MAKEINTRESOURCE(IDI_ICON1), + IMAGE_ICON, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON), + LR_DEFAULTCOLOR + ); + + SendMessage(GetParent(hwndDlg), WM_SETICON, ICON_SMALL, (LPARAM)smallIconHandle); + SendMessage(GetParent(hwndDlg), WM_SETICON, ICON_BIG, (LPARAM)largeIconHandle); + + DeleteObject(largeIconHandle); + DeleteObject(smallIconHandle); +} + +VOID LoadSetupImage( + _In_ HWND hwndDlg + ) +{ + HBITMAP imageBitmap = LoadPngImageFromResources(MAKEINTRESOURCE(IDB_PNG1)); + + // The image control uses a large square frame so that we can use the VS designer easily. + // Remove the frame style and apply the bitmap style. + PhSetWindowStyle( + GetDlgItem(hwndDlg, IDC_PROJECT_ICON), + SS_BITMAP | SS_BLACKFRAME, + SS_BITMAP + ); + + SendMessage( + GetDlgItem(hwndDlg, IDC_PROJECT_ICON), + STM_SETIMAGE, + IMAGE_BITMAP, + (LPARAM)imageBitmap + ); + + DeleteObject(imageBitmap); +} + +BOOL PropSheetPage1_OnInitDialog( + _In_ HWND hwndDlg, + _In_ HWND hwndFocus, + _Inout_ LPARAM lParam + ) +{ + LoadSetupIcons(hwndDlg); + LoadSetupImage(hwndDlg); + + // Center the dialog window on the desktop + PhCenterWindow(GetParent(hwndDlg), NULL); + SetForegroundWindow(GetParent(hwndDlg)); + + // Set the fonts + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER), 24, FW_SEMIBOLD); + InitializeFont(GetDlgItem(hwndDlg, IDC_SUBHEADER), 0, FW_NORMAL); + + // Enable the themed dialog background texture. + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + return TRUE; +} + +BOOL PropSheetPage1_OnNotify( + _In_ HWND hwndDlg, + _In_ INT idCtrl, + _Inout_ LPNMHDR lpNmh + ) +{ + LPPSHNOTIFY pageNotify = (LPPSHNOTIFY)lpNmh; + + switch (pageNotify->hdr.code) + { + case PSN_SETACTIVE: + { + HWND hwPropSheet = pageNotify->hdr.hwndFrom; + + // Disable the back button on Welcome page. + //PropSheet_SetWizButtons(hwPropSheet, PSWIZB_NEXT); + + // Hide the back button on the Welcome page. + ShowWindow(GetDlgItem(hwPropSheet, IDC_PROPSHEET_BACK), SW_HIDE); + } + break; + case PSN_KILLACTIVE: + { + HWND hwPropSheet = pageNotify->hdr.hwndFrom; + + // Enable the back button for other pages. + //PropSheet_SetWizButtons(hwPropSheet, PSWIZB_NEXT | PSWIZB_BACK); + + // Show the back button for other pages. + ShowWindow(GetDlgItem(hwPropSheet, IDC_PROPSHEET_BACK), SW_SHOW); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PropSheetPage1_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + HANDLE_MSG(hwndDlg, WM_INITDIALOG, PropSheetPage1_OnInitDialog); + HANDLE_MSG(hwndDlg, WM_NOTIFY, PropSheetPage1_OnNotify); + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/page2.c b/tools/ProcessHackerSetup/ProcessHackerSetup/page2.c new file mode 100644 index 0000000..c30ad91 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/page2.c @@ -0,0 +1,131 @@ +#include +#include + +VOID LoadEulaText( + _In_ HWND hwndDlg + ) +{ + HRSRC resourceHandle; + HGLOBAL resourceData; + PVOID resourceBuffer; + + resourceHandle = FindResource(PhLibImageBase, MAKEINTRESOURCE(IDR_TXT1), L"TXT"); + + if (resourceHandle) + { + resourceData = LoadResource(PhLibImageBase, resourceHandle); + + if (resourceData) + { + resourceBuffer = LockResource(resourceData); + + if (resourceBuffer) + { + PPH_STRING eulaTextString = PhConvertMultiByteToUtf16(resourceBuffer); + + SetWindowText(GetDlgItem(hwndDlg, IDC_EDIT1), eulaTextString->Buffer); + + PhDereferenceObject(eulaTextString); + } + } + + FreeResource(resourceHandle); + } +} + +BOOL PropSheetPage2_OnInitDialog( + _In_ HWND hwndDlg, + _In_ HWND hwndFocus, + _Inout_ LPARAM lParam + ) +{ + // Set the fonts. + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER), -17, FW_SEMIBOLD); + + // Set the default radio button state to 'do not accept'. + Button_SetCheck(GetDlgItem(hwndDlg, IDC_RADIO2), BST_CHECKED); + + LoadEulaText(hwndDlg); + + // Enable the themed dialog background texture. + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + return TRUE; +} + +BOOL PropSheetPage2_OnNotify( + _In_ HWND hwndDlg, + _In_ INT idCtrl, + _Inout_ LPNMHDR lpNmh + ) +{ + LPPSHNOTIFY pageNotify = (LPPSHNOTIFY)lpNmh; + + switch (pageNotify->hdr.code) + { + case PSN_SETACTIVE: + { + HWND hwPropSheet = pageNotify->hdr.hwndFrom; + +#ifndef DEBUG + // Disable the property sheet Next button, the user must accept the EULA to continue. + PropSheet_SetWizButtons(hwPropSheet, PSWIZB_BACK); +#endif + } + break; + case PSN_QUERYINITIALFOCUS: + { + // Set the default control as the 'do not accept' radio button. + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LPARAM)GetDlgItem(hwndDlg, IDC_RADIO2)); + } + return TRUE; + } + + return FALSE; +} + +BOOL PropSheetPage2_OnCommand( + _In_ HWND hwndDlg, + _In_ INT id, + _In_ HWND hwndCtl, + _In_ UINT codeNotify + ) +{ + switch (id) + { + case IDC_RADIO1: + case IDC_RADIO2: + { + if (Button_GetCheck(GetDlgItem(hwndDlg, IDC_RADIO1)) == BST_CHECKED) + { + // The user has agreed to the EULA, enable the Next button. + PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT | PSWIZB_BACK); + } + else + { + // The user did not agree, disable the next button. + PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PropSheetPage2_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + HANDLE_MSG(hwndDlg, WM_INITDIALOG, PropSheetPage2_OnInitDialog); + HANDLE_MSG(hwndDlg, WM_NOTIFY, PropSheetPage2_OnNotify); + HANDLE_MSG(hwndDlg, WM_COMMAND, PropSheetPage2_OnCommand); + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/page3.c b/tools/ProcessHackerSetup/ProcessHackerSetup/page3.c new file mode 100644 index 0000000..cebb9e3 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/page3.c @@ -0,0 +1,144 @@ +#include +#include + +VOID LoadInstallDirectory( + _In_ HWND hwndDlg + ) +{ + if (SetupInstallPath = GetProcessHackerInstallPath()) + { + // We must make sure the install path ends with a backslash since + // this string is wcscat' with our zip extraction paths. + if (PathAddBackslash(SetupInstallPath->Buffer)) + { + //PathSearchAndQualify() + } + } + + // If the string is null or empty, use the default installation path. + if (PhIsNullOrEmptyString(SetupInstallPath)) + { + //SetupInstallPath = PhGetKnownLocation(CSIDL_PROGRAM_FILES, L"\\Process Hacker 2\\settings.xml"); + + // TODO: Find a better method that handles Program Files on different drives than C: + // (It's common for some poeple to do this) + + if (WindowsVersion >= WINDOWS_7) + { + PPH_STRING defaultInstallPath; + PPH_STRING expandedString; + + // TODO: Does ProgramW6432 work on 32bit? + defaultInstallPath = PhCreateString(L"%ProgramW6432%\\Process Hacker 2\\"); + + if (expandedString = PhExpandEnvironmentStrings(&defaultInstallPath->sr)) + { + SetupInstallPath = expandedString; + } + } + } + + if (PhIsNullOrEmptyString(SetupInstallPath)) + { + SetupInstallPath = PhCreateString(L"C:\\Program Files\\Process Hacker 2\\"); + } + + SetDlgItemText(hwndDlg, IDC_INSTALL_DIRECTORY, SetupInstallPath->Buffer); +} + +BOOL PropSheetPage3_OnInitDialog( + _In_ HWND hwndDlg, + _In_ HWND hwndFocus, + _Inout_ LPARAM lParam + ) +{ + // Set the fonts. + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER), -17, FW_SEMIBOLD); + + // Set the default checkboxes. + Button_SetCheck(GetDlgItem(hwndDlg, IDC_CHECK1), TRUE); + Button_SetCheck(GetDlgItem(hwndDlg, IDC_CHECK6), TRUE); + + if (WindowsVersion >= WINDOWS_VISTA) + { + SendMessage( + GetDlgItem(hwndDlg, IDC_INSTALL_DIRECTORY), + EM_SETMARGINS, + EC_LEFTMARGIN | EC_RIGHTMARGIN, + MAKELPARAM(0, 0) + ); + } + + LoadInstallDirectory(hwndDlg); + + // Enable the themed dialog background texture. + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + return TRUE; +} + +BOOL PropSheetPage3_OnNotify( + _In_ HWND hwndDlg, + _In_ INT idCtrl, + _Inout_ LPNMHDR lpNmh + ) +{ + LPPSHNOTIFY pageNotify = (LPPSHNOTIFY)lpNmh; + + switch (pageNotify->hdr.code) + { + case PSN_QUERYINITIALFOCUS: + { + // Set the default control as the browse button. + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LPARAM)GetDlgItem(hwndDlg, IDC_FOLDER_BROWSE)); + } + return TRUE; + } + + return FALSE; +} + +BOOL PropSheetPage3_OnCommand( + _In_ HWND hwndDlg, + _In_ INT id, + _In_ HWND hwndCtl, + _In_ UINT codeNotify + ) +{ + switch (id) + { + case IDC_FOLDER_BROWSE: + { + PPH_STRING installFolder; + + installFolder = BrowseForFolder(hwndDlg, L"Select installation folder"); + + if (installFolder) + { + PhSwapReference(&SetupInstallPath, installFolder); + + SetDlgItemText(hwndDlg, IDC_INSTALL_DIRECTORY, SetupInstallPath->Buffer); + } + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PropSheetPage3_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + HANDLE_MSG(hwndDlg, WM_INITDIALOG, PropSheetPage3_OnInitDialog); + HANDLE_MSG(hwndDlg, WM_NOTIFY, PropSheetPage3_OnNotify); + HANDLE_MSG(hwndDlg, WM_COMMAND, PropSheetPage3_OnCommand); + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/page4.c b/tools/ProcessHackerSetup/ProcessHackerSetup/page4.c new file mode 100644 index 0000000..74e5cc5 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/page4.c @@ -0,0 +1,120 @@ +#include +#include + +NTSTATUS DownloadThread( + _In_ PVOID Arguments + ) +{ + BOOLEAN setupSuccess = FALSE; + + // Download the latest build + if (setupSuccess = SetupDownloadBuild(Arguments)) + { + // Reset the current installation + if (setupSuccess = SetupResetCurrentInstall(Arguments)) + { + // Extract and install the latest build + if (setupSuccess = SetupExtractBuild(Arguments)) + { + PostMessage(Arguments, PSM_SETCURSELID, 0, IDD_DIALOG5); + } + } + } + + if (!setupSuccess) + { + // Retry download... + PostMessage(Arguments, PSM_SETCURSELID, 0, IDD_DIALOG4); + } + + return STATUS_SUCCESS; +} + +BOOL PropSheetPage4_OnInitDialog( + _In_ HWND hwndDlg, + _In_ HWND hwndFocus, + _Inout_ LPARAM lParam + ) +{ + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER), -17, FW_SEMIBOLD); + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER1), -12, FW_SEMIBOLD); + + SetWindowSubclass( + GetDlgItem(hwndDlg, IDC_PROGRESS1), + SubclassWindowProc, + IDC_PROGRESS1, + 0 + ); + + // Enable the themed dialog background texture. + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + return TRUE; +} + +BOOL PropSheetPage4_OnNotify( + _In_ HWND hwndDlg, + _In_ INT idCtrl, + _Inout_ LPNMHDR lpNmh + ) +{ + LPPSHNOTIFY pageNotify = (LPPSHNOTIFY)lpNmh; + + switch (pageNotify->hdr.code) + { + case PSN_SETACTIVE: + { + HWND hwPropSheet = pageNotify->hdr.hwndFrom; + + // Disable Next/Back buttons + PropSheet_SetWizButtons(hwPropSheet, 0); + + _hwndProgress = hwndDlg; + + SetTimer(hwndDlg, 1, 100, NULL); + + PhCreateThread(0, DownloadThread, hwPropSheet); + } + break; + case PSN_QUERYCANCEL: + { + //if (UpdateResetState == InstallStateResetting || UpdateResetState == InstallStateInstalling) + + //PropSheet_CancelToClose(GetParent(hwndDlg)); + //EnableMenuItem(GetSystemMenu(GetParent(hwndDlg), FALSE), SC_CLOSE, MF_GRAYED); + //EnableMenuItem(GetSystemMenu(GetParent(hwndDlg), FALSE), SC_CLOSE, MF_ENABLED); + + //SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LPARAM)TRUE); + //return TRUE; + } + break; + case PSN_KILLACTIVE: + { + KillTimer(hwndDlg, 1); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PropSheetPage4_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + HANDLE_MSG(hwndDlg, WM_INITDIALOG, PropSheetPage4_OnInitDialog); + HANDLE_MSG(hwndDlg, WM_NOTIFY, PropSheetPage4_OnNotify); + case WM_TIMER: + { + _SetProgressTime(); + } + break; + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/page5.c b/tools/ProcessHackerSetup/ProcessHackerSetup/page5.c new file mode 100644 index 0000000..db0292d --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/page5.c @@ -0,0 +1,79 @@ +#include +#include + +static VOID LoadSetupImage( + _In_ HWND hwndDlg + ) +{ + HBITMAP imageBitmap = LoadPngImageFromResources(MAKEINTRESOURCE(IDB_PNG1)); + + // The image control uses a large square frame so that we can use the VS dialog designer more easily. + // Remove the frame style and apply the bitmap style. + PhSetWindowStyle( + GetDlgItem(hwndDlg, IDC_PROJECT_ICON), + SS_BITMAP | SS_BLACKFRAME, + SS_BITMAP + ); + + SendMessage( + GetDlgItem(hwndDlg, IDC_PROJECT_ICON), + STM_SETIMAGE, + IMAGE_BITMAP, + (LPARAM)imageBitmap + ); + + DeleteObject(imageBitmap); +} + +BOOL PropSheetPage5_OnInitDialog( + _In_ HWND hwndDlg, + _In_ HWND hwndFocus, + _Inout_ LPARAM lParam + ) +{ + // Set the fonts. + InitializeFont(GetDlgItem(hwndDlg, IDC_MAINHEADER), -17, FW_SEMIBOLD); + + LoadSetupImage(hwndDlg); + + // Enable the themed dialog background texture. + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + + return TRUE; +} + +BOOL PropSheetPage5_OnNotify( + _In_ HWND hwndDlg, + _In_ INT idCtrl, + _Inout_ LPNMHDR lpNmh + ) +{ + LPPSHNOTIFY pageNotify = (LPPSHNOTIFY)lpNmh; + + switch (pageNotify->hdr.code) + { + case PSN_SETACTIVE: + { + Button_SetText(GetDlgItem(pageNotify->hdr.hwndFrom, IDCANCEL), L"Close"); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PropSheetPage5_WndProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _Inout_ WPARAM wParam, + _Inout_ LPARAM lParam + ) +{ + switch (uMsg) + { + HANDLE_MSG(hwndDlg, WM_INITDIALOG, PropSheetPage5_OnInitDialog); + HANDLE_MSG(hwndDlg, WM_NOTIFY, PropSheetPage5_OnNotify); + } + + return FALSE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resource.h b/tools/ProcessHackerSetup/ProcessHackerSetup/resource.h new file mode 100644 index 0000000..d593b7d Binary files /dev/null and b/tools/ProcessHackerSetup/ProcessHackerSetup/resource.h differ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resource.rc b/tools/ProcessHackerSetup/ProcessHackerSetup/resource.rc new file mode 100644 index 0000000..9054f92 Binary files /dev/null and b/tools/ProcessHackerSetup/ProcessHackerSetup/resource.rc differ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resources/Licence.txt b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/Licence.txt new file mode 100644 index 0000000..4a64d83 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/Licence.txt @@ -0,0 +1,234 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified it, and giving a relevant date. +b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. +c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. +d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + +a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. +b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. +c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. +d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. +e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or +b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or +c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or +d) Limiting the use for publicity purposes of names of licensors or authors of the material; or +e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or +f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . + +Process Hacker is distributed under the GNU GPL version 3, +with the following exception: + +Permission is granted to dynamically (but not statically) link this +program with independent modules, regardless of the license terms of +these independent modules, provided that this program is not modified +in any way. An independent module is a module which is not derived +from or based on this program. If you modify this program, this +additional permission no longer applies unless authorized by the +copyright holders. \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.ico b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.ico new file mode 100644 index 0000000..96d8e6a Binary files /dev/null and b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.ico differ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.png b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.png new file mode 100644 index 0000000..73f282b Binary files /dev/null and b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/ProcessHacker.png differ diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/resources/app.manifest b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/app.manifest new file mode 100644 index 0000000..ba5ecbc --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/resources/app.manifest @@ -0,0 +1,30 @@ + + + + Process Hacker Setup + + + + + + + + + + + + + + + + + + + + + + + false + + + \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/setup/download.c b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/download.c new file mode 100644 index 0000000..722fa85 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/download.c @@ -0,0 +1,294 @@ +#include +#include +#include + +PWSTR Version = NULL; +PWSTR DownloadURL = NULL; + +BOOLEAN QueryBuildServerThread( + _Inout_ P_HTTP_SESSION HttpSocket + ) +{ + BOOLEAN isSuccess = FALSE; + PPH_STRING xmlStringData = NULL; + + __try + { + STATUS_MSG(L"Connecting to build server...\n"); + Sleep(500); + + if (!HttpConnect(HttpSocket, L"wj32.org", INTERNET_DEFAULT_HTTPS_PORT)) + { + STATUS_MSG(L"HttpConnect: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Connected to build server...\n"); + Sleep(500); + + if (!HttpBeginRequest(HttpSocket, NULL, L"/processhacker/update.php", WINHTTP_FLAG_SECURE)) + { + STATUS_MSG(L"HttpBeginRequest: %u\n", GetLastError()); + __leave; + } + + if (!HttpAddRequestHeaders(HttpSocket, L"ProcessHacker-OsBuild: 0x0D06F00D")) + { + STATUS_MSG(TEXT("HttpAddRequestHeaders: %u\n"), GetLastError()); + __leave; + } + + if (!HttpSendRequest(HttpSocket, 0)) + { + STATUS_MSG(L"HttpSendRequest: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Querying build server..."); + Sleep(500); + + if (!HttpEndRequest(HttpSocket)) + { + STATUS_MSG(L"HttpEndRequest: %u\n", GetLastError()); + __leave; + } + + if (!(xmlStringData = HttpDownloadString(HttpSocket))) + { + STATUS_MSG(L"HttpDownloadString: %u\n", GetLastError()); + __leave; + } + + Version = PhFormatString( + L"%s.%s", + XmlParseToken(xmlStringData->Buffer, L"ver"), + XmlParseToken(xmlStringData->Buffer, L"rev") + )->Buffer; + + DownloadURL = PhFormatString( + L"https://github.com/processhacker2/processhacker2/releases/download/v%s/processhacker-%s-bin.zip", + XmlParseToken(xmlStringData->Buffer, L"ver"), + XmlParseToken(xmlStringData->Buffer, L"ver") + )->Buffer; + + STATUS_MSG(L"Found build: %s\n", Version); + + isSuccess = TRUE; + } + __finally + { + if (xmlStringData) + { + PhDereferenceObject(xmlStringData); + } + } + + return isSuccess; +} + + + + +BOOLEAN SetupDownloadBuild( + _In_ PVOID Arguments + ) +{ + ULONG contentLength = 0; + BOOLEAN isDownloadSuccess = FALSE; + P_HTTP_SESSION httpSocket = NULL; + HTTP_PARSED_URL urlComponents = NULL; + HANDLE tempFileHandle = NULL; + ULONG writeLength = 0; + ULONG readLength = 0; + ULONG totalLength = 0; + ULONG bytesDownloaded = 0; + ULONG downloadedBytes = 0; + ULONG contentLengthSize = sizeof(ULONG); + PH_HASH_CONTEXT hashContext; + IO_STATUS_BLOCK isb; + BYTE buffer[PAGE_SIZE]; + + StartProgress(); + SendDlgItemMessage(Arguments, IDC_PROGRESS1, PBM_SETSTATE, PBST_NORMAL, 0); + + httpSocket = HttpSocketCreate(); + + __try + { + if (!QueryBuildServerThread(httpSocket)) + __leave; + + if (!HttpParseURL(httpSocket, DownloadURL, &urlComponents)) + { + STATUS_MSG(L"HttpParseURL: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Connecting to download server...\n"); + + if (!HttpConnect(httpSocket, urlComponents->HttpServer, INTERNET_DEFAULT_HTTPS_PORT)) + { + STATUS_MSG(L"HttpConnect: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Connected to download server...\n"); + + if (!HttpBeginRequest(httpSocket, NULL, urlComponents->HttpPath, WINHTTP_FLAG_SECURE)) + { + STATUS_MSG(L"HttpBeginRequest: %u\n", GetLastError()); + __leave; + } + + if (!HttpAddRequestHeaders(httpSocket, L"User-Agent: 0x0D06F00D")) + { + STATUS_MSG(L"HttpAddRequestHeaders: %u\n", GetLastError()); + __leave; + } + + if (!HttpSendRequest(httpSocket, 0)) + { + STATUS_MSG(L"HttpSendRequest: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Querying download server..."); + + if (!HttpEndRequest(httpSocket)) + { + STATUS_MSG(L"HttpEndRequest: %u\n", GetLastError()); + __leave; + } + + contentLength = HttpGetRequestHeaderDword(httpSocket, WINHTTP_QUERY_CONTENT_LENGTH); + + if (contentLength == 0) + { + STATUS_MSG(L"HttpGetRequestHeaderDword: %u\n", GetLastError()); + __leave; + } + + STATUS_MSG(L"Downloading latest build %s...\n", Version); + + // Create output file + if (!NT_SUCCESS(PhCreateFileWin32( + &tempFileHandle, + L"processhacker-2.38-bin.zip", + FILE_GENERIC_READ | FILE_GENERIC_WRITE, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_TEMPORARY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + __leave; + } + + // Initialize hash algorithm. + PhInitializeHash(&hashContext, Sha1HashAlgorithm); + + // Zero the buffer. + memset(buffer, 0, PAGE_SIZE); + + // Download the data. + while (WinHttpReadData(httpSocket->RequestHandle, buffer, PAGE_SIZE, &bytesDownloaded)) + { + // If we get zero bytes, the file was uploaded or there was an error + if (bytesDownloaded == 0) + break; + + // Update the hash of bytes we downloaded. + PhUpdateHash(&hashContext, buffer, bytesDownloaded); + + // Write the downloaded bytes to disk. + if (!NT_SUCCESS(NtWriteFile( + tempFileHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + bytesDownloaded, + NULL, + NULL + ))) + { + __leave; + } + + downloadedBytes += (DWORD)isb.Information; + + // Check the number of bytes written are the same we downloaded. + if (bytesDownloaded != isb.Information) + __leave; + + // Update the GUI progress. + // TODO: Update on GUI thread. + //{ + // //int percent = MulDiv(100, downloadedBytes, contentLength); + // FLOAT percent = ((FLOAT)downloadedBytes / contentLength * 100); + // PPH_STRING totalDownloaded = PhFormatSize(downloadedBytes, -1); + // PPH_STRING totalLength = PhFormatSize(contentLength, -1); + // + // PPH_STRING dlLengthString = PhFormatString( + // L"%s of %s (%.0f%%)", + // totalDownloaded->Buffer, + // totalLength->Buffer, + // percent + // ); + // + // // Update the progress bar position + // SendMessage(context->ProgressHandle, PBM_SETPOS, (ULONG)percent, 0); + // Static_SetText(context->StatusHandle, dlLengthString->Buffer); + // + // PhDereferenceObject(dlLengthString); + // PhDereferenceObject(totalDownloaded); + // PhDereferenceObject(totalLength); + //} + + SetProgress(downloadedBytes, contentLength); + } + + // Compute hash result (will fail if file not downloaded correctly). + //if (PhFinalHash(&hashContext, &hashBuffer, 20, NULL)) + //{ + // // Allocate our hash string, hex the final hash result in our hashBuffer. + // PPH_STRING hexString = PhBufferToHexString(hashBuffer, 20); + // + // if (PhEqualString(hexString, context->Hash, TRUE)) + // { + // //hashSuccess = TRUE; + // } + // + // PhDereferenceObject(hexString); + //} + // + // //if (_tcsstr(hashETag->Buffer, finalHexString->Buffer)) + // { + // //DEBUG_MSG(TEXT("Hash success: %s (%s)\n"), SetupFileName->Buffer, finalHexString->Buffer); + // } + // //else + // //{ + // // SendDlgItemMessage(_hwndProgress, IDC_PROGRESS1, PBM_SETSTATE, PBST_ERROR, 0); + // // SetDlgItemText(_hwndProgress, IDC_MAINHEADER, TEXT("Retrying download... Hash error.")); + // // DEBUG_MSG(TEXT("Hash error (retrying...): %s\n"), SetupFileName->Buffer); + // //} + + + isDownloadSuccess = TRUE; + } + __finally + { + if (tempFileHandle) + { + NtClose(tempFileHandle); + } + + if (urlComponents) + { + PhFree(urlComponents); + } + } + + return TRUE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/setup/extract.c b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/extract.c new file mode 100644 index 0000000..efa2105 --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/extract.c @@ -0,0 +1,228 @@ +#include +#include +#include "..\lib\miniz\miniz.h" + +struct +{ + PSTR FileName; + PWSTR ExtractFileName; +} SetupFiles_X64[] = +{ + { "CHANGELOG.txt", L"CHANGELOG.txt" }, + { "COPYRIGHT.txt", L"COPYRIGHT.txt" }, + { "LICENSE.txt", L"LICENSE.txt" }, + { "README.txt", L"README.txt" }, + + { "x64/peview.exe", L"peview.exe" }, + { "x64/ProcessHacker.exe", L"ProcessHacker.exe" }, + { "x64/kprocesshacker.sys", L"kprocesshacker.sys" }, + + { "x64/plugins/DotNetTools.dll", L"plugins\\DotNetTools.dll" }, + { "x64/plugins/ExtendedNotifications.dll", L"plugins\\ExtendedNotifications.dll" }, + { "x64/plugins/ExtendedServices.dll", L"plugins\\ExtendedServices.dll" }, + { "x64/plugins/ExtendedTools.dll", L"plugins\\ExtendedTools.dll" }, + { "x64/plugins/HardwareDevices.dll", L"plugins\\HardwareDevices.dll" }, + { "x64/plugins/NetworkTools.dll", L"plugins\\NetworkTools.dll" }, + { "x64/plugins/OnlineChecks.dll", L"plugins\\OnlineChecks.dll" }, + { "x64/plugins/SbieSupport.dll", L"plugins\\SbieSupport.dll" }, + { "x64/plugins/ToolStatus.dll", L"plugins\\ToolStatus.dll" }, + { "x64/plugins/Updater.dll", L"plugins\\Updater.dll" }, + { "x64/plugins/UserNotes.dll", L"plugins\\UserNotes.dll" }, + { "x64/plugins/WindowExplorer.dll", L"plugins\\WindowExplorer.dll" }, + + { "x86/ProcessHacker.exe", L"x86\\ProcessHacker.exe" }, + { "x86/plugins/DotNetTools.dll", L"x86\\plugins\\DotNetTools.dll" }, +}; + + +BOOLEAN SetupExtractBuild( + _In_ PVOID Arguments + ) +{ + BOOLEAN isInstallSuccess = FALSE; + mz_uint64 totalLength = 0; + mz_uint64 total_size = 0; + mz_uint64 downloadedBytes = 0; + + PPH_STRING totalSizeStr = NULL; + + mz_bool status = MZ_FALSE; + mz_zip_archive zip_archive; + + memset(&zip_archive, 0, sizeof(zip_archive)); + + STATUS_MSG(L"Extracting update %s...", Version); + Sleep(1000); + + StartProgress(); + + __try + { + if (!CreateDirectoryPath(SetupInstallPath)) + { + __leave; + } + + // TODO: Move existing folder items. + + if (!(status = mz_zip_reader_init_file(&zip_archive, "processhacker-2.38-bin.zip", 0))) + { + __leave; + } + + // Get filesize information for extract progress + for (mz_uint i = 0; i < ARRAYSIZE(SetupFiles_X64); i++) + { + mz_uint index = mz_zip_reader_locate_file( + &zip_archive, + SetupFiles_X64[i].FileName, + NULL, + 0 + ); + + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(&zip_archive, index, &file_stat)) + { + //mz_zip_reader_end(&zip_archive); + //break; + } + + total_size += file_stat.m_uncomp_size; + + DEBUG_MSG(L"Filename: \"%hs\", Uncompressed size: %I64u, Compressed size: %I64u\r\n", + file_stat.m_filename, + file_stat.m_uncomp_size, + file_stat.m_comp_size + ); + } + + totalSizeStr = PhFormatSize(total_size, -1); + DEBUG_MSG(L"Opened Zip: %hs (%s)\r\n", "processhacker-2.38-bin.zip", totalSizeStr->Buffer); + + for (ULONG i = 0; i < ARRAYSIZE(SetupFiles_X64); i++) + { + mz_ulong file_crc32 = MZ_CRC32_INIT; + IO_STATUS_BLOCK isb; + HANDLE fileHandle; + ULONG indexOfFileName = -1; + PPH_STRING fullSetupPath; + PPH_STRING extractPath; + mz_uint file_index; + mz_zip_archive_file_stat file_stat; + size_t bufferLength = 0; + + extractPath = PhConcatStrings(2, + SetupInstallPath->Buffer, + SetupFiles_X64[i].ExtractFileName + ); + + // Create the directory if it does not exist. + if (fullSetupPath = PhGetFullPath(extractPath->Buffer, &indexOfFileName)) + { + PPH_STRING directoryPath; + + if (indexOfFileName == -1) + __leave; + + if (directoryPath = PhSubstring(fullSetupPath, 0, indexOfFileName)) + { + if (!CreateDirectoryPath(directoryPath)) + { + PhDereferenceObject(directoryPath); + PhDereferenceObject(fullSetupPath); + __leave; + } + + PhDereferenceObject(directoryPath); + } + + PhDereferenceObject(fullSetupPath); + } + + STATUS_MSG(L"Extracting: %s\r\n", SetupFiles_X64[i].ExtractFileName); + Sleep(100); + + // Create output file + if (!NT_SUCCESS(PhCreateFileWin32( + &fileHandle, + extractPath->Buffer, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OVERWRITE_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT + ))) + { + DEBUG_MSG(L"PhCreateFileWin32 Failed\r\n"); + __leave; + } + + file_index = mz_zip_reader_locate_file( + &zip_archive, + SetupFiles_X64[i].FileName, + NULL, + 0 + ); + + if (file_index == -1) + { + __leave; + } + + if (!mz_zip_reader_file_stat(&zip_archive, file_index, &file_stat)) + { + __leave; + } + + PVOID buffer = mz_zip_reader_extract_to_heap( + &zip_archive, + file_index, + &bufferLength, + 0 + ); + + // Update the hash + file_crc32 = mz_crc32(file_crc32, buffer, bufferLength); + + // Write the downloaded bytes to disk. + if (!NT_SUCCESS(NtWriteFile( + fileHandle, + NULL, + NULL, + NULL, + &isb, + buffer, + bufferLength, + NULL, + NULL + ))) + { + __leave; + } + + if (isb.Information != bufferLength && file_crc32 != file_stat.m_crc32) + { + // Corrupted file. + __leave; + } + + totalLength += (mz_uint64)bufferLength; + SetProgress((LONG)totalLength, (LONG)total_size); + + NtClose(fileHandle); + mz_free(buffer); + } + + isInstallSuccess = TRUE; + } + __finally + { + // Close the archive + mz_zip_reader_end(&zip_archive); + } + + Sleep(3000); + + return TRUE; +} \ No newline at end of file diff --git a/tools/ProcessHackerSetup/ProcessHackerSetup/setup/reset.c b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/reset.c new file mode 100644 index 0000000..c69ac1a --- /dev/null +++ b/tools/ProcessHackerSetup/ProcessHackerSetup/setup/reset.c @@ -0,0 +1,168 @@ +#include +#include + +BOOLEAN SetupResetCurrentInstall( + _In_ PVOID Arguments + ) +{ + STATUS_MSG(L"Setting up for first time use..."); + + ProcessHackerShutdown(); + + ULONG err = ERROR_SERVICE_DOES_NOT_EXIST; + + do + { + Sleep(1000); + + err = KphUninstall(); + + } while (err != ERROR_SERVICE_DOES_NOT_EXIST); + + RemoveAppCompatEntries(); + + //PPH_STRING clientPath = GetProcessHackerInstallPath(); + //PPH_STRING startmenu = GetKnownLocation(CSIDL_COMMON_PROGRAMS, TEXT("\\ProcessHacker2")); + //PPH_STRING desktopShortcutString = GetKnownLocation(CSIDL_DESKTOPDIRECTORY, TEXT("\\Process Hacker 2.lnk")); + //PPH_STRING startmenuShortcutString = GetKnownLocation(CSIDL_COMMON_PROGRAMS, TEXT("\\Process Hacker 2.lnk")); + //PPH_STRING startmenuStartupShortcutString = GetKnownLocation(CSIDL_COMMON_PROGRAMS, TEXT("\\Startup\\Process Hacker 2.lnk")); + + + STATUS_MSG(L"Cleaning up...\n"); + Sleep(1000); + + //PPH_STRING clientPathString = StringFormat(TEXT("%s\\ProcessHacker.exe"), SetupInstallPath->Buffer); + //PPH_STRING uninstallPath = StringFormat(TEXT("%s\\ProcessHacker-Setup.exe"), SetupInstallPath->Buffer); + //PPH_STRING clientPathArgsString = StringFormat(TEXT("\"%s\\ProcessHacker.exe\""), SetupInstallPath->Buffer); + //PPH_STRING clientPathExeString = StringFormat(TEXT("\"%s\" \"%%1\""), clientPathString->Buffer); + + // Create the Uninstall key... + + //HKEY keyHandle = RegistryCreateKey( + // HKEY_LOCAL_MACHINE, + // TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ProcessHacker2"), + // KEY_ALL_ACCESS | KEY_WOW64_32KEY + // ); + // + //if (keyHandle) + //{ + // RegistrySetString(keyHandle, TEXT("DisplayName"), TEXT("Process Hacker")); + // RegistrySetString(keyHandle, TEXT("InstallLocation"), SetupInstallPath->Buffer); + // RegistrySetString(keyHandle, TEXT("UninstallString"), uninstallPath->Buffer); + // RegistrySetString(keyHandle, TEXT("DisplayIcon"), uninstallPath->Buffer); + // RegistrySetString(keyHandle, TEXT("Publisher"), TEXT("Electronic Arts, Inc.")); + // RegistrySetString(keyHandle, TEXT("URLInfoAbout"), TEXT("http://wj32.org/ProcessHacker")); + // RegistrySetString(keyHandle, TEXT("HelpLink"), TEXT("http://wj32.org/ProcessHacker")); + // RegistrySetDword(keyHandle, TEXT("NoModify"), 1); + // RegistrySetDword(keyHandle, TEXT("NoRepair"), 1); + // RegCloseKey(keyHandle); + //} + + //if (IsCreateDesktopSelected) + //{ + // PPH_STRING desktopFolderString = GetKnownLocation( + // CSIDL_DESKTOPDIRECTORY, + // TEXT("\\ProcessHacker.lnk") + // ); + + // //if (!CreateLink( + // // desktopFolderString->Buffer, clientPathString->Buffer, SetupInstallPath->Buffer, + // // TEXT("") + // // )) + // //{ + // // DEBUG_MSG(TEXT("ERROR CreateDesktopLink: %u\n"), GetLastError()); + // //} + + // PhFree(desktopFolderString); + //} + + //if (IsCreateStartSelected) + //{ + // PPH_STRING startmenuFolderString = GetKnownLocation( + // CSIDL_COMMON_PROGRAMS, + // TEXT("\\ProcessHacker.lnk") + // ); + + // //if (!CreateLink( + // // startmenuFolderString->Buffer, clientPathString->Buffer, SetupInstallPath->Buffer, + // // TEXT("") + // // )) + // //{ + // // DEBUG_MSG(TEXT("ERROR CreateStartLink: %u\n"), GetLastError()); + // //} + + // PhFree(startmenuFolderString); + //} + + //if (IsCreateRunStartupSelected) + //{ + // PPH_STRING startmenuStartupString = GetKnownLocation( + // CSIDL_COMMON_STARTUP, + // TEXT("\\ProcessHacker.lnk") + // ); + + // //if (!CreateLink( + // // startmenuStartupString->Buffer, clientPathString->Buffer, SetupInstallPath->Buffer, + // // TEXT("") + // // )) + // //{ + // // DEBUG_MSG(TEXT("ERROR CreateLinkStartup: %u\n"), GetLastError()); + // //} + + // PhFree(startmenuStartupString); + //} + + // GetCachedImageIndex Test + //{ + // SHFOLDERCUSTOMSETTINGS fcs = { sizeof(SHFOLDERCUSTOMSETTINGS) }; + // fcs.dwMask = FCSM_ICONFILE; + // fcs.pszIconFile = TEXT("ProcessHacker.exe"); + // fcs.cchIconFile = _tcslen(TEXT("ProcessHacker.exe")) * sizeof(TCHAR); + // fcs.iIconIndex = 0; + + // if (SUCCEEDED(SHGetSetFolderCustomSettings(&fcs, SetupInstallPath->Buffer, FCS_FORCEWRITE))) + // { + // SHFILEINFO shellFolderInfo = { 0 }; + + // if (SHGetFileInfo(SetupInstallPath->Buffer, 0, &shellFolderInfo, sizeof(SHFILEINFO), SHGFI_ICONLOCATION)) + // { + // PPH_STRING fileName = FileGetBaseName(shellFolderInfo.szDisplayName); + + + // INT shellIconIndex = Shell_GetCachedImageIndex( + // fileName->Buffer, + // shellFolderInfo.iIcon, + // 0 + // ); + + // SHUpdateImage( + // fileName->Buffer, + // shellFolderInfo.iIcon, + // 0, + // shellIconIndex + // ); + + // SHChangeNotify(SHCNE_UPDATEIMAGE, SHCNF_DWORD, NULL, (PVOID)shellIconIndex); + + // PhFree(fileName); + // } + // } + //} + + //_tspawnl(_P_DETACH, clientPathString->Buffer, clientPathArgsString->Buffer, NULL); + + //if (clientPathExeString) + // PhFree(clientPathExeString); + + //if (clientPathArgsString) + // PhFree(clientPathArgsString); + + //if (uninstallPath) + // PhFree(uninstallPath); + + //if (clientPathString) + // PhFree(clientPathString); + + + return TRUE; +} diff --git a/tools/UpdateGitRevision/UpdateGitRevision.sln b/tools/UpdateGitRevision/UpdateGitRevision.sln new file mode 100644 index 0000000..7f77fb9 --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateGitRevision", "UpdateGitRevision\UpdateGitRevision.csproj", "{DBD4656C-5F40-4067-A70B-C4460DE20F77}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DBD4656C-5F40-4067-A70B-C4460DE20F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBD4656C-5F40-4067-A70B-C4460DE20F77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBD4656C-5F40-4067-A70B-C4460DE20F77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBD4656C-5F40-4067-A70B-C4460DE20F77}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tools/UpdateGitRevision/UpdateGitRevision/App.config b/tools/UpdateGitRevision/UpdateGitRevision/App.config new file mode 100644 index 0000000..b2750aa --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/tools/UpdateGitRevision/UpdateGitRevision/CommandLineRunner.cs b/tools/UpdateGitRevision/UpdateGitRevision/CommandLineRunner.cs new file mode 100644 index 0000000..1618c13 --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/CommandLineRunner.cs @@ -0,0 +1,43 @@ +using System.Diagnostics; + +namespace UpdateRevision +{ + public class CommandLineRunner + { + public string Result { get; set; } + + public bool RunCommandAndGetOutput(string command, string arguments, string workingFolder) + { + var p = new Process(); + p.StartInfo.FileName = command; + p.StartInfo.Arguments = arguments; + p.StartInfo.WorkingDirectory = workingFolder; + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardInput = true; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.CreateNoWindow = true; + p.OutputDataReceived += OutputDataReceived; + + try + { + p.Start(); + } + catch + { + return false; + } + p.BeginOutputReadLine(); // Async reading of output to prevent deadlock + if (p.WaitForExit(5000)) + { + return p.ExitCode == 0; + } + return false; + } + + private void OutputDataReceived(object sender, DataReceivedEventArgs e) + { + if (e != null && e.Data != null) + Result = e.Data; + } + } +} diff --git a/tools/UpdateGitRevision/UpdateGitRevision/Program.cs b/tools/UpdateGitRevision/UpdateGitRevision/Program.cs new file mode 100644 index 0000000..308615c --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/Program.cs @@ -0,0 +1,259 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace UpdateRevision +{ + internal class Program + { + private static readonly Regex LongGitTagRegex; // e.g.: v3.4-226-g7037fef + private static readonly Regex LongVersionRegex; // e.g.: v3.4.226 + private static readonly Regex ShortVersionRegex; // e.g.: v3.4 (w/o commits) + static Program() + { + var options = RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant; + LongGitTagRegex = new Regex(@"\Av(?[0-9]+)\.(?[0-9]+)-(?[0-9]+)-g[0-9a-z]+\z", options); + LongVersionRegex = new Regex(@"\Av(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)\z", options); + ShortVersionRegex = new Regex(@"\A(?[0-9]+)\.(?[0-9]+)\z", options); + options |= RegexOptions.Multiline; + } + + private const int UnknownBuild = 9999; + + private class VersionInfo : IComparable, IEquatable + { + public int Major { get; private set; } + public int Minor { get; private set; } + public int Commits { get; private set; } + public string RevisionGuid { get; private set; } + + public VersionInfo() + { + RevisionGuid = string.Empty; + } + + public VersionInfo(string version, string guid = null) + { + var match = LongGitTagRegex.Match(version); + if (!match.Success) + match = LongVersionRegex.Match(version); + if (!match.Success || string.IsNullOrWhiteSpace(guid)) + { + Commits = UnknownBuild; + RevisionGuid = string.Empty; + } + else + { + Commits = int.Parse(match.Groups["commits"].Value, NumberStyles.None, CultureInfo.InvariantCulture); + RevisionGuid = guid.Trim().ToLowerInvariant(); + } + if (!match.Success) + match = ShortVersionRegex.Match(version); + if (!match.Success) + throw new ArgumentException("Invalid version identifier: '" + version + "'"); + Major = int.Parse(match.Groups["major"].Value, NumberStyles.None, CultureInfo.InvariantCulture); + Minor = int.Parse(match.Groups["minor"].Value, NumberStyles.None, CultureInfo.InvariantCulture); + } + + public int CompareTo(VersionInfo vi) + { + int cmp = 1; + if (!object.ReferenceEquals(vi, null)) + { + cmp = Major.CompareTo(vi.Major); + if (cmp == 0) + cmp = Minor.CompareTo(vi.Minor); + } + return cmp; + } + + public bool Equals(VersionInfo vi) + { + return object.ReferenceEquals(vi, null) ? false : Major.Equals(vi.Major) && Minor.Equals(vi.Minor) && Commits.Equals(vi.Commits) && RevisionGuid.Equals(vi.RevisionGuid, StringComparison.Ordinal); + } + + public override bool Equals(object obj) + { + return Equals(obj as VersionInfo); + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0:D}.{1:D}+{2:D}: {3}", Major, Minor, Commits, RevisionGuid); + } + + public override int GetHashCode() + { + return this.ToString().GetHashCode(); + } + + public static bool operator ==(VersionInfo vi1, VersionInfo vi2) + { + return object.ReferenceEquals(vi2, null) ? object.ReferenceEquals(vi1, null) : vi2.Equals(vi1); + } + + public static bool operator !=(VersionInfo vi1, VersionInfo vi2) + { + return object.ReferenceEquals(vi2, null) ? !object.ReferenceEquals(vi1, null) : !vi2.Equals(vi1); + } + } + + private static void GetRepositoryVersions(out VersionInfo currentRepositoryVersion, out VersionInfo latestRepositoryVersion) + { + var workingDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); + var clrHash = new CommandLineRunner(); + var clrTags = new CommandLineRunner(); + var gitPath = GetGitPath(); + if (clrHash.RunCommandAndGetOutput(gitPath, "rev-parse --verify HEAD", workingDirectory) && clrTags.RunCommandAndGetOutput(gitPath, "describe --long --tags", workingDirectory)) + { + if (!LongGitTagRegex.IsMatch(clrTags.Result)) + { + throw new Exception("Invalid Git version tag: '" + clrTags.Result + "' (vmajor.minor-build expected)"); + } + currentRepositoryVersion = new VersionInfo(clrTags.Result, clrHash.Result); + } + else + { + currentRepositoryVersion = new VersionInfo(); // no git repository + } + if (clrHash.RunCommandAndGetOutput(gitPath, "rev-parse --verify refs/heads/master", workingDirectory) && clrTags.RunCommandAndGetOutput(gitPath, "describe --long --tags refs/heads/master", workingDirectory)) + { + if (!LongGitTagRegex.IsMatch(clrTags.Result)) + { + throw new Exception("Invalid Git version tag: '" + clrTags.Result + "' (vmajor.minor-build expected)"); + } + latestRepositoryVersion = new VersionInfo(clrTags.Result, clrHash.Result); + } + else + { + latestRepositoryVersion = new VersionInfo(); // no git repository + } + } + + private static void WriteWarning(string message) + { + Console.WriteLine(); + Console.WriteLine("WARNING: " + message); + } + + private static int Main(string[] args) + { + var myName = Environment.GetCommandLineArgs()[0]; + myName = Path.GetFileNameWithoutExtension(string.IsNullOrWhiteSpace(myName) ? System.Reflection.Assembly.GetEntryAssembly().Location : myName); + if (args.Length != 2) + { + Console.WriteLine("Usage: " + myName + " input-file output-file"); + return 1; + } + + try + { + var inputFileName = Environment.GetCommandLineArgs()[1]; + var outputFileName = Environment.GetCommandLineArgs()[2]; + VersionInfo currentRepositoryVersion; + VersionInfo latestRepositoryVersion; + + GetRepositoryVersions(out currentRepositoryVersion, out latestRepositoryVersion); + + Console.WriteLine("Current version: " + currentRepositoryVersion.ToString()); + Console.WriteLine("Latest version: " + latestRepositoryVersion.ToString()); + + var template = File.ReadAllText(inputFileName); + var output = template.Replace("$COMMITS$", currentRepositoryVersion.Commits.ToString()); + + if (!File.Exists(outputFileName) || File.ReadAllText(outputFileName) != output) + File.WriteAllText(outputFileName, output); + + return 0; + } + catch (Exception exception) + { + Console.WriteLine(); + Console.WriteLine("ERROR: " + myName + ": " + exception.Message + Environment.NewLine + exception.StackTrace); + + return 2; + } + } + + private static string GetGitPath() + { + var envPath = Environment.GetEnvironmentVariable("PATH"); + if (!string.IsNullOrWhiteSpace(envPath)) + { + foreach (var p in envPath.Split(Path.PathSeparator)) + { + var path = Path.Combine(p, "git.exe"); + if (File.Exists(path)) + return path; + } + } + + var gitPath = Path.Combine("Git", "bin", "git.exe"); + + var envProgramFiles = Environment.GetEnvironmentVariable("ProgramFiles"); + if (!string.IsNullOrWhiteSpace(envProgramFiles)) + { + var path = Path.Combine(envProgramFiles, gitPath); + if (File.Exists(path)) + return path; + } + + envProgramFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)"); + if (!string.IsNullOrWhiteSpace(envProgramFiles)) + { + var path = Path.Combine(envProgramFiles, gitPath); + if (File.Exists(path)) + return path; + } + + var envSystemDrive = Environment.GetEnvironmentVariable("SystemDrive"); + if (string.IsNullOrEmpty(envSystemDrive)) + { + WriteWarning(@"Environment.GetEnvironmentVariable(""SystemDrive"") returned null!"); + } + if (!string.IsNullOrWhiteSpace(envSystemDrive)) + { + var path = Path.Combine(envSystemDrive, "Program Files", gitPath); + if (File.Exists(path)) + return path; + + path = Path.Combine(envSystemDrive, "Program Files (x86)", gitPath); + if (File.Exists(path)) + return path; + + path = Path.Combine(envSystemDrive, gitPath); + if (File.Exists(path)) + return path; + } + + try + { + var cRoot = new DriveInfo("C").RootDirectory.FullName; + if (string.IsNullOrWhiteSpace(envSystemDrive) || !cRoot.StartsWith(envSystemDrive, StringComparison.OrdinalIgnoreCase)) + { + var path = Path.Combine(cRoot, "Program Files", gitPath); + if (File.Exists(path)) + return path; + + path = Path.Combine(cRoot, "Program Files (x86)", gitPath); + if (File.Exists(path)) + return path; + + path = Path.Combine(cRoot, gitPath); + if (File.Exists(path)) + return path; + } + } + catch (Exception exception) + { + WriteWarning(@"System.IO.DriveInfo(""C"") exception: " + exception.Message); + } + + WriteWarning("Might not be able to run Git command line tool!"); + return "git"; + } + + } +} diff --git a/tools/UpdateGitRevision/UpdateGitRevision/Properties/AssemblyInfo.cs b/tools/UpdateGitRevision/UpdateGitRevision/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8792338 --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UpdateAssemblyInfo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UpdateAssemblyInfo")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("07219928-b0da-484d-a7de-b3d9470e2e57")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tools/UpdateGitRevision/UpdateGitRevision/README.md b/tools/UpdateGitRevision/UpdateGitRevision/README.md new file mode 100644 index 0000000..1574289 --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/README.md @@ -0,0 +1 @@ +Based on https://github.com/SubtitleEdit/subtitleedit/tree/master/src/UpdateAssemblyInfo \ No newline at end of file diff --git a/tools/UpdateGitRevision/UpdateGitRevision/UpdateGitRevision.csproj b/tools/UpdateGitRevision/UpdateGitRevision/UpdateGitRevision.csproj new file mode 100644 index 0000000..2f61f48 --- /dev/null +++ b/tools/UpdateGitRevision/UpdateGitRevision/UpdateGitRevision.csproj @@ -0,0 +1,56 @@ + + + + + Properties + UpdateGitRevision + Debug + 512 + Exe + AnyCPU + {DBD4656C-5F40-4067-A70B-C4460DE20F77} + UpdateGitRevision + Client + v4.0 + + + AllRules.ruleset + true + full + DEBUG;TRACE + prompt + false + bin\Debug\ + AnyCPU + 4 + + + AllRules.ruleset + pdbonly + TRACE + prompt + true + bin\Release\ + AnyCPU + 4 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/UpdateGitRevision/UpdateGitRevision/bin/Release/UpdateGitRevision.exe b/tools/UpdateGitRevision/UpdateGitRevision/bin/Release/UpdateGitRevision.exe new file mode 100644 index 0000000..186fcc0 Binary files /dev/null and b/tools/UpdateGitRevision/UpdateGitRevision/bin/Release/UpdateGitRevision.exe differ diff --git a/tools/fixlib/bin/Release/fixlib.exe b/tools/fixlib/bin/Release/fixlib.exe new file mode 100644 index 0000000..64d5533 Binary files /dev/null and b/tools/fixlib/bin/Release/fixlib.exe differ diff --git a/tools/fixlib/fixlib.c b/tools/fixlib/fixlib.c new file mode 100644 index 0000000..abb2688 --- /dev/null +++ b/tools/fixlib/fixlib.c @@ -0,0 +1,113 @@ +#include +#include + +#define ARG_OUTFILE 1 + +PPH_STRING inFile = NULL; +PPH_STRING outFile = NULL; + +BOOLEAN NTAPI CommandLineCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + if (Option) + { + switch (Option->Id) + { + case ARG_OUTFILE: + { + PhSwapReference(&outFile, Value); + } + break; + } + } + else + { + PhSwapReference(&inFile, Value); + } + + return TRUE; +} + +int __cdecl main(int argc, char *argv[]) +{ + static PH_COMMAND_LINE_OPTION options[] = + { + { ARG_OUTFILE, L"o", MandatoryArgumentType } + }; + NTSTATUS status; + PH_STRINGREF commandLine; + PH_MAPPED_ARCHIVE mappedArchive; + PH_MAPPED_ARCHIVE_MEMBER member; + PH_MAPPED_ARCHIVE_IMPORT_ENTRY entry; + + if (!NT_SUCCESS(PhInitializePhLib())) + return 1; + + PhUnicodeStringToStringRef(&NtCurrentPeb()->ProcessParameters->CommandLine, &commandLine); + + if (!PhParseCommandLine( + &commandLine, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_FIRST_PART, + CommandLineCallback, + NULL + ) || !inFile) + { + wprintf(L"Usage: fixlib [-o outfile] infile\n"); + return 1; + } + + if (!outFile) + outFile = inFile; + + CopyFile(inFile->Buffer, outFile->Buffer, FALSE); + + status = PhLoadMappedArchive(outFile->Buffer, NULL, FALSE, &mappedArchive); + + if (!NT_SUCCESS(status)) + { + wprintf(L"Error: %s\n", PhGetStringOrDefault(PhGetNtMessage(status), L"unknown")); + return status; + } + + member = *(mappedArchive.LastStandardMember); + + while (NT_SUCCESS(PhGetNextMappedArchiveMember(&member, &member))) + { + if (NT_SUCCESS(PhGetMappedArchiveImportEntry(&member, &entry))) + { + IMPORT_OBJECT_HEADER *header; + PWSTR type = L"unknown"; + + switch (entry.NameType) + { + case IMPORT_OBJECT_ORDINAL: + type = L"ordinal"; + break; + case IMPORT_OBJECT_NAME: + type = L"name"; + break; + case IMPORT_OBJECT_NAME_NO_PREFIX: + type = L"name-noprefix"; + break; + case IMPORT_OBJECT_NAME_UNDECORATE: + type = L"name-undecorate"; + break; + } + + wprintf(L"%S: %S (%s)\n", entry.DllName, entry.Name, type); + + header = (IMPORT_OBJECT_HEADER *)member.Data; + + // Changes + + header->NameType = IMPORT_OBJECT_NAME_UNDECORATE; + } + } + + PhUnloadMappedArchive(&mappedArchive); +} diff --git a/tools/fixlib/fixlib.vcxproj b/tools/fixlib/fixlib.vcxproj new file mode 100644 index 0000000..0368097 --- /dev/null +++ b/tools/fixlib/fixlib.vcxproj @@ -0,0 +1,109 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {31F4AA06-7ED5-4A6D-B901-19AD4BD16175} + fixlib + Win32Proj + 10.0.10586.0 + + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + + + + + + + + + + + $(ProjectDir)bin\$(Configuration)\ + $(ProjectDir)obj\$(Configuration)\ + true + $(ProjectDir)bin\$(Configuration)\ + $(ProjectDir)obj\$(Configuration)\ + false + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;%(AdditionalIncludeDirectories) + _PHLIB_;_CONSOLE;WIN32;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + true + Console + MachineX86 + 5.01 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;%(AdditionalIncludeDirectories) + _PHLIB_;_CONSOLE;WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + true + Console + true + true + MachineX86 + true + 5.01 + + + + + + + + {477d0215-f252-41a1-874b-f27e3ea1ed17} + false + + + + + + \ No newline at end of file diff --git a/tools/fixlib/fixlib.vcxproj.filters b/tools/fixlib/fixlib.vcxproj.filters new file mode 100644 index 0000000..33bee46 --- /dev/null +++ b/tools/fixlib/fixlib.vcxproj.filters @@ -0,0 +1,14 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + \ No newline at end of file diff --git a/tools/peview/include/peview.h b/tools/peview/include/peview.h new file mode 100644 index 0000000..3e1634d --- /dev/null +++ b/tools/peview/include/peview.h @@ -0,0 +1,38 @@ +#ifndef PEVIEW_H +#define PEVIEW_H + +#include +#include +#include +#include "resource.h" + +extern PPH_STRING PvFileName; + +// peprp + +VOID PvPeProperties( + VOID + ); + +// libprp + +VOID PvLibProperties( + VOID + ); + +// misc + +PPH_STRING PvResolveShortcutTarget( + _In_ PPH_STRING ShortcutFileName + ); + +VOID PvCopyListView( + _In_ HWND ListViewHandle + ); + +VOID PvHandleListViewNotifyForCopy( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle + ); + +#endif diff --git a/tools/peview/libprp.c b/tools/peview/libprp.c new file mode 100644 index 0000000..6597516 --- /dev/null +++ b/tools/peview/libprp.c @@ -0,0 +1,181 @@ +/* + * Process Hacker - + * PE viewer + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include + +INT_PTR CALLBACK PvpLibExportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PH_MAPPED_ARCHIVE PvMappedArchive; + +VOID PvLibProperties( + VOID + ) +{ + NTSTATUS status; + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[1]; + + status = PhLoadMappedArchive(PvFileName->Buffer, NULL, TRUE, &PvMappedArchive); + + if (!NT_SUCCESS(status)) + { + PhShowStatus(NULL, L"Unable to load the archive file", status, 0); + return; + } + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = NULL; + propSheetHeader.pszCaption = PvFileName->Buffer; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // Exports page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_LIBEXPORTS); + propSheetPage.pfnDlgProc = PvpLibExportsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + PropertySheet(&propSheetHeader); + + PhUnloadMappedArchive(&PvMappedArchive); +} + +INT_PTR CALLBACK PvpLibExportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG fallbackColumns[] = { 0, 1, 2, 3 }; + HWND lvHandle; + PH_MAPPED_ARCHIVE_MEMBER member; + PH_MAPPED_ARCHIVE_IMPORT_ENTRY importEntry; + + PhCenterWindow(GetParent(hwndDlg), NULL); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 60, L"DLL"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 200, L"Name"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 40, L"Ordinal/Hint"); + PhAddListViewColumn(lvHandle, 3, 3, 3, LVCFMT_LEFT, 40, L"Type"); + PhAddListViewColumn(lvHandle, 4, 4, 4, LVCFMT_LEFT, 60, L"Name type"); + PhSetExtendedListView(lvHandle); + ExtendedListView_AddFallbackColumns(lvHandle, 4, fallbackColumns); + + member = *PvMappedArchive.LastStandardMember; + + while (NT_SUCCESS(PhGetNextMappedArchiveMember(&member, &member))) + { + if (NT_SUCCESS(PhGetMappedArchiveImportEntry(&member, &importEntry))) + { + INT lvItemIndex; + PPH_STRING name; + WCHAR number[PH_INT32_STR_LEN_1]; + PWSTR type; + + name = PhZeroExtendToUtf16(importEntry.DllName); + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, name->Buffer, NULL); + PhDereferenceObject(name); + + name = PhZeroExtendToUtf16(importEntry.Name); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, name->Buffer); + PhDereferenceObject(name); + + // Ordinal is unioned with NameHint, so this works both ways. + PhPrintUInt32(number, importEntry.Ordinal); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, number); + + switch (importEntry.Type) + { + case IMPORT_OBJECT_CODE: + type = L"Code"; + break; + case IMPORT_OBJECT_DATA: + type = L"Data"; + break; + case IMPORT_OBJECT_CONST: + type = L"Const"; + break; + default: + type = L"Unknown"; + break; + } + + PhSetListViewSubItem(lvHandle, lvItemIndex, 3, type); + + switch (importEntry.NameType) + { + case IMPORT_OBJECT_ORDINAL: + type = L"Ordinal"; + break; + case IMPORT_OBJECT_NAME: + type = L"Name"; + break; + case IMPORT_OBJECT_NAME_NO_PREFIX: + type = L"Name, no prefix"; + break; + case IMPORT_OBJECT_NAME_UNDECORATE: + type = L"Name, undecorate"; + break; + default: + type = L"Unknown"; + break; + } + + PhSetListViewSubItem(lvHandle, lvItemIndex, 4, type); + } + } + + ExtendedListView_SortItems(lvHandle); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_NOTIFY: + { + PvHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} diff --git a/tools/peview/main.c b/tools/peview/main.c new file mode 100644 index 0000000..4ec4abe --- /dev/null +++ b/tools/peview/main.c @@ -0,0 +1,127 @@ +/* + * Process Hacker - + * PE viewer + * + * Copyright (C) 2010 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include + +PPH_STRING PvFileName = NULL; + +static BOOLEAN NTAPI PvCommandLineCallback( + _In_opt_ PPH_COMMAND_LINE_OPTION Option, + _In_opt_ PPH_STRING Value, + _In_opt_ PVOID Context + ) +{ + if (!Option) + PhSwapReference(&PvFileName, Value); + + return TRUE; +} + +static VOID PvpInitializeDpi( + VOID + ) +{ + HDC hdc; + + if (hdc = GetDC(NULL)) + { + PhGlobalDpi = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } +} + +INT WINAPI wWinMain( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ PWSTR lpCmdLine, + _In_ INT nCmdShow + ) +{ + static PH_COMMAND_LINE_OPTION options[] = + { + { 0, L"h", NoArgumentType } + }; + PH_STRINGREF commandLine; + + if (!NT_SUCCESS(PhInitializePhLibEx(0, 0, 0))) + return 1; + + PhGuiSupportInitialization(); + PvpInitializeDpi(); + + PhApplicationName = L"PE Viewer"; + + PhUnicodeStringToStringRef(&NtCurrentPeb()->ProcessParameters->CommandLine, &commandLine); + + PhParseCommandLine( + &commandLine, + options, + sizeof(options) / sizeof(PH_COMMAND_LINE_OPTION), + PH_COMMAND_LINE_IGNORE_FIRST_PART, + PvCommandLineCallback, + NULL + ); + + if (!PvFileName) + { + static PH_FILETYPE_FILTER filters[] = + { + { L"Supported files (*.exe;*.dll;*.ocx;*.sys;*.scr;*.cpl;*.ax;*.acm;*.lib;*.winmd;*.efi)", L"*.exe;*.dll;*.ocx;*.sys;*.scr;*.cpl;*.ax;*.acm;*.lib;*.winmd;*.efi" }, + { L"All files (*.*)", L"*.*" } + }; + PVOID fileDialog; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + fileDialog = PhCreateOpenFileDialog(); + PhSetFileDialogFilter(fileDialog, filters, sizeof(filters) / sizeof(PH_FILETYPE_FILTER)); + + if (PhShowFileDialog(NULL, fileDialog)) + { + PvFileName = PhGetFileDialogFileName(fileDialog); + } + + PhFreeFileDialog(fileDialog); + } + + if (!PvFileName) + return 1; + + if (PhEndsWithString2(PvFileName, L".lnk", TRUE)) + { + PPH_STRING targetFileName; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + targetFileName = PvResolveShortcutTarget(PvFileName); + + if (targetFileName) + PhMoveReference(&PvFileName, targetFileName); + } + + if (!PhEndsWithString2(PvFileName, L".lib", TRUE)) + PvPeProperties(); + else + PvLibProperties(); + + return 0; +} diff --git a/tools/peview/misc.c b/tools/peview/misc.c new file mode 100644 index 0000000..fc036c8 --- /dev/null +++ b/tools/peview/misc.c @@ -0,0 +1,102 @@ +/* + * Process Hacker - + * PE viewer + * + * Copyright (C) 2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#define CINTERFACE +#define COBJMACROS +#include +#include +#undef CINTERFACE +#undef COBJMACROS +#include + +static GUID CLSID_ShellLink_I = { 0x00021401, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; +static GUID IID_IShellLinkW_I = { 0x000214f9, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; +static GUID IID_IPersistFile_I = { 0x0000010b, 0x0000, 0x0000, { 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; + +PPH_STRING PvResolveShortcutTarget( + _In_ PPH_STRING ShortcutFileName + ) +{ + PPH_STRING targetFileName; + IShellLinkW *shellLink; + IPersistFile *persistFile; + WCHAR path[MAX_PATH]; + + targetFileName = NULL; + + if (SUCCEEDED(CoCreateInstance(&CLSID_ShellLink_I, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW_I, &shellLink))) + { + if (SUCCEEDED(IShellLinkW_QueryInterface(shellLink, &IID_IPersistFile_I, &persistFile))) + { + if (SUCCEEDED(IPersistFile_Load(persistFile, ShortcutFileName->Buffer, STGM_READ)) && + SUCCEEDED(IShellLinkW_Resolve(shellLink, NULL, SLR_NO_UI))) + { + if (SUCCEEDED(IShellLinkW_GetPath(shellLink, path, MAX_PATH, NULL, 0))) + { + targetFileName = PhCreateString(path); + } + } + + IPersistFile_Release(persistFile); + } + + IShellLinkW_Release(shellLink); + } + + return targetFileName; +} + +// Copied from appsup.c + +VOID PvCopyListView( + _In_ HWND ListViewHandle + ) +{ + PPH_STRING text; + + text = PhGetListViewText(ListViewHandle); + PhSetClipboardString(ListViewHandle, &text->sr); + PhDereferenceObject(text); +} + +VOID PvHandleListViewNotifyForCopy( + _In_ LPARAM lParam, + _In_ HWND ListViewHandle + ) +{ + if (((LPNMHDR)lParam)->hwndFrom == ListViewHandle && ((LPNMHDR)lParam)->code == LVN_KEYDOWN) + { + LPNMLVKEYDOWN keyDown = (LPNMLVKEYDOWN)lParam; + + switch (keyDown->wVKey) + { + case 'C': + if (GetKeyState(VK_CONTROL) < 0) + PvCopyListView(ListViewHandle); + break; + case 'A': + if (GetKeyState(VK_CONTROL) < 0) + PhSetStateAllListViewItems(ListViewHandle, LVIS_SELECTED, LVIS_SELECTED); + break; + } + } +} diff --git a/tools/peview/peprp.c b/tools/peview/peprp.c new file mode 100644 index 0000000..193b322 --- /dev/null +++ b/tools/peview/peprp.c @@ -0,0 +1,1003 @@ +/* + * Process Hacker - + * PE viewer + * + * Copyright (C) 2010-2011 wj32 + * + * This file is part of Process Hacker. + * + * Process Hacker is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Process Hacker is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Process Hacker. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PVM_CHECKSUM_DONE (WM_APP + 1) +#define PVM_VERIFY_DONE (WM_APP + 2) + +INT_PTR CALLBACK PvpPeGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PvpPeImportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PvpPeExportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PvpPeLoadConfigDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +INT_PTR CALLBACK PvpPeClrDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ); + +PH_MAPPED_IMAGE PvMappedImage; +PIMAGE_COR20_HEADER PvImageCor20Header; + +HICON PvImageLargeIcon; +PH_IMAGE_VERSION_INFO PvImageVersionInfo; +VERIFY_RESULT PvImageVerifyResult; +PPH_STRING PvImageSignerName; + +VOID PvPeProperties( + VOID + ) +{ + NTSTATUS status; + PROPSHEETHEADER propSheetHeader = { sizeof(propSheetHeader) }; + PROPSHEETPAGE propSheetPage; + HPROPSHEETPAGE pages[5]; + PH_MAPPED_IMAGE_IMPORTS imports; + PH_MAPPED_IMAGE_EXPORTS exports; + PIMAGE_DATA_DIRECTORY entry; + + status = PhLoadMappedImage(PvFileName->Buffer, NULL, TRUE, &PvMappedImage); + + if (!NT_SUCCESS(status)) + { + PhShowStatus(NULL, L"Unable to load the PE file", status, 0); + return; + } + + propSheetHeader.dwFlags = + PSH_NOAPPLYNOW | + PSH_NOCONTEXTHELP | + PSH_PROPTITLE; + propSheetHeader.hwndParent = NULL; + propSheetHeader.pszCaption = PvFileName->Buffer; + propSheetHeader.nPages = 0; + propSheetHeader.nStartPage = 0; + propSheetHeader.phpage = pages; + + // General page + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PEGENERAL); + propSheetPage.pfnDlgProc = PvpPeGeneralDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + + // Imports page + if ((NT_SUCCESS(PhGetMappedImageImports(&imports, &PvMappedImage)) && imports.NumberOfDlls != 0) || + (NT_SUCCESS(PhGetMappedImageDelayImports(&imports, &PvMappedImage)) && imports.NumberOfDlls != 0)) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PEIMPORTS); + propSheetPage.pfnDlgProc = PvpPeImportsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Exports page + if (NT_SUCCESS(PhGetMappedImageExports(&exports, &PvMappedImage)) && exports.NumberOfEntries != 0) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PEEXPORTS); + propSheetPage.pfnDlgProc = PvpPeExportsDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // Load Config page + if (NT_SUCCESS(PhGetMappedImageDataEntry(&PvMappedImage, IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, &entry)) && entry->VirtualAddress) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PELOADCONFIG); + propSheetPage.pfnDlgProc = PvpPeLoadConfigDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + + // CLR page + if (NT_SUCCESS(PhGetMappedImageDataEntry(&PvMappedImage, IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, &entry)) && + entry->VirtualAddress && + (PvImageCor20Header = PhMappedImageRvaToVa(&PvMappedImage, entry->VirtualAddress, NULL))) + { + status = STATUS_SUCCESS; + + __try + { + PhProbeAddress(PvImageCor20Header, sizeof(IMAGE_COR20_HEADER), + PvMappedImage.ViewBase, PvMappedImage.Size, 4); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + status = GetExceptionCode(); + } + + if (NT_SUCCESS(status)) + { + memset(&propSheetPage, 0, sizeof(PROPSHEETPAGE)); + propSheetPage.dwSize = sizeof(PROPSHEETPAGE); + propSheetPage.pszTemplate = MAKEINTRESOURCE(IDD_PECLR); + propSheetPage.pfnDlgProc = PvpPeClrDlgProc; + pages[propSheetHeader.nPages++] = CreatePropertySheetPage(&propSheetPage); + } + } + + PropertySheet(&propSheetHeader); + + PhUnloadMappedImage(&PvMappedImage); +} + +static NTSTATUS CheckSumImageThreadStart( + _In_ PVOID Parameter + ) +{ + HWND windowHandle; + ULONG checkSum; + + windowHandle = Parameter; + checkSum = PhCheckSumMappedImage(&PvMappedImage); + + PostMessage( + windowHandle, + PVM_CHECKSUM_DONE, + checkSum, + 0 + ); + + return STATUS_SUCCESS; +} + +VERIFY_RESULT PvpVerifyFileWithAdditionalCatalog( + _In_ PPH_STRING FileName, + _In_ ULONG Flags, + _In_opt_ HWND hWnd, + _Out_opt_ PPH_STRING *SignerName + ) +{ + static PH_STRINGREF codeIntegrityFileName = PH_STRINGREF_INIT(L"\\AppxMetadata\\CodeIntegrity.cat"); + + VERIFY_RESULT result; + PH_VERIFY_FILE_INFO info; + PPH_STRING windowsAppsPath; + PPH_STRING additionalCatalogFileName = NULL; + PCERT_CONTEXT *signatures; + ULONG numberOfSignatures; + + memset(&info, 0, sizeof(PH_VERIFY_FILE_INFO)); + info.FileName = FileName->Buffer; + info.Flags = Flags; + info.hWnd = hWnd; + + windowsAppsPath = PhGetKnownLocation(CSIDL_PROGRAM_FILES, L"\\WindowsApps\\"); + + if (windowsAppsPath) + { + if (PhStartsWithStringRef(&FileName->sr, &windowsAppsPath->sr, TRUE)) + { + PH_STRINGREF remainingFileName; + ULONG_PTR indexOfBackslash; + PH_STRINGREF baseFileName; + + remainingFileName = FileName->sr; + PhSkipStringRef(&remainingFileName, windowsAppsPath->Length); + indexOfBackslash = PhFindCharInStringRef(&remainingFileName, '\\', FALSE); + + if (indexOfBackslash != -1) + { + baseFileName.Buffer = FileName->Buffer; + baseFileName.Length = windowsAppsPath->Length + indexOfBackslash * sizeof(WCHAR); + additionalCatalogFileName = PhConcatStringRef2(&baseFileName, &codeIntegrityFileName); + } + } + + PhDereferenceObject(windowsAppsPath); + } + + if (additionalCatalogFileName) + { + info.NumberOfCatalogFileNames = 1; + info.CatalogFileNames = &additionalCatalogFileName->Buffer; + } + + if (!NT_SUCCESS(PhVerifyFileEx(&info, &result, &signatures, &numberOfSignatures))) + { + result = VrNoSignature; + signatures = NULL; + numberOfSignatures = 0; + } + + if (additionalCatalogFileName) + PhDereferenceObject(additionalCatalogFileName); + + if (SignerName) + { + if (numberOfSignatures != 0) + *SignerName = PhGetSignerNameFromCertificate(signatures[0]); + else + *SignerName = NULL; + } + + PhFreeVerifySignatures(signatures, numberOfSignatures); + + return result; +} + +static NTSTATUS VerifyImageThreadStart( + _In_ PVOID Parameter + ) +{ + HWND windowHandle; + + windowHandle = Parameter; + PvImageVerifyResult = PvpVerifyFileWithAdditionalCatalog(PvFileName, PH_VERIFY_PREVENT_NETWORK_ACCESS, NULL, &PvImageSignerName); + PostMessage(windowHandle, PVM_VERIFY_DONE, 0, 0); + + return STATUS_SUCCESS; +} + +FORCEINLINE PWSTR PvpGetStringOrNa( + _In_ PPH_STRING String + ) +{ + if (String) + return String->Buffer; + else + return L"N/A"; +} + +INT_PTR CALLBACK PvpPeGeneralDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + ULONG i; + PPH_STRING string; + PWSTR type; + PH_STRING_BUILDER stringBuilder; + + PhCenterWindow(GetParent(hwndDlg), NULL); + + // File version information + + { + PhInitializeImageVersionInfo(&PvImageVersionInfo, PvFileName->Buffer); + + if (ExtractIconEx( + PvFileName->Buffer, + 0, + &PvImageLargeIcon, + NULL, + 1 + ) == 0) + { + PvImageLargeIcon = PhGetFileShellIcon(PvFileName->Buffer, NULL, TRUE); + } + + SendMessage(GetDlgItem(hwndDlg, IDC_FILEICON), STM_SETICON, (WPARAM)PvImageLargeIcon, 0); + + SetDlgItemText(hwndDlg, IDC_NAME, PvpGetStringOrNa(PvImageVersionInfo.FileDescription)); + string = PhConcatStrings2(L"(Verifying...) ", PvpGetStringOrNa(PvImageVersionInfo.CompanyName)); + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, string->Buffer); + PhDereferenceObject(string); + SetDlgItemText(hwndDlg, IDC_VERSION, PvpGetStringOrNa(PvImageVersionInfo.FileVersion)); + + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), VerifyImageThreadStart, hwndDlg); + } + + // PE properties + + switch (PvMappedImage.NtHeaders->FileHeader.Machine) + { + case IMAGE_FILE_MACHINE_I386: + type = L"i386"; + break; + case IMAGE_FILE_MACHINE_AMD64: + type = L"AMD64"; + break; + case IMAGE_FILE_MACHINE_IA64: + type = L"IA64"; + break; + case IMAGE_FILE_MACHINE_ARMNT: + type = L"ARM Thumb-2"; + break; + default: + type = L"Unknown"; + break; + } + + SetDlgItemText(hwndDlg, IDC_TARGETMACHINE, type); + + { + LARGE_INTEGER time; + SYSTEMTIME systemTime; + + RtlSecondsSince1970ToTime(PvMappedImage.NtHeaders->FileHeader.TimeDateStamp, &time); + PhLargeIntegerToLocalSystemTime(&systemTime, &time); + + string = PhFormatDateTime(&systemTime); + SetDlgItemText(hwndDlg, IDC_TIMESTAMP, string->Buffer); + PhDereferenceObject(string); + } + + if (PvMappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + string = PhFormatString(L"0x%Ix", PvMappedImage.NtHeaders->OptionalHeader.ImageBase); + } + else + { + string = PhFormatString(L"0x%I64x", ((PIMAGE_OPTIONAL_HEADER64)&PvMappedImage.NtHeaders->OptionalHeader)->ImageBase); + } + + SetDlgItemText(hwndDlg, IDC_IMAGEBASE, string->Buffer); + PhDereferenceObject(string); + + string = PhFormatString(L"0x%Ix (verifying...)", PvMappedImage.NtHeaders->OptionalHeader.CheckSum); // same for 32-bit and 64-bit images + SetDlgItemText(hwndDlg, IDC_CHECKSUM, string->Buffer); + PhDereferenceObject(string); + + PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), CheckSumImageThreadStart, hwndDlg); + + switch (PvMappedImage.NtHeaders->OptionalHeader.Subsystem) + { + case IMAGE_SUBSYSTEM_NATIVE: + type = L"Native"; + break; + case IMAGE_SUBSYSTEM_WINDOWS_GUI: + type = L"Windows GUI"; + break; + case IMAGE_SUBSYSTEM_WINDOWS_CUI: + type = L"Windows CUI"; + break; + case IMAGE_SUBSYSTEM_OS2_CUI: + type = L"OS/2 CUI"; + break; + case IMAGE_SUBSYSTEM_POSIX_CUI: + type = L"POSIX CUI"; + break; + case IMAGE_SUBSYSTEM_WINDOWS_CE_GUI: + type = L"Windows CE CUI"; + break; + case IMAGE_SUBSYSTEM_EFI_APPLICATION: + type = L"EFI Application"; + break; + case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: + type = L"EFI Boot Service Driver"; + break; + case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: + type = L"EFI Runtime Driver"; + break; + case IMAGE_SUBSYSTEM_EFI_ROM: + type = L"EFI ROM"; + break; + case IMAGE_SUBSYSTEM_XBOX: + type = L"Xbox"; + break; + case IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION: + type = L"Windows Boot Application"; + break; + default: + type = L"Unknown"; + break; + } + + SetDlgItemText(hwndDlg, IDC_SUBSYSTEM, type); + + string = PhFormatString( + L"%u.%u", + PvMappedImage.NtHeaders->OptionalHeader.MajorSubsystemVersion, // same for 32-bit and 64-bit images + PvMappedImage.NtHeaders->OptionalHeader.MinorSubsystemVersion + ); + SetDlgItemText(hwndDlg, IDC_SUBSYSTEMVERSION, string->Buffer); + PhDereferenceObject(string); + + PhInitializeStringBuilder(&stringBuilder, 10); + + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) + PhAppendStringBuilder2(&stringBuilder, L"Executable, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL) + PhAppendStringBuilder2(&stringBuilder, L"DLL, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) + PhAppendStringBuilder2(&stringBuilder, L"Large address aware, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) + PhAppendStringBuilder2(&stringBuilder, L"Removable run from swap, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_NET_RUN_FROM_SWAP) + PhAppendStringBuilder2(&stringBuilder, L"Net run from swap, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_SYSTEM) + PhAppendStringBuilder2(&stringBuilder, L"System, "); + if (PvMappedImage.NtHeaders->FileHeader.Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY) + PhAppendStringBuilder2(&stringBuilder, L"Uni-processor only, "); + + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) + PhAppendStringBuilder2(&stringBuilder, L"High entropy VA, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) + PhAppendStringBuilder2(&stringBuilder, L"Dynamic base, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY) + PhAppendStringBuilder2(&stringBuilder, L"Force integrity check, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT) + PhAppendStringBuilder2(&stringBuilder, L"NX compatible, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_ISOLATION) + PhAppendStringBuilder2(&stringBuilder, L"No isolation, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_SEH) + PhAppendStringBuilder2(&stringBuilder, L"No SEH, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NO_BIND) + PhAppendStringBuilder2(&stringBuilder, L"Do not bind, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_APPCONTAINER) + PhAppendStringBuilder2(&stringBuilder, L"AppContainer, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_WDM_DRIVER) + PhAppendStringBuilder2(&stringBuilder, L"WDM driver, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_GUARD_CF) + PhAppendStringBuilder2(&stringBuilder, L"Control Flow Guard, "); + if (PvMappedImage.NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE) + PhAppendStringBuilder2(&stringBuilder, L"Terminal server aware, "); + + if (PhEndsWithString2(stringBuilder.String, L", ", FALSE)) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + SetDlgItemText(hwndDlg, IDC_CHARACTERISTICS, stringBuilder.String->Buffer); + PhDeleteStringBuilder(&stringBuilder); + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 80, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 80, L"VA"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 80, L"Size"); + + for (i = 0; i < PvMappedImage.NumberOfSections; i++) + { + INT lvItemIndex; + WCHAR sectionName[9]; + WCHAR pointer[PH_PTR_STR_LEN_1]; + + if (PhCopyStringZFromBytes(PvMappedImage.Sections[i].Name, + IMAGE_SIZEOF_SHORT_NAME, sectionName, 9, NULL)) + { + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, sectionName, NULL); + + PhPrintPointer(pointer, UlongToPtr(PvMappedImage.Sections[i].VirtualAddress)); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, pointer); + + PhPrintPointer(pointer, UlongToPtr(PvMappedImage.Sections[i].SizeOfRawData)); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, pointer); + } + } + } + break; + case PVM_CHECKSUM_DONE: + { + PPH_STRING string; + ULONG headerCheckSum; + ULONG realCheckSum; + + headerCheckSum = PvMappedImage.NtHeaders->OptionalHeader.CheckSum; // same for 32-bit and 64-bit images + realCheckSum = (ULONG)wParam; + + if (headerCheckSum == 0) + { + // Some executables, like .NET ones, don't have a check sum. + string = PhFormatString(L"0x0 (real 0x%Ix)", realCheckSum); + SetDlgItemText(hwndDlg, IDC_CHECKSUM, string->Buffer); + PhDereferenceObject(string); + } + else if (headerCheckSum == realCheckSum) + { + string = PhFormatString(L"0x%Ix (correct)", headerCheckSum); + SetDlgItemText(hwndDlg, IDC_CHECKSUM, string->Buffer); + PhDereferenceObject(string); + } + else + { + string = PhFormatString(L"0x%Ix (incorrect, real 0x%Ix)", headerCheckSum, realCheckSum); + SetDlgItemText(hwndDlg, IDC_CHECKSUM, string->Buffer); + PhDereferenceObject(string); + } + } + break; + case PVM_VERIFY_DONE: + { + PPH_STRING string; + + if (PvImageVerifyResult == VrTrusted) + { + if (PvImageSignerName) + { + string = PhFormatString(L"(Verified) %s", PvImageSignerName->Buffer); + SetDlgItemText(hwndDlg, IDC_COMPANYNAME_LINK, string->Buffer); + PhDereferenceObject(string); + ShowWindow(GetDlgItem(hwndDlg, IDC_COMPANYNAME), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_COMPANYNAME_LINK), SW_SHOW); + } + else + { + string = PhConcatStrings2(L"(Verified) ", PhGetStringOrEmpty(PvImageVersionInfo.CompanyName)); + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, string->Buffer); + PhDereferenceObject(string); + } + } + else if (PvImageVerifyResult != VrUnknown) + { + string = PhConcatStrings2(L"(UNVERIFIED) ", PhGetStringOrEmpty(PvImageVersionInfo.CompanyName)); + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, string->Buffer); + PhDereferenceObject(string); + } + else + { + SetDlgItemText(hwndDlg, IDC_COMPANYNAME, PvpGetStringOrNa(PvImageVersionInfo.CompanyName)); + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case NM_CLICK: + { + switch (header->idFrom) + { + case IDC_COMPANYNAME_LINK: + { + PvpVerifyFileWithAdditionalCatalog(PvFileName, PH_VERIFY_VIEW_PROPERTIES, hwndDlg, NULL); + } + break; + } + } + break; + } + + PvHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} + +VOID PvpProcessImports( + _In_ HWND ListViewHandle, + _In_ PPH_MAPPED_IMAGE_IMPORTS Imports, + _In_ BOOLEAN DelayImports + ) +{ + PH_MAPPED_IMAGE_IMPORT_DLL importDll; + PH_MAPPED_IMAGE_IMPORT_ENTRY importEntry; + ULONG i; + ULONG j; + + for (i = 0; i < Imports->NumberOfDlls; i++) + { + if (NT_SUCCESS(PhGetMappedImageImportDll(Imports, i, &importDll))) + { + for (j = 0; j < importDll.NumberOfEntries; j++) + { + if (NT_SUCCESS(PhGetMappedImageImportEntry(&importDll, j, &importEntry))) + { + INT lvItemIndex; + PPH_STRING name; + WCHAR number[PH_INT32_STR_LEN_1]; + + if (!DelayImports) + name = PhZeroExtendToUtf16(importDll.Name); + else + name = PhFormatString(L"%S (Delay)", importDll.Name); + + lvItemIndex = PhAddListViewItem(ListViewHandle, MAXINT, name->Buffer, NULL); + PhDereferenceObject(name); + + if (importEntry.Name) + { + name = PhZeroExtendToUtf16(importEntry.Name); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 1, name->Buffer); + PhDereferenceObject(name); + + PhPrintUInt32(number, importEntry.NameHint); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 2, number); + } + else + { + name = PhFormatString(L"(Ordinal %u)", importEntry.Ordinal); + PhSetListViewSubItem(ListViewHandle, lvItemIndex, 1, name->Buffer); + PhDereferenceObject(name); + } + } + } + } + } +} + +INT_PTR CALLBACK PvpPeImportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + ULONG fallbackColumns[] = { 0, 1, 2 }; + HWND lvHandle; + PH_MAPPED_IMAGE_IMPORTS imports; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 130, L"DLL"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 210, L"Name"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 50, L"Hint"); + PhSetExtendedListView(lvHandle); + ExtendedListView_AddFallbackColumns(lvHandle, 3, fallbackColumns); + + if (NT_SUCCESS(PhGetMappedImageImports(&imports, &PvMappedImage))) + { + PvpProcessImports(lvHandle, &imports, FALSE); + } + + if (NT_SUCCESS(PhGetMappedImageDelayImports(&imports, &PvMappedImage))) + { + PvpProcessImports(lvHandle, &imports, TRUE); + } + + ExtendedListView_SortItems(lvHandle); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_NOTIFY: + { + PvHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PvpPeExportsDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + HWND lvHandle; + PH_MAPPED_IMAGE_EXPORTS exports; + PH_MAPPED_IMAGE_EXPORT_ENTRY exportEntry; + PH_MAPPED_IMAGE_EXPORT_FUNCTION exportFunction; + ULONG i; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 220, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 50, L"Ordinal"); + PhAddListViewColumn(lvHandle, 2, 2, 2, LVCFMT_LEFT, 120, L"VA"); + PhSetExtendedListView(lvHandle); + + if (NT_SUCCESS(PhGetMappedImageExports(&exports, &PvMappedImage))) + { + for (i = 0; i < exports.NumberOfEntries; i++) + { + if ( + NT_SUCCESS(PhGetMappedImageExportEntry(&exports, i, &exportEntry)) && + NT_SUCCESS(PhGetMappedImageExportFunction(&exports, NULL, exportEntry.Ordinal, &exportFunction)) + ) + { + INT lvItemIndex; + PPH_STRING name; + WCHAR number[PH_INT32_STR_LEN_1]; + WCHAR pointer[PH_PTR_STR_LEN_1]; + + if (exportEntry.Name) + { + name = PhZeroExtendToUtf16(exportEntry.Name); + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, name->Buffer, NULL); + PhDereferenceObject(name); + } + else + { + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, L"(unnamed)", NULL); + } + + PhPrintUInt32(number, exportEntry.Ordinal); + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, number); + + if (!exportFunction.ForwardedName) + { + if ((ULONG_PTR)exportFunction.Function >= (ULONG_PTR)PvMappedImage.ViewBase) + PhPrintPointer(pointer, PTR_SUB_OFFSET(exportFunction.Function, PvMappedImage.ViewBase)); + else + PhPrintPointer(pointer, exportFunction.Function); + + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, pointer); + } + else + { + name = PhZeroExtendToUtf16(exportFunction.ForwardedName); + PhSetListViewSubItem(lvHandle, lvItemIndex, 2, name->Buffer); + PhDereferenceObject(name); + } + } + } + } + + ExtendedListView_SortItems(lvHandle); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_NOTIFY: + { + PvHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PvpPeLoadConfigDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PH_AUTO_POOL autoPool; + HWND lvHandle; + PIMAGE_LOAD_CONFIG_DIRECTORY32 config32; + PIMAGE_LOAD_CONFIG_DIRECTORY64 config64; + PPH_STRING string; + + lvHandle = GetDlgItem(hwndDlg, IDC_LIST); + PhSetListViewStyle(lvHandle, FALSE, TRUE); + PhSetControlTheme(lvHandle, L"explorer"); + PhAddListViewColumn(lvHandle, 0, 0, 0, LVCFMT_LEFT, 220, L"Name"); + PhAddListViewColumn(lvHandle, 1, 1, 1, LVCFMT_LEFT, 170, L"Value"); + +#define ADD_VALUE(Name, Value) \ + do { \ + INT lvItemIndex; \ + \ + lvItemIndex = PhAddListViewItem(lvHandle, MAXINT, Name, NULL); \ + PhSetListViewSubItem(lvHandle, lvItemIndex, 1, Value); \ + } while (0) + +#define ADD_VALUES(Config) \ + do { \ + { \ + LARGE_INTEGER time; \ + SYSTEMTIME systemTime; \ + \ + RtlSecondsSince1970ToTime((Config)->TimeDateStamp, &time); \ + PhLargeIntegerToLocalSystemTime(&systemTime, &time); \ + \ + string = PhFormatDateTime(&systemTime); \ + ADD_VALUE(L"Time stamp", string->Buffer); \ + PhDereferenceObject(string); \ + } \ + \ + ADD_VALUE(L"Version", PhaFormatString(L"%u.%u", (Config)->MajorVersion, (Config)->MinorVersion)->Buffer); \ + ADD_VALUE(L"Global flags to clear", PhaFormatString(L"0x%x", (Config)->GlobalFlagsClear)->Buffer); \ + ADD_VALUE(L"Global flags to set", PhaFormatString(L"0x%x", (Config)->GlobalFlagsSet)->Buffer); \ + ADD_VALUE(L"Critical section default timeout", PhaFormatUInt64((Config)->CriticalSectionDefaultTimeout, TRUE)->Buffer); \ + ADD_VALUE(L"De-commit free block threshold", PhaFormatUInt64((Config)->DeCommitFreeBlockThreshold, TRUE)->Buffer); \ + ADD_VALUE(L"De-commit total free threshold", PhaFormatUInt64((Config)->DeCommitTotalFreeThreshold, TRUE)->Buffer); \ + ADD_VALUE(L"LOCK prefix table", PhaFormatString(L"0x%Ix", (Config)->LockPrefixTable)->Buffer); \ + ADD_VALUE(L"Maximum allocation size", PhaFormatString(L"0x%Ix", (Config)->MaximumAllocationSize)->Buffer); \ + ADD_VALUE(L"Virtual memory threshold", PhaFormatString(L"0x%Ix", (Config)->VirtualMemoryThreshold)->Buffer); \ + ADD_VALUE(L"Process affinity mask", PhaFormatString(L"0x%Ix", (Config)->ProcessAffinityMask)->Buffer); \ + ADD_VALUE(L"Process heap flags", PhaFormatString(L"0x%Ix", (Config)->ProcessHeapFlags)->Buffer); \ + ADD_VALUE(L"CSD version", PhaFormatString(L"%u", (Config)->CSDVersion)->Buffer); \ + ADD_VALUE(L"Edit list", PhaFormatString(L"0x%Ix", (Config)->EditList)->Buffer); \ + ADD_VALUE(L"Security cookie", PhaFormatString(L"0x%Ix", (Config)->SecurityCookie)->Buffer); \ + ADD_VALUE(L"SEH handler table", PhaFormatString(L"0x%Ix", (Config)->SEHandlerTable)->Buffer); \ + ADD_VALUE(L"SEH handler count", PhaFormatUInt64((Config)->SEHandlerCount, TRUE)->Buffer); \ + } while (0) + + PhInitializeAutoPool(&autoPool); + + if (PvMappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + { + if (NT_SUCCESS(PhGetMappedImageLoadConfig32(&PvMappedImage, &config32))) + { + ADD_VALUES(config32); + } + } + else + { + if (NT_SUCCESS(PhGetMappedImageLoadConfig64(&PvMappedImage, &config64))) + { + ADD_VALUES(config64); + } + } + + PhDeleteAutoPool(&autoPool); + + EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); + } + break; + case WM_NOTIFY: + { + PvHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_LIST)); + } + break; + } + + return FALSE; +} + +INT_PTR CALLBACK PvpPeClrDlgProc( + _In_ HWND hwndDlg, + _In_ UINT uMsg, + _In_ WPARAM wParam, + _In_ LPARAM lParam + ) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + PH_STRING_BUILDER stringBuilder; + PPH_STRING string; + PVOID metaData; + ULONG versionStringLength; + + string = PhFormatString(L"%u.%u", PvImageCor20Header->MajorRuntimeVersion, + PvImageCor20Header->MinorRuntimeVersion); + SetDlgItemText(hwndDlg, IDC_RUNTIMEVERSION, string->Buffer); + PhDereferenceObject(string); + + PhInitializeStringBuilder(&stringBuilder, 256); + + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_ILONLY) + PhAppendStringBuilder2(&stringBuilder, L"IL only, "); + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_32BITREQUIRED) + PhAppendStringBuilder2(&stringBuilder, L"32-bit only, "); + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_32BITPREFERRED) + PhAppendStringBuilder2(&stringBuilder, L"32-bit preferred, "); + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_IL_LIBRARY) + PhAppendStringBuilder2(&stringBuilder, L"IL library, "); + + if (PvImageCor20Header->StrongNameSignature.VirtualAddress != 0 && PvImageCor20Header->StrongNameSignature.Size != 0) + { + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_STRONGNAMESIGNED) + PhAppendStringBuilder2(&stringBuilder, L"Strong-name signed, "); + else + PhAppendStringBuilder2(&stringBuilder, L"Strong-name delay signed, "); + } + + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_NATIVE_ENTRYPOINT) + PhAppendStringBuilder2(&stringBuilder, L"Native entry-point, "); + if (PvImageCor20Header->Flags & COMIMAGE_FLAGS_TRACKDEBUGDATA) + PhAppendStringBuilder2(&stringBuilder, L"Track debug data, "); + + if (PhEndsWithString2(stringBuilder.String, L", ", FALSE)) + PhRemoveEndStringBuilder(&stringBuilder, 2); + + SetDlgItemText(hwndDlg, IDC_FLAGS, stringBuilder.String->Buffer); + PhDeleteStringBuilder(&stringBuilder); + + metaData = PhMappedImageRvaToVa(&PvMappedImage, PvImageCor20Header->MetaData.VirtualAddress, NULL); + + if (metaData) + { + __try + { + PhProbeAddress(metaData, PvImageCor20Header->MetaData.Size, PvMappedImage.ViewBase, PvMappedImage.Size, 4); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + metaData = NULL; + } + } + + versionStringLength = 0; + + if (metaData) + { + // Skip 12 bytes. + // First 4 bytes contains the length of the version string. + // The version string follows. + versionStringLength = *(PULONG)((PCHAR)metaData + 12); + + // Make sure the length is valid. + if (versionStringLength >= 0x100) + versionStringLength = 0; + } + + if (versionStringLength != 0) + { + string = PhZeroExtendToUtf16Ex((PCHAR)metaData + 12 + 4, versionStringLength); + SetDlgItemText(hwndDlg, IDC_VERSIONSTRING, string->Buffer); + PhDereferenceObject(string); + } + else + { + SetDlgItemText(hwndDlg, IDC_VERSIONSTRING, L"N/A"); + } + } + break; + case WM_NOTIFY: + { + LPNMHDR header = (LPNMHDR)lParam; + + switch (header->code) + { + case PSN_QUERYINITIALFOCUS: + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)GetDlgItem(hwndDlg, IDC_RUNTIMEVERSION)); + } + return TRUE; + } + } + break; + } + + return FALSE; +} diff --git a/tools/peview/peview.manifest b/tools/peview/peview.manifest new file mode 100644 index 0000000..fbe4f8d --- /dev/null +++ b/tools/peview/peview.manifest @@ -0,0 +1,43 @@ + + + + PE Viewer + + + + + + + + + + + + + + + + + + + + + + + + true + + + \ No newline at end of file diff --git a/tools/peview/peview.rc b/tools/peview/peview.rc new file mode 100644 index 0000000..5b402a8 --- /dev/null +++ b/tools/peview/peview.rc @@ -0,0 +1,220 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" +#include "../../ProcessHacker/include/phappres.h" +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (Australia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "#include ""../../ProcessHacker/include/phappres.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PEGENERAL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END + + IDD_PEIMPORTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END + + IDD_PEEXPORTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END + + IDD_LIBEXPORTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END + + IDD_PECLR, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END + + IDD_PELOADCONFIG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 293 + TOPMARGIN, 7 + BOTTOMMARGIN, 273 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PEGENERAL DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "General" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Target machine:",IDC_STATIC,7,62,53,8 + LTEXT "Static",IDC_TARGETMACHINE,78,62,215,8 + LTEXT "Checksum:",IDC_STATIC,7,95,36,8 + LTEXT "Static",IDC_CHECKSUM,78,95,215,8 + LTEXT "Subsystem:",IDC_STATIC,7,106,38,8 + LTEXT "Subsystem version:",IDC_STATIC,7,117,64,8 + LTEXT "Characteristics:",IDC_STATIC,7,129,51,8 + LTEXT "Static",IDC_SUBSYSTEM,78,106,215,8 + LTEXT "Static",IDC_SUBSYSTEMVERSION,78,117,215,8 + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,153,286,119 + LTEXT "Sections:",IDC_STATIC,7,143,30,8 + EDITTEXT IDC_CHARACTERISTICS,76,129,217,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Time stamp:",IDC_STATIC,7,73,40,8 + LTEXT "Static",IDC_TIMESTAMP,78,73,215,8 + LTEXT "Image base:",IDC_STATIC,7,84,41,8 + LTEXT "Static",IDC_IMAGEBASE,78,84,215,8 + ICON "",IDC_FILEICON,14,18,20,20 + GROUPBOX "File",IDC_FILE,7,7,286,51 + EDITTEXT IDC_NAME,44,17,242,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + EDITTEXT IDC_COMPANYNAME,44,29,242,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Version:",IDC_STATIC,15,41,27,8 + EDITTEXT IDC_VERSION,44,41,242,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + CONTROL "Company Name Link",IDC_COMPANYNAME_LINK,"SysLink",LWS_NOPREFIX | NOT WS_VISIBLE | WS_TABSTOP,46,29,240,9 +END + +IDD_PEIMPORTS DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Imports" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,286,266 +END + +IDD_PEEXPORTS DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Exports" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,286,266 +END + +IDD_LIBEXPORTS DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Exports" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,6,6,287,267 +END + +IDD_PECLR DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "CLR" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "Runtime Version:",IDC_STATIC,7,7,55,8 + LTEXT "Static",IDC_RUNTIMEVERSION,78,7,215,8 + LTEXT "Flags:",IDC_STATIC,7,19,20,8 + EDITTEXT IDC_FLAGS,76,19,217,12,ES_AUTOHSCROLL | ES_READONLY | NOT WS_BORDER + LTEXT "Version String:",IDC_STATIC,7,31,48,8 + LTEXT "Static",IDC_VERSIONSTRING,78,31,215,8 +END + +IDD_PELOADCONFIG DIALOGEX 0, 0, 300, 280 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Load config" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,286,266 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +IDR_RT_MANIFEST RT_MANIFEST "peview.manifest" + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_PELOADCONFIG AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (Australia) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/tools/peview/peview.vcxproj b/tools/peview/peview.vcxproj new file mode 100644 index 0000000..a2ad3a1 --- /dev/null +++ b/tools/peview/peview.vcxproj @@ -0,0 +1,221 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {72C124A2-3C80-41C6-ABA1-C4948B713204} + peview + Win32Proj + 10.0.10586.0 + + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + Application + Unicode + true + v140 + + + Application + Unicode + v140 + + + + + + + + + + + + + + + + + + + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + true + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\ + $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\ + false + + + false + + + false + + + false + + + false + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN32;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + + + phlib.lib;ntdll.lib;uxtheme.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Windows + MachineX86 + 5.01 + + + + + Disabled + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN64;DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + ProgramDatabase + StdCall + true + + + phlib.lib;ntdll.lib;uxtheme.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Windows + MachineX64 + 5.02 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN32;NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + StreamingSIMDExtensions + true + + + phlib.lib;ntdll.lib;uxtheme.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Windows + true + true + MachineX86 + true + 5.01 + + + + + MaxSpeed + true + ..\..\phnt\include;..\..\phlib\include;include;%(AdditionalIncludeDirectories) + _PHLIB_;_WINDOWS;WIN64;DEBUG;%(PreprocessorDefinitions) + MultiThreaded + true + Level3 + ProgramDatabase + StdCall + true + true + + + phlib.lib;ntdll.lib;uxtheme.lib;%(AdditionalDependencies) + ..\..\phlib\bin\$(Configuration)$(PlatformArchitecture);%(AdditionalLibraryDirectories) + + + true + Windows + true + true + MachineX64 + true + 5.02 + + + + + + + + + + + + + + + + + + + {477d0215-f252-41a1-874b-f27e3ea1ed17} + false + + + + + + + + + \ No newline at end of file diff --git a/tools/peview/peview.vcxproj.filters b/tools/peview/peview.vcxproj.filters new file mode 100644 index 0000000..5dd0cf2 --- /dev/null +++ b/tools/peview/peview.vcxproj.filters @@ -0,0 +1,52 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/tools/peview/resource.h b/tools/peview/resource.h new file mode 100644 index 0000000..9507e3e --- /dev/null +++ b/tools/peview/resource.h @@ -0,0 +1,41 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by peview.rc +// +#define IDR_RT_MANIFEST 1 +#define IDD_PEGENERAL 102 +#define IDD_PEIMPORTS 103 +#define IDD_PEEXPORTS 104 +#define IDD_LIBEXPORTS 105 +#define IDD_PECLR 106 +#define IDD_PEEXPORTS1 107 +#define IDD_PELOADCONFIG 107 +#define IDC_TARGETMACHINE 1003 +#define IDC_CHECKSUM 1004 +#define IDC_SUBSYSTEM 1005 +#define IDC_SUBSYSTEMVERSION 1006 +#define IDC_CHARACTERISTICS 1007 +#define IDC_LIST 1008 +#define IDC_FILEICON 1009 +#define IDC_TIMESTAMP 1010 +#define IDC_RUNTIMEVERSION 1011 +#define IDC_FILE 1011 +#define IDC_FLAGS 1012 +#define IDC_COMPANYNAME 1012 +#define IDC_EDIT1 1013 +#define IDC_VERSION 1013 +#define IDC_VERSIONSTRING 1014 +#define IDC_IMAGEBASE 1015 +#define IDC_NAME 1044 +#define IDC_COMPANYNAME_LINK 1279 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1016 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/tools/peview/version.rc b/tools/peview/version.rc new file mode 100644 index 0000000..1e412bb --- /dev/null +++ b/tools/peview/version.rc @@ -0,0 +1,35 @@ +#include "winres.h" +#include "../../ProcessHacker/include/phappres.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION PHAPP_VERSION_NUMBER + PRODUCTVERSION PHAPP_VERSION_NUMBER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0904b0" + BEGIN + VALUE "CompanyName", "wj32" + VALUE "FileDescription", "PE Viewer" + VALUE "FileVersion", PHAPP_VERSION_STRING + VALUE "InternalName", "peview" + VALUE "LegalCopyright", "Licensed under the GNU GPL, v3." + VALUE "OriginalFilename", "peview.exe" + VALUE "ProductName", "Process Hacker" + VALUE "ProductVersion", PHAPP_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc09, 1200 + END +END